获取macOS平台Docker虚拟机shell
我们知道macOS上的Docker Desktop for macOS实际上是在Linux虚拟机中运行的Docker容器,这对于macOS主机上使用Docker多了一层虚拟化。有些情况下,我们需要能够访问这个Linux虚拟机,以便实现一些hack操作。
方法一: netcat(推荐)
使用 nc
命令连接Docker的debug-shell socket文件( 在 Docker Desktop on Mac 虚拟机 详述):
nc
命令连接Docker的debug-shell socket访问docker虚拟机终端nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock
备注
这里 nc
使用 macOS 内置的 nc
命令( /usr/bin/nc
),支持参数 -U
(即 Use UNIX-domain sockets
),而不能使用 Homebrew 提供的 nc
(不支持 -U
参数)。Linux版本的 nc
是支持 -U
参数的,但是 homebrew 版本没有提供
显示的提示符比较奇怪,不过不影响使用:
/ # ^[[14;5R
我们使用 df -h
命令可以看到Docker虚拟机的存储挂载:
Filesystem Size Used Available Use% Mounted on
overlay 994.1M 4.0K 994.1M 0% /
tmpfs 994.1M 8.0K 994.1M 0% /containers/onboot/000-dhcpcd/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/001-sysfs/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/002-sysctl/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/003-format/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/004-extend/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/005-mount/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/006-metadata/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/007-services0/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/008-services1/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/009-swap/tmp
tmpfs 994.1M 0 994.1M 0% /containers/onboot/010-mount-docker/tmp
/dev/vda1 58.4G 9.8G 45.6G 18% /containers/services
/dev/vda1 58.4G 9.8G 45.6G 18% /containers/services/docker
tmpfs 994.1M 4.0K 994.1M 0% /containers/services/acpid/tmp
overlay 994.1M 4.0K 994.1M 0% /containers/services/acpid/rootfs
...
使用命令 exit
可以推出这个shell
进入shell,可以执行 . /etc/profile
获得环境
Alpine Linux 非常精简,没有找到 apk 安装工具,迷惑
方法二:nsenter(推荐)
警告
使用nsenter从容器内部进入host主机的名字空间,但是对文件系统是只读
另外一种巧妙的方法是运行一个debian容器,然后在这个debian容器中执行 nsenter
通过 pid=host
来实现进入到运行 Docker4Mac 的mini VM的进程空间,这样就相当于进入了macOS的Docker虚拟机:
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
在这个运行的debian容器中通过 nsenter
进入到host主机,也就是Docker VM名字空间以后,就可以看到虚拟机的提示符:
/ #
我们可以在这个Docker VM中执行网络检查:
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 brd 127.255.255.255 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 02:50:00:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.65.3/24 brd 192.168.65.255 scope global dynamic noprefixroute eth0
valid_lft 6320sec preferred_lft 4880sec
inet6 fe80::50:ff:fe00:1/64 scope link
valid_lft forever preferred_lft forever
3: 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: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd ::
5: services1@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 2e:12:69:6d:a4:d2 brd ff:ff:ff:ff:ff:ff link-netns services
inet 192.168.65.4 peer 192.168.65.5/32 scope global services1
valid_lft forever preferred_lft forever
inet6 fe80::2c12:69ff:fe6d:a4d2/64 scope link
valid_lft forever preferred_lft forever
7: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:60:37:48:61 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:60ff:fe37:4861/64 scope link
valid_lft forever preferred_lft forever
9: vethc2f1823@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether e2:a5:ee:12:74:fe brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::e0a5:eeff:fe12:74fe/64 scope link
valid_lft forever preferred_lft forever
11: vethd5f3782@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether ae:3f:ef:d6:8a:04 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::ac3f:efff:fed6:8a04/64 scope link
valid_lft forever preferred_lft forever
这里可以看到Docker VM使用的虚拟网卡 eth0
分配的IP地址是 192.168.65.3
,这个虚拟机和macOS物理主机上对应的IP地址 192.168.65.1
对应,也就是说,如果我们使用 NFS 方式挂载物理主机上的NFS卷,访问的NFS服务器端地址就是这样获得的。
这里还可以看到在Docker VM上运行的Docker网络是 172.17.xx.xx/16
,是一个NAT网络,我们可以看到在Docker VM端分配的IP地址是 172.17.0.1
,我们登录到 Docker运行Studio容器 中配置的 fedora-ssh
容器中,执行 ifconfig
可以看到对应的Docker 容器的NAT网卡的IP地址 172.17.0.2
。这也验证了我们的Docker VM上实际上有2个网络:
192.168.65.x/24 => 和物理主机macOS连接的NAT网络,用于虚拟机
172.17.x.x/16 => 和Docker0连接的NAT网络,用于容器
在Docker容器中,通过两层NAT,依然可以访问外界Internet。不过,反过来,外部需要访问Docker容器就比较麻烦了,需要做端口映射。
方法三:从一个预制镜像运行nsenter
这个方法最简单:
docker run -it --rm --privileged --pid=host justincormack/nsenter1
实践
我感觉使用方法二最为理想,通过运行容器进入物理服务器的名字空间,也就实现了访问虚拟机的能力。这对我使用 macOS系统NFS服务 挂载macOS物理主机上共享的NFS卷,实现存储持久化非常方便。