健康檢查的意義
為了保證叢集的穩定性,需要及時的對故障服務進行處理。無論是通過下線的方式,不再将請求轉發到該服務,還是通過重新開機的方式,使服務得以自動恢複,都是有效的處理方式。
健康檢查的政策
而如何檢測出服務是否有故障,k8s提供了以下的健康檢查政策。
- livenessProbe 存活性探針
- readinessProbe 就緒性探針
livenessProbe,k8s利用存活性探針去檢測一個容器的運作狀态,如果容器處于running的健康狀态,此時不作任何處理。如果容器崩潰,即檢查失敗,則k8s會根據之前設定的重新開機政策去重新啟動該容器。
重新開機政策有以下幾種:
- always:預設的重新開機政策,當容器停止,會建立新的容器
- onFailure:容器異常退出,重新開機容器
- Never:當容器終止退出,從不重新開機容器
readinessProbe,就緒性探針旨在告訴k8s叢集知道我們啟動的容器在什麼時候可以接收請求。如果就緒性探針檢查通過,說明該容器可以接收請求,控制器就會将此容器所在的Pod加入到Service的EndPoint清單中,那麼Service則會将流量分發到該Pod上。反之,如果就緒性探針檢查失敗,表明該Pod還沒準備好,不可以接收請求,那麼控制器就會将此容器所在的Pod從對應的Service的EndPoint清單中移除。
當下次就緒性探針檢查成功後,Service則會将流量再重新轉發到該Pod上。
兩者的差別簡單來講,
livenessProbe,容器不存活,就重新開機該容器
readinessProbe,容器不就緒,就不轉發請求給該容器
健康檢查的檢測方法
每種探針都支援以下三種方法:
- Exec:通過執行指令來檢查服務是否正常,針對複雜檢測或無HTTP接口的服務,指令傳回值為0則表示容器健康。
- HTTPGet:通過發送http或https請求檢查服務是否正常,傳回【200,400)狀态碼則表明容器健康。
- TCPSocket:嘗試在指定端口上建立TCP連接配接。如果它可以建立連接配接,容器被認為是健康的; 如果它不能被認為是不健康的。這常用于對gRPC或FTP服務的探測。
探針探測的結果有以下幾種:
- Success:目标容器通過了檢查。
- Failure:目标容器未通過檢查。
- Unknown:未能執行檢查,是以不采取任何措施。
LivenessProbe探針配置
示例一:通過exec方式做健康探測
exec-liveness.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
在該配置檔案中,對容器執行livenessProbe檢查,periodSeconds字段指定kubelet每5s執行一次檢查,檢查的指令為cat /tmp/healthy,initialDelaySeconds字段告訴kubelet應該在執行第一次檢查之前等待5秒。
如果指令執行成功,則傳回0,那麼kubelet就認為容器是健康的,如果為非0,則Kubelet會Kill掉容器并根據重新開機政策來決定是否需要重新開機。
當容器啟動時,它會執行以下指令:
/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傳回失敗代碼。
建立Pod:
kubectl create -f exec-liveness.yaml
在30秒内,檢視Pod事件:
kubectl describe pod liveness-exec
輸出表明尚未探測到失敗:
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 "k8s.gcr.io/busybox"
23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "k8s.gcr.io/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事件:
kubectl describe pod liveness-exec
在輸出的中顯示探測失敗,并且容器已被殺死并重新建立。
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 "k8s.gcr.io/busybox"
36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "k8s.gcr.io/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秒,确認Container已重新啟動:
kubectl get pod liveness-exec
下面輸出中RESTARTS的次數已增加:
AME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 1m
示例二:通過HTTP方式做健康探測
http-liveness.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
在配置檔案中,使用k8s.gcr.io/liveness鏡像,建立出一個Pod,其中periodSeconds字段指定kubelet每3秒執行一次探測,initialDelaySeconds字段告訴kubelet延遲等待3秒,探測方式為向容器中運作的服務發送HTTP GET請求,請求8080端口下的/healthz, 任何大于或等于200且小于400的代碼表示成功。任何其他代碼表示失敗。
建立此Pod
kubectl create -f http-liveness.yaml
10秒後,檢視Pod事件以驗證liveness探測失敗并且Container已重新啟動:
kubectl describe pod liveness-http
httpGet探測方式有如下可選的控制字段
- host:要連接配接的主機名,預設為Pod IP,可以在http request head中設定host頭部。
- scheme: 用于連接配接host的協定,預設為HTTP。
- path:http伺服器上的通路URI。
- httpHeaders:自定義HTTP請求headers,HTTP允許重複headers。
- port: 容器上要通路端口号或名稱。
示例三:通過TCP方式做健康探測
Kubelet将嘗試在指定的端口上打開容器上的套接字,如果能建立連接配接,則表明容器健康。
tcp-liveness-readiness.yaml
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
TCP檢查方式和HTTP檢查方式非常相似,示例中兩種探針都使用了,在容器啟動5秒後,kubelet将發送第一個readinessProbe探針,這将連接配接到容器的8080端口,如果探測成功,則該Pod将被辨別為ready,10秒後,kubelet将進行第二次連接配接。
除此之後,此配置還包含了livenessProbe探針,在容器啟動15秒後,kubelet将發送第一個livenessProbe探針,仍然嘗試連接配接容器的8080端口,如果連接配接失敗則重新開機容器。
建立該pod:
kubectl create -f tcp-liveness-readiness.yaml
15秒後,檢視Pod事件以驗證活動探測:
kubectl describe pod goproxy
當容器有多個端口時,通常會給每個端口命名,是以在使用探針探測時,也可以直接寫自定義的端口名稱
ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: liveness-port
ReadinessProbe探針配置:
ReadinessProbe探針的使用場景livenessProbe稍有不同,有的時候應用程式可能暫時無法接受請求,比如Pod已經Running了,但是容器内應用程式尚未啟動成功,在這種情況下,如果沒有ReadinessProbe,則Kubernetes認為它可以處理請求了,然而此時,我們知道程式還沒啟動成功是不能接收使用者請求的,是以不希望kubernetes把請求排程給它,則使用ReadinessProbe探針。
ReadinessProbe和livenessProbe可以使用相同探測方式,隻是對Pod的處置方式不同,ReadinessProbe是将Pod IP:Port從對應的EndPoint清單中删除,而livenessProbe則Kill容器并根據Pod的重新開機政策來決定作出對應的措施。
ReadinessProbe探針探測容器是否已準備就緒,如果未準備就緒則kubernetes不會将流量轉發給此Pod。
ReadinessProbe探針與livenessProbe一樣也支援exec、httpGet、TCP的探測方式,配置方式相同,隻不過是将livenessProbe字段修改為ReadinessProbe。
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
ReadinessProbe探針的HTTP、TCP的探測方式也與livenessProbe的基本一緻。
示例四: ReadinessProbe示例
現在來看一個加入ReadinessProbe探針和一個沒有ReadinessProbe探針的示例:
該示例中,建立了一個deploy,名為JavaApp,啟動的容器運作一個java應用程式,程式監聽端口為8080。
# 沒有加入ReadinessProbe
cat JavaApp.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: JavaApp
namespace: default
spec:
replicas: 1
selector:
matchLabels:
test: JavaApp
template:
metadata:
labels:
test: JavaApp
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: env
operator: In
values:
- testing
containers:
- image: 192.168.1.183:8081/jar/JavaApp:29
name: JavaApp
ports:
- containerPort: 8080
imagePullSecrets:
- name: myregistrykey
---
kind: Service
apiVersion: v1
metadata:
name: JavaApp
namespace: default
spec:
selector:
test: JavaApp
ports:
- protocol: TCP
port: 8080
建立:
kubectl create -f JavaApp.yaml
剛建立後,等幾秒鐘後,檢視Pod狀态:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
JavaApp-579b45567c-tdsrk 1/1 Running 0 6s
從上面可以看到,Pod剛啟動6s,自身狀态已Running,其READ字段,1/1 表示1個容器狀态已準備就緒了,此時,對于kubernetes而言,它已經可以接收請求了,而實際上服務還無法通路,因為JAVA程式還尚啟動起來,本人實驗中的JAVA程式啟動時間大概需要50s,50s後方可正常通路,是以針對此類程式,必須配置ReadinessProbe。
#加入readinessProbe
kind: Deployment
apiVersion: apps/v1
metadata:
name: JavaApp
namespace: default
spec:
replicas: 1
selector:
matchLabels:
test: JavaApp
template:
metadata:
labels:
test: JavaApp
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: env
operator: In
values:
- testing
containers:
- image: 192.168.1.183:8081/jar/JavaApp:29
name: JavaApp
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
imagePullSecrets:
- name: myregistrykey
---
kind: Service
apiVersion: v1
metadata:
name: JavaApp
namespace: default
spec:
selector:
test: JavaApp
ports:
- protocol: TCP
port: 8080
在該配置檔案中,ReadinessProbe探針的探測方式為tcpSocket,因為程式監聽在8080端口,是以這裡探測為對8080建立連接配接。程式啟動時間大概50多秒,是以這裡第一次探測時間是在Pod Runing後60秒後,間隔10秒後執行第二次探測。
建立Pod:
kubectl create -f JavaApp.yaml
建立後等50秒檢視狀态:
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
JavaApp-64b58cfd5c-kpc4s 0/1 Running 0 55s 172.26.91.109 192.168.1.180
Pod雖然已處于Runnig狀态,但是由于第一次探測時間未到,是以READY字段為0/1,即容器的狀态為未準備就緒,在未準備就緒的情況下,其Pod對應的Service下的Endpoint也為空,是以才不會有任何請求被排程進來。
$ kubectl get endpoints
NAME ENDPOINTS
JavaApp
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
JavaApp-64b58cfd5c-qj886 1/ Running 0 1m
$ kubectl get endpoints
NAME ENDPOINTS AGE
JavaApp 172.26.91.113:8080 1m
配置探針(Probe)相關屬性
- initialDelaySeconds:Pod啟動後延遲多久才進行檢查,機關:秒。
- periodSeconds:檢查的間隔時間,預設為10,機關:秒。
- timeoutSeconds:探測的逾時時間,預設為1,機關:秒。
- successThreshold:探測失敗後認為成功的最小連接配接成功次數,預設為1,在Liveness探針中必須為1,最小值為1。
- failureThreshold:探測失敗的重試次數,重試一定次數後将認為失敗,在readiness探針中,Pod會被标記為未就緒,預設為3,最小值為1。