天天看點

【K8S】健康檢查初探

健康檢查的意義

為了保證叢集的穩定性,需要及時的對故障服務進行處理。無論是通過下線的方式,不再将請求轉發到該服務,還是通過重新開機的方式,使服務得以自動恢複,都是有效的處理方式。

健康檢查的政策

而如何檢測出服務是否有故障,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。