مهمة Helm: إضفاء الطابع الصناعي على نشر متعدد البيئات
12 دقيقة
المشروع 13 — Helm على Kubernetes · المستوى متوسط → متقدم · المدة المقدَّرة: 4 إلى 6 ساعات
تنطلق من تطبيق مكوَّن من خدمتَي Python (portail مرئي و api خلفية)، وستنشره ثلاث مرات جنباً إلى جنب — في DEV (أزرق)، STAGING (برتقالي)، PROD (أخضر) — بـ Chart Helm واحد وثلاثة ملفات قيم. في النهاية ستجري helm upgrade ثم helm rollback، وتصلح ثلاثة قوالب مليئة بأخطاء حقيقية تُرى في المؤسسات.
تعمل في فريق يجب أن تمر كل نسخة جديدة لتطبيق عبر ثلاث بيئات:
DEV — صندوق رمل للمطوّرين، بيانات قابلة للرمي، نسخة واحدة.
STAGING — ما قبل الإنتاج، بيانات اختبار، نسختان للتحقق من قابلية التوسّع.
PROD — إنتاج، بيانات حقيقية، ثلاث نسخ حداً أدنى، لا توقف مسموح.
اليوم تنسخ الفريق وتلصق بيانات YAML نفسها لكل بيئة مغيّرة يدوياً القيم المختلفة. النتيجة: تنحرف الملفات، رقعة مطبَّقة في التطوير لا تصل إلى الإنتاج، ويطلب النشر نصف يوم.
مهمتك: إضفاء الطابع الصناعي على كل ذلك بـ Helm. Chart واحد، ثلاثة ملفات قيم، أمر لكل بيئة. ستثبت أن ذلك يعمل بعرض ثلاث لوحات جنباً إلى جنب في متصفحك — لكل منها لونها وعدد Podsها ورسالتها.
مفاهيم أساسية قبل البدء
هذا المستند مكتفٍ بذاته. لا تحتاج إلى أي مرجع خارجي لإنهائه.
1. دور Helm في جملة واحدة
يولّد Helm بيانات Kubernetes من قوالب ومتغيرات. حيث يأخذ kubectl apply YAML ثابتاً، يأخذ Helm قالب YAML وملف قيم، ينتج YAML النهائي، ثم يطبّقه كوحدة مُصدَّرة تُسمّى إصداراً (release).
الإصدار (release) هو تثبيت لـ chart. يمكن تثبيت الـ chart نفسه عدة مرات، كل مرة باسم إصدار مختلف (hedge-dev، hedge-staging، hedge-prod) — هذا أساس تعدد البيئات.
يتغيّر {{ .Release.Name }} في كل تثبيت، يبقى {{ .Chart.Name }} مطابقاً. احفظ هذا التباين: هو مركزي.
6. القاعدة الذهبية للمُنتقي غير القابل للتغيير
الحقل spec.selector.matchLabels لـ Deployment يُثبَّت مرة واحدة وإلى الأبد عند الإنشاء. إن وضع قالبك في هذا الحقل قيمة قد تتغيّر (مثل إصدار، بيئة، تاريخ)، سيعمل أول helm install، لكن أول helm upgrade سيفشل بـ:
spec.selector: Invalid value: ...: field is immutable
قاعدة تُحفَر في الرخام: في matchLabels، ضع فقط أشياء لن تتغيّر أبداً لهذه النسخة — نموذجياً name و instance و component.
المعمارية المستهدفة: DEV / STAGING / PROD
ستنشر الـ chart نفسه في ثلاثة نطاقات متمايزة، لكل منها معاملاته:
المعامل
DEV
STAGING
PROD
النطاق
hedge-dev
hedge-staging
hedge-prod
اسم الإصدار
hedge-dev
hedge-staging
hedge-prod
لون الشريط
أزرق#2563eb
برتقالي#ea580c
أخضر#16a34a
الرسالة
« بيئة تطوير… »
« ما قبل الإنتاج — بيانات اختبار فقط »
« إنتاج — لكل فعل أثر حقيقي »
نسخ portail
1
2
3
نسخ api
1
2
3
المنفذ المعروض (NodePort)
30130
30131
30132
عنوان الاختبار
http://localhost:30130
http://localhost:30131
http://localhost:30132
في نهاية التدريب، تفتح ثلاثة تبويبات جنباً إلى جنب وترى ثلاث لوحات ملوّنة باختلاف، كل منها تعرض بيئتها وإصدارها وPodsها وحالة خلفيتها.
توزيع الملفات
تنطلق من الشجرة التالية — يوفّر الملحق المحتوى الدقيق لكل ملف:
نقطة حاسمة: ملفات chart/casses/ليست في chart/templates/. إذن لا يحمّلها Helm تلقائياً. ستطلب منك المهمة 6 نسخها واحداً واحداً إلى templates/ لمراقبة الخطأ، ثم إصلاحها قبل الإبقاء عليها.
قواعد اللعبة
حظر مطلق لتعديل المجلد apps/. الشفرة التطبيقية مكتوبة مسبقاً — أنت DevOps، لا المطوّر.
تعدّل فقط ملفات المجلد chart/.
لا ضبط بيئي مرمَّز ثابتاً في قالب: replicas، nodePort، لون، رسالة، بيئة — كل شيء يجب أن يأتي من .Values.*.
يجب أن تختلف ملفات values-<env>.yaml الثلاثةفقط بالقيم التي تميّز DEV و STAGING و PROD. ملف values-prod.yaml يعيد تعريف image.repository أو service.targetPort بلا داعٍ خطأ — هذه الأشياء تأتي من values.yaml.
لدى Docker Desktop 4 غيغابايت RAM مخصَّصة على الأقل (Settings → Resources → Memory ≥ 4 GB). يشغّل هذا التدريب 12 Pod معاً (1+1 + 2+2 + 3+3).
Helm مثبَّت:
powershell
helm version --short # يجب أن يعرض v3.x أو v4.x
وإلا: winget install Helm.Helm (أو choco install kubernetes-helm).
أنت على العنقود الصحيح:
powershell
kubectl config use-context docker-desktopkubectl get nodes # docker-desktop Ready
بناء الصور
يشير الـ chart إلى صورتين محليتين يجب بناؤهما مرة واحدة:
powershell
docker build -t hedge-portail:1.0 .\apps\portaildocker build -t hedge-api:1.0 .\apps\apidocker images | Select-String "^hedge" # يجب أن يعرض الصورتين
تذكير: يشارك Docker Desktop عفريته مع Kubernetes؛ لا خطوة «تحميل» لازمة (عكس kind أو minikube).
المهام
المهمة 1 — إحياء Chart أدنى (10 نقاط)
أكمل chart/Chart.yaml (اسم، apiVersion، نوع، إصدار، appVersion). ثم تحقّق:
powershell
helm lint .\chart# يجب أن يعرض: 1 chart(s) linted, 0 chart(s) failed
المتوقع: Chart يجتاز lint بلا خطأ.
المهمة 2 — قولبة البوابة و api (20 نقطة)
أكمل الملفات الأربعة في chart/templates/:
portail-deployment.yaml — Deployment يستخدم .Values.portail.replicas و .Values.portail.image.* ويحقن متغيرات البيئة ENVIRONMENT و APP_VERSION و THEME_COLOR و BANNIERE_MESSAGE و BACKEND_URL و REPLICAS_INFO.
portail-service.yaml — خدمة NodePort تشير إلى Pods البوابة.
api-deployment.yaml — Deployment لـ api (المتغيران ENVIRONMENT و APP_VERSION).
api-service.yaml — خدمة ClusterIP.
النقطة الأساس: يجب أن يحتوي متغير BACKEND_URL للبوابة على اسم خدمة api المبني بـ {{ .Release.Name }} (مثلاً http://hedge-dev-api)، لا اسماً ثابتاً.
التحقق:
powershell
helm template check .\chart -f .\chart\environments\values-dev.yaml# يجب أن يعرض 2 Deployments + 2 Services، كلها مسبوقة بـ "check-"
المهمة 3 — كتابة مساعدات نظيفة (15 نقطة)
أكمل chart/templates/_helpers.tpl بـ ثلاثة مساعدات:
kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
راجع التاريخ:
powershell
helm history hedge-dev -n hedge-dev
ترى مراجعتين على الأقل.
ألغِ الترقية بالعودة إلى المراجعة 1:
powershell
helm rollback hedge-dev 1 -n hedge-dev
تحقّق أننا عدنا إلى Pod بوابة واحد، وأن التاريخ يعرض مراجعة جديدة من النوع Rollback:
powershell
kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portailhelm history hedge-dev -n hedge-dev
سؤال يُعالَج في التقرير: ما الفرق الجوهري بين helm upgrade --set portail.replicas=5 و kubectl scale deploy/hedge-dev-portail --replicas=5؟ لماذا يفضّل Helm أن تمر عبره؟
المهمة 6 — تحقيق: إصلاح 3 قوالب معيبة (20 نقطة)
يحتوي المجلد chart/casses/ على ثلاثة قوالب مكتوبة مسبقاً تُترجَم لكنها تُدخل كل منها خطأ حقيقياً يُصادَف في المؤسسات. يجب، لكل واحد:
نسخه إلى chart/templates/؛
إعادة إنتاج العَرَض الموصوف أعلى الملف؛
تشخيص السبب بقراءة رسالة الخطأ؛
الإصلاح (بتعديل القالب في chart/templates/، لا الأصل في casses/)؛
إثبات أن العطل اختفى.
الملف
المكوّن المضاف
طبيعة الخطأ
casse-1-configmap.yaml
ConfigMap عام
تصادم اسم بين الإصدارات
casse-2-worker-deployment.yaml
Deploymentworker
انتهاك مُنتقي غير قابل للتغيير عند أول helm upgrade
casse-3-cache-deployment.yaml
Deploymentcache
مسار قيمة خاطئ (خطأ إملائي صامت)
تلميح تحقيق:
powershell
# عرض جاف لقالب واحد (لا ينشر شيئاً)helm template hedge-dev .\chart -f .\chart\environments\values-dev.yaml ` --show-only templates/casse-3-cache-deployment.yaml --debug
يطبع هذا الأمر تماماً ما سيرسله Helm إلى Kubernetes. هو أداتك الأولى للتشخيص — استخدمه بلا اعتدال.
المهمة 7 — مكافأة: الصقل (5 نقاط)
حسب اختيارك، واحد فقط يكفي:
أ) أضف خطاف pre-install (Job) يعرض Bienvenue dans <environnement> في سجلات Helm. يجب أن ينتظر الإصدار نهاية الـ Job قبل المتابعة.
ب) اجعل عدد النسخ ديناميكياً بقيمة values.yaml ذات هيكل متداخل (مثلاً portail.autoscaling.enabled، portail.autoscaling.min، portail.autoscaling.max) واجعل يُولَّد شرطياً HorizontalPodAutoscaler حسب .enabled.
ج) أضف NOTES.txt في templates/ يعرض، بعد كل helm install، العنوان الدقيق لفتح اللوحة (بـ nodePort الصحيح حسب Values).
انسخ هذه الملفات، ثم استبدل كل TODO بالقيمة الصحيحة.
الأسطر المسبوقة بـ # ?أسئلة للحسم: عليك أن تقرّر كيف تكمل الشفرة.
الملف: chart/Chart.yaml
yaml
# ? املأ الحقول الإلزامية لـ Chart Helm.# ? يجب أن تساوي apiVersion القيمة v2 (v1 مهجورة منذ Helm 3).# ? type هو "application" (مقابل "library").apiVersion: TODOname: hedgedescription: TODOtype: TODOversion: 0.1.0appVersion: "1.0.0"
{{/*اسم كامل لمورد: "<release>-<composant>".الاستخدام: {{ include "hedge.fullname" (dict "root" . "composant" "portail") }}*/}}{{- define "hedge.fullname" -}}{{- printf "TODO" .root.Release.Name .composant | trunc 63 | trimSuffix "-" -}}{{- end -}}{{/*وسوم مشتركة لكل الموارد.الاستخدام: {{ include "hedge.labels" (dict "root" . "composant" "portail") | nindent 4 }}*/}}{{- define "hedge.labels" -}}# ? املأ الوسوم السبعة المطلوبة في المهمة 3app.kubernetes.io/name: TODOapp.kubernetes.io/instance: TODO# ... تابع ...{{- end -}}{{/*وسوم المُنتقي: مجموعة فرعية مستقرة من الوسوم.ضع هنا فقط وسوماً لن تتغيّر أبداً لنسخة.*/}}{{- define "hedge.selectorLabels" -}}# ? الوسوم الثلاثة غير القابلة للتغيير حصراً{{- end -}}
الملف: chart/templates/portail-deployment.yaml
yaml
# ? Deployment البوابة. استخدم:# - .Values.portail.replicas# - .Values.portail.image.{repository,tag,pullPolicy}# - .Values.portail.service.targetPort# - .Values.environment# - .Values.banniere.{couleur,message}# - .Chart.AppVersion (لـ APP_VERSION)# - اسم خدمة api المبني بـ .Release.Name (لـ BACKEND_URL)apiVersion: apps/v1kind: Deploymentmetadata: name: TODO labels: TODOspec: replicas: TODO selector: matchLabels: TODO template: metadata: labels: TODO spec: containers: - name: portail image: TODO imagePullPolicy: TODO ports: - containerPort: TODO env: - name: ENVIRONMENT value: TODO # ? أضف APP_VERSION، THEME_COLOR، BANNIERE_MESSAGE، # BACKEND_URL، REPLICAS_INFO readinessProbe: httpGet: path: /health port: TODO initialDelaySeconds: 3 periodSeconds: 5
الملف: chart/templates/portail-service.yaml
yaml
# ? خدمة للبوابة. نوع NodePort. استخدم الشرط# {{- if eq .Values.portail.service.type "NodePort" }} ... {{- end }}# لتضمين "nodePort" فقط إن كان النوع فعلاً NodePort.apiVersion: v1kind: Servicemetadata: name: TODO labels: TODOspec: type: TODO selector: TODO ports: - port: TODO targetPort: TODO # ? nodePort فقط إن كان type == NodePort
# ? خدمة ClusterIP لـ api. منفذ واحد. بلا nodePort.apiVersion: v1kind: Servicemetadata: name: TODOspec: type: TODO selector: TODO ports: - port: TODO targetPort: TODO
الملف: chart/environments/values-dev.yaml
yaml
# ? بيئة DEV: نسخة واحدة، شريط أزرق #2563eb، NodePort 30130.environment: TODObanniere: message: TODO couleur: TODOportail: replicas: TODO service: nodePort: TODOapi: replicas: TODO
الملف: chart/environments/values-staging.yaml
yaml
# ? بيئة STAGING: نسختان، شريط برتقالي #ea580c، NodePort 30131.environment: TODO# ... أكمل على نموذج values-dev.yaml ...