3

需求

正常情况下这种操作比较反设计,需要谨慎使用
有些时候我们使用docker的时候会需要用到宿主机命令操作,比如

  • 执行 netplan apply生效机器网络配置
  • 查看宿主机网络信息使用ifconfig或者 ip addr
  • 从容器发出命令重启宿主机

具体实现

docker run实现

最简单的实现
使用docker运行一个ubuntu容器
进入容器之后执行nsenter命令查看宿主机网络配置信息

$ sudo docker run -it --pid=host --privileged=true ubuntu /bin/bash
# 进入容器内部之后执行

/# nsenter -a -t 1 sh -c "ip addr"

docker compose实现

新建一个compose.yaml文件,写入如下内容

services:
    demo:
        image: python:3-bullseye
        pid: host
        privileged: true
        container_name: demo-exec
        command: /bin/sh -c "while true; do echo hello; sleep 3600;done"

执行命令

$ docker compose up -d

进入容器,并且创建写入demo.py文件代码

$ docker exec -it demo-exec /bin/bash
/# cat > demo.py <<EOF
import subprocess


if __name__ == '__main__':
    command = 'ls /var/lib/docker'
    resp = subprocess.run(
        f'nsenter -m -u -i -n -p -t 1 sh -c "{command}"',
        capture_output=True, check=True, text=True, shell=True)
    print(f'stdout: {resp.stdout}')
    print(f'stderr: {resp.stderr}')
EOF

在容器内部检查文件是否写入成功并且执行程序

/# cat demo.py
/# python demo.py

实现原理

docker 参数

--pid=host

  • 使用宿主机命名空间,方便容器获取到宿主机所有进程信息
  • 把宿主机的/proc文件夹挂载进入容器的/proc路径,其中/proc/1作为nsentertarget,作为容器向宿主机发送命令的关键部分

--privileged=true

  • 使得docker容器有root权限执行宿主机命令,确保从容器执行命令的时候不会产生权限不足错误

nsenter命令

nsenter命令是一个可以在指定进程的命令空间下运行指定程序的命令

$ nsenter --help

用法:
 nsenter [选项] [<程序> [<参数>...]]

以其他程序的名字空间运行某个程序。

选项:
 -a, --all              enter all namespaces
 -t, --target <pid>     要获取名字空间的目标进程
 -m, --mount[=<文件>]   进入 mount 名字空间
 -u, --uts[=<文件>]     进入 UTS 名字空间(主机名等)
 -i, --ipc[=<文件>]     进入 System V IPC 名字空间
 -n, --net[=<文件>]     进入网络名字空间
 -p, --pid[=<文件>]     进入 pid 名字空间
 -C, --cgroup[=<文件>]  进入 cgroup 名字空间
 -U, --user[=<文件>]    进入用户名字空间
 -S, --setuid <uid>     设置进入空间中的 uid
 -G, --setgid <gid>     设置进入名字空间中的 gid
     --preserve-credentials 不干涉 uid 或 gid
 -r, --root[=<目录>]     设置根目录
 -w, --wd[=<dir>]       设置工作目录
 -F, --no-fork          执行 <程序> 前不 fork
 -Z, --follow-context  根据 --target PID 设置 SELinux 环境

 -h, --help             display this help
 -V, --version          display version

更多信息请参阅 nsenter(1)。

具体执行

$ nsenter -a -t 1 sh -c "ip addr"
  • -a表示进入宿主机的所有命名空间
  • -t 1表示获取/proc/1进程,就是pid=1的进程,这个进程是docker使用--pid=host参数挂载进入容器内部的宿主机进程
  • sh -c "ip addr"就表示发送给宿主机的命令是ip addr

实际使用过程中如果出现宿主机和容器命名空间不一致问题,主要产生原因是宿主机内核版本和容器
所默认的加载内核版本不一致

比如cgroup是在Linux4.6版本加入的,如果使用Ubuntu20或者其他python3.10等比较新的镜像启动容器的时候,当nsenter使用参数-a,容器会加载所有命名空间,但是cgroup命名空间在旧版本的系统里面由于内核版本比较旧,所以该命名空间是没有的,最终nsenter命令就会报错

需要按照宿主机有的命名空间来调整nsenter参数,可以调整如下

$ nsenter -m -u -i -n -p  -t 1 sh -c "ip addr"

比如可以把-a参数替换成-m -u -i -n -p,明确指定进入mount, UTS,System V IPC,网络,pid命名空间
这几个命名空间包含了绝大多数的空间环境,linux的大部分命令都可以正常执行

命名空间说明

namespaceLinux中一些进程的属性的作用域,使用命名空间,可以隔离不同的进程

Linux在不断的添加命名空间,目前有

  • mount:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19
  • ipcipc命名空间,使进程有一个独立的ipc,包括消息队列,共享内存和信号量,始于Linux 2.6.19
  • utsuts命名空间,使进程有一个独立的hostnamedomainname,始于Linux 2.6.19
  • netnetwork命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24
  • pidpid命名空间,使进程有一个独立的pid空间,始于Linux 2.6.24
  • useruser命名空间,是进程有一个独立的user空间,始于Linux 2.6.23,结束于Linux 3.8
  • cgroupcgroup命名空间,使进程有一个独立的cgroup控制组,始于Linux 4.6

参考阅读


龚正阳
29 声望5 粉丝

粗犷型程序员