Docker容器运行已经不是简单的通过Docker daemon来启动,而是集成了containerd、runC等多个组件。Docker服务启动之后,我们也可以看见系统上启动了dockerd、docker-containerd等进程,本文主要介绍新版Docker(1.11以后)每个部分的功能和作用。
下面就来介绍下独立分拆出来的其他几个模块。
Containerd
containerd是容器技术标准化之后的产物,为了能够兼容OCI标准,将容器运行时及其管理功能从Docker Daemon剥离。理论上,即使不运行dockerd,也能够直接通过containerd来管理容器。(当然,containerd本身也只是一个守护进程,容器的实际运行时由后面介绍的runC控制。)
最近,Docker刚刚宣布开源containerd。从其项目介绍页面可以看出,containerd主要职责是镜像管理(镜像、元信息等)、容器执行(调用最终运行时组件执行)。
containerd向上为Docker Daemon提供了gRPC接口,使得Docker Daemon屏蔽下面的结构变化,确保原有接口向下兼容。向下通过containerd-shim结合runC,使得引擎可以独立升级,避免之前Docker Daemon升级会导致所有容器不可用的问题。
Docker、containerd和containerd-shim之间的关系,可以通过启动一个Docker容器,观察进程之间的关联。首先启动一个容器,
[root@jettoloader k3s-ansible-master]# yum install psmisc
[root@jettoloader k3s-ansible-master]# ps -ef | grep docker
root 30589 1 0 2021 ? 02:30:19 /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store consul://172.16.10.21:8500 --cluster-advertise 172.16.10.21:2375
pid=30589
[root@jettoloader k3s-ansible-master]# pstree -l -a -A 30589
dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store consul://172.16.10.21:8500 --cluster-advertise 172.16.10.21:2375
|-containerd --config /var/run/docker/containerd/containerd.toml --log-level info
| |-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/304fdae4c7094db09a646a95b85917ae05582491d762d6edd29f986e7b6c324a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
| | |-tini -- /entrypoint.sh
| | | `-entrypoint.sh /entrypoint.sh
| | | |-sleep 1
| | | |-start_hbase.sh /opt/bin/start_hbase.sh
| | | | `-java -Dproc_master -XX:onOutOfMemoryError=kill -9 %p -XX:+UseConcMarkSweepGC -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ReservedCodeCacheSize=256m -Dhbase.log.dir=/opt/hbase/bin/../logs -Dhbase.log.file=hbase.log -Dhbase.home.dir=/opt/hbase/bin/.. -Dhbase.id.str= -Dhbase.root.logger=INFO,console -Dhbase.security.logger=INFO,RFAS org.apache.hadoop.hbase.master.HMaster start
| | | | `-235*[{java}]
| | | `-start_opentsdb. /opt/bin/start_opentsdb.sh
| | | `-java -XX:+UseConcMarkSweepGC -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -enableassertions -enablesystemassertions -classpath /usr/local/share/opentsdb/*.jar:/usr/local/share/opentsdb:/usr/local/share/opentsdb/bin:/usr/local/share/opentsdb/lib/asm-4.0.jar:/usr/local/share/opentsdb/lib/async-1.4.0.jar:/usr/local/share/opentsdb/lib/asynchbase-1.8.2.jar:/usr/local/share/opentsdb/lib/commons-jexl-2.1.1.jar:/usr/local/share/opentsdb/lib/commons-logging-1.1.1.jar:/usr/local/share/opentsdb/lib/commons-math3-3.4.1.jar:/usr/local/share/opentsdb/lib/guava-18.0.jar:/usr/local/share/opentsdb/lib/jackson-annotations-2.9.5.jar:/usr/local/share/opentsdb/lib/jackson-core-2.9.5.jar:/usr/local/share/opentsdb/lib/jackson-databind-2.9.5.jar:/usr/local/share/opentsdb/lib/javacc-6.1.2.jar:/usr/local/share/opentsdb/lib/jgrapht-core-0.9.1.jar:/usr/local/share/opentsdb/lib/kryo-2.21.1.jar:/usr/local/share/opentsdb/lib/log4j-over-slf4j-1.7.7.jar:/usr/local/share/opentsdb/lib/logback-classic-1.0.13.jar:/usr/local/share/opentsdb/lib/logback-core-1.0.13.jar:/usr/local/share/opentsdb/lib/minlog-1.2.jar:/usr/local/share/opentsdb/lib/netty-3.10.6.Final.jar:/usr/local/share/opentsdb/lib/protobuf-java-2.5.0.jar:/usr/local/share/opentsdb/lib/reflectasm-1.07-shaded.jar:/usr/local/share/opentsdb/lib/slf4j-api-1.7.7.jar:/usr/local/share/opentsdb/lib/tsdb-2.4.0.jar:/usr/local/share/opentsdb/lib/zookeeper-3.4.6.jar:/etc/opentsdb net.opentsdb.tools.TSDMain --port=4242 --staticroot=/usr/local/share/opentsdb/static --cachedir=/tmp --auto-metric
| | | `-41*[{java}]
| | `-9*[{containerd-shim}]
| `-15*[{containerd}]
|-docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 4242 -container-ip 172.17.0.2 -container-port 4242
| `-7*[{docker-proxy}]
`-15*[{dockerd}]
虽然pstree命令截断了命令,但我们还是能够看出,当Docker daemon启动之后,dockerd和docker-containerd进程一直存在。当启动容器之后,docker-containerd进程(也是这里介绍的containerd组件)会创建docker-containerd-shim进程,其中的参数304fdae4c7094db09a646a95b85917ae05582491d762d6edd29f986e7b6c324a就是要启动容器的id。最后docker-containerd-shim子进程,已经是实际在容器中运行的进程(既sleep 1000)。
docker-containerd-shim另一个参数,是一个和容器相关的目录[root@jettoloader k3s-ansible-master]# ls -al /var/run/docker/containerd/304fdae4c7094db09a646a95b85917ae05582491d762d6edd29f986e7b6c324a/,里面的内容有:
[root@jettoloader k3s-ansible-master]# ls -al /var/run/docker/containerd/304fdae4c7094db09a646a95b85917ae05582491d762d6edd29f986e7b6c324a/ total 4 drwxr-xr-x 2 root root 100 Jan 17 15:15 . drwx------ 4 root root 160 Nov 15 17:09 .. prwx------ 1 root root 0 Jan 17 15:54 init-stderr prwx------ 1 root root 0 Nov 15 17:10 init-stdout -rw------- 1 root root 4096 Jan 17 15:15 .init-stdout.sw
其中包括了容器配置和标准输入、标准输出、标准错误三个管道文件。
RunC
OCI定义了容器运行时标准,runC是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。
runC是从Docker的libcontainer中迁移而来的,实现了容器启停、资源隔离等功能。Docker默认提供了docker-runc实现,事实上,通过containerd的封装,可以在Docker Daemon启动的时候指定runc的实现。
我们可以通过启动Docker Daemon时增加--add-runtime参数来选择其他的runC现。下面就让我们看下这几个模块如何工作。
容器导出到rootfs, 此时,标准包所需的容器数据已经准备完毕,接下来我们需要创建配置文件:
[root@jettoloader aaa]# mkdir rootfs [root@jettoloader aaa]# docker export $(docker create petergrace/opentsdb-docker:latest) | tar -C rootfs -xvf - [root@jettoloader aaa]# ls rootfs/ bin data dev entrypoint.sh etc home lib media mnt opentsdb-plugins opt proc root run sbin srv sys tini tmp usr var
此时会生成一个名为config.json的配置文件,该文件和Docker容器的配置文件类似,主要包含容器挂载信息、平台信息、进程信息等容器启动依赖的所有数据。
最后,可以通过runc命令来启动容器:
[root@jettoloader aaa]# runc spec [root@jettoloader aaa]# ls config.json rootfs
最后,可以通过runc命令来启动容器,执行之后,我们可以看见容器已经启动:
[root@jettoloader aaa]# runc run busybox
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
8 root 0:00 ps aux
[root@jettoloader k3s-ansible-master]# runc list
ID PID STATUS BUNDLE CREATED OWNER
busybox 26539 running /root/work/aaa 2022-01-17T07:50:18.917471987Z root
注意,runc必须使用root权限启动。
此时,事实上已经可以不依赖Docker本身,如果系统上安装了runc包,即可运行容器。
当然,也可以使用docker-runc命令来启动容器:
从这里可以看到标准化的重要性。
总结
从Docker 1.11之后,Docker Daemon被分成了多个模块以适应OCI标准。拆分之后,结构分成了以下几个部分。
其中,containerd独立负责容器运行时和生命周期(如创建、启动、停止、中止、信号处理、删除等),其他一些如镜像构建、卷管理、日志等由Docker Daemon的其他模块处理。
Docker的模块块拥抱了开放标准,希望通过OCI的标准化,容器技术能够有很快的发展。
导入
从网络地址导入
docker import https://example.com/container.tar
从本地导入
docker import /path/to/exampleContainer.tgz
基于当前系统制作docker镜像
1:通过tar 备份目录 tar -cvpf /home/exampleContainer.tar --directory=/ --exclude=proc --exclude=sys --exclude=dev --exclude=run / 2:导入镜像 cat exampleContainer.tar | docker import - exampleContainer
1.export命令导出的tar文件略小于save命令导出的
2.export命令是从容器(container)中导出tar文件,而save命令则是从镜像(images)中导出
3.基于第二点,export导出的文件再import回去时,无法保留镜像所有历史(即每一层layer信息,不熟悉的可以去看Dockerfile),不能进行回滚操作;而save是依据镜像来的,所以导入时可以完整保留下每一层layer信息。如下图所示,nginx:latest是save导出load导入的,nginx:imp是export导出import导入的。
建议
可以依据具体使用场景来选择命令
若是只想备份images,使用save、load即可若是在启动容器后,容器内容有变化,需要备份,则使用export、import
Docker、Containerd、RunC...:你应该知道的所有_高效开发运维的博客-CSDN博客



