开源 Github Actions Self-Hosted Runner

GitHub 仓库地址: https://github.com/knatnetwork/github-runner

文档站点: https://runner.knat.network


其实也不算什么开源,只是拿出来,整理一下并和大家一起分享一下,顺便希望针对这个事情写一篇文章做点记录。

Why GitHub Actions

由于我对于 CI 的了解不是很多,但是偶尔还是会设计到一些 CI/CD 流水线的改造,以及一些自动化的动作(可能就是其他人所谓的 GitOps 啦),在使用过一些 CI 工具之后,对于不同的 CI 系统有着如下粗浅的认知和偏见:

  • GitHub Actions:只要关注 YML 就好,然后大量 use 他人的 step 来完成工作,文档齐全,编写容易,操作起来(尤其是对于取消任务,查看日志等操作)比较卡顿
  • Jenkins:Java 一把梭,插件很多(装插件要重启),文件即数据库,不好做高可用,容易出现各种各样的 Bug,Groovy 脚本看上去非常头痛,K8s 插件安装好后可以直接调度集群在 Pod 中跑负载好评
  • Travis CI:从排队,到几乎排不到队,到不可用,语法和 GitHub Actions 类似,但是…服务不太好用
  • Prow:All in K8s,理解/安装/使用起来有点迷惑
  • 自己造 DSL 封装 Jenkins:人类迷惑行为

当然可能各位还有见过一些什么把 PR 上的 CI 叫 测试CI,合并后才开始跑的测试叫 合并CI,每天只跑一次 master 分支的叫 日常CI ,然后本来就应该 per commit 触发的 CI 测试叫做「左移测试」的这种生造名词以给大家和维护者产生困扰的事情也就不在这个讨论范围内了。

哦,当然

由于我的需求在于:打镜像,部署镜像,跑测试,跑一些 奇 妙 的负载,从上述了解来看,GitHub Actions 无疑是最合适的选择了。

Why Self-hosted

很简单,我要 ARM64 的 Runner,我需要更多的内存和 CPU,我希望使用到内网资源,这些官方的 2C7G 的小 Runner 完全没法满足我的某些需求,加上大量已经通过 GitHub Actions 写好的流水线迁移起来有额外的成本,自托管 Runner 无疑是一个最佳的选择。

目前市面上有不少 Runner 的 Operator,可以很方便地在集群中 apply 一个 YML,然后就有了弹性调度 Runner,但是苦于一直没有找到一个可以方便部署的,单机的,没有那么复杂的方案,所以只要自己造轮子,做出来了这么个 Runner,从个人使用,到公司内部使用,目前看上去 it really works.

而且在内部使用的过程中,进行了一些蜜汁优化,比如:GitHub Actions Self-Hosted Runner 优化——Golang 相关内网缓存

由于在我们自己的使用场景下有着不错的体验,我打算把这个方案公开出来与大家一起分享。

Topology

整个 Self-hosted Runner 的结构如下:

第一个服务被我称为 KMS,它存储着 Personal Access Token,其余 Runner 服务通过和 ‘KMS’ 服务通信,拿到自己的 Registration Code 并向 GitHub 注册自己,防止 Token 在 Runner 中被攻击者偷走拿去搞事。

关于这个 KMS 以及偷 Token 的方式可以见我之前的文章「关于从 GitHub Actions Self-Hosted Runner 中偷 Secrets/Credentials 的一些安全研究

其次就是 Runner, 其实就是一个基于 ubuntu:20.04 构建出来的 Docker 容器,装了一些必要的软件并加入了 GitHub Runner 组件,如果在 CI 任务中需要使用到 Docker 的一些功能,可以启动一个 DinD 或者直接暴露宿主机的 Docker Sock(虽然我不推荐这么做)。

如果是希望单机部署的话,只要像下面一样糊一个 docker-compose.yml (并写一下 config.json)就可以启动了:

version: '3'

services:
  runner:
    image: knatnetwork/github-runner:focal-2.290.1
    restart: always
    environment:
      RUNNER_REGISTER_TO: 'knatnetwork'
      RUNNER_LABELS: 'docker,knat'
      KMS_SERVER_ADDR: 'http://kms:3000/'
      GOPROXY: 'http://goproxy.knat.network,https://proxy.golang.org,direct'
      ADDITIONAL_FLAGS: '--ephemeral'
    depends_on:
      - kms
  
  kms:
    image: knatnetwork/github-runner-kms:latest
    restart: always
    volumes:
      - ./config.json:/usr/src/app/config.json

你看,很简单是不是,不需要污染本地环境,也不需要搞什么 Operator,docker-compose up -d,然后你的 Runner 就上线了,多快乐!

如果你不再需要它了的话,就 docker-compose down,它会自己从 GitHub 上删除自己,不留一点垃圾。

如果你已经有一个 K8s 环境,并且希望快速扩/缩容的话,可以写一个 Deployment 来完成,鉴于篇幅,大伙儿可以直接参考文档站:Kubernetes | GitHub Actions Runner

KNAT

在把 Runner 公开前我想过很多方式,譬如弄一个新的域名和名字来维护,或者直接挂在 n0vad3v 账户和 nova.moe 的一个子域名下,感觉都不是很合适,正好看到之前 G2FS 和 G2WW 都已经使用了 knat.network 这个域名,于是便想到:嘿,为什么不直接使用这个名字来做点事情呢?

强哥也曾经这么说过:

把事情放到一个大的框架下做

Twitter@zhouqiang_cl

关于 KNAT:这其实是我大学时期的一个…想法,也是毕业设计的内容,它尝试模拟了 Cloudflare 的一个服务并将反代的效率提升了 20%(当然是以内存和算力的代价换来的)。

至于 KNAT 这个名字代表着什么,容我摘录一段毕业论文上的文字(请无视某个 Typo)

以上,Have Fun.