1. daemon-less 镜像构建工具
1.1 什么是 daemon-less 镜像构建工具
在 CICD 流程中,经常会涉及镜像构建,常规的做法是使用 Docker in Docker 或者 Docker out of Docker 进行构建。详情可以参考文档:如何在 Docker 中使用 Docker
实际上,为了避免垄断,促进行业发展,基于 Docker 的镜像格式,早就指定了统一的 OCI 镜像格式规范。也就是说,只需要通过 Dockerfile 得到一个符合 OCI 规范的镜像即可,并没有强制要求谁来做这件事。
daemon-less 的构建工具,可以不依赖于 Docker Daemon 进行构建镜像。这在 CICD 场景下,具有重要的意义。同时,Kubernetes 正在摆脱 Docker 的影响,构建更加开放的架构生态,CICD 需要与 Docker Daemon 解耦。
1.2 daemon-less 的优势
目前,实现 CRI 和 OCI 接口的运行时组件越来越多,例如 cri-o、containerd。这些运行时管理工具,并没有类似 /var/run/docker.sock 的文件用于挂载服务。daemon-less 的构建方式能够对接这些工具。
这些 daemon-less 工具,通常运行在 userspace ,而不是以 root 特权运行,提供更加安全的运行时。
通过挂载 /var/run/docker.sock 的方式,使用 Docker Daemon 构建镜像,是一种集中的构建方式。镜像的构建主要交给节点上的 Docker Daemon 来完成。而如果能直接将 Dockerfile 转换成镜像,更加适应 Kubernetes、Serverless 基础设施,构建规模和效率将得到提高。
1.3 相关的开源项目
- Kaniko, Google 主导
- Buildah, Red Hat 主导
- Img, Jessie Frazelle 发起
从 Star 量上看,Buildah、Img 目前是 3K+,而 Kaniko 达到 7K+,下面以 Kaniko 为例行文。
2. Kaniko 的工作原理
如上图是 Kaniko 的工作原理图。Kaniko 执行器从 Dockerfile 构建镜像,并将其推送到镜像仓库。主要分为以下几步:
- 根据 Dockerfile 中 FROM 描述,解压基础镜像的文件系统
- 执行 Dockerfile 中的每条命令,在用户空间建立文件快照
- 将变更的文件层添加到基础镜像中,更新镜像的元数据
- 推送镜像
已知的问题
- 不支持构建 Windows 镜像
- kaniko 命令只能在官方镜像中运行,不支持其他 Docker 镜像
- 不支持 Registry V1 接口
3. Kaniko Demo
3.1 测试 Demo 选择
这里选择的是 https://github.com/traefik/whoami 项目。访问该服务之后,接口会返回访问者的相关信息。
对项目的要求是,通过 Dockerfile 能够直接编译得到镜像。一共有两个验证点:
3.2 在 Docker 上运行 Kaniko
1 2 3 4 5 6 7 8 9 10 11
| $ export AUTH=$(echo -n YOUR_USERNAME:YOUR_PASSWORD | base64 )
$ cat > config.json <<-EOF { "auths": { "https://index.docker.io/v1/": { "auth": "${AUTH}" } } } EOF
|
1 2 3 4 5
| $ docker run \ --interactive -v `pwd`/config.json:/kaniko/.docker/config.json gcr.io/kaniko-project/executor:latest \ --context git://github.com/traefik/whoami \ --dockerfile Dockerfile \ --destination=shaowenchen/kaniko-demo:v1
|
参数说明:
1 2 3
| - context, 构建需要的上下文。支持多种格式,S3、本地目录、标准输入、Git 仓库等 - dockerfile, Dockerfile 路径 - destination, 构建后推送的镜像地址
|
在生产环境,可以配置缓存,用于加速镜像构建。
执行上一步的命令之后,可以看到如下输出:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| Enumerating objects: 17, done. Counting objects: 100% (17/17), done. Compressing objects: 100% (13/13), done. Total 157 (delta 3), reused 12 (delta 3), pack-reused 140 INFO[0004] Resolved base name golang:1-alpine to builder INFO[0004] Retrieving image manifest golang:1-alpine INFO[0004] Retrieving image golang:1-alpine INFO[0007] Retrieving image manifest golang:1-alpine INFO[0007] Retrieving image golang:1-alpine INFO[0010] No base image, nothing to extract INFO[0010] Built cross stage deps: map[0:[/usr/share/zoneinfo /etc/ssl/certs/ca-certificates.crt /go/whoami/whoami]] INFO[0010] Retrieving image manifest golang:1-alpine INFO[0010] Retrieving image golang:1-alpine INFO[0012] Retrieving image manifest golang:1-alpine INFO[0012] Retrieving image golang:1-alpine INFO[0015] Executing 0 build triggers INFO[0015] Unpacking rootfs as cmd RUN apk --no-cache --no-progress add git ca-certificates tzdata make && update-ca-certificates && rm -rf /var/cache/apk/* requires it. INFO[0035] RUN apk --no-cache --no-progress add git ca-certificates tzdata make && update-ca-certificates && rm -rf /var/cache/apk/* INFO[0035] Taking snapshot of full filesystem... INFO[0041] cmd: /bin/sh INFO[0041] args: [-c apk --no-cache --no-progress add git ca-certificates tzdata make && update-ca-certificates && rm -rf /var/cache/apk/*] INFO[0041] Running: [/bin/sh -c apk --no-cache --no-progress add git ca-certificates tzdata make && update-ca-certificates && rm -rf /var/cache/apk/*] fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz (1/7) Installing nghttp2-libs (1.41.0-r0) (2/7) Installing libcurl (7.69.1-r2) (3/7) Installing expat (2.2.9-r1) (4/7) Installing pcre2 (10.35-r0) (5/7) Installing git (2.26.2-r0) (6/7) Installing make (4.3-r0) (7/7) Installing tzdata (2020c-r1) Executing busybox-1.31.1-r19.trigger OK: 25 MiB in 22 packages WARNING: ca-certificates.crt does not contain exactly one certificate or CRL: skipping INFO[0042] Taking snapshot of full filesystem... INFO[0045] WORKDIR /go/whoami INFO[0045] cmd: workdir INFO[0045] Changed working directory to /go/whoami INFO[0045] Creating directory /go/whoami INFO[0045] Taking snapshot of files... INFO[0045] COPY go.mod . INFO[0045] Taking snapshot of files... INFO[0045] COPY go.sum . INFO[0045] Taking snapshot of files... INFO[0045] RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download INFO[0045] cmd: /bin/sh INFO[0045] args: [-c GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download] INFO[0045] Running: [/bin/sh -c GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download] INFO[0045] Taking snapshot of full filesystem... INFO[0047] COPY . . INFO[0047] Taking snapshot of files... INFO[0047] RUN make build INFO[0047] cmd: /bin/sh INFO[0047] args: [-c make build] INFO[0047] Running: [/bin/sh -c make build] CGO_ENABLED=0 go build -a --trimpath --installsuffix cgo --ldflags="-s" -o whoami INFO[0058] Taking snapshot of full filesystem... INFO[0062] Saving file usr/share/zoneinfo for later use INFO[0062] Saving file etc/ssl/certs/ca-certificates.crt for later use INFO[0062] Saving file go/whoami/whoami for later use INFO[0062] Deleting filesystem... INFO[0063] No base image, nothing to extract INFO[0063] Executing 0 build triggers INFO[0063] Unpacking rootfs as cmd COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo requires it. INFO[0063] COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo INFO[0063] Taking snapshot of files... INFO[0063] COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ INFO[0063] Taking snapshot of files... INFO[0063] COPY --from=builder /go/whoami/whoami . INFO[0063] Taking snapshot of files... INFO[0063] ENTRYPOINT ["/whoami"] INFO[0063] EXPOSE 80 INFO[0063] cmd: EXPOSE INFO[0063] Adding exposed port: 80/tcp
|
最后正常退出,构建并推送镜像成功。
1
| $ docker images|grep kaniko-demo
|
执行完命令,没有任何输出,符合预期。构建之后的镜像,直接被推送到了远程 Registry。
在 DockerHub 页面可以查看到推送的镜像:
执行命令:
1
| $ docker run -d -p 8011:80 shaowenchen/kaniko-demo:v1
|
验证服务是否正常:
1 2 3 4 5 6 7 8 9 10
| $ curl localhost:8011
Hostname: 6dd22f1e4100 IP: 127.0.0.1 IP: 172.17.0.2 RemoteAddr: 172.17.0.1:40940 GET / HTTP/1.1 Host: localhost:8011 User-Agent: curl/7.29.0 Accept: */*
|
3.3 在 Kubernetes 上运行 Kaniko
- 查看 Kubernetes 版本 - v1.17.9
对于不同版本的 Kubernetes,下面有些命令会有所差异,需要自行适配。
1 2 3 4
| $ kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:18:16Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:10:45Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
|
1
| $ kubectl create ns kaniko-demo
|
1 2 3 4 5
| $ kubectl -n kaniko-demo create secret docker-registry kaniko-secret \ --docker-server=https://index.docker.io/v1/ \ --docker-username=YOUR_USERNAME \ --docker-password=YOUR_PASSWORD \ --docker-email=mail@chenshaowen.com
|
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
| $ cat > kaniko-builder.yaml <<-EOF apiVersion: v1 kind: Pod metadata: name: kaniko namespace: kaniko-demo spec: containers: - name: kaniko image: gcr.io/kaniko-project/executor:latest args: - "--dockerfile=Dockerfile" - "--context=git://github.com/traefik/whoami" - "--destination=shaowenchen/kaniko-demo:v2" volumeMounts: - name: kaniko-secret mountPath: /kaniko/.docker/ restartPolicy: Never volumes: - name: kaniko-secret secret: secretName: kaniko-secret items: - key: .dockerconfigjson path: config.json EOF
|
1
| $ kubectl apply -f kaniko-builder.yaml
|
1
| $ kubectl -n kaniko-demo logs kaniko
|
日志内容与直接在 Docker 中运行类似,这里就不重复贴出。
1
| $ kubectl -n kaniko-demo run kaniko --image=shaowenchen/kaniko-demo:v2
|
1 2 3 4
| $ kubectl -n kaniko-demo expose deploy/kaniko --type=NodePort --port=80 --target-port=80 $ kubectl -n kaniko-demo get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kaniko NodePort 10.233.27.225 <none> 80:30772/TCP 29s
|
1 2 3 4 5 6 7 8 9 10
| $ curl 192.168.13.3:30772
Hostname: kaniko-5ddbf597b6-5h8k8 IP: 127.0.0.1 IP: 10.233.90.187 RemoteAddr: 192.168.13.3:11443 GET / HTTP/1.1 Host: 192.168.13.3:30772 User-Agent: curl/7.29.0 Accept: */*
|
4. 其他关注的问题
在最新的版本中,已经可以看到 ARM 的 Kaniko 执行器: https://github.com/GoogleContainerTools/kaniko/releases/tag/v1.3.0
官方提供的一种方式是直接将秘钥和分支参数拼接在 Git 仓库的 URL 中
需要在 Dockerfile 中完整描述从源码到镜像的构建过程。最好借助于分阶段构建,改造有些项目中直接 Copy 构建产物的的 Dockerfile 。类似下面的 Dockerfile 不能直接使用:
1 2 3 4 5 6 7
| FROM java:openjdk-8-jre-alpine
WORKDIR /home
COPY target/*.jar /home
ENTRYPOINT java -jar *.jar
|
5. 参考
- https://github.com/GoogleContainerTools/kaniko
- https://github.com/genuinetools/img
- https://github.com/containers/buildah
本文转载自:「陈少文的博客」,原文:https://tinyurl.com/yyrnttya,版权归原作者所有。欢迎投稿,投稿邮箱: editor@hi-linux.com。