【GitOps系列】自动化构建Image

文章目录

      • 使用 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 构建镜像

【GitOps系列】自动化构建Image_第1张图片
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 就能够找到对应的镜像版本。
【GitOps系列】自动化构建Image_第2张图片

使用 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

【GitOps系列】自动化构建Image_第3张图片

Tekton 简介

【GitOps系列】自动化构建Image_第4张图片

【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流水线实现的效果~
【GitOps系列】自动化构建Image_第5张图片

当我们向 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”菜单,在右侧的页面中按照下图进行配置。
【GitOps系列】自动化构建Image_第6张图片

触发 Pipeline

现在我们向仓库提交一个空的 commit 来触发 Pipeline,测试验证一下

➜ kubernetes-example (main) ✔ git commit --allow-empty -m "Trigger Build"
[main fa8acc7] Trigger Build
➜ kubernetes-example (main) ✔ git push origin main

【GitOps系列】自动化构建Image_第7张图片
可以看到 Pipeline 包含 3 个 Task 及其输出的日志信息。
【GitOps系列】自动化构建Image_第8张图片

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/)
在这里插入图片描述

1.安装 Cert-manager
[root@VM-1-13-tencentos ~]# helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories

2.更新本地缓存
[root@VM-1-13-tencentos ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈Happy Helming![root@VM-1-13-tencentos ~]# helm install cert-manager jetstack/cert-manager \
> --namespace cert-manager \
> --create-namespace \
> --version v1.10.0 \
> --set ingressShim.defaultIssuerName=letsencrypt-prod \
> --set ingressShim.defaultIssuerKind=ClusterIssuer \
> --set ingressShim.defaultIssuerGroup=cert-manager.io \
> --set installCRDs=true

NAME: cert-manager
LAST DEPLOYED: Sat Jul 15 12:11:06 2023
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.10.0 has been deployed successfully!

3.为 Cert-manager 创建 ClusterIssuer,用来提供签发机构
[root@VM-1-13-tencentos ~]# cat cluster-issuer.yaml 
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: "[email protected]"
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx
          
4.安装和配置 Harbor
[root@VM-1-13-tencentos ~]# helm repo add harbor https://helm.goharbor.io
"harbor" has been added to your repositories

[root@VM-1-13-tencentos ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "harbor" chart repository
Update Complete. ⎈Happy Helming!⎈

4.1 定制化安装 Harbor,所以需要修改 Harbor 的安装参数
[root@VM-1-13-tencentos ~]# cat values.yaml 
expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: "harbor-secret-tls"
      notarySecretName: "notary-secret-tls"
  ingress:
    hosts:
      core: harbor.yinwucheng.com
      notary: notary.yinwucheng.com
    className: nginx
    annotations:
      kubernetes.io/tls-acme: "true"
persistence:
  persistentVolumeClaim:
    registry:
      size: 20Gi
    chartmuseum:
      size: 10Gi
    jobservice:
      jobLog:
        size: 10Gi
      scanDataExports:
        size: 10Gi
    database:
      size: 10Gi
    redis:
      size: 10Gi
    trivy:
      size: 10Gi

[root@VM-1-13-tencentos ~]# helm install harbor harbor/harbor -f values.yaml --namespace harbor --create-namespace

NAME: harbor
LAST DEPLOYED: Sat Jul 15 12:16:17 2023
NAMESPACE: harbor
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://core.harbor.domain
For more details, please visit https://github.com/goharbor/harbor

配置DNSPod解析
[root@VM-1-13-tencentos ~]# kubectl get services --namespace ingress-nginx ingress-nginx-controller --output jsonpath='{.status.loadBalancer.ingress[0].ip}'
【GitOps系列】自动化构建Image_第9张图片
访问 Harbor Dashboard

首先确认 Cert-manager 是否已经成功签发了 HTTPS 证书?
[root@VM-1-13-tencentos ~]# kubectl get certificate -A
NAMESPACE   NAME                READY   SECRET              AGE
harbor      harbor-secret-tls   True    harbor-secret-tls   5m2s
harbor      notary-secret-tls   True    notary-secret-tls   5m2s

由于在部署 Harbor 的时候需要配置两个域名,所以这里会出现两个证书。当这两个证书的 Ready 状态都为 True 时,说明 HTTPS 证书已经签发成功了。此外, Cert-manager 自动从 Ingress 对象中读取了 tls 配置,还自动创建了名为 harbor-secret-tls 和 notary-secret-tls 两个包含证书信息的 Secret。

【GitOps系列】自动化构建Image_第10张图片

在 Tekton Pipeline 中使用 Harbor

要在 Tekton Pipeline 中使用 Harbor,需要将 Pipeline 中的spec.params.registry_url变量值由 docker.io 修改为 harbor.yinwucheng.com,并且将spec.params.registry_mirror变量值修改为infra。可通过 kubectl edit 命令来修改。

$ kubectl edit Pipeline github-trigger-pipeline
...
  params:
  - default: harbor.yinwucheng.com  # 修改为自己的域名
    name: registry_url
    type: string
  - default: "infra"  # 仓库名称
    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 推送的新镜像。
【GitOps系列】自动化构建Image_第11张图片
【GitOps系列】自动化构建Image_第12张图片
【GitOps系列】自动化构建Image_第13张图片

附:

Harbor 生产建议:

  1. 确认 PVC 是否支持在线扩容。
  2. 尽量使用 S3 作为镜像存储系统。
  3. 使用外部数据库和 Redis。
  4. 开启自动镜像扫描和阻止漏洞镜像。

参考资料:
https://time.geekbang.org/column/article/623839
https://www.lixueduan.com/posts/tekton/01-deploy-tekton/#

你可能感兴趣的:(CLOUD,NATIVE,#,Kubernetes,CI/CD,云原生,kubernetes,GitOps)