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 负责冻结进程,并将作为一系列文件存储在硬盘上。并负责使用这些文件还原这个被冻结的进程。