常用容器镜像构建工具和方案介绍
在使用 Docker 的时候一般情况下我们都会直接使用 docker build
来构建镜像,切换到 Containerd 的时候,上节我们也介绍了可以使用 nerdctl + buildkit
来构建容器镜像,除了这些方式之外,还有其他常见的镜像构建工具吗?
接下来我们就来介绍下在 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
如下所示,我们有一个名为 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: {}
通过上面添加的 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。示例如下:
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
上面的 DaemonSet 会在每个节点上运行一个 dockerd
服务,这其实就类似于将以前的 docker 服务放入到了 Kubernetes 集群中进行管理,然后其他的地方和之前没什么区别,甚至都不需要更改以前方式的任何东西。将业务构建 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
Kaniko 是 Google 开源的一款容器镜像构建工具,可以在容器或 Kubernetes 集群内从 Dockerfile 构建容器镜像,Kaniko
构建容器镜像时并不依赖于 docker daemon,也不需要特权模式,而是完全在用户空间中执行 Dockerfile 中的每条命令,这使得在无法轻松或安全地运行 docker daemon 的环境下构建容器镜像成为了可能。
![](https://filescdn.proginn.com/4ca0c1cab0432a9bab181ef73de71dfd/451532bad12da4ca958ae9114b4669f6.webp)
Kaniko 构建容器镜像时,需要使用 Dockerfile、构建上下文、以及构建成功后镜像在仓库中的存放地址。此外 Kaniko 支持多种方式将构建上下文挂载到容器中,比如可以使用本地文件夹、GCS bucket、S3 bucket 等方式,使用 GCS 或者 S3 时需要把上下文压缩为 tar.gz
,kaniko 会自行在构建时解压上下文。
Kaniko executor
读取 Dockerfile 后会逐条解析 Dockerfile 内容,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个 snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中,等所有命令执行完,kaniko 会将最终镜像推送到指定的远端镜像仓库。。整个过程中,完全不依赖于 docker daemon。
如下所示我们有一个简单的 Dokerfile 示例:
FROM alpine:latest
RUN apk add busybox-extras curl
CMD ["echo","Hello Kaniko"]
然后我们可以启动一个 kaniko 容器去完成上面的镜像构建,当然也可以直接在 Kubernetes 集群中去运行,如下所示新建一个 kaniko 的 Pod 来构建上面的镜像:
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=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
,内容如下:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "AbcdEdfgEdggds="
}
}
}
其中 auth 的值为: docker_registry_username:docker_registry_password
base64 编码过后的值。然后 Dockerfile 通过 Configmap 的形式挂载进去,如果构建上下文中还有其他内容也需要一同挂载进去。
关于 kaniko 的更多使用方式可以参考官方仓库:https://github.com/GoogleContainerTools/kaniko。
Jib
如果你是在 Java 环境下面,还可以使用 Jib 来构建镜像,Jib 也是 Google 开源的,只是是针对 Java 容器镜像构建的工具。
![](https://filescdn.proginn.com/7647b2df5e057361fb45b822cbe38031/29eb20a7212adc5e8b87e9d434f3f508.webp)
通过使用 Jib,Java 开发人员可以使用他们熟悉的 Java 工具来构建镜像。Jib 是一个快速而简单的容器镜像构建工具,它负责处理将应用程序打包到容器镜像中所需的所有步骤,它不需要你编写 Dockerfile 或安装 Docker,而且可以直接集成到 Maven 和 Gradle 中,只需要将插件添加到构建中,就可以立即将 Java 应用程序容器化。
Jib 利用了 Docker 镜像的分层机制,将其与构建系统集成,并通过以下方式优化 Java 容器镜像的构建:
简单:Jib 使用 Java 开发,并作为 Maven 或 Gradle 的一部分运行。你不需要编写 Dockerfile 或运行 Docker 守护进程,甚至无需创建包含所有依赖的大 JAR 包。因为 Jib 与 Java 构建过程紧密集成,所以它可以访问到打包应用程序所需的所有信息。 快速:Jib 利用镜像分层和缓存来实现快速、增量的构建。它读取你的构建配置,将你的应用程序组织到不同的层(依赖项、资源、类)中,并只重新构建和推送发生变更的层。在项目进行快速迭代时,Jib 只将发生变更的层(而不是整个应用程序)推送到镜像仓库来节省宝贵的构建时间。 可重现:Jib 支持根据 Maven 和 Gradle 的构建元数据进行声明式的容器镜像构建,因此,只要输入保持不变,就可以通过配置重复创建相同的镜像。
以下示例将使用 Jib 提供的 gradle 插件集成到一个 spring boot 项目的构建中,并展示 Jib 如何简单快速的构建镜像。
首先,在项目的 build.gradle
构建文件中引入 jib 插件:
buildscript{
...
dependencies {
...
classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:1.1.2"
}
}
apply plugin: 'com.google.cloud.tools.jib'
如果需要配置相关参数,可以使用下面的 gradle 配置:
jib {
from {
image = 'harbor.k8s.local/library/base:1.0'
auth {
username = '********'
password = '********'
}
}
to {
image = 'harbor.k8s.local/library/xxapp:1.0'
auth {
username = '********'
password = '********'
}
}
container {
jvmFlags = ['-Djava.security.egd=file:/dev/./urandom']
ports = ['8080']
useCurrentTimestamp = false
workingDirectory = "/app"
}
}
然后执行以下命令就可以直接触发构建生成容器镜像了:
# 构建 jib.to.image 指定的镜像,并且推送至镜像仓库
$ gradle jib
如果你还想将构建的镜像保存到本地 dockerd,则可以使用下面的命令构建:
gradle jibDockerBuild
当然还有前文我们介绍的 buildkit
可以用于镜像构建,还有一个经常和 Podman 搭配使用的 Buildah,是一个可以用于构建符合 OCI 标准容器镜像的命令行工具,有了这些工具,在构建容器镜像时已经完全可以脱离 docker daemon 了,而且这些工具都能很好的与 Kubernetes 集成,支持在容器环境下完成构建。
K8S 进阶训练营
![](https://filescdn.proginn.com/2aa5c89d00fd1fcfd554d3f5df2a0581/5784e717210e4c9bffcd2a4ad0cd84c1.webp)
![](https://filescdn.proginn.com/172c80607a93015a554a86841a9c1b66/365f1e4a364f34ea6936578186a59dd9.webp)
![](https://filescdn.proginn.com/abb31741fa4abf5afa322f6757f412db/b3d789f01f82c21ca11dd6989cfee29f.webp)
扫描二维码获取
更多云原生知识
k8s 技术圈