Helm وتغليف Kubernetes

القوالب المسمّاة والمساعدات

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

القوالب المسمّاة والمساعدات

في الدرس الرابع تعلّمتَ أن قوالب Helm هي ملفات Go text/template مرتبطة بشجرة .Values. لكن chart تطبيق إنتاجي حقيقي يحتوي على عشرة قوالب أو أكثر: Deployment وService وIngress وHPA وPDB وServiceAccount وRBAC وConfigMap وNetworkPolicy وغيرها. يحتاج كل واحد منها إلى نفس مجموعة التسميات (labels) في Kubernetes، ونفس تسميات المحدّد (selector labels)، ونفس مساعد الاسم الذي يضبط الطول عند 63 حرفاً، ونفس تعليقات إصداري الـ chart والتطبيق. من دون موقع مشترك لهذا المنطق ستنسخ نفس الاثني عشر سطراً في كل ملف، وحين يتغيّر معيار التسمية ستجد نفسك أمام عشرة ملفات للتحديث — وستفوتك واحدة حتماً.

الحل هو _helpers.tpl: ملف يحتوي على قوالب مسمّاة (تُعرف أيضاً بالـ partials) تمركّز المنطق القابل لإعادة الاستخدام. يتناول هذا الدرس الميكانيكيات بعمق، إذ الإتقان في هذه الطبقة هو الفارق بين مكتبة chart قابلة للصيانة وفوضى من YAML المكرّر.

آلية عمل القوالب المسمّاة: define وinclude

يوفّر محرّك قوالب Go في Helm توجيهَين لمشاركة المنطق عبر الملفات:

  • {{ define "name" }}{{ end }} — يُعلن عن كتلة قالب مسمّاة. يمكن لأي ملف في templates/ تعريفها، لكن الاتفاقية تقضي بوضعها جميعاً في templates/_helpers.tpl. الملفات التي تبدأ بشرطة سفلية لا تُصيَّر كـ manifests أبداً — وجودها فقط لتضمين التعريفات في الفضاء المشترك.
  • {{ include "name" . }} — يُصيّر قالباً مسمّى في النطاق الحالي (النقطة .) ويُعيد الناتج كسلسلة نصية. هذه هي الأداة الصحيحة لحقن ناتج جزئي في مستند أب.
  • {{ template "name" . }} — مطابق دلالياً لكنه لا يُعيد السلسلة المُصيَّرة؛ بل يكتب مباشرةً في تدفق الإخراج. هذا يجعله غير قابل للتمرير عبر nindent أو trim، لذا في الممارسة العملية استخدم include دائماً.
فخ إنتاجي — template مقابل include: خطأ شائع في الـ charts هو استخدام {{ template "myapp.labels" . }} مباشرةً في كتلة metadata: بدلاً من {{ include "myapp.labels" . | nindent 4 }}. لا يمكن تمرير التوجيه template عبر الأنابيب، لذا تُشفَّر المسافة البادئة داخل الـ partial نفسه وتنكسر فور استخدامه على عمق تداخل مختلف. استخدم include دائماً.

تشريح _helpers.tpl

كل chart تُنشئها بـ helm create تأتي مع _helpers.tpl جاهز. فهم كل قالب مسمّى فيه شرط مسبق لكتابة قوالبك الخاصة. إليك النسخة الإنتاجية المعيارية لـ chart باسم myapp:

{{/* توسيع اسم الـ chart. يحدّ Helm أسماء الموارد بـ 63 حرفاً (حدّ تسمية DNS في Kubernetes). */}} {{- define "myapp.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* إنشاء اسم مؤهّل كامل افتراضي. يجمع اسم الإصدار + اسم الـ chart؛ محدود بـ 63 حرفاً. إن كان اسم الإصدار يحتوي بالفعل على اسم الـ chart، يُستخدم اسم الإصدار وحده. */}} {{- define "myapp.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* تسمية الـ chart: "helm.sh/chart: myapp-1.2.3" يزيل أي "+" من الإصدار ليكون قيمة تسمية صالحة. */}} {{- define "myapp.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* التسميات المشتركة — تُطبَّق على كل مورد لتسهيل الاكتشاف. هي تسميات المحدّد + إضافات لأدوات التشغيل. */}} {{- define "myapp.labels" -}} helm.sh/chart: {{ include "myapp.chart" . }} {{ include "myapp.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* تسميات المحدّد — المجموعة الفرعية المستخدمة في matchLabels ومحدّدات Service. لا تغيّرها بعد النشر الأولي أبداً؛ تغيير تسميات المحدّد يستلزم حذف الـ Deployment وإعادة إنشائه (إنها غير قابلة للتعديل في الكلاستر). */}} {{- define "myapp.selectorLabels" -}} app.kubernetes.io/name: {{ include "myapp.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* مساعد اسم ServiceAccount. */}} {{- define "myapp.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "myapp.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }}

لاحظ عدة أنماط حرجة مضمّنة في هذا القالب:

  • حذف المسافات البيضاء بـ {{- … -}} يُبقي الـ YAML المُصيَّر نظيفاً.
  • trunc 63 | trimSuffix "-" يمنع أخطاء التحقّق من صحة تسميات DNS في Kubernetes حين تكون أسماء الإصدارات طويلة (مثلاً في ArgoCD يتضمّن اسم الإصدار غالباً البيئة وفرع Git).
  • selectorLabels مجموعة فرعية صارمة من labels. يستخدم selector: في Service وكذلك matchLabels: في Deployment تسميات selectorLabels؛ أما metadata.labels فيستخدم قالب labels الكامل. هذا الفصل جوهري.

استخدام المساعدات في القوالب

إليك كيفية استهلاك قالب Deployment للمساعدات مع قواعد المسافة البادئة الدقيقة:

apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "myapp.fullname" . }} labels: {{- include "myapp.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "myapp.selectorLabels" . | nindent 8 }} annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} spec: serviceAccountName: {{ include "myapp.serviceAccountName" . }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" ports: - name: http containerPort: {{ .Values.service.port }} protocol: TCP

تُضيف دالة nindent N سطراً جديداً ثم تُبادر بمسافة N بادئة لكامل السلسلة متعددة الأسطر. هذا هو سبب إلزامية include: دالة nindent هي دالة تمرير سلسلة نصية — لا تقبل الإخراج الفارغ لتوجيه template.

حيلة التعليق checksum/config: إضافة checksum/config: {{ include … | sha256sum }} إلى قالب الـ pod يجعل Helm يُدوّر الـ Deployment تلقائياً كلما تغيّر الـ ConfigMap المُشار إليه. بدون هذا، يُنتج helm upgrade الذي يعدّل ConfigMap فقط صفراً من عمليات إعادة تشغيل الـ pods — يستمر تطبيقك في العمل بإعدادات قديمة. هذا التعليق معيار إنتاجي في كل كبرى شركات التقنية.

كتابة مساعدات مخصّصة

إلى جانب مساعدات التسمية القياسية، ستكتب كثيراً من الـ partials المتخصّصة. أمثلة شائعة من charts الإنتاج:

{{/* كتلة متغيّرات البيئة المشتركة بين جميع حاويات الإصدار. تُنتج قائمة YAML تبدأ بـ "- name:". الاستخدام: {{- include "myapp.commonEnv" . | nindent 12 }} */}} {{- define "myapp.commonEnv" -}} - name: APP_ENV value: {{ .Values.global.environment | quote }} - name: LOG_LEVEL value: {{ .Values.logLevel | default "info" | quote }} - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace {{- end }} {{/* كتلة الموارد — تمركز الطلبات والحدود ليحصل كل workload في الـ chart على إدارة موارد متسقة. الاستخدام: {{- include "myapp.resources" .Values.resources | nindent 10 }} ملاحظة: مرّر .Values.resources (لا النقطة الكاملة) لجعل المساعد مستقلاً عن النطاق. */}} {{- define "myapp.resources" -}} {{- if . }} resources: {{- toYaml . | nindent 2 }} {{- end }} {{- end }}

معيار تسمية Kubernetes

التسميات التي تُنتجها قوالب المساعدات تتبع مواصفة التسميات الموصى بها في Kubernetes (app.kubernetes.io/*). ليست هذه اصطلاحاً اختيارياً — فالأدوات في النظام البيئي (Prometheus ServiceMonitors، اكتشاف Datadog التلقائي، ArgoCD، Lens) تستعلم الموارد بهذه التسميات تحديداً. إتقانها يفتح لك المراقبة (observability) مجاناً.

Named template call flow in a Helm chart _helpers.tpl define "myapp.name" define "myapp.fullname" define "myapp.labels" define "myapp.selectorLabels" define "myapp.commonEnv" deployment.yaml include "myapp.labels" include "myapp.selectorLabels" service.yaml include "myapp.selectorLabels" ingress.yaml include "myapp.labels" Rendered YAML app.kubernetes.io/name: myapp app.kubernetes.io/instance: prod app.kubernetes.io/version: "2.1.0" helm.sh/chart: myapp-1.0.0 app.kubernetes.io/managed-by: Helm
جميع ملفات القوالب تستدعي القوالب المسمّاة المشتركة في _helpers.tpl، مُنتجةً مجموعة تسميات متسقة عبر كل manifest مُصيَّر.

المجموعة الكاملة من التسميات الموصى بها لكل مورد:

  • app.kubernetes.io/name — اسم التطبيق (لا اسم الإصدار). تستخدمه Prometheus للاكتشاف التلقائي لـ ServiceMonitors.
  • app.kubernetes.io/instance — اسم الإصدار (فريد لكل تثبيت). يتيح تشغيل نسخ متعددة من التطبيق ذاته في namespace واحد.
  • app.kubernetes.io/version — إصدار التطبيق (عادةً .Chart.AppVersion). يغذّي تتبّع الإصدارات في Datadog.
  • app.kubernetes.io/managed-by — دائماً Helm. تستخدمه helm list لاكتشاف الإصدارات.
  • helm.sh/chart — اسم الـ chart + الإصدار. يستخدمه ArgoCD للكشف عن انحراف الـ chart.
نمط كبرى التقنية — تسميات عالمية عبر مساعد رفيع المستوى: تُوسّع فرق المنصّات في شركات كـ Lyft وStripe مجموعة التسميات بتسميات عزو التكلفة: team: platform، cost-center: infrastructure، env: prod. تُحقن هذه التسميات عبر مساعد myapp.globalLabels يقرأ من .Values.global. كل مورد في كل chart في الشركة يُصدر هذه التسميات، لذا تستطيع أدوات تكلفة السحابة (Kubecost, Infracost) تفصيل الإنفاق حسب الفريق والبيئة تلقائياً — دون وسم يدوي.

التحقّق من صحة الإخراج المُصيَّر

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

# صيّر جميع القوالب محلياً — يُظهر بالضبط ما سيُطبَّق helm template myapp ./mychart --values values-prod.yaml # الفحص: يكتشف أخطاء الصياغة، والقيم المطلوبة المفقودة، و YAML الخاطئ helm lint ./mychart --values values-prod.yaml # تشغيل جافّ مقابل كلاستر حيّ — يستخدم خادم API للتحقق # (يكتشف هياكل موارد غير صالحة يفوتها helm lint) helm install myapp ./mychart \ --values values-prod.yaml \ --dry-run \ --namespace staging # تصحيح توسّع قالب معيّن خطوة بخطوة helm template myapp ./mychart \ --debug \ --values values-prod.yaml \ 2>&1 | grep -A 20 "deployment.yaml"

شغّل helm template في بيئة CI لكل pull request. القالب المكسور يُنتج YAML مكسوراً قبل أن يصل إلى الكلاستر، والبيئة تكتشف المشكلة قبل أن ينظر فيها أي مراجع.

الخلاصة الجوهرية: _helpers.tpl ليس مجرد اتفاقية — إنه الأساس المعماري لـ Helm chart قابل للصيانة. عرّف معيار تسميتك مرة واحدة، طبّق نمط include … | nindent N في كل مكان، أضف تعليق checksum/config على قوالب الـ pods، وسيصبح chart مواطناً أصيلاً في Kubernetes يتكامل مع المنظومة الكاملة من أدوات المراقبة وGitOps.