前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何在主机上调试容器、在容器中操作主机

如何在主机上调试容器、在容器中操作主机

原创
作者头像
陈少文
修改2021-01-14 10:51:09
2.4K1
修改2021-01-14 10:51:09
举报
文章被收录于专栏:陈少文陈少文

1. 一个奇怪的需求

老板有个奇怪的需求,通过一个 kubeconfig 文件,获取主机的各种状态信息,比如进程列表、进程状态等。

第一反应就是,老板是不是不懂容器,容器怎么能这样玩,这样玩还要什么容器,内心万马奔腾。

直到最近遇到了一个命令行工具,才发现原来小丑是我自己。下面一起来看看,我发现了什么吧。

2. 容器的原理

沙箱是一个虚拟环境,在沙箱内部进行的操作对外部没有影响。沙箱与沙箱之间是隔离的,也是不可见的,看不到彼此的存在。

我们常说的容器就是基于 Linux 的 Cgroups 和 Namespace 技术构建的一个沙箱环境。

从图中,可以看到,容器与容器的边界就是通过 Cgroups 和 Namespace 这两种技术控制的。下面简单描述一下这两种技术:

  1. Namespace

不同 Namespace 下的资源相互独立、不可见。Linux 从 2.4.19 完成了支持 Mount Namespace,2.6.19 完成了支持 UTS、IPS Namespace,2.6.24 完成了支持 PID Namespace,2.6.29 完成了支持 Network Namespace,3.8 完成了支持 User Namespace 。其中,除了 User Namespace ,其他都需要以 root 权限创建。同时,在 4.6 中已经新增了 Cgroup namespace,目前 RunC(Docker 提供的运行时) ,已经合并了相关的 PR: https://github.com/opencontainers/runc/pull/1916 。下面是其中的 7 种 Namespace。

  • Mount namespace,隔离文件系统挂载点。一个 Namespace 中,程序对文件的修改,只影响自身的文件系统,而对其他 Namespace 没有影响。
  • UTS namespace,隔离主机名和域名信息。每个 Namespace 中,主机和域名信息相互独立。
  • IPC namespace,隔离进程通信的行为。只有一个 Namespace 中的进程可以互相通信。
  • PID namespace,隔离进程的 PID 空间。不同 Namespace 中的进程 PID 可以重复,互不影响。PID 为 1 的进程是其他所有进程的父进程,因此这个 Namespace 非常有意义。
  • Network namespace,隔离网络资源。每个 Namespace 都具有独立的网络栈信息,容器运行时仿佛在一个独立的网络中。
  • User namespace,隔离用户和用户组。同一个用户在不同的 Namespce,可以具有不同的角色,用来保障安全性。
  • Cgroup namespace,隔离 Cgroup 的可见性。每个 Namespace 中,都具有独立的 cgroupns root 和 cgroup filesystem 视图。
  1. Cgroups

上面将一组进程放置到一个 Namespace,对外隔离,对内共享资源,接着使用 Cgroups 对其进行资源的控制。Cgroups 提供了四个功能:

  • 资源限制。针对一个进程或进程组,设置资源消耗限制。比如内存超出限制,会导致申请内存失败。
  • 资源统计。统计 CPU 使用时长、内存用量等。
  • 任务控制。控制进程的状态,可以挂起、恢复进程。
  • 优先级分配。设置进程的优先级。

利用 Namespcae 和 Cgroups 提供的沙箱环境,再加上文件系统技术,就支持起了容器技术。

3. 一个调试工具: nsenter

nsenter 是一个用来进入指定程序,所在 Namespace,并执行命令的工具。在容器场景下,很多容器为了轻量化,而裁剪了很多基础的命令,比如 iptcpdump 等。这样给调试容器带来了一定的困难,通过 nsenter 共享 Namespace 进行调试,可以很好地解决这个问题。

实际上,RunC 在创建容器时,也是调用的 nsenter ,在 libcontainer 的代码中可以看到。

  1. 安装 nsenter

大部分的 Linux 操作系统,已经内置了 nsenter 命令。如果没有,以 CentOS 为例,执行如下命令,安装 util-linux 包即可:

1

yum install -y util-linux

  1. nsenter 的版本和参数

由于不同的 Linux Kernel 对 Namespace 支持的程度不一样,nsenter 的版本会有所差异。这里以 CentOS 7 为例:

  • 查看系统内核版本

1 2 3

uname -a Linux i-x29a8rdc 3.10.0-1127.10.1.el7.x86_64 #1 SMP Wed Jun 3 14:28:03 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

  • 查看 nsenter 版本

1 2 3

nsenter -V nsenter from util-linux 2.23.2

  • 查看 nsenter 的参数

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

nsenter -h Usage: nsenter [options] <program> [<argument>...] Run a program with namespaces of other processes. Options: -t, --target <pid> target process to get namespaces from -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 -U, --user[=<file>] enter user namespace -S, --setuid <uid> set uid in entered namespace -G, --setgid <gid> set gid in entered namespace --preserve-credentials do not touch uids or gids -r, --root[=<dir>] set the root directory -w, --wd[=<dir>] set the working directory -F, --no-fork do not fork before exec'ing <program> -Z, --follow-context set SELinux context according to --target PID -h, --help display this help and exit -V, --version output version information and exit

这里需要注意的是 -t 参数,指定一个进程,用于获取 Namepace 参数。其他参数主要是使能、设置参数。

由于非沙箱环境下,并不容易体现 nsenter 的功能,我们在容器环境下进一步实验。

4. nsenter 在 Docker 容器环境下的应用

4.1 主机下,进入容器的 Namespace 环境

  • 选择一个容器

1 2 3 4

docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9addecf82c5e sonarqube:7.9.4-community "./bin/run.sh" 3 weeks ago Up 3 weeks 0.0.0.0:9000->9000/tcp sonarqube_sonarqube_1

  • 获取容器的 PID

每个容器内都有一个 PID=1 的进程,如同宿主机上的 init 进程一样,是其他进程的父进程。但是在主机上,容器进程具有另外一个 PID ,可以用于管理容器。

1 2 3

docker inspect --format "{{ .State.Pid }}" 9addecf82c5e 3969

  • 进入容器的 Namespace 环境

这里以进入网络空间为例:

1

nsenter -t 3969 -n /bin/bash

如果宿主机上的默认 shell,在容器中存在,可以省略 /bin/bash,否则需要显式指定一个容器中的 shell。

  • 执行主机上的命令行工具,调试容器环境

1 2 3 4 5 6 7 8 9 10

ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever

从执行结果可以看到,显示的是容器上的网卡地址信息,但 ip 命令却来自宿主机。

4.2 容器下,进入主机的 Namespace 环境

  • 以特权模式,使用主机的 Namespace 创建容器

1

docker run --privileged --net=host --ipc=host --pid=host -it --rm docker.io/alpine:3.12 /bin/sh

  • 进入 PID=1 进程的 Namespace 环境

1

nsenter -t 1 -m -u -i -n

  • 执行命令,操作主机环境

1 2 3 4 5

docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2cd99b9d7b5a alpine:3.12 "/bin/sh" About a minute ago Up About a minute trusting_khorana 9addecf82c5e sonarqube:7.9.4-community "./bin/run.sh" 3 weeks ago Up 3 weeks 0.0.0.0:9000->9000/tcp sonarqube_sonarqube_1

从执行结果可以看到,命令显示的是主机上的容器信息,但却是在容器下执行的命令。

5. nsenter 在 Kubernetes 容器环境下的应用

这部分的内容和上一个章节类似,只不过在进入容器时,需要借道 Pod 获取 PID;在主机上执行命令时,需要借道 Pod 创建容器。

5.1 从主机进入 Kubernetes Pod 中,调试容器环境

  • 选择一个 Pod

1 2 3 4

kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-6db489d4b7-589bd 1/1 Running 0 11s 10.233.76.91 tf-cd-allinone-0 <none> <none>

  • 获取容器 ID

1 2 3

kubectl describe pod nginx-6db489d4b7-589bd | grep -A10 "^Containers:" | grep -Eo 'docker://.*$' | head -n 1 | sed 's/docker:\/\/\(.*\)$/\1/' 981c94ef07abfbeca548e9e36cd70a7369d1cf38a50754c2dc4f87fbc27601d1

  • 切换到主机所在节点

1

ssh root@tf-cd-allinone-0

  • 获取容器的 PID

1 2 3

docker inspect --format "{{.State.Pid}}" 981c94ef07abfbeca548e9e36cd70a7369d1cf38a50754c2dc4f87fbc27601d1 6954

  • 进入容器的 Namespace

1

nsenter -t 6954 -n

  • 执行主机命令,调试容器

1 2 3 4 5 6 7 8 9 10 11 12

ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 4: eth0@if100: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default link/ether 16:a3:44:dc:58:ce brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.233.76.91/32 scope global eth0 valid_lft forever preferred_lft forever

这里需要注意的是,容器和节点是绑定在一起的,对于多节点环境,获取容器 ID 之后,需要切换到所在主机进行操作。

5.2 在 Kubernetes Pod 中,直接操作主机

  • 新建一个 pod-test.yaml 文件,内容如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

apiVersion: v1 kind: Pod metadata: name: pod-test namespace: default spec: containers: - command: ['sh', '-c', 'echo "Hello, wwww.chenshaowen.com !" && sleep 3600'] image: docker.io/alpine:3.12 name: pod-test securityContext: privileged: true hostIPC: true hostNetwork: true hostPID: true

  • 创建 Pod

1

kubectl apply -f pod-test.yaml

  • 进入 Pod 中

1

kubectl exec -it pod-test /bin/sh

  • 进入 PID=1 进程的 Namespace

1

nsenter -t 1 -m -u -i -n -p

  • 执行主机命令测试

1 2 3 4 5 6

docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bd778c3172 389fef711851 "sh -c 'echo \"Hello,…" 4 minutes ago Up 4 minutes k8s_pod-test_pod-test_default_3a496075-419e-477a-b03c-a423677a90be_0 4e197fd98294 kubesphere/pause:3.1 "/pause" 4 minutes ago Up 4 minutes k8s_POD_pod-test_default_3a496075-419e-477a-b03c-a423677a90be_0 ...

以特权模式启动容器,通过 PID=1 的进程共享 Namespace,直接执行主机上的命令。

6. 总结

本篇主要介绍了在容器环境下,如何逃逸到主机执行命令;在主机下,如何进入容器调试环境。同时,还给出了在 Container 和 Kubernetes 两种场景下的实践示例。

其中有两点对我有所启发,一个是 nsenter 命令,加深了对容器技术的理解。另外一个是特权模式启动的容器,权限十分大,需要谨慎,业务应该尽量采用 rootless 的方式运行容器。

在以特权模式启动的 Docker Daemon 中,创建 Kuberntes 集群,通过 nsenter 命令,可以 nodeSelector 到任意节点,然后执行 kubectl/docker/systemctl 等命令进行破坏活动。

7. 参考

原文最新更新链接:https://www.chenshaowen.com/blog/operate-host-in-container-and-debug-container-on-host.html


更多精彩内容,请关注公众号。

问其
问其

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 一个奇怪的需求
  • 2. 容器的原理
  • 3. 一个调试工具: nsenter
  • 4. nsenter 在 Docker 容器环境下的应用
    • 4.1 主机下,进入容器的 Namespace 环境
      • 4.2 容器下,进入主机的 Namespace 环境
      • 5. nsenter 在 Kubernetes 容器环境下的应用
        • 5.1 从主机进入 Kubernetes Pod 中,调试容器环境
          • 5.2 在 Kubernetes Pod 中,直接操作主机
          • 6. 总结
          • 7. 参考
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档