EduFlow

الوحدة 5 · التشغيل في الإنتاج

الـ Probes والإيقاف الآمن

20 دقراءة 4 داختر بحكمة

الدرس في لمحة

أضف إلى notes-api فحوص startup وreadiness وliveness تخبر Kubernetes بالحقيقة، وأوقف الـ Pods دون إسقاط الطلبات باستخدام توقّف preStop ومعالج SIGTERM.

  1. 1ثلاثة probes، وثلاث عواقب
  2. 2الـ Readiness مفتاح للحركة
  3. 3البدء دون حلقات إعادة تشغيل
  4. 4الإيقاف دون إسقاط الطلبات

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

لا يستطيع Kubernetes رؤية ما بداخل تطبيقك. إن تُرك وشأنه، يعدّ الـ container سليمًا ما دامت عمليته تعمل، حتى لو كانت تلك العملية عالقة في deadlock، أو ما زالت تسخّن، أو عاجزة عن الوصول إلى قاعدة البيانات. الـ probes (الفحوص) هي طريقة التطبيق لقول الحقيقة عن نفسه. لكن يجب صياغة هذه الحقيقة بعناية، لأن Kubernetes يتصرّف بناءً عليها تلقائيًا وعلى نطاق واسع.

ثلاثة probes، وثلاث عواقب

الـ Probe السؤال الذي يجيب عنه حين يفشل
startup هل انتهى التطبيق من البدء؟ واصل الانتظار حتى الحدّ، ثم أعِد التشغيل
readiness هل يجب أن يستقبل هذا الـ Pod حركة الآن؟ أزِل الـ Pod من endpoints الـ Service؛ دون إعادة تشغيل
liveness هل العملية عالقة بلا أمل في التعافي؟ أعِد تشغيل الـ container

ما دام startup probe مضبوطًا ولم ينجح بعد، لا يعمل الاثنان الآخران. وحالما ينجح، لا يعمل مرة أخرى أبدًا لذلك الـ container.

لدى notes-api أصلًا المساران (endpoints) اللذان يحتاج إليهما هذا. ‏/healthz يُرجع ok إذا كانت حلقة الأحداث (event loop) تستطيع الإجابة عن طلب أصلًا. و/readyz يفحص أيضًا مجمّع اتصالات قاعدة البيانات ويُرجع 503 إذا تعذّر الوصول إلى Postgres.

k8s/deployment.yaml
        - name: api
          image: ghcr.io/skylane/notes-api:1.4.0
          ports:
            - name: http
              containerPort: 3000
          startupProbe:
            httpGet:
              path: /healthz
              port: http
            periodSeconds: 2
            failureThreshold: 30      # up to 60 s to start
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            periodSeconds: 5
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3

أي حالة HTTP من 200 إلى 399 تُعدّ نجاحًا. وإلى جانب httpGet توجد probes من نوع tcpSocket وgrpc وexec؛ فضّل HTTP لخدمات الويب، لأنه يختبر المسار الذي تسلكه الحركة الحقيقية. القيم الافتراضية، إن تركت الحقول، هي periodSeconds: 10 وtimeoutSeconds: 1 وfailureThreshold: 3. مهلة الثانية الواحدة تفاجئ التطبيقات البطيئة، فاضبطها عن قصد.

الـ Readiness مفتاح للحركة

الـ readiness هو الـ probe الأهمّ في العمل اليومي. الـ Pod الذي يبدأ، أو المُثقل، أو المقطوع عن قاعدة بياناته يُبلغ بأنه غير جاهز ويخرج من endpoints الـ Service حتى يتعافى. وهو أيضًا بوّابة للـ rollouts: لا يُحسب الـ Pod الجديد متاحًا إلا حين يصبح جاهزًا، ولا يزيل التحديث المتدرّج الـ Pods القديمة إلا بقدر ما تصبح الجديدة متاحة. الإصدار الذي لا يصل إلى قاعدة البيانات لا يأخذ الحركة أبدًا من إصدار يصل إليها. لهذا فإن الـ Deployment الذي لا readiness probe فيه هو أول ما أنبّه عليه في كل مراجعة.

البدء دون حلقات إعادة تشغيل

قبل وجود الـ startup probes، كانت التطبيقات بطيئة البدء تُحمى بـ initialDelaySeconds على الـ liveness. هذا يؤخّر الفحص الأول في كل بدء بمقدار أسوأ زمن بدء، وينكسر مجددًا حين يصبح البدء أبطأ. أما الـ startup probe فيعطي التطبيق periodSeconds × failureThreshold (هنا 2 × 30 = 60 ثانية) لينهض، مع فحص متكرّر حتى يُكتشف البدء السريع بسرعة، ثم يسلّم الأمر إلى الـ liveness بوتيرته العادية.

الإيقاف دون إسقاط الطلبات

حين يُحذف Pod، أثناء rollout أو تقليص للنسخ أو تفريغ لـ node، يبدأ شيئان في الوقت نفسه: يُزال الـ Pod من endpoints الـ Service، ويبدأ الـ kubelet في إنهاء الـ container. إزالة الـ endpoints يجب أن تنتشر إلى قواعد الشبكة في كل node وإلى أي موازن أحمال، وهذا يستغرق لحظة. إذا توقّف تطبيقك عن قبول الاتصالات لحظة وصول SIGTERM، فالطلبات الموجّهة إليه في تلك اللحظة تُرفض.

عند حذف الـ Pod، تنتشر إزالة الـ endpoints خلال بضع ثوانٍ بينما يعمل توقّف preStop. ثم تُرسل SIGTERM فيفرّغ التطبيق الطلبات الجارية ويخرج، وكل ذلك خلال مهلة السماح البالغة 30 ثانية، وبعدها كانت ستُرسل SIGKILL. حذف الـ Pod إزالة الـ endpoints preStop sleep 5s SIGTERM: تفريغ، ثم exit 0 terminationGracePeriodSeconds: 30 SIGKILL
إزالة الـ endpoints والإنهاء يبدآن معًا؛ وتوقّف preStop يغطّي الفجوة

للحل نصفان. في مواصفات الـ Pod، توقّف قبل SIGTERM حتى يلحق التوجيه بالتغيير:

k8s/deployment.yaml
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: api
          lifecycle:
            preStop:
              sleep:
                seconds: 5

الإجراء sleep يعمل داخل الـ kubelet، فيعمل حتى في image من نوع distroless لا تحتوي على ملف sleep التنفيذي. وفي التطبيق، أبقِ معالج SIGTERM من الدرس 1.4: توقّف عن قبول اتصالات جديدة، وأنهِ الطلبات الجارية، وأغلق مجمّع اتصالات قاعدة البيانات، ثم اخرج. يجب أن يتّسع توقّف preStop والتفريغ معًا داخل terminationGracePeriodSeconds، وإلا فسيرسل الـ kubelet إشارة SIGKILL عند انتهاء المهلة.

يعرف Kubernetes الآن متى يكون notes-api سليمًا وكيف يوقفه بأمان. التالي: يحتاج إلى معرفة كم يستهلك notes-api من المعالج والذاكرة، وكيف يضيف Pods حين تنمو الحركة.

أهم النقاط

  • فشل الـ readiness probe يزيل الـ Pod من endpoints الـ Service؛ وفشل الـ liveness probe يعيد تشغيل الـ container؛ والـ startup probe يؤجّل الاثنين حتى يكتمل بدء التطبيق.
  • الـ liveness يفحص فقط هل العملية نفسها عالقة؛ لا تجعله أبدًا يعتمد على قاعدة بيانات أو خدمة أخرى.
  • الـ readiness بوّابة للتحديثات المتدرّجة، فالإصدار الجديد العاجز عن الخدمة لا يحلّ أبدًا محلّ إصدار يعمل.
  • عند الحذف، تحدث إزالة الـ endpoints وإرسال SIGTERM في الوقت نفسه؛ وتوقّف preStop قصير يتيح للحركة أن تنصرف عن الـ Pod قبل أن يبدأ التطبيق بالإيقاف.
  • يجب أن ينهي التطبيق العمل الجاري خلال terminationGracePeriodSeconds ‏(30 افتراضيًا)، وإلا فسيُقتل.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

يبدأ الـ readiness probe لـ notes-api بالفشل على أحد الـ Pods لأنه لا يصل إلى Postgres. ماذا يفعل Kubernetes؟

السؤال 2 من 4

الـ liveness probe لديك يستدعي /readyz، الذي يفحص قاعدة البيانات. يُعاد تشغيل Postgres لمدة 20 ثانية. ماذا يحدث؟

السؤال 3 من 4

يستغرق notes-api حتى 60 ثانية ليبدأ على قاعدة بيانات كبيرة لأنه يسخّن cache. أي إعداد للـ probes يتجنّب حلقة إعادة التشغيل دون إبطاء اكتشاف الأعطال لاحقًا؟

السؤال 4 من 4

خلال كل rollout تفشل بضعة طلبات إلى notes-api بـ connection refused، مع أن التطبيق يعالج SIGTERM. ما الحل المعتاد؟

تدريب

اختر بحكمة

حلقة إعادة التشغيل مع قواعد البيانات الكبيرة

بعد أن كبرت قاعدة بيانات أحد العملاء، صار تسخين الـ cache عند بدء notes-api يستغرق حتى 90 ثانية. الـ liveness probe على /healthz يستخدم القيم الافتراضية (periodSeconds: 10 وfailureThreshold: 3)، فتُقتل الـ Pods عند نحو 30 ثانية ولا تنتهي من البدء أبدًا. معالج /healthz لا يُسجَّل إلا بعد اكتمال التسخين. أي تغيير هو الحل الأفضل؟

حلقة إعادة التشغيل مع قواعد البيانات الكبيرة