Kubernetes驅逐事件分析


本文主要分析了針對podcustom-metrics-apiserver的驅逐事件,分析相關成因並給出解決措施。

問題

在學習HPA自動伸縮時,部署完custom-metrics-apiserver這個的Deployment后資源后經過一段時間總能觀察到大量的驅逐事件:

$ kubectl get pods -n custom-metrics 
NAME                                       READY   STATUS    RESTARTS   AGE
custom-metrics-apiserver-f884776d4-9x9br   0/1     Evicted   0          6h34m
custom-metrics-apiserver-f884776d4-cztcr   0/1     Evicted   0          8h
custom-metrics-apiserver-f884776d4-fpr25   0/1     Evicted   0          4h46m
custom-metrics-apiserver-f884776d4-lcvtt   1/1     Running   0          33m
custom-metrics-apiserver-f884776d4-pb58n   0/1     Evicted   0          5h28m
custom-metrics-apiserver-f884776d4-plzr5   0/1     Evicted   0          99m
custom-metrics-apiserver-f884776d4-q7fpz   0/1     Evicted   0          10h
custom-metrics-apiserver-f884776d4-tllrm   0/1     Evicted   0          154m
custom-metrics-apiserver-f884776d4-wqnw9   0/1     Evicted   0          7h41m
custom-metrics-apiserver-f884776d4-xh9wz   0/1     Evicted   0          8h
custom-metrics-apiserver-f884776d4-xrs44   0/1     Evicted   0          3h40m

一開始並不了解是什么情況也是第一次見到STATUS=Evicted的狀態,只是簡單的刪除被驅逐的podkubectl delete pods --all -n custom-metrics。但是經過一段時間后又會出現同樣的情況,本着發生驅逐就意味着節點資源緊缺的判斷,有必要分析下驅逐的原因並且梳理下避免驅逐的措施。

分析

從kubelet日志可以發現,是由於ephemeral-storage不足而引發的驅逐。那么這個ephemeral-storage是什么呢?帶着問題去[kubernetes官網](Managing Resources for Containers | Kubernetes)搜索,發現這是類似於cpu、內存的一種資源,暫時翻譯為臨時性存儲

node-1 kubelet[7082]: W 7082 eviction_manager.go:330] eviction manager: attempting to reclaim ephemeral-storage
node-1 kubelet[7082]: I 7082 container_gc.go:85] attempting to delete unused containers
node-1 kubelet[7082]: I 7082 image_gc_manager.go:317] attempting to delete unused images
node-1 kubelet[7082]: I 7082 eviction_manager.go:341] eviction manager: must evict pod(s) to reclaim ephemeral-storage
node-1 kubelet[7082]: I 7082 eviction_manager.go:359] eviction manager: pods ranked for eviction: custom-metrics-apiserver-747fd5d6f6-9dg8t_custom-metrics(e8ea2efe-7e5f-4098-a9a6-359f7e79cdef), node-exporter-khwxn_monitoring(5f8ffd48-e9fe-4273-9937-1e4e6788e437), nginx-proxy-node-1_kube-system(1e396843ff781cfa662c1e9f2423a988), rbd-provisioner-bc84fd48f-nckhq_kube-system(ec731329-782f-4768-b989-b07f04724c0f), ceph-pod_default(bd698243-bea0-4175-993c-00de365a9d19), metrics-server-cc9d976bf-h8jqh_kube-system(d06ea89f-f8ea-474d-9864-82d336621a8b), nodelocaldns-8d9ml_kube-system(ccfcf424-3f70-4a3e-8525-f0fddebc301f), calico-node-kkfsx_kube-system(386b8857-3354-43e9-827d-b7245a4a6e81), kube-proxy-vcw5x_kube-system(a7db076a-a9ff-41b6-9bf5-0f190b99abc9)

node-1 kubelet[7082]: I 7082 kubelet_node_status.go:486] Recording NodeHasDiskPressure event message for node node-1
node-1 kubelet[7082]: I 7082 kubelet_pods.go:1102] Killing unwanted pod "custom-metrics-apiserver-747fd5d6f6-9dg8t"
node-1 kubelet[7082]: I 7082 eviction_manager.go:566] eviction manager: pod custom-metrics-apiserver-747fd5d6f6-9dg8t_custom-metrics(e8ea2efe-7e5f-4098-a9a6-359f7e79cdef) is evicted successfully
#清理殘留數據過程...
node-1 kubelet[7082]: I 7082 eviction_manager.go:403] eviction manager: pods custom-metrics-apiserver-747fd5d6f6-9dg8t_custom-metrics(e8ea2efe-7e5f-4098-a9a6-359f7e79cdef) successfully cleaned up

日志中提到了需要回收ephemeral-storage,並且嘗試刪除未使用的容器及鏡像無果,發現必須要驅逐pod才能完成對於ephemeral-storage的回收操作。然后對所有運行中的pod進行打分,custom-metrics-apiserver-747fd5d6f6-9dg8t_custom-metrics的分數最高從而被驅逐。在此之間kubelet向apiserver報告了NodeHasDiskPressure的事件,並且kubelet在驅逐完pod后對節點資源進行回收等操作。

關於驅逐的誤區

一開始我以為驅逐是把k8s把資源緊張的節點上的某個pod移動到另外的節點上,但后來發現所謂的驅逐僅僅是終止pod。在別的節點拉起的動作是由Deployment、ReplicaSet等高層控制器完成的。

  • 定義測試pod
$ cat test-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod
spec:
  hostNetwork: true
  containers:
  - name: nginx
    image: reg.kolla.org/library/nginx:1.18
    imagePullPolicy: IfNotPresent
    ports:
    - name: test-pod
      containerPort: 80
      
$ kubectl get pods 
NAME     READY   STATUS    RESTARTS   AGE
pod      1/1     Running   0          118s
  • 定義驅逐文件
$ cat eviction.json 
{
  "apiVersion": "policy/v1beta1",
  "kind": "Eviction",
  "metadata": {
    "name": "pod",
    "namespace": "default"
  }
}
  • 測試驅逐
$ kubectl proxy
Starting to serve on 127.0.0.1:8001

#復制終端后執行
$ curl -X POST -d @eviction.json -H 'Content-type: application/json' http://127.0.0.1:8001/api/v1/namespaces/default/pods/pod/eviction
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Success",
  "code": 201
}
  • 查看結果
$ kubectl get pods 
No resources found in default namespace.

從結果來看,由於pod資源上層沒有更高級的副本控制器,在pod被驅逐后pod就無法運行了。所以驅逐僅是完成對pod的終止操作。

臨時存儲的問題

我們知道volume中的emptyDir就是一種臨時存儲,這種存儲不能用來存放持久化數據。當pod被刪除后,emptyDir中的數據就會被清空,所以emptyDir這種類型的volume只是用來存儲pod運行過程中產生的臨時數據,或者是pod內多個container共享臨時數據的一種方式。那么是否有可能是emptyDir的問題呢?查看配置發現,配置中的確有emptyDir類型的卷:

$ cat custom-metrics-apiserver-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: custom-metrics-apiserver
  name: custom-metrics-apiserver
  namespace: custom-metrics
spec:
  replicas: 1
  selector:
    matchLabels:
      app: custom-metrics-apiserver
  template:
    metadata:
      labels:
        app: custom-metrics-apiserver
      name: custom-metrics-apiserver
    spec:
      serviceAccountName: custom-metrics-apiserver
      containers:
      - name: custom-metrics-apiserver
        image: reg.kolla.org/prometheus/k8s-prometheus-adapter-amd64 
        args:
        - --secure-port=6443
        - --tls-cert-file=/var/run/serving-cert/serving.crt
        - --tls-private-key-file=/var/run/serving-cert/serving.key
        - --logtostderr=true
        - --prometheus-url=http://prometheus-k8s.monitoring.svc:9090/
        - --metrics-relist-interval=1m
        - --v=10
        - --config=/etc/adapter/config.yaml
        ports:
        - containerPort: 6443
        volumeMounts:
        - mountPath: /var/run/serving-cert
          name: volume-serving-cert
          readOnly: true
        - mountPath: /etc/adapter/
          name: config
          readOnly: true
        - mountPath: /tmp
          name: tmp-vol
      volumes:
      - name: volume-serving-cert
        secret:
          secretName: cm-adapter-serving-certs
      - name: config
        configMap:
          name: adapter-config
      - name: tmp-vol
        emptyDir: {}

但是仔細分析配置文件發現這個emptyDir卷的作用不大可以忽略,嘗試注釋這個名為tmp-vol的emptyDIr以及上面的掛載配置之后重新應用后發現過一段時間后仍然會出現大量的Eviction,那就證明與配置中定義的emptyDir沒有關系。

資源配額的問題

思前想后,翻閱有關驅逐的定義:驅逐是指當某個節點的資源緊張時。既然是資源緊張,那么我設置一個ResourceQuota試試看:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
  namespace: custom-metrics
spec:
  hard:
    pods: "2" 
    requests.cpu: "1" 
    requests.memory: 512Mi 
    requests.ephemeral-storage: 4Gi 
    limits.cpu: "2" 
    limits.memory: 1Gi 
    limits.ephemeral-storage: 8Gi 

由於日志中是ephemeral-storage資源緊張,那么這段配置實際的上也是看requests.ephemeral-storagelimits.ephemeral-storage兩個配置。這樣設置后就能保證custom-metrics名稱空間下只能申請最大8Gi的ephemeral-storage,由於該名稱空間下只有這一個pod,所以也是限制了這個pod的ephemeral-storage是8Gi。由於設置ResourceQuota必須要在Deployment的配置中添加資源配額,所以在deployment中增加資源配額的定義:

...
   resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
            ephemeral-storage: "4Gi"
          limits:
            memory: "512Mi"
            cpu: "1"
            ephemeral-storage: "8Gi"
...

刪除並重新應用這個deployment之后發現,不僅沒有解決,驅逐的數量反而大大增加

仔細想想其實很快就能理解:原本沒有限制ResourceQuota時,這個ephemeral-storage肯定是大於8Gi的。加了限制后肯定很快就達到了限值從而被驅逐。

轉機

走投無路之后,本着資源緊張才會發生驅逐的想法。在node-1節點部署簡單的監控腳本,每分鍾輸出一次free -mdf -h /以及du -sh /var/lib/docker/,在pod發生驅逐后查看日志果然發現了問題:

------------------------------------------
2021-01-05 22:59:01
              total        used        free      shared  buff/cache   available
Mem:           7983        1037        2565         283        4380        6186
Swap:             0           0           0

Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   44G  8.7G   36G  20% /

6.4G	/var/lib/docker
------------------------------------------

...
------------------------------------------
2021-01-05 23:22:01
              total        used        free      shared  buff/cache   available
Mem:           7983        1019        1544         283        5419        6243
Swap:             0           0           0

Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   44G   13G   32G  29% /

11G	/var/lib/docker
------------------------------------------

------------------------------------------
2021-01-06 00:52:01
              total        used        free      shared  buff/cache   available
Mem:           7983         939        1258         283        5785        6324
Swap:             0           0           0

Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   44G   18G   27G  40% /

15G	/var/lib/docker
------------------------------------------

------------------------------------------
2021-01-06 06:43:01
              total        used        free      shared  buff/cache   available
Mem:           7983        1023         626         291        6332        6231
Swap:             0           0           0

Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   44G   38G  6.8G  85% /

35G	/var/lib/docker
------------------------------------------

------------------------------------------
2021-01-06 07:16:01
              total        used        free      shared  buff/cache   available
Mem:           7983         853        6159         291         970        6423
Swap:             0           0           0

Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   44G  4.6G   40G  11% /

2.2G	/var/lib/docker
------------------------------------------

根分區磁盤使用率達到85%后開始回落到11%,查閱了官網中有關於硬驅逐的定義中發現,當imagefs.available<15%時就會觸發硬驅逐,正好與85%對應:

The kubelet has the following default hard eviction threshold:

  • memory.available<100Mi
  • nodefs.available<10%
  • nodefs.inodesFree<5%
  • imagefs.available<15%

關於nodefs和imagefs的區別:官網中的說法是這樣:

kubelet supports only two filesystem partitions.

  1. The nodefs filesystem that kubelet uses for volumes, daemon logs, etc.
  2. The imagefs filesystem that container runtimes uses for storing images and container writable layers.

翻譯過來就是:

nodefs是kubelet用來存儲卷及日志的文件系統,而imagefs是容器運行時用來存儲鏡像及容器可寫層的文件系統。我的理解是imagefs實際上也和nodefs一樣使用系統的磁盤空間,干脆直接理解為磁盤的使用率,僅僅是用途不一致。回到監控的輸出可以發現/var/lib/docker/所占用的磁盤空間越來越大,想了想,什么東西會在容器運行的時候越來越大?只可能是日志了。回到配置文件看到容器的參數- --v=10一下子恍然大悟:容器日志級別太低導致瘋狂輸出日志,大量占用了/var/lib/docker目錄的空間,逐步積累到磁盤85%的使用率引發kubelet驅逐。被驅逐后由於Deployment的作用在別的節點拉起相同的pod,如此反復造成大量的驅逐事件。

解決

聯系上文的分析,斷定是日志的問題,解決方法也很簡單:1、修改日志的級別 2、docker中配置日志相關參數

  • 將日志的級別修改為1后重新應用,運行一天后不再觀察到驅逐事件:
$ kubectl get pods -n custom-metrics                   
NAME                                        READY   STATUS    RESTARTS   AGE
custom-metrics-apiserver-6594b9d597-t84q2   1/1     Running   0          28h

雖然調整日志級別的方法很管用,但是當容器長期運行時隨着日志量的逐步累積仍然可能被驅逐。並且日志級別太高不能很好的分析問題。

  • docker啟動參數中添加最大日志及日志數量的限制:
$ cat >> /etc/docker/daemon.json <<EOF
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "500m",
    "max-file": "3"
  }
}

$ systemctl daemon-reload
$ systemctl restart docker

限制最多有3個日志並且每個日志最大500M。長期運行后未發現驅逐事件。其實在部署k8s集群就應該將日志的限制加到docker的配置項中,但我這個是測試環境懶得加,直到出現這個問題才發現是這個原因,生產環境應添加日志配置的參數。

限制前單個日志不斷增大造成磁盤緊張引發驅逐:

$ ls -lh
total 1.0G
-rw-r-----. 1 root root 610M Jan  9 17:01 a9d63a1a2c48765ac9a962d269b5ab59aae2617f6954280826dbdc8cfd22fcd0-json.log
drwx------. 2 root root    6 Jan  9 16:50 checkpoints
-rw-------. 1 root root 7.2K Jan  9 16:50 config.v2.json
-rw-r--r--. 1 root root 2.4K Jan  9 16:50 hostconfig.json
drwx------. 2 root root    6 Jan  9 16:50 mounts

限制后日志不斷回滾,最大不超過1.5G:

$ ls -lh
total 970M
-rw-r-----. 1 root root  15M Jan  9 17:02 86acee999a12760622fc7983944ecfe44d8e944b5cdd5905d9d999ff21f9aa4e-json.log
-rw-r-----. 1 root root 477M Jan  9 17:01 86acee999a12760622fc7983944ecfe44d8e944b5cdd5905d9d999ff21f9aa4e-json.log.1
-rw-r-----. 1 root root 477M Jan  9 16:53 86acee999a12760622fc7983944ecfe44d8e944b5cdd5905d9d999ff21f9aa4e-json.log.2
drwx------. 2 root root    6 Jan  9 16:10 checkpoints
-rw-------. 1 root root 7.2K Jan  9 16:10 config.v2.json
-rw-r--r--. 1 root root 2.4K Jan  9 16:10 hostconfig.json
drwx------. 2 root root    6 Jan  9 16:10 mounts


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM