需求
正常情况下这种操作比较反设计,需要谨慎使用
有些时候我们使用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
作为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 -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
的大部分命令都可以正常执行
命名空间说明
namespace
是Linux
中一些进程的属性的作用域,使用命名空间,可以隔离不同的进程
Linux
在不断的添加命名空间,目前有
mount
:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19
ipc
:ipc
命名空间,使进程有一个独立的ipc
,包括消息队列,共享内存和信号量,始于Linux 2.6.19
uts
:uts
命名空间,使进程有一个独立的hostname
和domainname
,始于Linux 2.6.19
net
:network
命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24
pid
:pid
命名空间,使进程有一个独立的pid
空间,始于Linux 2.6.24
user
:user
命名空间,是进程有一个独立的user
空间,始于Linux 2.6.23
,结束于Linux 3.8
cgroup
:cgroup
命名空间,使进程有一个独立的cgroup
控制组,始于Linux 4.6
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。