Docker Compose नेटवर्किंग कैसे काम करती है?
Docker Compose नेटवर्किंग के बुनियादी सिद्धांतों को समझें। इसमें डिफ़ॉल्ट ब्रिज नेटवर्क, सर्विस नाम से DNS, होस्ट मोड और UFW को बायपास करने वाले पब्लिश्ड पोर्ट्स की जानकारी दी गई है।
Compose आपके ऐप के शुरू होने से पहले क्या बनाता है
Docker Compose नेटवर्किंग एक नियम से शुरू होती है: docker compose up प्रोजेक्ट के लिए एक निजी नेटवर्क बनाता है, हर सर्विस को उससे जोड़ता है, और उन सर्विसेज को सर्विस नाम के जरिए एक-दूसरे तक पहुँचने की अनुमति देता है। इसे पाने के लिए आपको एक भी networks: लाइन लिखने की आवश्यकता नहीं है। Compose नेटवर्किंग को लेकर अधिकांश भ्रम इस बात से पैदा होता है कि यह डिफ़ॉल्ट रूप से पहले से मौजूद है।
यहाँ एक छोटी फ़ाइल है। इसे shop नामक डायरेक्टरी में compose.yaml के रूप में सेव करें।
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: exampleइसे शुरू करें और देखें कि Docker ने क्या बनाया है:
docker compose up -d
docker network lsइस सूची में अब shop_default नामक एक नेटवर्क है। Compose इसे <project>_default नाम देता है, और प्रोजेक्ट का नाम डिफ़ॉल्ट रूप से डायरेक्टरी के लोअरकेस नाम पर आधारित होता है। इसे docker compose -p myproject up -d के साथ या फ़ाइल में टॉप-लेवल name: myproject के साथ बदलें। इसका ड्राइवर bridge है, जो होस्ट के अंदर एक वर्चुअल स्विच है। प्रत्येक कंटेनर को एक निजी सबनेट पर एक एड्रेस मिलता है, और बाहर जाने वाला ट्रैफ़िक बाहर निकलते समय होस्ट के एड्रेस में अनुवादित (translate) हो जाता है।
docker compose down उस नेटवर्क को फिर से हटा देता है। यही कारण है कि किसी पुराने प्रोजेक्ट का पुराना कंटेनर नेटवर्क को खुला रख सकता है: Docker error while removing network: network shop_default has active endpoints के साथ मना कर देता है, और इसका समाधान उस कंटेनर को रोकना या हटाना है जो अभी भी उससे जुड़ा हुआ है।
यदि आप Compose में नए हैं, तो Compose फ़ाइल लेआउट और लाइफ़साइकिल कमांड्स को पहले पढ़ना उचित है, क्योंकि नीचे दी गई हर चीज़ यह मानकर चलती है कि आप किसी प्रोजेक्ट को शुरू और बंद कर सकते हैं।
सर्विस नाम द्वारा DNS वह हिस्सा है जिसे शुरुआती लोग भूल जाते हैं
किसी भी यूजर-डिफाइंड नेटवर्क पर, Docker एक एम्बेडेड DNS सर्वर चलाता है जिसे प्रत्येक कंटेनर 127.0.0.11 पर देखता है। यह सर्विस नामों को वर्तमान कंटेनर पतों में रिज़ॉल्व करता है। इसलिए web बिना किसी कॉन्फ़िगरेशन के, 5432 पोर्ट पर, db होस्टनेम पर डेटाबेस तक पहुँच जाता है।
docker compose exec web getent hosts dbयह 172.18.0.2 db जैसी एक लाइन प्रिंट करता है। यदि यह कुछ भी प्रिंट नहीं करता है, तो दोनों सर्विस एक ही नेटवर्क पर नहीं हैं।
वह गलती जो लगभग हर कोई एक बार करता है, वह है एप्लिकेशन कॉन्फ़िगरेशन में localhost का उपयोग करना। एक कंटेनर के अंदर, localhost वह कंटेनर स्वयं है, न कि होस्ट और न ही दूसरी सर्विस। Postgres क्लाइंट इसे स्पष्ट रूप से रिपोर्ट करते हैं:
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP connections on port 5432?कनेक्शन स्ट्रिंग postgresql://postgres:example@db:5432/postgres होनी चाहिए। होस्ट वाला हिस्सा सर्विस का नाम है।
दो विवरण जो बाद में समय बचाते हैं। नाम उस चीज़ पर रिज़ॉल्व होते हैं जो अभी चल रही है, इसलिए docker compose up -d --scale web=3 तीन पतों के साथ एक नाम देता है, और जो क्लाइंट DNS को हमेशा के लिए कैश (cache) कर लेता है, वह खुद को एक मृत कंटेनर से जोड़ लेगा। और बिना --network वाले साधारण docker run द्वारा उपयोग किए जाने वाले लेगेसी bridge नेटवर्क में कोई नाम रिज़ॉल्यूशन नहीं होता है, यही कारण है कि 2016 के कंटेनर लिंक्स के बारे में दी गई सलाह आपके द्वारा देखे जा रहे परिणामों से मेल नहीं खाती है।
दो सेवाओं को कनेक्ट करने के लिए आपको ports: की आवश्यकता नहीं है
ports: होस्ट पर एक कंटेनर पोर्ट को पब्लिश करता है। यह Docker के बाहर से आने वाले ट्रैफ़िक के लिए है। इसका सर्विस-टू-सर्विस ट्रैफ़िक से कोई लेना-देना नहीं है, जो प्रोजेक्ट नेटवर्क पर पूरे पोर्ट रेंज में पहले से ही काम करता है।
इसलिए, जो ports: - "5432:5432" बहुत से लोग अपनी डेटाबेस सर्विस में जोड़ते हैं, वह कोई लाभ नहीं देता और वास्तव में नुकसान पहुँचाता है: यह Postgres को सर्वर के पब्लिक इंटरफ़ेस पर एक्सपोज़ कर देता है। इसे हटा दें। यदि आप चाहते हैं कि यह माइग्रेशन के लिए आपके लैपटॉप से एक्सेस हो सके, तो इसे "127.0.0.1:5432:5432" के साथ लूपबैक पर बाइंड करें और इसे SSH टनल के माध्यम से एक्सेस करें। लिसनिंग सॉकेट, पब्लिश पोर्ट और फ़ायरवॉल नियम के बीच का अंतर Linux पर पोर्ट और लिसनिंग सर्विस कैसे काम करते हैं में समझाया गया है।
expose: Compose के अंतर्गत केवल दस्तावेज़ीकरण है। यह कुछ भी ओपन नहीं करता है, क्योंकि एक ही नेटवर्क पर कंटेनरों के बीच कुछ भी बंद नहीं था।
कब network_mode host का उपयोग करना सही है और इसकी क्या कीमत चुकानी पड़ती है
Host मोड कंटेनर के अपने नेटवर्क नेमस्पेस को हटा देता है और प्रोसेस को सीधे होस्ट के इंटरफेस का उपयोग करने देता है।
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityइसे चाहने के वास्तविक कारण हैं। एक प्रोसेस जिसे लोकल नेटवर्क पर ब्रॉडकास्ट या मल्टीकास्ट ट्रैफिक देखने की आवश्यकता होती है, जैसे कि मीडिया सर्वर या होम ऑटोमेशन हब के लिए डिवाइस डिस्कवरी, वह ब्रिज के पीछे से इसे नहीं देख सकती है, क्योंकि ब्रिज उस ट्रैफिक को कंटेनर तक फॉरवर्ड नहीं करता है। एक मॉनिटरिंग एजेंट जो होस्ट के इंटरफेस काउंटर को पढ़ता है, उसे होस्ट के इंटरफेस की आवश्यकता होती है। और आप एड्रेस ट्रांसलेशन हॉप को छोड़ देते हैं, जो उच्च पैकेट दरों पर मायने रखता है।
इसकी लागत विशिष्ट है।
ports: काम करना बंद कर देता है। Docker चेतावनी देता है कि host नेटवर्क मोड का उपयोग करते समय पब्लिश किए गए पोर्ट्स को हटा दिया जाता है, और कंटेनर वही बाइंड करता है जो उसकी प्रोसेस बाइंड करती है। दो host मोड कंटेनर जो पोर्ट 8080 चाहते हैं, वे आपस में टकराते हैं, और दूसरा कंटेनर bind: address already in use के साथ बंद हो जाता है।
सर्विस नाम द्वारा नेम रिज़ॉल्यूशन दोनों दिशाओं में समाप्त हो जाता है। कंटेनर प्रोजेक्ट नेटवर्क पर नहीं है, इसलिए यह db को रिज़ॉल्व नहीं कर सकता है, और अन्य सर्विसेज इसे रिज़ॉल्व नहीं कर सकती हैं। यह उन तक केवल होस्ट पर पब्लिश किए गए पोर्ट्स के माध्यम से पहुँचता है, आमतौर पर 127.0.0.1 पर।
आइसोलेशन समाप्त हो जाता है। एक प्रोसेस जो host मोड कंटेनर के अंदर 0.0.0.0 को बाइंड करती है, वह आपके सर्वर के हर इंटरफेस पर लिसन कर रही होती है, जिसमें पब्लिक इंटरफेस भी शामिल है, बिल्कुल वैसे ही जैसे apt के साथ इंस्टॉल किया गया कोई पैकेज। इसका एक फायदा यह है: यह ट्रैफिक सामान्य इनपुट पाथ का पालन करता है, इसलिए UFW नियम इस पर लागू होते हैं, जो पब्लिश किए गए पोर्ट्स के मामले में सच नहीं है।
Host मोड एक Linux Docker Engine फीचर है। Docker Desktop इसे केवल 4.34 वर्जन से आगे और इसे इनेबल करने के बाद ही सपोर्ट करता है, जिसमें यह अतिरिक्त सीमा है कि कंटेनर होस्ट IP एड्रेस को बाइंड नहीं कर सकते और केवल TCP और UDP ही हैंडल किए जाते हैं। यदि आपकी टीम के आधे लोग Linux सर्वर पर हैं और आधे Docker Desktop पर, तो एक ही फाइल के अलग-अलग व्यवहार करने की उम्मीद रखें।
जब आपको होस्ट के इंटरफेस की आवश्यकता हो, तभी host मोड का उपयोग करें। किसी कनेक्शन समस्या को ठीक करने के लिए इसका उपयोग न करें, क्योंकि यह आमतौर पर एक समस्या को दूसरी अधिक कठिन समस्या से बदल देता है।
दो Compose प्रोजेक्ट्स को एक बाहरी नेटवर्क से जोड़ना
एक प्रोजेक्ट द्वारा बनाया गया नेटवर्क दूसरे को दिखाई नहीं देता है। यही कारण है कि proxy/compose.yaml में मौजूद रिवर्स प्रॉक्सी app/compose.yaml में मौजूद ऐप को नहीं देख पाता, भले ही वे एक ही सर्वर पर हों। इसका समाधान एक ऐसा नेटवर्क है जिसका स्वामित्व किसी भी प्रोजेक्ट के पास न हो।
इसे एक बार मैन्युअल रूप से बनाएँ:
docker network create edgeफिर प्रत्येक प्रोजेक्ट में इसे external के रूप में घोषित करें। प्रॉक्सी साइड:
services:
proxy:
image: traefik:v3.1
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- edge
networks:
edge:
name: edge
external: trueएप्लीकेशन साइड:
services:
app:
image: nginx:1.27
networks:
- edge
- internal
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- internal
networks:
edge:
name: edge
external: true
internal:external: true, Compose को नया नेटवर्क बनाने के बजाय मौजूदा नेटवर्क से जुड़ने और docker compose down पर इसे यथावत छोड़ने का निर्देश देता है। अलग name: कुंजी दिखने से कहीं अधिक महत्वपूर्ण है: इसके बिना Compose ठीक edge नाम के नेटवर्क की तलाश करता है, और इसके साथ आप अपनी फ़ाइल में नेटवर्क को कुछ और और होस्ट पर कुछ और नाम दे सकते हैं।
यदि नेटवर्क मौजूद नहीं है, तो Compose शुरू होने से मना कर देता है और रिपोर्ट करता है कि नेटवर्क को external के रूप में घोषित किया गया था लेकिन वह मिल नहीं सका। इसे पहले बनाएँ।
ध्यान दें कि एप्लीकेशन फ़ाइल internal के साथ क्या करती है। डेटाबेस केवल उस प्रोजेक्ट-लोकल नेटवर्क पर स्थित होता है, इसलिए प्रॉक्सी उस तक नहीं पहुँच सकता और केवल app ही पहुँच सकता है। नेटवर्क के अंतर्गत internal: true जोड़ने से यह और आगे बढ़ जाता है और बाहरी दुनिया के लिए इसके रूट को पूरी तरह से हटा देता है। यह डेटाबेस के लिए एक अच्छा डिफ़ॉल्ट है, लेकिन इसे सेट करने से पहले एक लागत के बारे में जानना आवश्यक है: इंटरनल नेटवर्क पर मौजूद कंटेनर कुछ भी डाउनलोड नहीं कर सकता है, इसलिए स्टार्टअप पर apt-get update या pip install चलाने वाला एंट्रीपॉइंट हैंग हो जाएगा और फिर टाइमआउट के साथ विफल हो जाएगा।
राउटिंग नियमों और प्रमाणपत्रों के साथ पूर्ण सेटअप के लिए, एक Traefik इंस्टेंस के पीछे कई ऐप्स चलाना देखें।
पब्लिश किए गए पोर्ट UFW को बायपास करते हैं
Compose नेटवर्किंग का यह हिस्सा सुरक्षा संबंधी घटना का कारण बनता है। आप एक पोर्ट पब्लिश करते हैं, आप जांचते हैं कि UFW सक्रिय है और SSH को छोड़कर सब कुछ अस्वीकार कर रहा है, फिर भी सेवा इंटरनेट से पहुंच योग्य रहती है।
sudo ufw status
curl http://203.0.113.10:8080UFW कहता है कि पोर्ट ब्लॉक है। curl फिर भी पेज दिखाता है। कुछ भी टूटा नहीं है। Docker अपने स्वयं के एड्रेस ट्रांसलेशन और फॉरवर्डिंग नियमों को सीधे iptables में लिखता है। पब्लिश किए गए कंटेनर पोर्ट पर आने वाला ट्रैफ़िक होस्ट तक पहुँचने के बजाय सीधे कंटेनर को फॉरवर्ड कर दिया जाता है। इसलिए, यह उस चेन से कभी नहीं गुजरता जिसे UFW स्थानीय ट्रैफ़िक के लिए प्रबंधित करता है। Docker के नियम UFW के नियमों से पहले मैच होते हैं।
इसका संक्षिप्त समाधान यह है कि पोर्ट को केवल वहीं पब्लिश करें जहाँ आपको इसकी आवश्यकता हो:
ports:
- "127.0.0.1:8080:80"यह होस्ट साइड को loopback से बाइंड करता है। इसलिए, पोर्ट केवल सर्वर से, SSH टनल के माध्यम से, या कहीं और से नहीं पहुँचा जा सकता है। सार्वजनिक एंट्री पॉइंट को एक रिवर्स प्रॉक्सी के पीछे रखें जो 80 और 443 पोर्ट को जानबूझकर पब्लिश करता है। पूर्ण स्पष्टीकरण, जिसमें उन मामलों के लिए DOCKER-USER चेन शामिल है जहाँ आपको पब्लिश किए गए पोर्ट को फ़िल्टर करना आवश्यक है, Docker UFW को बायपास करके पोर्ट क्यों पब्लिश करता है और इसे कैसे ठीक करें में उपलब्ध है।
इसे चार कमांड में डीबग कैसे करें
सबसे पहले यह जांचें कि प्रत्येक कंटेनर वास्तव में किस नेटवर्क पर है:
docker network inspect shop_defaultContainers ब्लॉक प्रत्येक जुड़े हुए कंटेनर को उसके पते के साथ सूचीबद्ध करता है। यदि कोई सर्विस उस सूची में नहीं है, तो वह किसी अन्य नेटवर्क पर है, होस्ट मोड में है, या चल नहीं रही है।
उसी नेटवर्क से जुड़े एक अस्थायी कंटेनर से नेम रिज़ॉल्यूशन का परीक्षण करें, ताकि आपको अपनी इमेज के अंदर किसी टूलिंग की आवश्यकता न पड़े:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup का विफल होना नेम रिज़ॉल्यूशन या नेटवर्क सदस्यता में समस्या की ओर इशारा करता है। यदि nslookup सफल होता है लेकिन nc विफल हो जाता है, तो इसका मतलब है कि सर्विस चल रही है लेकिन उस पोर्ट पर लिसन नहीं कर रही है, या वह अपने कंटेनर के अंदर 127.0.0.1 के बजाय 0.0.0.0 पर लिसन कर रही है। यह समस्या डेवलपमेंट सर्वर्स के साथ आम है, और इसका समाधान एप्लिकेशन के बाइंड एड्रेस में है, न कि Docker में।
एक और विफलता जो Docker बग जैसी दिखती है। यदि कंटेनर एक-दूसरे से बात कर सकते हैं लेकिन आपके ऑफिस या VPN नेटवर्क पर किसी मशीन तक नहीं पहुँच सकते हैं, तो संभवतः Docker सबनेट उस नेटवर्क के साथ ओवरलैप हो रहा है। Docker डिफ़ॉल्ट रूप से 172.17.0.0/16 से ऊपर के पते आवंटित करता है। /etc/docker/daemon.json में पूल को बदलें:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}इसके बाद sudo systemctl restart docker चलाएं और प्रभावित नेटवर्क्स को फिर से बनाएं, क्योंकि एक मौजूदा नेटवर्क उसी सबनेट को बनाए रखता है जिसके साथ उसे बनाया गया था।
FAQ
मेरे कंटेनर सर्विस नाम के माध्यम से एक-दूसरे तक क्यों नहीं पहुँच पा रहे हैं?
वे एक ही नेटवर्क पर नहीं हैं। Compose स्वचालित रूप से हर सर्विस को <project>_default पर डाल देता है, लेकिन जैसे ही आप किसी सर्विस में networks: सूची जोड़ते हैं, वह सूची उसके लिए नेटवर्क का पूरा सेट बन जाती है और डिफ़ॉल्ट नेटवर्क अब लागू नहीं होता है। docker network inspect <network> चलाएँ और जाँचें कि क्या दोनों कंटेनर Containers ब्लॉक में दिखाई देते हैं। यह भी जाँचें कि कोई भी सर्विस network_mode: host का उपयोग न कर रही हो, क्योंकि host मोड वाला कंटेनर किसी Docker नेटवर्क पर नहीं होता है और सर्विस नामों को रिज़ॉल्व नहीं कर सकता है।
क्या एक सर्विस को दूसरी सर्विस तक पहुँचने के लिए पोर्ट पब्लिश करने की आवश्यकता है?
नहीं। Compose नेटवर्क पर, हर कंटेनर का हर पोर्ट उस नेटवर्क के अन्य कंटेनरों द्वारा एक्सेस किया जा सकता है। ports: केवल कंटेनर को Docker के बाहर से आने वाले ट्रैफ़िक के लिए एक्सपोज़ करने के लिए मौजूद है, और expose: केवल दस्तावेज़ीकरण के लिए है। डेटाबेस पोर्ट को पब्लिश करना एक सामान्य और महंगी आदत है, क्योंकि यह डेटाबेस को आपके सर्वर के पब्लिक इंटरफ़ेस पर डाल देता है।
bridge और host नेटवर्किंग में क्या अंतर है?
Bridge कंटेनर को उसका अपना नेटवर्क नेमस्पेस और वर्चुअल स्विच पर एक पता देता है, जिसमें कंटेनरों के बीच स्वचालित नाम रिज़ॉल्यूशन और अनुवादित आउटबाउंड ट्रैफ़िक होता है। Host कंटेनर को सीधे होस्ट का नेटवर्क स्टैक देता है: कोई अलग पता नहीं, सर्विस नाम द्वारा कोई रिज़ॉल्यूशन नहीं, कोई पोर्ट पब्लिशिंग नहीं, और होस्ट के अन्य लिसनर्स से कोई अलगाव नहीं। Bridge डिफ़ॉल्ट है और सही विकल्प है जब तक कि प्रोसेस को होस्ट के इंटरफ़ेस की आवश्यकता न हो।
मैं दो अलग-अलग Compose फ़ाइलों से कंटेनरों को कैसे कनेक्ट करूँ?
docker network create edge के साथ एक साझा नेटवर्क बनाएँ, फिर दोनों फ़ाइलों में external: true के साथ इसे घोषित करें और उन सर्विस को अटैच करें जिन्हें बात करने की आवश्यकता है। Compose इसे न तो बनाएगा और न ही हटाएगा। यदि आप बनाने वाला चरण छोड़ देते हैं, तो Compose स्टार्ट होने से मना कर देगा और रिपोर्ट करेगा कि नेटवर्क को external के रूप में घोषित किया गया है लेकिन वह मिला नहीं।
जब UFW पोर्ट को ब्लॉक करता है, तो मेरा कंटेनर इंटरनेट से क्यों पहुँच योग्य है?
क्योंकि पब्लिश किए गए पोर्ट को उन फ़ॉरवर्डिंग नियमों द्वारा नियंत्रित किया जाता है जिन्हें Docker iptables में जोड़ता है। वे नियम UFW के नियमों से पहले मैच होते हैं, और फ़ॉरवर्ड किया गया ट्रैफ़िक वैसे भी उस चेन से नहीं गुज़रता जिसे UFW फ़िल्टर करता है। होस्ट साइड को "127.0.0.1:8080:80" के साथ loopback पर बाइंड करें और किसी भी पब्लिक चीज़ को पोर्ट 80 और 443 पर रिवर्स प्रॉक्सी के पीछे रखें।