當kubernetes對服務滾動更新的期間,默認配置的情況下可能會讓部分連接異常(比如連接被拒絕),我們來分析下原因並給出最佳實踐
滾動更新場景
使用 deployment 部署服務並關聯 service
- 修改 deployment 的 replica 調整副本數量來滾動更新
- 升級程序版本(修改鏡像tag)觸發 deployment 新建 replicaset 啓動新版本的 pod
- 使用 HPA (HorizontalPodAutoscaler) 來對 deployment 自動擴縮容
更新過程連接異常的原因
滾動更新時,service 對應的 pod 會被創建或銷燬,也就是 service 對應的 endpoint 列表會新增或移除endpoint,更新期間可能讓部分連接異常,主要原因是:
- pod 被創建,還沒完全啓動就被 endpoint controller 加入到 service 的 endpoint 列表,然後 kube-proxy 配置對應的路由規則(iptables/ipvs),如果請求被路由到還沒完全啓動完成的 pod,這時 pod 還不能正常處理請求,就會導致連接異常
- pod 被銷燬,但是從 endpoint controller watch 到變化並更新 service 的 endpoint 列表到 kube-proxy 更新路由規則這期間有個時間差,pod可能已經完全被銷燬了,但是路由規則還沒來得及更新,造成請求依舊還能被轉發到已經銷燬的 pod ip,導致連接異常
最佳實踐
- 針對第一種情況,可以給 pod 裏的 container 加 readinessProbe (就緒檢查),這樣可以讓容器完全啓動了才被endpoint controller加進 service 的 endpoint 列表,然後 kube-proxy 再更新路由規則,這時請求被轉發到的所有後端 pod 都是正常運行,避免了連接異常
- 針對第二種情況,可以給 pod 裏的 container 加 preStop hook,讓 pod 真正銷燬前先 sleep 等待一段時間,留點時間給 endpoint controller 和 kube-proxy 清理 endpoint 和路由規則,這段時間 pod 處於 Terminating 狀態,在路由規則更新完全之前如果有請求轉發到這個被銷燬的 pod,請求依然可以被正常處理,因爲它還沒有被真正銷燬
最佳實踐 yaml 示例:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: "nginx"
ports:
- name: http
hostPort: 80
containerPort: 80
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: 80
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
lifecycle:
preStop:
exec:
command: ["/bin/bash", "-c", "sleep 30"]
參考資料
- Container probes: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
- Container Lifecycle Hooks: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/