runc —— 从入门到放弃
本文以分析OCI Runtime Spec为主,过程中使用runc容器方案加以举例,希望在理解OCI规范之后,能够更加轻松地理解runc的命令行接口与设计原则。
·
文章目录
概述
- 开源容器方案运行时规范(OCI Runtime Spec)主要目的是定义容器运行时相关规范,包括容器运行前的配置,容器生命周期和容器执行环境,当一种容器方案遵照OCI Runtime Spec实现了以上所有东西,我们认为它就是OCI兼容的。目前OCI兼容的容器方案有runc,kata。
- 本文以分析OCI Runtime Spec为主,过程中使用runc容器方案加以举例,希望在理解OCI规范之后,能够更加轻松地理解runc的命令行接口与设计原则。
准备工作
go
- runc,containerd,docker都是基于go语言开发,因此需要安装go语言用于编译runc源码,命令如下:
yum install -y golang
- 检查是否安装成功,命令如下:
[root@hy runc]# go version
go version go1.15 linux/amd64
- go工具有默认的工作目录
GOPATH
,当使用build和get命令时,GOPATH
环境变量决定了go工具的查找路径,因此如果需要更改环境为个人的工作目录,首先通过如下命令查看go的环境变量:
go env
- 然后修改GOPATH环境变量,将如下内容写入~/.bashrc中:
export GOPATH="/path/to/mygopath"
runc
- 有两种方法下载runc的代码,一种是通过go工具下载,如下:
go get github.com/opencontainers/runc
- 以上命令会将runc的源码自动下载到
$GOPATH/src/github.com/opencontainers/runc
目录下,另一种方法是直接通过git工具克隆代码,如下:
cd $GOPATH/src/github.com/opencontainers
git clone https://github.com/opencontainers/runc
- 以上两种方法最终下载的代码都在
$GOPATH/src/github.com/opencontainers/runc
目录下,编译并安装runc,命令如下:
cd $GOPATH/src/github.com/opencontainers/runc
make
make install
- 检查runc工具是否安装成功:
[root@hy ~]# whereis runc
runc: /usr/local/sbin/runc
[root@hy ~]# runc -v
runc version 1.0.0-rc92+dev
commit: 33faa5d0e2404aaf4ceb1abbfe36c5135179d32f
spec: 1.0.2-dev
go: go1.15
libseccomp: 2.3.1
docker
- 理论上,我们是不需要安装上层的docker工具用于学习runc的,但为了方便下载容器镜像,这里也把docker工具准备好,docker安装如下:
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum makecache -c /etc/yum.repos.d/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
- 启动docker服务,并确认docker 命令可用:
systemctl start docker
docker version
Client: Docker Engine - Community
Version: 19.03.13
API version: 1.40
Go version: go1.13.15
Git commit: 4484c46d9d
Built: Wed Sep 16 17:03:45 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.13
API version: 1.40 (minimum version 1.12)
Go version: go1.13.15
Git commit: 4484c46d9d
Built: Wed Sep 16 17:02:21 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.3.7
GitCommit: 8fba4e9a7d01810a393d5d25a3621dc101981175
runc:
Version: 1.0.0-rc92+dev
GitCommit: 33faa5d0e2404aaf4ceb1abbfe36c5135179d32f
docker-init:
Version: 0.18.0
GitCommit: fec3683
- 下载一个常用的docker镜像为测试runc命令行接口做准备,比如busybox:
docker create busybox
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 6858809bf669 2 weeks ago 1.23MB
- 将容器镜像导出,输出到文件:
[root@PC-Hyman containerd]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c7e726e36d1a busybox "sh" 26 hours ago Created stoic_noyce
[root@PC-Hyman containerd]# docker export c7e726e36d1a -o busybox.tar
- 将busybox.tar解压,作为runc运行容器的根文件系统:
tar busybox.tar -C busyboxfs
状态查询
- 运行时规范规定了容器必须包含的字段如下:
- ociVersion:描述容器遵循的OCI规范版本
- id:描述容器ID,用于区分同主机上的容器。对于跨主机的容器,id字段可以相同
- status:容器的生命周期状态,可以是creating,created,running,stopped,这些状态在生命周期中定义
- pid:容器进程ID,在linux平台上,进程ID是必选的。它是容器内部运行的应用程序对应进程的ID
- bundle:容器的bundle目录,bundle目录主要存放容器运行时的配置文件和容器的根文件系统
annotations字段是可选的,存放容器的注释信息。容器的状态信息除以上字段以外,具体的OCI兼容容器方案还可以定义其它字段,视具体的实现而定。
- 容器的状态可以通过state操作来查询,runc的查询命令如下:
[root@PC-Hyman ~]# runc state 1234
{
"ociVersion": "1.0.2-dev",
"id": "1234",
"pid": 267441,
"status": "created",
"bundle": "/home/ubuntuVM/containerd/demo/runc",
"rootfs": "/home/ubuntuVM/containerd/demo/runc/busyboxfs",
"created": "2020-09-24T07:58:46.28138205Z",
"owner": ""
}
生命周期
- 生命周期描述了容器从创建到最终停止退出的整个时间线,OCI兼容运行时方案必须实现以下生命周期过程中定义的动作。
create
- 运行时的创建命令(create)需要关联到bundle路径和唯一的容器ID。也就是说,创建命令的核心动作必须包含创建容器ID的动作和保存bundle路径的动作。
environment
- 运行时环境在启动容器时必须按照配置文件config.json中配置好环境,包括环境变量,挂载点等等。在创建资源的时不允许用户定义的程序运行。当环境好之后,所有对config.json文件的更新都不能影响容器。举例如下:
- config.json中配置环境变量和挂载点:
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"mounts": [
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
}
]
- 在运行时启动容器之前,必须按照此配置在容器内部准备好这些资源,容器内部看到的信息如下:
/ # env
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
/ # mount |grep pts
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
start
- 容器启动操作,核心实现视具体容器方案而定,但必须关联一个容器ID。
delete
- 容器启动操作,核心实现视具体容器方案而定,但必须关联一个容器ID。
hooks
- OCI兼容的运行时还必须设计容器生命周期中各个阶段的回调函数,供上层使用者注册自己的程序,包括如下hook:
- Prestart:这个hook在start命令之后,用户定义的程序执行之前调用,比如在linux平台上,对于runc运行时方案,prestart hook在容器命令空间之后被执行,这样hook可以有机会定制即将创建的容器。
- CreateRuntime:这个hook需要作为create操作的一部分在执行create操作时被调用。它的执行时间介于环境变量配置之后,改变当前所有进程/线程工作目录之前(pivot_root)
- CreateContainer:同CreateRuntime hook执行阶段相同,但必须在它之后。
- StartContainer:在用户定义的程序执行之前执行。
- Poststart:在用户定义的程序执行之后之前。
- Poststop:在容器删除的核心操作之后,删除动作返回之前执行。
容器操作
create
create <container-id> <path-to-bundle>
- 容器创建操作需要指定容器ID和bundle目录,对于runc命令来说,bundle目录是可选的,如果不指定,默认去当前目录下查找。/home/ubuntuVM/containerd/demo/runc目录下放置了一个busybox容器的配置文件config.json以及这个容器的根文件系统
[root@PC-Hyman runc]# ls /home/ubuntuVM/containerd/demo/runc
busyboxfs config.json
- 指定容器的id和bundle目录,创建该容器:
[root@PC-Hyman demo]# pwd
/home/ubuntuVM/containerd/demo
[root@PC-Hyman demo]# ls
containerd-1.4.1 main main.go runc tools v1.4.1.zip
- 传入容器ID创建容器,由于当前目录没有配置文件,报错
[root@PC-Hyman demo]# runc create 12345
ERRO[0000] JSON specification file config.json not found
- 指定bundle目录创建容器
[root@PC-Hyman demo]# runc create 12345 -b /home/ubuntuVM/containerd/demo/runc
[root@PC-Hyman demo]# runc list
ID PID STATUS BUNDLE CREATED OWNER
12345 262814 created /home/ubuntuVM/containerd/demo/runc 2020-09-24T02:23:23.388415943Z root
start
start <container-id>
- 容器创建之后,其状态时created,通过start操作可以启动容器,启动后容器状态变为running,如果容器内部启动的应用程序是需要长久运行的,比如交互式程序sh,那么容器会一直运行直到被kill掉。如果内部应用程序是运行一段时间就结束的普通程序,那容器状态也会随之变为stopped。
kill
kill <container-id> <signal>
- 对于一个一直运行的容器,可以通过kill命令向它内部的应用发信号,通知其结束
delete
delete <container-id>
- 删除一个容器是创建容器的逆操作,它将容器的ID和配置信息容runc得管理中删除
容器配置
- OCI的配置规范定义了实现容器操作所需要的所有元数据,即容器操作的配置。它可以是json格式,可以是go格式,规范不限制。以下以几个关键的配置字段举例。
root
- root字段指定了容器的根文件系统,它有两个属性,path和readonly,path定义了根文件系统在主机上的路径,可以是绝对路径,也可以是相对路径(相对bundle目录的路径),readonly声明该文件系统是否只读,如下:
"root": {
"path": "busyboxfs", /* 相对bundle的路径 */
"readonly": true /* 根文件系统只读 */
},
mount
- mount字段指定进程的挂载点信息,如下:
"mounts": [
{
"destination": "/proc", /* 对应挂载点:proc on /proc type proc (rw,relatime) */
"type": "proc",
"source": "proc"
},
{
"destination": "/dev", /* 对应挂载点:tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755) */
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
....
}
总结
- 总体来看,OCI的运行时规范比较简单,它将容器运行过程中一些状态的定义,动作的执行规范化,以便于兼容不同的运行时实现方案。
更多推荐
已为社区贡献1条内容
所有评论(0)