使用 Tekton Sidecar 实现 Docker IN Docker 构建
作者 |阳明
来源 | k8s技术圈(ID:kube100)
在 Tekton 中有一项 Sidecar 功能,和 Pod 中的 Sidecar 类似,它也是一个容器,用于和 Task 任务的 Steps 中指定的容器一起运行,为这些 Steps 的执行提供一些辅助支持,比如 Sidecar 可以运行一个 logging daemon、更新共享 volume 上的文件或者提供网络代理等功能。
Tekton 会将 Sidecar 注入属于 TaskRun 的 Pod,一旦 Task 中的所有 Steps 完成执行,Pod 内运行的每一个 Sidecar 就会终止掉,如果 Sidecar 成功退出,kubectl get pods 命令会将 Pod 的状态返回为 Completed,如果 Sidecar 退出时出现了错误,则返回 Error,而忽略实际执行 Pod 内部 Steps 的容器镜像的退出码值。
上面我们在构建容器镜像的时候是通过挂载宿主机的 docker.sock 文件到容器中来执行的,严格意义上来说这种方式叫 Dood - Docker Outside of Docker,DooD 通过绑定安装 Docker 套接字来使用其底层宿主机的 Docker Daemon,而真正的 DinD 是在其中包含一个完整的 Docker 服务。显然 DooD 这种方式更快,因为可以利用它的缓存机制,而 DinD 显然更加安全、更加干净,对宿主机产生的影响更小,而且支持并行运行,因为每个容器里面都是一个独立的 Docker Daemon,互相不受影响,当然 DooD 更加简单易用。这里我们就来使用 Sidecar 的方式为 Tekton 中的容器构建提供一个 DinD 模式的构建服务。
新建一个如下所示的 Task 任务,专门用来构建 Docker 镜像:
# task-docker-build.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: docker-build-push
spec:
resources:
inputs: # 定义输入资源
- name: source # 源代码仓库
type: git
params:
- name: image
description: Reference of the image docker will produce.
- name: builder_image
description: The location of the docker builder image.
default: docker:stable
- name: dockerfile
description: Path to the Dockerfile to build.
default: ./Dockerfile
- name: context
description: Path to the directory to use as context.
default: .
- name: build_extra_args
description: Extra parameters passed for the build command when building images.
default: ""
- name: push_extra_args
description: Extra parameters passed for the push command when pushing images.
default: ""
- name: insecure_registry
description: Allows the user to push to an insecure registry that has been specified
default: ""
- name: registry_mirror
description: Specific the docker registry mirror
default: ""
- name: registry_url
description: private docker images registry url
steps:
- name: docker-build # 构建步骤
image: $(params.builder_image)
env:
- name: DOCKER_HOST # 用 TLS 形式通过 TCP 链接 sidecar
value: tcp://localhost:2376
- name: DOCKER_TLS_VERIFY # 校验 TLS
value: '1'
- name: DOCKER_CERT_PATH # 使用 sidecar 守护进程生成的证书
value: /certs/client
workingDir: $(resources.inputs.source.path)
script: | # docker 构建命令
docker login $(params.registry_url)
docker build \
$(params.build_extra_args) \
--no-cache \
-f $(params.dockerfile) -t $(params.image) $(params.context)
volumeMounts: # 声明挂载证书目录
- mountPath: /certs/client
name: dind-certs
- name: docker-push #
image: $(params.builder_image)
env:
- name: DOCKER_HOST
value: tcp://localhost:2376
- name: DOCKER_TLS_VERIFY
value: '1'
- name: DOCKER_CERT_PATH
value: /certs/client
workingDir: $(resources.inputs.source.path)
script: | # 推送 docker 镜像
docker push $(params.push_extra_args) $(params.image)
volumeMounts:
- mountPath: /certs/client
name: dind-certs
sidecars: # sidecar 模式,提供 docker daemon服务,实现真正的 DinD 模式
- image: docker:dind
name: server
args:
- --storage-driver=vfs
- --userland-proxy=false
- --debug
- --insecure-registry=$(params.insecure_registry)
- --registry-mirror=$(params.registry_mirror)
securityContext:
privileged: true
env:
- name: DOCKER_TLS_CERTDIR # 将生成的证书写入与客户端共享的路径
value: /certs
volumeMounts:
- mountPath: /certs/client
name: dind-certs
readinessProbe: # 等待 dind daemon 生成它与客户端共享的证书
periodSeconds: 1
exec:
command: ['ls', '/certs/client/ca.pem']
volumes: # 使用 emptyDir 的形式即可
- name: dind-certs
emptyDir: {}
上面的 Task 最重要的就是其中的 sidecars 部分,使用了一个 docker:dind 镜像来提供 docker 服务端,由于是 sidecar 模式,所以它和上面构建的 steps 中的容器是共享 network namespace 的,所以在构建的时候我们可以通过 tcp://localhost:2376 和 docker 服务端进行通信,由于还使用的是 TLS 证书模式,所以需要将证书目录进行声明挂载。
接着重新修改我们的 Pipeline 流水线:
# test-pipeline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: test-sidecar-pipeline
spec:
resources: # 为 Tasks 提供输入和输出资源声明
- name: demo-git
type: git
params:
- name: image
type: string
- name: image-tag
type: string
default: "v0.4.0"
- name: registry_url
type: string
default: "harbor.k8s.local"
- name: registry_mirror
type: string
default: "https://ot2k4d59.mirror.aliyuncs.com/"
- name: insecure_registry
type: string
default: "harbor.k8s.local"
tasks: # 添加task到流水线中
# 运行应用测试
- name: test
taskRef:
name: test
resources:
inputs:
- name: repo # Task 输入名称
resource: demo-git # Pipeline 资源名称
- name: get-build-id
taskRef:
name: generate-build-id
params:
- name: base-version
value: $(params.image-tag)
# 构建并推送 Docker 镜像
- name: build-and-push
taskRef:
name: docker-build-push # 使用上面定义的镜像构建任务
runAfter:
- test # 测试任务执行之后
resources:
inputs:
- name: source # 指定输入的git仓库资源
resource: demo-git
params:
- name: image
value: "$(params.image):$(tasks.get-build-id.results.build-id)"
- name: registry_url
value: $(params.registry_url)
- name: insecure_registry
value: $(params.insecure_registry)
- name: registry_mirror
value: $(params.registry_mirror)
这里的流水线最重要的就是将镜像构建的任务替换成上面的 docker-build-push 这个 Task,然后传入几个需要的参数,接着修改 PipelineRun:
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: test-sidecar-pipelinerun
spec:
serviceAccountName: build-sa
pipelineRef:
name: test-sidecar-pipeline
resources:
- name: demo-git # 指定输入的git仓库资源
resourceRef:
name: demo-git
params:
- name: image
value: harbor.k8s.local/course/tekton-demo
这里就比较简单了,只需要引用上面的 Pipeline 流水线,然后提供需要的几个参数即可,直接创建上面的几个资源对象即可执行我们的流水线了:
$ tkn pr list
NAME STARTED DURATION STATUS
test-sidecar-pipelinerun 3 minutes ago 2 minutes Succeeded
test-pipelinerun 1 day ago 1 minute Succeeded
$ tkn pr describe test-sidecar-pipelinerun
Name: test-sidecar-pipelinerun
Namespace: default
Pipeline Ref: test-sidecar-pipeline
Service Account: build-sa
Timeout: 1h0m0s
Labels:
tekton.dev/pipeline=test-sidecar-pipeline
🌡️ Status
STARTED DURATION STATUS
5 minutes ago 2 minutes Succeeded
📦 Resources
NAME RESOURCE REF
∙ demo-git demo-git
⚓ Params
NAME VALUE
∙ image harbor.k8s.local/course/tekton-demo
📝 Results
No results
📂 Workspaces
No workspaces
🗂 Taskruns
NAME TASK NAME STARTED DURATION STATUS
∙ test-sidecar-pipelinerun-build-and-push-bmrpn build-and-push 4 minutes ago 1 minute Succeeded
∙ test-sidecar-pipelinerun-test-l4k2x test 5 minutes ago 9 seconds Succeeded
∙ test-sidecar-pipelinerun-get-build-id-r72xl get-build-id 5 minutes ago 28 seconds Succeeded
我们也可以看到很快就构建成功了:最终在 Harbor 中也可以看到我们刚刚推送的镜像版本:这种方式还可以避免在宿主机上产生大量无用的构建过程产生的镜像,因为每次构建完成就销毁掉了,这才是真正的 Docker IN Docker,也是 Tekton 中的 Sidecar 的一个使用场景。