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

مشروع: حمل عمل على مستوى الإنتاج

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

مشروع: حمل عمل على مستوى الإنتاج

كل مفهوم من مفاهيم هذا الدرس — ConfigMaps وSecrets وفحوصات liveness/readiness/startup وطلبات الموارد وحدودها وHorizontal Pod Autoscaling — موجود لحل مشكلة إنتاج ملموسة. كل قطعة منها سهلة الفهم بمعزل عن الأخريات؛ أما في الجمع وعلى نطاق واسع، فإن التفاعلات بينها هي ما يُعثر الفرق. يربط هذا المشروع جميعها في حمل عمل واحد قابل للنشر ومُعدّ للمعركة يمكنك اليوم نشره على مجموعة حقيقية.

سنبني خدمة API عديمة الحالة تُسمى order-api. تقرأ الإعدادات غير الحساسة في وقت التشغيل من ConfigMap، وتثبّت بيانات اعتماد قاعدة البيانات من Secret، وتعرض نقاط نهاية HTTP للصحة تستهلكها فحوصات Kubernetes، وتُعلن ميزانيات CPU/ذاكرة يُنفّذها المجدول والنواة، وتتوسع أفقياً تحت الحمل. كل قرار مُوثَّق أدناه يعكس ما يكتبه المهندسون الكبار في شركات تُشغّل آلاف الـ Pods لكل مجموعة.

الخطوة الأولى — مساحة الأسماء والكائنات المساندة

عزل أحمال العمل دائماً في مساحة أسماء خاصة بها. يمنحك ذلك حدود RBAC وحصص موارد وإخراجاً نظيفاً من kubectl get all -n orders.

# namespace kubectl create namespace orders # ConfigMap — إعداد twelve-factor، غير مخبوز في الصورة kubectl apply -f - <<'EOF' apiVersion: v1 kind: ConfigMap metadata: name: order-api-config namespace: orders data: APP_ENV: "production" LOG_LEVEL: "warn" DB_HOST: "postgres-svc.orders.svc.cluster.local" DB_PORT: "5432" DB_NAME: "orders" MAX_CONN_POOL: "20" CACHE_TTL_SECONDS: "300" EOF # Secret — مُشفَّر بـ base64، لا يُودَع في git أبداً kubectl apply -f - <<'EOF' apiVersion: v1 kind: Secret metadata: name: order-api-secret namespace: orders type: Opaque stringData: DB_USER: "order_svc" DB_PASSWORD: "s3cr3t-rotate-me" JWT_SIGNING_KEY: "HS256-production-key-min-32-chars" EOF
في المجموعات الحقيقية، تأتي Secrets من خزينة خارجية (AWS Secrets Manager عبر ASCP، أو HashiCorp Vault Agent Injector، أو Sealed Secrets). اختصار stringData المُوضَّح هنا مناسب للبدء، لكن دوّر بيانات الاعتماد فوراً بعد النشر الأول وادمج متحكم vault-sync قبل أن تعتبر الخدمة جاهزة للإنتاج.

الخطوة الثانية — مانيفست النشر

هذا هو جوهر المشروع. اقرأ كل حقل مُعلَّق — كل حقل يُعالج نمط فشل إنتاج موثقاً.

apiVersion: apps/v1 kind: Deployment metadata: name: order-api namespace: orders labels: app: order-api version: "1.0.0" spec: replicas: 2 # HPA ستتجاوز هذا في وقت التشغيل selector: matchLabels: app: order-api strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # Pod إضافي واحد أثناء التحديث maxUnavailable: 0 # صفر وقت توقف — لا تقتل قبل أن يكون البديل جاهزاً template: metadata: labels: app: order-api version: "1.0.0" spec: terminationGracePeriodSeconds: 30 # وقت لاستنزاف الطلبات الجارية securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 containers: - name: api image: registry.example.com/order-api:1.0.0 imagePullPolicy: Always ports: - containerPort: 8080 name: http # ── إعداد من ConfigMap (envFrom = كل المفاتيح دفعة واحدة) ─────── envFrom: - configMapRef: name: order-api-config # ── Secrets مثبَّتة كمتغيرات بيئة فردية ────────────────────────── env: - name: DB_USER valueFrom: secretKeyRef: name: order-api-secret key: DB_USER - name: DB_PASSWORD valueFrom: secretKeyRef: name: order-api-secret key: DB_PASSWORD - name: JWT_SIGNING_KEY valueFrom: secretKeyRef: name: order-api-secret key: JWT_SIGNING_KEY # ── ميزانية الموارد ──────────────────────────────────────────────── resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "1000m" # 1 vCPU — معثَّر لا مقتول memory: "512Mi" # OOM إذا تجاوز — حدِّد بعناية # ── Startup probe — أعطِ JVM أو المهاجرة وقتاً للانتهاء ────────── startupProbe: httpGet: path: /healthz/startup port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 24 # 24 * 5s = دقيقتان كحد أقصى للبدء # ── Liveness probe — أعد تشغيل الحاوية إن توقفت عن الاستجابة ──── livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 0 # startup probe تحرسها؛ 0 آمن هنا periodSeconds: 15 timeoutSeconds: 3 failureThreshold: 3 # 3 * 15s = 45 ثانية قبل إعادة التشغيل # ── Readiness probe — احجب الحركة حتى يدفأ مجمع اتصال قاعدة البيانات readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 0 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 3 # أزل من نقاط نهاية Service بعد 15 ثانية # وزِّع الـ Pods عبر مناطق الفشل (يتطلب Kubernetes 1.19+) topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: order-api
أكثر حوادث الإنتاج شيوعاً التي يسببها هذا المانيفست: ضبط maxUnavailable: 1 مع replicas: 2 فقط يعني أن تحديثاً متداولاً قد يتركك لفترة وجيزة مع Pod واحد. إن كان ذلك الـ Pod لا يزال يبدأ، فكل الحركة تضرب نسخة واحدة غير جاهزة. اضبط دائماً maxUnavailable: 0 على النشرات قليلة النسخ؛ التكلفة هي فتحة Pod إضافية أثناء التحديث.

الخطوة الثالثة — الخدمة وHPA

# ClusterIP Service — DNS ثابت للمستهلكين داخل المجموعة apiVersion: v1 kind: Service metadata: name: order-api-svc namespace: orders spec: selector: app: order-api ports: - port: 80 targetPort: 8080 name: http type: ClusterIP --- # Horizontal Pod Autoscaler apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-api-hpa namespace: orders spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-api minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 75 behavior: scaleDown: stabilizationWindowSeconds: 300 # انتظر 5 دقائق قبل التقليص policies: - type: Percent value: 25 periodSeconds: 60 # أزل 25% كحد أقصى في الدقيقة scaleUp: stabilizationWindowSeconds: 0 # توسَّع فوراً policies: - type: Pods value: 4 periodSeconds: 60 # أضف 4 Pods كحد أقصى في الدقيقة
scaleDown.stabilizationWindowSeconds: 300 أمر بالغ الأهمية. بدونه، يمكن لـ HPA التقليص السريع من 20 Pod إلى 2 فور انتهاء ذروة الحركة — لتجد المجموعة غير مستعدة للذروة التالية. يُسمي مهندسو Google هذا التذبذب، وهو يُدمر زمن الاستجابة p99. نافذة الـ 5 دقائق تُخفف هذا.

الخطوة الرابعة — النشر والتحقق

# طبّق كل شيء دفعة واحدة (ملفات المانيفست في ./manifests/) kubectl apply -f manifests/ -n orders # راقب التحديث المتداول — يجب أن يظهر كلا الـ Pod بحالة 2/2 READY kubectl rollout status deployment/order-api -n orders kubectl get pods -n orders -w # تأكد أن env من ConfigMap وSecret وصل إلى الحاوية kubectl exec -n orders deploy/order-api -- env | grep -E 'APP_ENV|DB_HOST|DB_USER' # تحقق من حالة الفحوصات (انظر قسم Events لأي فشل) kubectl describe pod -n orders -l app=order-api # افحص HPA — عمود TARGETS يُظهر الاستخدام الحالي مقابل المطلوب kubectl get hpa -n orders -w # محاكاة حمل لتشغيل توسع HPA kubectl run load-gen --image=busybox -n orders --rm -it --restart=Never -- \ /bin/sh -c "while true; do wget -q -O- http://order-api-svc/api/orders; done" # بعد الاختبار، راقب HPA وهو يتقلص (يستغرق ~5 دقائق بسبب نافذة الاستقرار) kubectl get hpa order-api-hpa -n orders -w

نظرة عامة على البنية

Production-Grade Workload Architecture HPA min:2 max:20 scales Deployment order-api rollingUpdate Pod (Zone A) Startup Probe /healthz/startup Liveness /healthz/live Readiness /healthz/ready Pod (Zone B) Startup Probe /healthz/startup Liveness /healthz/live Readiness /healthz/ready ConfigMap APP_ENV, DB_HOST … Secret DB_USER, DB_PASSWORD … Resources req 250m/256Mi lim 1/512Mi Service (ClusterIP) order-api-svc :80
جميع أوليات حمل العمل مترابطة: HPA تتحكم في عدد النسخ، ConfigMap وSecret يحقنان الإعداد، الفحوصات تحرس الحركة، والخدمة توفر نقطة نهاية ثابتة.

أنماط الفشل الإنتاجية التي يجب معرفتها

ما يميز المهندسين الكبار هو معرفة ما يعطل لا فقط ما يعمل. إليك أنماط الفشل الأكثر شيوعاً في هذا الإعداد بالذات:

  • نقطة نهاية الفحص مكلفة للغاية. إن كانت /healthz/ready تُنفّذ استعلام قاعدة بيانات عند كل استدعاء وKubernetes تستقصي كل 5 ثوانٍ عبر 50 Pod، فهذا 600 اتصال بقاعدة البيانات في الدقيقة من فحوصات الصحة وحدها. أبقِ الفحوصات خفيفة — تحقق من علامة في الذاكرة تضبطها تطبيقاتك بعد دفء مجمع اتصال قاعدة البيانات، لا من قاعدة البيانات نفسها.
  • حد الذاكرة قريب جداً من الطلب. حد 512 Mi مع طلب 256 Mi يعني أن العقدة قد تجدول الـ Pod، يكبر JVM تحت الحمل الحقيقي متجاوزاً 256 Mi، وقاتل OOM يُنهي الحاوية. اضبط الحد على 1.5-2x على الأقل من الطلب، أو استخدم Guaranteed QoS (request == limit) للخدمات الحساسة للزمن.
  • HPA لا يستطيع التوسع لأن metrics-server مفقود. شغّل kubectl top pods؛ إن أعطى خطأ، metrics-server غير مثبّت. HPA يفشل صامتاً. ثبِّته قبل أن تحتاجه.
  • التحديث المتداول يتوقف عند 50% لأن readiness probe تستخدم Secret تم تدويره. الـ Pods القديمة تستمر في الخدمة. الـ Pods الجديدة تفشل في readiness لأن ذاكرة التخزين المؤقت للـ Pod قديمة. الحل: أعد تشغيل النشر (kubectl rollout restart deployment/order-api -n orders) بعد تدوير Secrets.
مجموعة maxUnavailable: 0 وtopologySpreadConstraints عبر المناطق وHPA بـ minReplicas غير صفري وstartup probe بـ failureThreshold سخي — هذا النمط المتعارف عليه للنشر بدون توقف على نطاق الشركات الكبرى. كل حارس رخيص الإضافة ومكلف الترقيع بعد الحادثة.

ما تحمله إلى الأمام

حمل العمل هذا عديم الحالة عمداً. الأنماط هنا — envFrom ConfigMap، متغيرات بيئة Secret الفردية، فحوصات ثلاثية الطبقات، نسبة request/limit، HPA بسلوك توسع غير متماثل، وتوزيع طوبولوجي — تنطبق على تقريباً كل microservice على Kubernetes ستكتبه في مسيرتك المهنية. الامتداد الطبيعي التالي هو إضافة PodDisruptionBudget (minAvailable: 1) لمنع عمليات استنزاف المجموعة أثناء صيانة العقد من إسقاط جميع النسخ في آنٍ واحد. ذلك، إضافة إلى سياسة شبكة تقيّد الدخول على متحكم Ingress فقط، يكمل المحيط الجاهز للإنتاج.