Docker 容器与镜像本质
关于 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