This page looks best with JavaScript enabled

How Tekton Connects to Physical Machines for Builds

 ·  ☕ 10 min read

1. Why Physical Build Machines Are Needed

In the article How to Add a Remote macOS Physical Machine for Jenkins Pipeline Builds, I described how to add a physical build machine in Jenkins. This was not a requirement I made up on a whim — at the time, real ToB commercial customers were actually asking for a solution.

For multi-platform developers, building Android, iOS, macOS, Arm, Windows, and X86 applications is a common requirement.

The good news is that GitHub Actions provides a macOS build environment, AWS provides macOS virtual machines, and Huawei provides ARM hosts. In the cloud-native context, the Kubernetes runtime is used more and more, and in the face of processor architectures and operating systems that Kubernetes does not support, continuous integration (CI) appears rather powerless. Continuous integration needs to support the physical build machine runtime.

The question this article hopes to discuss is how, under Kubernetes, to connect to a physical machine for CI builds. This article uses Tekton as an example; other engines handle the logic in a similar way.

2. How Tekton Interacts with Physical Machines

Managing physical machines or virtual machines with Kubernetes is actually a typical Operator scenario. We can define a CRD to describe the relevant fields, and write a Controller to handle the logic between the Pod and the build machine.

We can also write a Tekton Task wrapper, which is the approach this article uses. This also raises another question for me: whether Tekton can replace some Operator scenarios. I will give my thoughts in a follow-up article.

Here we are only doing prototype validation and will not pay much attention to productization details.

In Tekton, each pipeline consists of many Tasks, and Tasks can run in parallel. A Task contains many serial steps, corresponding to a Pod containing many containers.

The key here is to associate the Pod with the build machine. I chose to use rsync to synchronize files between the Pod and the build machine, and to use sshpass inside the Pod to execute build commands on the physical machine.

The main steps are as follows (the following commands are all executed inside the container):

  1. Clone the code
  2. Run rsync to synchronize the code to the build machine
  3. Run sshpass to execute the build command on the build machine
  4. Run rsync to synchronize the build artifacts from the build machine to the container
  5. Archive the build artifacts (in this example this step is omitted; we only verify that the build artifacts can be obtained)

As you can see, the whole process actually has nothing directly to do with Tekton; it is feasible for any environment with a direct connection between a container and a build machine. Below we use Tekton as an example for the demonstration.

3. Resource Preparation Checklist

  • A Kubernetes cluster. Used to run Tekton; the latest Tekton 0.23 requires Kubernetes no lower than 1.17
  • A physical machine or virtual machine. Used to build the application

3.1 Check the Kubernetes Version

1
2
3
4
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 Physical Machine Preparation

  • The operating system is CentOS 7.6
1
2
3
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
  • Pre-install the Golang compilation environment

The original plan was to choose a macOS build example, but a direct network environment could not be provided, so it was changed to a Golang build example.

1
2
3
go version

go version go1.13 linux/amd64

4. Prepare the Tekton and Pipeline Resources

4.1 Deploy Tekton Pipeline

  • Create the workload

Tekton uses gcr.io images by default; for environments in China you can replace them with the gcr.azk8s.cn mirror.

1
kubectl apply -f https://github.com/tektoncd/pipeline/releases/download/v0.23.0/release.notags.yaml
  • View the resources
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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 Resource Planning

The required pipeline resource manifest:

  • One task, used to clone the code
  • One pv, used to share files between tasks
  • One custom task, used to synchronize the code to the build machine and, after the build completes, synchronize it back
  • One pipeline, used to describe the pipeline and orchestrate the tasks
  • One pipelinerun, used to instantiate the pipeline and provide the parameters necessary for the build

4.2 Write the Task for Synchronizing Files and Executing Scripts

As shown in the figure above, the Task here is used to connect the files and processes between the container and the vm, achieving an effect similar to cross-compilation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
---
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)"/ .        

For the approach, you can refer to the examples provided by Tekton. It is mainly divided into a few steps:

  • Define the parameters
  • Write the step flow
  • Write the script

This is just a process of stringing together a script, except that with the help of a container image, the step of installing various tools is saved.

4.3 Prepare the Tekton pipeline description

  • Clone code Task

Tekton has officially launched the Hub service for sharing Tasks; here we directly use https://hub.tekton.dev/tekton/task/git-clone

1
kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.3/git-clone.yaml
  • Build a toolbox image shaowenchen/rsync-sshpass:v1

The Dockerfile is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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/*
  • pipeline
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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)

The pipeline contains two tasks: one task clones the code, and one task performs the remote build.

  • pipelinerun
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
---
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        

Here the code is cloned into the terraform-provider-qingcloud-001 directory of the pv, and synchronized to the ~/workspaces/terraform-provider-qingcloud-001 directory of the build machine. That is to say, the files in these two directories will ultimately stay consistent, while the built binary is generated on the build machine.

  • View the Tekton resource definitions

After all the above resources are applied, you can view the related resources and pipeline status.

1
2
3
4
5
kubectl get tasks.tekton.dev

NAME           AGE
git-clone      18m
remote-shell   5m47s
1
2
3
4
kubectl get pipelines.tekton.dev

NAME                    AGE
remote-build-pipeline   4m21s
1
2
3
4
kubectl get pipelineruns.tekton.dev

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

In pipelineruns you can use describe to get the record of the entire pipeline execution, used for displaying execution steps and querying build logs. Below is an excerpt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Task Runs:
    remote-build-pipelinerun-1-fetch-repo-56ws8:
      Pipeline Task Name:  fetch-repo
      Status:
        Completion Time:  2021-04-27T13:22:08Z
        Conditions:
          Last Transition Time:  2021-04-27T13:22:08Z
          Message:               All Steps have completed executing
          Reason:                Succeeded
          Status:                True
          Type:                  Succeeded
        Pod Name:                remote-build-pipelinerun-1-fetch-repo-56ws8-pod-mgx77
        Start Time:              2021-04-27T13:21:54Z
        Steps:
          Container:  step-clone
          Image ID:   docker-pullable://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:db18a9c1607c8cbbcd72f61d0c4d795b9ff528669deacd5f8a1672e4ef198ffd
          Name:       clone
          Terminated:
            Container ID:  docker://e5258bc7b0770e0333a93395eda13514abbd293652c0c0494352407a3fbc1a7f
            Exit Code:     0
            Finished At:   2021-04-27T13:22:07Z
            Message:       [{"key":"commit","value":"d024b4deeb2f328098fed88eb702cb19dac8452f","type":"TaskRunResult"},{"key":"url","value":"https://github.com/shaowenchen/terraform-provider-qingcloud.git","type":"TaskRunResult"}]
            Reason:        Completed
            Started At:    2021-04-27T13:22:04Z
        Task Results:
          Name:   commit
          Value:  d024b4deeb2f328098fed88eb702cb19dac8452f
          Name:   url
          Value:  https://github.com/shaowenchen/terraform-provider-qingcloud.git

5. Functionality Verification

  • View the related workloads
1
2
3
4
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
  • On the physical build machine, view the build directory
1
2
3
4
5
6
7
8
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
  • View the build directory of the Kubernetes PV
1
2
3
4
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

Find the PV storage path

1
2
3
kubectl describe pv pvc-860016bb-14b6-414a-9c5a-1a71d7290ba8 |grep Path

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

View the PV directory file structure

1
2
3
4
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

In both directories, the build artifact terraform-provider-qingcloud exists, which meets expectations and shows that we have achieved our goal.

6. Summary

A traditional CICD engine is usually a C/S architecture. It needs an S side to parse the process and schedule the pipeline; it needs many C sides to execute high-load build tasks. The scalability of this approach is not linear, and under cloud native, when the business volume is large, it easily hits a bottleneck. Therefore, we need a more cloud-native build engine. Under the new engine we need to solve some old problems, and supporting physical machine builds is one of them.

This article mainly uses Tekton as an example to provide an approach to connecting physical machines for builds using rsync and sshpass. The key points are as follows:

  • The purpose of using rsync\sshpass is mainly to bind the container and the physical machine together, with bidirectional file synchronization and interconnected process spaces.
  • Not limited to Tekton; any engine can use this approach.
  • This is only a solution verification; if it is to be put into production, issues such as caching, key security, data security, and tenant isolation still need to be considered.

7. Reference


微信公众号
WRITTEN BY
微信公众号