Pod Terminating 續(xù)集:最終章

更多奇技淫巧歡迎訂閱博客:https://fuckcloudnative.io
前言
承接?上文,近期我們排查彈性云線上幾起故障時,故障由多個因素共同引起,列舉如下:
- 彈性云在逐步灰度升級 docker 版本至
18.06.3-ce - 由于歷史原因,彈性云啟用了 docker 服務的 systemd 配置選項
MountFlags=slave - 為了避免 dockerd 重啟引起業(yè)務容器重建,彈性云啟用了
live-restore=true配置,docker 服務發(fā)生重啟,dockerd 與 shim 進程 mnt ns 不一致
在以上三個因素合力作用下,線上容器在重建與漂移場景下,出現刪除失敗的事件。
同樣,文章最后也給出了兩種解決方案:
- 長痛:修改代碼,忽略錯誤
- 短痛:修改配置,一勞永逸
作為優(yōu)秀的社會主義接班人,我們當然選擇短痛了!依據官方提示 MountFlags=slave 與 live-restore=true 不能協同工作,那么我們只需關閉二者之一就能解決問題。
與我們而言,docker 提供的 live-restore 能力是一個很關鍵的特性。docker 重啟的原因多種多樣,既可能是人為調試因素,也可能是機器的非預期行為,當 docker 重啟后,我們并不希望用戶的容器也發(fā)生重建。似乎關閉 MountFlags=slave 成了我們唯一的選擇。
等等,回想一下docker device busy 問題解決方案[1],別人正是為了避免 docker 掛載泄漏而引起刪除容器失敗才開啟的這個特性。
但是,這個 17 年的結論真的還具有普適性嗎?是與不是,我們親自驗證即可。
1. 對比實驗
為了驗證在關閉 MountFlags=slave 選項后,docker 是否存在掛載點泄漏的問題,我們分別挑選了一臺 1.13.1 與 18.06.3-ce 的宿主進行實驗。實驗步驟正如docker device busy 問題解決方案[2]所提示,在驗證之前,環(huán)境準備如下:
- 刪除 docker 服務的 systemd 配置項
MountFlags=slave - 挑選啟用 systemd 配置項
PrivateTmp=true的任意服務,本文以httpd為例
下面開始驗證:
////// docker 1.13.1 驗證步驟及結果
// 1. 重新加載配置
[stupig@hostname2 ~]$ sudo systemctl daemon-reload
// 2. 重啟docker
[stupig@hostname2 ~]$ sudo systemctl restart docker
// 3. 創(chuàng)建容器
[stupig@hostname2 ~]$ sudo docker run -d nginx
c89c2aeff6e3e6414dfc7f448b4a560b4aac96d69a82ba021b78ee576bf6771c
// 4. 重啟httpd
[stupig@hostname2 ~]$ sudo systemctl restart httpd
// 5. 停止容器
[stupig@hostname2 ~]$ sudo docker stop c89c2aeff6e3e6414dfc7f448b4a560b4aac96d69a82ba021b78ee576bf6771c
c89c2aeff6e3e6414dfc7f448b4a560b4aac96d69a82ba021b78ee576bf6771c
// 6. 清理容器
[stupig@hostname2 ~]$ sudo docker rm c89c2aeff6e3e6414dfc7f448b4a560b4aac96d69a82ba021b78ee576bf6771c
Error response from daemon: Driver overlay2 failed to remove root filesystem c89c2aeff6e3e6414dfc7f448b4a560b4aac96d69a82ba021b78ee576bf6771c: remove /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged: device or resource busy
// 7. 定位掛載點
[stupig@hostname2 ~]$ grep -rwn /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged /proc/*/mountinfo
/proc/19973/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
/proc/19974/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
/proc/19975/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
/proc/19976/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
/proc/19977/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
/proc/19978/mountinfo:40:231 227 0:40 / /home/docker_rt/overlay2/6c77cfb6c0c4b1e809c47af3c5ff6a4732a783cc14ff53270a7709c837c96346/merged rw,relatime shared:119 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
// 8. 定位目標進程
[stupig@hostname2 ~]$ ps -ef|egrep '19973|19974|19975|19976|19977|19978'
root 19973 1 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 19974 19973 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 19975 19973 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 19976 19973 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 19977 19973 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 19978 19973 0 15:13 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
docker 1.13.1 版本的實驗結果正如網文所料,容器讀寫層掛載點出現了泄漏,并且 docker rm 無法清理該容器(注意 docker rm -f 仍然可以清理,原因參考上文)。
彈性云啟用 docker 配置 MountFlags=slave 也是為了避免該問題發(fā)生。
那么現在壓力轉移到 docker 18.06.3-ce 這邊來了,新版本是否仍然存在這個問題呢?
////// docker 18.06.3-ce 驗證步驟及結果
[stupig@hostname ~]$ sudo systemctl daemon-reload
[stupig@hostname ~]$ sudo systemctl restart docker
[stupig@hostname ~]$ sudo docker run -d nginx
718114321d67a817c1498e530b943c2514ed4200f2d0d138880f8c345df7048f
[stupig@hostname ~]$ sudo systemctl restart httpd
[stupig@hostname ~]$ sudo docker stop 718114321d67a817c1498e530b943c2514ed4200f2d0d138880f8c345df7048f
718114321d67a817c1498e530b943c2514ed4200f2d0d138880f8c345df7048f
[stupig@hostname ~]$ sudo docker rm 718114321d67a817c1498e530b943c2514ed4200f2d0d138880f8c345df7048f
718114321d67a817c1498e530b943c2514ed4200f2d0d138880f8c345df7048f
針對 docker 18.06.3-ce 的實驗非常絲滑順暢,不存在任何問題?;仡櫳衔闹R點,當容器讀寫層掛載點出現泄漏后,docker 18.06.3-ce 清理容器必定失敗,而現在的結果卻成功了,說明容器讀寫層掛載點沒有泄漏。
這簡直就是黎明的曙光。
2. 蛛絲馬跡
上一節(jié)對比實驗的結果給了我們莫大的鼓勵,本節(jié)我們探索兩個版本的 docker 的表現差異,以期定位癥結所在。
既然核心問題在于掛載點是否被泄漏,那么我們就以掛載點為切入點,深入分析兩個版本 docker 的差異性。我們對比在兩個環(huán)境下執(zhí)行完 步驟4 后,不同進程內的掛載詳情,結果如下:
// docker 1.13.1
[stupig@hostname2 ~]$ sudo docker run -d nginx
0fe8d412f99a53229ea0df3ec44c93496e150a39f724ea304adb7f924910d61b
[stupig@hostname2 ~]$ sudo docker inspect -f {{.GraphDriver.Data.MergedDir}} 0fe8d412f99a53229ea0df3ec44c93496e150a39f724ea304adb7f924910d61b
/home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged
// 共享命名空間
[stupig@hostname2 ~]$ grep -rw /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged /proc/$$/mountinfo
223 1143 0:40 / /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged rw,relatime - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
[stupig@hostname2 ~]$ sudo systemctl restart httpd
[stupig@hostname2 ps -ef|grep httpd|head -n 1
root 16715 1 2 16:09 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
// httpd進程命名空間
[stupig@hostname2 ~]$ grep -rw /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged /proc/16715/mountinfo
257 235 0:40 / /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged rw,relatime shared:123 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
// docker 18.06.3-ce
[stupig@hostname ~]$ sudo docker run -d nginx
ce75d4fdb6df6d13a7bf4270f71b3752ee2d3849df1f64d5d5d19a478ac7db8d
[stupig@hostname ~]$ sudo docker inspect -f {{.GraphDriver.Data.MergedDir}} ce75d4fdb6df6d13a7bf4270f71b3752ee2d3849df1f64d5d5d19a478ac7db8d
/home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged
// 共享命名空間
[stupig@hostname ~]$ grep -rw /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged /proc/$$/mountinfo
218 43 0:105 / /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged rw,relatime shared:109 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
[stupig@hostname ~]$ sudo systemctl restart httpd
[stupig@hostname ~]$ ps -ef|grep httpd|head -n 1
root 63694 1 0 16:14 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
// httpd進程命名空間
[stupig@hostname ~]$ grep -rw /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged /proc/63694/mountinfo
435 376 0:105 / /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged rw,relatime shared:122 master:109 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
咋一看,好像沒啥區(qū)別啊!睜大你們的火眼金睛,是否發(fā)現差異所在了?
如果細心對比,還是很容易分辨出差異所在的:
- 共享命名空間中
- docker
18.06.3-ce版本創(chuàng)建的掛載點是 shared 的 - 而 docker
1.13.1版本創(chuàng)建的掛載點是 private 的
- docker
- httpd 進程命名空間中
- docker
18.06.3-ce創(chuàng)建的掛載點仍然是共享的,并且接收共享組 109 傳遞的掛載與卸載事件,注意:共享組 109 正好就是共享命名空間中對應的掛載點 - 而 docker
1.13.1版本創(chuàng)建的掛載點雖然也是共享的,但是卻與共享命名空間中對應的掛載點沒有關聯關系
- docker
可能會有用戶不禁要問:怎么分辨掛載點是什么類型?以及不同類型掛載點的傳遞屬性呢?請參閱:mount 命名空間說明文檔[3]。
問題已然明了,由于兩個版本 docker 所創(chuàng)建的容器讀寫層掛載點具備不同的屬性,導致它們之間的行為差異。
3. 刨根問底
相信大家如果理解了上一節(jié)的內容,就已經了解了問題的本質。本節(jié)我們繼續(xù)探索問題的根因。
為什么兩個版本的 docker 行為表現不一致?不外乎兩個主要原因:
- docker 處理邏輯發(fā)生變動
- 宿主環(huán)境不一致,主要指內核
第二個因素很好排除,我們對比了兩個測試環(huán)境的宿主內核版本,結果是一致的。所以,基本還是因 docker 代碼升級而產生的行為不一致。理論上,我們只需逐個分析 docker 1.13.1 與 docker 18.06.3-ce 兩個版本間的所有提交記錄,就一定能夠定位到關鍵提交信息,大力總是會出現奇跡。
但是,我們還是希望能夠從現場中發(fā)現有用信息,縮小檢索范圍。
仍然從掛載點切入,既然兩個版本的 docker 所創(chuàng)建的掛載點在共享命名空間中就已經出現差異,我們順藤摸瓜,找找容器讀寫層掛載點鏈路上是否存在差異:
// docker 1.13.1
// 本掛載點
[stupig@hostname2 ~]$ grep -rw /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged /proc/$$/mountinfo
223 1143 0:40 / /home/docker_rt/overlay2/4e09fa6803feab9d96fe72a44fb83d757c1788812ff60071ac2e62a5cf14cd97/merged rw,relatime - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
// 定位本掛載點的父掛載點
[stupig@hostname2 ~]$ grep -rw 1143 /proc/$$/mountinfo
1143 44 8:4 /docker_rt/overlay2 /home/docker_rt/overlay2 rw,relatime - xfs /dev/sda4 rw,attr2,inode64,logbsize=256k,sunit=512,swidth=512,prjquota
// 繼續(xù)定位祖父掛載點
[stupig@hostname2 ~]$ grep -rw 44 /proc/$$/mountinfo
44 39 8:4 / /home rw,relatime shared:28 - xfs /dev/sda4 rw,attr2,inode64,logbsize=256k,sunit=512,swidth=512,prjquota
// 繼續(xù)往上
[stupig@hostname2 ~]$ grep -rw 39 /proc/$$/mountinfo
39 1 8:3 / / rw,relatime shared:1 - ext4 /dev/sda3 rw,stripe=64,data=ordered
// docker 18.06.3-ce
// 本掛載點
[stupig@hostname ~]$ grep -rw /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged /proc/$$/mountinfo
218 43 0:105 / /home/docker_rt/overlay2/a9823ed6b3c5a752eaa92072ff9d91dbe1467ceece3eedf613bf6ffaa5183b76/merged rw,relatime shared:109 - overlay overlay rw,lowerdir=XXX,upperdir=XXX,workdir=XXX
// 定位本掛在點的父掛載點
[stupig@hostname ~]$ grep -rw 43 /proc/$$/mountinfo
43 61 8:17 / /home rw,noatime shared:29 - xfs /dev/sdb1 rw,attr2,nobarrier,inode64,prjquota
// 繼續(xù)定位祖父掛載點
[stupig@hostname ~]$ grep -rw 61 /proc/$$/mountinfo
61 1 8:3 / / rw,relatime shared:1 - ext4 /dev/sda3 rw,data=ordered
兩個版本的 docker 所創(chuàng)建的容器讀寫層掛載點鏈路上差異還是非常明顯的:
- 容器讀寫層掛載點的父級掛載點不同
- docker
18.06.3-ce創(chuàng)建的容器讀寫層掛載點的父級掛載點是/home/,并且是共享的 - docker
1.13.1創(chuàng)建的容器讀寫層掛載點的父級掛載點是/home/docker_rt/overlay2,并且是私有的
- docker
這里補充一個背景,彈性云機器在初始化階段,會將 /home 初始化為 xfs 文件系統(tǒng)類型,因此所有宿主上 /home 掛載點都具備相同屬性。
那么,問題基本就是由 docker 1.13.1 中多出的一層掛載層 /home/docker_rt/overlay2 引起。
如何驗證這個猜想呢?現在,其實我們已經具備了檢索代碼的關鍵目標,docker 1.13.1 會設置容器鏡像層根目錄的傳遞屬性。拿著這個先驗知識,我們直接查代碼,檢索過程基本沒費什么功夫,直接展示相關代碼:
// filepath: daemon/graphdriver/overlay2/overlay.go
func init() {
graphdriver.Register(driverName, Init)
}
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
if err := mount.MakePrivate(home); err != nil {
return nil, err
}
supportsDType, err := fsutils.SupportsDType(home)
if err != nil {
return nil, err
}
if !supportsDType {
// not a fatal error until v1.16 (#27443)
logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
}
d := &Driver{
home: home,
uidMaps: uidMaps,
gidMaps: gidMaps,
ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
supportsDType: supportsDType,
}
d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
if backingFs == "xfs" {
// Try to enable project quota support over xfs.
if d.quotaCtl, err = quota.NewControl(home); err == nil {
projectQuotaSupported = true
}
}
return d, nil
}
很明顯,問題就出在 mount.MakePrivate 函數調用上。
官方將 GraphDriver 根目錄設置為 Private,本意是為了避免容器讀寫層掛載點泄漏。那為什么在高版本中去掉了這個邏輯呢?顯然官方也意識到這么做并不能實現期望的目的,官方也在修復[4]中給出了詳細說明。
實際上,不設置 GraphDriver 根目錄的傳播屬性,反而能避免絕大多數掛載點泄漏的問題。。。
4. 結語
現在,我們已經了解了問題的來龍去脈,我們總結下問題的解決方案:
針對
1.13.1版本 docker,存量宿主較多,我們可以忽略device or resource busy問題,基本也不會給線上服務帶來什么影響針對
18.06.3-ce版本 docker,存量宿主較少,我們刪除 docker 服務的 systemd 配置項MountFlags,通過故障自愈解決 docker 卡在問題- 在容器創(chuàng)建后,卸載容器讀寫層掛載,如果不影響容器內文件訪問。那么可以直接卸載所有掛載點,修改 docker 配置,并重啟 docker 服務【本方案尚未驗證】
針對增量宿主,全部刪除 docker 服務的 systemd 配置項
MountFlags
參考資料
[1]docker device busy 問題解決方案: https://blog.terminus.io/docker-device-is-busy/
[2]docker device busy 問題解決方案: https://blog.terminus.io/docker-device-is-busy/
[3]mount 命名空間說明文檔: https://man7.org/linux/man-pages/man7/mount_namespaces.7.html
[4]修復: https://github.com/moby/moby/pull/36047
原文鏈接:https://www.likakuli.com/posts/docker-pod-terminating2/


你可能還喜歡
點擊下方圖片即可閱讀

云原生是一種信仰 ?

掃碼關注公眾號
后臺回復?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!

點擊 "閱讀原文" 獲取更好的閱讀體驗!
??給個「在看」,是對我最大的支持??

