天天看點

在 Kubernetes 上執行 GitHub Actions 流水線作業

在 Kubernetes 上執行 GitHub Actions 流水線作業

GitHub Actions 是一個功能強大、“免費” 的 CI(持續內建)工具。

與之前介紹的 Tekton 類似,GitHub Actions 的核心也是 Pipeline as Code 也就是所謂的流水線即代碼。二者不同的是,GitHub Actions 本身就是一個 CI 平台,使用者可以使用代碼來定義流水線并在平台上運作;而 Tekton 本身是一個用于建構 CI/CD 平台的開源架構。

Pipeline as Code,既然與代碼扯上了關系。那流水線的定義就可繁可簡了,完全看需求。小到一個 GitHub Pages,大到流程複雜的項目都可以使用 GitHub Actions 來建構。

本篇文章不會介紹如何使用 GitHub Actions 的,如果還未用過的同學可以浏覽下官方的文檔。今天主要來分享下如何在 Kubernetes 上的自托管資源來執行流水線作業。

0x01 背景

在介紹 GitHub Actions 的時候,免費帶上了引号,為何?其作為一個 CI 工具,允許使用者定義流水線并在平台上運作,需要消耗計算、存儲、網絡等資源,這些運作流水線的機器稱為 Runner。GitHub 為不同類(等)型(級)的使用者每月提供了不同的免費額度(額度用完後,每分鐘 0.008 美元。),見下圖。不同類型的主機,分鐘數的消耗倍數也不同:Linux 為 1、macOS 為 10、Windows 為 2。

在 Kubernetes 上執行 GitHub Actions 流水線作業

拿免費使用者來看,每月 2000 分鐘看似也不少。比如筆者個人就是拿來建構下部落格靜态頁面,以及幾個簡單的應用,每個月也用不了太多。但對于企業或者組織來說,尤其是當流水線的觸發頻繁(每次代碼送出觸發)、或者項目的單元測試耗時很長(bug 引起的或者項目本身的複雜度所緻),積少成多也會變成一筆不小的開支。

那有沒有辦法使用自己的資源來運作流水線呢?有。GitHub Actions Runner 分為兩種:Github 托管的 Runner 和自托管的 Runner。我們可以将自己的資源作為自托管的 Runner 來運作流水線,而且還可以借助 Kubernetes 的能力來管理這些 Runner。

同時,自托管 Runner 也适合那些對 CI 有更高要求的使用者,比如更高性能、更多類型的計算資源等等。

0x02 準備工作

Kubernetes 叢集

我們使用 k3s 快速建立一個單節點的叢集。

export INSTALL_K3S_VERSION=v1.22.11+k3s2
curl -sfL https://get.k3s.io | sh -s - --disable traefik --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config
           

使用 Github Actions 建構的項目

這裡使用之前做的一個 graalvm+maven 的基礎鏡像倉庫來進行測試:https://github.com/addozhang/docker-graalvm-maven。

GitHub Access Token

參考文檔,建立 Access Token。

注意:鑒于本示範的需要,配置設定完全的 repo 通路權限。

在 Kubernetes 上執行 GitHub Actions 流水線作業

0x03 建立 Runner

GitHub Actions Runner 的程式源碼是開源的,GitHub 托管的 Runner 也是使用該程式運作的。

Runner 可以是某個倉庫使用,也可以由組織下的所有倉庫共享。鑒于這裡示範用的項目,我們為上面提到的項目倉庫建立 Runner。

Runner 在啟動時,會通過 GitHub API 将自己注冊到 GitHub Actions;然後不斷發送請求到 GitHub 檢視是否配置設定了流水線作業,如有則會執行流水線作業;Runner 停止運作時,需要進行登出的操作。這裡的一系列操作,都會用到上面建立的 Access Token。

要在 Kubernetes 上運作,我們要将 Runner 應用以及上面的邏輯打成鏡像,并以 Deployment 的方式部署到 Kubernetes 中。然後通過 workflow 作業 webhook 檢視排隊的作業數來決定是否增加或者減少 Deployment 的副本數。

聽起來就有些複雜,但确實是實作的基本方式。這裡我們使用一個 Kubernetes controller actions-runner-controller/actions-runner-controller 來實作所有的流程。

actions-runner-controller 提供了三種 CRD:

  • Runner:可以了解為

    Pod

    ,該 Runner 隻能執行一次作業。
  • RunnerDeployments:可以了解為

    Deployment

    ,可以設定要建立的 Runner 數量。Runner 在執行完作業後會銷毀,然後 Controller 會建立新的 Runner 等待作業排程。
  • RunnerSets:可以了解為

    StatefulSet

    ,也是基于

    StatefulSet

    來建立 Pod(即 Runner),提供

    StatefulSet

    的特性。

更多用法,可以參考 actions-runner-controller 官方文檔。

部署 Cert Manager

actions-runner-controller 的運作,需要使用 cert-mananger。通過下面的指令來部署:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.yaml
           

檢查 pod 是否成功啟動:

kubectl get pods -n cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-66b646d76-7b9fz               1/1     Running   0          18s
cert-manager-cainjector-59dc9659c7-rzlrl   1/1     Running   0          18s
cert-manager-webhook-7d8f555998-6zkqp      1/1     Running   0          18s
           

啟動 Controller

接下來就是啟動 controller,本文釋出時的最新版本是 v0.25.0。使用下面的指令部署 controller:

kubectl create -f https://github.com/actions-runner-controller/actions-runner-controller/releases/download/v0.25.0/actions-runner-controller.yaml
           
注:這裡使用的

create

而非

apply

。使用

apply

會報類似下面的錯誤:

Error from server (Invalid): error when creating "https://github.com/actions-runner-controller/actions-runner-controller/releases/download/v0.25.0/actions-runner-controller.yaml": CustomResourceDefinition.apiextensions.k8s.io "runnerdeployments.actions.summerwind.dev" is invalid: metadata.annotations: Too long: must have at most 262144 bytes

此時,pod 無法啟動,還需要為其建立 Secret 來提供 GitHub Access Token:

export GITHUB_TOKEN=<TOKEN_HERE>
kubectl create secret generic controller-manager \
    -n actions-runner-system \
    --from-literal=github_token=${GITHUB_TOKEN}
           

現在可以看到 pod 可以成功運作:

kubectl get pods -n actions-runner-system
NAME                                  READY   STATUS    RESTARTS   AGE
controller-manager-58c598f64d-27xn6   2/2     Running   0          40s
           

建立 Runner

執行下面的指令,建立一個名為

building-runner

Runner

CR。

kubectl apply -f - <<EOF
apiVersion: actions.summerwind.dev/v1alpha1
kind: Runner
metadata:
  name: building-runner
spec:
  repository: addozhang/docker-graalvm-maven
  env: []
EOF
           

此時,會發現 controller 建立了一個同名的 pod:

kubectl get pods -l actions-runner="" -n actions-runner-system
NAME              READY   STATUS    RESTARTS   AGE
building-runner   2/2     Running   0          16s
           

在倉庫的

Settings/Actions/Runners

中可以看到同名的 Runner,處于

idle

狀态。

在 Kubernetes 上執行 GitHub Actions 流水線作業

假如此時啟動流水線作業,會發現作業并沒有排程該 Runner 上。這是因為還沒有将作業 Runner 的 label 設定為該 runner 的 label:

self-hosted

Linux

X64

修改流水線定義,将

runs-on

指定為

[self-hosted, linux, X64]

在 Kubernetes 上執行 GitHub Actions 流水線作業

然後可以檢視作業成功排程到該 runner 上運作:

在 Kubernetes 上執行 GitHub Actions 流水線作業

現在再去看 pod 的狀态,其中的

runner

容器是

Completed

kubectl get pods -l actions-runner="" -n actions-runner-system
NAME              READY   STATUS     RESTARTS   AGE
building-runner   1/2     Running   0          3m53s
           

此時,再去觸發流水線執行,作業會一直等待可用的 runner 來執行:

在 Kubernetes 上執行 GitHub Actions 流水線作業

runner 無法重複使用,怎麼辦?

0x04 可重用 Runner

RunnerDeployment

要派上用場了,前面提到可以将

RunnerDeployments

了解為

Deployment

,可以設定要建立的 Runner 數量。Runner 在執行完作業後會銷毀,然後 Controller 會建立新的 Runner 等待作業排程。

kubectl apply -f - <<EOF
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: building-runner
spec:
  template:
    spec:
      repository: addozhang/docker-graalvm-maven
      env: []
EOF
           

使用上面的指令建立

RunnerDeployment

之後,controller 會建立新的 pod(runner)并得到作業的排程;完成作業的執行後,pod

building-runner-9k4cj-ml46v

被銷毀,新的 pod

building-runner-9k4cj-f8twz

被建立并等待作業的排程:

kubectl get events --sort-by='metadata.creationTimestamp'
...
3m7s        Normal    PodCreated                 runner/building-runner-9k4cj-ml46v         Created pod 'building-runner-9k4cj-ml46v'
3m6s        Normal    Scheduled                  pod/building-runner-9k4cj-ml46v            Successfully assigned actions-runner-system/building-runner-9k4cj-ml46v to ubuntu-dev1
3m7s        Normal    RegistrationTokenUpdated   runner/building-runner-9k4cj-ml46v         Successfully update registration token
3m6s        Normal    Pulling                    pod/building-runner-9k4cj-ml46v            Pulling image "summerwind/actions-runner:latest"
3m3s        Normal    Started                    pod/building-runner-9k4cj-ml46v            Started container docker
3m3s        Normal    Pulled                     pod/building-runner-9k4cj-ml46v            Successfully pulled image "summerwind/actions-runner:latest" in 3.06531539s
3m3s        Normal    Created                    pod/building-runner-9k4cj-ml46v            Created container runner
3m3s        Normal    Started                    pod/building-runner-9k4cj-ml46v            Started container runner
3m3s        Normal    Created                    pod/building-runner-9k4cj-ml46v            Created container docker
3m3s        Normal    Pulled                     pod/building-runner-9k4cj-ml46v            Container image "docker:dind" already present on machine
2m6s        Normal    RegistrationTokenUpdated   runner/building-runner-9k4cj-f8twz         Successfully update registration token
2m6s        Normal    PodCreated                 runner/building-runner-9k4cj-f8twz         Created pod 'building-runner-9k4cj-f8twz'
2m5s        Normal    Scheduled                  pod/building-runner-9k4cj-f8twz            Successfully assigned actions-runner-system/building-runner-9k4cj-f8twz to ubuntu-dev1
2m5s        Normal    Pulling                    pod/building-runner-9k4cj-f8twz            Pulling image "summerwind/actions-runner:latest"
2m5s        Normal    Killing                    pod/building-runner-9k4cj-ml46v            Stopping container docker
2m3s        Normal    Pulled                     pod/building-runner-9k4cj-f8twz            Successfully pulled image "summerwind/actions-runner:latest" in 2.50468863s
2m3s        Normal    Created                    pod/building-runner-9k4cj-f8twz            Created container runner
2m3s        Normal    Started                    pod/building-runner-9k4cj-f8twz            Started container runner
2m3s        Normal    Pulled                     pod/building-runner-9k4cj-f8twz            Container image "docker:dind" already present on machine
2m3s        Normal    Created                    pod/building-runner-9k4cj-f8twz            Created container docker
2m3s        Normal    Started                    pod/building-runner-9k4cj-f8twz            Started container docker
           

前面建立

RunnerDeployment

的時候沒有指定副本數,也就是 runner 的數量,controller 隻建立了一個 Runner。假設這個

RunnerDeployment

是某個組織下多個項目共用的,多個項目同時執行作業會怎樣?

這裡我重新運作 3 個已經完成的作業,模拟作業的并發:

curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/addozhang/docker-graalvm-maven/actions/runs/2643982502/rerun
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/addozhang/docker-graalvm-maven/actions/runs/2643982274/rerun
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/addozhang/docker-graalvm-maven/actions/runs/2643982274/rerun
           

或者 push 幾個空的送出到倉庫(不推薦,會有 commit 曆史。測試項目随意):

git commit --allow-empty -m "trigger action"
git push
           

去 Actions 清單會發現,隻有一個作業在運作,其他兩個都是

queued

的等待狀态。前面的作業執行完成後,後一個作業才會被執行。

不支援并發?既然是類似 Deployment 的方式運作 runner,那是否有類似 HPA(水準 pod 自動擴縮容)的功能?

0x05 自動伸縮

actions-runner-controller 在 3 個 Runner 的 CRD 之外,還提供了類似 HPA 的 CRD

HorizontalRunnerAutoscaler

,簡稱 HRA。

HRA 可以根據名額

PercentageRunnersBusy

或者

TotalNumberOfQueuedAndInProgressWorkflowRuns

來對 runner 進行擴縮容,或者基于 GitHub Events(webhook)來進行擴縮容。這兩種都各有優缺點,前者名額是通過 GitHub API 輪訓等待的作業數,實作簡單,但時效性差;後者基于事件觸發時效性更佳,但是實作複雜,需要對外暴露通路端點接收 GitHub Event。

這裡為了示範,使用基于名額的方式進行擴縮容。

kubectl apply -f - <<EOF
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
  name: building-runner-autoscaler
spec:
  scaleDownDelaySecondsAfterScaleOut: 30
  scaleTargetRef:
    name: building-runner
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: PercentageRunnersBusy
    scaleUpThreshold: '0.75'
    scaleDownThreshold: '0.25'
    scaleUpFactor: '2'
    scaleDownFactor: '0.5'
EOF
           

還是通過 API 觸發作業模拟并發執行,由于輪訓間隔比較長(預設 1 分鐘),自動擴容需要等待一段時間:

kubectl get pods -l actions-runner="" -n actions-runner-system
NAME                          READY   STATUS    RESTARTS   AGE
building-runner-k6jlc-dk7gc   2/2     Running   0          1m34s
building-runner-k6jlc-pbwnz   2/2     Running   0          54s
building-runner-k6jlc-nlptp   2/2     Running   0          54s
           

0x06總結

  • 如何限制 Runner 運作時的資源占用
  • 持久化如何實作,比如 Maven 建構時的本地庫,Nodejs 的 node_mudules 如何避免重複下載下傳
  • 自動擴縮容能否更加高效

繼續閱讀