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