Kubernetes पर निजी GitLab इमेज: पूरी गाइड

Kubernetes से निजी GitLab रजिस्ट्री की इमेज खींचें: deploy token, imagePullSecret, रोटेशन, ImagePullBackOff डिबग, कमांड सहित।

सफेद पैच पैनल से जुड़े पीले ऑप्टिकल फाइबर केबल, साथ में एक नीला कनेक्टर

संक्षिप्त उत्तर: Kubernetes से किसी निजी GitLab रजिस्ट्री की इमेज खींचने के लिए, प्रोजेक्ट या ग्रुप पर read_registry स्कोप वाला एक deploy token बनाएँ, उसे लक्ष्य नेमस्पेस में kubernetes.io/dockerconfigjson प्रकार के सीक्रेट में बदलें, फिर उस सीक्रेट को imagePullSecrets में संदर्भित करें (पॉड में, या बेहतर होगा ServiceAccount में)। तीन कमांड ही काफी हैं; इस लेख का बाकी हिस्सा सही दायरा चुनने, रोटेशन और ImagePullBackOff त्रुटियों के निदान पर केंद्रित है।

जब कोई टीम एक ही सर्वर पर चल रहे docker compose से Kubernetes क्लस्टर की ओर बढ़ती है, तो यह सबसे आम अड़चनों में से एक होती है: पाइपलाइन इमेज को registry.gitlab.com/mon-groupe/mon-app में ठीक से पुश कर देती है, लेकिन क्लस्टर के पास उसे लाने के लिए कोई पहचान ही नहीं होती। पॉड लूप में चलते रहते हैं, लॉग खाली रहते हैं, और kubectl describe बस इतना दिखाता है: unauthorized: HTTP Basic: Access denied।

सफेद पैच पैनल से जुड़े पीले ऑप्टिकल फाइबर केबल, साथ में एक नीला कनेक्टर

Kubernetes आपकी GitLab रजिस्ट्री क्यों नहीं पढ़ पाता

GitLab Container Registry डिफ़ॉल्ट रूप से निजी होती है: उसकी दृश्यता प्रोजेक्ट की दृश्यता का अनुसरण करती है। निजी प्रोजेक्ट का मतलब है प्रमाणीकृत रजिस्ट्री, और kubelet — इमेज वही खींचता है, कंट्रोल प्लेन नहीं — आपके कंप्यूटर की ~/.docker/config.json को विरासत में नहीं पाता। भले ही docker login के बाद आपके यहाँ docker pull काम करता हो, क्लस्टर के लिए पहचान अनाम ही रहती है।

Kubernetes इस समस्या को kubernetes.io/dockerconfigjson प्रकार के Secret ऑब्जेक्ट से हल करता है। kubelet इस सीक्रेट को पढ़ता है, उसमें से क्रेडेंशियल निकालता है और पुल के दौरान उन्हें रजिस्ट्री को प्रस्तुत करता है। एक अहम बात, जो अक्सर तकलीफ़ झेलने के बाद ही समझ आती है: सीक्रेट नेमस्पेस-विशिष्ट होता है। default में बनाया गया सीक्रेट staging या production से दिखाई नहीं देता। इसलिए जिस भी नेमस्पेस में इमेज डिप्लॉय होती है, वहाँ उसे दोबारा बनाना पड़ता है।

कौन-सा क्रेडेंशियल इस्तेमाल करें?

GitLab कई तरह के टोकन देता है, और इस काम के लिए सभी एक जैसे उपयुक्त नहीं हैं।

क्रेडेंशियलदायराअवधिक्लस्टर पुल के लिए उपयुक्त?
Deploy token (प्रोजेक्ट)एक प्रोजेक्टसमाप्ति वैकल्पिकहाँ — डिफ़ॉल्ट विकल्प
Deploy token (ग्रुप)ग्रुप के सभी प्रोजेक्टसमाप्ति वैकल्पिकहाँ, यदि एक ही ग्रुप के कई ऐप हों
Project access tokenएक प्रोजेक्ट, व्यापक स्कोपसमाप्ति अनिवार्यसंभव, पर ज़रूरत से ज़्यादा
Personal access tokenउपयोगकर्ता के सभी प्रोजेक्टसमाप्ति अनिवार्यनहीं — किसी व्यक्ति से जुड़ा हुआ
CI_JOB_TOKENचालू जॉबकुछ मिनटनहीं — जॉब के साथ ही समाप्त

Deploy token तीन वजहों से जीतता है: यह किसी इंसान से जुड़ा नहीं होता (कोई टीम छोड़े तो कुछ नहीं टूटता), यह अकेले read_registry स्कोप स्वीकार करता है, और इसे ग्रुप स्तर पर बनाकर एक ही यूज़रनेम/पासवर्ड जोड़ी से कई माइक्रोसर्विसेज़ को कवर किया जा सकता है।

CI_JOB_TOKEN पाइपलाइन के भीतर docker login के लिए बहुत सुविधाजनक है, पर उसका जीवनकाल जॉब जितना ही होता है। उससे बना Kubernetes सीक्रेट पहली तैनाती में तो चलेगा, लेकिन पॉड के पहले ही पुनरारंभ पर टूट जाएगा — एक क्लासिक और खासा कपटी जाल, क्योंकि लक्षण प्रोडक्शन में जाने के कई घंटे बाद सामने आता है।

Deploy token बनाना

GitLab में: किसी प्रोजेक्ट के लिए Settings → Repository → Deploy tokens, या ग्रुप स्तर पर Settings → Repository → Deploy tokens। इन्हें भरें:

  • Name: k8s-prod-pull (नाम ऐप्लिकेशन के हिसाब से नहीं, क्लस्टर/परिवेश के हिसाब से रखें)
  • Expiration date: ज़रूर डालें। अधिकतम एक साल; अगर रोटेशन स्वचालित करते हैं तो छह महीने अच्छा संतुलन है।
  • Username: खाली छोड़ दें तो जनरेट किया हुआ नाम मिलेगा (gitlab+deploy-token-123456), या अपनी स्क्रिप्ट सरल रखने के लिए इसे तय कर दें।
  • Scopes: केवल read_registry चुनें। न read_repository, न write_registry।

पासवर्ड तुरंत कॉपी कर लें: GitLab उसे दोबारा कभी नहीं दिखाता।

सुझाव: Dependency Proxy या package registry में रखी इमेज के लिए ज़रूरी स्कोप अलग होता है (read_package_registry)। सामान्य Docker इमेज के लिए read_registry काफी है।

imagePullSecret बनाना

इम्पेरेटिव कमांड सबसे सुरक्षित है, क्योंकि JSON वह आपके लिए खुद बना देती है:

kubectl create secret docker-registry gitlab-registry \
  --namespace=production \
  --docker-server=registry.gitlab.com \
  --docker-username='gitlab+deploy-token-123456' \
  --docker-password='gldt-xxxxxxxxxxxxxxxxxxxx' \
  --docker-email='devops@exemple.fr'

तीन बारीकियाँ, जिनकी वजह से आधी कोशिशें नाकाम होती हैं:

  1. --docker-server रजिस्ट्री का होस्ट होना चाहिए, GitLab का नहीं। gitlab.com पर यह registry.gitlab.com है। सेल्फ-होस्टेड इंस्टैंस पर यह gitlab.rb में मौजूद registry_external_url का मान होता है, अक्सर registry.exemple.fr — कभी gitlab.exemple.fr नहीं। प्रोजेक्ट के Deploy → Container Registry पेज पर दिखने वाले प्रीफ़िक्स से इसकी पुष्टि करें।
  2. न स्कीम, न पाथ। https://registry.gitlab.com/mon-groupe से बेकार सीक्रेट बनता है: kubelet इमेज के होस्ट की तुलना config.json की कुंजी से करता है।
  3. पासवर्ड के चारों ओर सिंगल कोट्स। GitLab टोकन में ऐसे अक्षर हो सकते हैं जिन्हें शेल व्याख्यायित कर देता है।

परिणाम को हिस्ट्री में उजागर किए बिना जाँचें:

kubectl -n production get secret gitlab-registry \
  -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq '.auths | keys'

आपको ठीक ["registry.gitlab.com"] दिखना चाहिए।

सीक्रेट को जोड़ना

दो तरीके हैं। पॉड स्तर पर, स्पष्ट रूप से:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  template:
    spec:
      imagePullSecrets:
        - name: gitlab-registry
      containers:
        - name: api
          image: registry.gitlab.com/mon-groupe/mon-app:1.4.2

या, ज़्यादा सुरुचिपूर्ण तरीके से, नेमस्पेस द्वारा उपयोग किए जाने वाले ServiceAccount स्तर पर — उसका उपयोग करने वाले सभी पॉड इसे विरासत में पा जाते हैं, मैनिफ़ेस्ट छुए बिना:

kubectl -n production patch serviceaccount default \
  -p '{"imagePullSecrets":[{"name":"gitlab-registry"}]}'

दूसरा विकल्प नई ऐप्लिकेशन आने पर भूल-चूक से बचाता है, पर पढ़ने में कम स्पष्ट है। अपने ग्राहकों के लिए जो क्लस्टर हम संचालित करते हैं, उनमें हम ServiceAccount को पैच करते हैं और Helm चार्ट्स में imagePullSecrets भी रखते हैं: यह दोहराव हानिरहित है और दस्तावेज़ीकरण का काम करता है।

डेटा सेंटर में नीली रोशनी वाले सर्वर रैक के सामने टैबलेट लिए बैठी महिला

एक अच्छी प्रथा: imagePullPolicy और अपरिवर्तनीय टैग

इसी दौरान पुल पॉलिसी भी तय कर लें। Kubernetes डिफ़ॉल्ट रूप से IfNotPresent लागू करता है, सिवाय :latest टैग के, जहाँ वह Always पर चला जाता है। नतीजा: :latest के साथ, हर पुनः तैनाती एक अलग इमेज खींच लाती है और Git में इसका कोई संकेत नहीं मिलता, और समाप्त हो चुका सीक्रेट पॉड को तुरंत तोड़ देता है।

हम हमेशा यही नियम लागू करते हैं:

  • सिमैंटिक वर्शन या छोटे कमिट SHA से टैग करें (:1.4.2, :sha-a1b2c3d);
  • imagePullPolicy: IfNotPresent बनाए रखें;
  • पहले से तैनात टैग के ऊपर लिखे जाने से रोकने के लिए GitLab की ओर से protected और immutable tags सक्षम करें।

SHA से टैग की गई इमेज रोलबैक को भी बेहद आसान बना देती है: kubectl rollout undo deployment/api नोड के कैश में पहले से मौजूद लेयर की ओर इशारा कर देता है, रजिस्ट्री पर निर्भर हुए बिना।

GitLab CI/CD से स्वचालन

हर नेमस्पेस में एक बार हाथ से सीक्रेट बनाना तब तक चलता है, जब तक टोकन की समाप्ति का दिन नहीं आ जाता। दो रणनीतियाँ लंबे समय तक टिकती हैं।

रणनीति 1: पाइपलाइन में इडेम्पोटेंट पुनर्निर्माण

Deploy token को masked और protected CI/CD वेरिएबल्स (K8S_PULL_USER, K8S_PULL_TOKEN) में रखें, फिर हर तैनाती पर सीक्रेट दोबारा बनाएँ:

deploy:
  stage: deploy
  image: bitnami/kubectl:1.34
  environment: production
  rules:
    - if: $CI_COMMIT_TAG
  script:
    - kubectl create secret docker-registry gitlab-registry
        --namespace="$KUBE_NAMESPACE"
        --docker-server="$CI_REGISTRY"
        --docker-username="$K8S_PULL_USER"
        --docker-password="$K8S_PULL_TOKEN"
        --dry-run=client -o yaml | kubectl apply -f -
    - helm upgrade --install api ./charts/api
        --namespace "$KUBE_NAMESPACE"
        --set image.tag="$CI_COMMIT_TAG"
        --atomic --timeout 5m

--dry-run=client -o yaml | kubectl apply -f - की जोड़ी kubectl create secret को इडेम्पोटेंट बनाने का मानक तरीका है: सीक्रेट मौजूद हो तो वह उसे अपडेट करती है, वरना बना देती है, और कभी AlreadyExists नहीं लौटाती। $CI_REGISTRY GitLab अपने आप उपलब्ध कराता है और उसमें पहले से सही होस्ट होता है, जिससे ऊपर बताई गई गलती नंबर 1 अपने आप खत्म हो जाती है।

Helm का --atomic आपको आधा-अधूरा तैनात Deployment छोड़ने से बचाता है, अगर पुल फिर भी विफल हो जाए: चार्ट अपनी पिछली स्थिति में लौट आता है।

रणनीति 2: पाइपलाइन के बाहर प्रबंधित सीक्रेट

बहु-टीम क्लस्टर पर टोकन को CI से बाहर रखना बेहतर है। दो टूल यह भूमिका बिना मेहनत के निभा देते हैं:

  • External Secrets Operator: टोकन AWS Secrets Manager, Vault या Scaleway Secret Manager में रहता है, और एक ExternalSecret ऑब्जेक्ट उसे हर नेमस्पेस में dockerconfigjson के रूप में साकार कर देता है। रोटेशन वॉल्ट में होता है, और क्लस्टर अगले रिफ़्रेश चक्र पर अपने आप उसका अनुसरण कर लेता है।
  • Reflector या kubernetes-replicator: आप किसी प्रशासनिक नेमस्पेस में एक ही स्रोत सीक्रेट बनाए रखते हैं और एक एनोटेशन उसे सभी अनुमत नेमस्पेस में प्रतिकृत कर देता है। सरल, और बेहद कारगर जब एकमात्र परेशानी नेमस्पेस की बढ़ती संख्या हो।

दोनों ही मामलों में, deploy token की समाप्ति तिथि अपने रनबुक में दर्ज करें। जिन टोकन की समाप्ति तिथि होती है, उनके लिए GitLab पहले से चेतावनी ईमेल भेजता है, पर यह संदेश अक्सर किसी एक ही प्रोजेक्ट मालिक के इनबॉक्स में पहुँचता है।

ImagePullBackOff का निदान

जब चीज़ें बिगड़ें, तो इसी क्रम का पालन करें। यह पाँच मिनट से कम में कारण अलग कर देता है।

# 1. इवेंट का सटीक संदेश पढ़ें
kubectl -n production describe pod api-7d9c8b5f4-abcde | tail -30

# 2. जाँचें कि सीक्रेट वाकई इसी नेमस्पेस में मौजूद है
kubectl -n production get secret gitlab-registry

# 3. जाँचें कि पॉड सचमुच उसका संदर्भ देता है
kubectl -n production get pod api-7d9c8b5f4-abcde \
  -o jsonpath='{.spec.imagePullSecrets}'

फिर संदेश पढ़ें:

संदेशसंभावित कारणसमाधान
unauthorized: HTTP Basic: Access deniedटोकन समाप्त, रद्द, या गलत स्कोपread_registry के साथ deploy token दोबारा बनाएँ
pull access denied ... may require authenticationनेमस्पेस में सीक्रेट नहीं है या संदर्भित नहीं हैसही नेमस्पेस में सीक्रेट बनाएँ, ServiceAccount पैच करें
manifest unknown / not foundटैग मौजूद ही नहीं (टाइपो, बिल्ड पाइपलाइन विफल)Container Registry में टैग जाँचें
x509: certificate signed by unknown authorityआंतरिक प्रमाणपत्र वाली सेल्फ-होस्टेड रजिस्ट्रीनोड्स पर प्रमाणन प्राधिकरण जोड़ें
toomanyrequestsदर सीमाएँ (अपस्ट्रीम रजिस्ट्री, बेस इमेज)Dependency Proxy या कैश मिरर

उन्हीं क्रेडेंशियल के साथ पुल को Kubernetes के बाहर, हाथ से दोहराने के लिए:

echo "$K8S_PULL_TOKEN" | docker login registry.gitlab.com \
  -u 'gitlab+deploy-token-123456' --password-stdin
docker pull registry.gitlab.com/mon-groupe/mon-app:1.4.2

अगर यह कमांड आपके कंप्यूटर से सफल होती है पर क्लस्टर में विफल, तो समस्या अब प्रमाणीकरण की नहीं रही: नोड्स के आउटबाउंड नेटवर्क (कॉर्पोरेट प्रॉक्सी, एग्रेस नियम, नेटवर्क पॉलिसी) या नोड की डिस्क स्पेस की ओर देखें — भरा हुआ /var/lib/containerd भ्रामक पुल विफलताएँ पैदा करता है, जो kubectl describe node की DiskPressure कंडीशन में दिखती हैं।

डेटा सेंटर रैक में फ़िरोज़ी और पीले ऑप्टिकल फाइबर केबल वाला पैच पैनल

रजिस्ट्री की लागत, जो अक्सर भुला दी जाती है

GitLab Container Registry का स्टोरेज GitLab नेमस्पेस के स्टोरेज कोटे पर गिना जाता है, और वह तेज़ी से बढ़ता है: हर पाइपलाइन एक पूरी इमेज पुश करती है। 350 MB की Node.js इमेज और हफ़्ते में चालीस पाइपलाइन के साथ, अगर कुछ भी सफ़ाई न करे तो आप हर महीने 50 GB से ज़्यादा जोड़ते जाएँगे।

दो ठोस उपाय:

  • टैग समाप्ति नियम (Settings → Packages and registries → Clean up image tags): आख़िरी 10 टैग रखें, v* वर्शन वाले टैग सुरक्षित रखें, बाकी 30 दिनों के बाद हटा दें। ध्यान दें, जगह वास्तव में तभी खाली होती है जब रजिस्ट्री का गार्बेज कलेक्टर चलता है।
  • छोटी इमेज: node:22-alpine जैसा बेस या npm ci --omit=dev के साथ मल्टी-स्टेज बिल्ड इमेज को कई सौ मेगाबाइट तक घटा देता है, और हर नए नोड पर पुल को उतना ही तेज़ कर देता है।

दूसरा उपाय लोड बढ़ने की स्थिति में आपके स्टार्टअप समय पर सीधा असर डालता है: जब ऑटोस्केलर कोई नया खाली नोड जोड़ता है, तो 350 MB के बजाय 80 MB खींचने वाला पॉड साफ़ तौर पर तेज़ी से शुरू होता है। यह ठीक उसी तरह का अनुकूलन है जिसे हम अपने ग्राहकों के लिए की जाने वाली इंफ्रास्ट्रक्चर और डिप्लॉयमेंट सेवाओं में शामिल करते हैं, स्वयं एप्लिकेशन डेवलपमेंट के साथ-साथ।

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

क्या हर नेमस्पेस के लिए अलग सीक्रेट चाहिए?

हाँ। Kubernetes सीक्रेट नेमस्पेस की सीमा पार नहीं करते। या तो आप हर नेमस्पेस में सीक्रेट दोबारा बनाएँ (पाइपलाइन के ज़रिए, इडेम्पोटेंट तरीके से), या फिर उसे एकल स्रोत से फैलाने के लिए Reflector या External Secrets Operator जैसे रेप्लिकेटर का उपयोग करें।

क्या imagePullSecret के लिए CI_JOB_TOKEN इस्तेमाल किया जा सकता है?

नहीं, टिकाऊ सीक्रेट के लिए नहीं। यह टोकन जॉब के अंत में समाप्त हो जाता है। बिल्ड के दौरान docker login के लिए यह बिल्कुल सही है, पर कुछ घंटे बाद पुनरारंभ हुआ पॉड इमेज नहीं खींच पाएगा। deploy token का उपयोग करें।

ग्रुप का deploy token या प्रोजेक्ट का?

अगर रजिस्ट्री का उपयोग सिर्फ़ एक ऐप्लिकेशन करती है तो प्रोजेक्ट का; और जैसे ही आप एक ही ग्रुप की कई सेवाएँ एक ही क्लस्टर पर तैनात करें, तो ग्रुप का। ग्रुप टोकन घुमाने वाले सीक्रेट की संख्या घटा देता है; बदले में, समझौता होने का दायरा बढ़ा देता है। हर हाल में: केवल read_registry स्कोप।

समाप्ति के दिन सेवा बाधित होने से कैसे बचें?

पुराने टोकन को हटाने से पहले नया टोकन बनाएँ, नया सीक्रेट सभी नेमस्पेस में लागू करें, किसी गैर-महत्वपूर्ण ऐप्लिकेशन पर kubectl rollout restart करके जाँच लें, फिर पुराना टोकन रद्द करें। और समाप्ति तिथि को GitLab के चेतावनी ईमेल के भरोसे छोड़ने के बजाय अपने संचालन कैलेंडर में दर्ज करें।

और आंतरिक प्रमाणपत्र के पीछे सेल्फ-होस्टेड रजिस्ट्री के साथ?

सीक्रेट तो वही रहता है, पर kubelet को प्रमाणन प्राधिकरण पर भरोसा करना होगा। उसे नोड्स के सिस्टम स्टोर में इंस्टॉल करें (/usr/local/share/ca-certificates/ फिर update-ca-certificates, और containerd का पुनरारंभ) या, रनटाइम के अनुसार, /etc/containerd/certs.d/<होस्ट>/ कॉन्फ़िगर करें। पॉड में बदलाव करने से कुछ नहीं होगा: पुल नोड स्तर पर होता है।

सारांश

  • GitLab रजिस्ट्री डिफ़ॉल्ट रूप से निजी है; उसमें प्रमाणित होने के लिए kubelet को kubernetes.io/dockerconfigjson सीक्रेट चाहिए।
  • केवल read_registry स्कोप वाला deploy token (प्रोजेक्ट या ग्रुप) इस्तेमाल करें, कभी व्यक्तिगत टोकन या CI_JOB_TOKEN नहीं।
  • --docker-server रजिस्ट्री का होस्ट होता है (registry.gitlab.com या $CI_REGISTRY), बिना https:// और बिना प्रोजेक्ट पाथ के।
  • हर नेमस्पेस में एक सीक्रेट: उसके निर्माण को --dry-run=client -o yaml | kubectl apply -f - से इडेम्पोटेंट बनाएँ, या External Secrets Operator से उसे फैलाएँ।
  • सभी पॉड को कवर करने के लिए उसे ServiceAccount से जोड़ें, और दस्तावेज़ीकरण के तौर पर अपने चार्ट्स में imagePullSecrets बनाए रखें।
  • वर्शन या SHA से टैग करें, :latest से नहीं, imagePullPolicy: IfNotPresent और अपरिवर्तनीय टैग के साथ।
  • ImagePullBackOff की स्थिति में kubectl describe का संदेश पढ़ें: वह अमान्य टोकन (Access denied) और अस्तित्वहीन टैग (manifest unknown) में फ़र्क साफ़ कर देता है।
  • रजिस्ट्री साफ़ रखें (टैग समाप्ति नियम) और इमेज का आकार घटाएँ: कम स्टोरेज बिल, तेज़ स्टार्टअप।

कोई तैनाती इमेज पुल पर अटकी है, किसी क्लस्टर को औद्योगिक स्तर पर लाना है, या GitLab CI/CD चेन संभालनी है? अपनी स्थिति हमें बताइए — हमारी टीम Pontault-Combault से रोज़ इन्हीं विषयों पर काम करती है।