پرش به محتویات

آموزش کوبرنتیز؛ شب دوازدهم: چرخهٔ حیات Pod و خاتمهٔ آرام

امشب راه‌اندازی اولیه، Signalها، Hookها، Grace Period، Restart و خاتمهٔ آرام را هنگام یک Rollout مشاهده می‌کنیم.

مدل ذهنی چرخهٔ حیات

schedule Pod
  → run init containers in order
  → start app containers
  → Pod becomes Ready
  → deletion/rollout begins
  → run preStop hook
  → send SIGTERM to container process
  → wait within termination grace period
  → send SIGKILL if still running

حذف Endpoint و خاموش شدن Container به‌وسیلهٔ Controllerهای توزیع‌شده انجام می‌شود؛ بنابراین برنامه باید یک بازهٔ کوتاه همگرایی را تحمل کند. Readiness، تخلیهٔ Connection و Grace Time کافی در کنار هم کار می‌کنند.

restartPolicy به Containerهای داخل Pod مربوط است و مقدار پیش‌فرضش Always است؛ Podهای Deployment نیز باید Always باشند. Containerای که Restart می‌شود در همان Pod باقی می‌ماند، اما Pod جایگزین نام و UID تازه‌ای دارد.

Manifest آزمایش

apiVersion: v1
kind: ConfigMap
metadata:
  name: lifecycle-scripts
  namespace: course-night-12
data:
  run.sh: |
    #!/bin/sh
    trap 'echo "$(date -Iseconds) SIGTERM received"; sleep 5; echo "$(date -Iseconds) shutdown complete"; exit 0' TERM
    echo "$(date -Iseconds) application started"
    while true; do sleep 1; done
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lifecycle-demo
  namespace: course-night-12
spec:
  replicas: 2
  strategy:
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: lifecycle-demo
  template:
    metadata:
      labels:
        app: lifecycle-demo
    spec:
      terminationGracePeriodSeconds: 20
      initContainers:
        - name: initialize
          image: busybox:1.36
          command: ["/bin/sh", "-c"]
          args: ["echo initialized > /work/status"]
          volumeMounts:
            - name: work
              mountPath: /work
      containers:
        - name: app
          image: busybox:1.36
          command: ["/bin/sh", "/scripts/run.sh"]
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "echo preStop-called; sleep 3"]
          volumeMounts:
            - name: scripts
              mountPath: /scripts
              readOnly: true
            - name: work
              mountPath: /work
      volumes:
        - name: scripts
          configMap:
            name: lifecycle-scripts
            defaultMode: 0555
        - name: work
          emptyDir: {}

استقرار و بررسی Initialization

kubectl create namespace course-night-12
kubectl apply -f app.yaml
kubectl rollout status deployment/lifecycle-demo --namespace course-night-12
kubectl get pods --namespace course-night-12

kubectl get pod <pod-name> --namespace course-night-12 \
  -o jsonpath='{range .status.initContainerStatuses[*]}{.name}{" state="}{.state}{"\n"}{end}'
kubectl exec <pod-name> --namespace course-night-12 -- cat /work/status
kubectl logs <pod-name> --namespace course-night-12 --container app

Init Container باید پیش از شروع App Container با موفقیت تمام شود. هر دو Container مسیر /work را می‌بینند، زیرا emptyDir متعلق به Pod است و تا پایان عمر همان Pod باقی می‌ماند.

مشاهدهٔ خاموش شدن آرام

در یک Terminal اجرا کنید:

kubectl get pods --namespace course-night-12 --watch

در Terminal دیگر، نام یکی از Podهای فعلی را ذخیره و Pod Template را تغییر دهید:

old_pod="$(kubectl get pod --namespace course-night-12 \
  -l app=lifecycle-demo -o jsonpath='{.items[0].metadata.name}')"
kubectl patch deployment lifecycle-demo --namespace course-night-12 \
  --type merge \
  --patch '{"spec":{"template":{"metadata":{"annotations":{"course.example/restarted":"night-12"}}}}}'
kubectl logs "$old_pod" --namespace course-night-12 --container app --follow

Hook مربوط به preStop پیش از ارسال TERM اجرا می‌شود. چون Hook یک Process جداست، نمایش خروجی آن در Log مربوط به Process اصلی تضمین نمی‌شود؛ اما باید Signal گرفته‌شده و پیام‌های Shutdown را ببینید.

kubectl rollout status deployment/lifecycle-demo --namespace course-night-12
kubectl get events --namespace course-night-12 \
  --sort-by=.metadata.creationTimestamp

تمرین خرابی: Grace Period کوتاه

terminationGracePeriodSeconds را به 5 تغییر دهید. Hook مربوط به preStop حدود سه ثانیه زمان می‌گیرد و برنامه نیز می‌کوشد پنج ثانیه صرف خاموش شدن کند؛ هر دو از یک بودجهٔ مشترک استفاده می‌کنند. Apply کنید و Rollout دیگری بسازید:

kubectl apply -f app.yaml
kubectl patch deployment lifecycle-demo --namespace course-night-12 \
  --type merge \
  --patch '{"spec":{"template":{"metadata":{"annotations":{"course.example/restarted":"too-short"}}}}}'
kubectl rollout status deployment/lifecycle-demo --namespace course-night-12

ممکن است برنامه پیش از خاتمهٔ اجباری فرصت ثبت shutdown complete را پیدا نکند. Grace Period را به ۲۰ ثانیه برگردانید و دوباره کل توالی را بررسی کنید.

راهنمای طراحی

  • Shutdown را Idempotent طراحی کنید.
  • ابتدا پذیرش کار جدید را متوقف، سپس کارهای در حال انجام و محدود را تمام کنید و بعد خارج شوید.
  • Grace Period باید از مجموع زمان preStop و بدترین زمان معتبر Drain بیشتر باشد.
  • preStop جایگزین مدیریت SIGTERM در خود برنامه نیست.
  • PID 1 باید Signal را دریافت یا Forward کند؛ Wrapperهای Shell ممکن است آن را ناخواسته ببلعند.
  • Init Containerها باید تکرارپذیر باشند، زیرا Pod بازسازی‌شده دوباره آن‌ها را اجرا می‌کند.

چک‌لیست پایان

  • تکمیل Init را پیش از شروع برنامه مشاهده کرده‌ام.
  • Restart شدن Container را از جایگزینی Pod تشخیص می‌دهم.
  • SIGTERM و Shutdown آرام را هنگام Rollout ثبت کرده‌ام.
  • نشان داده‌ام بودجهٔ Grace Period شامل زمان اجرای preStop نیز هست.
  • Grace Period امن را بازگردانده‌ام.
kubectl delete namespace course-night-12