进阶篇:18-使用 Kaniko 在无 Docker Daemon 环境中构建镜像
在标准的 Docker 构建流程中,我们需要依赖运行中的 Docker Daemon 来执行 docker build 命令。然而,在某些环境中,特别是 Kubernetes 集群内的 CI/CD 流水线中,出于安全和权限管理的考虑,直接访问宿主机的 Docker Daemon 可能是不被允许或不推荐的。
Kaniko 是 Google 开源的一个工具,它可以在完全用户空间内,根据 Dockerfile 构建出符合标准的容器镜像,无需依赖 Docker Daemon。这使得在受限环境(如普通的 Kubernetes Pod)中构建镜像成为可能。
Kaniko 的优势:
- 无需 Docker Daemon: 解决了 CI/CD 环境中访问 Docker Socket 的安全问题。
- 无需特权: Kaniko 可以在非特权容器中运行。
- 兼容 Dockerfile: 直接使用标准的 Dockerfile 进行构建。
官方镜像:
- gcr.io/kaniko-project/executor:latest (或指定版本,如 v1.9.0): 标准执行器镜像。 建议在 CI/CD 等自动化流程中使用明确的版本号以保证构建的稳定性与可重现性。
- gcr.io/kaniko-project/executor:debug: 包含 busybox shell,方便调试。
如果你访问不到这个地址,可以通过下列方式获取:
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/kaniko-project/executor:v1.9.0
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/kaniko-project/executor:v1.9.0 gcr.io/kaniko-project/executor:v1.9.0
Kaniko 是如何工作的?
Kaniko 的核心原理是在其自身的容器环境中,一步步执行 Dockerfile 中的指令:
- 提取基础镜像文件系统: Kaniko 从指定的 Registry 拉取 Dockerfile 中 FROM 指令指定的基础镜像。
- 逐条执行指令: 它在用户空间内模拟执行 Dockerfile 中的每一条指令 (如 RUN, COPY, ADD 等)。
- 生成层快照: 每执行完一条指令,Kaniko 会在内存中创建文件系统快照,计算出与上一层快照的差异,并将这个差异打包成一个新的镜像层。
- 更新镜像元数据: 根据 Dockerfile 指令(如 ENV, EXPOSE, CMD)更新镜像的配置信息。
- 推送镜像层和配置: 将新生成的层和最终的镜像配置文件推送到目标 Registry。
整个过程完全在 Kaniko Executor 容器的用户空间内完成,不与 Docker Daemon 交互。
如何运行 Kaniko?
Kaniko 本身就是一个容器镜像 (gcr.io/kaniko-project/executor),可以通过 Docker 或在 Kubernetes Pod 中运行。
关键参数说明:
- –dockerfile
或 -f : 指定 Dockerfile 的路径(相对于构建上下文)。默认为 ./Dockerfile。 - –context
或 -c : 指定构建上下文的路径。这是 Kaniko 读取 Dockerfile 和执行 COPY/ADD 指令的根目录。支持: - 本地目录 (挂载到容器内,如 /workspace)。
- 远程 Git 仓库: git://<repo_url>[#<commit_or_branch>]。
- 远程 tar 包: http(s)😕/… 或 gs://…, s3://…。
- 默认为 /workspace。
---destination <image_name>: 或 -d <image_name>:: 必需参数,指定构建完成后要推送到的目标镜像仓库地址和标签。可以指定多个 -d 来推送多个标签。
- --cache=[true|false]: 是否启用 Kaniko 的层缓存。默认为 false。启用后,Kaniko 会尝试复用之前构建过的层。
- –cache-repo <image_name>: 指定一个远端镜像仓库用于存储和拉取 Kaniko 的层缓存。强烈建议在 CI/CD 中启用缓存并配置此项以加速构建。例如 --cache=true --cache-repo your-registry/kaniko-cache。
- --build-arg =: 传递构建参数给 Dockerfile。
- --no-push: 只构建镜像,不推送到目标仓库(用于测试或本地构建)。
- --verbosity : 设置日志级别 (如 info, debug, trace)。
场景一:在本地使用 Docker 运行 Kaniko (用于测试)
这可以在本地模拟 Kaniko 构建过程,但请注意,它仍然需要你本地安装了 Docker 来运行 Kaniko 容器本身。
# 1. 克隆示例代码仓库
git clone https://github.com/lework/ci-demo-go.git# 2. 确保 Docker 已登录到目标仓库 (会生成 ~/.docker/config.json)
docker login harbor.leops.local# 3. 运行 Kaniko executor 容器
docker run --rm \-v $(pwd)/ci-demo-go:/workspace \-v $HOME/.docker/config.json:/kaniko/.docker/config.json:ro \--add-host harbor.leops.local:192.168.77.140 \--add-host goproxy.leops.local:192.168.77.140 \gcr.io/kaniko-project/executor:v1.9.0 \--cache=true \--cache-repo=harbor.leops.local/dev/kaniko-go-cache \--context=dir:///workspace \--dockerfile=Dockerfile-kaniko \--destination=harbor.leops.local/dev/ci-demo-go:master-93f3aa7-202505100615 \--build-arg=APP=ci-demo-go \--build-arg=APP_ENV=dev \--build-arg=GIT_BRANCH=master \--build-arg=GIT_COMMIT_ID=93f3aa7
场景二:在 Kubernetes 中使用 Kaniko (主要场景)
这是 Kaniko 最典型的应用场景,通常作为 CI/CD 流水线(如 Jenkins Pipeline, Gitlab CI, Tekton)中的一个步骤,在一个 Pod 中运行。
关键步骤:
- 准备 Docker 配置文件 Secret: 将你的 config.json 文件(包含推送到目标仓库的认证信息)创建为一个 Kubernetes Secret。
# 确保你的 config.json 包含目标仓库的认证信息
kubectl create secret generic regcred \--from-file=.dockerconfigjson=$HOME/.docker/config.json \--type=kubernetes.io/dockerconfigjson
注意:
- 此命令中的 $HOME/.docker/config.json 指向你本地的 Docker 配置文件。请确保它包含了推送到目标镜像仓库以及拉取基础镜像(如果基础镜像是私有的)所需的认证信息。
- --type=kubernetes.io/dockerconfigjson 参数非常重要,它指定了 Secret 的类型,Kubernetes 及相关工具(如 Kaniko)会据此正确解析和使用认证信息。
- 如果 Kaniko 需要访问多个私有镜像仓库(例如,用于基础镜像、推送目标镜像、Kaniko 缓存仓库),请确保 config.json 中包含了所有这些仓库的认证凭据。
- 准备构建上下文:
- 方法一 (Git Repo): 直接让 Kaniko 从 Git 拉取。
- 对于公共仓库,可以直接使用 git:// 或 https:// URL。
- 对于私有 Git 仓库,你需要配置认证。常见方法包括:
- HTTPS 与 Personal Access Token (PAT): 将 PAT 嵌入 URL 中,例如 github.com/lework/ci-demo-go.git。请注意,直接在 YAML 中硬编码 PAT 存在安全风险,推荐的做法是通过 Kubernetes Secret 将 PAT 作为环境变量注入到 Kaniko Pod 中,然后在 --context 参数中引用该环境变量。
- SSH 密钥: 将 SSH 私钥存储为一个 Kubernetes Secret,并将其挂载到 Kaniko Pod 的 /root/.ssh/ (或其他适当位置)。然后,配置 Kaniko 使用此 SSH 密钥进行 Git 操作。这通常需要在 Kaniko 的参数中指定 GIT_SSH_COMMAND 或确保 Git 客户端能自动找到密钥。
- 方法二 (Volume): 通过 CI/CD 工具先将代码 checkout 到一个持久卷声明 (PVC) 或 emptyDir Volume 中,然后将此 Volume 挂载给 Kaniko Pod 的构建上下文路径 (默认为 /workspace)。
- 方法三 (Init Container): 使用 Init Container (例如 alpine/git 镜像) 在 Kaniko Pod 启动前将代码克隆到一个共享的 Volume (如 emptyDir) 中。
- 创建 Kubernetes Job 或 Pod:运行 Kaniko 构建任务通常使用 Kubernetes Job,因为它代表一个批处理任务,完成后即结束。示例 Kubernetes Job (kaniko-job.yaml):
apiVersion: batch/v1
kind: Job
metadata:name: kaniko-image-build
spec:template:spec:containers:- name: kanikoimage: gcr.io/kaniko-project/executor:v1.9.0args:- '--context=git://github.com/lework/ci-demo-go.git#refs/heads/master' # 构建上下文:从 Git 仓库拉取- '--dockerfile=Dockerfile' # Dockerfile 在仓库中的路径- '--destination=harbor.leops.local/dev/ci-demo-go:master-93f3aa7-202505100615' # 目标镜像仓库和标签- '--cache=true' # 启用 Kaniko 层缓存- '--cache-repo=harbor.leops.local/dev/kaniko-go-cache' # 指定用于存储缓存的远端镜像仓库 (推荐)- '--verbosity=info' # 日志级别- '--build-arg=APP=ci-demo-go' # 构建参数- '--build-arg=APP_ENV=dev'- '--build-arg=GIT_BRANCH=master'- '--build-arg=GIT_COMMIT_ID=93f3aa7'resources:requests:memory: "1Gi"cpu: "500m"limits:memory: "4Gi"cpu: "2"volumeMounts:# 挂载包含 Docker config.json 的 Secret 到 Kaniko 期望的路径-name: docker-configmountPath: /kaniko/.docker/readOnly: true # 建议以只读方式挂载凭证restartPolicy: Never # Job 完成或失败后不重启 Podvolumes:# 定义 Volume 来引用之前创建的 regcred Secret-name: docker-configsecret:secretName: regcred # Secret 的名称items:# 将 Secret 中的 .dockerconfigjson 数据项挂载为名为 config.json 的文件- key: .dockerconfigjsonpath: config.jsonhostAliases:# 添加额外的主机名解析 (可选)- ip: 192.168.77.140hostnames:- harbor.leops.local- goproxy.leops.local
backoffLimit: 0 # Job 失败时不进行重试
运行 Job: kubectl apply -f kaniko-job.yaml这个 Job 会创建一个 Pod 来运行 Kaniko Executor。Kaniko 将:
- 从指定的 Git 仓库 (github.com/lework/ci-demo-go.git 的 master 分支) 拉取源代码作为构建上下文。
- 根据仓库中 Dockerfile-kaniko 文件的定义来构建镜像。
- 启用层缓存,并将缓存层推送到 harbor.leops.local/dev/kaniko-go-cache 镜像仓库。
- 最后,将构建完成的镜像推送到 harbor.leops.local/dev/ci-demo-go:master-93f3aa7-202505100615。
与 Dockerfile 的兼容性
Kaniko 致力于尽可能广泛地支持标准的 Dockerfile 指令。然而,由于 Kaniko 在用户空间内模拟 Docker Daemon 的行为,与原生 Docker 构建相比,存在一些已知的不兼容或行为差异。用户在使用 Kaniko 时应注意以下几点:
- COPY --link: Kaniko 不支持 COPY 指令的 --link 参数。在 Docker 构建中,–link (需要 BuildKit 支持) 允许在多阶段构建中将前一阶段的文件或目录以硬链接的方式复制到当前阶段,从而在某些情况下优化构建速度和减少层大小。如果你的 Dockerfile 中使用了 COPY --link,在 Kaniko 构建时会遇到错误或该参数会被忽略。你需要将其修改为普通的 COPY 指令。
- 例如,将 COPY --from=builder --link /app /app 修改为 COPY --from=builder /app /app。
- 部分 RUN 指令的行为差异:
- 特权操作: 依赖于 Docker Daemon 特权的操作(例如,挂载某些类型的文件系统、修改内核参数等)在 Kaniko 中无法执行,因为 Kaniko 在非特权容器中运行。
- 网络相关: 某些依赖于 Docker Daemon 网络配置的 RUN 指令行为可能有所不同。
- 多阶段构建中的缓存: 虽然 Kaniko 支持多阶段构建和层缓存 (–cache=true),但其缓存命中逻辑和效率可能与 Docker BuildKit 的缓存有所不同。
- BuildKit 特有功能: Docker BuildKit 引入了许多高级功能 (如 RUN --mount=type=cache, RUN --mount=type=secret 等)。Kaniko 对这些 BuildKit 特有语法的支持可能不完整或正在开发中。建议查阅 Kaniko 的最新官方文档以获取当前的支持状态。
建议:
- 查阅官方文档: Kaniko 的 GitHub 仓库和官方文档是获取最新兼容性信息和已知问题的最佳来源。
- 测试: 在将复杂的 Dockerfile 迁移到 Kaniko 构建之前,进行充分的测试以确保其行为符合预期。
- 简化 Dockerfile: 尽量使用 Dockerfile 的标准和常用指令,避免过于复杂或依赖特定 Docker Daemon/BuildKit 行为的写法,这样可以提高与 Kaniko 的兼容性。
尽管存在这些差异,Kaniko 仍然能够成功构建绝大多数标准的 Dockerfile。对于中小企业常见的应用场景,其兼容性通常是足够的。
总结
Kaniko 提供了一种在无法或不便使用 Docker Daemon 的环境中(尤其是 Kubernetes)构建容器镜像的有效方法。通过理解其工作原理、配置好构建上下文、凭证和缓存,你可以在 CI/CD 流水线中安全、高效地自动化镜像构建过程。