五種快速查找容器文件系統(tǒng)中文件的方法
如果你經(jīng)常使用容器,那么你很有可能希望在某個(gè)時(shí)刻查看正在運(yùn)行的容器的文件系統(tǒng)。也許容器無(wú)法正常運(yùn)行,你想讀取一些日志,也許你想檢查容器內(nèi)部的一些配置文件…或者,你可能像我一樣,想在該容器中的二進(jìn)制文件上放置一些 eBPF 探針(稍后將詳細(xì)介紹)。
不管原因是什么,在這篇文章中,我們將介紹一些可以用來(lái)檢查容器中的文件的方法。
我們將從研究容器文件系統(tǒng)的簡(jiǎn)單和通常推薦的方法開(kāi)始,并討論為什么它們不能總是工作。接下來(lái),我們將對(duì) Linux 內(nèi)核如何管理容器文件系統(tǒng)有一個(gè)基本的了解,我們將利用這一了解以不同但仍然簡(jiǎn)單的方式檢查文件系統(tǒng)。
方法一:Exec 到容器中
如果你快速搜索如何檢查容器的文件系統(tǒng),你會(huì)發(fā)現(xiàn)一個(gè)常見(jiàn)的解決方案是使用 Docker 命令:
- docker exec -it mycontainer /bin/bash
這是一個(gè)很好的開(kāi)始。如果它能滿足你的所有需求,你應(yīng)該繼續(xù)使用它。
然而,這種方法的一個(gè)缺點(diǎn)是,它需要在容器中存在一個(gè) shell。如果容器中沒(méi)有/bin/bash、/bin/sh 或其他 shell,那么這種方法將不起作用。例如,我們?yōu)?Pixie 項(xiàng)目構(gòu)建的許多容器都是基于無(wú) distroless 的,并且沒(méi)有包含一個(gè) shell 來(lái)保持鏡像較小。在這些情況下,這種方法不起作用。
即使 shell 可用,你也無(wú)法訪問(wèn)所有你習(xí)慣使用的工具。因此,如果容器中沒(méi)有安裝 grep,那么你也不能訪問(wèn) grep。這是另一個(gè)找更好工作的理由。
方法二:使用 nsenter
如果你再深入一點(diǎn),就會(huì)意識(shí)到容器進(jìn)程與 Linux 主機(jī)上的其他進(jìn)程一樣,只是在命名空間中運(yùn)行,以使它們與系統(tǒng)的其他部分隔離。
所以你可以使用 nsenter 命令來(lái)輸入目標(biāo)容器的命名空間,使用類似這樣的東西:
- # Get the host PID of the process in the container
- PID=$(docker container inspect mycontainer | jq '.[0].State.Pid')
- # Use nsenter to go into the container’s mount namespace.
- sudo nsenter -m -t $PID /bin/bash
它進(jìn)入目標(biāo)進(jìn)程的掛載(-m)命名空間(-t $PID),并運(yùn)行/bin/bash。進(jìn)入掛載命名空間本質(zhì)上意味著我們獲得容器所看到的文件系統(tǒng)視圖。
這種方法似乎比 docker 的 exec 方法更有前途,但也遇到了類似的問(wèn)題:它要求目標(biāo)容器中包含/bin/bash(或其他 shell)。如果我們輸入的不是掛載命名空間,我們?nèi)匀豢梢栽L問(wèn)主機(jī)上的文件,但是因?yàn)槲覀兪窃趫?zhí)行/bin/bash(或其他 shell)之前輸入掛載命名空間,所以如果掛載命名空間中沒(méi)有 shell,我們就不走運(yùn)了。
方法三:使用 docker 復(fù)制
解決這個(gè)問(wèn)題的另一種方法是簡(jiǎn)單地將相關(guān)文件復(fù)制到主機(jī),然后使用復(fù)制的文件。
要從正在運(yùn)行的容器中復(fù)制選定的文件,可以使用:
- docker cp mycontainer:/path/to/file file
也可以用以下方法來(lái)快照整個(gè)文件系統(tǒng):
- docker export mycontainer -o container_fs.tar
這些命令使你能夠檢查文件,當(dāng)容器可能沒(méi)有 shell 或你需要的工具時(shí),這些命令比前兩種方法有了很大的改進(jìn)。
方法四:在主機(jī)上查找文件系統(tǒng)
復(fù)制方法解決了我們的許多問(wèn)題,但是如果你試圖監(jiān)視日志文件呢?或者,如果你試圖將 eBPF 探針部署到容器中的文件中,又該怎么辦呢?在這些情況下,復(fù)制是不起作用的。
我們希望直接從主機(jī)訪問(wèn)容器的文件系統(tǒng)。容器的文件應(yīng)該在主機(jī)的文件系統(tǒng)中,但是在哪里呢?
Docker 的 inspect 命令給了我們一個(gè)線索:
- docker container inspect mycontainer | jq '.[0].GraphDriver'
這給我們:
- {
- "Data": {
- "LowerDir": "/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab-init/diff:/var/lib/docker/overlay2/524a0d000817a3c20c5d32b79c6153aea545ced8eed7b78ca25e0d74c97efc0d/diff",
- "MergedDir": "/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/merged",
- "UpperDir": "/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/diff",
- "WorkDir": "/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/work"
- },
- "Name": "overlay2"
- }
讓我們來(lái)分析一下:
- LowerDir:包含容器內(nèi)所有層的文件系統(tǒng),最后一層除外
- UpperDir:容器最上層的文件系統(tǒng)。這也是反映任何運(yùn)行時(shí)修改的地方。
- MergedDir:文件系統(tǒng)所有層的組合視圖。
- WorkDir:用于管理文件系統(tǒng)的內(nèi)部工作目錄。
基于 overlayfs 的容器文件系統(tǒng)結(jié)構(gòu)。
因此,要查看容器中的文件,只需查看 MergedDir 路徑。
- sudo ls /var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/merged
如果你想了解文件系統(tǒng)工作的更多細(xì)節(jié),你可以查看 Martin Heinz 關(guān)于 overlay 文件系統(tǒng)的博客文章:https://martinheinz.dev/blog/44。
方法五:/proc/<pid>/root
把最好的留到最后,還有一種從主機(jī)找到容器文件系統(tǒng)的更簡(jiǎn)單的方法。使用容器內(nèi)進(jìn)程的宿主 PID,你可以簡(jiǎn)單地運(yùn)行:
- sudo ls /proc/<pid>/root
Linux 已經(jīng)為你提供了進(jìn)程掛載命名空間的視圖。
此時(shí),你可能會(huì)想:為什么我們不采用這種方法,并將其變成一篇只有一行字的博客文章呢?但這都是關(guān)于旅程,對(duì)吧?
彩蛋:/proc/<pid>/mountinfo
出于好奇,方法四中討論的關(guān)于容器 overlay 文件系統(tǒng)的所有信息也可以直接從 Linux /proc 文件系統(tǒng)中發(fā)現(xiàn)。如果你查看/proc/<pid>/mountinfo,你會(huì)看到如下內(nèi)容:
- 2363 1470 0:90 / / rw,relatime master:91 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/YZVAVZS6HYQHLGEPJHZSWTJ4ZU:/var/lib/docker/overlay2/l/ZYW5O24UWWKAUH6UW7K2DGV3PB,upperdir=/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/diff,workdir=/var/lib/docker/overlay2/63ec1a08b063c0226141a9071b5df7958880aae6be5dc9870a279a13ff7134ab/work
- 2364 2363 0:93 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
- 2365 2363 0:94 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
- …
在這里,你可以看到容器已經(jīng)掛載了一個(gè)覆蓋文件系統(tǒng)作為它的根。它還報(bào)告與 docker inspect 報(bào)告相同類型的信息,包括容器文件系統(tǒng)的 LowerDir 和 UpperDir。它沒(méi)有直接顯示 MergedDir,但你可以直接使用 UpperDir 并將 diff 改為 merged,這樣你就可以看到容器的文件系統(tǒng)了。
我們?cè)?Pixie 怎么用這個(gè)
在本博客的開(kāi)頭,我提到了 Pixie 項(xiàng)目需要如何在容器上放置 eBPF 探針。為什么和如何?
Pixie 內(nèi)部的 Stirling 模塊負(fù)責(zé)收集可觀察數(shù)據(jù)。由于是 k8s 原生的,所以收集的很多數(shù)據(jù)都來(lái)自于在容器中運(yùn)行的應(yīng)用程序。Stirling 還使用 eBPF 探針從它監(jiān)視的進(jìn)程中收集數(shù)據(jù)。例如,Stirling 在 OpenSSL 上部署 eBPF 探針來(lái)跟蹤加密的消息(如果你想了解更多有關(guān)這方面的細(xì)節(jié),請(qǐng)參閱SSL 跟蹤博客[1])。
由于每個(gè)容器都捆綁了自己的 OpenSSL 和其他庫(kù),因此 Stirling 部署的任何 eBPF 探針都必須位于容器內(nèi)的文件上。因此,Stirling 使用本文中討論的技術(shù)在 K8s 容器中找到感興趣的庫(kù),然后從主機(jī)將 eBPF 探針部署到這些二進(jìn)制文件上。
下圖概述了在另一個(gè)容器中部署 eBPF 探針的工作方式。
Stirling 通過(guò)掛載主機(jī)文件系統(tǒng)在其他容器上部署 eBPF 探針,然后在主機(jī)上找到目標(biāo)容器文件系統(tǒng)。
總結(jié)
下次當(dāng)你需要檢查容器中的文件時(shí),希望你能嘗試一下這些技巧。一旦你體驗(yàn)到不再受容器有沒(méi)有 shell 限制的自由,你可能就再也不會(huì)回去了。只需要訪問(wèn)/proc/<pid>/root!