本文主要分析了針對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-storage
和limits.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 -m
、df -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.
- The
nodefs
filesystem that kubelet uses for volumes, daemon logs, etc.- 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