基于containerd CRI如何查看容器的文件内容?

CRI:containerd安装 crictl:https://github.com/kubernetes-sigs/cri-tools/releasesexec进入容器中查看使用 crictl crictl exec -it <container-id> <shell-in-co

CRI:containerd

安装 crictl:https://github.com/kubernetes-sigs/cri-tools/releases

exec进入容器中查看

使用 crictl crictl exec -it <container-id> <shell-in-container> 需要在 Pod 所在的 Node 上执行。

ubuntu@work-1:~$ sudo crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                        ATTEMPT             POD ID              POD
64aad8b603123       985a52bfdfe21       13 minutes ago      Running             simple-http-server          0                   0c2daa092abd3       shs-5698fcd598-dpxlg

ubuntu@work-1:~$ sudo crictl exec -it 64aad8b603123 bash
bash-5.1# ls -l
total 6352
-rwxr-xr-x    1 root     root       6503679 Oct 28 05:48 simple-http-server

使用 kubectl kubectl exec -it <pod-name> -- bash

# ubuntu @ master in ~ [23:16:17]
$ kubectl exec -it shs-5698fcd598-dpxlg -- bash
bash-5.1# ls -l
total 6352
-rwxr-xr-x    1 root     root       6503679 Oct 28 05:48 simple-http-server

前提

  1. 容器中需要存在 shell 或 ls 等命令;
  2. 如果在容器中想做一些操作,它还需要存在你想使用的命令。

使用 nsenter

需要在 Pod 所在 Node 上执行

查找目标 Pod 进程 id。有两种方式

  1. 直接 ps -ef ,然后用 grep 去过滤出来 Pod 的启动命令。
ubuntu@work-1:~$ ps -ef | grep simple-http-server | grep -v grep
root        7630    7510  0 23:00 ?        00:00:00 /app/simple-http-server
root        8158    8103  0 23:01 ?        00:00:00 /app/simple-http-server

如果分得清楚是哪个 Pod 的进程,直接那对应进程的 pid 即可;如果分不清,那继续看下一种方法。

  1. 先用 kubectl get pod <target-pod> -oyaml | grep containerID 找目标 Pod 中业务容器的 id,然后再用 crictl inspect <container-id> 获取到容器进程的pid
$ kubectl get pod shs-5698fcd598-sjq5p -oyaml | grep containerID
  - containerID: containerd://602515a3a5fb6e36902844a0654d82841bb565d57df9d4b0e6ecf024947b0407

$ sudo crictl inspect 602515a3a5fb6e36902844a0654d82841bb565d57df9d4b0e6ecf024947b0407 | grep pid
    "pid": 7630,
            "pid": 1
            "type": "pid"
  1. 使用 nsenter 进入容器进程的各个 namespace 中
ubuntu@work-1:~$ sudo nsenter -t 7630 -a bash
bash-5.1# ls -lh /app
total 6M
-rwxr-xr-x    1 root     root        6.2M Oct 28 05:48 simple-http-server

此种方法仍然需要在容器中有 shell 程序或者相应命令。但也可以单独只指定某个 namespace。

例如,对 CoreDNS 抓包。CoreDNS 使用了 Google  distroless/static 作为基础镜像的容器,需要对其进行网卡抓包,但容器中又没有 tcpdump 命令时,便可以只进入 net namespace,同时使用宿主机的 shell 和 tcpdump 程序,对其网卡进行抓包。

$ kubectl -n kube-system get pod -owide | grep coredns
coredns-84b58f6b4-qd4vp                      1/1     Running   11 (57m ago)    30d   172.20.251.235   192.168.64.5

$ kubectl -n kube-system exec -it coredns-84b58f6b4-qd4vp -- /bin/zsh
error: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "...": OCI runtime exec failed: exec failed: unable to start container process: exec: "/bin/zsh": stat /bin/zsh: no such file or directory: unknown

$ ps -ef | grep -i coredns | grep -v grep
root        1738    1618  0 22:45 ?        00:00:11 /coredns -conf /etc/coredns/Corefile

$ sudo nsenter -t 1738 -n /bin/zsh
work-2# ip address show dev eth0
3: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 66:b9:35:7b:77:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.251.235/32 brd 172.20.251.235 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::64b9:35ff:fe7b:7702/64 scope link
       valid_lft forever preferred_lft forever
work-2# tcpdump -i eth0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

另外 nsenter 还可以单独进入其他的 namespace,分别如下:

 -a, --all              enter all namespaces
 -m, --mount[=<file>]   enter mount namespace
 -u, --uts[=<file>]     enter UTS namespace (hostname etc)
 -i, --ipc[=<file>]     enter System V IPC namespace
 -n, --net[=<file>]     enter network namespace
 -p, --pid[=<file>]     enter pid namespace
 -C, --cgroup[=<file>]  enter cgroup namespace
 -U, --user[=<file>]    enter user namespace
 -T, --time[=<file>]    enter time namespace
 -S, --setuid <uid>     set uid in entered namespace
 -G, --setgid <gid>     set gid in entered namespace

单独进入某个 namespace 或多个 namespace 时,需要注意其他的 namespace 仍然是宿主机的默认 namespace,可能会产生一些有意思的现象,可能是个莫名奇妙的问题,需要适当拿捏这种情况。

在宿主机的文件系统中查找

先找到运行中的容器 ID

$ kubectl -n kube-system get pod coredns-84b58f6b4-qd4vp -oyaml | grep containerID
  - containerID: containerd://2bf4db84e5ec38f4cd2c7aebd144258bffd9e3883fcd8741d1bc535a7438cc7d
        containerID: containerd://833f8abbb616895dc8bd8fb15c2153adddd0dcbef8a51a540a53ed2f663ac3ee

这里有两个,其中下面那个是该 Pod 的上一个容器的 ID,处于异常状态,第一个才是处于 Running 状态的容器。

到该 Pod 所在的 Node 节点上,用 df -h | grep <container-id> 的方式,来获取挂载信息

# df -h | grep 2bf4db84e5ec38f4cd2c7aebd144258bffd9e3883fcd8741d1bc535a7438cc7d
overlay          20G  4.7G   15G  25% /run/containerd/io.containerd.runtime.v2.task/k8s.io/2bf4db84e5ec38f4cd2c7aebd144258bffd9e3883fcd8741d1bc535a7438cc7d/rootf

进入 /run/containerd/io.containerd.runtime.v2.task/k8s.io/<container-id> 目录,可以看到如下信息:

# ls -l
total 36
-rw-r--r-- 1 root root    89 Jan  5 22:45 address
-rw-r--r-- 1 root root 11554 Jan  5 22:45 config.json
-rw-r--r-- 1 root root     4 Jan  5 22:45 init.pid
prwx------ 1 root root     0 Jan  5 22:45 log
-rw-r--r-- 1 root root   170 Jan  5 23:45 log.json
-rw------- 1 root root    23 Jan  5 22:45 options.json
drwxr-xr-x 1 root root  4096 Jan  5 22:45 rootfs
-rw------- 1 root root     0 Jan  5 22:45 runtime
-rw------- 1 root root    37 Jan  5 22:45 shim-binary-path
lrwxrwxrwx 1 root root   121 Jan  5 22:45 work -> /var/lib/containerd/io.containerd.runtime.v2.task/k8s.io/2bf4db84e5ec38f4cd2c7aebd144258bffd9e3883fcd8741d1bc535a7438cc7d

rootfs 目录便是容器的目录,但是内容不是挂载完成之后的。

# ls rootfs
coredns  dev  etc  proc  sys  var

# cat rootfs/etc/resolv.conf
# 无输出

也可以用 /proc/<pid>/mounts 来查看 overlay 的挂载详情(即第一条根目录/的挂载信息)

# cat /proc/1738/mounts
overlay / overlay ro,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/20/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/19/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/926/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/926/work 0 0
...
/dev/vda1 /etc/coredns ext4 ro,relatime,discard,errors=remount-ro 0 0
/dev/vda1 /etc/hosts ext4 rw,relatime,discard,errors=remount-ro 0 0
/dev/vda1 /dev/termination-log ext4 rw,relatime,discard,errors=remount-ro 0 0
/dev/vda1 /etc/hostname ext4 ro,relatime,discard,errors=remount-ro 0 0
/dev/vda1 /etc/resolv.conf ext4 ro,relatime,discard,errors=remount-ro 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k,inode64 0 0
tmpfs /var/run/secrets/kubernetes.io/serviceaccount tmpfs ro,relatime,size=307200k,inode64 0 0
...

通过 /proc/<pid>/root 来查看

先获取到目标 Pod 中容器的进程 pid,然后到 Pod 所在的 Node 上,直接 cd & ls & cat 即可。

root@work-2:/home/ubuntu# cd /proc/1738/root
root@work-2:/proc/1738/root# cat etc/coredns/Corefile
.:53 {
    errors
    health {
        lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf {
        max_concurrent 1000
    }
    cache 30
    reload
    loadbalance
}

Reference

Read more

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd