मुझे लगा कि मैं Containers समझता हूँ। फिर मैंने एक बनाने की कोशिश की।

मुझे लगा कि मैं Containers समझता हूँ। फिर मैंने एक बनाने की कोशिश की।

स्क्रैच से निर्माण करने पर कैसे वह उजागर होता है जिसे पाठ्यपुस्तकें छोड़ देती हैं

जानने और निर्माण करने के बीच का अंतर

आप Docker परीक्षा में टॉप कर सकते हैं, सही शब्द—namespaces, cgroups, images, layers, PID 1, Kubernetes Pods—बिना रुके बोल सकते हैं—और फिर भी जब आप वास्तव में एक कंटेनर बनाने की कोशिश करते हैं तो आपको कोई अंदाज़ा नहीं होता कि आप क्या कर रहे हैं। यह उस डेवलपर का सबक है जिसने हाल ही में DEV Community पर अपनी कहानी साझा की है, और यह इस बात की याद दिलाता है कि सिद्धांत और व्यावहारिक प्रयोग अलग-अलग दुनिया में रहते हैं। यह अभी इसलिए मायने रखता है क्योंकि containerization हर जगह है—Docker, Kubernetes, और हज़ारों डिप्लॉयमेंट सिस्टम उन अवधारणाओं पर टिके हैं जो तब तक अमूर्त महसूस होती हैं जब तक कि आप वास्तविक समय में उनसे टकरा न जाएँ।

पहला कमांड: एक विनम्र शुरुआत

इसकी शुरुआत सरल थी: का उपयोग करके एक नए namespace में एक प्रोसेस चलाने की कोशिश करें unshare कमांड। पहला प्रयास था:

bash sudo unshare -p 1 test

Error: unshare: failed to execute 1: No such file or directory

फ्लैग गलत थे। कमांड ने सिस्टम से "1" नामक प्रोग्राम को execute करने के लिए कहा, जो मौजूद नहीं है। कुछ भी बनाने से पहले, वास्तविक समस्याओं तक पहुँचने से भी पहले, कीबोर्ड और डॉक्यूमेंटेशन के पास इस बारे में अलग-अलग विचार थे कि क्या होना चाहिए था।

भाग 1: Namespaces और PID 1

पहला वास्तविक प्रयास एक नए PID (process ID) namespace में एक प्रोसेस चलाने और यह साबित करने का था कि वह स्वयं को PID 1 के रूप में देखता है—process tree का root। इसलिए यह कमांड चलाया गया:

bash sudo unshare --pid bash

फिर उस shell के अंदर:

bash echo $$

अपेक्षित आउटपुट: 1. वास्तविक आउटपुट: 25184.

यह काम नहीं किया। वह PID 1 नहीं था; यह होस्ट से केवल पैरेंट प्रोसेस ID था। नियम सरल था लेकिन सहज ज्ञान के विपरीत था: PID namespaces चाइल्ड प्रोसेस पर लागू होते हैं, न कि उस प्रोसेस पर जो कॉल कर रही है unshare। आपको fork करने की आवश्यकता है। नए namespace में पैदा हुआ पहला चाइल्ड PID 1 बन जाता है।

इसलिए कार्य करने वाला संस्करण था:

bash sudo unshare --pid --fork bash echo $$

अब आउटपुट 1 था। Shell ने सोचा कि यह process tree का root है। अंदर से सब कुछ अलग महसूस हुआ।

फिर अगला आश्चर्य आया। चलाने पर ps अंदर से दिखाया गया:

PID PPID COMMAND 25310 25304 bash 25344 25310 ps

लेकिन shell ने कहा कि यह PID 1 था। इसका कोई मतलब नहीं बनता था। रहस्योद्घाटन: ps कर्नेल से विशुद्ध "कौन सी प्रोसेस मौजूद हैं?" प्रश्न नहीं पूछता है। यह फ़ाइलों को पढ़ता है। यदि /proc अभी भी होस्ट के प्रोसेस फ़ाइलसिस्टम की ओर इंगित करता है, तो आपके टूल आपसे झूठ बोलेंगे। वे होस्ट की नंबरिंग योजना दिखाएंगे।

इसका समाधान फिर से माउंट करना था /proc namespace के अंदर से:

bash mount -t proc proc /proc ps -o pid,ppid,comm

अब इसने दिखाया:

PID PPID COMMAND 1 0 bash 7 1 ps

वह क्षण था जब यह समझ में आया। Namespace ने वास्तविक अलगाव प्रदान किया, लेकिन फ़ाइलसिस्टम दृश्य बदलने तक टूल इसे नहीं देख सके। अलगाव और दृश्यता अलग-अलग चीज़ें हैं।

UTS namespace—जो होस्टनाम को नियंत्रित करता है—समझने में अधिक स्पष्ट था। चलाने पर hostname होस्ट पर एक नाम दिखाया। एक नए UTS namespace के अंदर (के साथ बनाया गया sudo unshare --uts bash), इसे बदलने पर कुछ अलग दिखाई दिया। होस्ट पर वापस आने पर, यह मूल रूप में वापस आ गया। एक मशीन, एक kernel, तीन अलग-अलग दृश्य।

भाग 2: फ़ाइलसिस्टम हैंडऑफ़

Namespaces के बाद, अगले संस्करण को प्रोसेस को अपना स्वयं का फ़ाइलसिस्टम देना था: BusyBox और एक shell के साथ एक rootfs (root filesystem)। बहुत कुछ container जैसा।

पहला एरर सीधा था:

exec /bin/sh: no such file or directory

Shell वहाँ नहीं था जहाँ स्क्रिप्ट ने कहा था कि वह होगा। इसे ठीक कर दिया गया। लेकिन फिर:

./rootfs/bin/busybox: cannot execute: required file not found

यह एरर क्रूर है क्योंकि फ़ाइल वहीं मौजूद है। आप इसे सूचीबद्ध कर सकते हैं। आप इसे देख सकते हैं। कर्नेल अभी भी इसे चलाने से इंकार करता है। का उपयोग करने पर file कमांड ने सच्चाई को उजागर किया:

ELF 64-bit LSB pie executable, ARM aarch64, dynamically linked, interpreter /lib/ld-musl-aarch64.so.1, stripped

बाइनरी वहाँ थी। इसे जिस इंटरप्रेटर की आवश्यकता थी—डायनेमिक लिंकर—वह पुरानी दुनिया से उपलब्ध नहीं था। Linux यह नहीं कह रहा था कि फ़ाइल मौजूद नहीं है। यह कह रहा था "यहाँ से, मैं उस इंटरप्रेटर को लोड नहीं कर सकता जिसकी इस ELF को आवश्यकता है।"

समाधान BusyBox को स्टैटिक बनाना नहीं था। यह Alpine को नया root फ़ाइलसिस्टम बनाना था ताकि इंटरप्रेटर सही पाथ पर हो। लेकिन सबसे पहले, ट्रांज़िशन को एक ब्रिज की आवश्यकता थी—एक ऐसा टूल जो फ़ाइलसिस्टम स्विच से पहले और उसके दौरान चल सके। BusyBox दो रूपों में आया: Alpine के अंदर के लिए एक डायनेमिक, और हैंडऑफ़ के लिए एक स्टैटिक:

bash /bin/busybox pivot_root . put_old

स्टैटिक BusyBox वह कुंजी थी जो निष्पादित कर सकती थी pivot_root इससे पहले कि सिस्टम पूरी तरह से दुनिया बदले।

Alpine के नया root बनने के बाद, और भी आश्चर्यजनक बातें सामने आईं। Bash को अभी भी पुराने फ़ाइलसिस्टम से कमांड पाथ याद थे। जब उसने चलाने की कोशिश की mount, तो उसने में देखा /usr/bin/mount एक ऐसी दुनिया में जिसे अभी-अभी निकाला गया था:

bash: /usr/bin/mount: No such file or directory

इसका समाधान था hash -r, जो कमांड कैश को साफ़ करता है। Bash ने पुरानी दुनिया में एक निर्णय लिया था और इसे जाने नहीं दे पा रहा था।

भाग 3: Mac की जटिलता

यह सेटअप कोई सामान्य Linux लैपटॉप नहीं था। यह Apple Silicon Mac → privileged Ubuntu container → macOS से माउंट किया गया repo था। इसका मतलब था कि virtiofs (वर्चुअलाइजेशन के लिए एक फ़ाइलसिस्टम पासथ्रू) शामिल था, चाहे कोई इसे चाहता हो या नहीं।

Alpine के अंदर, जो अब root फ़ाइलसिस्टम था, symlinks के माध्यम से निष्पादित करना Mac-शेयर किए गए माउंट पर "Permission denied" के साथ विफल हो सकता था, जबकि सीधे BusyBox को कॉल करने पर काम करता था:

bash ls

sh: ls: Permission denied

/bin/busybox ls

(काम करता है)

फ़ाइलें वहीं थीं। उन symlinks के माध्यम से निष्पादित करना अजीब हिस्सा था। समाधान उबाऊ और सही था: rootfs को एक कंटेनर-नेटिव पाथ पर ले जाएँ और वहाँ से प्रयास करें। Mac-शेयर किए गए माउंट को सामान्य Linux की तरह व्यवहार करने के लिए मजबूर न करें।

भाग 4: pivot_root के अपने विचार हैं

इस सब के बाद भी, pivot_root ने खुद सिखाना बंद नहीं किया था। एरर:

pivot_root: invalid argument

नया root एक माउंट पॉइंट होना चाहिए था। पुराने root को जाने के लिए कहीं जगह की आवश्यकता थी। इसलिए अनुष्ठान था:

  1. नए root को उसी पर बाइंड-माउंट (Bind-mount) करें
  2. एक बनाएं oldroot डायरेक्टरी
  3. कॉल करें pivot_root(newroot, oldroot)
  4. डायरेक्टरी को नए root पर बदलें
  5. पुराने root को अनमाउंट करें

जब यह अंततः काम कर गया, तो इनाम छोटा और सटीक था:

bash cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.24.1

बस एक टेक्स्ट फ़ाइल। लेकिन अब यह इस बात का प्रमाण था कि कंटेनर काम कर रहा था।

निष्कर्ष

स्क्रैच से एक कंटेनर बनाना वह सिखाता है जो कोई कोर्स कभी नहीं सिखा सकता: सिद्धांत को जानने और उसे वास्तविक समय में विफल होते देखने के बीच की दूरी। Namespaces, cgroups, और बाकी सब वास्तविक हैं। वे बिल्कुल वैसे ही काम करते हैं जैसा कि डॉक्यूमेंटेशन में वर्णित है। लेकिन "मैं इसे समझता हूँ" से "मैं इसे काम करा सकता हूँ" तक का रास्ता एरर मैसेज, Bash कैश, फ़ाइलसिस्टम अनुमतियों और इस अहसास से होकर गुजरता है कि यदि फ़ाइलसिस्टम सही नहीं है तो आपके टूल आपसे झूठ बोलेंगे।

गुण

  • व्यावहारिक (Hands-on) शिक्षा समझ को इस तरह से स्थापित करती है जो केवल अध्ययन से संभव नहीं है
  • वास्तविक त्रुटियाँ उन बाधाओं को सिखाती हैं जिन्हें डॉक्यूमेंटेशन अक्सर छोड़ देता है
  • स्क्रैच से निर्माण करने पर यह उजागर होता है कि आधुनिक कंटेनर टूल जटिलता को कैसे छिपाते हैं
  • Namespaces, फ़ाइलसिस्टम, और इंटरप्रेटर को समझना Docker और Kubernetes को कम रहस्यमय बनाता है

कमियाँ

  • सीखने का दौर कठिन (steep) है और इसमें कई छोटी, भ्रमित करने वाली त्रुटियाँ शामिल हैं
  • पर्यावरणीय कारक (जैसे macOS पर virtiofs) अप्रत्याशित जटिलताएँ जोड़ते हैं
  • सीधे Docker का उपयोग करने की तुलना में यह प्रक्रिया धीमी है
  • डॉक्यूमेंटेशन से कई एज़ केस (Bash hash caching, symlink permissions) स्पष्ट नहीं हैं

सावधानी

यह लेख शैक्षणिक है और मूल सिद्धांतों से कंटेनर बनाने के वास्तविक अनुभव का वर्णन करता है। दिखाए गए कमांड और अवधारणाएं मूल सामग्री के अनुसार सटीक हैं। यह प्रोडक्शन-ग्रेड कंटेनर रनटाइम नहीं है; यह यह समझने के लिए एक शिक्षण उपकरण है कि कंटेनर कैसे काम करते हैं। प्रोडक्शन में किसी भी कंटेनर सिस्टम पर भरोसा करने से पहले, आधिकारिक डॉक्यूमेंटेशन और सर्वोत्तम प्रथाओं (best practices) से परामर्श लें। अपने परिवेश में कुछ भी लागू करने से पहले मूल स्रोत से सभी दावों को सत्यापित करें।

अक्सर पूछे जाने वाले प्रश्न

  • PID namespace क्या है और यह कंटेनरों में क्यों मायने रखता है?
  • unshare को कॉल करने वाली प्रोसेस स्वचालित रूप से PID 1 क्यों नहीं बन जाती?
  • /proc फ़ाइलसिस्टम का कंटेनरों से क्या संबंध है?
  • pivot_root, chroot से किस प्रकार भिन्न है?
  • /proc को फिर से माउंट करने से ps द्वारा दिखाए गए आउटपुट में बदलाव क्यों आया?
  • कंटेनर निर्माण में BusyBox की क्या भूमिका है?
  • एक नए root फ़ाइलसिस्टम पर स्विच करते समय डायनेमिक इंटरप्रेटर क्यों मायने रखते हैं?
  • virtiofs macOS पर कंटेनर सेटअप को कैसे जटिल बनाता है?

टैग

#containers #linux #docker #devops #namespaces #cgroups #filesystem #learning

Free field guide

API Security Testing Checklist

A practical workflow for testing authentication, authorization, input handling, business logic, and evidence without losing track of scope.