怎么使用Pod的Liveness和Readiness與StartupProbe
本文轉載自微信公眾號「運維開發(fā)故事」,作者沒有文案的夏老師。轉載本文請聯(lián)系運維開發(fā)故事公眾號。
怎么配置Pod的liveness和readiness與startup探針
當你使用kubernetes的時候,有沒有遇到過Pod在啟動后一會就掛掉然后又重新啟動這樣的惡性循環(huán)?你有沒有想過kubernetes是如何檢測pod是否還存活?雖然容器已經(jīng)啟動,但是kubernetes如何知道容器的進程是否準備好對外提供服務了呢?讓我們通過kubernetes官網(wǎng)的這篇文章Configure Liveness and Readiness Probes,來一探究竟。
本文將展示如何配置容器的存活和可讀性探針。
Kubelet使用liveness probe(存活探針)來確定何時重啟容器。例如,當應用程序處于運行狀態(tài)但無法做進一步操作,liveness探針將捕獲到deadlock,重啟處于該狀態(tài)下的容器,使應用程序在存在bug的情況下依然能夠繼續(xù)運行下去(誰的程序還沒幾個bug呢)。
Kubelet使用readiness probe(就緒探針)來確定容器是否已經(jīng)就緒可以接受流量。只有當Pod中的容器都處于就緒狀態(tài)時kubelet才會認定該Pod處于就緒狀態(tài)。該信號的作用是控制哪些Pod應該作為service的后端。如果Pod處于非就緒狀態(tài),那么它們將會被從service的load balancer中移除。
Kubelet使用startup probe(啟動探針)來確定容器是否已經(jīng)啟動。有時候,會有一些現(xiàn)有的應用程序在啟動時需要較多的初始化時間。要不影響對引起探測死鎖的快速響應,在這種情況下,設置存活探測參數(shù)是要技巧的。技巧就是使用一個命令來設置啟動探測,針對HTTP 或者 TCP 檢測,可以通過設置 failureThreshold * periodSeconds 參數(shù)來保證有足夠長的時間應對糟糕情況下的啟動時間。
定義 liveness命令
許多長時間運行的應用程序最終會轉換到broken狀態(tài),除非重新啟動,否則無法恢復。Kubernetes提供了liveness probe來檢測和補救這種情況。在本次練習將基于 gcr.io/google_containers/busybox鏡像創(chuàng)建運行一個容器的Pod。以下是Pod的配置文件exec-liveness.yaml:
- apiVersion: v1
- kind: Pod
- metadata:
- labels:
- test: liveness
- name: liveness-exec
- spec:
- containers:
- - name: liveness
- args:
- - /bin/sh
- - -c
- - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
- image: gcr.io/google_containers/busybox
- livenessProbe:
- exec:
- command:
- - cat
- - /tmp/healthy
- initialDelaySeconds: 5
- periodSeconds: 5
該配置文件給Pod配置了一個容器。periodSeconds 規(guī)定kubelet要每隔5秒執(zhí)行一次liveness probe。initialDelaySeconds 告訴kubelet在第一次執(zhí)行probe之前要的等待5秒鐘。探針檢測命令是在容器中執(zhí)行 cat /tmp/healthy 命令。如果命令執(zhí)行成功,將返回0,kubelet就會認為該容器是活著的并且很健康。如果返回非0值,kubelet就會殺掉這個容器并重啟它。容器啟動時,執(zhí)行該命令:
- /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
在容器生命的最初30秒內有一個 /tmp/healthy 文件,在這30秒內 cat /tmp/healthy命令會返回一個成功的返回碼。30秒后, cat /tmp/healthy 將返回失敗的返回碼。創(chuàng)建Pod:
- kubectl create -f https://k8s.io/docs/tasks/configure-pod-container/exec-liveness.yaml
在30秒內,查看Pod的event:
- kubectl describe pod liveness-exec
結果顯示沒有失敗的liveness probe:
- FirstSeen LastSeen Count From SubobjectPath Type Reason Message
- --------- -------- ----- ---- ------------- -------- ------ -------
- 24s 24s 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-exec to worker0
- 23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Pulling pulling image "gcr.io/google_containers/busybox"
- 23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "gcr.io/google_containers/busybox"
- 23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Created Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
- 23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Started Started container with docker id 86849c15382e
啟動35秒后,再次查看pod的event:
- kubectl describe pod liveness-exec
在最下面有一條信息顯示liveness probe失敗,容器被刪掉并重新創(chuàng)建。
- FirstSeen LastSeen Count From SubobjectPath Type Reason Message
- --------- -------- ----- ---- ------------- -------- ------ -------
- 37s 37s 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-exec to worker0
- 36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Pulling pulling image "gcr.io/google_containers/busybox"
- 36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "gcr.io/google_containers/busybox"
- 36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Created Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
- 36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Started Started container with docker id 86849c15382e
- 2s 2s 1 {kubelet worker0} spec.containers{liveness} Warning Unhealthy Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
再等30秒,確認容器已經(jīng)重啟:
- kubectl get pod liveness-exec
從輸出結果來RESTARTS值加1了
- NAME READY STATUS RESTARTS AGE
- liveness-exec 1/1 Running 1 1m
定義一個liveness HTTP請求
我們還可以使用HTTP GET請求作為liveness probe。下面是一個基于gcr.io/google_containers/liveness鏡像運行了一個容器的Pod的例子http-liveness.yaml:該配置文件只定義了一個容器,livenessProbe 指定kubelet需要每隔3秒執(zhí)行一次liveness probe。initialDelaySeconds 指定kubelet在該執(zhí)行第一次探測之前需要等待3秒鐘。該探針將向容器中的server的8080端口發(fā)送一個HTTP GET請求。如果server的/healthz路徑的handler返回一個成功的返回碼,kubelet就會認定該容器是活著的并且很健康。如果返回失敗的返回碼,kubelet將殺掉該容器并重啟它。任何大于200小于400的返回碼都會認定是成功的返回碼。其他返回碼都會被認為是失敗的返回碼。最開始的10秒該容器是活著的, /healthz handler返回200的狀態(tài)碼。這之后將返回500的返回碼。
- http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
- duration := time.Now().Sub(started)
- if duration.Seconds() > 10 {
- w.WriteHeader(500)
- w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
- } else {
- w.WriteHeader(200)
- w.Write([]byte("ok"))
- }
- })
容器啟動3秒后,kubelet開始執(zhí)行健康檢查。第一次健康監(jiān)測會成功,但是10秒后,健康檢查將失敗,kubelet將殺掉和重啟容器。創(chuàng)建一個Pod來測試一下HTTP liveness檢測:
- kubectl create -f https://k8s.io/docs/tasks/configure-pod-container/http-liveness.yaml
After 10 seconds, view Pod events to verify that liveness probes have failed and the Container has been restarted: 10秒后,查看Pod的event,確認liveness probe失敗并重啟了容器。
- kubectl describe pod liveness-http
定義TCP liveness探針
第三種liveness probe使用TCP Socket。使用此配置,kubelet將嘗試在指定端口上打開容器的套接字。如果可以建立連接,容器被認為是健康的,如果不能就認為是失敗的。
- apiVersion: v1
- kind: Pod
- metadata:
- name: goproxy
- labels:
- app: goproxy
- spec:
- containers:
- - name: goproxy
- image: gcr.io/google_containers/goproxy:0.1
- ports:
- - containerPort: 8080
- readinessProbe:
- tcpSocket:
- port: 8080
- initialDelaySeconds: 5
- periodSeconds: 10
- livenessProbe:
- tcpSocket:
- port: 8080
- initialDelaySeconds: 15
- periodSeconds: 20
TCP檢查的配置與HTTP檢查非常相似。此示例同時使用了readiness和liveness probe。容器啟動后5秒鐘,kubelet將發(fā)送第一個readiness probe。這將嘗試連接到端口8080上的goproxy容器。如果探測成功,則該pod將被標記為就緒。Kubelet將每隔10秒鐘執(zhí)行一次該檢查。除了readiness probe之外,該配置還包括liveness probe。容器啟動15秒后,kubelet將運行第一個liveness probe。就像readiness probe一樣,這將嘗試連接到goproxy容器上的8080端口。如果liveness probe失敗,容器將重新啟動。使用命名的端口,可以使用命名的ContainerPort作為HTTP或TCP liveness檢查:
- ports:
- - name: liveness-port
- containerPort: 8080
- hostPort: 8080
- livenessProbe:
- httpGet:
- path: /healthz
- port: 8080
定義readiness探針
有時,應用程序暫時無法對外部流量提供服務。例如,應用程序可能需要在啟動期間加載大量數(shù)據(jù)或配置文件。在這種情況下,你不想殺死應用程序,但你也不想發(fā)送請求。Kubernetes提供了readiness probe來檢測和減輕這些情況。Pod中的容器可以報告自己還沒有準備,不能處理Kubernetes服務發(fā)送過來的流量。
Readiness probe的配置跟liveness probe很像。唯一的不同是使用 readinessProbe而不是livenessProbe。
- readinessProbe:
- exec:
- command:
- - cat
- - /tmp/healthy
- initialDelaySeconds: 5
- periodSeconds: 5
Readinessprobe的HTTP和TCP的探測器配置跟liveness probe一樣。Readiness和livenssprobe可以并行用于同一容器。使用兩者可以確保流量無法到達未準備好的容器,并且容器在失敗時重新啟動。
定義startup探針
這是kubernetes1.16帶來的新功能。Probes 允許 Kubernetes 監(jiān)控應用程序的狀態(tài)??梢允褂胠ivenessProbe定期檢查應用程序是否還活著。一個示例容器定義了這個探測器:
- livenessProbe : httpGet:
- path: /healthz
- port: 8080
- failureThreshold: 3
- periodSeconds: 10
如果在 30 秒內失敗 3 次,容器將重新啟動。但是由于這個容器很慢,需要30多秒才能啟動,所以探針會失敗,容器會再次重啟。這個新特性讓你可以定義一個startupProbe在 pod 完成啟動之前阻止所有其他探測的方法:
- startupProbe:
- httpGet:
- path: /healthz
- port: 8080
- failureThreshold: 30
- periodSeconds: 10
現(xiàn)在我們的慢速容器最多有 5 分鐘(30 個檢查 * 10 秒 = 300 秒)來完成它的啟動。
配置Probe
Probe 中有很多精確和詳細的配置,通過它們你能準確的控制liveness和readiness檢查:
- initialDelaySeconds:容器啟動后第一次執(zhí)行探測是需要等待多少秒。
- periodSeconds:執(zhí)行探測的頻率。默認是10秒,最小1秒。
- timeoutSeconds:探測超時時間。默認1秒,最小1秒。
- successThreshold:探測失敗后,最少連續(xù)探測成功多少次才被認定為成功。默認是1。對于liveness必須是1。最小值是1。
- failureThreshold:探測成功后,最少連續(xù)探測失敗多少次才被認定為失敗。默認是3。最小值是1。
HTTP probe 可以給 httpGet設置其他配置項:
- host:連接的主機名,默認連接到pod的IP。你可能想在http header中設置"Host"而不是使用IP。
- scheme:連接使用的schema,默認HTTP。
- path: 訪問的HTTP server的path。
- httpHeaders:自定義請求的header。HTTP運行重復的header。
- port:訪問的容器的端口名字或者端口號。端口號必須介于1和65535之間。
對于HTTP探測器,kubelet向指定的路徑和端口發(fā)送HTTP請求以執(zhí)行檢查。Kubelet將probe發(fā)送到容器的IP地址,除非地址被httpGet中的可選host字段覆蓋。在大多數(shù)情況下,你不想設置主機字段。有一種情況下你可以設置它。假設容器在127.0.0.1上偵聽,并且Pod的hostNetwork字段為true。然后,在httpGet下的host應該設置為127.0.0.1。如果你的pod依賴于虛擬主機,這可能是更常見的情況,你不應該用host,而是應該在httpHeaders中設置Host頭。
我們應該怎么做?
- 對于提供 HTTP 協(xié)議(REST 服務等)的微服務, 始終定義一個ReadinessProbe,用于檢查的應用程序(Pod)是否已準備好接收流量。
- 對于慢啟動的應用,我們應該使用StartupProbe,來防止容器沒有啟動,就被LivenessProbe殺死了。
- 如果服務是多端口的,請確保ReadinessProbe覆蓋了所以的端口。例如:為readinessProbe使用“admin”或“management”端口(例如 9090)時,請確保端點僅在主要 HTTP 端口(例如 8080)準備好接受流量時才返回 success.
- 使用httpGet對服務端口與路徑(例如 /health)進行就緒探測。
我們不應該怎么做?
- 不要依賴外部依賴項(如數(shù)據(jù)存儲)進行就緒/探活檢查,因為這可能會導致級聯(lián)故障
1. 假如10 個pod的服務,數(shù)據(jù)庫使用Postgres,緩存使用redis:當你的探針的路徑依賴于工作的redis連接時,如果出現(xiàn)redis/網(wǎng)絡故障,則所有 10 個 Pod 都將“重啟”——這通常會產(chǎn)生影響比它應該的更糟。因為服務還能到Postgres拿去數(shù)據(jù)。
2. 服務最好不要與數(shù)據(jù)庫做強依賴。
3. 只探測自己內部的端口,不要去探測外部pod的端口。探測器不應依賴于同一集群中其他 Pod 的狀態(tài),以防止級聯(lián)故障。
- 需要明確知道使用 Liveness Probe 的原因,否則不要為 Pod 使用 Liveness Probe。
Liveness Probe 可以幫助恢復“卡住”的容器,但是當我們能控制我們的應用程序,出現(xiàn)意料之外的“卡住”進程和死鎖之類的故障,更好的選擇是從應用內部故意崩潰以恢復到已知良好狀態(tài)。
失敗的 Liveness Probe 將導致容器重啟,從而可能使與負載相關的錯誤的影響變得更糟:容器重啟將導致停機(至少的應用程序的啟動時間,例如 30s+),從而導致更多錯誤并為其他容器提供更多流量負載,導致更多失敗的容器,等等
Liveness Probes 與外部依賴相結合是導致級聯(lián)故障的最壞情況:單個環(huán)境的小問題將重新啟動所有容器。
- 如果使用 LivenessProbe,請不要為LivenessProbe和ReadinessProbe設置相同的規(guī)范
可以使用具有相同運行狀況檢查但具有更高failureThreshold的 Liveness Probe (例如,在 3 次嘗試后標記為未就緒,在 10 次嘗試后將 Liveness Probe 標記為失敗)
不要使用“exec”探測器,它們存在導致僵尸進程的。因為我們寫的應用進程很大一部分不會解決依附在主進程上的進程的
總結
- 為 Web 應用程序使用ReadinessProbe來決定 Pod 何時應接收流量
- 不正確使用Readiness/LivenessProbes可能導致可用性降低和級聯(lián)故障
- 對于慢啟動的應用,我們應該使用StartupProbe
參考:
https://jimmysong.io/kubernetes-handbook/guide/configure-liveness-readiness-probes.html
https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html#id6