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進行"容器應用的優雅"發版了。