背景
項目使用敏捷開發(fā)抛虏,jenkins負責持續(xù)集成博其,構(gòu)建新鏡像push到harbor套才,最后k8s節(jié)點從harbor倉庫拉取新鏡像更新集群迂猴。
暴露的問題
jenkins
githup上的項目代碼一更新,觸發(fā)webhook,jenkins開始構(gòu)建項目的新鏡像,長此以往背伴,jenkins節(jié)點本地無用的鏡像會越來越多沸毁,占用磁盤空間也會越來越大峰髓。
harbor
jenkins構(gòu)建的新鏡像會push到harbor倉庫,長此以往息尺,harbor節(jié)點本地無用的鏡像就會越來越多携兵,占用的磁盤空間也就越來越大延赌。
k8s
- 每次更新k8s徊都,將要從harbor倉庫pull新的鏡像到worker節(jié)點本地,隨著頻繁更新宽档,worker節(jié)點的無用鏡像也會越來越多
- k8s運行著多個docker炭懊,docker會產(chǎn)生日志并级,日志也是占用磁盤主要渠道。docker日志分為倆種侮腹,一種為容器的本身的標準輸出日志嘲碧,另一種是容器內(nèi)部卷形式的文件日志,隨著docker的運行父阻,日志也會慢慢變大愈涩,占用磁盤空間的很大部分。
解決方案
jenkins
我們知道jenkins屬于pipeline,我們的目的是想刪除jenkins節(jié)點的無用鏡像加矛,我們只要在pipeline里加一段stage即可履婉。
harbor
harbor就更簡單了,有tag保留策略斟览。
k8s
偶爾看到這篇文章記一次k8s集群節(jié)點鏡像存儲容量報警問題谐鼎,很有感觸,其實也是每個人應(yīng)該思考的趣惠。
自從我們的kubernetes集群部署到生產(chǎn)環(huán)境后狸棍,將流量從原有的服務(wù)器上切過來之后,部分節(jié)點出現(xiàn)掛載目錄容量爆滿的情況味悄。
運維的同事報給我們之后草戈,我們首先想到的是節(jié)點鏡像過多,于是我們提供一個命令用于清理當前節(jié)點上無用的侍瑟、報錯的唐片、鏡像和docker資源文件
docker system prune 命令可以用于清理磁盤,刪除關(guān)閉的容器涨颜、無用的數(shù)據(jù)卷和網(wǎng)絡(luò)费韭,以及dangling鏡像(即無tag的鏡像)
docker system prune -a 命令清理得更加徹底,可以將沒有容器使用Docker鏡像都刪掉庭瑰。
待運維執(zhí)行之后星持,目錄存儲資源釋放了一些,我們本以為這就告一段落了弹灭。然而督暂,事與愿違揪垄,沒過多久,再次容量報警逻翁。饥努。。
我們開始重視起來八回,開始檢視節(jié)點上工作的容器酷愧,發(fā)現(xiàn)在日志爆炸的節(jié)點上運行了定時任務(wù),開發(fā)人員將定時任務(wù)的日志輸出到控制臺缠诅,于是我們回到節(jié)點docker的工作目錄伟墙,通過du -sh *方式查看每個文件夾大小,發(fā)現(xiàn)docker目錄下containers目錄占用空間巨大滴铅,進去看原來是每個運行的容器存放日志的目錄戳葵,我們找出占用空間最大的日志目錄,發(fā)現(xiàn)容器日志特別的大
我們可使用如下命令查看各個日志的文件大小
ls -lh $(find /var/lib/docker/containers/ -name *-json.log)
那我們?nèi)绾吻謇砣罩灸睾撼祝绻鹍ocker容器正在運行拱烁,那么使用rm -rf 方式刪除日志后,通過df -h會發(fā)現(xiàn)磁盤空間并沒有釋放
原因:在Linux或者Unix系統(tǒng)中噩翠,通過rm或者文件管理器刪除文件將會從文件系統(tǒng)的目錄結(jié)構(gòu)上解除鏈接(unlink).然而如果文件是被打開的(有一個進程正在使用)戏自,那么進程將仍然可以讀取該文件,磁盤空間也一直被占用
我們通過cat /dev/null > *-json.log來清理相應(yīng)的日志伤锚,然后重啟
systemctl daemon-reload
systemctl restart docker
然而擅笔,我思考,不能每次滿的時候找運維清理日志啊屯援,這多麻煩猛们,難道docker沒有相應(yīng)的機制應(yīng)付輸出到控制臺的日志嗎?答案是:當然不會
在新版的docker中我們可以通過設(shè)置vim /etc/docker/daemon.json 來限制docker的日志量
"log-driver":"json-file","log-opts":{ "max-size" :"200m","max-file":"5"}
顧名思義max-size就是每個日志文件大小狞洋,max-file是最多生成的文件數(shù)弯淘,如上我設(shè)置成功后,每個容器運行的日志最多有五份每份200M大小吉懊,這樣就基本限制了容器的日志大小庐橙。
然后你覺得結(jié)束了嗎?借嗽?并不L睢!
容器日志我們是限制完了恶导,本以為高枕無憂浆竭,不用擔心出現(xiàn)日志爆滿的情況了,但是事與愿違,過幾天硬盤容量又滿了兆蕉。羽戒。缤沦。
我們究其原因虎韵,發(fā)現(xiàn)在docker的運行目錄下overlay這個文件夾里存放著所有的容器掛載目錄,也就是容器的系統(tǒng)文件在這里放著缸废,在容器中跑著的服務(wù)產(chǎn)生日志很可能并不是輸出到控制臺包蓝,而是保存到本地,容器內(nèi)的日志文件也是會占用磁盤空間的企量,這就讓我們犯愁了测萎,這個不好限制開發(fā)團隊不存日志或者規(guī)定團隊存放目錄啊,對于一個成熟的容器平臺來說届巩,海納百川那是必須的~
下面我來總結(jié)以下:
docker日志分為倆種硅瞧,一種是容器的標準輸出日志,也就是docker啟動后控制臺輸出的日志恕汇,另一種是容器內(nèi)的文件日志腕唧,通常是我們項目通過卷的形式掛載到宿主機上的。
k8s中卷volume支持很多形式瘾英,常見的如下
- emptyDir
- hostPath
- nfs
- glusterfs
還支持很多很多類型枣接,如果掛載日志的方式volume選擇emptyDir,則是直接在pod所在node上存放相關(guān)日志,只要pod一直運行在該node上缺谴,emptyDir就會一直在但惶。如果pod遷移到別的pod或者node宕機,日志都會消失湿蛔,所以一般存儲日志不會選擇該方式膀曾。
hostPath方式是 允許Node上的文件系統(tǒng)掛載到pod里去,如果pod里需要node上的文件阳啥,可以使用該種方式妓肢。比如pod需要node節(jié)點上的docker命令,kubectl命令等苫纤。該種方式如果pod遷移或者node宕機日志也會消失碉钠,所以也推薦使用該方式。
nfs和glusterfs這些文件存儲系統(tǒng)卷拘, 看到的效果是把pod內(nèi)的日志存儲到他們自己的文件存儲系統(tǒng)內(nèi)喊废,但本質(zhì)是pod內(nèi)的日志以emptyDir的方式先存儲到node節(jié)點的某個目錄上,然后nfs和glusterfs到node的這些文件目錄中copy日志到自己文件存儲系統(tǒng)中栗弟。
說這么多污筷,想要表達的意思就是不管你以哪種方式在存儲日志,k8s的worker節(jié)點都存在日志爆滿的風險。
針對這個問題瓣蛀,我們可以查看我的另一篇文章http://www.reibang.com/p/a06d79334426陆蟆,有詳細解釋k8s的驅(qū)逐策略。
我們需要注意
kubelet 只支持兩種文件系統(tǒng)分區(qū)惋增。
nodefs 文件系統(tǒng)叠殷,kubelet 將其用于卷和守護程序日志等。
imagefs 文件系統(tǒng)诈皿,容器運行時用于保存鏡像和容器可寫層林束。
針對這倆種文件系統(tǒng),k8s有4種相關(guān)的驅(qū)逐信號
驅(qū)逐信號 | 描述 |
---|---|
nodefs.available | node.stats.fs.available |
nodefs.inodesFree | node.stats.fs.inodesFree |
imagefs.available | node.stats.runtime.imagefs.available |
imagefs.inodesFree | node.stats.runtime.imagefs.inodesFree |
也就是說你的驅(qū)逐信號中如果包含nodefs相關(guān)的稽亏,觸發(fā)閥值后kubelet會刪除卷和守護程序日志壶冒,本質(zhì)就是驅(qū)逐pod了。刪除卷就是項目內(nèi)的文件日志截歉,刪除守護程序日志就是容器的標準日志胖腾。
驅(qū)逐信號中如果包含imagefs相關(guān)的,觸發(fā)閥值后kubelet會刪除沒有使用的鏡像瘪松。
到此咸作,已經(jīng)知道了k8s節(jié)點因鏡像或者日志占用磁盤空間占用過大的解決方案。
執(zhí)行vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
在里面插入
Environment="KUBELET_OTHER_ARGS=
--eviction-hard=memory.available<2Gi,nodefs.available<5Gi,imagefs.available<5Gi
--eviction-minimum-reclaim=memory.available=500Mi,nodefs.available=5Gi,imagefs.available=5Gi
--node-status-update-frequency=10s
--eviction-pressure-transition-period=30s"