Connection Draining & Graceful Shutdown — Không Cắt Connection Đang Chạy
Tại sao connection draining quan trọng
Khi một backend bị remove khỏi Load Balancer — dù do scale down, rolling update, hay node failure — có thể có hàng trăm connections đang in-flight. Nếu connections này bị cắt ngay lập tức:
- Clients nhận TCP RST → request fail
- Clients phải retry → tăng latency
- Long-running operations (file upload, streaming) bị interrupt
Connection draining giải quyết bằng cách cho phép backend "hoàn thành" các requests đang xử lý trước khi bị removed hoàn toàn.
Cơ chế Connection Draining
drainingTimeoutSec
# BackendConfig cho GKE Ingress
spec:
connectionDraining:
drainingTimeoutSec: 60Khi backend được mark để remove (health check fail hoặc bị explicitly removed):
- GCP LB ngừng gửi requests mới đến backend đó
- Existing connections được giữ cho đến khi hoàn thành hoặc
drainingTimeoutSechết - Sau
drainingTimeoutSec, connections còn lại bị force-close
drainingTimeoutSec: 0 — disable draining. Connections bị cắt ngay.
Range: 0–3600 giây. Default: 0 (không drain).
Proxy LB draining — Ai giữ connections?
Với Proxy LB (Application LB, Proxy NLB), proxy layer là nơi giữ connections trong giai đoạn draining. GFE hay Envoy proxy biết backend đang trong draining state và:
- Không gửi requests mới đến backend
- Giữ nguyên connections hiện có đến backend
- Chờ backend close connections hoặc timeout hết
Backend trong draining state chuyển sang DRAINING health status — không nhận traffic mới nhưng cũng chưa bị removed.
Passthrough NLB draining — Khác biệt cơ bản
Passthrough NLB không có proxy, nên "draining" có ngữ nghĩa khác:
- Khi backend được mark unhealthy hoặc removed, Maglev loại backend khỏi consistent hash table
- New connections không được route đến backend đó
- Existing connections (đã được track bởi Andromeda connection tracking) tiếp tục đến backend đó cho đến khi connection tự đóng
Với Passthrough NLB, draining xảy ra tự nhiên theo connection lifecycle — không cần explicit drainingTimeoutSec config.
Phối hợp với Pod lifecycle trong Kubernetes
Connection draining ở GCP LB chỉ là một phần của graceful shutdown. Cần phối hợp với Pod termination sequence:
Kubernetes xóa Pod
│
├─1─ kubelet gửi SIGTERM đến container
│
├─2─ preStop hook chạy (nếu có)
│ ├── sleep 5 (để kube-proxy/NEG controller có thời gian update)
│ └── Hoặc custom shutdown logic
│
├─3─ NEG controller nhận EndpointSlice update
│ └── Remove Pod khỏi NEG
│
├─4─ GCP LB nhận NEG update
│ └── Ngừng gửi traffic mới đến Pod
│ └── Bắt đầu draining countdown (drainingTimeoutSec)
│
├─5─ Pod hoàn thành requests in-flight
│
└─6─ terminationGracePeriodSeconds hết → SIGKILLRace condition nguy hiểm
Nếu terminationGracePeriodSeconds (mặc định 30 giây) ngắn hơn drainingTimeoutSec, Pod bị SIGKILL trước khi draining hoàn thành. Connections vẫn đang trong draining queue của GCP LB nhưng backend đã chết → clients nhận connection reset.
Quy tắc: terminationGracePeriodSeconds phải lớn hơn drainingTimeoutSec + thời gian process requests in-flight.
spec:
terminationGracePeriodSeconds: 90 # Phải > drainingTimeoutSec (60)
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # Cho NEG update timepreStop hook — Thêm thời gian cho NEG update
NEG controller cần thời gian để detect Pod termination và remove endpoint khỏi NEG. Trong khoảng thời gian này (vài giây đến ~30 giây), GCP LB vẫn route traffic đến Pod đang shutdown.
preStop hook giải quyết bằng cách delay quá trình shutdown. Trong thời gian delay, NEG controller hoàn thành việc remove endpoint → GCP LB ngừng gửi traffic → Pod có thể shutdown sạch.
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 15" # 15 giây cho NEG controller updateLong-polling và WebSocket connections
Connections tồn tại lâu (WebSocket, gRPC streaming, long-polling) là thách thức cho connection draining:
Nếu drainingTimeoutSec: 60 nhưng WebSocket connections tồn tại hàng giờ, draining sẽ cắt chúng sau 60 giây — không graceful.
Các approaches:
- Tăng
drainingTimeoutSec— nhưng delay deployment completion. Không phù hợp nếu connections rất long-lived. - Application-level draining: Application nhận SIGTERM, gửi "close" frame đến WebSocket clients, chờ clients disconnect gracefully trước khi shutdown.
- Kết hợp:
drainingTimeoutSecđủ dài cho P99 request duration, application xử lý graceful close cho long-lived connections.
Impact khi drainingTimeoutSec cao với Instance Group backends
Theo tài liệu GKE chính thức: "A high value for drainingTimeoutSec on instance group backends can delay Ingress updates for other backends."
Với instance group backends, một Ingress update yêu cầu update Backend Service. Nếu Backend Service đang có backends trong draining state (với high timeout), update operation phải chờ draining hoàn thành. Điều này có thể delay các changes khác như thêm backends mới.
Với NEG backends, issue này không xảy ra — operations trên individual endpoints không block Backend Service updates.
Monitoring connection draining
# Kiểm tra backend health và draining status
gcloud compute backend-services get-health my-backend-service \
--global
# Xem current connections đến backend
gcloud monitoring metrics list \
--filter="metric.type=loadbalancing.googleapis.com/l7/backend/request_count"CloudWatch metric loadbalancing.googleapis.com/l7/backend/connection_count cho biết số connections hiện tại đến mỗi backend — giúp verify draining đang hoạt động.