Tekton 如何接入物理機進行構建

1. 爲什麼需要物理構建機

在文章《如何接入遠程 macOS 物理機進行 Jenkins 流水線構建》中,我描述了在 Jenkins 中添加物理構建機的方法。這並不是我拍腦袋想的需求,而是當時真的有 ToB 的商業客戶在諮詢方案。

對於多端開發商來說,構建 Android、IOS、macOS、Arm 、Windows、X86 應用是常見的需求。

好的方面是 GitHub Actions 提供了 macOS 構建環境、AWS 提供了 macOS 虛擬機,而華爲提供了 ARM 主機。在雲原生背景下,更多使用的是 Kubernetes 運行時,在 Kubernetes 不支持的處理器架構和操作系統面前,持續集成 (CI) 顯得很無力。持續集成需要支持物理構建機。

本文希望討論的問題是在 Kubernetes 下,如何接入物理機進行 CI 的構建。本文以 Tekton 爲例,其他引擎在處理邏輯上類似。

2. Tekton 如何與物理機交互

Kuberntes 對物理機或者虛擬機的管理,實際上是一個典型的 Operator 場景。我們可以定義一個 CRD 用來描述相關字段,通過寫 Controller 處理 Pod 與構建機之間的邏輯。

也可以寫 Tekton 的 Task 封裝,本文將使用這種方式。由此也給我帶來另一個疑問,Tekton 能否代替部分 Operator 的場景,在後續的文章中我會給出思考。

這裏僅做原型驗證,不會太關注產品化的細節。

在 Tekton 中,每個流水線由很多個 Task 構成,Task 可以並行。一個 Task 包含很多個串行的 step 步驟,對應着一個 Pod 包含很多個容器。

這裏的關鍵是要將 Pod 與構建機關聯起來。我選擇的是使用 rsync 同步 Pod 與構建機之間的文件,在 Pod 中使用 sshpass 執行物理機的構建命令。

主要分爲如下步驟 (以下命令都是在容器中執行):

  1. 克隆代碼

  2. 執行 rsync 將代碼同步到構建機

  3. 執行 sshpass 在構建機上執行構建命令

  4. 執行 rsync 將構建機中的構建產物同步到容器

  5. 歸檔構建產物 (示例中, 這一步會被省略,僅驗證能拿到構建產物)

可以看到整個過程其實和 Tekton 沒有直接關係,對於任意容器與構建機直連的環境都是可行的。下面以 Tekton 爲例進行演示。

3. 資源準備清單

3.1 查看 Kubernetes 版本

kubectl version

Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.7", GitCommit:"1dd5338295409edcfff11505e7bb246f0d325d15", GitTreeState:"clean", BuildDate:"2021-01-13T13:23:52Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-21T01:11:42Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

3.2 物理機準備

uname -a

Linux test 3.10.0-957.21.3.el7.x86\_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86\_64 x86\_64 x86\_64 GNU/Linux

原計劃是選擇一個 macOS 的構建示例,但是無法提供直通的網絡環境,因此換成 Golang 的構建示例。

go version

go version go1.13 linux/amd64

4. 準備 Tekton 以及 Pipeline 資源

4.1 部署 Tekton Pipeline

Tekton 默認使用的是 gcr.io 鏡像,如果是國內環境可以替換爲 gcr.azk8s.cn 鏡像。

kubectl apply -f https://github.com/tektoncd/pipeline/releases/download/v0.23.0/release.notags.yaml
kubectl -n tekton-pipelines get all

NAME                                               READY   STATUS    RESTARTS   AGE
pod/tekton-pipelines-controller-86c487c965-p6s5t   1/1     Running   0          51s
pod/tekton-pipelines-webhook-7b775d9cd8-fzdrq      1/1     Running   0          51s

NAME                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                              AGE
service/tekton-pipelines-controller   ClusterIP   10.233.61.46    <none>        9090/TCP,8080/TCP                    51s
service/tekton-pipelines-webhook      ClusterIP   10.233.46.233   <none>        9090/TCP,8008/TCP,443/TCP,8080/TCP   51s

NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/tekton-pipelines-controller   1/1     1            1           51s
deployment.apps/tekton-pipelines-webhook      1/1     1            1           51s

NAME                                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/tekton-pipelines-controller-86c487c965   1         1         1       51s
replicaset.apps/tekton-pipelines-webhook-7b775d9cd8      1         1         1       51s

NAME                                                           REFERENCE                             TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/tekton-pipelines-webhook   Deployment/tekton-pipelines-webhook   <unknown>/100%   1         5         1          51s

4.2 資源規劃

需要的流水線資源清單:

4.2 編寫同步文件、執行腳本的 Task

如上圖,這裏的 Task 就是用於打通 container 和 vm 直接的文件和進程,實現類似交叉編譯的效果。

\---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: remote-shell
  labels:
    app.kubernetes.io/version: "0.1"
  annotations:
    tekton.dev/pipelines.minVersion: "0.12.1"
    tekton.dev/tags: git
    tekton.dev/displayName: "remote shell"
spec:
  description: >-
    This task can be used to run shell in remote machine
  workspaces:
  - name: source
  params:
  - name: remote-ip
    type: string
  - name: remote-port
    type: string
  - name: remote-username
    type: string
  - name: remote-password
    type: string
  - name: remote-workspace
    type: string
  - name: remote-script
    type: string
  steps:
  - name: remote-shell
    image: shaowenchen/rsync-sshpass:v1
    workingDir: $(workspaces.source.path)
    script: |
      sshpass  -p "$(params.remote-password)" ssh -o StrictHostKeyChecking=no "$(params.remote-username)"@"$(params.remote-ip)" -p "$(params.remote-port)" "mkdir -p $(params.remote-workspace)"

      rsync -ratlz --progress --rsh="sshpass -p $(params.remote-password) ssh -o StrictHostKeyChecking=no -l $(params.remote-username)" ./ "$(params.remote-ip)":"$(params.remote-workspace)"

      sshpass  -p "$(params.remote-password)" ssh -o StrictHostKeyChecking=no "$(params.remote-username)"@"$(params.remote-ip)" -p "$(params.remote-port)" "$(params.remote-script)"

      rsync -ratlz --progress --rsh="sshpass -p $(params.remote-password) ssh -o StrictHostKeyChecking=no -l $(params.remote-username)" "$(params.remote-ip)":"$(params.remote-workspace)"/ .

在寫法上,可以參考 Tekton 提供的示例。主要分爲幾步:

這就是一個串腳本的過程,只不過藉助容器鏡像,省去了安裝各種工具的步驟。

4.3 準備 Tekton 的 pipeline 描述

Tekton 已經正式上線 Hub 服務,用於共享 Task,這裏直接使用 https://hub.tekton.dev/tekton/task/git-clone

kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.3/git-clone.yaml

Dockerfile 爲:

ARG alpine\_ver=3.13
FROM alpine:${alpine\_ver}.5

RUN apk update \\
 && apk upgrade \\
 && apk add --no-cache \\
            rsync \\
            openssh-client \\
            openssh \\
            sshpass \\
            ca-certificates \\
 && update-ca-certificates \\
 && rm -rf /var/cache/apk/\*
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: remote-build-pipeline
spec:
  params:
  - name: repo-url
    type: string
  - name: branch-name
    type: string
  - name: remote-ip
    type: string
  - name: remote-port
    type: string
  - name: remote-username
    type: string
  - name: remote-password
    type: string
  - name: remote-workspace
    type: string
  - name: remote-script
    type: string
  workspaces:
  - name: shared-data
  tasks:
  - name: fetch-repo
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    params:
    - name: url
      value: $(params.repo-url)
    - name: revision
      value: $(params.branch-name)
  - name: remote-build
    taskRef:
      name: remote-shell
    runAfter: \["fetch-repo"\]
    workspaces:
    - name: source
      workspace: shared-data
    params:
    - name: remote-ip
      value: $(params.remote-ip)
    - name: remote-port
      value: $(params.remote-port)
    - name: remote-username
      value: $(params.remote-username)
    - name: remote-password
      value: $(params.remote-password)
    - name: remote-workspace
      value: $(params.remote-workspace)
    - name: remote-script
      value: $(params.remote-script)

pipeline 包含兩個 task,一個 task 克隆代碼,一個 task 執行遠程構建。

\---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: remote-build-pipelinerun-1
spec:
  pipelineRef:
    name: remote-build-pipeline
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
  params:
  - name: repo-url
    value: https://github.com/shaowenchen/terraform-provider-qingcloud.git
  - name: branch-name
    value: master
  - name: subdirectory
    value: terraform-provider-qingcloud-001
  - name: remote-ip
    value: 0.0.0.0
  - name: remote-port
    value: "22"
  - name: remote-username
    value: root
  - name: remote-password
    value: YourPassword
  - name: remote-workspace
    value: ~/workspaces/terraform-provider-qingcloud-001
  - name: remote-script
    value: |
        cd ~/workspaces/terraform-provider-qingcloud-001
        make

這裏將克隆代碼到 pv 的 terraform-provider-qingcloud-001 目錄,同步到構建機的 ~/workspaces/terraform-provider-qingcloud-001 目錄。也就是說,這兩個目錄最終的文件會保持一致,而構建的二進制是在構建機上生成的。

以上資源全部 apply 之後,就可以查看相關的資源和流水線狀態了。

kubectl get task

NAME           AGE
git-clone      18m
remote-shell   5m47s
kubectl get pipelinerun

NAME                         SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
remote-build-pipelinerun-1   True        Succeeded   6m15s       5m42s

5. 功能驗證

kubectl get pod

NAME                                                      READY   STATUS      RESTARTS   AGE
remote-build-pipelinerun-1-fetch-repo-56ws8-pod-mgx77     0/1     Completed   0          8m49s
remote-build-pipelinerun-1-remote-build-wxtms-pod-bcn6r   0/1     Completed   0          8m35s
pwd

/root/workspaces/terraform-provider-qingcloud-001

ls

CHANGELOG.md  glide.yaml  go.sum   main.go   qingcloud  scripts    terraform-provider-qingcloud  website
dev.md        go.mod      LICENSE  Makefile  README.md  terraform  vendor
kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                              STORAGECLASS       
pvc-860016bb-14b6-414a-9c5a-1a71d7290ba8   10Gi       RWO            Delete           Bound    default/pvc-e7ceb0582a                                             openebs-hostpath            2m12s
kubectl describe pv pvc-860016bb-14b6-414a-9c5a-1a71d7290ba8 |grep Path

    Path:  /var/openebs/local/pvc-860016bb-14b6-414a-9c5a-1a71d7290ba8
ls /var/openebs/local/pvc-860016bb-14b6-414a-9c5a-1a71d7290ba8

CHANGELOG.md  glide.yaml  go.sum   main.go   qingcloud  scripts    terraform-provider-qingcloud  website
dev.md        go.mod      LICENSE  Makefile  README.md  terraform  vendor

6. 總結

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。