上一篇文件 Tekton介绍 介绍了Tekton、Tekton的安装教程、以及使用Tekton实现简单的HelloWorld,这篇文章通过复杂的项目实现完整的CI/CD流程来了解Tekton的使用。
概述
流水线的流程
本文实现一个 springboot 项目 CI/CD 的完整流程,具体包括以下步骤:
- 从 git 仓库拉取代码
- maven 构建,将源码打包成 jar 包
- 根据 Dockerfile 构建镜像并推送到镜像仓库
- 从 git 仓库拉取helm部署用的 chart包模板
- 使用 kubectl 命令部署全局信息:镜像仓库的secret(多个chart包会共用,加到多个chart包会报错)
- 使用 helm 部署应用,镜像参数使用前一步动态生成的值
在实际使用过程中,helm可能被设计的比较小,每个微服务单独一个,便于独立交付。而要执行完整的部署操作,有一些全局的编排文件,放在helm chart中就不太合适,往往通通过 kubectl apply -f 命令一次创建创建好就完成了,比如:拉取镜像的secret信息、istio的gateway信息。这时就可以用一个单独的执行 kubectl 命令的 Task 来做。
使用的材料和工具
使用到的材料、工具:
- git:存放源码的地址、账号信息
- maven:打包java项目的工具
- registry:远程镜像仓库,存放打包的镜像
- GoogleContainerTools/kaniko:容器内构建镜像并推送到镜像仓库
- Lachie83/k8s-kubectl:容器内访问k8s集群
- docker.io/lachlanevenson/k8s-helm:v3.3.4:容器内部署helm应用的工具
整体架构图
整条流水线包括四个Task:
- 自动化测试的 Task,独立运行
- maven 编译并且打包成镜像推送到镜像仓库,和前一个任务并行执行
- kubectl 命令执行的 Task,和前面两个并行执行
- helm 部署应用的 Task,需等待第二个任务执行成功后才能执行
用到了三个外部资源:
- src-git:存放源码的git仓库
- image-repository:存放构建好的镜像的仓库
- helm-git: 存放应用部署的 helm 模板文件的仓库
参数传递
镜像构建完成后,生成的镜像url信息(包括tag),动态的传递到下一个Task,helm 部署时,通过指定 --set 参数,完成新应用的部署
编排文件准备
目录结构
.
├── gcp-git-resource.yaml
├── gcp-git-secret.yaml
├── gcp-helm-git-resource.yaml
├── gcp-helm-task.yaml
├── gcp-image-resource.yaml
├── gcp-image-secret.yaml
├── gcp-kubectl-task.yaml
├── gcp-maven-kaniko-task.yaml
├── gcp-pipeline.yaml
├── gcp-pipelinerun.yaml
├── gcp-unittest-task.yaml
└── serviceaccount.yaml
定义三个PipelineResource数据源
存放源码的git数据源
首先通过PipelineResource
定义源代码的配置信息,存在在 gcp-git-resource.yaml 文件中
- type 指定了类型为 git
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: gcp-git-resource
namespace: tekton-pipelines
spec:
type: git
params:
- name: url
value: http://gitlab.xxx.com/project/xxx.git
- name: revision
value: master
git仓库的账号密码
如果git仓库不是公开的,需要定义账号密码信息,存放在 gcp-git-secret.yaml 文件中
annotations 中的 tekton.dev/git-0 指定了将此账号密码信息应用于哪个域名下的git仓库
apiVersion: v1
kind: Secret
metadata:
name: gcp-git-secret
namespace: tekton-pipelines
annotations:
tekton.dev/git-0: http://gitlab.xxx.com
type: kubernetes.io/basic-auth
stringData:
username: username
password: "********"
存放部署应用的helm chart包git数据源
存放在 gcp-helm-git-resource.yaml 文件中
- 这个仓库和前面的是同一个域名,复用上一个账号密码信息
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: gcp-helm-git-resource
namespace: tekton-pipelines
spec:
type: git
params:
- name: url
value: http://gitlab.xxx.com/xxx/manifests.git
- name: revision
value: dev
存放镜像的仓库信息
存放在 gcp-image-resource.yaml 文件中
- type 指定了类型是 image
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: gcp-image-resource
namespace: tekton-pipelines
spec:
type: image
params:
- name: url
value: xxx-registry.cn-beijing.cr.aliyuncs.com/project/app
镜像仓库的账号信息
存放在 gcp-image-secret.yaml 文件中
- annotations 字段指定了账号密码应用于那个镜像仓库
apiVersion: v1
kind: Secret
metadata:
name: gcp-image-secret
namespace: tekton-pipelines
annotations:
tekton.dev/docker-0: http://xxx-registry.cn-beijing.cr.aliyuncs.com/v1/
type: kubernetes.io/basic-auth
stringData:
username: username
password: ******
定义三个Task
单元测试的Task
- resources.inputs 定义了该 Task 需要用到的资源信息
- image:定义了执行该Task的镜像的maven镜像,里面预装了maven软件
- volumeMounts:设置磁盘挂载,挂载到宿主机上的/root/.m2 目录,避免每次执行流水线都要下载依赖包
- command & args:在容器内执行 mvn test 命令
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: gcp-maven-test
namespace: tekton-pipelines
spec:
resources:
inputs:
- name: src-git-repo
type: git
steps:
- name: maven-test
image: maven:3.5.0-jdk-8-alpine
workingDir: /workspace/src-git-repo
command:
- mvn
args:
- test
volumeMounts:
- name: m2
mountPath: /root/.m2
volumes:
- name: m2
hostPath:
path: /root/.m2
maven构建和镜像构建并推送的Task
该 Task 定义了两个 Step:
- 源码通过maven构建成jar包,调用 mvn clean package 命令
- 通过Dockerfile构建成镜像,并推送到镜像仓库
构建镜像使用的是google开源的kaniko,因为使用docker构建,存在 docker in docker 的问题,docker构建需要docker daemon进程,因此需要挂载宿主机的 docker.sock 文件,这样不安全。kaniko 这个镜像构建工具特别轻量化,不像 docker 一样依赖一个 daemon 进程。
- 执行的命令:/kaniko/executor
- 相关参数说明:
- dockerfile:引用了 inputs 的 resource 中的 git 仓库地址中的 Dockerfile
- context:引用了 inputs 的 resource 中的 git 仓库地址
- destination:应用了 outputs 的 resource 中的 image 仓库地址
使用到两个资源文件:
- inputs 类型的 src-git-repo,指明需要使用的源码地址,type 是 git
- outputs 类型的 image-repo,指明镜像构建完成后推送到的目的地址,type 是 image
文件中还定义了一个名为 DOCKER_CONFIG
的环境变量,这个变量是用于 Kaniko 去查找 Docker 认证信息的
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: gcp-maven-kaniko-build
namespace: tekton-pipelines
spec:
params:
- name: imageTag
type: string
resources:
inputs:
- name: src-git-repo
type: git
outputs:
- name: image-repo
type: image
steps:
- name: maven-build
image: maven:3.5.0-jdk-8-alpine
workingDir: /workspace/src-git-repo
command:
- mvn
args:
- clean
- package
volumeMounts:
- name: m2
mountPath: /root/.m2
- name: kaniko-build
image: cnych/kaniko-executor:v0.22.0
workingDir: /workspace/kaniko
env:
- name: DOCKER_CONFIG
value: /tekton/home/.docker
command:
- /kaniko/executor
- --dockerfile=$(resources.inputs.src-git-repo.path)/Dockerfile
- --context=$(resources.inputs.src-git-repo.path)
- --destination=$(resources.outputs.image-repo.url):$(params.imageTag)
- --insecure
- --skip-tls-verify
- --skip-tls-verify-pull
- --insecure-pull
volumes:
- name: m2
hostPath:
path: /root/.m2
执行 kubectl 部署的 Task
该 Task 使用了 lachlanevenson/k8s-kubectl 这个镜像,内部预装了 kubectl 工具,参数是需要执行的脚本内容。
定义参数使用 params 字段设置,引用该参数的语法格式为 $(xxx)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: gcp-kubectl-deploy
namespace: tekton-pipelines
spec:
params:
- name: script_body
type: string
default: "kubectl version"
steps:
- name: kubectl-deploy
image: lachlanevenson/k8s-kubectl
script: |
$(params.script_body)
执行 helm 部署的 Task
- 该 Task 使用了 docker.io/lachlanevenson/k8s-helm:v3.3.4 这个镜像,内部按照了 helm 工具。
- 执行的脚本内容是 helm upgrade --install --wait --values xxx.yaml ....
- params 指定了部署过程中用到的参数,可以通过外部传入,也可以使用 default 定义默认值
- resources 字段定义了执行过程中用到的两个数据源
- helm-git-repo:描述应用程序使用 helm 部署时的chart包的 git 存放地址
- image-repo:镜像信息
- 执行脚本中 通过 --set 覆盖 helm 中的默认镜像地址:值是从 input 这个资源文件中获取到的。这个input在哪里赋值的呢?其实是在后面定义了 pipeline 中引用了上面介绍的镜像构建的输出,作为这里的输入
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: gcp-helm-deploy
namespace: tekton-pipelines
spec:
params:
- name: release_namespace
default: "tekton-pipelines"
- name: charts_dir
default: "autotest/cloudsandbox-charts"
- name: release_name
default: "scheduler-ast"
- name: values_file
default: "autotest/cloudsandbox-ast-slave.yaml"
- name: imageTag
default: "latest"
resources:
inputs:
- name: helm-git-repo
type: git
- name: image-repo
type: image
steps:
- name: deploy-chart
image: docker.io/lachlanevenson/k8s-helm:v3.3.4
workingDir: /workspace/helm-git-repo
script: |
echo current installed helm releases
helm list --namespace "$(params.release_namespace)"
echo installing helm chart...
helm upgrade --install --wait --values "$(params.values_file)" --namespace "$(params.release_namespace)" $(params.release_name) $(params.charts_dir) --set "deploy.init_copyfiles_image=$(resources.inputs.image-repo.url):$(params.imageTag)"
定义serviceaccount
serviceaccount 定义了需要访问k8s资源的权限, 引用 git 和 image 的 secret
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-bot
namespace: tekton-pipelines
secrets:
- name: gcp-git-secret
- name: gcp-image-secret
定义流水线 pipeline
- params:声明用到的参数
- imageTag:镜像构建的tag,这个值需要后面的 pipelinerun 为它赋值。而使用这个值的是 maven构建这个Task
- resources:声明用到的资源信息
- tasks:编排任务之间的关系。前面四个Task的关系如下:
- gcp-maven-test、 gcp-maven-kaniko-build、gcp-kubectl-deploy 没有声明依赖关系,并行执行
- gcp-helm-deploy 中 resource 里面声明了一个 from,表明这里的输入数据源依赖 gcp-maven-kaniko-build 的输出数据源,因此会等待 gcp-maven-kaniko-build 执行完才开始执行
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: gcp-pipeline
namespace: tekton-pipelines
spec:
params:
- name: imageTag
default: v0.0.2
- name: kubectl_script
type: string
resources:
- name: src-git-repo
type: git
- name: image-repo
type: image
- name: helm-git-repo
type: git
tasks:
- name: gcp-maven-test
taskRef:
name: gcp-maven-test
resources:
inputs:
- name: src-git-repo
resource: src-git-repo
- name: gcp-maven-kaniko-build
taskRef:
name: gcp-maven-kaniko-build
params:
- name: imageTag
value: $(params.imageTag)
resources:
inputs:
- name: src-git-repo
resource: src-git-repo
outputs:
- name: image-repo
resource: image-repo
- name: gcp-kubectl-deploy
taskRef:
name: gcp-kubectl-deploy
params:
- name: script_body
value: $(params.kubectl_script)
- name: gcp-helm-deploy
taskRef:
name: gcp-helm-deploy
params:
- name: imageTag
value: $(params.imageTag)
resources:
inputs:
- name: helm-git-repo
resource: helm-git-repo
- name: image-repo
resource: image-repo
from:
- gcp-maven-kaniko-build
定义 pipelinerun
pipeline 是流水线模板,真正执行需要定义 pipelineRun 对象。
- metadata中使用 generateName 设置名称的前缀(必须用 kubectl create 执行)
- serviceAccountName 字段值为前面声明的 serviceacount
- resources 字段中引用前面声明的 type 类型的 pipelineResource
- pipelineRef 字段引用前面申明的 pipeline
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
namespace: tekton-pipelines
generateName: gcp-pipeline-run-
spec:
serviceAccountName: build-bot
pipelineRef:
name: gcp-pipeline
params:
- name: imageTag
value: v0.0.3
- name: kubectl_script
value: "kubectl get pod"
resources:
- name: src-git-repo
resourceRef:
name: gcp-git-resource
- name: image-repo
resourceRef:
name: gcp-image-resource
- name: helm-git-repo
resourceRef:
name: gcp-helm-git-resource
执行部署
kubectl apply -f gcp-git-resource.yaml \
gcp-git-secret.yaml \
gcp-helm-git-resource.yaml \
gcp-helm-task.yaml \
gcp-image-resource.yaml \
gcp-image-secret.yaml \
gcp-kubectl-task.yaml \
gcp-maven-kaniko-task.yaml \
gcp-pipeline.yaml \
gcp-unittest-task.yaml \
serviceaccount.yaml
kubect create -f gcp-pipelinerun.yaml
效果图
执行完毕后dashboard 页面
pod情况
后面几个是 tekton 系统本身的pod,前面四个pod 分别对应 pipeline中的四个 pod
> kubectl get pod -n tekton-pipelines
NAME READY STATUS RESTARTS AGE
gcp-pipeline-run-1618836739877-gcp-helm-deploy-glrg2-pod-wrqbv 0/2 Completed 0 37m
gcp-pipeline-run-1618836739877-gcp-kubectl-deploy-wlplk-p-5twxq 0/1 Completed 0 39m
gcp-pipeline-run-1618836739877-gcp-maven-kaniko-build-2k8-ccgtf 0/5 Completed 0 39m
gcp-pipeline-run-1618836739877-gcp-maven-test-q7rbz-pod-2v5xq 0/2 Completed 0 39m
tekton-dashboard-7b8db5fd75-2wkhj 1/1 Running 1 4d10h
tekton-pipelines-controller-544957d9d8-p65zr 1/1 Running 1 4d10h
tekton-pipelines-webhook-54cb64d5f7-k6qbw 1/1 Running 1 4d10h