资讯详情

在 Kubernetes 上执行 GitHub Actions 流水线作业

cfc52b65c34ea83ff13cf182443e18f2.gif

作者 | Addo Zhang

来源 | 云原生指北

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 实施流水线作业的自托管资源。

背 景

在介绍 GitHub Actions 为什么免费带引号?其作为一个 CI 工具允许用户定义装配线并在平台上运行,需要消耗计算、存储、网络等资源。这些操作装配线的机器被称为 Runner。GitHub 每月为不同类型(等)型(等级)的用户提供不同的免费额度(额度用完后每分钟) 0.008 美元。),见下图。不同类型的主机消耗不同的分钟倍数:Linux 为 1、macOS 为 10、Windows 为 2。

以免费用户为例,每月 2000 似乎每分钟都有很多。例如,作者个人用它来构建博客静态页面和几个简单的应用程序,而不是每个月使用太多。但对于企业或组织,特别是当流水线频繁触发(每次代码提交触发)时,或项目单元测试需要很长时间(bug 或项目本身的复杂性造成的),积少成多也会成为一笔不小的开支。

有没有办法利用自己的资源运营流水线?GitHub Actions Runner 分为两种:Github 托管的 Runner 和自托管的 Runner。我们可以把自己的资源作为自托管 Runner 运行流水线,也可以借助 Kubernetes 管理这些的能力 Runner。

同时,自托管 Runner 也适合那些对的人 CI 对用户高的用户,如性能较高、计算资源类型较多等。

准备工作

Kubernetes 集群

我们使用 k3s 快速创建单节点集群。

exportINSTALL_K3S_VERSION=v1.22.11 k3s2 curl-sfLhttps://get.k3s.io|sh-s---disabletraefik--write-kubeconfig-mode644--write-kubeconfig~/.kube/config

使用 Github Actions 构建的项目

这里用之前做的一个 graalvm maven 测试基本镜像仓库:https://github.com/addozhang/docker-graalvm-maven。

GitHub Access Token

创建参考文件 Access Token。

注:根据本演示的需要,完全分配 repo 访问权限。

access token

创建 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。部署以下命令:

kubectlapply-fhttps://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.yaml

检查 pod 成功启动:

kubectlgetpods-ncert-manager  NAMEREADYSTATUSRESTARTSAGE cert-manager-66b646d76-7b9fz1/1Running018s cert-manager-cainjector-59dc9659c7-rzlrl1/1Running018s cert-manager-webhook-7d8f555998-6zkqp1/1Running018s

启动 Controller

接下来是启动 controller,本文发表时的最新版本是 v0.25.0.使用以下命令部署 controller:

kubectlcreate-fhttps://github.com/actions-runner-controller/actions-runner-controller/relases/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 状态。

runner list

假如此时启动流水线作业,会发现作业并没有调度该 Runner 上。这是因为还没有将作业 Runner 的 label 设置为该 runner 的 label:self-hostedLinux、 X64

修改流水线定义,将 runs-on 指定为 [self-hosted, linux, X64]

custom labels

然后可以查看作业成功调度到该 runner 上运行:

schedule job

现在再去看 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 来执行:

queued job

runner 无法重复使用,怎么办?

可重用 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 自动扩缩容)的功能?

自动伸缩

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

总结

GitHub Actions 是个很强大的 CI 工具,结合自托管的 Runner 可以在付出较低成本的基础上有更好的体验。本文也只是从满足需求的出发,对 “Runner on Kubernetes” 进行了探索。对一个工具从会用到用好,还有很长的路要走。但满足当前需求,已是足矣。

有兴趣的同学可以更进一步思考:

  • 如何限制 Runner 运行时的资源占用

  • 持久化如何实现,比如 Maven 构建时的本地库,Nodejs 的 node_mudules 如何避免重复下载

  • 自动扩缩容能否更加高效

往期推荐

Redis 内存优化神技,小内存保存大数据

使用 nginx 轻松管理 kubernetes 资源文件

Redis 内存满了怎么办?这样置才正确!

实战 Kubectl 创建 Deployment 部署应用

标签: 53s光纤传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台