Tekton理论
1:简介
Tekton是开源的云原生CI/CD项目,是基于Kubernetes CRD来定义Pipeline,功能强大并且很容易扩展。
在上篇文章中,我们安装完Tekton之后,可以看到安装的CRD如下:
[root@k8s-master tekton]# kubectl get crd | grep tekton
clustertasks.tekton.dev 2022-04-12T21:06:45Z
conditions.tekton.dev 2022-04-12T21:06:45Z
extensions.dashboard.tekton.dev 2022-04-12T21:32:58Z
pipelineresources.tekton.dev 2022-04-12T21:06:46Z
pipelineruns.tekton.dev 2022-04-12T21:06:46Z
pipelines.tekton.dev 2022-04-12T21:06:45Z
runs.tekton.dev 2022-04-12T21:06:46Z
taskruns.tekton.dev 2022-04-12T21:06:46Z
tasks.tekton.dev 2022-04-12T21:06:46Z
其中Task、TaskRun、Pipeline、PipelineRun、PipelineResource、Condition作为其核心CRD,这里主要介绍它们。
1:Task:定义构建任务,它由一系列有序steps构成。每个step可以定义输入和输出,且可以将上一个step的输出作为下一个step的输入。每个step都会由一个container来执行。
2:TaskRun:Task用于定义具体要做的事情,并不会真正的运行,而TaskRun就是真正的执行者,并且会提供执行所需需要的参数,一个TaskRun就是一个Pod。
3:Pipeline:顾名思义就是流水线,它由一系列Tasks组成。就像Task中的step一样,上一个Task的输出可以作为下一个Task的输入。
4:PipelineRun:Pipeline的实际执行,创建后会创建Pod来执行Task,一个PipelineRun中有多个Task。
5:PipelineResource:主要用于定义Pipeline的资源,常见的如Git地址、Docker镜像等。
6:Condition:它主要是在Pipeline中用于判断的,Task的执行与否通过Condition的判断结果来决定。
##注:PipelineResource和Condition都会被废弃。但是在低版本中还是会继续使用,所以这里会简单介绍一下。
图片来自官网(tekton.dev)
如上图所示,一个Pipeline是由许多Task组成,每个Task又由许多step组成。在实际工作中,我们可以灵活定义各种Task,然后根据需要任意组合Task形成各类Pipeline来完成不同的需求。
2:实现原理
上面大致介绍了Tekton的主要CRD以及它们所具备的能力,那么,Tekton是如何把这些CRD串联起来的呢?
我们在安装完Tekton后,可以看到如下两个Pod。
[root@k8s-master tekton]# kubectl get po -n tekton-pipelines
NAME READY STATUS RESTARTS AGE
tekton-dashboard-6c66f85968-mqnvj 1/1 Running 0 20m
tekton-pipelines-controller-f7c657f7-8wfq8 1/1 Running 0 46m
tekton-pipelines-webhook-6d85898ff8-rrq96 1/1 Running 0 46m
# dashboard可以暂时忽略它
一个是tekton-pipelines-controller,一个是tekton-pipelines-webhook。其实从命名方式就可以看出,一个是tekton的控制器,用于监听CRD对象,一个是tekton的网络钩子,用于做CRD校验,其中tekton-pipelines-controller就是Tekton的核心实现Pod。
tekton-pipelines-controller在启动的时候会初始化两个Controller:PipelineRunController以及TaskRunController。我们可以通过main.go(cmd/controller/main.go)看到,如下:
......
go func() {
// start the web server on port and accept requests
log.Printf("Readiness and health check server listening on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, mux))
}()
ctx = filteredinformerfactory.WithSelectors(ctx, v1beta1.ManagedByLabelKey)
sharedmain.MainWithConfig(ctx, ControllerLogKey, cfg,
taskrun.NewController(opts, clock.RealClock{}),
pipelinerun.NewController(opts, clock.RealClock{}),
)
}
如上所示会通过taskrun.NewController和pipelinerun.NewController来进行初始化,然后通过sharedmain.MainWithConfig调用controller.StartAll来启动所有Controller。
PipelineRunController通过监听PipelineRun对象的变化,然后从PipelineSpec中获取Task列表并构建成一张有向无环图(DAG),然后通过遍历DAG找到可被调度的Task节点创建对应的TaskRun对象。具体可以通过(pkg/reconciler/pipelinerun/pipelinerun.go)中的reconcile方法进行查看。
TaskRunController监听到TaskRun对象的变化,就会将TaskRun中的Task转化为Pod,由Kubernetes调度执行。可以通过(pkg/reconciler/taskrun/taskrun.go)中的reconcile方法进行查看。
利用 Kubernetes 的 OwnerReference 机制, PipelineRun Own TaskRun、TaskRun Own Pod、Pod 状态变更时,触发 TaskRun 的 reconcile 逻辑, TaskRun 状态变更时触发 PipelineRun 的 reconcile 逻辑。
当TaskRun的Pod变成running过后,就会通知第一个step容器来执行(通过一个名叫entrypoint的二进制文件来完成)。
当然这个entrypoint二进制文件也有运行条件的,当且仅当pipeline的状态的annotation通过
Kubernetes Download Api以文件的方式注入到step container后才会启动提供的命令。这句话是不是有点绕?按照官方的说法是:Tekton Pipeline是通过Kubernetes Annotation来跟踪Pipeline的状态,而且这些annotations会通过Kubernetes Download Api以文件的方式注入到Step Container中,Step Container中的entrypoint会监听着这些文件,当特定的annotation以文件的形式注入进来过后,entrypoint才会去执行命令。比方说,一个Task中有两个step,第二个step中的entrypoint会等待,直到annotation以文件的形式告诉它第一个step已经完成。
我们来梳理一下整体的流程,如下:
1:用户通过client创建PipelineRun资源
2:PipelineRunController监听到PipelineRun资源,就把里面的Task组成DAG(有向无环图),遍历DAG得到Task,并创建TaskRun
3:TaskRunController监听到TaskRun资源,就会通过Kubernetes将Task转化为Pod启动(Task受Condition条件控制)
4:Pod启动后会运行Task中的每一个Step完成具体的指令
5:运行完成后Pod会变成Completed状态,同时也会更新PipelineRun的状态
到此一个Pipeline就运行完成了。
3:PipelineResources
这里将PipelintResource提到最前面来说明,主要是后面的操作有需要它的地方。
PipelineResource用于定义资源的信息,虽然会被弃用,但是在旧版本中依然会使用。
PipelineResource的定义很简单,如下:
[root@k8s-master tekton]# cat piplineresource.yaml
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: resource
spec:
type: git
params:
- name: url
value: https://github.com/kubernetes/kubernetes.git
在TaskRun中就可以引用resource资源得到具体的git地址。
4:Tasks
Task就是一个任务模板,Task的定义中可以包含变量,在真正执行的时候需要给变量赋值。
Task通过input.params定义入参,每一个入参还可以指定默认值,在每一个step中可以$(params.A)引用变量。steps字段表示当前Task有哪些步骤组成,每一个step都会通过定义一个container来执行具体的操作。
Task主要包括以下元素:
1:Parameters:用于定义params参数
2:Resources:定义输入、输出资源,老版本由PipelineResources定义,不过在新版本中PipelineResources将被弃用
3:Steps:定义具体的操作步骤
4:Workspaces:定义工作区,Task可以共享工作区
5:Results:定义结果输出,可以用于展示或者给另外的Task使用
Task的定义如下:
[root@k8s-master tekton]# cat task-template.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: maven
spec:
resources:
inputs:
- name: repo
type: git
steps:
- name: mvn-build
image: maven:3.3-jdk-8
command:
- mvn clean package
workingDir: /workspace/repo
再定义一个构建Dokcer镜像并推送到Hub的Task,如下:
[root@k8s-master tekton]# cat task-docker.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: build-and-push-image
spec:
params:
- name: pathToDockerfile
type: string
default: /workspace/repo/Dockerfile
description: define Dockerfile path
- name: pathToContext
type: string
default: /workspace/repo
description: Docker deamon build context
- name: imageRepo
type: string
default: registry.cn-hangzhou.aliyuncs.com
description: docker image repo
resources:
inputs:
- name: repo
type: git
outputs:
- name: builtImage
type: image
steps:
- name: build-image
image: docker:stable
scripts: |
#!/usr/bin/env sh
docker login $(params.imageRepo)
docker build -t $(resources.outputs.builtImage.url) -f $(params.pathToDockerfile) $(params.pathToContext)
docker push $(resources.outputs.builtImage.url)
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
如上,我们可以通过直接编写shell脚本的方式来实现需求,而且使用docker构建镜像需要sock文件,可以像pod挂载那样挂载需要的东西。
step还有其他的配置,比如为某个step设置超时时间,如下:
steps:
- name: sleep-then-timeout
image: ubuntu:latest
script: |
#!/usr/bin/env bash
echo "I am supposed to sleep for 60 seconds!"
sleep 60
timeout: 5s
更多的操作可以通过(https://tekton.dev/docs/pipelines/tasks/)进行学习研究。
5:TaskRuns
Task在定义好之后,并不会被执行,就像我们定义了一个函数,如果没被调用的话,这个函数就不会被执行一样。而TaskRun就可以就好似调用方,用它来执行Task里的具体内容。
TaskRun会设置Task需要的参数,并通过taskRef字段来引用Task,如下:
[root@k8s-master tekton]# cat taskruns.yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: build-and-push-image
spec:
params:
- name: imageRepo
value: registry.cn-zhangjiakou.aliyuncs.com
taskRef:
name: build-and-push-image # 关联定义好的task
resources:
inputs:
- name: repo # 指定输入的仓库资源
resourceRef:
name: hello-word-resource
outputs: # 指定输出的镜像资源
- name: builtImage
resourceRef:
name: hello-word-image
通过如上的定义,就将build-and-push-image的Task进行关联,并且通过resources定义Task需要的sources参数,然后通过parms来定义参数,该参数会替代掉Task中的默认参数。
在实际中,基本不会去定义TaskRun,除非自己去测试某个Task是否正常。
6:Pipelines
一个TaskRun只能执行一个Task,当我们需要同时编排许多Task的时候,就需要使用Pipeline了,就像使用Jenkinsfile来编排不同的任务一样。
Pipeline是一个编排Task的模板,通过spec.params来声明执行时需要的入参,通过spec.tasks来编排具体的task,除此之外还可以通过runAfter来控制Task的先后顺序。
先定义一个简单的Pipeline,如下:
[root@k8s-master tekton]# cat pipline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: build-and-push-image
spec:
resources:
- name: repo
type: git
- name: builtImage
type: image
tasks:
# 构建并推送 Docker 镜像
- name: build-and-push-image
taskRef:
name: build-and-push-image
resources:
inputs:
- name: repo # Task 输入名称
resource: repo # Pipeline 资源名称
outputs:
- name: builtImage
resource: builtImage
上面定义的Pipeline关联了build-and-push-image Task,该Task所需要的输入输出参数,通过Pipeline的spec.resources定义,这里的spec.resources依然依赖PipelineResources中定义的具体资源。
上面提到过,如果要在Pipeline中控制Task顺序,则要使用runAfter参数,如下:
- name: test-app
taskRef:
name: make-test
resources:
inputs:
- name: workspace
resource: my-repo
- name: build-app
taskRef:
name: kaniko-build
runAfter:
- test-app
resources:
inputs:
- name: workspace
resource: my-repo
如上build-app的Task依赖test-app的Task。
除此之外,还可以将上个Task的输出作为下一个Task的输入,如下。
- name: build-app
taskRef:
name: build-push
resources:
outputs:
- name: image
resource: my-image
- name: deploy-app
taskRef:
name: deploy-kubectl
resources:
inputs:
- name: image
resource: my-image
from:
- build-app
如上通过from关键字来引入其他Task的输出。
如果要在Pipeline中使用条件判断,也可以像以下方式使用when关键字。
tasks:
- name: deploy-to-dev
when:
- input: "$(params.branch)"
operator: in
values: ["dev"]
taskRef:
name: deploy-to-dev
---
tasks:
- name: deploy-to-test
when:
- input: "$(params.branch)"
operator: in
values: ["test"]
taskRef:
name: deploy-to-test
# 注意:when和condition不能同时在一个Task中使用,不然会被认定为无效。
还有一个关键字和when效果一样,就是condition。
condition的作用就是用一些条件来保护Task,只有在满足条件的情况下才会运行Task。在Task运行之前,会对所有的条件进行判断,只有全部条件成功,才会运行Task,否则不会允许。
如下定义一个简单的条件语句。
tasks:
- name: deploy-if-branch-is-master
conditions:
- conditionRef: is-master-branch
params:
- name: branch-name
value: my-value
taskRef:
name: deploy
当然条件约束仅针对当前的Task,如果其他Task不受当前Task影响,则不受约束。
更多的使用方式见(https://tekton.dev/docs/pipelines/pipelines/)。
7:PipelineRuns
Pipeline和Task一样,单纯的定义完并不会运行,Pipeline需要借助PipelineRun来完成真正的执行。
PipelineRun会自动为Pipeline中定义的Task创建对应的TaskRun。
下面定义一个简单的PipelineRun。
[root@k8s-master tekton]# cat taskruns.yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-and-push-image
spec:
pipelineRef:
name: build-and-push-image
resources:
- name: repo
resourceRef:
name: demo-git
- name: builtImage
resourceRef:
name: harbor-image
其中spec.pipelineRef用来关联定义的Pipeline,spec.resources用来给Pipeline传递参数。
上面的repo和builtImage参数依然需要通过PipelineResources定义。不过在新版本,也可以通过resourceSpec来进行定义,如下:
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-and-push-image
spec:
pipelineRef:
name: build-and-push-image
resources:
- name: repo
resouorceSpec:
type: git
params:
- name: url
value: https://gitee.com/coolops/springboot-helloworld.git
- name: builtImage
resouorceSpec:
type: image
params:
- name: url
value: registry.cn-hangzhou.aliyuncs.com/coolops/helloworld:latest
8:Conditions
condition用于在Pipeline中进行条件判断,不过在新版本中会被废弃,使用上面介绍的when替代,这里不再做多的介绍了。
9:鉴权管理
上面介绍了主要的CRD以及它们的使用方式,但是还有一种是需要我们关注的,比如代码仓库的密码怎么管理?镜像仓库的密码怎么管理?因为这些都是在实际工作中需要使用的。
Tekton通过在PipelineRun中指定ServiceAccount来实现。不过Tekton要求定义的每个Secret都需要指定对应的annotation。目前支持的annotation有以下两种:
1:Git:tekton.dev/git-**0:** https**:**//github.com
2:Docker:tekton.dev/docker-**0:** https**:**//gcr.io
目前这两种分别支持以下类型。
Git Docker
kubernetes.io/basic-auth
kubernetes.io/ssh-auth kubernetes.io/basic-auth
kubernetes.io/dockercfg
kubernetes.io/dockerconfigjson |
Tekton到底是如何使用到这些secret的呢?
原来,为了使用这些Secret,Tekton在实例化Pod的时候就会执行凭证初始化, Tekton会将具体的Secret进行关联并聚合到/tekton/creds目录中,之后才会执行具体的Task步骤。
下面我们具体操作一下,以镜像仓库为例。
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
annotations:
tekton.dev/docker-0: https://gcr.io # Described below
type: kubernetes.io/basic-auth
stringData:
username: <cleartext username>
password: <cleartext password>
其中tekton.dev/docker-0: [https://gcr.io](https://gcr.io)用来指定对应的仓库地址。
创建seviceaccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: docker-registry-sa
secrets:
- name: docker-registry-secret
在PipelineRun中引用
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: demo-pipeline
namespace: default
spec:
serviceAccountName: docker-registry-sa
pipelineRef:
name: demo-pipeline
如果需要同时使用多个serviceaccount怎么办呢?比如我们在一条完成的Pipeline中,在拉取代码的时候会用到Git的账户,在推送镜像的时候会用到镜像仓库的账户。
这时候我们就不能用serviceAccountName了,而是需要使用serviceAccountNames。serviceAccountNames是一个List,可以指定Task关联具体的serviceaccount,如下。
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: demo-pipeline
namespace: default
spec:
serviceAccountNames:
- taskName: build-app
serviceAccountName: gitlab-sa
- taskName: push-image
serviceAccountName: docker-registry-sa
pipelineRef:
name: demo-pipeline
到这里基本的资源以及介绍完了,弄懂这篇文章,写一个简单的Pipeline应该不成问题,