تثبيت نزال

مخطط Helm واحد وأمر واحد. ونحو خمس عشرة دقيقة من لا شيء حتى يفتح المطوّر بيئته الأولى. ويعمل نزال بالكامل داخل الكلستر لديك: فبعد أن يسحب التثبيت صوره لا يجري أي اتصال خارجي من أي نوع.

قبل أن تبدأ

لا تحتاج إلى مفتاح ترخيص ولا إلى حساب ولا إلى التحدث مع أحد. والتثبيت أدناه كامل ومجاني لثلاثة مطوّرين.

التثبيت

TOKEN=$(openssl rand -hex 16)
echo "$TOKEN"
helm install nezal oci://registry-1.docker.io/nezal/nezal --version 0.2.32 \
  --namespace nezal-system --create-namespace \
  --set ingress.domain=dev.example.internal \
  --set ingress.dashboard.host=nezal.example.internal \
  --set api.bootstrap.token="$TOKEN"

القيم الأربع الأولى مهمة في كل تثبيت. أما البقية فلها قيم افتراضية تعمل وهي موجودة لترفعها من أجل مستودع كبير أو فريق كثير النشاط:

القيمة ما هي
ingress.domain النطاق الشامل الذي تُبنى منه روابط المعاينة. وتصبح البيئة https://<env>--<app>.<domain>. استخدم نطاقًا فرعيًا مستقلًا مع سجل DNS شامل *.<domain> يشير إلى الـ ingress.
ingress.dashboard.host اسم مضيف لوحة التحكم والـ API مثل nezal.example.internal.
api.bootstrap.token رمز مشرف لمرة واحدة لأول تسجيل دخول قبل إعداد SSO. ويولّده السطر الأول ويطبعه لتحتفظ به. وكل استخدام له يُسجَّل في سجل التدقيق.
operator.maxTreeSizeMiB أكبر حجم مستودع يقرؤه المشغّل بوحدة MiB. والافتراضي 1024. ارفعه لمستودع أحادي كبير (monorepo). وعند تجاوزه يرفض المشغّل المستودع برسالة تذكر الحجم بدلًا من أن يملأ ذاكرته المؤقتة ويعيد التشغيل.
operator.sourceCache.sizeLimit مساحة القرص لنسخ git لدى المشغّل. والافتراضي 8Gi. ارفعها مع operator.maxTreeSizeMiB للمستودعات الكبيرة جدًا.
operator.router.resources.requests.* وoperator.router.resources.limits.* موارد الموجّه لكل تطبيق افتراضيًا. وتركها فارغة يبقي طلب 50m من المعالج و64Mi من الذاكرة وحدًا للذاكرة قدره 256Mi. ويستطيع كل تطبيق ضبط قيمه من حجم الموجّه في إعداداته.
operator.resources.requests.cpu وoperator.resources.requests.memory ما يُضمن للمشغّل. والافتراضي 50m من المعالج و192Mi.
operator.resources.limits.cpu وoperator.resources.limits.memory أقصى ما قد يستهلكه المشغّل. والافتراضي بلا حد للمعالج و1Gi للذاكرة. وهو يستنسخ المستودعات لذلك فالذاكرة هي ما يُرفع لمستودع أحادي كبير.
api.resources.requests.cpu وapi.resources.requests.memory ما يُضمن للـ API ولوحة التحكم. والافتراضي 100m من المعالج و256Mi.
api.resources.limits.cpu وapi.resources.limits.memory أقصى ما قد يستهلكه الـ API. والافتراضي بلا حد للمعالج و1Gi للذاكرة. ارفع الذاكرة حين يفحص مطوّرون كثيرون المستودعات في وقت واحد.
registry.resources.requests وregistry.resources.limits سجل الصور المضمَّن حين يكون registry.enabled=true. والافتراضي طلب 100m من المعالج و256Mi مع حد للذاكرة قدره 1Gi.
ingress.tlsSecret وingress.dashboard.tlsSecret كائنات Secret من نوع TLS تحمل الشهادة الشاملة لـ *.<domain> وشهادة لوحة التحكم. ومع cert-manager استخدم بدلًا منها ingress.perHostTLS وingress.annotations للحصول على شهادة لكل مضيف معاينة. ومن دون أي منها تُقدَّم روابط المعاينة ولوحة التحكم عبر HTTP غير مشفّر. وهذا مقبول على حاسوب محمول وغير مقبول في أي مكان آخر. راجع روابط المعاينة: DNS وTLS.
audit.retention.days تحذف مدخلات التدقيق الأقدم من هذا العدد من الأيام مرة كل ساعة حتى لا يملأ سجل التدقيق وحدة التخزين. والافتراضي 0 يحفظ كل المدخلات. وتُكتب كل عملية حذف في السجل وتبقى المدخلات الباقية قابلة للتحقق. راجع مدة الاحتفاظ بالتدقيق.
audit.retention.onlyArchived القيمة true تحذف فقط المدخلات التي يحفظها أرشيف موقّع. والافتراضي false.

راقب بدء التشغيل:

kubectl -n nezal-system get pods -w
helm test nezal -n nezal-system      # confirms the API answers from inside the cluster

روابط المعاينة: DNS وTLS

تحصل كل بيئة على اسم مضيف خاص بها. لذلك يحتاج نزال إلى نطاق فرعي مخصص له وإلى شهادة شاملة لذلك النطاق. وDNS والشهادات من مسؤوليتك لا من مسؤولية نزال: فالمنتج لا يُصدر أي شهادات ولا يتصل أبدًا بأي جهة إصدار شهادات. خطّط لهذه الأمور قبل التثبيت.

1. اختر نطاقًا فرعيًا لروابط المعاينة. اختر اسمًا يخص نزال وحده مثل dev.example.internal. وتصبح البيئة <env>--<app>.dev.example.internal والأساس المشترك <app>.dev.example.internal. ولا تستخدم النطاق الرئيسي لمؤسستك: فالبيئات ستقع حينها مباشرة تحت نطاق الإنتاج وسيتعين أن تغطيها شهادته. وهذه القيمة هي ingress.domain.

2. اختر اسم مضيف للوحة التحكم مثل nezal.example.internal. ويمكن أن يقع تحت النطاق الفرعي للمعاينة أو بجانبه. وهذه القيمة هي ingress.dashboard.host.

3. وجّه DNS إلى متحكم ingress. اعثر على العنوان الذي يستجيب عليه متحكم ingress. ثم أنشئ سجلين في خدمة DNS التي يستخدمها مطوّروك أصلًا. ولا يلزم أن تكون هذه الأسماء قابلة للحل من الإنترنت.

kubectl get svc -A | grep -i ingress      # EXTERNAL-IP of the LoadBalancer, or the node IPs
السجل النوع يشير إلى
*.dev.example.internal A (عنوان IP) أو CNAME (اسم) متحكم ingress
nezal.example.internal A أو CNAME متحكم ingress

4. احصل على الشهادات. تحتاج إلى شهادة لمضيف لوحة التحكم وإلى شهادة تغطي روابط المعاينة. ولا يُصدر نزال أيًا منهما ولا يتصل أبدًا بأي جهة إصدار شهادات: فهو يعمل مع ما يستخدمه الكلستر لديك أصلًا. اعثر على حالتك في الجدول أدناه ثم تابع من الخطوة 6.

لديك ما تفعله إعداد نزال
شهاداتك الخاصة من مورّد أو من جهة إصدار داخلية المسار 4a: ضع الشهادة الشاملة في Secret في مساحة أسماء كل مساحة عمل ingress.tlsSecret
cert-manager المسار 4b: لا شيء يدويًا. وتظهر الشهادات من تلقاء نفسها ingress.perHostTLS وingress.annotations
Traefik بوصفه متحكم ingress المسار 4b: لا شيء يدويًا هنا أيضًا. فـ Traefik يحصل على الشهادات بنفسه ويقرأ annotations الخاصة به وينسخها نزال إلى كل Ingress ingress.annotations مع annotation محلّل الشهادات في Traefik
TLS ينتهي عند موزّع حمل أو عند الحافة أمام الكلستر لا شيء داخل الكلستر. فالشهادة على الحافة ويجب أن تغطي *.<domain> لروابط المعاينة لا شيء. اضبط api.publicUrl على https://...
لا شيء بعد المسار 4c: HTTP غير مشفّر لأغراض التقييم لا شيء

كلمة عن الصفّين الأخيرين. عندما ينتهي TLS خارج الكلستر لا يستطيع أي شيء داخله تقديم شهادة. لذلك لا يغيّر Secret ولا cert-manager ما يراه المتصفح: فشهادة الحافة هي التي يجب أن تذكر مضيفات المعاينة. ثم إن تحدّي HTTP-01 يحتاج إلى أن يكون المنفذ 80 مفتوحًا من جهة إصدار الشهادات إلى المضيف. والحافة التي لا تستمع إلا على المنفذ 443 تحجب ذلك.

4a. شهادات تملكها بالفعل

جهة إصدار الشهادات الداخلية لديك هي المصدر المعتاد للأسماء الواقعة تحت نطاق خاص. وتصلح جهة إصدار عامة لنطاق تملكه. حمّل كل شهادة في Secret في Kubernetes.

ويوجد Secret لوحة التحكم في مساحة الأسماء التي ثُبّت فيها نزال. أما Secret الشهادة الشاملة فيجب أن يوجد في كل مساحة أسماء للتطبيقات لأن المشغّل ينشئ هناك كائنات Ingress الخاصة بالمعاينة. وتوجد مساحة أسماء واحدة للتطبيقات لكل مساحة عمل.

kubectl create namespace nezal-system
kubectl -n nezal-system create secret tls nezal-dashboard-tls \
  --cert=dashboard.crt --key=dashboard.key

بعد أن تنشئ مساحة عمل في لوحة التحكم أضف الشهادة الشاملة إلى مساحة أسمائها. وإلى أن تفعل ذلك يُبلغ الأساس المشترك لتلك المساحة عن الحالة IngressTLS=False وتُقدَّم روابط المعاينة فيها عبر HTTP غير مشفّر.

kubectl -n <workspace-namespace> create secret tls nezal-preview-tls \
  --cert=wildcard.crt --key=wildcard.key

4b. cert-manager يُصدرها نيابة عنك

إذا كان cert-manager يعمل أصلًا على الكلستر فسمِّ كائنات Secret ودعه يملؤها. ولا يشترط نزال سوى وجود هذه الكائنات. أضف إلى Ingress لوحة التحكم annotation تحمل اسم المُصدِر وسينشئ cert-manager الشهادة في أول مرة يظهر فيها الـ Ingress.

kubectl get clusterissuer        # the issuers this cluster already has

أضف هذه الأسطر إلى التثبيت في الخطوة 6 وضع اسم المُصدِر لديك مكان الاسم الموجود:

  --set ingress.dashboard.tlsSecret=nezal-dashboard-tls \
  --set 'ingress.dashboard.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod' \

والشرطة المائلة العكسية قبل .io ضرورية: فـ Helm يعامل النقطة غير المهرَّبة على أنها فاصل مستويات.

وتعمل روابط المعاينة بالطريقة نفسها مع شهادة لكل بيئة. أضف هاتين القيمتين وعندها يذكر كل Ingress معاينة ينشئه المشغّل Secret خاصًا به اسمه <host>-tls. ويملؤه cert-manager بشهادة لذلك المضيف وحده لحظة ظهور الـ Ingress. فلا شهادة شاملة ولا مزوّد DNS في الأمر وتحصل البيئة الجديدة على شهادتها في غضون دقيقة. وهذا ما يفعله الوكيل العكسي المزوّد بـ ACME مدمج على جهاز واحد. وهنا يُعبَّر عنه بلغة Kubernetes.

  --set ingress.perHostTLS=true \
  --set 'ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod' \

وإذا كان Traefik هو متحكم ingress لديك بدلًا من cert-manager فالقيم نفسها تنطبق مع annotation الخاصة بـ Traefik مثل traefik.ingress.kubernetes.io/router.tls.certresolver=letsencrypt: فالمشغّل ينسخ أي annotations تعطيها له ولا يفسّرها.

ويجدر بك معرفة قيدين قبل اختيار هذا المسار. فالمُصدِر من نوع HTTP-01 يتحقق بالرد على طلب على المنفذ 80 عند كل مضيف. لذلك يجب أن يمكن الوصول إلى المضيفات من جهة إصدار الشهادات وهذا يستبعد المسار في الشبكة المعزولة. ثم إن جهات الإصدار العامة تحدّ عدد الشهادات الجديدة لكل نطاق مسجَّل: خمسون شهادة في الأسبوع لدى Let's Encrypt. والفريق الذي ينشئ عشرات البيئات يوميًا ينبغي أن يستخدم شهادة شاملة بدلًا من ذلك. ومع مزوّد DNS-01 في cert-manager لخدمة DNS لديك يُصدر كائن Certificate واحد في مساحة أسماء كل مساحة عمل الشهادة الشاملة إلى الـ Secret المذكور في ingress.tlsSecret ويبقى perHostTLS معطّلًا:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: nezal-preview-tls
  namespace: <workspace-namespace>
spec:
  secretName: nezal-preview-tls
  dnsNames: ["*.dev.example.internal"]
  issuerRef: {name: letsencrypt-dns, kind: ClusterIssuer}

وفي الحالتين يُبلغ كل أساس مشترك عن الحالة IngressTLS: وهي True متى وُجدت شهادته وCertificatePending ما دام المُصدِر يعمل عليها.

4c. لا شيء بعد لأغراض التقييم

اترك كائني Secret دون ضبط. وعندها تُقدَّم لوحة التحكم وروابط المعاينة عبر HTTP غير مشفّر ويُبلغ كل أساس مشترك عن IngressTLS=False بالسبب NoTLSSecret. ويعمل كل شيء آخر. وهذا مقبول على حاسوب محمول وغير مقبول في أي مكان آخر لأن المطوّر يسجّل الدخول عبره.

5. اختر فئة ingress. تجاوز هذه الخطوة إذا كان الكلستر يشغّل متحكم ingress واحدًا. أما إذا كان يشغّل أكثر من واحد فسمِّ المتحكم الذي تشير إليه سجلات DNS من الخطوة 3 ومرّره بالصيغة --set ingress.className=<name> أدناه.

kubectl get ingressclass

6. ثبّت بالقيم المناسبة. ومع وجود cert-manager على الكلستر فهذا هو التثبيت كله: فلوحة التحكم وكل رابط معاينة تحصل على شهاداتها من تلقاء نفسها.

helm install nezal oci://registry-1.docker.io/nezal/nezal --version 0.2.32 \
  --namespace nezal-system --create-namespace \
  --set ingress.domain=dev.example.internal \
  --set ingress.dashboard.host=nezal.example.internal \
  --set ingress.dashboard.tlsSecret=nezal-dashboard-tls \
  --set 'ingress.dashboard.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod' \
  --set ingress.perHostTLS=true \
  --set 'ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod' \
  --set api.bootstrap.token="$TOKEN"

ومع شهادات تملكها من المسار 4a سمِّ كائنات Secret بدلًا من ذلك:

helm install nezal oci://registry-1.docker.io/nezal/nezal --version 0.2.32 \
  --namespace nezal-system \
  --set ingress.domain=dev.example.internal \
  --set ingress.dashboard.host=nezal.example.internal \
  --set ingress.dashboard.tlsSecret=nezal-dashboard-tls \
  --set ingress.tlsSecret=nezal-preview-tls \
  --set api.bootstrap.token="$TOKEN"

تحقق من النتيجة بالأمر curl -I https://nezal.example.internal. وبعد أن تعمل البيئة الأولى افتح رابط المعاينة الذي يطبعه CLI. ومع cert-manager راقب ظهور الشهادة:

kubectl -n nezal-system get certificate -w

استخدام سجل الصور المضمَّن

للتقييم يمكنك تثبيت سجل صور داخل الكلستر بـ --set registry.enabled=true. وتدفع إليه عمليات البناء ويقدّم HTTPS بشهادة من جهة التصديق الخاصة بنزال. اربطه من سجلات الصور بالعنوان nezal-registry.nezal-system.svc:5000 دون بيانات اعتماد.

تسحب العقدة الصور عبر containerd الذي يحل الأسماء بـ DNS العقدة نفسها ولا يثق بجهة تصديق نزال. نفّذ هذه الخطوات مرة واحدة على كل عقدة وإلا فشلت كل صورة مبنية بالخطأ ErrImagePull:

# على جهاز فيه kubectl
kubectl -n nezal-system get svc nezal-registry -o jsonpath='{.spec.clusterIP}'      # دوّن العنوان
kubectl -n nezal-system get secret nezal-registry-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > nezal-registry-ca.crt

# على كل عقدة بالعنوان من السطر الأول
echo "<address> nezal-registry.nezal-system.svc" | sudo tee -a /etc/hosts
d=/etc/containerd/certs.d/nezal-registry.nezal-system.svc:5000
sudo mkdir -p "$d" && sudo cp nezal-registry-ca.crt "$d/ca.crt"
printf 'server = "https://nezal-registry.nezal-system.svc:5000"\n\n[host."https://nezal-registry.nezal-system.svc:5000"]\n  ca = "%s/ca.crt"\n' "$d" | sudo tee "$d/hosts.toml"
sudo systemctl restart containerd

لا يقرأ containerd المجلد /etc/containerd/certs.d إلا حين يشير إليه config_path تحت [plugins."io.containerd.grpc.v1.cri".registry] في /etc/containerd/config.toml. وتضبطه معظم التوزيعات. وعلى k3s ضع العنوان وجهة التصديق نفسيهما في /etc/rancher/k3s/registries.yaml بدلًا من ذلك ثم أعد تشغيل k3s.

ولأي استخدام بعد التقييم اربط سجل الصور الذي تشغّله أصلًا. فعقدك تسحب منه بالفعل ولا حاجة إلى أي من هذا.

الشبكة

يستطيع كل Pod يشغّله نزال الوصول إلى الإنترنت ويُوصَل إليه منه افتراضيًا. ويشمل ذلك واجهة API والمشغّل وكل عملية بناء وكل بيئة معاينة. وبذلك يعمل سجل صور أو خادم git أو مزوّد هوية على الإنترنت دون أي عنوان تضبطه. وأمران ثابتان مهما ضبطت:

لإغلاق الإنترنت في كلستر معزول أو خاضع لرقابة صارمة:

  1. اضبط القيمتين أدناه واذكر الشبكات الخاصة التي يحتاجها نزال تحت networkPolicy.egress.allowCIDRs. ويجب أن يقع خادم git وسجل الصور ومزوّد الهوية داخلها.

    networkPolicy:
      internet: false        # the API and the operator and builds
      egress:
        allowCIDRs:
          - cidr: 10.0.0.0/8
    environments:
      internet: false        # preview environments
    
  2. رقِّ الإصدار.

    helm upgrade nezal oci://registry-1.docker.io/nezal/nezal -n nezal-system --reuse-values -f network.yaml
    
  3. افتح سجلات الصور واضغط اختبار لكل سجل. ويخبرك السجل الذي لا يصل إليه الكلستر بذلك بكلمات واضحة.

وتستطيع سياسة الفريق أن تفتح شبكة داخلية لبيئاتها بـegressCIDRs.

أول تسجيل دخول

افتح https://nezal.example.internal أو استخدم هذا الأمر إذا لم يكن لديك ingress:

kubectl -n nezal-system port-forward svc/nezal-api 8080:8080

سجّل الدخول برمز الإعداد الأولي. وتعرض لوحة التحكم تثبيتًا مجانيًا: ثلاثة مطوّرين وكل الميزات. ثم شغّل تسجيل التدقيق من سجل التدقيق > التسجيل ومدة الاحتفاظ. لا يسجّل التثبيت الجديد شيئًا حتى يشغّله المشرف وتذكر صفحة سجل التدقيق ونظرة المشرف العامة ذلك حتى تشغّله. وأعدّ SSO عندما تكون جاهزًا (الإعدادات → الأمان). وبعد إعداد OIDC أو SAML وإعادة تشغيل الـ API يتوقف رمز الإعداد الأولي عن العمل.

اربط مستودعًا واحصل على بيئة

صِف تطبيقك مرة واحدة بمجلد nezal في المستودع. تأخذ البداية السريعة خمس خطوات وكل الأوامر مذكورة في دليل مجلد nezal.

  1. المستودعات → اربط مستودعًا. أعطه عنوان Git URL وبيانات اعتماد.

  2. سجلات الصور → اربط سجلًا. وهذه الخطوة مطلوبة: لا يمكن إنشاء تطبيق قبل ربط سجل صور. وتُدفع الصور المبنية إليه وتسحب pods الصور ببيانات اعتماده.

  3. التطبيقات → اربط تطبيقًا. يقرأ نزال المستودع ويقترح تعريف التطبيق من مخططات Helm أو Kustomize أو ملفات manifests عادية. ويُظهر الملف الذي جاء منه كل استنتاج.

  4. رقِّ أساسًا مشتركًا. وهو النسخة المشتركة من التطبيق كله التي تعود إليها كل بيئة في التوجيه.

  5. نزّل CLI من nezal.dev. وتعرض الإدارة → التثبيت الأمر لكل نظام. والجهاز الذي لا يتصل بالإنترنت ينزّل الملف نفسه من تثبيتك على https://nezal.example.internal/downloads/nezal-linux-amd64 بدلًا من ذلك:

    curl -fsSL https://nezal.dev/downloads/nezal-linux-amd64 -o /tmp/nezal
    chmod +x /tmp/nezal
    sudo mv /tmp/nezal /usr/local/bin/
    
  6. أنشئ بيئة:

    nezal login --server https://nezal.example.internal
    nezal up --name my-feature --ref feat/my-branch
    

    يقارن نزال الفرع بالأساس المشترك ويعيد نشر الخدمات التي تغيّرت فقط. ثم يطبع رابط المعاينة.

الحصول على ترخيص

التثبيت المجاني ثلاثة مطوّرين بكل الميزات دون مفتاح تطلبه: المشرف وشخصان آخران. وتُقبل دعوتان ويسري الحدّ نفسه على من يسجّل الدخول عبر SSO. ولإضافة مطوّرين راسل [email protected]. والترخيص 499 دولارًا أمريكيًا شهريًا تُفوتر سنويًا بمبلغ 5988 دولارًا أمريكيًا في السنة لعدد غير محدود من المطوّرين على كلستر واحد.

أرسل معرّف الكلستر مع الطلب. وهو UID لمساحة الأسماء kube-system ويظهر أيضًا في الإدارة → الترخيص مع زر للنسخ:

kubectl get namespace kube-system -o jsonpath='{.metadata.uid}'

والمفتاح الذي تتسلّمه يسجّل هذا المعرّف. لكنه لا يفرضه: فإذا أعدت بناء الكلستر تغيّر المعرّف ويبقى مفتاحك الحالي قابلًا للتثبيت ويُكتب الاختلاف في سجل التدقيق بدلًا من أن يُرفض المفتاح. وهذا مقصود حتى لا تنتظرنا إعادة البناء أثناء حادثة أبدًا. وتشغيل نزال على كلستر ثانٍ يعني ترخيصًا ثانيًا. ويُظهر ذلك سجل التدقيق وتصدير الاستخدام السنوي.

وما يصلك ملف موقَّع وليس حسابًا. الصقه في الإدارة → الترخيص. ويُتحقق منه محليًا بمفتاح عام مضمَّن في المشغّل. لذلك يعمل في غرفة معزولة تمامًا ولا يُرسل شيء إلى أي مكان للتحقق منه.

ويسري الترخيص سنة واحدة. وعند انتهائه تبدأ فترة سماح مدتها 30 يومًا يستمر فيها كل شيء في العمل. وتصلك التنبيهات بدءًا من 30 يومًا قبل الانتهاء عبر البريد الإلكتروني ومقياس Prometheus وEvent في Kubernetes وكذلك في لوحة التحكم وفي CLI. وبعد فترة السماح يعود التثبيت إلى المطوّرين الثلاثة المجانيين: لا يُحذف شيء ولا تُوقف أي بيئة تعمل ولا تُقلَّص. وتبقى المعاينات مفتوحة ويبقى سجل التدقيق قابلًا للتصدير. والتجديد يعيد كل المقاعد عند الطلب التالي.

إذا لم يستطع الكلستر الوصول إلى Docker Hub

يسحب نزال صوره مرة واحدة عند التثبيت ثم لا يتصل بأي شيء خارج شبكتك. وإذا كان حتى ذلك السحب الأول مستحيلًا فانسخ الصور إلى سجل الصور الخاص بك واضبط global.imageRegistry:

helm install nezal oci://registry-1.docker.io/nezal/nezal --version 0.2.32 \
  --set global.imageRegistry=registry.example.internal/mirror \
  --namespace nezal-system --create-namespace

ويعرض الأمر kubectl get pods -n nezal-system -o jsonpath='{..image}' كل ما يسحبه التثبيت. وإذا كان الموقع بلا أي طريق إلى الإنترنت فتواصل مع [email protected]: فهناك حزمة جاهزة للعمل دون اتصال ويستحق الأمر أن نُنجزه معًا في المرة الأولى.