Container Lifecycle Hooks

Trong bài viết này ta sẽ tìm hiểu cách kubelet sử dụng container lifecycle hook framework để chạy code khi được kích hoạt bởi các event trong quá trình quản lý.

Overview

Tương tự như nhiều ngôn ngữ lập trình có các lifecycle hook, chẳng hạn như Angular, Kubernetes cung cấp cho các Container các lifecycle hook. Các hook cho phép các container nhận biết các event trong vòng đời quản lý của chúng và chạy code được triển khai trong handler khi lifecycle hook tương ứng được thực thi.

Container hooks

Có hai loại hook tới Container:

PostStart

Hook này thực thi ngay lập tức sau khi một container được tạo. Tuy nhiên không chắc chắn rằng hook sẽ được thực thi trước ENTRYPOINT của container. Không có tham số nào được truyền cho handler.

PreStop

Hook này được gọi ngay lập tức trước khi một container chuẩn bị bị terminate do API request hoặc event quản lý như liveness/startup probe fail, preemption, resource contention, ... Một call tới PreStop hook sẽ fail nếu container đã đang terminate hoặc đã hoàn tất terminate và hook cần phải hoàn thành trước khi gửi TERM signal để stop container. Pod termination grace period countdown bắt đầu trước khi thực thi PreStop vì vậy không quan trọng output của handler là gì, container sẽ terminate trong termination grace period. Không tham số nào được truyền cho handler.

Triển khai hook handler

Các container có thể truy cập một hook bằng cách triển khai và đăng ký handler cho hook đó. Có 3 loại hook handler có thể được triển khai cho các container:

  • Exec - Thực thi một command cụ thể ví dụ như pre-stop.sh bên trong các cgroup và namespace của container.

  • HTTP - Thực thi HTTP request tới một endpoint cụ thể trên container.

  • Sleep - Tạm dừng container trong một khoảng thời gian nhất định. "Sleep" khả dụng khi feature gate PodLifeCycleSleepAction được bật.

Thực thi hook handler

Khi một hook được gọi, hệ thống quản lý Kubernetes sẽ thực thi handler dựa trên hook action, httpGet, tcpSocket và sleep được thực thi bởi kubelet còn exec được thực thi trong container.

Các handler call đồng bộ với context của pod chứa container tức là khi một hook được gọi, nó sẽ chờ đến khi hoàn thành trước khi các bước tiếp theo được thực hiện. Trong khi đó container ENTRYPOINT và hook có thể thực thi bất đồng bộ. Tuy nhiên nếu hook mất quá nhiều thời gian để chạy, container sẽ không thể chuyển sang trạng thái Running.

PreStop không được thực thi bất đồng bộ từ signal stop container tức là hook phải hoàn tất quá trình thực thi trước khi TERM signal có thể được gửi đến container. Nếu PreStop hook bị treo trong quá trình thực thi, Pod sẽ vào giai đoạn Terminating và sẽ ở đó tới khi Pod được thoát sau khi hết terminationGracePeriodSeconds. Grace period này áp dụng cho toàn bộ thời gian cần thiết để cả PreStop hook thực thi và container dừng thủ công. Nếu terminationGracePeriodSeconds là 60 và hook cần 55 giây để hoàn thành và container mất 10 giây để stop sau khi nhận signal thì container sẽ bị thoát trước khi nó có thể thoát thủ công vì terminationGracePeriodSeconds ngắn hơn tổng thời gian (55+10) cần thiết để thực thi cả 2 quá trình.

Nếu PostStart hoặc PreStop lỗi thì container sẽ bị thoát.

Người dùng nên cố gắng giảm tải handler hết mức có thể. Tuy nhiên có những trường hợp cần chạy các command dài như là khi lưu trạng thái trước khi stop một container.

Hook delivery guarantees

Hook delivery có thể thực hiện ít nhất một lần, có nghĩa là một hook có thể được gọi nhiều lần cho bất kỳ event cụ thể nào, chẳng hạn như PostStart hoặc PreStop. Việc xử lý việc này một cách chính xác tùy thuộc vào việc triển khai hook.

Thông thường chỉ có delivery đơn tức là chỉ delivery một lần duy nhất. Ví dụ một HTTP hook receiver down và không thể nhận traffic thì sẽ không cố gắng resend nó nữa. Tuy nhiên trong một vài trường hợp thì có thể có double delivery. Ví dụ nếu kubelet khởi động lại trong quá trình đang gửi hook, hook có thể được resent sau khi kubelet đã khởi động lại hoàn tất.

Debugging Hook handlers

Log của một hook handler không được công khai trong các pod event. Nếu một handler lỗi thì nó sẽ broadcast một event. Ví dụ với PostStart thì nó là event FailedPostStartHook, và với PreStop thì là event FailedPreStopHook. Để tự tạo một event FailedPostStartHook ta có thể chỉnh sửa lifecycle-events.yaml để thay đổi command PostStart thành "badcommand" và apply nó. Đây là một số output ví dụ của các event bạn thấy nếu chạy kubectl describe pod lifecycle-demo:

Events:
  Type     Reason               Age              From               Message
  ----     ------               ----             ----               -------
  Normal   Scheduled            7s               default-scheduler  Successfully assigned default/lifecycle-demo to ip-XXX-XXX-XX-XX.us-east-2...
  Normal   Pulled               6s               kubelet            Successfully pulled image "nginx" in 229.604315ms
  Normal   Pulling              4s (x2 over 6s)  kubelet            Pulling image "nginx"
  Normal   Created              4s (x2 over 5s)  kubelet            Created container lifecycle-demo-container
  Normal   Started              4s (x2 over 5s)  kubelet            Started container lifecycle-demo-container
  Warning  FailedPostStartHook  4s (x2 over 5s)  kubelet            Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
  Normal   Killing              4s (x2 over 5s)  kubelet            FailedPostStartHook
  Normal   Pulled               4s               kubelet            Successfully pulled image "nginx" in 215.66395ms
  Warning  BackOff              2s (x2 over 3s)  kubelet            Back-off restarting failed con