Helm وتغليف Kubernetes

الـ Hooks والاختبارات

18 دقيقة الدرس 7 من 28

الـ Hooks والاختبارات

إصدار Helm ليس مجرد تطبيق دُفعة من ملفات YAML — إنه حدث له دورة حياة كاملة. يجب ترقية مخطّطات قواعد البيانات قبل أن تبدأ الـ Pods الجديدة. يجب حقن الأسرار في Vault قبل أن يُقلع التطبيق. ويجب أن تنجح اختبارات الدخان بعد الترقية قبل إعادة توجيه حركة المرور. يكشف Helm عن كل هذه نقاط التكامل من خلال آليتين: هوكات دورة الحياة وhelm test. إتقان الاثنتين هو ما يُفرّق بين الفرق التي تُنشر في الإنتاج بثقة والفرق التي تبني أغلفة Shell هشّة حول Helm.

كيف تعمل الـ Hooks

الـ Hook هو manifest Kubernetes عادي — في أغلب الأحيان Job — يحمل التعليق التوضيحي helm.sh/hook. يعترض Helm هذا الـ manifest قبل مرحلة التطبيق العادية، وينفّذه عند نقطة دورة الحياة المحدّدة، وينتظر اكتماله، ثم يتابع. الـ manifest الخاص بالـ Hook لا يُتتبّع كمورد إصدار عادي؛ بل له سياسة حذف خاصة تُتحكّم بها عبر helm.sh/hook-delete-policy.

أهمّ الـ Hooks في الاستخدام اليومي للإنتاج:

  • pre-install — يعمل بعد تصيير القوالب وقبل إنشاء أي موارد للإصدار. يُستخدم لـ: تهيئة مخطط قاعدة البيانات، وبذر مستخدم Admin، وإنشاء ربط RBAC تعتمد عليه التطبيقات.
  • post-install — يعمل بعد إنشاء جميع موارد الإصدار وجاهزيتها. يُستخدم لـ: إرسال إشعار Slack، أو ملء ذاكرة التخزين المؤقت بالبيانات الأولية.
  • pre-upgrade — يعمل قبل تطبيق الترقية. الاستخدام الأكثر شيوعاً: تشغيل عمليات ترقية قاعدة البيانات (migrations)، أو أخذ لقطة من مورد ذي حالة قبل الترقية.
  • post-upgrade — يعمل بعد اكتمال الترقية. يُستخدم لـ: تشغيل اختبارات الدخان مباشرةً، وإبطال ذاكرة تخزين CDN.
  • pre-delete — يعمل قبل حذف موارد الإصدار. يُستخدم لـ: إنهاء العمّال ذوي العمليات الطويلة، وأرشفة سجلات التدقيق، وإبطال بيانات اعتماد الخدمة.
  • pre-rollback / post-rollback — مرآة لـ hooks الترقية، نادراً ما تُحتاج لكنها قيّمة للتطبيقات ذات الحالة التي تتطلّب سكريبتات تخفيض إصدار المخطط.
Helm lifecycle hook execution order helm install / helm upgrade lifecycle pre-install / pre-upgrade Apply Release Resources Wait for Readiness post-install / post-upgrade OK Hook Job detail (pre-upgrade: db-migrate) Job Created by Helm Pod Runs migrate.sh Helm Unblocks Job exit 0 Helm Fails Release exit non-0 → rollback helm test runs post-deploy validation pods (annotation: helm.sh/hook: test)
ترتيب تنفيذ هوكات Helm خلال دورة التثبيت والترقية، مع بيان كيفية تأخير الـ Job من نوع pre-upgrade للإصدار وكيف يُطلق الفشلُ تراجعاً تلقائياً.

كتابة Hook لترقية قاعدة البيانات قبل الإصدار (pre-upgrade)

النمط الأكثر أهمية في الإنتاج هو Job لترقية قاعدة البيانات يعمل قبل جدولة الـ Pods الجديدة. فيما يلي قالب احترافي جاهز للإنتاج:

# templates/hooks/db-migrate.yaml apiVersion: batch/v1 kind: Job metadata: name: {{ include "myapp.fullname" . }}-db-migrate-{{ .Release.Revision }} labels: {{- include "myapp.labels" . | nindent 4 }} annotations: # تشغيل قبل تطبيق Pods الجديدة للترقية "helm.sh/hook": pre-upgrade,pre-install # الوزن يُحدّد الترتيب عند وجود هوكات متعددة؛ الأصغر ينفّذ أولاً "helm.sh/hook-weight": "-5" # حذف الـ Job بعد نجاحه (يُبقي الـ namespace نظيفاً) "helm.sh/hook-delete-policy": hook-succeeded spec: # إعادة المحاولة 3 مرات قبل إفشال الإصدار backoffLimit: 3 activeDeadlineSeconds: 300 template: metadata: name: {{ include "myapp.fullname" . }}-db-migrate spec: restartPolicy: Never serviceAccountName: {{ include "myapp.serviceAccountName" . }} containers: - name: migrate image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: ["python", "manage.py", "migrate", "--noinput"] env: - name: DATABASE_URL valueFrom: secretKeyRef: name: {{ include "myapp.fullname" . }}-db-secret key: url resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi
لماذا نُلحق .Release.Revision باسم الـ Job؟ يجب أن تكون أسماء Kubernetes Jobs فريدة. بدون لاحقة المراجعة، ستفشل محاولة الترقية الثانية فوراً لأن الـ Job من المحاولة الأولى لا يزال موجوداً (أو في حالة فشل). اجعل دائماً اسم الـ Job فريداً بإضافة رقم المراجعة أو طابع زمني.

أوزان الـ Hooks وترتيبها

عند وجود هوكات متعددة من نفس النوع، يرتّبها Helm حسب القيمة الصحيحة لـ helm.sh/hook-weight — من الأصغر إلى الأكبر. الهوكات ذات الوزن نفسه تُنفَّذ بترتيب أبجدي حسب اسم المورد. هذا مهمّ حين تحتاج إلى: (1) إنشاء مستخدم Admin مؤقت في قاعدة البيانات قبل تشغيل migrations، ثم (2) تشغيل migrations، ثم (3) إبطال مستخدم Admin — ثلاثة هوكات بأوزان -10 و0 و10.

سياسات حذف الـ Hook

ثلاث قيم تُتحكّم في وقت حذف Helm لمورد الـ Hook بعد تشغيله:

  • hook-succeeded — يحذف الـ Job فقط عند اكتماله بنجاح. الـ Jobs الفاشلة تبقى لتتمكّن من فحص سجلاتها. هذا هو الخيار الافتراضي الموصى به.
  • hook-failed — يحذف حتى عند الفشل. يُستخدم حين تحتوي موارد الـ Hook على بيانات حساسة (بيانات اعتماد migration) يجب ألا تستمر.
  • before-hook-creation — يحذف أي مورد Hook سابق بنفس الاسم قبل إنشاء مورد جديد. يحلّ مشكلة تكرار الأسماء للفرق التي تُفضّل اسم Job ثابتاً.
مخاطرة إنتاجية — غياب سياسة الحذف: إن حذفتَ helm.sh/hook-delete-policy، يستخدم Helm before-hook-creation افتراضياً. هذا يعني أن Job الـ migration الفاشل يُحذف صامتاً في محاولة النشر التالية، آخذاً معه سجلاته. اضبط دائماً hook-succeeded صراحةً كي تبقى الـ Jobs الفاشلة للتشخيص، وأرسل السجلات إلى مجمّع السجلات المركزي (Loki, CloudWatch, Datadog) حتى تُحفظ بعد حذف الـ Pod.

helm test — اختبارات الدخان بعد النشر

helm test أمر متكامل يُشغّل مجموعة من Pods خاصة مُعلَّقة بـ "helm.sh/hook": test. هذه ليست اختبارات وحدة — بل هي اختبارات تحقّق من الإصدار تعمل مقابل الإصدار المُنشَر الفعلي للتحقّق من عمله. فكّر فيها كاختبارات دخان آلية يستطيع أي شخص تشغيلها: helm test <release-name>.

Pod اختبار نموذجي يتحقّق من نقطة نهاية صحة HTTP للخدمة المُنشَرة:

# templates/tests/test-connection.yaml apiVersion: v1 kind: Pod metadata: name: {{ include "myapp.fullname" . }}-test-connection labels: {{- include "myapp.labels" . | nindent 4 }} annotations: "helm.sh/hook": test "helm.sh/hook-delete-policy": hook-succeeded spec: restartPolicy: Never containers: - name: wget image: busybox:1.36 command: - sh - -c - | set -e echo "Testing HTTP health endpoint..." wget -qO- http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/healthz echo "Testing API liveness..." response=$(wget -qO- http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/api/v1/status) echo "$response" | grep -q '"status":"ok"' echo "All checks passed." resources: requests: cpu: 50m memory: 32Mi
# تشغيل الاختبارات مقابل إصدار مُنشَر helm test myapp-prod -n production # تدفّق السجلات من Pods الاختبار أثناء التشغيل helm test myapp-prod -n production --logs # مثال على المخرجات: # NAME: myapp-prod # LAST DEPLOYED: Wed Jun 11 14:22:31 2025 # STATUS: deployed # TEST SUITE: myapp-prod-test-connection # Last Started: Wed Jun 11 14:25:00 2025 # Last Completed: Wed Jun 11 14:25:03 2025 # Phase: Succeeded

أنماط الاختبار على نطاق واسع

مجموعة اختبارات احترافية في Chart تغطّي أكثر من مجرد فحص HTTP واحد. نظّم Pods اختبارات متعددة كل منها مُعلَّق بـ "helm.sh/hook": test:

  • اختبار الاتصال — هل تستطيع الخدمة حلّ DNS والوصول إلى قاعدة بياناتها؟ استخدم psql -c "\l" أو redis-cli PING من صورة sidecar.
  • اختبار المصادقة — هل تُعيد API الكود 401 للطلبات غير المُصادَق عليها و200 مع رمز صالح؟ Pod مبني على curl مع بيانات اعتماد اختبار محقونة يغطّي هذا المسار.
  • اختبار سلامة البيانات — بعد migration، هل يُعيد الاستعلام عن صف معروف المخطط المتوقّع؟ Hook migration يكتب صف sentinel في الجدول، مقرون باختبار Pod يقرأه، يُوفّر تحقّقاً من توافق إصدار المخطط مع التطبيق في كل نشر.
ممارسة كبرى التقنية: في شركات كـ Netflix وStripe، تسير pipeline الـ CI/CD هكذا: بناء الصورة → الدفع إلى السجل → helm upgrade --install --wait --atomic إلى namespace تجريبي → helm test (10-30 ثانية) → اشتراط نجاح الاختبار قبل الترقية للإنتاج. إن فشل helm test، توقفت الـ pipeline قبل أن تبدأ ترقية الإنتاج. هذا يكلّف 30 ثانية لكل نشر، وقد اعترض مئات الانحرافات التي كانت ستصل إلى الإنتاج.

هوكات CRD ونمط pre-install الخاص

أحد أكثر أسباب فشل الـ Charts عند التثبيت الأول شيوعاً هو تطبيق Custom Resource Definition (CRD) بعد الموارد التي تعتمد عليه. يمتلك Helm مجلداً مخصّصاً crds/ يُثبّت الـ CRDs تلقائياً قبل كل شيء، لكن حين تُركّب CRDs من طرف ثالث كاعتمادية، يمكن لـ Hook من نوع pre-install تطبيق manifest الـ CRD صراحةً والانتظار حتى يُسجّله خادم API قبل المتابعة:

# templates/hooks/install-crds.yaml apiVersion: batch/v1 kind: Job metadata: name: {{ include "myapp.fullname" . }}-install-crds annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "-10" "helm.sh/hook-delete-policy": hook-succeeded spec: backoffLimit: 2 template: spec: restartPolicy: Never serviceAccountName: crd-installer-sa # يحتاج RBAC على مستوى الكلاستر containers: - name: kubectl image: bitnami/kubectl:1.30 command: - sh - -c - | kubectl apply -f https://raw.githubusercontent.com/org/repo/main/config/crd/bases/myoperator.io_myresource.yaml kubectl wait --for=condition=Established crd/myresources.myoperator.io --timeout=60s

تشخيص أخطاء الـ Hooks

عند فشل Hook، يُصنّف Helm الإصدار كفاشل وينفّذ تراجعاً (مع --atomic). يبقى الـ Job في الـ namespace (إن استخدمتَ hook-succeeded كسياسة حذف). افحصه كأي Job فاشل آخر:

# سرد جميع Jobs الهوكات في namespace kubectl get jobs -n production -l "helm.sh/chart=myapp-1.4.2" # الحصول على سجلات Pod الـ migration الفاشل kubectl logs -n production -l "job-name=myapp-prod-db-migrate-7" --previous # وصف الـ Job لرؤية تاريخ الأحداث وأكواد الخروج kubectl describe job myapp-prod-db-migrate-7 -n production # لإعادة تشغيل الـ Hook دون ترقية كاملة: # 1. احذف الـ Job الفاشل يدوياً kubectl delete job myapp-prod-db-migrate-7 -n production # 2. شغّل helm upgrade مجدداً — الـ Hook سيُعاد تنفيذه helm upgrade myapp-prod ./mychart -n production --reuse-values

تُحوّل الـ Hooks والاختبارات إصدار Helm من مجرد تطبيق ملفات manifest ثابتة إلى pipeline نشر منسّق ومتحقّق من نفسه. الدرس التالي — الدرس الثامن — يُغطّي نشر الـ Charts وإصدارها حتى تتمكّن الفرق الأخرى من استهلاكها بشكل موثوق من سجلّ.