Docker中容器底层实现技术cgroup和namespace
cgroup 实现资源限额, namespace 实现资源隔离
一、cgroup
1、概述
cgroup 全称 Control Group
Linux 操作系统通过 cgroup 设置进程使用 CPU、内存 和 IO 资源的限额
--cpu-shares、-m、--device-write-bps 就是在配置 cgroup
cgroup 可在 /sys/fs/cgroup 中找到它
2、启动一个容器--cpu-shares=512
docker run -it --cpu-shares 512 progrium/stress -c 1
容器的 ID:6a3c3c8f60c8
在 /sys/fs/cgroup/cpu/docker 下,Linux 为每个容器创建一个 cgroup 目录,以容器长ID 命名:6a3c3c8f60c82d9f651ccf3f17dce279905a053626a6c93d25b4d933929e4efb
ll /sys/fs/cgroup/cpu/docker/
ll /sys/fs/cgroup/cpu/docker/6a3c3c8f60c82d9f651ccf3f17dce279905a053626a6c93d25b4d933929e4efb/
cat /sys/fs/cgroup/cpu/docker/6a3c3c8f60c82d9f651ccf3f17dce279905a053626a6c93d25b4d933929e4efb/cpu.shares
目录中包含所有与 cpu 相关的 cgroup 配置
文件 cpu.shares 保存的就是 --cpu-shares 的配置,值为 512
3、内存的 cgroup 配置
/sys/fs/cgroup/memory/docker
4、Block IO 的 cgroup 配置
/sys/fs/cgroup/blkio/docker
二、namespace
在容器中,可看到文件系统、网卡等资源,这些资源看上去是容器自己的
如网卡,每个容器都会认为自己有一块独立的网卡,即使 host 上只有一块物理网卡,使得容器更像一个独立的计算机
Linux 实现这种方式的技术是 namespace
namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它
namespace 实现了容器间资源的隔离
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User
1、Mount namespace
Mount namespace 让容器看上去拥有整个文件系统
容器有自己的 / 目录,可以执行 mount 和 umount 命令,操作只在当前容器中生效,不会影响到 host 和其他容器
2、UTS namespace
UTS namespace 让容器有自己的 hostname
默认情况下,容器的 hostname 是它的短ID,可以通过 -h 或 --hostname 参数设置
docker run -h zolahost -it centos
3、IPC namespace
IPC namespace 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起
4、PID namespace
容器在 host 中以进程的形式运行
查看容器在host上的进程
ps axf
-a : 显示现行终端机下的所有进程,包括其他用户的进程
-x :通常与 a 这个参数一起使用,可列出较完整信息
-f : 用树形格式来显示进程
此次练习使用的是docker版本:
Docker version 19.03.8
从Docker 1.11开始,Docker容器运行已经不是简单的通过Docker daemon来启动,而是集成了containerd、runC等多个组件
A、Docker Daemon
作为Docker容器管理的守护进程,Docker Daemon从最初集成在docker命令中(1.11版本前),到后来的独立成单独二进制程序(1.11版本开始),其功能正在逐渐拆分细化,被分配到各个单独的模块中去
B、Containerd
containerd是容器技术标准化之后的产物,为了能够兼容OCI标准,将容器运行时及其管理功能从Docker Daemon剥离。理论上,即使不运行dockerd,也能够直接通过containerd来管理容器。(containerd本身只是一个守护进程,容器的实际运行时由runC控制)
containerd主要职责是镜像管理(镜像、元信息等)、容器执行(调用最终运行时组件执行)
containerd向上为Docker Daemon提供了gRPC接口,使得Docker Daemon屏蔽下面的结构变化,确保原有接口向下兼容。向下通过containerd-shim结合runC,使得引擎可以独立升级,避免之前Docker Daemon升级会导致所有容器不可用的问题
C、Docker、containerd和containerd-shim之间的关系
观察进程之间的关联
yum -y install psmisc
查看进程之间的父子关系
pstree -l -a -A dockerd的PID
说明:当Docker daemon启动之后,dockerd和docker-containerd进程一直存在
当启动容器之后,docker-containerd进程(即containerd组件)会创建docker-containerd-shim进程,其中的参数 容器长ID 就是要启动容器的id
docker-containerd-shim子进程,是实际在容器中运行的进程
docker-containerd-shim另一个参数,是一个和容器相关的目录ls /var/run/docker/containerd/,里面的内容有:
find / -name config.json
ls /run/containerd/io.containerd.runtime.v1.linux/moby/
ls /var/run/docker/
ls /var/run/docker/containerd/
其中包括了容器配置和标准输入、标准输出、标准错误三个管道文件
D、RunC
OCI定义了容器运行时标准,runC是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现
runC是从Docker的libcontainer中迁移而来的,实现了容器启停、资源隔离等功能
Docker默认提供了docker-runc实现,事实上,通过containerd的封装,可以在Docker Daemon启动的时候指定runc的实现
从Docker 1.11之后,Docker Daemon被分成了多个模块以适应OCI标准
拆分之后:
containerd独立负责容器运行时和生命周期(如创建、启动、停止、中止、信号处理、删除等),其他一些如镜像构建、卷管理、日志等由Docker Daemon的其他模块处理
dockerd 对应启动container(容器)的关系:
docker run 访问了docker服务提供的接口
然后由 dockerd 装载 image,拉起 container
E、查看容器自己的进程
进入到某个容器,然后 ps
docker exec -it ID bash
docker exec -it 94028ecfa080 bash
ps axf
容器中进程的 PID 不同于 host 中对应进程的 PID
容器中 PID=1 的进程当然也不是 host 的 init 进程
容器拥有自己独立的一套 PID,这就是 PID namespace 提供的功能
5、Network namespace
Network namespace 让容器拥有自己独立的网卡、IP、路由等资源
6、User namespace
User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户
在容器中创建docker01用户,不会出现在host里面
在host中创建的zola用户,不会出现在容器中
create 创建容器
run 运行容器
pause 暂停容器
unpause 取消暂停继续运行容器
stop 发送 SIGTERM 停止容器
kill 发送 SIGKILL 快速停止容器
start 启动容器
restart 重启容器
attach attach 到容器启动进程的终端
exec 在容器中启动新进程,通常使用 "-it" 参数
logs 显示容器启动进程的控制台输出,用 "-f" 持续打印
rm 从磁盘中删除容器