共计 7087 个字符,预计需要花费 18 分钟才能阅读完成。
关于 docker 安装,可以参考这篇文章。
什么是容器?
容器其实只是 linux 系统系统下带有特殊设置的进程。
在命令行下输入以下,看看会发生什么:
docker run -d --name=db redis:alpine
上面启动的 docker 容器内会启动一个叫 redis-server
的进程。在宿主机上,我们可以查看到 docker 容器启动的所有进程。使用下面命令查看 redis-server
进程:
ps aux | grep redis-server
docker 可以帮助我们查看进程的信息,包括进程 pid,以及 ppid。
docker ps top
这个进程的 ppid
是谁?使用命令 ps aux | grep <ppid>
查看对应的进程。没啥意外的话应该是 Containered
。
pstree
命令可以查看进程下所有的子进程,使用下面命令查看 dockerd 的所有子进程。
pstree -c -p -A $(pgrep dockerd)
以上命令输出:
$ ps aux | grep redis-server
999 1854 0.2 1.0 25252 10344 ? Ssl 10:47 0:00 redis-server
root 2143 0.0 0.0 14220 960 pts/0 R+ 10:51 0:00 grep --color=auto redis-server
$ docker top db
UID PID PPID C STIME TTY TIME CMD
999 1854 1840 0 10:47 ? 00:00:00 redis-server
$ ps aux | grep 1840
root 1840 0.0 0.7 8924 7620 ? Sl 10:47 0:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/55c6d2c2bf2e62eddd0d688dfc88519d116d1ee9afb917bbfffe778f8ec234ff -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc -debug
root 2243 0.0 0.0 14220 1008 pts/0 R+ 10:52 0:00 grep --color=auto 1840
$ pstree -c -p -A $(pgrep dockerd)
dockerd(689)-+-docker-containe(729)-+-docker-containe(1840)-+-redis-server(1854)-+-{redis-server}(1889)
| | | |-{redis-server}(1890)
| | | `-{redis-server}(1891)
| | |-{docker-containe}(1841)
| | |-{docker-containe}(1842)
| | |-{docker-containe}(1843)
| | |-{docker-containe}(1844)
| | |-{docker-containe}(1870)
| | `-{docker-containe}(1871)
| |-{docker-containe}(738)
| |-{docker-containe}(739)
| |-{docker-containe}(740)
| |-{docker-containe}(741)
| |-{docker-containe}(742)
| |-{docker-containe}(760)
| |-{docker-containe}(761)
| `-{docker-containe}(1616)
|-{dockerd}(710)
|-{dockerd}(715)
|-{dockerd}(716)
|-{dockerd}(728)
|-{dockerd}(731)
|-{dockerd}(754)
|-{dockerd}(755)
|-{dockerd}(756)
`-{dockerd}(1795)
进程目录
每个进程的配置目录都在 /proc
目录下,如果你知道进程的 pid,你就能找到它的配置目录。
DBPID=$(pgrep redis-server)
echo Redis is $DBPID
ls /proc
ls /proc/$DBPID
cat /proc/$DBPID/environ
命名空间
容器的一个基本部分是命名空间。命名空间的概念是限制哪些进程可以查看和访问系统的某些部分,例如其他网络接口或进程。
启动容器时,容器运行时(如 Docker)将创建新的命名空间以对该进程进行沙盒处理。通过在它自己的 Pid 命名空间中运行一个进程,它看起来就像是系统上唯一的进程。
可用的命名空间有:
- Mount (mnt)
- Process ID (pid)
- Network (net)
- Interprocess Communication (ipc)
- UTS (hostnames)
- User ID (user)
- Control group (cgroup)
Unshare 可以启动“contained”进程
如果不适用 docker 运行时,也可以用 unshare 之类的工具构造只在自己命名空间内运行的进程。
unshare --help
通过 unshare,可以启动进程并创建一个新的命名空间,例如 Pid。通过从主机取消共享 Pid 命名空间,看起来 bash 提示符是计算机上运行的唯一进程。
sudo unshare --fork --pid --mount-proc bash # 使用新的命名空间启动 bash
ps # 在上面的 bash 环境下查看进程,此时只能看到 bash 和 ps 进程
exit # 退出上面启动的 bash 进程
当我们共享命名空间时,发生了什么?
命名空间实际就是磁盘上的 inode。这使得我们可以共享 / 重用命名空间,以及允许对它们查看并交互。
查看进程的命名空间列表:
$ ls -lha /proc/$DBPID/ns/
total 0
dr-x--x--x 2 999 packer 0 Sep 2 12:21 .
dr-xr-xr-x 9 999 packer 0 Sep 2 12:21 ..
lrwxrwxrwx 1 999 packer 0 Sep 2 12:22 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 ipc -> ipc:[4026532157]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 mnt -> mnt:[4026532155]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 net -> net:[4026532160]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 pid -> pid:[4026532158]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 user -> user:[4026531837]
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 uts -> uts:[4026532156]
使用 Docker,可以使用语法容器共享这些命名空间:<container-name>。例如,下面的命令将 nginx 连接到 DB 名称空间。
docker run -d --name=web --net=container:db nginx:alpine
WEBPID=$(pgrep nginx | tail -n1)
echo nginx is $WEBPID
cat /proc/$WEBPID/cgroup
查看 web 容器的命名空间,发现有些跟 db 容器的命名空间一致:
$ ls -lha /proc/$WEBPID/ns/
total 0
dr-x--x--x 2 systemd-network systemd-journal 0 Sep 2 12:24 .
dr-xr-xr-x 9 systemd-network systemd-journal 0 Sep 2 12:23 ..
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 ipc -> ipc:[4026532225]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 mnt -> mnt:[4026532223]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 net -> net:[4026532160]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 pid -> pid:[4026532226]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 user -> user:[4026531837]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 uts -> uts:[4026532224]
$ ls -lha /proc/$DBPID/ns/ | grep net:
lrwxrwxrwx 1 999 packer 0 Sep 2 12:21 net -> net:[4026532160]
$ ls -lha /proc/$WEBPID/ns/ | grep net:
lrwxrwxrwx 1 systemd-network systemd-journal 0 Sep 2 12:24 net -> net:[4026532160]
Chroot
容器进程的一个重要部分是能够拥有独立于主机的不同文件。这就是我们如何根据我们系统上运行的不同操作系统获得不同的 Docker 镜像。
Chroot 为进程提供了从父 OS 的不同根目录开始的能力。这允许不同的文件出现在根目录中。
Cgroups (Control Groups)
CGroup 限制进程可以使用的资源量。这些 cgroup 是在 / proc 目录中的特定文件中定义的值。
要查看映射,请运行以下命令:
cat /proc/$DBPID/cgroup
这些映射到磁盘上的其他 cgroup 目录
ls /sys/fs/cgroup/
什么是进程的 CPU 统计信息(stats)
CPU 统计信息和使用情况也存储在文件中!
cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpuacct.stat
此处还定义了 CPU 份额限制。
cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpu.shares
容器内存配置的所有 Docker cgroup 都存储在:
ls /sys/fs/cgroup/memory/docker/
每个目录都根据 Docker 分配的容器 ID 进行分组。
DBID = $(docker ps --no-trunc | grep'db'| awk'{print $ 1}')WEBID = $(docker ps --no-trunc | grep'nginx'| awk'{print $ 1}')ls /sys/fs/cgroup/memory/docker/$DBID
如何配置 cgroups?
Docker 的一个属性是控制内存限制的能力。这是通过 cgroup 设置完成的。
默认情况下,容器对内存没有限制。我们可以通过 docker stats 命令查看。
docker stats db --no-stream
内存引用存储在名为 memory.limit_in_bytes
的文件中。
通过写入文件,我们可以更改进程的限制。
echo 8000000> /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes
如果您重新阅读该文件,您会发现它已被转换为 7999488.
cat /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes
再次检查 Docker Stats
时,进程的内存限制现在为 7.629M.
docker stats db --no-stream
Seccomp / AppArmor
Linux 的所有操作都是通过系统调用完成的。内核有 330 个系统调用,执行读取文件,关闭句柄和检查访问权限等操作。所有应用程序都使用这些系统调用的组合来执行所需的操作。
AppArmor 是一个应用程序定义的配置文件,描述了进程可以访问的系统部分。
可以通过 cat /proc/$DBPID/attr/current
查看分配给进程的当前 AppArmor 配置文件
Docker 的默认 AppArmor 配置文件是 docker-default(enforce)。
在 Docker 1.13 之前,它将 AppArmor 配置文件存储在 /etc/apparmor.d/docker-default 中(当 Docker 启动时被覆盖,因此用户无法修改它。在 v1.13 之后,Docker 现在在 tmpfs 中生成 docker-default,使用 apparmor_parser 将其加载到内核中,然后删除该文件。
该模板可以在 https://github.com/moby/moby/…。
Seccomp 提供限制可以进行哪些系统调用的功能,阻止诸如安装内核模块或更改文件权限等方面。
可以在 https://github.com/moby/moby/…。
分配给进程时,意味着进程将仅限于能力系统调用的子集。如果它试图呼叫被阻止的系统呼叫,则会收到错误“Operation Not Allowed”。
SecComp 的状态也在文件中定义。
cat /proc/$DBPID/status
cat /proc/$DBPID/status | grep Seccomp
标志含义为:0:禁用 1:严格 2:过滤
Capabilities
Capabilities 是关于进程或用户有权执行的操作的分组。
这些 Capabilities 可能涵盖多个系统调用或操作,例如更改系统时间或主机名。状态文件还容纳 Capabilities 标志。
进程可以尽可能多地降低 Capabilities 以确保其安全。
cat /proc/$DBPID/status | grep ^ Cap
标志存储为可以使用 capsh 解码的位掩码
capsh --decode = 00000000a80425fb
容器镜像
容器镜像是包含 tar 文件的 tar 文件。每个 tar 文件都是一个层。将所有 tar 文件提取到同一位置后,您就拥有了容器的文件系统。
可以通过 Docker 进行探索。将镜像拉到本地系统上。
docker pull redis:3.2.11-alpine
将图像导出为原始 tar 格式。
docker save redis:3.2.11-alpine> redis.tar
提取到磁盘
tar -xvf redis.tar
现在可以查看所有镜像 tar 文件。
ls
该图像还包括有关图像的元数据,例如版本信息和标签名称。
cat repositories
cat manifest.json
提取镜像将显示该镜像提供的文件。
tar -xvf da2a73e79c2ccb87834d7ce3e43d274a750177fe6527ea3f8492d08d3bb0123c / layer.tar
创建空镜像
由于镜像只是一个 tar 文件,因此可以使用以下命令创建空图像。
tar cv --files-from /dev/null | docker import - empty
通过导入 tar,将创建其他元数据。
docker images
但是,由于容器不包含任何内容,因此无法启动进程。
不使用 Dockerfile 创建映像
可以扩展先前导入 tar 文件的想法,从头开始创建整个镜像。
首先,我们将使用 BusyBox 作为基础。这将为我们提供基础 linux 命令。这被定义为 rootfs。
Docker 提供了一个下载 BusyBox rootfs 的脚本:https://github.com/moby/moby/blob/a575b0b1384b2ba89b79cbd7e770fbeb616758b3/contrib/mkimage/busybox-static
curl -LO https://raw.githubusercontent.com/moby/moby/a575b0b1384b2ba89b79cbd7e770fbeb616758b3/contrib/mkimage/busybox-static&& chmod + x busybox-static
./busybox-static busybox
运行该脚本将下载 rootfs 和主二进制文件。
ls -lha busybox
默认的 Busybox rootfs 不包含任何版本信息,所以让我们创建一个文件。
echo KatacodaPrivateBuild> busybox / release
和以前一样,目录可以转换为 tar,并作为镜像自动导入 Docker。
tar -C busybox -c。| docker import - busybox
现在可以作为容器启动。
docker run busybox cat /release
内容来源:https://www.katacoda.com/courses/container-runtimes
翻译:google 翻译
校验:xx