K8S自身帶有優雅終止Pod容器的機制,發送SIGTERM終止信號,在規定的terminationGracePeriodSeconds優雅時間內完成Pod優雅終止動作。
terminationGracePeriodSeconds默認是30秒,該時間是從Pod的Termination狀態開始計算的,包括了Prestop鈎子處理時間、SIGTERM信號發送即程序優雅處理時間。
Pod容器終止流程:
1)新Pod啟動,通過Readiness就緒性探測,加入service的endpoint服務列表。
2)老pod進入Termination狀態,從service的endpoint服務列表摘除,此時不會有新請求打到即將終止的老pod上。
3)如果設置了Prestop鈎子,則優先執行Prestop里的優雅動作。如果在規定的terminationGracePeriodSeconds優雅時間內(默認30s)完成不了,則kubelet會發送SIGTERM終止信號,並等待2秒,如果2秒后還未終止pod容器,則發送SIGKILL信號強制終止。
4)如果沒有設置Prestop鈎子,則發送SIGTERM終止信號優雅關閉容器進程,如果在規定的terminationGracePeriodSeconds優雅時間內(默認30s)未能終止pod容器,則發送SIGKILL信號強制終止。
需要注意:
1)SIGTERM終止信號只能被那些pid為1的父進程捕捉到,並優雅關閉容器進程。對於那些pid不為1的子進程是捕捉不到SIGTERM終止信號的。
所以對於單個容器只有一個pid為1的進程來說,使用K8S默認的優雅機制就可以,只需要拉長terminationGracePeriodSeconds優雅時間,確保在規定時間內完成容器優雅終止。
2)對於那些單個容器里有多個進程,即除了pid為1的進程外,還有子進程。這種情況下就需要設置Prestop鈎子函數,在prestop里提前優雅處理掉那些子進程,然后再通過SIGTERM正常終止掉pod容器。
注意設置好terminationGracePeriodSeconds優雅時間。
線上基於nacos注冊中心的優雅上線
對於請求通過k8s的service層到達pod容器的情況,可以通過k8s優雅機制來確保pod容器在上線滾動更新期間,做到業務"無感知"。但是目前線上pod容器服務主動注冊到nacos配置中心,業務方通過nacos網關調用pod容器服務,即調用請求繞過了k8s的service層。
這就出現了一個問題:pod容器更新期間,老pod已經優雅終止掉了,但是其ip和端口還在nacos的網關緩存里,調用請求會在nacos網關緩存未完全更新之前打到已經終止掉的pod地址,這就會出現連接超時,調用失敗錯誤,從而造成業務流量損失。
正確的做法:
1)拉長terminationGracePeriodSeconds的優雅時間。
2)設置Prestop鈎子,在Pod容器終止之前,在Prestop里通過nacos提供的API接口,主動摘除nacos注冊。接着sleep 30秒時間,用於刷新nacos網關緩存,摘除下線的pod地址。
3)最后再執行pod容器的優雅終止。
容器優雅發布的配置記錄:
這里以customer-services應用模塊的pod容器優雅配置為例:
1)將nacos主動下線的腳本在鏡像制作階段推送到容器內部
編寫customer-services主動下線nacos的腳本:
[root@k8s-storage01 ~]# ls /home/k8s_deploy/fin/online/deploy/customer-services/
Dockerfile service_offline_nacos.sh customer-services.jar
[root@k8s-storage01 ~]# cat /home/k8s_deploy/fin/online/deploy/customer-services/service_offline_nacos.sh
#!/bin/bash
serviceName="customer-services"
groupName="kevin-app"
metadata="preserved.register.source=SPRING_CLOUD"
namespaceId="online"
port="9810"
Token=$(curl -s --location --request POST 'http://nacos:8848/nacos/v1/auth/users/login' --form 'username=nacos' --form 'password=nacos'|awk -F"accessToken" '{print $2}'|awk -F":" '{print $2}'|awk -F'"' '{print $2}')
# 從nacos注冊中心下線
curl --location --request PUT "http://nacos:8848/nacos/v1/ns/instance?&accessToken=${Token}" \
--form "serviceName=${serviceName}" \
--form "clusterName=DEFAULT" \
--form "groupName=${groupName}" \
--form "metadata=${metadata}" \
--form "namespaceId=${namespaceId}" \
--form "ip=${podip}" \
--form "port=${port}" \
--form "ephemeral=true" \
--form "weight=1" \
--form "enabled=false"
# 等待30s,刷新nacos網關緩存
sleep 30
制作finhub-customer-services服務的容器鏡像
[root@k8s-storage01 ~]# cat /home/k8s_deploy/fin/online/deploy/customer-services/Dockerfile FROM 172.16.60.196/kevin/jdk1.8.0_192 RUN rm -f /etc/localtime \ && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone ENV LANG en_US.UTF-8 COPY customer-services.jar /usr/local/src COPY service_offline_nacos.sh /opt/ WORKDIR /usr/local/src EXPOSE 9810 CMD ["nohup","java","-jar","customer-services.jar","&"]
制作和推送鏡像
[root@k8s-storage01 ~]# docker build -t 172.16.60.196/kevin/customer-services_v1 . [root@k8s-storage01 ~]# docker push 172.16.60.196/kevin/customer-services_v1
2)配置pod的yml部署文件,添加env變量動態獲取pod ip地址、拉長terminationGracePeriodSeconds優雅時間、設置prestop鈎子
[root@k8s-master01 customer-services]# pwd
/opt/k8s/k8s-project/kevin/customer-services
[root@k8s-master01 customer-services]# cat customer-services.yml
apiVersion: v1
kind: Service
metadata:
name: customer-services
namespace: kevin
labels:
app: customer-services
spec:
type: NodePort
selector:
app: customer-services
ports:
- name: http
port: 9810
targetPort: 9810
nodePort: 39810
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: customer-services
namespace: kevin
spec:
replicas: 2
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: customer-services
template:
metadata:
labels:
app: customer-services
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- customer-services
topologyKey: "kubernetes.io/hostname"
terminationGracePeriodSeconds: 60
containers:
- name: customer-services
image: 172.16.60.196/kevin/customer-services_v1
imagePullPolicy: Always
ports:
- name: customer-port
containerPort: 9810
env:
- name: podip
valueFrom:
fieldRef:
fieldPath: status.podIP
resources:
requests:
cpu: 950m
memory: 2048Mi
limits:
cpu: 1500m
memory: 4096Mi
lifecycle:
postStart:
exec:
command: ["/bin/sh","-c","touch /tmp/health"]
preStop:
exec:
command: ["/bin/sh","-c","sh /opt/service_offline_nacos.sh"]
livenessProbe:
exec:
command: ["test","-e","/tmp/health"]
initialDelaySeconds: 5
timeoutSeconds: 5
periodSeconds: 10
readinessProbe:
tcpSocket:
port: customer-port
initialDelaySeconds: 15
timeoutSeconds: 5
periodSeconds: 20
volumeMounts:
- name: customerlog
mountPath: /var/log/customer-services
readOnly: false
volumes:
- name: customerlog
hostPath:
path: /var/log/k8s_log/customer-services
容器發布流程:

生產環境通過Jenkins配置的Pod容器部署流程:即從Gitlab拉取代碼、打包、制作鏡像、上傳鏡像到Harbor倉庫、更新pod等發布流程。完成如上配置,后續通過Jenkins進行"容器應用的優雅"發版了。
