K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆問(wèn)題徹底解決方法
存儲(chǔ)的內(nèi)容
現(xiàn)在云原生越來(lái)越流行,很多企業(yè)都上馬了K8S,但是這里邊也有很多的坑要填,這篇文章就聊一下K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆的問(wèn)題,也就是磁盤(pán)被占滿(mǎn)的問(wèn)題。
要解決存儲(chǔ)使用過(guò)多的問(wèn)題,就得先了解存儲(chǔ)中都保存了些什么內(nèi)容,否則解決不了問(wèn)題,還可能帶來(lái)更多的風(fēng)險(xiǎn)。
鏡像
容器要在節(jié)點(diǎn)上運(yùn)行,kubelet 首先要拉取容器鏡像到節(jié)點(diǎn)本地,然后再根據(jù)鏡像創(chuàng)建容器。隨著Pod的調(diào)度和程序的升級(jí),日積月累,節(jié)點(diǎn)本地就會(huì)保存大量的容器鏡像,占用大量存儲(chǔ)空間。
如果使用的是Docker容器運(yùn)行時(shí),這些文件保存在 /var/lib/docker/image/overlay2 目錄下。
可寫(xiě)層
關(guān)于可寫(xiě)層,了解容器本質(zhì)的同學(xué)應(yīng)該比較熟悉,容器運(yùn)行時(shí)使用的是一種聯(lián)合文件系統(tǒng)技術(shù),它把鏡像中的多層合并起來(lái),然后再增加一個(gè)可寫(xiě)層,容器中寫(xiě)操作的結(jié)果會(huì)保存在這一層,這一層存在于容器當(dāng)前節(jié)點(diǎn)的本地存儲(chǔ)中。雖然鏡像中的層是容器實(shí)例共享的,但是可寫(xiě)層是每個(gè)容器一份。
假如我們有一個(gè)名為 mypod 的Pod實(shí)例,在其中創(chuàng)建一個(gè)文件:/hello.txt,并寫(xiě)入 hello k8s 的字符。
$ kubectl exec mypod -- sh -c 'echo "hello k8s" > /hello.txt' $ kubectl exec mypod -- cat /k8s/hello.txt hello k8s
如果使用的是Docker容器運(yùn)行時(shí),可以在Docker的相關(guān)目錄中找到可寫(xiě)層以及剛剛創(chuàng)建的這個(gè)文件,它們?cè)?nbsp; /var/lib/docker/overlay2 這個(gè)目錄下。
如果毫無(wú)節(jié)制的使用可寫(xiě)層,也會(huì)導(dǎo)致大量的本地磁盤(pán)空間被占用。
日志
K8S推薦的日志輸出方式是將程序日志直接輸出到標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,此時(shí)容器運(yùn)行時(shí)會(huì)捕捉這些數(shù)據(jù),并把它們寫(xiě)到本地存儲(chǔ),然后再由節(jié)點(diǎn)上的日志代理或者Pod中的邊車(chē)日志代理轉(zhuǎn)運(yùn)到獨(dú)立的日志處理中心,以供后續(xù)分析使用。
這些日志保存在節(jié)點(diǎn)本地的 /var/log/container 目錄下,我們可以實(shí)際創(chuàng)建一個(gè)Pod來(lái)確認(rèn)下:
apiVersion: v1
kind: Pod
metadata:
name: pod-log-stdout
spec:
containers:
- name: count
image: busybox:latest
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date) a log entry."; i=$((i+1)); usleep 1000; done']
這個(gè)Pod每隔1毫秒會(huì)寫(xiě)1條數(shù)據(jù)到標(biāo)準(zhǔn)輸出。要找到容器運(yùn)行時(shí)根據(jù)標(biāo)準(zhǔn)輸出創(chuàng)建的日志文件,首先要找到這個(gè)Pod部署的節(jié)點(diǎn),然后登錄到這個(gè)節(jié)點(diǎn),就能找到對(duì)應(yīng)的文件了。

如果程序輸出的日志很多,占滿(mǎn)磁盤(pán)空間就是早晚的事。
emptyDir
emptyDir 是一種基于節(jié)點(diǎn)本地存儲(chǔ)的Volume類(lèi)型,它通過(guò)在本地存儲(chǔ)創(chuàng)建一個(gè)空目錄來(lái)實(shí)際承載Volume。使用這種存儲(chǔ)卷可以在Pod的多個(gè)容器之間共享數(shù)據(jù),比如一個(gè)容器造數(shù)據(jù),一個(gè)容器消費(fèi)數(shù)據(jù)。
看下面這個(gè)例子:
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-empty-dir
spec:
containers:
- name: count
image: busybox:latest
args: [/bin/sh, -c, 'echo "k8s" > /cache/k8s.txt;sleep 1800']
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
在 spec.volumes[] 中只需要添加一個(gè)名為 emptyDir 的字段,它的配置都可以使用默認(rèn)值,然后這個(gè)卷會(huì)被掛載到容器的 /cache 路徑。
容器的啟動(dòng)參數(shù)是一個(gè)shell命令,它會(huì)在容器的 cache 目錄下創(chuàng)建1個(gè)名為 k8s.txt 的文件。容器創(chuàng)建后稍等一會(huì),使用下面的命令獲取這個(gè)文件的內(nèi)容:
$ kubectl exec pod-vol-empty-dir -- cat /cache/k8s.txt k8s
可以看到,文件內(nèi)容正是容器啟動(dòng)命令中寫(xiě)入的 k8s 字符。
K8S會(huì)在當(dāng)前的Node自動(dòng)創(chuàng)建一個(gè)目錄來(lái)實(shí)際承載這個(gè)卷,目錄的位置在Node的 /var/lib/kubelet/pods 路徑下。要查看這個(gè)目錄中的內(nèi)容,需要先找到Pod Id和對(duì)應(yīng)的Node,然后登錄到這個(gè)Node,就能找到這個(gè)目錄了。minikube中的查找方法如下圖所示:

注意用顏色框圈出來(lái)的內(nèi)容,不同的Pod對(duì)應(yīng)的數(shù)據(jù)不同。查找Pod Id的命令:
kubectl get pods -o custom-columns=PodName:.metadata.name,PodUID:.metadata.uid,PodNode:.spec.nodeName
如果不對(duì) emptyDir Volume 做一些限制,也是有很大的風(fēng)險(xiǎn)會(huì)使用過(guò)多的磁盤(pán)空間。
存儲(chǔ)的限制方法
通過(guò)上文的介紹,我們可以看到,除了容器鏡像是系統(tǒng)機(jī)制控制的,其它的內(nèi)容都跟應(yīng)用程序有關(guān)。
應(yīng)用程序完全可以控制自己使用的存儲(chǔ)空間,比如少寫(xiě)點(diǎn)日志,將數(shù)據(jù)保存到遠(yuǎn)程存儲(chǔ),及時(shí)刪除使用完畢的臨時(shí)數(shù)據(jù),使用LRU等算法控制存儲(chǔ)空間的使用量,等等。不過(guò)完全依賴(lài)開(kāi)發(fā)者的自覺(jué)也不是一件很可靠的事,萬(wàn)一有BUG呢?所以K8S也提供了一些機(jī)制來(lái)限制容器可以使用的存儲(chǔ)空間。
K8S的GC
K8S有一套自己的GC控制邏輯,它可以清除不再使用的鏡像和容器。這里我們重點(diǎn)看下對(duì)鏡像的清理。
這個(gè)清理工作是 kubelet 執(zhí)行的,它有三個(gè)參數(shù)來(lái)控制如何執(zhí)行清理:
- imageMinimumGCAge 未使用鏡像進(jìn)行垃圾回收時(shí),其存在的時(shí)間要大于這個(gè)閾值,默認(rèn)是2分鐘。
- imageGCHighThresholdPercent 鏡像占用的磁盤(pán)空間比例超過(guò)這個(gè)閾值時(shí),啟動(dòng)垃圾回收。默認(rèn)85。
- ImageGCLowThresholdPercent 鏡像占用的磁盤(pán)空間比例低于這個(gè)閾值時(shí),停止垃圾回收。默認(rèn)80。
可以根據(jù)自己的鏡像大小和數(shù)量的水平來(lái)更改這幾個(gè)閾值。
日志總量限制
K8S對(duì)寫(xiě)入標(biāo)準(zhǔn)輸出的日志有一個(gè)輪轉(zhuǎn)機(jī)制,默認(rèn)情況下每個(gè)容器的日志文件最多可以有5個(gè),每個(gè)文件最大允許10Mi,如此每個(gè)容器最多保留最新的50Mi日志,再加上Node也可以對(duì)Pod數(shù)量進(jìn)行限制,日志使用的本地存儲(chǔ)空間就變得可控了。這個(gè)控制也是 kubelet 來(lái)執(zhí)行的,有兩個(gè)參數(shù):
- containerLogMaxSize 單個(gè)日志文件的最大尺寸,默認(rèn)為10Mi。
- containerLogMaxFiles 每個(gè)容器的日志文件上限,默認(rèn)為5。
以上文的 pod-log-stdout 這個(gè)Pod為例,它的日志輸出量很多就會(huì)超過(guò)10Mi,我們可以實(shí)際驗(yàn)證下。
不過(guò)如果沒(méi)有意外,意外將要發(fā)生了,K8S的限制不起作用。這是因?yàn)槲覀兪褂玫娜萜鬟\(yùn)行時(shí)是docker,docker有自己的日志處理方式,這套機(jī)制可能過(guò)于封閉,K8S無(wú)法適配或者不愿意適配。可以更改docker deamon的配置來(lái)解決這個(gè)問(wèn)題,在K8S Node中編輯這個(gè)文件 /etc/docker/daemon.json (如果沒(méi)有則新建),增加關(guān)于日志的配置:
{
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
然后重啟Node上的docker:systemctl restart docker。注意還需要重新創(chuàng)建這個(gè)Pod,因?yàn)檫@個(gè)配置只對(duì)新的容器生效。
在docker運(yùn)行時(shí)下,容器日志實(shí)際上位于 /var/lib/docker/containers 中,先找到容器Id,然后就可以觀察到這些日志的變化了:

emptyDir Volume 限制
對(duì)于emptyDir類(lèi)型的卷,可以設(shè)置 emptyDir.sizeLimit,比如設(shè)置為 100Mi。
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-empty-dir-limit
spec:
containers:
- name: count
image: busybox:latest
args: [/bin/sh, -c,
'while true; do dd if=/dev/zero of=/cache/$(date "+%s").out count=1 bs=5MB; sleep 1; done']
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
sizeLimit: 100Mi
稍等幾分鐘,然后查詢(xún)Pod的事件:

可以看到 kubelet 發(fā)現(xiàn) emptyDir volume 超出了100Mi的限制,然后就把 Pod 關(guān)掉了。
臨時(shí)數(shù)據(jù)的總量限制
對(duì)于所有類(lèi)型的臨時(shí)性本地?cái)?shù)據(jù),包括 emptyDir 卷、容器可寫(xiě)層、容器鏡像、日志等,K8S也提供了一個(gè)統(tǒng)一的存儲(chǔ)請(qǐng)求和限制的設(shè)置,如果使用的存儲(chǔ)空間超過(guò)限制就會(huì)將Pod從當(dāng)前Node逐出,從而避免磁盤(pán)空間使用過(guò)多。
然后我們創(chuàng)建一個(gè)Pod,它會(huì)每秒寫(xiě)1個(gè)5M的文件,同時(shí)使用 spec.containers[].resources.requests.limits 給存儲(chǔ)資源設(shè)置了一個(gè)限制,最大100Mi。
apiVersion: v1
kind: Pod
metadata:
name: pod-ephemeral-storage-limit
spec:
containers:
- name: count
image: busybox:latest
args: [/bin/sh, -c,
'while true; do dd if=/dev/zero of=$(date "+%s").out count=1 bs=5MB; sleep 1; done']
resources:
requests:
ephemeral-storage: "50Mi"
limits:
ephemeral-storage: "100Mi"
稍等幾分鐘,然后查詢(xún)Pod的事件:
kubectl describe pod pod-ephemeral-storage-limit

可以看到 kubelet 發(fā)現(xiàn)Pod使用的本地臨時(shí)存儲(chǔ)空間超過(guò)了限制的100Mi,然后就把 Pod 關(guān)掉了。
通過(guò)這些存儲(chǔ)限制,基本上就可以說(shuō)是萬(wàn)無(wú)一失了。當(dāng)然還要在節(jié)點(diǎn)預(yù)留足夠的本地存儲(chǔ)空間,可以根據(jù)Pod的數(shù)量和每個(gè)Pod最大可使用的空間進(jìn)行計(jì)算,否則程序也會(huì)因?yàn)榭偸堑貌坏剿璧拇鎯?chǔ)空間而出現(xiàn)無(wú)法正常運(yùn)行的問(wèn)題。
以上就是K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆問(wèn)題徹底解決方法的詳細(xì)內(nèi)容,更多關(guān)于K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rainbond網(wǎng)絡(luò)治理插件ServiceMesh官方文檔說(shuō)明
這篇文章主要為大家介紹了Rainbond網(wǎng)絡(luò)治理插件ServiceMesh官方文檔說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
Rainbond對(duì)微服務(wù)進(jìn)行請(qǐng)求速率限制詳解
這篇文章主要為大家介紹了Rainbond對(duì)微服務(wù)進(jìn)行請(qǐng)求速率限制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
Rancher部署配置開(kāi)源Rainbond云原生應(yīng)用管理平臺(tái)
這篇文章主要為大家介紹了Rancher部署配置開(kāi)源Rainbond云原生應(yīng)用管理平臺(tái),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
k8s?service?nodePort無(wú)法訪(fǎng)問(wèn)的問(wèn)題解決
今天有一個(gè)項(xiàng)目做service nodeport轉(zhuǎn)發(fā),結(jié)果設(shè)置完之后發(fā)現(xiàn)外網(wǎng)訪(fǎng)問(wèn)失敗,下面這篇文章主要給大家介紹了關(guān)于k8s?service?nodePort無(wú)法訪(fǎng)問(wèn)的問(wèn)題解決,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆問(wèn)題徹底解決方法
這篇文章主要為大家介紹了K8S節(jié)點(diǎn)本地存儲(chǔ)被撐爆問(wèn)題徹底解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
云原生技術(shù)kubernetes(K8S)簡(jiǎn)介
這篇文章主要介紹了云原生技術(shù)kubernetes的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用K8S,感興趣的朋友可以了解下2021-03-03
帶你學(xué)會(huì)k8s?更高級(jí)的對(duì)象Deployment
這篇文章主要為大家介紹了k8s還有更高級(jí)的"對(duì)象"Deployment使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Kubernetes教程之Windows?HostProcess?運(yùn)行容器化負(fù)載
這篇文章主要介紹了Kubernetes?Windows?HostProcess?運(yùn)行容器化負(fù)載,本篇內(nèi)容還是比較多的,總共包含了?Windows?HostProcess的創(chuàng)建、為?Windows?Pod?和容器配置?GMSA?和?Windows?的?Pod?和容器配置?RunAsUserName三大功能模塊,需要的朋友可以參考下2022-07-07
Kubernetes故障排除有效維護(hù)集群的最佳實(shí)踐工具
這篇文章主要為大家介紹了Kubernetes故障排除有效維護(hù)集群的最佳實(shí)踐工具詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

