OCI - Open Container Initiative
·
文章目录
概述
- 开源容器方案 —— Open Container Initiative 围绕的是把一个给定的 rootfs,按照配置文件的要求启动。当软件组件满足该规范时,它就是一个标准容器。软件的调用方只要理解这个规范并遵守其原则,就能够在没有依赖的情况下运行这个软件单元。下图举例说明了基于 OCI Runtime Spec 的软件生态:

- k8s 在上述软件生态的位置如下:

- OCI 最核心规范有两个:镜像格式标准(OCI Image Spec)和容器运行标准(OCI Runtime Spec)
OCI Image Spec
- OCI 要求容器必须按照
container bundle即rootfs + config.json组织容器运行的前置依赖,目录结构如下:
bundle/
├── config.json
└── rootfs/
OCI Runtime Spec
- OCI Runtime Spec 从以下三个方面描述标准容器需要满足的条件:
- 配置
- 执行环境
- 生命周期
- 本文用 runc 举例,分析 OCI 标准,加深对容器底层实现的理解。
准备工作
下载 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
Image Spec
- OCI 要求容器运行时只依赖
container bundle,镜像簇由两个组件构成:
- config.json: 描述容器运行时预期的配置,所有容器必须满足 config.json 配置中包含的标准,不同运行时容器内部实现有所不同,但必须满足 config.json 的预期
- root filesystem: 描述容器运行所依赖镜像根文件系统,路径由 config.json 的 root->path 字段指定,不同运行时容器实现有所不同,但都围绕如何把一个 root filesystem 运行起来
config.json
- config.json 经典字段说明:
- root
指定了容器的根文件系统,它有两个属性,path 和 readonly,path 定义了根文件系统在主机上的路径,可以是绝对路径,也可以是相对路径(相对bundle目录的路径),readonly 声明该文件系统是否只读 - mount
字段指定进程的挂载点信息
root filesystem
- 标准容器基于根文件系统进一步构建自己的存储介质,满足其内部实现需要。
实验
- 下载一个学习用的小镜像 Alpine minirootfs tarball,首先使用
curl -I检查 HTTP Response 头部:
curl -sSI --max-time 10 https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/alpine-minirootfs-3.20.3-aarch64.tar.gz | head -5
HTTP/1.1 200 OK
Connection: close
Content-Length: 3947906 // 数据大小:接近 4 MB
Content-Security-Policy: script-src 'self'
Content-Type: application/octet-stream // 数据类型:二进制流
- 下载并解压
使用curl -O /path/to/image指定镜像要下载的路径
curl -sSL -o alpine-minirootfs.tar.gz https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/alpine-minirootfs-3.20.3-aarch64.tar.gz
mkdir -p bundle/rootfs & tar -xzf alpine-minirootfs.tar.gz -C bundle/rootfs && ls bundle/rootfs/
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
- 手动编辑一份 config.json 文件:
目录组织如下:
[root@oe2403-dev bundle]# tree -L 1
.
├── config.json
└── rootfs
2 directories, 1 file
[root@oe2403-dev bundle]# tree -L 2
.
├── config.json
└── rootfs
├── bin
├── dev
├── etc
├── home
├── lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
config.json 内容如下:
{
"ociVersion": "1.2.0",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs", // 指定根文件系统位置,当前目录下的 rootfs
"readonly": false
},
"hostname": "oci-demo",
"mounts": [ // 指定文件系统的挂载点
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"namespaces": [
{ "type": "pid" },
{ "type": "network" },
{ "type": "ipc" },
{ "type": "uts" },
{ "type": "mount" },
{ "type": "cgroup" }
],
"maskedPaths": [
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}
Runtime Spec
Configure
- 标准规定容器运行时必须理解 config.json,典型内容如下:
{
"process": {
"args": ["/bin/bash"] // 启动哪个 Linux process
},
"root": {
"path": "rootfs" // process 使用哪个根文件系统
},
"linux": { // 给 process 设置哪些 namespace
"namespaces": [
{"type": "pid"},
{"type": "network"},
{"type": "mount"}
]
}
}
- 从配置文件可以看出,OCI 的理解中
Container = Process + Isolation,至于使用什么方式、什么技术来隔离进程使其具备容器的特征,完全是具体运行时实现考虑的事情(runc、kata 等)
Execution environment
Lifecycle
生命周期
- 生命周期描述了容器从创建到最终停止退出的整个时间线,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
- 容器启动操作,核心实现视具体容器方案而定,但必须关联一个容器I D。
hooks
- OCI 兼容的运行时还必须设计容器生命周期中各个阶段的回调函数,供上层使用者注册自己的程序,包括如下 hook:
- Prestart:这个 hook 在 start 命令之后,用户定义的程序执行之前调用,比如在 linux 平台上,对于 runc 运行时方案,prestart hook 在容器命令空间之后被执行,这样 hook 可以有机会定制即将创建的容器。
- CreateRuntime:这个 hook 需要作为 create 操作的一部分在执行 create 操作时被调用。它的执行时间介于环境变量配置之后,改变当前所有进程/线程工作目录之前(pivot_root)
- CreateContainer:同CreateRuntime hook执行阶段相同,但必须在它之后。
- StartContainer:在用户定义的程序执行之前执行。
- Poststart:在用户定义的程序执行之后之前。
- Poststop:在容器删除的核心操作之后,删除动作返回之前执行。
状态查询
- 运行时规范规定了容器必须包含的字段如下:
- 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": ""
}
容器操作
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的运行时规范比较简单,它将容器运行过程中一些状态的定义,动作的执行规范化,以便于兼容不同的运行时实现方案。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)