أحمال عمل Kubernetes وإعدادها

خرائط الإعداد (ConfigMaps)

18 دقيقة الدرس 1 من 32

خرائط الإعداد (ConfigMaps)

كل تطبيق يحتاج إلى إعدادات: أسماء مضيفي قواعد البيانات، وعلامات الميزات، وأحجام مجمعات الخيوط، ومستويات السجل، ونطاقات CORS الموثوقة. الطريقة التي تُسلَّم بها هذه الإعدادات إلى التطبيق هي من أكثر قرارات البنية التحتية أثرًا. تضمينها مباشرةً في صورة الحاوية — النهج الساذج — يربط دورة الإصدار بدورة الإعداد. تغيير مستوى السجل يستلزم إعادة البناء وإعادة النشر. والأسوأ من ذلك أنك تنتهي بـ"صور خاصة ببيئة معينة"، مما يُفسد ضمان قابلية نقل الحاويات الأساسي.

يحل Kubernetes هذه المشكلة عبر ConfigMaps: كائن API من الدرجة الأولى يحمل بيانات إعداد غير حساسة على شكل أزواج مفتاح-قيمة أو محتوى ملفات كاملة. تبقى الصورة محايدة للبيئة، بينما يحمل ConfigMap القيم الخاصة بكل بيئة. تعمل الصورة ذاتها في بيئات التطوير والاختبار والإنتاج — ويختلف ConfigMap فحسب. هذا هو مبدأ تطبيق 12-Factor الثالث ("تخزين الإعداد في البيئة") معبَّرًا عنه بشكل أصيل في Kubernetes.

إنشاء ConfigMaps

يمكن إنشاء ConfigMaps بأسلوب أمري (مفيد للنصوص البرمجية والتصحيح السريع) أو بإعلانها كملفات YAML (النهج الملائم للإنتاج، المخزَّن في نظام التحكم في الإصدارات). يجب دائمًا تخزين الملف في Git — فالشكل الأمري مؤقت وغير قابل للتتبع.

# --- الإنشاء الأمري (مفيد للاختبار، غير مناسب للإنتاج) --- # من أزواج مفتاح=قيمة حرفية kubectl create configmap app-config \ --from-literal=LOG_LEVEL=info \ --from-literal=MAX_CONNECTIONS=100 \ --from-literal=FEATURE_DARK_MODE=true # من ملف .env موجود (كل سطر يصبح مفتاحًا واحدًا) kubectl create configmap app-config --from-env-file=.env # من ملف كامل (اسم الملف يصبح المفتاح، ومحتواه هو القيمة) kubectl create configmap nginx-conf --from-file=nginx.conf # من كل الملفات في مجلد kubectl create configmap html-pages --from-file=./html/ # فحص النتيجة kubectl get configmap app-config -o yaml kubectl describe configmap app-config

الشكل التصريحي YAML هو ما تُخزِّنه في Git وتنشره عبر خط CI الخاص بك. يبدو ملف ConfigMap هكذا:

# app-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: production labels: app: my-api env: production data: # أزواج مفتاح-قيمة بسيطة (تُستهلك كمتغيرات بيئة) LOG_LEVEL: "info" MAX_CONNECTIONS: "100" FEATURE_DARK_MODE: "true" DB_HOST: "postgres.production.svc.cluster.local" # محتوى ملف متعدد الأسطر (يُستهلك كملف مُثبَّت) nginx.conf: | server { listen 80; location /healthz { return 200 "ok"; add_header Content-Type text/plain; } location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } } app.properties: | thread.pool.size=16 cache.ttl.seconds=300 metrics.enabled=true
ConfigMaps مقابل Secrets: ConfigMaps مخصصة للبيانات غير الحساسة. كلمات مرور قواعد البيانات، ورموز API، ومفاتيح TLS الخاصة تنتمي إلى Secrets (مغطاة في الدرس الثاني). يُخزَّن ConfigMap في etcd بنص عادي — يمكن لأي شخص يملك إذن RBAC بـget configmap في ذلك النطاق قراءته. لا تضع أبدًا بيانات اعتماد في ConfigMap.

استهلاك ConfigMaps: متغيرات البيئة

أكثر أنماط الاستهلاك شيوعًا يضخ مفاتيح فردية كمتغيرات بيئة. يقرأها التطبيق عبر آليته القياسية os.Getenv / process.env / System.getenv — لا يلزم تغيير أي كود إذا كان التطبيق يقرأ من متغيرات البيئة أصلًا.

# pod-with-configmap-env.yaml apiVersion: v1 kind: Pod metadata: name: my-api spec: containers: - name: api image: my-api:v2.4.1 # الخيار 1: حقن مفاتيح فردية بالاسم env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL optional: false # تفشل بداية الـ Pod إذا كان المفتاح مفقودًا (الافتراضي: false) - name: DB_HOST valueFrom: configMapKeyRef: name: app-config key: DB_HOST # الخيار 2: حقن جميع المفاتيح من ConfigMap مرة واحدة envFrom: - configMapRef: name: app-config optional: false # إذا استُخدم env و envFrom معًا، يأخذ env الأولوية عند تعارض المفاتيح
فخ إنتاجي — envFrom مع ConfigMaps غير موثوقة: يضخ استخدام envFrom كل مفتاح في ConfigMap كمتغير بيئة. إذا أضاف أحدهم مفتاحًا يتعارض مع متغير نظام يعتمد عليه تطبيقك (مثل PATH أو HOME أو متغير داخلي لمكتبة)، فسيكون السلوك غير محدد وغالبًا صامتًا. على مستوى الشركات الكبرى، يُفضَّل استخدام إدخالات env[].valueFrom.configMapKeyRef الصريحة حتى يكون العقد بين ConfigMap والتطبيق واضحًا وقابلًا للتدقيق في مراجعة الكود.

استهلاك ConfigMaps: تثبيت وحدات التخزين (Volumes)

نمط تثبيت وحدة التخزين ضروري عندما يقرأ تطبيقك من ملف إعداد بدلًا من متغيرات البيئة — وهو أمر شائع مع NGINX وPrometheus وKafka وRedis والتطبيقات القديمة التي لا يمكنك تعديلها. كل مفتاح في ConfigMap يصبح ملفًا منفصلًا داخل المجلد المُثبَّت. اسم الملف هو المفتاح؛ ومحتوى الملف هو القيمة.

# pod-with-configmap-volume.yaml apiVersion: v1 kind: Pod metadata: name: nginx spec: volumes: - name: nginx-config-vol configMap: name: app-config # تثبيت مفاتيح محددة فقط (احذف 'items' لتثبيت كل المفاتيح كملفات) items: - key: nginx.conf path: nginx.conf # اسم الملف داخل مسار التثبيت - key: app.properties path: app.properties containers: - name: nginx image: nginx:1.27-alpine volumeMounts: - name: nginx-config-vol mountPath: /etc/nginx/conf.d # يُستبدل المجلد بالكامل readOnly: true # بديلًا، يمكن تثبيت مفتاح واحد على مسار ملف محدد: # (لا يستبدل المجلد بالكامل): # volumeMounts: # - name: nginx-config-vol # mountPath: /etc/nginx/conf.d/default.conf # subPath: nginx.conf # readOnly: true
استخدم subPath للتثبيت الجراحي: بدون subPath، يؤدي تثبيت حجم ConfigMap على مجلد إلى استبدال المجلد بأكمله. إذا كان /etc/nginx/conf.d/ يحتوي بالفعل على إعدادات افتراضية تحتاجها، استخدم subPath: nginx.conf لتثبيت ذلك الملف وحده دون المساس ببقية المجلد. المقايضة: تثبيتات subPath لا تتلقى تحديثات مباشرة عند تغيير ConfigMap — راجع قسم سلوك التحديث أدناه.
ConfigMap consumption: env vars vs. volume mount ConfigMap app-config LOG_LEVEL: info DB_HOST: postgres… nginx.conf: | server { … } Pod Container (api) ENV: LOG_LEVEL=info ENV: DB_HOST=postgres… via env[].valueFrom or envFrom: Container (nginx) /etc/nginx/conf.d/ nginx.conf ← key via volumeMount live-updated (no subPath) env var volume mount سلوك التحديث متغيرات البيئة: لا تُحدَّث — يجب إعادة تشغيل الـ Pod Volume (بدون subPath): تُحدَّث خلال ~60 ثانية Volume (مع subPath): لا تُحدَّث — تتصرف كمتغيرات بيئة
نمطا استهلاك ConfigMap وسلوك التحديث المباشر داخل الـ Pod.

سلوك التحديث: الفرق الحاسم

هنا يقع المهندسون في مشكلات إنتاجية. يختلف سلوك تحديث ConfigMaps بحسب طريقة استهلاكها، وفهم ذلك يمنع الانقطاعات:

  • متغيرات البيئة (env / envFrom): تُحقن عند بداية تشغيل الحاوية. لا تُحدَّث أبدًا في حاوية قيد التشغيل. لتطبيق تغيير ConfigMap، يجب إعادة تشغيل الـ Pod — إما بحذفه (تُعيد ReplicaSet إنشاءه) أو بتشغيل تحديث متدرج.
  • تثبيتات Volume بدون subPath: يحدِّث Kubernetes الملفات المُثبَّتة تلقائيًا. فترة مزامنة kubelet (افتراضيًا --sync-frequency=1m في معظم الكلاسترات المُدارة) تعني أن التغييرات تنتشر خلال نحو 60 ثانية. لا يزال التطبيق بحاجة إلى مراقبة الملف للتغييرات — عملية تقرأ إعداداتها مرة واحدة فقط عند البداية لن تستفيد من ذلك.
  • تثبيتات Volume مع subPath: التغييرات لا تنتشر. هذا قيد معروف في Kubernetes. تعامل مع هذه التثبيتات كثابتة — مثل متغيرات البيئة تمامًا.
فرض إعادة تشغيل متدرج بعد تحديث ConfigMap: الطريقة المعيارية لإجبار إعادة القراءة عند استخدام متغيرات البيئة هي kubectl rollout restart deployment/my-api. في سير عمل GitOps (ArgoCD أو Flux)، النهج الأنظف هو تعليق Deployment بتجزئة محتوى ConfigMap — حيلة sha256sum في Helm — حتى يُشغِّل تغيير ConfigMap تلقائيًا طرحًا جديدًا دون تدخل يدوي.
# تشغيل إعادة تشغيل متدرجة بعد تعديل ConfigMap kubectl rollout restart deployment/my-api -n production # مراقبة الطرح kubectl rollout status deployment/my-api -n production # حيلة sha256sum في Helm / GitOps: # في تعليقات قالب pod الخاص بـ Deployment: # annotations: # checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} # يُعيد Helm حساب هذا عند كل `helm upgrade`، فيحصل الـ Deployment # على تجزئة قالب pod جديدة عند تغيير ConfigMap — ما يُشغِّل تحديثًا متدرجًا تلقائيًا. # التحقق من محتوى ConfigMap المباشر kubectl get configmap app-config -n production -o jsonpath='{.data.LOG_LEVEL}' # تعديل ConfigMap في المكان (للتغييرات الطارئة — تابع دائمًا بـ commit في Git) kubectl edit configmap app-config -n production

ConfigMaps غير القابلة للتعديل (Immutable)

يدعم Kubernetes 1.21+ تحديد ConfigMap على أنه غير قابل للتعديل بضبط immutable: true في الملف. لا يمكن تحديث ConfigMaps غير القابلة للتعديل — يجب حذفها وإعادة إنشائها. في المقابل، يتوقف Kubernetes عن مراقبتها للتغييرات، مما يُقلِّص ملحوظًا حِمل خادم API و kubelet على نطاق واسع (الكلاسترات التي تحتوي على عشرات الآلاف من ConfigMaps). تستخدم فرق Google SRE على نطاق Kubernetes ConfigMaps غير القابلة للتعديل كممارسة: تغييرات الإعداد تحمل رقم نسخة (مثلًا app-config-v42app-config-v43) وتُحدَّث Deployments للإشارة إلى الاسم الجديد. يمنحك هذا النمط تاريخًا كاملًا للإعداد في Git، وتراجعًا فوريًا بالرجوع إلى مرجع Deployment، وصفر خطر من تعديل ConfigMap مباشر عن طريق الخطأ.

# ConfigMap غير قابل للتعديل — لا يمكن تعديله بعد الإنشاء apiVersion: v1 kind: ConfigMap metadata: name: app-config-v42 namespace: production immutable: true data: LOG_LEVEL: "warn" MAX_CONNECTIONS: "200"
اصطلاحات التسمية في الإنتاج: تُطبِّق المنظمات الهندسية الكبيرة مخطط تسمية لـ ConfigMaps: {app}-{component}-{env} (مثلًا payments-api-production) وتخزِّنها في نطاق مخصص لكل بيئة. مع RBAC الذي يمنح ServiceAccount الخاص بـpayments صلاحية get فقط على ConfigMaps في نطاق payments، تحقق وصولًا للإعداد بأقل الصلاحيات اللازمة — لا تستطيع الـ Pod قراءة ConfigMap فريق آخر حتى لو حاولت.