مهمة 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).

2. تشريح Chart

mon-chart/
├── Chart.yaml            # بيانات وصفية (اسم، إصدار)
├── values.yaml           # قيم افتراضية
└── templates/            # قوالب YAML
    ├── deployment.yaml
    ├── service.yaml
    └── _helpers.tpl      # دوال قالب مشتركة (بادئة _)

الملفات التي يبدأ اسمها بـ _ لا تنتج أي بيان: تخدم لتعريف «مساعدات» قابلة لإعادة الاستخدام عبر {{ include "nom" . }}.

3. صياغة القوالب (Go template)

المكتوبالمعروض
{{ .Values.portail.replicas }}القيمة المعرَّفة في values.yaml
{{ .Release.Name }}الاسم الذي مرّرته إلى helm install (مثلاً: hedge-dev)
{{ .Chart.Name }}اسم الـ chart (معرَّف في Chart.yaml)
{{ .Chart.AppVersion }}الإصدار التطبيقي (معرَّف في Chart.yaml)
{{ include "hedge.labels" . }}استدعاء مساعد معرَّف في _helpers.tpl
{{- ... -}}يحذف - المسافات قبل/بعد العرض
{{ .Values.env | quote }}يضيف علامات اقتباس حول القيمة
{{ .Values.replicas | default 1 }}يستخدم 1 إن لم تُعرَّف القيمة

4. أوامر Helm الخمسة التي ستستخدمها

powershell
helm lint ./chart                                              # التحقق من الصياغة
helm template <release> ./chart -f values-<env>.yaml           # عرض جاف (بلا نشر)
helm install <release> ./chart -f values-<env>.yaml -n <ns>    # نشر حقيقي
helm upgrade <release> ./chart -f values-<env>.yaml -n <ns>    # تعديل تزايدي
helm rollback <release> <revision> -n <ns>                     # رجوع للخلف

5. الكلمة المفتاح « 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 نفسه في ثلاثة نطاقات متمايزة، لكل منها معاملاته:

المعاملDEVSTAGINGPROD
النطاقhedge-devhedge-staginghedge-prod
اسم الإصدارhedge-devhedge-staginghedge-prod
لون الشريطأزرق #2563ebبرتقالي #ea580cأخضر #16a34a
الرسالة« بيئة تطوير… »« ما قبل الإنتاج — بيانات اختبار فقط »« إنتاج — لكل فعل أثر حقيقي »
نسخ portail123
نسخ api123
المنفذ المعروض (NodePort)301303013130132
عنوان الاختبارhttp://localhost:30130http://localhost:30131http://localhost:30132

في نهاية التدريب، تفتح ثلاثة تبويبات جنباً إلى جنب وترى ثلاث لوحات ملوّنة باختلاف، كل منها تعرض بيئتها وإصدارها وPodsها وحالة خلفيتها.


توزيع الملفات

تنطلق من الشجرة التالية — يوفّر الملحق المحتوى الدقيق لكل ملف:

projet13-kubernetes-helm-tp/
├── 00-ENONCE.md                              <- هذا المستند

├── apps/                                     <- الشفرة (الملحق أ) — لا تُعدَّل
│   ├── portail/
│   │   ├── app.py
│   │   ├── requirements.txt
│   │   └── Dockerfile
│   └── api/
│       ├── app.py
│       ├── requirements.txt
│       └── Dockerfile

├── chart/                                    <- الـ CHART المطلوب إكماله
│   ├── Chart.yaml                            <- هيكل (الملحق ب)
│   ├── values.yaml                           <- قيم افتراضية (الملحق ب)
│   │
│   ├── environments/                         <- دورك أنت
│   │   ├── values-dev.yaml                   <- هيكل TODO (الملحق ب)
│   │   ├── values-staging.yaml               <- هيكل TODO (الملحق ب)
│   │   └── values-prod.yaml                  <- هيكل TODO (الملحق ب)
│   │
│   ├── templates/                            <- دورك أنت
│   │   ├── _helpers.tpl                      <- هيكل TODO (الملحق ب)
│   │   ├── portail-deployment.yaml           <- هيكل TODO (الملحق ب)
│   │   ├── portail-service.yaml              <- هيكل TODO (الملحق ب)
│   │   ├── api-deployment.yaml               <- هيكل TODO (الملحق ب)
│   │   └── api-service.yaml                  <- هيكل TODO (الملحق ب)
│   │
│   └── casses/                               <- مقدَّمة لكنها معيبة (الملحق ج)
│       ├── casse-1-configmap.yaml
│       ├── casse-2-worker-deployment.yaml
│       └── casse-3-cache-deployment.yaml

├── outils/
│   └── valider.ps1                           <- مقدَّم (الملحق د)

└── RAPPORT.md                                <- تكتبه أنت

نقطة حاسمة: ملفات chart/casses/ ليست في chart/templates/. إذن لا يحمّلها Helm تلقائياً. ستطلب منك المهمة 6 نسخها واحداً واحداً إلى templates/ لمراقبة الخطأ، ثم إصلاحها قبل الإبقاء عليها.


قواعد اللعبة

  1. حظر مطلق لتعديل المجلد apps/. الشفرة التطبيقية مكتوبة مسبقاً — أنت DevOps، لا المطوّر.
  2. تعدّل فقط ملفات المجلد chart/.
  3. لا ضبط بيئي مرمَّز ثابتاً في قالب: replicas، nodePort، لون، رسالة، بيئة — كل شيء يجب أن يأتي من .Values.*.
  4. يجب أن تختلف ملفات values-<env>.yaml الثلاثة فقط بالقيم التي تميّز DEV و STAGING و PROD. ملف values-prod.yaml يعيد تعريف image.repository أو service.targetPort بلا داعٍ خطأ — هذه الأشياء تأتي من values.yaml.
  5. تعمل على Kubernetes المدمج في Docker Desktop.

التحضير

المتطلبات السابقة — تُتحقَّق مرة واحدة

  1. Docker Desktop شغّال وKubernetes مفعَّل (Settings → Kubernetes → Enable Kubernetes).
  2. لدى Docker Desktop 4 غيغابايت RAM مخصَّصة على الأقل (Settings → Resources → Memory ≥ 4 GB). يشغّل هذا التدريب 12 Pod معاً (1+1 + 2+2 + 3+3).
  3. Helm مثبَّت:
    powershell
    helm version --short           # يجب أن يعرض v3.x أو v4.x
    وإلا: winget install Helm.Helm (أو choco install kubernetes-helm).
  4. أنت على العنقود الصحيح:
    powershell
    kubectl config use-context docker-desktop
    kubectl get nodes              # docker-desktop   Ready

بناء الصور

يشير الـ chart إلى صورتين محليتين يجب بناؤهما مرة واحدة:

powershell
docker build -t hedge-portail:1.0 .\apps\portail
docker build -t hedge-api:1.0     .\apps\api

docker 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 بـ ثلاثة مساعدات:

  1. hedge.fullname — يعيد {{ .Release.Name }}-<composant> (مثلاً: hedge-dev-portail).
  2. hedge.labels — يعيد الوسوم القياسية:
    • app.kubernetes.io/name
    • app.kubernetes.io/instance
    • app.kubernetes.io/component
    • app.kubernetes.io/managed-by
    • app.kubernetes.io/version
    • helm.sh/chart
    • hedge/environment
  3. hedge.selectorLabels — يعيد فقط name و instance و component (الوسوم الثلاثة المضمونة غير القابلة للتغيير لهذه النسخة).

قيد قوي: استخدم هذه المساعدات في كل قوالبك. لا اسم مورد ثابت، لا وسم منسوخ يدوياً.

تلميح: لتمرير عدة قيم إلى مساعد، استخدم dict:

yaml
{{ include "hedge.labels" (dict "root" . "composant" "portail") | nindent 4 }}

يتلقى المساعد عندئذ .root.Release.Name و .root.Values... و .composant.


المهمة 4 — ثلاث بيئات جنباً إلى جنب (20 نقطة)

أنشئ الملفات الثلاثة في chart/environments/ — كل منها يعيد تعريف فقط القيم التي تميّز بيئته.

الملفenvironmentreplicas (بوابة + api)nodePort (بوابة)اللونرسالة مقترحة
values-dev.yamldev130130#2563eb« بيئة تطوير — انتبه، قد يتغيّر كل شيء »
values-staging.yamlstaging230131#ea580c« ما قبل الإنتاج — بيانات اختبار فقط »
values-prod.yamlprod330132#16a34a« إنتاج — لكل فعل أثر حقيقي »

نشر البيئات الثلاث:

powershell
helm install hedge-dev     .\chart -f .\chart\environments\values-dev.yaml     -n hedge-dev     --create-namespace
helm install hedge-staging .\chart -f .\chart\environments\values-staging.yaml -n hedge-staging --create-namespace
helm install hedge-prod    .\chart -f .\chart\environments\values-prod.yaml    -n hedge-prod    --create-namespace

انتظر حتى تصبح الـ Pods جاهزة (~30 ث):

powershell
kubectl wait --for=condition=ready pod --all -n hedge-dev     --timeout=120s
kubectl wait --for=condition=ready pod --all -n hedge-staging --timeout=120s
kubectl wait --for=condition=ready pod --all -n hedge-prod    --timeout=120s

افتح اللوحات الثلاث:

powershell
start http://localhost:30130       # DEV — شريط أزرق، نسخة واحدة
start http://localhost:30131       # STAGING — شريط برتقالي، نسختان
start http://localhost:30132       # PROD — شريط أخضر، 3 نسخ

المتوقع: ثلاث صفحات بألوان مختلفة، كل منها تعرض بيئتها وإصدارها وPodsها وخلفيتها بـ OK أخضر.


المهمة 5 — ترقية ثم تراجع (10 نقاط)

حاكِ حادث إنتاج، ثم ألغه.

السيناريو:

  1. في DEV، انتقل بـ portail.replicas إلى 5:
    powershell
    helm upgrade hedge-dev .\chart -f .\chart\environments\values-dev.yaml --set portail.replicas=5 -n hedge-dev
  2. تحقّق أن 5 Pods بوابة تعمل:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
  3. راجع التاريخ:
    powershell
    helm history hedge-dev -n hedge-dev
    ترى مراجعتين على الأقل.
  4. ألغِ الترقية بالعودة إلى المراجعة 1:
    powershell
    helm rollback hedge-dev 1 -n hedge-dev
  5. تحقّق أننا عدنا إلى Pod بوابة واحد، وأن التاريخ يعرض مراجعة جديدة من النوع Rollback:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
    helm 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/ على ثلاثة قوالب مكتوبة مسبقاً تُترجَم لكنها تُدخل كل منها خطأ حقيقياً يُصادَف في المؤسسات. يجب، لكل واحد:

  1. نسخه إلى chart/templates/؛
  2. إعادة إنتاج العَرَض الموصوف أعلى الملف؛
  3. تشخيص السبب بقراءة رسالة الخطأ؛
  4. الإصلاح (بتعديل القالب في chart/templates/، لا الأصل في casses/
  5. إثبات أن العطل اختفى.
الملفالمكوّن المضافطبيعة الخطأ
casse-1-configmap.yamlConfigMap عامتصادم اسم بين الإصدارات
casse-2-worker-deployment.yamlDeployment workerانتهاك مُنتقي غير قابل للتغيير عند أول helm upgrade
casse-3-cache-deployment.yamlDeployment cacheمسار قيمة خاطئ (خطأ إملائي صامت)

تلميح تحقيق:

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).

التحقق التلقائي

يعطيك مخطوط درجتك في أي لحظة:

powershell
.\outils\valider.ps1

مثال مخرجات لعمل منجز جزئياً:

[OK]    Mission 1 - Chart valide.............. 10/10
[OK]    Mission 2 - Templating de base........ 20/20
[ECHEC] Mission 3 - Helpers et labels.........  0/15   -> helper hedge.selectorLabels manquant
[OK]    Mission 4 - Trois environnements...... 20/20
[ECHEC] Mission 5 - Upgrade + rollback........  0/10   -> aucun rollback detecte dans l'historique
[OK]    Mission 6 - Reparations (3 pannes).... 14/20   -> casse-3 : typo .Values.portal toujours present

SCORE AUTOMATIQUE : 64 / 95

إن رفض PowerShell تنفيذ المخطوط (l'exécution de scripts est désactivée)، استخدم:

powershell
powershell -ExecutionPolicy Bypass -File .\outils\valider.ps1

المخرجات

  1. المجلد chart/ كاملاً، بحالة عمل (helm lint نظيف).
  2. ملف RAPPORT.md يحتوي:
    • مخرجات helm list -A تُظهر إصداراتك الثلاثة؛
    • لقطة لكل بيئة (3 لوحات ملوّنة)؛
    • التاريخ الكامل لـ hedge-dev (مع ترقية + تراجع)؛
    • لـ كل عطل في المهمة 6: أمر تشخيص، سبب، تصحيح، دليل؛
    • إجاباتك على أسئلة التفكير.
  3. المخرجات النهائية لـ .\outils\valider.ps1.

أسئلة للتفكير

  1. لماذا الحقل spec.selector.matchLabels غير قابل للتغيير في Kubernetes؟ أي مشكلة يحل هذا القيد؟
  2. لديك 3 بيئات اليوم. غداً يطلب فريق DevSecOps رابعة (« pre-prod »). أي ملفات تنشئ وأيها لا تلمس؟
  3. ما الفرق بين helm upgrade --set replicas=5 و kubectl scale، من جهة التتبّع والتراجع؟
  4. تعرض البوابة « backend OK » — لماذا هذه المعلومة أوثق من مجرد kubectl get svc api؟
  5. ماذا يحدث إن حذفت Podاً بـ kubectl delete pod، بينما أنشأه Deployment عبر Helm؟ هل Helm على علم بـ «الفقدان»؟
  6. يقترح زميل وضع app.kubernetes.io/version: {{ .Chart.AppVersion }} في matchLabels لـ Deployment. ماذا تجيبه؟

السلم

العنصرالنقاط
المهمة 1 — Chart صالح و lint نظيف10
المهمة 2 — قولبة البوابة + api20
المهمة 3 — مساعدات ووسوم قابلة لإعادة الاستخدام15
المهمة 4 — ثلاث بيئات جنباً إلى جنب20
المهمة 5 — ترقية + تراجع مُتتبَّعان10
المهمة 6 — تشخيص + إصلاح الأعطال الثلاثة20
جودة التقرير وتبرير الاختيارات5
مكافأة — المهمة 7+5
المجموع100 (+5)

عقوبات:

  • −10 لكل قيمة بيئية مرمَّزة ثابتة في قالب (replicas: 3 حرفي بدل .Values....).
  • −5 لكل إعادة تعريف بلا داعٍ في values-<env>.yaml (قيمة لا سبب لاختلافها بين البيئات).
  • −10 لكل تعديل لملف في apps/.

صندوق أدوات Helm

powershell
# تحليل (بلا نشر)
helm lint .\chart                                                        # صياغة + أفضل ممارسات
helm template <release> .\chart -f <values.yaml>                         # عرض كامل
helm template <release> .\chart -f <values.yaml> --show-only templates/<fichier>   # عرض موجَّه
helm template <release> .\chart -f <values.yaml> --debug                 # مع آثار
helm show values .\chart                                                 # قيم افتراضية

# النشر
helm install <release> .\chart -f <values.yaml> -n <ns> --create-namespace
helm upgrade <release> .\chart -f <values.yaml> -n <ns>
helm upgrade <release> .\chart -f <values.yaml> --set portail.replicas=5 -n <ns>
helm rollback <release> <revision> -n <ns>
helm uninstall <release> -n <ns>

# المراقبة
helm list -A                                                             # كل الإصدارات
helm status <release> -n <ns>
helm history <release> -n <ns>
helm get values <release> -n <ns>                                        # القيم النشطة
helm get manifest <release> -n <ns>                                      # البيانات المطبَّقة

الردود الثلاثة عند خطأ:

  1. ابدأ دائماً بـ helm template — عرض جاف، بلا مخاطر، يُظهر تماماً ما سيُرسَل إلى Kubernetes.
  2. اقرأ المسار في رسالة الخطأ — يعطي Helm دائماً الملف + السطر + المسار .Values.* الخاطئ.
  3. helm get manifest يعرض ما هو حالياً في العنقود (مفيد للمقارنة مع ما يولّده قالبك الجديد).


الملحق أ — التطبيقات

لا تعدّل أياً من هذه الملفات. انسخها كما هي إلى المسارات المشار إليها.

أ.1 — البوابة (لوحة متعددة البيئات)

كل القيم المعروضة تأتي من متغيرات بيئة يحقنها Helm. تتصرف الصورة نفسها باختلاف حسب env: للـ Deployment.

الملف: apps/portail/app.py

python
"""Portail — tableau de bord multi-environnement.

Ce Pod affiche l'environnement dans lequel il tourne (DEV / STAGING / PROD),
la version applicative, le nombre de replicas, et l'etat du backend.

Toutes les valeurs affichees viennent de VARIABLES D'ENVIRONNEMENT injectees
par Helm depuis values-<env>.yaml. Le meme code s'adapte a chaque
environnement sans aucune modification.
"""

import os
import socket
import time
import urllib.error
import urllib.request

from flask import Flask, jsonify, request

app = Flask(__name__)
DEMARRAGE = time.time()


def cfg():
    return {
        "env": os.environ.get("ENVIRONMENT", "inconnu"),
        "version": os.environ.get("APP_VERSION", "0.0.0"),
        "theme": os.environ.get("THEME_COLOR", "#64748b"),
        "message": os.environ.get("BANNIERE_MESSAGE", "Deploye avec Helm"),
        "backend_url": os.environ.get("BACKEND_URL", "http://api"),
        "replicas_info": os.environ.get("REPLICAS_INFO", "?"),
        "pod": socket.gethostname(),
        "uptime": int(time.time() - DEMARRAGE),
    }


def tester_backend(url):
    try:
        with urllib.request.urlopen(url + "/ping", timeout=1.5) as reponse:
            corps = reponse.read(200).decode("utf-8", "ignore")
        return "ok", corps.strip()
    except urllib.error.HTTPError as err:
        return "http", "HTTP %s" % err.code
    except Exception as err:
        return "ko", type(err).__name__


@app.route("/health")
def health():
    return "OK", 200


@app.route("/api-json")
def api_json():
    """مسار مفيد للتحقق التلقائي."""
    c = cfg()
    etat, detail = tester_backend(c["backend_url"])
    return jsonify(pod=c["pod"], env=c["env"], version=c["version"],
                   backend=etat, backend_detail=detail, uptime=c["uptime"])


@app.route("/")
def accueil():
    c = cfg()
    etat, detail = tester_backend(c["backend_url"])
    # ... (قالب HTML كامل في الملف — غير مكرر هنا ليبقى مقروءاً)

الملف الكامل مقدَّم في apps/portail/app.py.

الملف: apps/portail/requirements.txt

text
flask==3.0.3

الملف: apps/portail/Dockerfile

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]

أ.2 — الواجهة البرمجية (خلفية بسيطة)

الملف: apps/api/app.py

python
"""API — backend simple pour l'exemple Helm."""

import os
import socket
import time

from flask import Flask, jsonify

app = Flask(__name__)
DEMARRAGE = time.time()

ENV = os.environ.get("ENVIRONMENT", "inconnu")
VERSION = os.environ.get("APP_VERSION", "0.0.0")


@app.route("/")
@app.route("/ping")
def ping():
    return jsonify(service="api", env=ENV, version=VERSION,
                   pod=socket.gethostname(),
                   uptime=int(time.time() - DEMARRAGE))


@app.route("/health")
def health():
    return "OK", 200


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

الملف: apps/api/requirements.txt

text
flask==3.0.3

الملف: apps/api/Dockerfile

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]


الملحق ب — هيكل الـ Chart

انسخ هذه الملفات، ثم استبدل كل TODO بالقيمة الصحيحة. الأسطر المسبوقة بـ # ? أسئلة للحسم: عليك أن تقرّر كيف تكمل الشفرة.

الملف: chart/Chart.yaml

yaml
# ? املأ الحقول الإلزامية لـ Chart Helm.
# ? يجب أن تساوي apiVersion القيمة v2 (v1 مهجورة منذ Helm 3).
# ? type هو "application" (مقابل "library").

apiVersion: TODO
name: hedge
description: TODO
type: TODO
version: 0.1.0
appVersion: "1.0.0"

الملف: chart/values.yaml

yaml
# values.yaml — قيم افتراضية لـ chart hedge.
# توفّر كل بيئة ملفاً values-<env>.yaml يتجاوز
# هذه القيم فوقها. أبقِ هذا الملف محايداً (لا قيمة خاصة
# ببيئة).

environment: default

banniere:
  message: "Chart Helm — application multi-environnement"
  couleur: "#64748b"

portail:
  image:
    repository: hedge-portail
    tag: "1.0"
    pullPolicy: IfNotPresent
  replicas: 1
  service:
    type: NodePort
    port: 80
    targetPort: 5000
    nodePort: 30130

api:
  image:
    repository: hedge-api
    tag: "1.0"
    pullPolicy: IfNotPresent
  replicas: 1
  service:
    type: ClusterIP
    port: 80
    targetPort: 8000

الملف: chart/templates/_helpers.tpl

yaml
{{/*
اسم كامل لمورد: "<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" -}}
# ? املأ الوسوم السبعة المطلوبة في المهمة 3
app.kubernetes.io/name: TODO
app.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/v1
kind: Deployment
metadata:
  name: TODO
  labels:
    TODO
spec:
  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: v1
kind: Service
metadata:
  name: TODO
  labels:
    TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO
      # ? nodePort فقط إن كان type == NodePort

الملف: chart/templates/api-deployment.yaml

yaml
# ? الهيكل نفسه لـ portail-deployment.yaml، لكن:
#   - المكوّن = "api"
#   - متغيرات البيئة: ENVIRONMENT و APP_VERSION فقط
#   - منفذ الحاوية = .Values.api.service.targetPort (8000)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: TODO
spec:
  # ... (هيكل مشابه للبوابة) ...

الملف: chart/templates/api-service.yaml

yaml
# ? خدمة ClusterIP لـ api. منفذ واحد. بلا nodePort.
apiVersion: v1
kind: Service
metadata:
  name: TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO

الملف: chart/environments/values-dev.yaml

yaml
# ? بيئة DEV: نسخة واحدة، شريط أزرق #2563eb، NodePort 30130.
environment: TODO

banniere:
  message: TODO
  couleur: TODO

portail:
  replicas: TODO
  service:
    nodePort: TODO

api:
  replicas: TODO

الملف: chart/environments/values-staging.yaml

yaml
# ? بيئة STAGING: نسختان، شريط برتقالي #ea580c، NodePort 30131.
environment: TODO
# ... أكمل على نموذج values-dev.yaml ...

الملف: chart/environments/values-prod.yaml

yaml
# ? بيئة PROD: 3 نسخ، شريط أخضر #16a34a، NodePort 30132.
environment: TODO
# ...


الملحق ج — الأعطال الثلاثة المطلوب إصلاحها

كل ملف أدناه موجود في chart/casses/. لا تعدّل الأصول — انسخها إلى chart/templates/، أعد إنتاج الخطأ، ثم صحّح النسخة.

الملف: chart/casses/casse-1-configmap.yaml

yaml
# العطل 1
# العَرَض: انشر hedge-dev، ثم حاول نشر hedge-staging
# في النطاق نفسه. يفشل التثبيت الثاني بـ:
#   ConfigMap "hedge-config" ... exists and cannot be imported ...
apiVersion: v1
kind: ConfigMap
metadata:
  name: hedge-config
  labels:
    {{- include "hedge.labels" (dict "root" . "composant" "config") | nindent 4 }}
data:
  timezone: "America/Toronto"
  langue: "fr-CA"

الملف: chart/casses/casse-2-worker-deployment.yaml

yaml
# العطل 2
# العَرَض: يعمل "helm install". يفشل "helm upgrade" مع
# --set environment=recette بـ:
#   spec.selector: Invalid value: ...: field is immutable
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "hedge.fullname" (dict "root" . "composant" "worker") }}
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name }}
      app.kubernetes.io/instance: {{ .Release.Name }}
      app.kubernetes.io/component: worker
      hedge/environment: {{ .Values.environment | quote }}
  template:
    metadata:
      labels:
        {{- include "hedge.labels" (dict "root" . "composant" "worker") | nindent 8 }}
    spec:
      containers:
        - name: worker
          image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"

الملف: chart/casses/casse-3-cache-deployment.yaml

yaml
# العطل 3
# العَرَض: يفشل "helm install" بـ:
#   Error: template ...at <.Values.portal.replicas>:
#   nil pointer evaluating interface {}.replicas
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "hedge.fullname" (dict "root" . "composant" "cache") }}
spec:
  replicas: {{ .Values.portal.replicas }}
  selector:
    matchLabels:
      {{- include "hedge.selectorLabels" (dict "root" . "composant" "cache") | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "hedge.selectorLabels" (dict "root" . "composant" "cache") | nindent 8 }}
    spec:
      containers:
        - name: cache
          image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"


الملحق د — مخطوط التحقق

الملف outils/valider.ps1 مقدَّم كما هو. لا يعطي أي حل — فقط درجة وأول نقطة للتصحيح. نفّذه في أي لحظة:

powershell
.\outils\valider.ps1

ما يتحقق منه المخطوط:

المهمةالمعايير التلقائية
1يجتاز helm lint، يملك Chart.yaml apiVersion: v2 و type: application
2ينتج helm template فعلاً 2 Deployments و 2 Services، أسماء مسبوقة بالإصدار
3يعرّف _helpers.tpl المساعدات الثلاثة، الوسوم القياسية موجودة
4ملفات values-<env>.yaml الثلاثة موجودة بالقيم الصحيحة (بيئة، نسخ، منفذ، لون)، الإصدارات الثلاثة منشورة
5للإصدار hedge-dev ≥ 2 مراجعة وتراجع في التاريخ
6لا hedge-config ثابت، لا وسم متغير في matchLabels، لا .Values.portal (بخطأ إملائي)

لا ينفّذ المخطوط بنفسه أي أمر helm install: عليك أنت النشر قبل التحقق.


دورة من إعداد د. هيثم رحومة — تطوير ونشر حلول البيانات