Tekton实现java项目部署到k8s的完整CICD流程

48,745 阅读7分钟

上一篇文件 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 参数,完成新应用的部署

pipeline

编排文件准备

目录结构

.
├── 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 页面

1.png

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