文章目录
- 使用 GitHub Action 构建镜像
- 使用 GitLab CI 构建镜像
- 使用 Tekton 构建镜像
- Tekton组件安装
- Tekton 简介
- 创建 Tekton Pipeline
- 创建 Task
- 创建 Pipeline
- 创建 EventListener
- 创建 TriggerTemplate
- 创建 Service Account 和 PVC
- 设置 Secret
- 创建 GitHub Webhook
- 触发 Pipeline
- 使用 Harbor 搭建企业级镜像仓库
- 在 Tekton Pipeline 中使用 Harbor
使用 GitHub Action 构建镜像
Workflow
Workflow 也叫做工作流。其实,GitHub Action 本质上是一个是一个 CI/CD 工作流,要使用工作流,我们首先需要先定义它。和 K8s Manifest 一样,GitHub Action 工作流是通过 YAML 来描述的,你可以在任何 GitHub 仓库创建 .github/workflows 目录,并创建 YAML 文件来定义工作流。
所有在 .github/workflows 目录创建的工作流文件,都将被 GitHub 自动扫描。在工作流中,通常我们会进一步定义 Event、Job 和 Step 字段,它们被用来定义工作流的触发时机和具体行为。
Event
Event 从字面上的理解是“事件”的意思,你可以简单地把它理解为定义了“什么时候运行工作流”,也就是工作流的触发器。
在定义自动化构建镜像的工作流时,我们通常会把 Event 的触发器配置成“当指定分支有新的提交时,自动触发镜像构建”。
Jobs
Jobs 的字面意思是一个具体的任务,它是一个抽象概念。在工作流中,它并不能直接工作,而是需要通过 Step 来定义具体的行为。此外,你还可以为 Job 定义它的运行的环境,例如 ubuntu。
在一个 Workflow 当中,你可以定义多个 Job,多个 Job 之间可以并行运行,也可以定义相互依赖关系。在自动构建镜像环节,通常我们只需要定义一个 Job 就够了,所以在上面的示意图中,我只画出了一个 Job。
Step
Step 隶属于 Jobs,它是工作流中最小的粒度,也是最重要的部分。通常来说,Step 的具体行为是执行一段 Shell 来完成一个功能。在同一个 Job 里,一般我们需要定义多个 Step 才能完成一个完整的 Job,由于它们是在同一个环境下运行的,所以当它们运行时,就等同于在同一台设备上执行一段 Shell。
以自动构建镜像为例,我们可能需要在 1 个 Job 中定义 3 个 Step。
- Step1,克隆仓库的源码。
- Step2,运行 docker build 来构建镜像。
- Step3,推送到镜像仓库。
实现的效果:
当我们向 Main 分支推送新的提交时,GitHub Action 工作流将会被自动触发,工作流会自动构建 frontend 和 backend 镜像,并且会使用当前的 short commit id 作为镜像的 Tag 推送到 Docker Hub 中。
这意味着,每一个提交都会生成一个 Docker 镜像,实现了代码和制品的对应关系。 这种对应关系给我们带来了非常大的好处,例如当我们要回滚或更新应用时,只需要找到代码的 commit id 就能够找到对应的镜像版本。
使用 GitLab CI 构建镜像
GitLab CI 是通过在仓库根目录创建 .gitlab-ci.yml 文件来定义流水线的,这和 GitHub 有明显的差异。.gitlab-ci.yml 文件定义的内容也相对简单,它基本上和我们在本地构建镜像所运行的命令以及顺序是一致的。
此外,相比较 GitHub Action Workflow,GitLab CI 省略了触发器和检出代码的配置步骤,并且,在 GitLab CI 中我们是通过 DiND 的方式来运行流水线的,也就是在容器的运行环境下启动另一个容器来运行流水线,而 GitHub Action 则是通过虚拟机的方式来运行流水线。
和 GitHub Action 相比较,它们除了流水线文件内容不一样以外,其他的操作例如创建 GitLab 仓库、创建 Docker Hub Secret 以及创建 GitLab CI Variables 等步骤都是差不多的。
最终,当我们有新的推送到仓库时,GitLab CI 将运行自动构建镜像的流水线,并且每次提交的 commit id 都会对应一个镜像版本,和 GitHub Action Workflow 一样, 也实现了代码版本和制品版本的对应关系。
使用 Tekton 构建镜像
自动构建镜像的自托管方案: 使用 Tekton 来自动构建镜像。 Tekton 是一款基于 Kubernetes 的 CI/CD 开源产品,如果有一个 Kubernetes 集群,那么利用 Tekton 直接在 Kubernetes 上构建镜像是一个不错的选择。
Tekton组件安装
官网:https://tekton.dev/docs/
kubernetes版本要求:>= 1.24
1.安装 Tekton Operator
kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
2.安装 Tekton Dashboard
kubectl apply --filename https://storage.googleapis.com/tekton-releases/dashboard/latest/release.yaml
3.安装 Tekton Trigger 和 Tekton Interceptors 组件
kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml
~# kubectl wait --for=condition=Ready pods --all -n tekton-pipelines --timeout=300s
pod/tekton-dashboard-6ddf4d8556-mdszf condition met
pod/tekton-pipelines-controller-68fb8c9df6-cnxkz condition met
pod/tekton-pipelines-webhook-b54b6d464-9hff9 condition met
pod/tekton-triggers-controller-5969f786d6-zfk2f condition met
pod/tekton-triggers-core-interceptors-77d6499b44-h9zxp condition met
pod/tekton-triggers-webhook-67559d98cf-l2q6v condition met
4.Traefik安装
参考链接: http://t.csdn.cn/gYNGH
5.暴露 Tekton Dashboard
cat tekton-ingressroute.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: tekton-dashboard
spec:
entryPoints:
- web
routes:
- match: Host(`tekton.k8s.local`)
kind: Rule
services:
- name: tekton-dashboard
port: 9097
Tekton 简介
【EventListener】
EventListener 顾名思义是一个事件监听器,它是外部事件的入口。EventListener 通常以 HTTP 的方式对外暴露,此处会在 GitHub 创建 WebHook 来调用 Tekton 的 EventListener,使它能接收到仓库推送事件。
【TriggerTemplate】
当 EventListener 接收到外部事件之后,它会调用 Trigger 也就是触发器,而 TriggerTemplate 是用来定义接收到事件之后需要创建的 Tekton 资源的,例如创建一个 PipelineRun 对象来运行流水线。此处会使用 TriggerTemplate 来创建 PipelineRun 资源。
【Step】
Step 是流水线中的一个具体的操作,例如构建和推送镜像操作。Step 接收镜像和需要运行的 Shell 脚本作为参数,Tekton 将会启动镜像并执行 Shell 脚本。
【Task】
Task 是一组有序的 Step 集合,每一个 Task 都会在独立的 Pod 中运行,Task 中不同的 Step 将在同一个 Pod 不同的容器内运行。
【Pipeline】
Pipeline 是 Tekton 中的一个核心组件,它是一组 Task 的集合,Task 将组成一组有向无环图(DAG),Pipeline 会按照 DAG 的顺序来执行。
【PipelineRun】
PipelineRun 实际上是 Pipeline 的实例化,它负责为 Pipeline 提供输入参数,并运行 Pipeline。例如,两次不同的镜像构建操作对应的就是两个不同的 PipelineRun 资源。
创建 Tekton Pipeline
此次Tekton流水线实现的效果~
当我们向 GitHub 推送代码时,GitHub 将以 HTTP 请求的方式通知集群内的 Tekton 触发器,触发器通过 Traefik ingressroute或者Ingress-Nginx 对外暴露,当触发器接收到来自 GitHub 的事件推送时,将通过 TriggerTemplate 来创建 PipelineRun 运行 Pipeline,最终实现镜像的自动构建和推送。
创建 Task
在创建 Pipeline 之前,需要先创建两个 Task,这两个 Task 分别负责“检出代码”还有“构建和推送镜像”。
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/task/git-clone.yaml
这个 Task 是 Tekton 官方提供的插件,它和 GitHub Action 的 checkout 插件有一点类似,主要作用是检出代码
创建构建和推送镜像的 Task:
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/task/docker-build.yaml
Task关键内容如下:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: docker-socket
spec:
workspaces:
- name: source
params:
- name: image
description: Reference of the image docker will produce.
......
steps:
- name: docker-build
image: docker:stable
env:
......
- name: IMAGE
value: $(params.image)
- name: DOCKER_PASSWORD
valueFrom:
secretKeyRef:
name: registry-auth
key: password
- name: DOCKER_USERNAME
valueFrom:
secretKeyRef:
name: registry-auth
key: username
workingDir: $(workspaces.source.path)
script: |
cd $SUBDIRECTORY
docker login $REGISTRY_URL -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
if [ "${REGISTRY_URL}" = "docker.io" ] ; then
docker build --no-cache -f $CONTEXT/$DOCKERFILE_PATH -t $DOCKER_USERNAME/$IMAGE:$TAG $CONTEXT
docker push $DOCKER_USERNAME/$IMAGE:$TAG
exit
fi
docker build --no-cache -f $CONTEXT/$DOCKERFILE_PATH -t $REGISTRY_URL/$REGISTRY_MIRROR/$IMAGE:$TAG $CONTEXT
docker push $REGISTRY_URL/$REGISTRY_MIRROR/$IMAGE:$TAG
volumeMounts: # 共享 docker.socket
- mountPath: /var/run/
name: dind-socket
sidecars: #sidecar 提供 docker daemon
- image: docker:dind
......
-------------------------------------------------------------------------
spec.params 字段用来定义变量,并最终由 PipelineRun 提供具体的值。
spec.steps 字段用来定义具体的执行步骤,例如,这里使用 docker:stable 镜像创建了容器,并将 spec.params 定义的变量以 ENV 的方式传递到容器内部,
其中 DOCKER_PASSWORD 和 DOCKER_USERNAME 两个变量来源于 Secret,将在后续创建。
spec.steps[0].script 字段定义了具体执行的命令,这里执行了 docker login 登录到 Docker Hub,并且使用了 docker build 和 docker push 来构建和推送镜像。我们对 Docker Hub 和其他镜像仓库做了区分,以便使用不同的 TAG 命名规则。
spec.sidecars 字段为容器提供 Docker daemon,它使用的镜像是 docker:dind。
创建 Pipeline
创建完 Task 之后,由于它们实现的具体功能是独立的,所以需要将他们联系起来。也就是说,我们希望 Pipeline 先克隆代码,再构建和推送镜像。所以,下面需要创建 Pipeline 来引用这两个 Task。
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/pipeline/pipeline.yaml
pipeline介绍:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: github-trigger-pipeline
spec:
workspaces:
- name: pipeline-pvc
......
params:
- name: subdirectory # 为每一个 Pipeline 配置一个 workspace,防止并发错误
type: string
default: ""
- name: git_url
......
tasks:
- name: clone
taskRef:
name: git-clone
workspaces:
- name: output
workspace: pipeline-pvc
- name: ssh-directory
workspace: git-credentials
params:
- name: subdirectory
value: $(params.subdirectory)
- name: url
value: $(params.git_url)
- name: build-and-push-frontend
taskRef:
name: docker-socket
runAfter:
- clone
workspaces:
- name: source
workspace: pipeline-pvc
params:
- name: image
value: "frontend"
......
- name: build-and-push-backend
taskRef:
name: docker-socket
runAfter:
- clone
workspaces:
- name: source
workspace: pipeline-pvc
params:
- name: image
value: "backend"
......
首先,spec.workspaces 定义了一个工作空间。还记得我们提到的每一个 Task 都会在独立的 Pod 中运行吗?那么不同的 Task 如何共享上下文呢?答案就是 workspaces。实际上它是一个 PVC 持久化卷,这个 PVC 将会在 Pod 之间复用,这就让下游 Task 可以读取到上游 Task 写入的数据(比如克隆的代码)。
spec.params 定义了 Pipeline 的参数,参数的传递顺序是:PipelineRun->Pipeline->Task。
spec.tasks 定义了 Pipeline 引用的 Task,例如在这里分别引用了 git-clone 和 docker-socket 两个 Task,并且都指定了同一个 workspaces pipeline-pvc,然后指定了 params 向 Task 传递了参数值。
在 build-and-push-frontend 和 build-and-push-backend Task 中,都指定了 runAfter 字段,它的含义是等待 clone Task 执行完毕后再运行。
所以,Pipeline 对 Task 的引用就形成了一个有向无环图(DAG),在这个 Pipeline 中,首先会检出源码,然后以并行的方式同时构建前后端镜像。
创建 EventListener
创建完 Pipeline 之后,工作流实际上就已经定义好了。但是我们并不希望手动来运行它,我们希望通过 GitHub 来自动触发它。所以,接下来需要创建 EventListener 来获得一个能够监听外部事件的服务。
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/trigger/github-event-listener.yaml
EventListener 的具体作用:
接收来自 GitHub 的 Webhook 调用,并将 Webhook 的参数和TriggerTemplate 定义的参数对应起来,以便将参数值从 Webhook 一直传递到 PipelineRun。
暴露 EventListener
cat listener-ingressroute.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: el-github-listener
spec:
entryPoints:
- web
routes:
- match: Host(`xxxxxx`) && PathPrefix(`/hooks`)
kind: Rule
services:
- name: el-github-listener
port: 8080
创建 TriggerTemplate
EventListener 并不能独立工作,它还需要一个助手,那就是 TriggerTemplate。TriggerTemplate 是真正控制 Pipeline 启动的组件,它负责创建 PipelineRun。
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/trigger/github-trigger-template.yaml
创建 Service Account 和 PVC
由于触发器并没有具体的执行用户,所以我们还需要为触发器配置权限,也就是创建 Service Account。同时,我们也可以一并创建用于共享 Task 之间上下文的 PVC。这一步操作可以提前操作。
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/lyzhang1999/gitops/main/ci/18/tekton/other/service-account.yaml
设置 Secret
需要为 Tekton 提供一些凭据信息,例如 Docker Hub Token、GitHub Webhook Secret 以及用于检出私有仓库的私钥信息。
$ kubectl apply -f secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: registry-auth
annotations:
tekton.dev/docker-0: https://docker.io
type: kubernetes.io/basic-auth
stringData:
username: "" # docker username
password: "" # docker hub token
---
# github webhook token secret
apiVersion: v1
kind: Secret
metadata:
name: github-secret
type: Opaque
stringData:
secretToken: "webhooksecret"
---
apiVersion: v1
kind: Secret
metadata:
name: git-credentials
data:
id_rsa: LS0tLS......
known_hosts: Z2l0aHViLm......
config: SG9zd......
- 将 stringData.username 替换为你的 Docker Hub 的用户名。
- 将 stringData.password 替换为你的 Docker Hub Token
- 将 data.id_rsa 替换为你本地 ~/.ssh/id_rsa 文件的 base64 编码内容,这将会为 Tekton 提供检出私有仓库的权限
- 将 data.known_hosts 替换为你本地 ~/.ssh/known_hosts 文件的 base64 编码内容,你可以通过 $ cat ~/.ssh/known_hosts | grep "github" | base64 命令来获取。
- 将 data.config 替换为你本地 ~/.ssh/config 文件的 base64 编码内容
创建 GitHub Webhook
打开GitHub 创建的 kubernetes-example 仓库,进入“Settings”页面,点击左侧的“Webhooks”菜单,在右侧的页面中按照下图进行配置。
触发 Pipeline
现在我们向仓库提交一个空的 commit 来触发 Pipeline,测试验证一下
➜ kubernetes-example (main) ✔ git commit --allow-empty -m "Trigger Build"
[main fa8acc7] Trigger Build
➜ kubernetes-example (main) ✔ git push origin main
可以看到 Pipeline 包含 3 个 Task 及其输出的日志信息。
kubectl get pods |grep github
github-run-n964p-build-and-push-backend-pod 2/2 Running 0 3m10s
github-run-n964p-build-and-push-frontend-pod 2/2 Running 0 3m10s
github-run-n964p-clone-pod 0/1 Completed 0 3m36s
使用 Harbor 搭建企业级镜像仓库
前提:k8s环境中已安装好helm3(https://helm.sh/docs/intro/install/)
在 Tekton Pipeline 中使用 Harbor
要在 Tekton Pipeline 中使用 Harbor,需要将 Pipeline 中的
spec.params.registry_url
变量值由docker.io
修改为harbor.n7t.dev
,并且将spec.params.registry_mirror
变量值修改为library
。可以使用 kubectl edit 命令来修改。
$ kubectl edit Pipeline github-trigger-pipeline
...
params:
- default: harbor.n7t.dev # 修改为 harbor.n7t.dev(域名使用自己的)
name: registry_url
type: string
- default: "library" # 修改为 library
name: registry_mirror
type: string
...
然后再修改镜像仓库的凭据,也就是 registry-auth Secret。
$ kubectl edit secret registry-auth
apiVersion: v1
data:
password: SGFyYm9yMTIzNDUK # 修改为 Base64 编码:Harbor12345
username: YWRtaW4K # 修改为 Base64 编码:admin
kind: Secret
注:生产环境中请修改默认密码!!!
回到本地示例应用 kubernetes-example 目录,向仓库推送一个空的 commit 来触发 Tekton 流水线。
访问Tekton dashboard页面 查看流水线运行状态,运行结束后,登录Harbor仓库中检查有咩有刚才 Tekton 推送的新镜像。
附:
Harbor 生产建议:
- 确认 PVC 是否支持在线扩容。
- 尽量使用 S3 作为镜像存储系统。
- 使用外部数据库和 Redis。
- 开启自动镜像扫描和阻止漏洞镜像。
参考资料:
https://time.geekbang.org/column/article/623839
https://www.lixueduan.com/posts/tekton/01-deploy-tekton/#