Lework Study hard, improve every day.

Docker 组件 RunC

2019-10-24
lework
本文 6742 字,阅读全文约需 20 分钟

RunC

OCI 定义了容器运行时标准,runC 是 Docker 按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。

runC 是从 Docker 的 libcontainer 中迁移而来的,实现了容器启停、资源隔离等功能。Docker 默认提供了 docker-runc 实现,事实上,通过 containerd 的封装,可以在 Docker Daemon 启动的时候指定 runc 的实现。

安装runC

本文的演示环境为 Centos 7.4 x64。

可以直接下载编译好的执行文件,也可以下载源代码进行编译

wget https://github.com/opencontainers/runc/releases/download/v1.0.0-rc9/runc.amd64
mv runc.amd64 /usr/local/sbin/
chmod +x /usr/local/sbin/runc.amd64

编译步骤

yum -y install libseccomp-devel
mkdir -p $HOME/go/src/github.com
go get github.com/opencontainers/runc
cd $HOME/go/src/github.com/opencontainers/runc
git checkout v1.0.0-rc9
make
make install

准备 OCI bundle

RunC 是运行容器的运行时,它负责利用符合标准的文件等资源运行容器,但是它不包含 docker 那样的镜像管理功能。所以要用 runC 运行容器,我们先得准备好容器的文件系统。所谓的 OCI bundle 就是指容器的文件系统和一个 config.json 文件。有了容器的文件系统后我们可以通过 runc spec 命令来生成 config.json 文件。使用 docker 可轻松的生成容器的文件系统,因为 runC 本来就是 docker 贡献给社区的嘛! 下面我们准备一个运行 busybox 容器所需的文件系统:

docker pull busybox
mkdir -p /tmp/mycontainer/rootfs
cd /tmp/mycontainer
docker export $(docker create busybox) | tar -C rootfs -xvf -

现在 rootfs 目录下就是 busybox 镜像的文件系统,然后生成 config.json 文件:

runc.amd64 spec
ll
总用量 4
-rw-r--r--  1 root root 2652 10月 30 10:19 config.json
drwxr-xr-x 12 root root  137 10月 30 10:19 rootfs

config.json的具体信息,请看官方文档

理解容器状态转移

在运行 busybox 容器前让我们先来看看 OCI 都定义了哪几种容器状态,以及这些状态是如何转移的。先看容器的状态:

  • creating:使用 create 命令创建容器,这个过程称为创建中。
  • created:容器已经创建出来,但是还没有运行,表示镜像文件和配置没有错误,容器能够在当前平台上运行。
  • running:容器里面的进程处于运行状态,正在执行用户设定的任务。
  • stopped:容器运行完成,或者运行出错,或者 stop 命令之后,容器处于暂停状态。这个状态,容器还有很多信息保存在平台中,并没有完全被删除。
  • paused:暂停容器中的所有进程,可以使用 resume 命令恢复这些进程的执行。

下图则是对容器不同状态间转移的一个粗略描述:

              +------------+
              |  Creating  |
              +-----+------+
                    |
                    | Successd
                    v
             +------+------+
             |   Created   |
             +-------------+
             |             |
Start failed |             |Start successd
             |             |
   +---------+             +----------+ pause  +--------+
   | stopped +-------------+ Runnging +<------>+ paused |
   +---------+ error,exit  +----------+ resume +--------+
               crash,kill

运行容器

需要在容器目录下,使用run命令即可运行容器

[root@node132 mycontainer]# runc.amd64 run mycontainerid
/ # 

使用exit就可退出容器。当容器中的命令退出后容器随即被删除。

使用busybox容器,默认会进入sh会话中,如果想要在后台运行命令,则需要修改config.json里的配置,将 "terminal"修改为 false 和增加args 参数 ["sleep", "3000"]

修改完配置后创建容器

runc.amd64 create mycontainerid

查看我们创建的容器

runc.amd64 list
ID              PID         STATUS      BUNDLE             CREATED                         OWNER
mycontainerid   57811       created     /tmp/mycontainer   2019-10-30T02:50:27.19749345Z   root

查看容器的状态

runc.amd64 state  mycontainerid      
{
  "ociVersion": "1.0.1-dev",
  "id": "mycontainerid",
  "pid": 57811,
  "status": "created",
  "bundle": "/tmp/mycontainer",
  "rootfs": "/tmp/mycontainer/rootfs",
  "created": "2019-10-30T02:50:27.19749345Z",
  "owner": ""
}

当通过 create 成功创建了容器后,容器的状态就是 “created”。

查看容器内运行的进程

runc.amd64 ps mycontainerid    
UID         PID   PPID  C STIME TTY          TIME CMD
root      57811      1  0 10:50 ?        00:00:00 runc.amd64 init

可以看到容器中有个init的进程在运行,而不是我们定义的命令。

使用start命令执行容器中定义的任务

runc.amd64 start  mycontainerid
runc.amd64 ps mycontainerid    
UID         PID   PPID  C STIME TTY          TIME CMD
root      57811      1  0 10:50 ?        00:00:00 sleep 3000

这个时候就能看到我们在config.json文件定义的sleep进程在运行

使用 exec 命令在容器中执行命令

runc.amd64 exec mycontainerid ps
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 3000
    6 root      0:00 ps

使用pause命令暂停容器中的所有进程

runc.amd64 pause mycontainerid     
runc.amd64 state mycontainerid         
{
  "ociVersion": "1.0.1-dev",
  "id": "mycontainerid",
  "pid": 57811,
  "status": "paused",
  "bundle": "/tmp/mycontainer",
  "rootfs": "/tmp/mycontainer/rootfs",
  "created": "2019-10-30T02:50:27.19749345Z",
  "owner": ""
}

执行 pause 命令后,容器的状态由 running 变成了 paused。然后我们再通过 resume 命令恢复容器中进程的执行:

runc.amd64 resume mycontainerid      
runc.amd64 state mycontainerid        
{
  "ociVersion": "1.0.1-dev",
  "id": "mycontainerid",
  "pid": 57811,
  "status": "running",
  "bundle": "/tmp/mycontainer",
  "rootfs": "/tmp/mycontainer/rootfs",
  "created": "2019-10-30T02:50:27.19749345Z",
  "owner": ""
}

使用 events 命令获取容器的资源使用情况

runc.amd64 events mycontainerid  
{"type":"stats","id":"mycontainerid","data":{"cpu":{"usage":{"total":14177218,"percpu":[14177218,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"kernel":10000000,"user":0},"throttling":{}},"memory":{"usage":{"limit":9223372036854771712,"usage":512000,"max":1716224,"failcnt":0},"swap":{"limit":9223372036854771712,"usage":520192,"max":1724416,"failcnt":0},"kernel":{"limit":9223372036854771712,"usage":479232,"max":638976,"failcnt":0},"kernelTCP":{"limit":9223372036854771712,"failcnt":0},"raw":{"active_anon":32768,"active_file":0,"cache":0,"dirty":0,"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":9223372036854771712,"inactive_anon":0,"inactive_file":0,"mapped_file":0,"pgfault":495,"pgmajfault":0,"pgpgin":297,"pgpgout":301,"rss":139264,"rss_huge":0,"shmem":0,"swap":0,"total_active_anon":32768,"total_active_file":0,"total_cache":0,"total_dirty":0,"total_inactive_anon":0,"total_inactive_file":0,"total_mapped_file":0,"total_pgfault":495,"total_pgmajfault":0,"total_pgpgin":297,"total_pgpgout":301,"total_rss":139264,"total_rss_huge":0,"total_shmem":0,"total_swap":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0}},"pids":{"current":1},"blkio":{},"hugetlb":{"1GB":{"failcnt":0},"2MB":{"failcnt":0}},"intel_rdt":{}}}

默认5s更新一次数据

使用 kill 命令停止容器中的任务

runc.amd64 kill  mycontainerid 
[root@node132 mycontainer]# runc.amd64 state  mycontainerid
{
  "ociVersion": "1.0.1-dev",
  "id": "mycontainerid",
  "pid": 57811,
  "status": "running",
  "bundle": "/tmp/mycontainer",
  "rootfs": "/tmp/mycontainer/rootfs",
  "created": "2019-10-30T02:50:27.19749345Z",
  "owner": ""
}
runc.amd64 kill  mycontainerid 9
runc.amd64 state  mycontainerid 
{
  "ociVersion": "1.0.1-dev",
  "id": "mycontainerid",
  "pid": 0,
  "status": "stopped",
  "bundle": "/tmp/mycontainer",
  "rootfs": "/tmp/mycontainer/rootfs",
  "created": "2019-10-30T02:50:27.19749345Z",
  "owner": ""
}
默认是优雅的结束容器中的进程,如果想强制终止,就要使用信号 9

删除容器

runc.amd64 delete mycontainerid

rootless containers

前面我们运行的所有命令都是以 root 权限执行的。能不能以普通用户的权限运行容器呢?答案是可以的,并被称为 rootless。

要想以 rootless 的方式运行容器,需要系统内核开启 “User Namespaces”

在centos7中可以使用 echo 28633 > /proc/sys/user/max_user_namespaces 设置名称空间的数量

我们在生成容器的配置文件时就为 spec 命令指定 rootless 参数:

runc.amd64 spec --rootless

并且在运行容器时通过 –root 参数指定一个存放容器状态的路径:

runc.amd64 --root /tmp/runc run mycontainerid

Service

可以使用 systemd 来管理容器

[Unit]
Description=Start My Container

[Service]
Type=forking
ExecStart=/usr/local/sbin/runc.amd64 run -d --pid-file /run/mycontainerid.pid mycontainerid
ExecStopPost=/usr/local/sbin/runc.amd64 delete mycontainerid
WorkingDirectory=/mycontainer
PIDFile=/run/mycontainerid.pid

[Install]
WantedBy=multi-user.target

Docker

我们可以通过启动 Docker Daemon 时增加--add-runtime参数来选择其他的 runC 现。例如:

docker daemon --add-runtime "custom=/usr/local/sbin/runc.amd64"

也可以通过配置文件定义

cat /etc/docker/daemon.json 
{
    "default-runtime": "myRunC",
    "runtimes": {
     "myRunC": {
       "path": "/usr/local/sbin/runc.amd64"
     }
    }
}

查看docker的runtime

docker info | grep Runtime  
Runtimes: myRunC runc
Default Runtime: myRunC

容器的热迁移操作

RunC 支持容器的热迁移操作,所谓热迁移就是将一个容器进行 checkpoint 操作,并获得一系列文件,使用这一系列文件可以在本机或者其他主机上进行容器的 restore 工作。这也是 checkpoint 和 restore 两个命令存在的原因。热迁移属于比较复杂的操作,目前 runC 使用了 CRIU 作为热迁移的工具。RunC 主要是调用 CRIU(Checkpoint and Restore in Userspace)来完成热迁移操作。CIRU 负责冻结进程,并将作为一系列文件存储在硬盘上。并负责使用这些文件还原这个被冻结的进程。

原文地址 https://lework.github.io/2019/10/24/runC/

Similar Posts

Comments