需求
正常情况下这种操作比较反设计,需要谨慎使用 有些时候我们使用
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"$ 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"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$ 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$ 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/# cat demo.py /# python demo.py实现原理
docker参数 --pid=host- 使用宿主机命名空间,方便容器获取到宿主机所有进程信息
- 把宿主机的
/proc文件夹挂载进入容器的/proc路径,其中/proc/1作为nsenter的target,作为容器向宿主机发送命令的关键部分
--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 --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"$ 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"$ 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的大部分命令都可以正常执行命名空间说明
namespace是Linux中一些进程的属性的作用域,使用命名空间,可以隔离不同的进程Linux在不断的添加命名空间,目前有mount:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19ipc:ipc命名空间,使进程有一个独立的ipc,包括消息队列,共享内存和信号量,始于Linux 2.6.19uts:uts命名空间,使进程有一个独立的hostname和domainname,始于Linux 2.6.19net:network命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24pid:pid命名空间,使进程有一个独立的pid空间,始于Linux 2.6.24user:user命名空间,是进程有一个独立的user空间,始于Linux 2.6.23,结束于Linux 3.8cgroup:cgroup命名空间,使进程有一个独立的cgroup控制组,始于Linux 4.6
参考阅读
- 执行