深入Docker容器运行时(一)

你也许对Docker命令很熟悉,但是你最好还是阅读下本文。

Docker 用的有多普及本文就不赘述了。今天只谈docker,不谈OCI,不谈CRI,不谈kubelet。由简入深。

1. 背景

自从 2013 年 docker 发布之后,docker 项目逐渐成为了一个庞然大物。为了能够降低项目维护的成本,内部代码能够回馈社区,他们于 2014 年开源了 libcontainer,docker 公司将 libcontainer 的实现移动到 runC 并捐赠给了 OCI。在 2016 年,docker 开源并将 containerd 捐赠给了 CNCF。

2. Docker 的内部逻辑

现代 docker 启动一个标准化容器需要经历这样的流程:
在这里插入图片描述

我们实际看下是不是这样。

  1. 首先我们先看下Docker版本:

    [root@VM_0_12_centos lmxia]# docker -v
    Docker version 18.09.9, build 039a7df9ba
    
  2. 看下docker 的服务状态:

    [root@VM_0_12_centos lmxia]# systemctl status docker
    ● docker.service - Docker Application Container Engine
       Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: disabled)
       Active: active (running) since 五 2020-05-08 10:52:00 CST; 6h ago
         Docs: https://docs.docker.com
     Main PID: 16196 (dockerd)
        Tasks: 39
       Memory: 893.9M
       CGroup: /system.slice/docker.service
       ├─16196 /usr/bin/dockerd
       ├─16203 containerd --config /var/run/docker/containerd/containerd.toml --log-level warn
    

    可以看到,docker服务的后台驻留进程是PID为16196Dockerd。我在主机上启动了一个docker 容器,python3。我们用这个容器做一个观察。

  3. 查看Dockerd的进程树(部分):

    [root@VM_0_12_centos lmxia]# pstree -up 16196
    dockerd(16196)─┬─containerd(16203)─┬─containerd-shim(22785)─┬─python3(22803)
                   │                   │                        ├─{containerd-shim}(22786)
                   │                   │                        ├─{containerd-shim}(22787)
                   │                   │                        ├─{containerd-shim}(22788)
                   │                   │                        ├─{containerd-shim}(22789)
                   │                   │                        ├─{containerd-shim}(22790)
                   │                   │                        ├─{containerd-shim}(22791)
                   │                   │                        ├─{containerd-shim}(22792)
                   │                   │                        ├─{containerd-shim}(22793)
                   │                   │                        ├─{containerd-shim}(22885)
                   │                   │                        └─{containerd-shim}(22891)
                   │                   ├─{containerd}(16204)
                   │                   ├─{containerd}(16205)
                   │                   ├─{containerd}(16206)
                   │                   ├─{containerd}(16207)
    
  4. 接下来我们再找找这些二进制,docker cli 和 runc:

    [root@VM_0_12_centos lmxia]# which docker
    /usr/bin/docker
    [root@VM_0_12_centos lmxia]# which runc
    /usr/bin/runc
    
  5. 我们也看下,安装docker的步骤:

    安装docker

    $ sudo yum install docker-ce docker-ce-cli containerd.io
    

    其中,docker-ce 是dockerd, docker-ce-cli 就是docker 命令的二进制,contained.io,包含了剩下的组件。

至此我们全部找到了内容1中所提到的组件,其中根据进程树来看,调用关系的确和图中描述的一致:

containerd 囊括了单机运行一个容器运行时所需要的一切:执行,分发,监控,网络,构建,日志等。为了能够支持多种 OCI Runtime,containerd 内部使用 containerd-shim,每启动一个容器都会创建一个新的 containerd-shim 进程,指定容器 ID,Bundle 目录,运行时的二进制(比如 runc)。

3.Docker 的背景知识

  1. 根文件系统

    根文件系统是内核启动时所mount的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

    根文件系统首先是一种文件系统,它不仅仅可以用来存储文件,提供系统启动和运行时所必须的目录和关键性配置文件,还可以为其他文件系统提供挂载。“/” 就是根文件系统的挂载点。

    我们简单回顾下Linux 系统的启动过程:

    • BIOS自检以及加载MBR(512字节的最后两个字节0x55aa标示的可引导分区)
    • GRUB 操作系统引导程序,grub.conf是它的配置文件,里面需要指明,操作系统所在的磁盘号、分区号,内核镜像的存放路径,比如:/boot/kernel-**.gz.
    • 内核启动,加载根文件系统
    • 不同的runlevel,init N。
  2. Cgroup技术

    Cgroup是linux内核支持的一种资源控制机制,我用人话来解释。

    容器说白了就是一个进程,进程里的跑的东西,谁也管不着。不做特殊操作的情况下,进程可以吃掉系统里的所有资源,比如CPU跑满全部的核,耗尽全部的内存。现在我们希望限制这个进程的资源使用,Cgroup就是干这个的。那么它是怎么实现的呢?我们先了解下Cgroup的基本概念。

    • 任务(task)

      就是进程

    • 控制组(cgroup)

      就是一组按照某种标准划分的进程,这个标准,指的是,你希望如何限制你的这组进程去使用资源。

    • 层级(hierarchy)

      就是控制组组成的树状结构,具有继承特性。

    • 子系统(subsysystem)

      一个子系统就是一类资源控制器,比如 cpu 子系统就是控制 cpu 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用。

    相互关系

    • 每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup,此 cgroup 在创建层级时自动创建,后面在该层级中创建的 cgroup 都是此 cgroup 的后代)的初始成员;

    • 一个子系统最多只能附加到一个层级;

    • 一个层级可以附加多个子系统;

    • 一个任务可以是多个 cgroup 的成员,但是这些 cgroup 必须在不同的层级;

    • 系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的 cgroup。

    有些一头雾水对么?别急,找一个linux主机去操作下就全明白了。

在这里插入图片描述

补充一句话,cgroup的开发团队,用VFS(虚拟文件系统)的方式,巧妙的表达了层级,并给我一个灵活的方式去操作task 和 cgroup。我们去实操一下。

​ 1. 先看下你当前挂载的cgroup 文件系统

[root@VM_0_12_centos rootfs]# mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

​ 我们看到/sys/fs/cgroup/cpu,cpuacct这个目录,就属于两个子系统(cpu 和 cpuacct)附加到一个层级的例子,每个子系统也必须最多只能挂载到一个层级。linux系统习惯把不同的子系统,单独存放。cpu 和cpuacct,我们可以看到,其实就是两个软连接,link 到真实的同一个目录下。

[root@VM_0_12_centos ~]# ls -al /sys/fs/cgroup/
总用量 0
drwxr-xr-x 13 root root 340 4月  29 11:35 .
drwxr-xr-x  6 root root   0 5月  11 16:04 ..
drwxr-xr-x  6 root root   0 4月  29 11:35 blkio
lrwxrwxrwx  1 root root  11 4月  29 11:35 cpu -> cpu,cpuacct
lrwxrwxrwx  1 root root  11 4月  29 11:35 cpuacct -> cpu,cpuacct
drwxr-xr-x  7 root root   0 4月  29 11:35 cpu,cpuacct
drwxr-xr-x  5 root root   0 5月  11 11:10 cpuset
drwxr-xr-x  6 root root   0 4月  29 11:35 devices
drwxr-xr-x  5 root root   0 5月  11 11:10 freezer
drwxr-xr-x  5 root root   0 5月  11 11:10 hugetlb
drwxr-xr-x  6 root root   0 4月  29 11:35 memory
lrwxrwxrwx  1 root root  16 4月  29 11:35 net_cls -> net_cls,net_prio
drwxr-xr-x  5 root root   0 5月  11 11:10 net_cls,net_prio
lrwxrwxrwx  1 root root  16 4月  29 11:35 net_prio -> net_cls,net_prio
drwxr-xr-x  5 root root   0 5月  11 11:10 perf_event
drwxr-xr-x  6 root root   0 4月  29 11:35 pids
drwxr-xr-x  6 root root   0 4月  29 11:35 systemd

接下来我们用一个进程来展示下Cgroup如何帮助我们限制资源。

[root@VM_0_12_centos test]# while true;do :;done &
[1] 14546
[root@VM_0_12_centos test]# top
top - 15:46:10 up 12 days,  4:11,  1 user,  load average: 0.54, 0.20, 0.11
Tasks:  91 total,   2 running,  89 sleeping,   0 stopped,   0 zombie
%Cpu(s): 51.5 us,  0.8 sy,  0.0 ni, 47.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3880228 total,   116576 free,   256652 used,  3507000 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  3333744 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
14546 root      20   0  116844   1888    268 R 100.0  0.0   0:32.57 bash

我们写了一个死循环,进程号是14546。CPU使用率100%。我们这个进程不在任何控制组里,所以没有限制他的cpu 使用。

接下来,我们在cpu这个子系统所 attach到的层级 /sys/fs/cgroup/cpu 里创建一个cgroup出来,用来限制下这个进程的cpu使用。

[root@VM_0_12_centos lmxia]# cd /sys/fs/cgroup/cpu
[root@VM_0_12_centos cpu]# mkdir test
[root@VM_0_12_centos cpu]# ls test/
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks

默认已经帮我们继承了关于父cgroup的内容,这里继承过来的cpu的时间片配额为-1,表示不限制的意思。

[root@VM_0_12_centos test]# cat cpu.cfs_quota_us
-1
[root@VM_0_12_centos cpu]# echo 50000 > /sys/fs/cgroup/cpu/test/cpu.cfs_quota_us
[root@VM_0_12_centos test]# cat cpu.cfs_quota_us
50000
[root@VM_0_12_centos test]# cat cpu.cfs_period_us
100000
[root@VM_0_12_centos test]# echo 14546 >> /sys/fs/cgroup/cpu/test/tasks
[root@VM_0_12_centos test]# top
top - 15:46:56 up 12 days,  4:11,  1 user,  load average: 1.11, 0.40, 0.18
Tasks:  89 total,   2 running,  87 sleeping,   0 stopped,   0 zombie
%Cpu(s): 25.5 us,  0.3 sy,  0.0 ni, 74.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3880228 total,   118784 free,   254284 used,  3507160 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  3336112 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
14546 root      20   0  116844   1888    268 R  50.2  0.0   1:12.91 bash

我们向这个cgroup里修改下配置,并且把14546这个进程号写入tasks文件里,这个时候发现这个进程所能使用的cpu马上被限制到了50%(50000 / 100000)。

Docker 做资源隔离也是这个思路。我们创建的容器都在这个目录下,可以看到docker目录,和我们自己的test目录,其中我们docker run 起来的容器都在docker目录下。根据容器的ID分不同的目录,原理是一样的。

[root@VM_0_12_centos cpu,cpuacct]# ls -al
总用量 0
drwxr-xr-x  7 root root   0 4月  29 11:35 .
drwxr-xr-x 13 root root 340 4月  29 11:35 ..
-rw-r--r--  1 root root   0 4月  29 11:35 cgroup.clone_children
--w--w--w-  1 root root   0 4月  29 11:35 cgroup.event_control
-rw-r--r--  1 root root   0 4月  29 11:35 cgroup.procs
-r--r--r--  1 root root   0 4月  29 11:35 cgroup.sane_behavior
-r--r--r--  1 root root   0 4月  29 11:35 cpuacct.stat
-rw-r--r--  1 root root   0 4月  29 11:35 cpuacct.usage
-r--r--r--  1 root root   0 4月  29 11:35 cpuacct.usage_percpu
-rw-r--r--  1 root root   0 4月  29 11:35 cpu.cfs_period_us
-rw-r--r--  1 root root   0 4月  29 11:35 cpu.cfs_quota_us
-rw-r--r--  1 root root   0 4月  29 11:35 cpu.rt_period_us
-rw-r--r--  1 root root   0 4月  29 11:35 cpu.rt_runtime_us
-rw-r--r--  1 root root   0 4月  29 11:35 cpu.shares
-r--r--r--  1 root root   0 4月  29 11:35 cpu.stat
drwxr-xr-x  3 root root   0 5月  11 14:43 docker
drwxr-xr-x  4 root root   0 4月  29 11:46 kubepods
-rw-r--r--  1 root root   0 4月  29 11:35 notify_on_release
-rw-r--r--  1 root root   0 4月  29 11:35 release_agent
drwxr-xr-x 58 root root   0 5月  11 14:43 system.slice
-rw-r--r--  1 root root   0 4月  29 11:35 tasks
drwxr-xr-x  2 root root   0 5月  11 15:42 test
  1. 通过Runc直接部署容器:

    • 创建容器的根文件系统

      mkdir -p ~/hello/rootfs && cd ~/hello
      docker export $(docker create busybox) | tar -C rootfs -xvf -
      

      那么在rootfs目录下,可以看到一个很常见的“根文件系统”结构:

      [root@VM_0_12_centos rootfs]# ls -al
      总用量 56
      drwxr-xr-x 12 root   root   4096 5月   9 15:15 .
      drwxr-xr-x  3 root   root   4096 5月  11 17:50 ..
      drwxr-xr-x  2 root   root  12288 4月  14 09:10 bin
      drwxr-xr-x  4 root   root   4096 5月   9 15:15 dev
      -rwxr-xr-x  1 root   root      0 5月   9 15:15 .dockerenv
      drwxr-xr-x  3 root   root   4096 5月   9 15:15 etc
      drwxr-xr-x  2 nobody 65534  4096 4月  14 09:10 home
      drwxr-xr-x  2 root   root   4096 5月   9 15:15 proc
      drwx------  2 root   root   4096 4月  14 09:10 root
      drwxr-xr-x  2 root   root   4096 5月   9 15:15 sys
      drwxrwxrwt  2 root   root   4096 4月  14 09:10 tmp
      drwxr-xr-x  3 root   root   4096 4月  14 09:10 usr
      drwxr-xr-x  4 root   root   4096 4月  14 09:10 var	
      
    • 接着我们利用 runc spec 产生默认的config.json,这是一个容器的启动配置文件,并启动它。

      [root@VM_0_12_centos hello]# runc spec
      [root@VM_0_12_centos hello]# runc run xlm
      / # ls
      bin  dev  etc  home  proc  root  sys  tmp  usr  var
      
    • 查看容器列表,执行一个exec命令

      [root@VM_0_12_centos ~]# runc list
      ID    PID     STATUS    BUNDLE              CREATED                          OWNER
      xlm   19251   running   /root/mycontainer   2020-05-11T09:50:07.882651059Z   root
      [root@VM_0_12_centos mycontainer]# runc exec xlm top
      Mem: 3742884K used, 137344K free, 652K shrd, 147732K buff, 3115792K cached
      CPU:  0.0% usr  0.0% sys  0.0% nic  100% idle  0.0% io  0.0% irq  0.0% sirq
      Load average: 0.02 0.07 0.07 2/183 10
      PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
      1     0 root     S     1300  0.0   1  0.0 sh
      6     0 root     R     1292  0.0   0  0.0 top
      

      runc exec xlm top 命令,不是一个容器的init 进程,所以只要给这个进程切换到xlm这个容器id所在的进程namespace即可。具体的代码部分的详细解释,我在下一篇文章里讲解。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值