- 原文地址: 常用容器镜像构建工具和方案介绍
在使用 Docker 的时候一般情况下我们都会直接使用 docker build
来构建镜像,切换到 Containerd 的时候,接下来我们就来介绍下在 Containerd 容器运行时下面镜像构建的主要工具和方案。
使用 Docker 做镜像构建服务
在 Kubernetes 集群中,部分 CI/CD 流水线业务可能需要使用 Docker 来提供镜像打包服务。可通过宿主机的 Docker 实现,将 Docker 的 UNIX Socket(/var/run/docker.sock) 通过 hostPath 挂载到 CI/CD 的业务 Pod 中,之后在容器里通过 UNIX Socket 来调用宿主机上的 Docker 进行构建,这个就是之前我们使用较多的 Docker outside of Docker 方案。
该方式操作简单,比真正意义上的 Docker in Docker 更节省资源,但该方式可能会遇到以下问题:
- 无法运行在 Runtime 是 containerd 的集群中。
- 如果不加以控制,可能会覆盖掉节点上已有的镜像。
- 在需要修改 Docker Daemon 配置文件的情况下,可能会影响到其他业务。
- 在多租户的场景下并不安全,当拥有特权的 Pod 获取到 Docker 的 UNIX Socket 之后,Pod 中的容器不仅可以调用宿主机的 Docker 构建镜像、删除已有镜像或容器,甚至可以通过 docker exec 接口操作其他容器。
对于部分需要 containerd 集群,而不改变 CI/CD 业务流程仍使用 Docker 构建镜像一部分的场景,我们可以通过在原有 Pod 上添加 DinD 容器作为 Sidecar 或者使用 DaemonSet 在节点上部署专门用于构建镜像的 Docker 服务。
使用 DinD 作为 Pod 的 Sidecar
如下所示,我们的 Pod 中有一个名为
clean-ci
的容器,会该容器添加一个 Sidecar 容器(dind),配合 emptyDir,让clean-ci
容器可以通过 UNIX Socket 访问 DinD 容器:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27apiVersion: v1
kind: Pod
metadata:
name: clean-ci
spec:
containers:
- name: dind
image: 'docker:stable-dind'
command:
- dockerd
- --host=unix:///var/run/docker.sock
- --host=tcp://0.0.0.0:8000
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run
name: cache-dir
- name: clean-ci
image: 'docker:stable'
command: ["/bin/sh"]
args: ["-c", "docker info >/dev/null 2>&1; while [ $? -ne 0 ] ; do sleep 3; docker info >/dev/null 2>&1; done; docker pull library/busybox:latest; docker save -o busybox-latest.tar library/busybox:latest; docker rmi library/busybox:latest; while true; do sleep 86400; done"]
volumeMounts:
- mountPath: /var/run
name: cache-dir
volumes:
- name: cache-dir
emptyDir: {}通过上面添加的 dind 容器来提供 dockerd 服务,然后在业务构建容器中通过
emptyDir{}
来共享/var/run
目录,业务容器中的 docker 客户端就可以通过unix:///var/run/docker.sock
来与 dockerd 进行通信。
使用 DaemonSet 在每个 containerd 节点上部署 Docker
除了上面的 Sidecar 模式之外,还可以直接在 containerd 集群中通过 DaemonSet 来部署 Docker,然后业务构建的容器就和之前使用的模式一样,直接通过 hostPath 挂载宿主机的 unix:///var/run/docker.sock
文件即可。
使用以下 YAML 部署 DaemonSet。示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29apiVersion: apps/v1
kind: DaemonSet
metadata:
name: docker-ci
spec:
selector:
matchLabels:
app: docker-ci
template:
metadata:
labels:
app: docker-ci
spec:
containers:
- name: docker-ci
image: 'docker:stable-dind'
command:
- dockerd
- --host=unix:///var/run/docker.sock
- --host=tcp://0.0.0.0:8000
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run
name: host
volumes:
- name: host
hostPath:
path: /var/run上面的 DaemonSet 会在每个节点上运行一个 dockerd 服务,这其实就类似于将以前的 docker 服务放入到了 Kubernetes 集群中进行管理,然后其他的地方和之前没什么区别,甚至都不需要更改以前方式的任何东西。将业务构建 Pod 与 DaemonSet 共享同一个 hostPath,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: v1
kind: Pod
metadata:
name: clean-ci
spec:
containers:
- name: clean-ci
image: 'docker:stable'
command: ["/bin/sh"]
args: ["-c", "docker info >/dev/null 2>&1; while [ $? -ne 0 ] ; do sleep 3; docker info >/dev/null 2>&1; done; docker pull library/busybox:latest; docker save -o busybox-latest.tar library/busybox:latest; docker rmi library/busybox:latest; while true; do sleep 86400; done"]
volumeMounts:
- mountPath: /var/run
name: host
volumes:
- name: host
hostPath:
path: /var/run
使用 Kaniko 做镜像构建
Kaniko 是 Google 开源的一款容器镜像构建工具,可以在容器或 Kubernetes 集群内从 Dockerfile 构建容器镜像,Kaniko 构建容器镜像时并不依赖于 docker daemon,也不需要特权模式,而是完全在用户空间中执行 Dockerfile 中的每条命令,这使得在无法轻松或安全地运行 docker daemon 的环境下构建容器镜像成为了可能。
Kaniko 构建容器镜像时,需要使用 Dockerfile、构建上下文、以及构建成功后镜像在仓库中的存放地址。此外 Kaniko 支持多种方式将构建上下文挂载到容器中,比如可以使用本地文件夹、GCS bucket、S3 bucket 等方式,使用 GCS 或者 S3 时需要把上下文压缩为 tar.gz,kaniko 会自行在构建时解压上下文。
Kaniko executor 读取 Dockerfile 后会逐条解析 Dockerfile 内容,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个 snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中,等所有命令执行完,kaniko 会将最终镜像推送到指定的远端镜像仓库。。整个过程中,完全不依赖于 docker daemon。
如下所示我们有一个简单的 Dokerfile 示例:
1
2
3FROM alpine:latest
RUN apk add busybox-extras curl
CMD ["echo","Hello Kaniko"]然后我们可以启动一个 kaniko 容器去完成上面的镜像构建,当然也可以直接在 Kubernetes 集群中去运行,如下所示新建一个 kaniko 的 Pod 来构建上面的镜像:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29apiVersion: v1
kind: Pod
metadata:
name: kaniko
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args: ["--dockerfile=/workspace/Dockerfile",
"--context=/workspace/",
"--destination=cnych/kaniko-test:v0.0.1"]
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
- name: dockerfile
mountPath: /workspace/Dockerfile
subPath: Dockerfile
volumes:
- name: dockerfile
configMap:
name: dockerfile
- name: kaniko-secret
projected:
sources:
- secret:
name: regcred
items:
- key: .dockerconfigjson
path: config.json上面的 Pod 执行的 args 参数中,主要就是指定 kaniko 运行时需要的三个参数:
- Dockerfile、
- 构建上下文
- 以及远端镜像仓库。
推送至指定远端镜像仓库需要 credential 的支持,所以需要将 credential 以 secret 的方式挂载到
/kaniko/.docker/
这个目录下,文件名称为config.json
,内容如下:1
2
3
4
5
6
7{
"auths": {
"https://index.docker.io/v1/": {
"auth": "AbcdEdfgEdggds="
}
}
}其中 auth 的值为:
docker_registry_username:docker_registry_password
base64 编码过后的值。然后 Dockerfile 通过 Configmap 的形式挂载进去,如果构建上下文中还有其他内容也需要一同挂载进去。
关于 kaniko 的更多使用方式可以参考官方仓库:kaniko。