Kubernetes中的優(yōu)雅關(guān)閉和零停機(jī)時(shí)間部署
在Kubernetes中,創(chuàng)建和刪除Pod是最常見(jiàn)的任務(wù)之一。
當(dāng)你執(zhí)行滾動(dòng)更新、擴(kuò)展部署、發(fā)布新版本、執(zhí)行作業(yè)和定時(shí)作業(yè)等操作時(shí),都會(huì)創(chuàng)建Pod。
但是,在Pod被驅(qū)逐后,例如將節(jié)點(diǎn)標(biāo)記為不可調(diào)度時(shí),Pod也會(huì)被刪除并重新創(chuàng)建。
如果這些Pod的性質(zhì)是如此短暫,那么當(dāng)一個(gè)Pod正在響應(yīng)請(qǐng)求時(shí),如果被告知關(guān)閉,會(huì)發(fā)生什么?在關(guān)閉之前,請(qǐng)求是否會(huì)完成?那么后續(xù)的請(qǐng)求呢?是否會(huì)被重定向到其他地方?
在討論P(yáng)od被刪除時(shí)會(huì)發(fā)生什么之前,有必要談?wù)劗?dāng)Pod被創(chuàng)建時(shí)會(huì)發(fā)生什么。
假設(shè)你想在集群中創(chuàng)建以下Pod:
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
你可以使用以下命令將YAML定義提交到集群中:
$ kubectl apply -f pod.yaml
一旦你輸入該命令,kubectl會(huì)將Pod定義提交給Kubernetes API。
在數(shù)據(jù)庫(kù)中保存集群的狀態(tài)
Pod的定義被API接收并進(jìn)行檢查,隨后存儲(chǔ)在數(shù)據(jù)庫(kù)(etcd)中。
Pod也被添加到調(diào)度器的隊(duì)列中。
調(diào)度器執(zhí)行以下操作:
檢查Pod的定義。
收集關(guān)于工作負(fù)載的詳細(xì)信息,例如CPU和內(nèi)存請(qǐng)求。
通過(guò)篩選器和判定的過(guò)程決定最適合運(yùn)行該P(yáng)od的節(jié)點(diǎn)。
在此過(guò)程結(jié)束時(shí):
- Pod在etcd中被標(biāo)記為已調(diào)度。
- Pod被分配給一個(gè)節(jié)點(diǎn)。
- Pod的狀態(tài)被存儲(chǔ)在etcd中。
然而,此時(shí)Pod仍然不存在。
- 當(dāng)你使用kubectl apply -f命令提交一個(gè)Pod的YAML文件時(shí),該YAML文件會(huì)被發(fā)送到Kubernetes API。
圖片
- API將Pod保存在數(shù)據(jù)庫(kù)(etcd)中。
圖片
3. 調(diào)度器為該P(yáng)od分配了最適合的節(jié)點(diǎn),Pod的狀態(tài)變?yōu)镻ending。此時(shí),Pod只存在于etcd中。
圖片
在控制平面中發(fā)生了前述任務(wù),并且狀態(tài)存儲(chǔ)在數(shù)據(jù)庫(kù)中。
**那么是誰(shuí)在你的節(jié)點(diǎn)上創(chuàng)建Pod呢?
Kubelet-Kubernetes代理
Kubelet 的任務(wù)是輪詢控制平面以獲取更新。
你可以想象kubelet不斷地向主節(jié)點(diǎn)發(fā)出請(qǐng)求:“我負(fù)責(zé)管理工作節(jié)點(diǎn)1,是否有新的Pod需要我處理?”
當(dāng)有一個(gè)Pod需要處理時(shí),kubelet會(huì)創(chuàng)建它,在某種程度上是這樣的。
kubelet并不是直接創(chuàng)建Pod。相反,它將工作委托給其他三個(gè)組件:
- 容器運(yùn)行時(shí)接口(CRI):負(fù)責(zé)為Pod創(chuàng)建容器。
- 容器網(wǎng)絡(luò)接口(CNI):負(fù)責(zé)將容器連接到集群網(wǎng)絡(luò)并分配IP地址。
- 容器存儲(chǔ)接口(CSI):負(fù)責(zé)在容器中掛載卷。
在大多數(shù)情況下,容器運(yùn)行時(shí)接口(CRI)的工作類似于:
$ docker run -d <my-container-image>
容器網(wǎng)絡(luò)接口(CNI)更有趣,因?yàn)樗?fù)責(zé)以下任務(wù):
- 為Pod生成有效的IP地址。
- 將容器連接到網(wǎng)絡(luò)的其他部分。
正如你所想象的,連接容器到網(wǎng)絡(luò)并分配有效的IP地址有多種方式(可以選擇IPv4或IPv6,甚至可以分配多個(gè)IP地址)。
以Docker為例,它會(huì)創(chuàng)建虛擬以太網(wǎng)對(duì)并將其附加到一個(gè)橋接器上;而AWS-CNI會(huì)直接將Pod連接到虛擬私有云(VPC)的其余部分。
當(dāng)容器網(wǎng)絡(luò)接口完成其工作時(shí),Pod將連接到網(wǎng)絡(luò)的其余部分,并被分配一個(gè)有效的IP地址。
但是存在一個(gè)問(wèn)題。
kubelet知道IP地址(因?yàn)樗{(diào)用了容器網(wǎng)絡(luò)接口),但控制平面不知道。
沒(méi)有人告訴主節(jié)點(diǎn)Pod已被分配了IP地址,并且準(zhǔn)備好接收流量。在控制平面的視角中,Pod仍在創(chuàng)建中。
kubelet的工作是收集Pod的所有細(xì)節(jié),例如IP地址,并將其報(bào)告給控制平面。
你可以想象,檢查etcd將不僅揭示Pod的運(yùn)行位置,還會(huì)顯示其IP地址。
1. Kubelet定期向控制平面輪詢更新。
2. 當(dāng)一個(gè)新的Pod被分配給它所在的節(jié)點(diǎn)時(shí),kubelet會(huì)獲取該P(yáng)od的詳細(xì)信息。
3. kubelet本身不會(huì)創(chuàng)建Pod,它依賴于三個(gè)組件:容器運(yùn)行時(shí)接口(Container Runtime Interface)、容器網(wǎng)絡(luò)接口(Container Network Interface)和容器存儲(chǔ)接口(Container Storage Interface)。
4. 一旦這三個(gè)組件都成功完成,Pod就會(huì)在你的節(jié)點(diǎn)上運(yùn)行,并分配了一個(gè)IP地址。
5. kubelet將IP地址報(bào)告給控制平面。
如果Pod不是任何服務(wù)的一部分,這就是任務(wù)的結(jié)束。Pod已創(chuàng)建并準(zhǔn)備好使用。
當(dāng)Pod是服務(wù)的一部分時(shí),還需要進(jìn)行一些額外的步驟。
Pods和服務(wù)
創(chuàng)建服務(wù)時(shí),通常需要注意兩個(gè)關(guān)鍵信息:
- 選擇器(selector):用于指定接收流量的Pod。
- 目標(biāo)端口(targetPort):Pod用于接收流量的端口。
一個(gè)典型的服務(wù)的YAML定義如下:
service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 3000
selector:
name: app
當(dāng)你使用kubectl apply將服務(wù)提交到集群時(shí),Kubernetes會(huì)查找所有具有與選擇器(name: app)相同標(biāo)簽的Pod,并收集它們的IP地址,但前提是它們通過(guò)了就緒探針(Readiness probe)。
然后,對(duì)于每個(gè)IP地址,Kubernetes會(huì)將IP地址和端口連接起來(lái)。
如果IP地址是10.0.0.3,目標(biāo)端口是3000,Kubernetes會(huì)將這兩個(gè)值連接起來(lái),并稱其為一個(gè)端點(diǎn)(endpoint)。
IP address + port = endpoint
---------------------------------
10.0.0.3 + 3000 = 10.0.0.3:3000
這些端點(diǎn)將以另一個(gè)名為Endpoint的對(duì)象形式存儲(chǔ)在etcd中。
有點(diǎn)困惑嗎?
在Kubernetes中,以下術(shù)語(yǔ)適用:
endpoint(本文和Learnk8s材料中以小寫字母e表示)是IP地址和端口對(duì)的組合(10.0.0.3:3000)。 Endpoint(本文和Learnk8s材料中以大寫字母E表示)是一組端點(diǎn)的集合。 Endpoint對(duì)象是Kubernetes中的一個(gè)真實(shí)對(duì)象,對(duì)于每個(gè)服務(wù),Kubernetes會(huì)自動(dòng)創(chuàng)建一個(gè)Endpoint對(duì)象。
你可以使用以下命令進(jìn)行驗(yàn)證:
$ kubectl get services,endpoints
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/my-service-1 ClusterIP 10.105.17.65 <none> 80/TCP
service/my-service-2 ClusterIP 10.96.0.1 <none> 443/TCP
NAME ENDPOINTS
endpoints/my-service-1 172.17.0.6:80,172.17.0.7:80
endpoints/my-service-2 192.168.99.100:8443
Endpoint會(huì)收集來(lái)自Pod的所有IP地址和端口。
但不僅如此。當(dāng)發(fā)生以下情況時(shí),Endpoint對(duì)象會(huì)使用新的端點(diǎn)列表進(jìn)行刷新:
- 創(chuàng)建一個(gè)Pod。
- 刪除一個(gè)Pod。
- 在Pod上修改標(biāo)簽。
因此,你可以想象,每當(dāng)你創(chuàng)建一個(gè)Pod,并且kubelet將其IP地址提交給主節(jié)點(diǎn)后,Kubernetes會(huì)更新所有的端點(diǎn)以反映這種變化:
$ kubectl get services,endpoints
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/my-service-1 ClusterIP 10.105.17.65 <none> 80/TCP
service/my-service-2 ClusterIP 10.96.0.1 <none> 443/TCP
NAME ENDPOINTS
endpoints/my-service-1 172.17.0.6:80,172.17.0.7:80
endpoints/my-service-2 192.168.99.100:8443
很好,端點(diǎn)被存儲(chǔ)在控制平面中,并且Endpoint對(duì)象已被更新。
1. 在這張圖片中,你的集群中部署了一個(gè)單獨(dú)的Pod。該P(yáng)od屬于一個(gè)服務(wù)。如果你要檢查etcd,你會(huì)發(fā)現(xiàn)Pod的詳細(xì)信息以及服務(wù)的信息。
2. 當(dāng)部署新的Pod時(shí)會(huì)發(fā)生什么?
3. Kubernetes需要跟蹤Pod及其IP地址。服務(wù)應(yīng)該將流量路由到新的端點(diǎn),因此IP地址和端口應(yīng)該被傳播。
4. 當(dāng)另一個(gè)Pod被部署時(shí)會(huì)發(fā)生什么?
5. 是的,完全相同的過(guò)程。在數(shù)據(jù)庫(kù)中創(chuàng)建了新的“行”來(lái)表示新的Pod,并將端點(diǎn)進(jìn)行傳播。
6. 當(dāng)刪除一個(gè)Pod時(shí)會(huì)發(fā)生什么?
7. 服務(wù)立即移除該端點(diǎn),最終該P(yáng)od也會(huì)從數(shù)據(jù)庫(kù)中刪除。
8. Kubernetes對(duì)你的集群中的每一個(gè)小變化都做出反應(yīng)。
在Kubernetes中使用端點(diǎn)
端點(diǎn)在Kubernetes中被多個(gè)組件使用。
Kube-proxy使用端點(diǎn)來(lái)在節(jié)點(diǎn)上設(shè)置iptables規(guī)則。
因此,每當(dāng)端點(diǎn)(Endpoint對(duì)象)發(fā)生變化時(shí),kube-proxy會(huì)獲取新的IP地址和端口列表,并編寫新的iptables規(guī)則。
- 讓我們考慮一個(gè)由三個(gè)節(jié)點(diǎn)組成的集群,其中有兩個(gè)Pod,并且沒(méi)有服務(wù)。Pod的狀態(tài)被存儲(chǔ)在etcd中。
2. 當(dāng)你創(chuàng)建一個(gè)服務(wù)(Service)時(shí)會(huì)發(fā)生什么?
3. Kubernetes創(chuàng)建了一個(gè)Endpoint對(duì)象,并收集了來(lái)自Pod的所有端點(diǎn)(IP地址和端口對(duì))。
4. kube-proxy守護(hù)程序訂閱對(duì)Endpoint的更改。
5. 當(dāng)Endpoint被添加、刪除或更新時(shí),kube-proxy會(huì)獲取新的端點(diǎn)列表。
6. kube-proxy使用端點(diǎn)來(lái)在集群的每個(gè)節(jié)點(diǎn)上創(chuàng)建iptables規(guī)則。
Ingress控制器也使用相同的端點(diǎn)列表。
Ingress控制器是集群中將外部流量路由到集群內(nèi)的組件。
當(dāng)你設(shè)置一個(gè)Ingress配置文件時(shí),通常會(huì)指定Service作為目標(biāo):
入口.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
service:
name: my-service
port:
number: 80
path: /
pathType: Prefix
實(shí)際上,流量并不會(huì)直接路由到Service。
相反,Ingress控制器建立了一個(gè)訂閱,以便在Service的端點(diǎn)發(fā)生變化時(shí)收到通知。
Ingress直接將流量路由到Pods,跳過(guò)了Service。
正如你所想象的那樣,每當(dāng)Endpoint(對(duì)象)發(fā)生變化時(shí),Ingress會(huì)獲取新的IP地址和端口列表,并重新配置控制器以包括新的Pods。
- 在這張圖片中,有一個(gè)帶有兩個(gè)副本的Ingress控制器和一個(gè)Service的部署(Deployment)。
2. 如果你想通過(guò)Ingress將外部流量路由到Pods,你應(yīng)該創(chuàng)建一個(gè)Ingress配置文件(一個(gè)YAML文件)。
3. 一旦你運(yùn)行kubectl apply -f ingress.yaml命令,Ingress控制器就會(huì)從控制平面中獲取配置文件。
4. Ingress的YAML文件中有一個(gè)serviceName屬性,用于描述應(yīng)該使用哪個(gè)Service。
5. Ingress控制器從Service中檢索端點(diǎn)列表,并跳過(guò)該Service。流量直接流向端點(diǎn)(Pods)。
6. 當(dāng)創(chuàng)建一個(gè)新的Pod時(shí)會(huì)發(fā)生什么?
7. 你已經(jīng)知道Kubernetes如何創(chuàng)建Pod并傳播端點(diǎn)信息了。
8. Ingress控制器訂閱對(duì)端點(diǎn)的更改。由于有一個(gè)新的變化,它會(huì)獲取新的端點(diǎn)列表。
9. Ingress控制器將流量路由到新的Pod上。
還有其他訂閱端點(diǎn)更改的Kubernetes組件的示例。
集群中的DNS組件CoreDNS就是其中之一。
如果你使用Headless類型的Service,CoreDNS將需要訂閱端點(diǎn)的更改,并在每次添加或刪除端點(diǎn)時(shí)重新配置自身。
同樣,服務(wù)網(wǎng)格(如Istio或Linkerd)、云服務(wù)提供商用于創(chuàng)建類型為L(zhǎng)oadBalancer的服務(wù),以及無(wú)數(shù)的操作員都會(huì)使用這些端點(diǎn)。
你必須記住,有多個(gè)組件訂閱端點(diǎn)的更改,它們可能在不同的時(shí)間接收到關(guān)于端點(diǎn)更新的通知。
這就足夠了,或者還有在創(chuàng)建Pod后發(fā)生的事情嗎?
創(chuàng)建Pod時(shí)發(fā)生的主要步驟的簡(jiǎn)要回顧:
- Pod被存儲(chǔ)在etcd中。
- 調(diào)度器分配一個(gè)節(jié)點(diǎn),并將該節(jié)點(diǎn)寫入etcd。
- kubelet收到新的已調(diào)度Pod的通知。
- kubelet將創(chuàng)建容器的任務(wù)委托給容器運(yùn)行時(shí)接口(CRI)。
- kubelet將容器連接到容器網(wǎng)絡(luò)接口(CNI)的任務(wù)委托給它。
- kubelet將容器中的掛載卷的任務(wù)委托給容器存儲(chǔ)接口(CSI)。
- 容器網(wǎng)絡(luò)接口分配一個(gè)IP地址。
- kubelet將IP地址報(bào)告給控制平面。
- IP地址被存儲(chǔ)在etcd中。
如果你的Pod屬于一個(gè)Service:
- kubelet等待成功的就緒探針(Readiness probe)。
- 所有相關(guān)的端點(diǎn)(對(duì)象)都會(huì)收到變更的通知。
- 端點(diǎn)將新的端點(diǎn)(IP地址+端口對(duì))添加到它們的列表中。
- Kube-proxy收到端點(diǎn)變更的通知。Kube-proxy在每個(gè)節(jié)點(diǎn)上更新iptables規(guī)則。
- Ingress控制器收到端點(diǎn)變更的通知。控制器將流量路由到新的IP地址上。
- CoreDNS收到端點(diǎn)變更的通知。如果Service的類型是Headless,DNS條目將被更新。
- 云服務(wù)提供商收到端點(diǎn)變更的通知。如果Service的類型是LoadBalancer,新的端點(diǎn)將作為負(fù)載均衡池的一部分進(jìn)行配置。
- 集群中安裝的任何服務(wù)網(wǎng)格都會(huì)收到端點(diǎn)變更的通知。
- 任何訂閱端點(diǎn)變更的其他操作員也會(huì)收到通知。
對(duì)于一個(gè)看似普通的任務(wù)——?jiǎng)?chuàng)建一個(gè)Pod來(lái)說(shuō),這個(gè)列表確實(shí)很長(zhǎng)。
Pod已經(jīng)運(yùn)行起來(lái)了。現(xiàn)在是時(shí)候討論一下當(dāng)你刪除Pod時(shí)會(huì)發(fā)生什么了。
刪除Pod
你可能已經(jīng)猜到了,但是當(dāng)刪除Pod時(shí),你需要按照相同的步驟但是逆序進(jìn)行操作。
首先,應(yīng)該從Endpoint(對(duì)象)中移除端點(diǎn)。
這次Readiness probe會(huì)被忽略,并且端點(diǎn)會(huì)立即從控制平面中刪除。
這反過(guò)來(lái)會(huì)觸發(fā)kube-proxy、Ingress控制器、DNS、服務(wù)網(wǎng)格等所有事件。
這些組件將更新其內(nèi)部狀態(tài),并停止將流量路由到該IP地址。
由于這些組件可能正在執(zhí)行其他操作,無(wú)法保證從其內(nèi)部狀態(tài)中刪除IP地址需要多長(zhǎng)時(shí)間。對(duì)于某些組件來(lái)說(shuō),可能只需要不到一秒的時(shí)間;而對(duì)于其他組件來(lái)說(shuō),可能需要更長(zhǎng)的時(shí)間。
對(duì)一些來(lái)說(shuō),這可能只需要不到一秒鐘;對(duì)其他來(lái)說(shuō),這可能需要更多。
1. 如果你使用kubectl delete pod刪除一個(gè)Pod,該命令首先會(huì)發(fā)送到Kubernetes API。
2. 該消息會(huì)被控制平面中的特定控制器——Endpoint控制器所攔截。
3. Endpoint控制器向API發(fā)送命令,將IP地址和端口從Endpoint對(duì)象中移除。
4. 誰(shuí)會(huì)監(jiān)聽Endpoint的更改?kube-proxy、Ingress控制器、CoreDNS等組件會(huì)收到關(guān)于這一變更的通知。
5. 一些組件,如kube-proxy,可能需要額外的時(shí)間來(lái)進(jìn)一步傳播這些更改。
與此同時(shí),etcd中的Pod狀態(tài)被更改為Terminating(終止中)。
kubelet收到此變更的通知,并進(jìn)行以下操作:
- 將容器中的任何掛載卷從容器存儲(chǔ)接口(CSI)卸載。
- 將容器從網(wǎng)絡(luò)中分離,并釋放IP地址給容器網(wǎng)絡(luò)接口(CNI)。
- 將容器銷毀給容器運(yùn)行時(shí)接口(CRI)。
換句話說(shuō),Kubernetes按照與創(chuàng)建Pod完全相同的步驟來(lái)進(jìn)行反向操作。
1. 如果你使用kubectl delete pod刪除一個(gè)Pod,該命令首先會(huì)發(fā)送到Kubernetes API。
2. 當(dāng)kubelet輪詢控制平面以獲取更新時(shí),它會(huì)注意到Pod已被刪除。
3. kubelet將銷毀Pod的任務(wù)委托給容器運(yùn)行時(shí)接口(Container Runtime Interface)、容器網(wǎng)絡(luò)接口(Container Network Interface)和容器存儲(chǔ)接口(Container Storage Interface)。
然而,這里存在一個(gè)微妙但關(guān)鍵的區(qū)別。
當(dāng)你終止一個(gè)Pod時(shí),移除端點(diǎn)和向kubelet發(fā)送的信號(hào)同時(shí)發(fā)出。
當(dāng)你首次創(chuàng)建一個(gè)Pod時(shí),Kubernetes會(huì)等待kubelet報(bào)告IP地址,然后開始端點(diǎn)傳播。
然而,當(dāng)你刪除一個(gè)Pod時(shí),事件會(huì)并行發(fā)生。
這可能導(dǎo)致多種競(jìng)爭(zhēng)條件的出現(xiàn)。
如果在端點(diǎn)傳播之前刪除了Pod會(huì)怎樣呢?
1. 刪除端點(diǎn)和刪除Pod同時(shí)進(jìn)行。
2. 因此,在kube-proxy更新iptables規(guī)則之前,你可能會(huì)先刪除端點(diǎn)。
3. 或者你可能更幸運(yùn),只有在端點(diǎn)完全傳播之后才刪除Pod。
優(yōu)雅關(guān)閉
當(dāng)一個(gè)Pod在從kube-proxy或Ingress控制器中移除端點(diǎn)之前被終止時(shí),你可能會(huì)遇到停機(jī)時(shí)間。
如果仔細(xì)思考一下,這是有道理的。
Kubernetes仍然將流量路由到該IP地址,但Pod已經(jīng)不存在了。
Ingress控制器、kube-proxy、CoreDNS等組件沒(méi)有足夠的時(shí)間將IP地址從其內(nèi)部狀態(tài)中移除。
理想情況下,Kubernetes應(yīng)該在刪除Pod之前等待集群中的所有組件都具有更新的端點(diǎn)列表。
但是Kubernetes并不是這樣工作的。
Kubernetes提供了強(qiáng)大的原始組件來(lái)分發(fā)端點(diǎn)(例如Endpoint對(duì)象和更高級(jí)的抽象,如Endpoint Slices)。
然而,Kubernetes并不驗(yàn)證訂閱端點(diǎn)變更的組件是否與集群狀態(tài)保持同步。
那么,為了避免這種競(jìng)爭(zhēng)條件并確保在端點(diǎn)傳播后刪除Pod,你可以做些什么呢?
你應(yīng)該等待。當(dāng)Pod即將被刪除時(shí),它會(huì)收到一個(gè)SIGTERM信號(hào)。
你的應(yīng)用程序可以捕獲該信號(hào)并開始關(guān)閉。
由于在Kubernetes中不太可能立即從所有組件中刪除端點(diǎn),你可以:
- 在退出之前等待更長(zhǎng)的時(shí)間。
- 盡管收到SIGTERM信號(hào),仍然處理傳入的流量。
- 最后,關(guān)閉現(xiàn)有的長(zhǎng)連接(例如數(shù)據(jù)庫(kù)連接或WebSockets)。
- 關(guān)閉進(jìn)程。
你應(yīng)該等待多長(zhǎng)時(shí)間呢?默認(rèn)情況下,Kubernetes會(huì)發(fā)送SIGTERM信號(hào),并在強(qiáng)制終止進(jìn)程之前等待30秒鐘。
因此,你可以在最初的15秒內(nèi)繼續(xù)正常運(yùn)行。
希望這個(gè)時(shí)間間隔足夠?qū)⒍它c(diǎn)移除的更改傳播到kube-proxy、Ingress控制器、CoreDNS等組件。
隨著時(shí)間的推移,越來(lái)越少的流量會(huì)到達(dá)你的Pod,直到最終停止。
在15秒之后,可以安全地關(guān)閉與數(shù)據(jù)庫(kù)(或任何持久連接)的連接并終止進(jìn)程。
如果你認(rèn)為需要更多時(shí)間,可以在20或25秒時(shí)停止進(jìn)程。
但是,請(qǐng)記住,Kubernetes將在30秒后強(qiáng)制終止進(jìn)程(除非你在Pod定義中更改了terminationGracePeriodSeconds)。
如果無(wú)法更改代碼以等待更長(zhǎng)時(shí)間怎么辦?你可以調(diào)用一個(gè)腳本等待固定的時(shí)間,然后讓應(yīng)用程序退出。
在調(diào)用SIGTERM之前,Kubernetes在Pod中提供了一個(gè)preStop鉤子。你可以將preStop鉤子設(shè)置為等待15秒鐘。
讓我們看一個(gè)示例:
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
lifecycle:
preStop:
exec:
command: ["sleep", "15"]
preStop鉤子是Pod生命周期鉤子之一。
15秒的延遲是推薦的時(shí)間嗎?這取決于情況,但這可能是開始測(cè)試的合理方式。
以下是你可以選擇的選項(xiàng)的總結(jié):
1. 你已經(jīng)知道,當(dāng)一個(gè)Pod被刪除時(shí),kubelet會(huì)收到這一變更的通知。
圖片
2. 如果Pod具有preStop鉤子,它會(huì)首先被調(diào)用。
3. 當(dāng)preStop鉤子完成后,kubelet會(huì)向容器發(fā)送SIGTERM信號(hào)。從那時(shí)起,容器應(yīng)該關(guān)閉所有長(zhǎng)連接并準(zhǔn)備終止。
4. 默認(rèn)情況下,進(jìn)程有30秒的時(shí)間退出,其中包括preStop鉤子的執(zhí)行時(shí)間。如果進(jìn)程在此期間未退出,kubelet將發(fā)送SIGKILL信號(hào)并強(qiáng)制終止進(jìn)程。
5. kubelet通知控制平面成功刪除了該P(yáng)od。
優(yōu)雅期限和滾動(dòng)更新
優(yōu)雅關(guān)閉適用于被刪除的Pod。
但如果你不刪除Pod呢?即使你不刪除Pod,Kubernetes也會(huì)定期刪除Pod。
特別是,每當(dāng)你部署應(yīng)用程序的新版本時(shí),Kubernetes會(huì)創(chuàng)建和刪除Pod。
當(dāng)你在部署中更改鏡像時(shí),Kubernetes會(huì)逐步推出變更。
pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
# image: nginx:1.18 OLD
image: nginx:1.19
ports:
- containerPort: 3000
如果你有三個(gè)副本,并且在提交新的YAML資源給Kubernetes后,Kubernetes會(huì):
- 創(chuàng)建一個(gè)包含新容器鏡像的Pod。
- 銷毀一個(gè)現(xiàn)有的Pod。
- 等待Pod就緒。
然后它會(huì)重復(fù)上述步驟,直到所有的Pod都遷移到新版本。
Kubernetes只有在新的Pod準(zhǔn)備好接收流量(也就是通過(guò)了就緒性檢查)后才會(huì)重復(fù)每個(gè)周期。
Kubernetes在繼續(xù)處理下一個(gè)Pod之前是否等待Pod被刪除?
不會(huì)。
如果你有10個(gè)Pod,并且每個(gè)Pod需要2秒鐘準(zhǔn)備就緒和20秒鐘關(guān)閉,情況如下:
- 首先創(chuàng)建一個(gè)新的Pod,并終止一個(gè)之前的Pod。
- 新的Pod需要2秒鐘準(zhǔn)備就緒,然后Kubernetes創(chuàng)建一個(gè)新的Pod。
- 與此同時(shí),正在終止的Pod保持終止?fàn)顟B(tài)20秒鐘。
在20秒鐘之后,所有新的Pod都處于活動(dòng)狀態(tài)(10個(gè)Pod,在2秒鐘后準(zhǔn)備就緒),而之前的10個(gè)Pod都處于終止?fàn)顟B(tài)(第一個(gè)終止的Pod即將退出)。
總體而言,在短時(shí)間內(nèi)你將擁有兩倍數(shù)量的Pod(10個(gè)運(yùn)行中,10個(gè)終止中)。
與就緒探針相比,寬限期限越長(zhǎng),你將同時(shí)擁有更多的運(yùn)行中(以及終止中)的Pod。
這是一件壞事嗎?不一定,因?yàn)槟阋⌒牡卮_保不丟失連接。
終止長(zhǎng)時(shí)間運(yùn)行的任務(wù)
那對(duì)于長(zhǎng)時(shí)間運(yùn)行的作業(yè)呢?
如果你正在轉(zhuǎn)碼一個(gè)大型視頻,有沒(méi)有辦法延遲停止Pod的操作?
想象一下,你有一個(gè)包含三個(gè)副本的部署。
每個(gè)副本被分配了一個(gè)視頻進(jìn)行轉(zhuǎn)碼,并且這個(gè)任務(wù)可能需要幾個(gè)小時(shí)才能完成。
當(dāng)你觸發(fā)滾動(dòng)更新時(shí),Pod在被終止之前有30秒的時(shí)間來(lái)完成任務(wù)。
你如何避免延遲關(guān)閉Pod的操作?你可以將terminationGracePeriodSeconds增加到幾個(gè)小時(shí)。
然而,在那個(gè)時(shí)間點(diǎn)上,Pod的端點(diǎn)是無(wú)法訪問(wèn)的。
如果你將指標(biāo)暴露用以監(jiān)控Pod,你的監(jiān)控工具將無(wú)法訪問(wèn)你的Pod。
為什么會(huì)這樣?
像Prometheus這樣的工具依賴于端點(diǎn)來(lái)抓取集群中的Pod。
然而,一旦你刪除Pod,端點(diǎn)刪除的信息會(huì)在集群中傳播,甚至傳遞給Prometheus!
與其增加寬限期限,你應(yīng)該考慮為每個(gè)新版本創(chuàng)建一個(gè)全新的部署。
當(dāng)你創(chuàng)建一個(gè)全新的部署時(shí),現(xiàn)有的部署將保持不變。
長(zhǎng)時(shí)間運(yùn)行的作業(yè)可以繼續(xù)正常處理視頻。一旦它們完成,你可以手動(dòng)刪除它們。
如果你希望自動(dòng)刪除它們,你可以設(shè)置一個(gè)自動(dòng)縮放器,當(dāng)任務(wù)用盡時(shí),它可以將部署的副本數(shù)縮減為零。
這種 Pod 自動(dòng)縮放器的一個(gè)例子是 Osiris——一個(gè) Kubernetes 的通用、縮放到零的組件。
這種技術(shù)有時(shí)被稱為Rainbow Deployments,在需要保持先前的Pod運(yùn)行時(shí)間長(zhǎng)于寬限期限的情況下非常有用。
另一個(gè)很好的例子是WebSockets。如果你正在向用戶實(shí)時(shí)傳輸更新,你可能不希望每次發(fā)布時(shí)都終止WebSockets。
如果你在一天內(nèi)頻繁發(fā)布,這可能會(huì)導(dǎo)致實(shí)時(shí)數(shù)據(jù)流中斷多次。
為每個(gè)發(fā)布創(chuàng)建一個(gè)新的部署是一個(gè)不太直觀但更好的選擇。
現(xiàn)有用戶可以繼續(xù)傳輸更新,而最新的部署為新用戶提供服務(wù)。
隨著用戶從舊的Pod斷開連接,你可以逐漸減少副本并淘汰過(guò)去的部署。
總結(jié)
你應(yīng)該注意從集群中刪除的Pod,因?yàn)樗鼈兊腎P地址可能仍然被用于路由流量。
與立即關(guān)閉Pod不同,你應(yīng)該考慮在應(yīng)用程序中等待更長(zhǎng)時(shí)間,或設(shè)置一個(gè)preStop鉤子。
只有在集群中的所有端點(diǎn)都被傳播并從kube-proxy、Ingress控制器、CoreDNS等中刪除后,才應(yīng)該刪除Pod。
如果你的Pod運(yùn)行長(zhǎng)時(shí)間的任務(wù),例如視頻轉(zhuǎn)碼或使用WebSockets提供實(shí)時(shí)更新,請(qǐng)考慮使用Rainbow Deployments。在Rainbow Deployments中,你為每個(gè)發(fā)布創(chuàng)建一個(gè)新的部署,并在連接(或任務(wù))耗盡時(shí)刪除先前的部署。
你可以在長(zhǎng)時(shí)間運(yùn)行的任務(wù)完成后手動(dòng)刪除舊的部署。或者,你可以自動(dòng)將部署的副本數(shù)縮減為零,以自動(dòng)化該過(guò)程。