无论是 docker 启动一个 container 还是在 k8s 中 deploy 一个 Pod 都可以指定 privileged 参数,之前在 Pod 的 spec YAML file 也里曾经用过,但是一直没有仔细想过加上这个参数后有什么不一样,今天就来研究一下。
首先来看一个最直观的对比,先运行一个没有 privileged 的容器:
$ docker run --rm -it ubuntu:18.04 bash
root@e6f5f42c5b7e:/# ls /dev/
console core fd full mqueue null ptmx pts
root@e6f5f42c5b7e:/# fdisk -l
再来看看如果加上了 privileged 会有什么不一样:
$ docker run --rm -it --privileged ubuntu:18.04 bash
root@8e28f79eec9e:/# ls /dev/
tty11 tty2 tty28 tty36 tty44 tty52 tty60
...
...
root@8e28f79eec9e:/# fdisk -l
Disk /dev/loop0: 97.1 MiB, 101777408 bytes, 198784 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
不止能看到设备文件,甚至还能 mount 宿主机的文件系统,
root@8e28f79eec9e:~# mount /dev/sda2 test/
root@8e28f79eec9e:~# cd test/
root@8e28f79eec9e:~/test# cd home/yourname/
root@8e28f79eec9e:~/test/home/yourname# echo "hello,world" >> testfile
yourname@host:~$ cat testfile
hello,world
可见 privileged 模式的权限还是很大的,容器能直接修改 host 上的文件。
在不加 privileged 参数时,虽然进入容器后看到的也是 root 用户,但是这是因为容器内默认使用了 user ID 为 0,而 0 表示的就是 root,所以这只是一个名字显示的问题,而不代表用户真的有了 root 权限。
我们可以在容器启动时加入 --user
参数来设置想要的 user ID,比如
$ docker run --rm --user 4000 -it ubuntu:18.04 bash
I have no name!@b0349ec73c3a:/$ ls
这样我们就看到了常见的 “I have no name!"。
关于容器的 UID 和 GID 问题,在另外一篇博客中再详细研究,这里就不展开了。
docker in docker
接着思考,既然都能把 /dev/sda2
这样的设备都挂载了,那是不是也能找到 /var/run/docker.sock
文件呢? 这样我就可以在容器内用 docker 命令操作 Host 上的其他容器了。
于是操作一番
root@7463d90ab1eb:~# mkdir test
root@7463d90ab1eb:~# mount /dev/sda2 test/
root@7463d90ab1eb:~/test/var# ls -l
lrwxrwxrwx 1 root root 4 Feb 3 2020 run -> /run
root@7463d90ab1eb:~/test/run# ls
dnsmasq grub initctl lock log lvm mount screen
sendsigs.omit.d shm sudo systemd user utmp
很奇怪,不单单是 docker.sock
文件不在其中,Host 上 /run
中的很多其他文件也不在,因此无发达成目标。
后来经过一番搜索资料,还是没搞明白为啥找不到 sock 文件,先暂且放一下,有空再研究
既然自创的方法不行,那就用一下 docker 官方的方法了。官方提供了一个名叫 docker:dind
的镜像,也就是 docker-in-docker
的缩写。
运行该镜像也需要 privileged
权限,并且只能以 -d
daemon 的方式运行
$ docker run -d --name dind --privileged docker:dind # 启动容器
$ docker logs -f dind # 查看启动日志
$ docker exec -it dind sh # 进入容器
之后就可以在容器内部执行 docker 命令了。
这种方式的优点是完全独立的一个docker,与 Host 是隔离的,在上面做任何操作都不会影响 Host 上的 docker,当 dind
容器销毁后,里面容器和镜像也都没有了。另外,容器文件系统 AUFS 不支持这种嵌套,因此 dind 的容器用的是 overlay2 类型的文件系统。
dind 的原理
注意,虽然 dind 方式也是开启了 privileged
,但它并不是用上一节开头我想的直接挂载 /var/run/docker.sock
,两者是不一样的。
参考 wrapdocker 源码,大概思路是挂载 cgroup, tmpfs, cgroup 的SUBSYS, 关掉不需要的文件描述符,启动dockerd
容器逃逸
说到这里,那就再来说一句容器逃逸。容器逃逸有很多种,有的是 docker 运行时的 vulnerability,有的是因为 Linux 内核的 bug,前两种都太难了,这里只说一种最简单的——配置不当,最典型的例子就是挂载 /var/run/docker.sock
文件到容器内。
(Host)# docker run -it -v /var/run/:/host/var/run/ ubuntu:18.04 /bin/bash
# apt-get install docker.io
# docker -H unix:///host/var/run/docker.sock container ls
最后一条命令可以显示出 Host 主机上正在运行的容器。