Container Note - Mount Namespace
Mount Namespace 研究
本文探討 Linux 的 Mount Namespace,這是一種隔離檔案系統的技術。文章將講解其原理、應用場景和如何有效地使用它來提高系統安全性。
使用環境
Rocky Linux 8.7
環境描述
在 kubernetes 上常常需要透過一些 CLI tool 幫助維護 container,如果 image 本身不提供這些 tool,執行上就會比較麻煩。通常可以透過 kubectl cp
將 CLI tool 複製到 container 內解決問題,需要注意的是,在官方的文件上有指出,如果使用 kubectl cp
則會需要容器內已安裝 tar
指令,否則會失敗:
以 simple-spring-boot-app 專案為例,base image 是 google 的 distroless,這個 image 內除了 java
外,並沒有其他的 command,在執行 kubectl cp
時會發生錯誤:
simple-spring-boot-app: https://github.com/tsunejui/simple-spring-boot-app
distroless: https://github.com/GoogleContainerTools/distroless
此時需要到 node 上,透過 container 的管理工具所提供的方法 (cp
指令),將檔案複製到 container 內,一些工具如下:
- Docker:
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
- Podman:
podman cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
- cri-tools:
crictl cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
- nerdctl:
nerdctl cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
假設 node 上 container 的管理工具並沒有提供 cp
的功能,或者沒有安裝 container 的管理工具時,可以透過以下測試步驟,找到 process (也就是 container) 所使用的 mnt namespace
的 mountinfo
,並查看對應的 mount point,直接將 CLI tool 放置於指定的目錄即可。
測試步驟
- 建立 POD
https://hub.docker.com/repository/docker/tsunejui/spring-boot-actuator/general
kubectl run spring-boot-actuator --image=tsunejui/spring-boot-actuator:v0.0.1
- 準備執行檔
本次實驗將直接搬移檔案到與 process 相同 mnt namespace 的 mount point 內指定的目錄,並且執行。首先我們先準備一個執行檔,可以透過 C 語言撰寫一個簡單的 hello world 工具: vi hello.c
#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}
- 編譯執行檔
測試環境如果沒有
gcc
,可以透過sudo dnf install gcc-c++
安裝
gcc -o hello hello.c
- 找到 process number
在測試環境中,container 所執行的 command 是 java -jar main.jar
,因此直接在 linux 上透過 ps
查看:
ps -ef | grep 'java -jar main.jar'
- 找到 merged dir
現在我們知道 container 的 pid 為 2637095
,並可以到 /proc/2637095/mountinfo
上找到 /
的 mount point:
我們可以複製 overlayfs 的 upperdir 目錄,並且使用 findmnt
指令查看所有 mount point,搭配篩選後便能看到 merged dir,以 CRI 是 containerd 的 kubernetes 為例,通常會在 /run/containerd/io.containerd.runtime.v2.task/k8s.io/<container-id>/rootfs
內:
findmnt | grep <upperdir>
- 複製執行檔至 merged dir 內
將 hello
複製於 merged dir 內:
cp hello /run/containerd/io.containerd.runtime.v2.task/k8s.io/<containe-id>/rootfs/bin/
- 使用
kubectl exec
執行hello
可以看到,執行檔成功執行:
kubectl exec po/<pod-name> -- /bin/hello