問(wèn)題
在2017年底,我們對(duì)自己的產(chǎn)品添加了監(jiān)控功能,監(jiān)控組件包括prometheus、grafana、cadvisor、node-exporter。監(jiān)控功能集成進(jìn)產(chǎn)品后,經(jīng)過(guò)一段時(shí)間的使用,在卸載產(chǎn)品(主要是停止并刪除各應(yīng)用對(duì)應(yīng)的docker容器)時(shí)經(jīng)常會(huì)出現(xiàn)如下的錯(cuò)誤信息:
Error response from daemon: Unable to remove filesystem for e1f4db20d043e73b997375c1db06c72127f22b5fa9b0163e98b311532fbbb257: remove /var/lib/docker/containers/e1f4db20d043e73b997375c1db06c72127f22b5fa9b0163e98b311532fbbb257/shm: device or resource busy
被刪除失敗的容器即刻變?yōu)镈ead狀態(tài)
上述錯(cuò)誤信息可知,被刪除的容器的shm文件系統(tǒng)一直被占用導(dǎo)致刪除容器失敗
cadvisor容器啟動(dòng)參數(shù)
docker run -itd --name cadvisor --network host --restart always -v /:/rootfs:ro -v /var/run:/var/run:rw -v /sys:/sys:ro -v /var/lib/docker:/var/lib/docker:ro -v /dev/disk:/dev/disk:ro -v /etc/localtime:/etc/localtime:ro cadvisor:v0.27.2 -port 8090
node-exporter容器啟動(dòng)參數(shù)
docker run -itd --name node-exporter --network host --restart always -v /proc:/host/proc -v /sys:/host/sys -v /:/rootfs -v /etc/localtime:/etc/localtime:ro node-exporter:v0.14.0 -collector.procfs /host/proc -collector.sysfs /host/sys -collector.filesystem.ignored-mount-points "^/(sys|proc|dev|host|etc)($|/)"
解決問(wèn)題的過(guò)程
- 考慮到是因?yàn)樘砑恿吮O(jiān)控組件才導(dǎo)致卸載失敗的問(wèn)題,因此首先把注意力放在了四個(gè)組件的停止、刪除操作上。處理的方法是:先停止四個(gè)監(jiān)控組件的容器服務(wù),其次刪除四個(gè)監(jiān)控組件的容器服務(wù),最后停止并刪除各應(yīng)用的容器服務(wù)。經(jīng)過(guò)大量的測(cè)試驗(yàn)證得知:此問(wèn)題已經(jīng)得到解決(服務(wù)停止正常、容器卸載正常、存儲(chǔ)空間釋放正常)。但心里還是想徹底地弄清楚錯(cuò)誤提示
device or resource busy產(chǎn)生的根本原因,因此開始了進(jìn)一步的研究 - 于是開始把注意力放在了docker的存儲(chǔ)引擎devicemapper上面,之前在使用devicemapper的時(shí)候偶爾也會(huì)出現(xiàn)
device or resource busy的錯(cuò)誤,主要是因?yàn)閐ocker的devicemapper存儲(chǔ)引擎自身所導(dǎo)致。因此想到了替換docker的存儲(chǔ)引擎devicemapper為overlay(之前的一次分享會(huì)上學(xué)習(xí)到Centos7.2系統(tǒng)存儲(chǔ)引擎devicemapper的坑要比overlay多一些)。實(shí)踐更改docker的存儲(chǔ)引擎為overlay后發(fā)現(xiàn)device or resource busy的錯(cuò)誤依然存在,可見此解決方案又失敗了 - 調(diào)整注意力到docker自身上,瀏覽docker官網(wǎng)文檔的Troubleshoot一欄,里面確實(shí)提到了遇到的這個(gè)問(wèn)題,但按照提示的做法并沒(méi)有解決問(wèn)題,這一思路又行不通了
- 再一次調(diào)整注意力到四個(gè)監(jiān)控組件的容器服務(wù)上,考慮到:產(chǎn)品是因?yàn)橐肓怂膫€(gè)監(jiān)控組件的容器后才出現(xiàn)的卸載失敗的錯(cuò)誤,所以問(wèn)題肯定是由引入的監(jiān)控組件所導(dǎo)致,于是開始采用對(duì)四個(gè)組件容器進(jìn)行一一排查的策略。經(jīng)過(guò)實(shí)踐排查發(fā)現(xiàn),一旦引入cadvisor與node-exporter兩個(gè)中的任一個(gè),均會(huì)出現(xiàn)上述錯(cuò)誤。于是開始對(duì)cadvisor、node-exporter兩個(gè)容器進(jìn)行深入研究,以cadvisor研究為例:首先確定容器的啟動(dòng)參數(shù)設(shè)置正確;然后開始停止并刪除測(cè)試容器的操作,查找導(dǎo)致
device or resource busy的shm文件系統(tǒng),確定宿主機(jī)上沒(méi)有進(jìn)程占用此shm文件系統(tǒng);于是開始嘗試進(jìn)入cadvisor容器內(nèi)查看掛載信息,發(fā)現(xiàn)cadvisor容器內(nèi)掛載了自身與其他容器的存儲(chǔ)文件系統(tǒng)與shm文件系統(tǒng);開始實(shí)踐查找此處的shm文件系統(tǒng)的掛載與產(chǎn)生device or resource busy錯(cuò)誤的關(guān)系,最終發(fā)現(xiàn)確實(shí)是由于cadvisor容器內(nèi)的shm文件系統(tǒng)的掛載導(dǎo)致的上述錯(cuò)誤。node-exporter的問(wèn)題與cadvisor一樣,此時(shí)終于找到了卸載失敗的原因 - 于是開始瀏覽github上cadvisor、docker的相關(guān)issues,想要找到徹底的解決方案。閱讀完所有的issues后得出:一、這是一個(gè)文件系統(tǒng)泄漏的問(wèn)題(內(nèi)核<=3.10);二、Centos7.3的版本提供了解決方案;三、Centos7.4新版本的內(nèi)核會(huì)嘗試解決這個(gè)問(wèn)題。由于我們當(dāng)前用的是Centos7.2,內(nèi)核是3.10,因此這里無(wú)法找到徹底的解決方案
- 將注意力調(diào)整到cadvisor容器啟動(dòng)的參數(shù)上,思路:cadvisor將其他容器的文件系統(tǒng)一并掛載的原因是因?yàn)樵趧?chuàng)建cadvisor容器的時(shí)候,將包含docker存儲(chǔ)的根路徑(默認(rèn)為/var/lib/docker)作為容器的映射卷映射到了cadvisor的容器中,因此想要深入研究一下cadvisor的實(shí)現(xiàn),在確保cadvisor正常功能的基礎(chǔ)上繞開docker存儲(chǔ)根路徑的映射,對(duì)cadvisor容器的啟動(dòng)采用細(xì)粒度的掛載方式?;舜蟀训臅r(shí)間研究cadvisor的源碼得知cadvisor實(shí)現(xiàn)的監(jiān)控功能必須要映射docker存儲(chǔ)的根路徑,否則關(guān)于容器的監(jiān)控信息將全部丟失,因此采用細(xì)粒度映射的思路又是行不通的
- 對(duì)于當(dāng)前Centos7.2+Docker1.12.6的環(huán)境,最終采用shell腳本的方式對(duì)創(chuàng)建完成的cadvisor與node-exporter兩個(gè)容器將其他容器的文件系統(tǒng)進(jìn)行卸載操作。安裝腳本(install.sh)會(huì)在安裝完所有容器服務(wù)后進(jìn)行文件系統(tǒng)的卸載操作;卸載腳本(uninstall.sh)會(huì)在卸載之前進(jìn)行文件系統(tǒng)的卸載操作。依靠這種方式解決了
device or resource busy的問(wèn)題
卸載cadvisor、node-exporter容器文件系統(tǒng)的腳本
cadvisor與node-exporter容器的操作方式一樣,這里以cadvisor為例說(shuō)明,因?yàn)橐谌萜髦凶鰑mount文件系統(tǒng)的操作因此這里需要對(duì)cadvisor、node-exporter容器添加--privileged參數(shù),代碼段如下:
function exec_cadvisor_container
{
cadvisor_container='cadvisor'
docker_root_dir='/var/lib/docker'
cadvisor_container_id=`docker ps --no-trunc -q --filter "name=${cadvisor_container}"`
for container_id in `docker ps --no-trunc -a -q | grep -v ${cadvisor_container_id}`
do
container_mount_id=`cat ${docker_root_dir}/image/devicemapper/layerdb/mounts/${container_id}/mount-id`
for container_fs in `docker exec ${cadvisor_container} cat /proc/mounts | awk '{print $2}' | grep -E "${container_id}|${container_mount_id}"`
do
if [ -n $container_fs ]
then
docker exec ${cadvisor_container} umount $container_fs
fi
done
done
}