一起“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情。
前言 很抱歉各位,最近实在是太忙了,我本期写的文档和上一篇可以说是不相关的,跨度有点大;主要是介绍了我这两天在研究
CICD在containerd的容器集群中推镜像的解决方案,下面Dong哥为大家详细的介绍一下吧。
背景
在 K8S 集群中,某些 CI/CD 流水线业务可能需要使用 docker 来提供镜像打包服务,我们之前的方案是利用宿主机上的 docker(这个解决方案治标不治本);具体做法是把宿主机上 docker 的 unix socket (/var/run/docker.sock) 作为 hostPath 挂载到 CI/CD 的业务 Pod 中,之后在容器里通过 unix socket 来调用宿主机上的 docker 进行构建。这种方法固然简单,照比真正意义上的 Docker in Docker 甚至还能节省资源,但这种做法可能会遇到一些问题:
- 无法运行在
runtime是containerd的集群中(当然我们目前的containerd的集群中还装了docker客户端) - 如果不加以控制可能会覆盖掉节点上已有的镜像
- 如果在需要修改
docker daemon配置文件的情况下,可能会影响到其他业务 - 这种方式并不安全,特别是在多租户的场景下,当有特权的
Pod拿到了docker的 unix socket 之后,Pod中的容器不只可以调用宿主机的docker构建镜像,甚至可以删除已有镜像或容器,更有甚者可以通过 docker exec 接口操作其他容器
像第1个问题相信大多数人都会提到,因为 Kubernetes 在官方博客中宣布要在 1.22 版本之后弃用 docker,这之后可能会把业务转投到 containerd 上。对于某些想用 containerd 集群,而不想改变 CI/CD 业务流程仍使用 docker 做构建构成一部分的话,可以通过在原有 Pod 上添加 dind容器作为 sidecar 或者使用 DaemonSet 在节点上部署专门用来构建镜像的 docker 服务;说到这里我调研并整理了一些解决方案,希望和大家一起探讨下:
解决方案
使用 DinD 作为 Pod 的 Sidecar
下面的例子中是给 clean-ci 容器添加一个 sidecar,并通过 emptyDir 让 clean-ci 容器可以通过 unix socket 访问到 DinD 容器:
apiVersion: 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: {}
集成到Jekins的时候可以吧上述yaml内容生成好然后加到pipeline流水线的脚本中。
DaemonSet 在每个 containerd 节点上部署 Docker
这种方式比较简单,不过这种方式比较耗费资源,CI/CD流水线的话应该Pod运行完就要停掉的避免占用集群资源;使用时直接在 containerd 集群中下发 DaemonSet 即可(挂载 hostPath),当然不想污染节点上 /var/run 路径的同学们可以指定其他路径,使用下面的 yaml 部署来 DaemonSet:
apiVersion: 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
然后让业务 Pod 与 DaemonSet 共享同一个 hostPath,示例:
apiVersion: 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
上述两种方案归根结底都是Docker in Docker的方案,而且据说Docker in Docker的方案会引来很多问、也不安全。Kaniko是谷歌开源的一款用来构建容器镜像的工具。与docker不同,Kaniko 并不依赖于docker daemon进程,完全是在用户空间根据Dockerfile的内容逐行执行命令来构建镜像,这就使得在一些无法获取 docker daemon 进程的环境下也能够构建镜像,比如在标准的Kubernetes Cluster上。
Kaniko会先提取基础镜像(Dockerfile FROM 之后的镜像)的文件系统,然后根据Dockerfile中所描述的,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中。等所有命令执行完,Kaniko会将最终镜像推送到指定的远端镜像仓库。
Kaniko 构建镜像Demo
准备好Dockerfile:
FROM alpine:latest
MAINTAINER <devops008@sina.com xiaomage>
RUN apk add busybox-extras curl
CMD ["echo","Hello DevOps"]
在K8s集群上面创建一个pod,这里由于是谷歌的镜像,所以这个镜像直接拉是拉不下来的,可以配置代理或者去国内的一些中转仓库下载,yaml文件如下:
apiVersion: v1
kind: Pod
metadata:
name: kaniko
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args: ["--dockerfile=/workspace/Dockerfile",
"--context=/workspace/",
"--destination=dllhb/kaniko-test:v0.4"]
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
- name: dockerfile
mountPath: /workspace/Dockerfile
subPath: Dockerfile
restartPolicy: Never
volumes:
- name: dockerfile
configMap:
name: dockerfile
- name: kaniko-secret
projected:
sources:
- secret:
name: regcred
items:
- key: .dockerconfigjson
path: config.json
参数说明:
- args
- Dockerfile(--dockerfile)
- 上下文(--context)
- 远端镜像仓库(--destination)
推送至指定远端镜像仓库需要
credential的支持,所以需要将credential以secret的方式挂载到/kaniko/.docker/这个目录下,文件名称为config.json,内容如下:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "AbcdEdfgEdggds="
}
}
}
auth的值为: echo"docker_registry_username:docker_registry_password"|base64
然后创建pod,查看状态,并看log:
$ kubectl -n kaniko apply -f kaniko.yaml
pod/kaniko created
$ kubectl -n kaniko get pods
NAME READY STATUS RESTARTS AGE
kaniko 1/1 Running 0 21s
$ kubectl -n kaniko logs -f kaniko
1 INFO[0000] Resolved base name alpine:latest to alpine:latest
2 INFO[0000] Resolved base name alpine:latest to alpine:latest
3 INFO[0000] Downloading base image alpine:latest
4 INFO[0001] Error while retrieving image from cache: getting fileinfo: stat /cachesha256:e4355b66995c96b4b468159fc5c7e3540fcef961189ca13fee877798649f51a: no such file or directory
5 INFO[0001] Downloading base image alpine:latest
6 INFO[0001] Built cross stage deps: map[]
7 INFO[0001] Downloading base image alpine:latest
8 INFO[0001] Error while retrieving image from cache: getting fileinfo: stat /cachesha256:e4355b66995c96b4b468159fc5c7e3540fcef961189ca13fee877798649f51a: no such file or directory
9 INFO[0001] Downloading base image alpine:latest
10 WARN[0002] maintainer is deprecated, skipping
11 INFO[0002] Unpacking rootfs as cmd RUN apk add busybox-extrascurl requires it.
12 INFO[0002] Taking snapshot of full filesystem...
13 INFO[0002] RUN apk add busybox-extras curl
14 INFO[0002] cmd: /bin/sh
15 INFO[0002] args: [-c apk add busybox-extras curl]
16 fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64APKINDEX.tar.gz
17 fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64APKINDEX.tar.gz
18 (1/5) Installing busybox-extras (1.30.1-r3)
19 Executing busybox-extras-1.30.1-r3.post-install
20 (2/5) Installing ca-certificates (20190108-r0)
21 (3/5) Installing nghttp2-libs (1.39.2-r0)
22 (4/5) Installing libcurl (7.66.0-r0)
23 (5/5) Installing curl (7.66.0-r0)
24 Executing busybox-1.30.1-r2.trigger
25 Executing ca-certificates-20190108-r0.trigger
26 OK: 7 MiB in 19 packages
27 INFO[0003] Taking snapshot of full filesystem...
28 INFO[0003] CMD ["echo","Hello DevOps"]
用新生成的镜像创建个容器来测试一下:
$ docker run --rm dllhb/kaniko-test:v0.5
Unable to find image 'dllhb/kaniko-test:v0.5' locally
v0.5: Pulling from dllhb/kaniko-test
89d9c30c1d48: Already exists
2beaee649353: Pull complete
Digest: sha256:461233efe63a8f16e8630ab92a31795fb0932d2910669131d9973f258070cca5
Status: Downloaded newer image for dllhb/kaniko-test:v0.5
Hello DevOps
可以看到输出了 Hello DevOps 这和Dockerfile所写是一致的。证明容器镜像已经构建完毕并已推送至远端仓库。
总结
从长期看来Kaniko的方案是最为可行的,使用起来简单方便,所需依赖项也是最小的。