Skip to main content

玩转 Gitea Actions

· 28 min read

Gitea 1.19.0 包含了一个新功能,叫做 Gitea Actions。 这是一个 Gitea 内置的 CI 系统,类似于 GitHub Actions,并与其兼容。

这篇博客将介绍 Gitea Actions 的一些细节,包括如何使用它以及它是如何工作的。 需要注意的是,文中的信息是基于当前版本的,所以可能会在未来过时。

如何使用

Feature Preview: Gitea Actions,我们已经讨论过如何试用它。 但是,随着开发工作的推进,有一些情况已经变了,所以我们有必要重新梳理一下。

配置 Gitea 实例

首先,你需要一个 Gitea 实例,版本为 1.19.0 或更高。 你可以参考文档来安装一个新的实例,或者升级你现有的实例。 无论你已什么方式安装或运行 Gitea,都可以启用 Actions。

Actions 功能默认是禁用的(因为它仍然是一个正在开发中的功能),所以你需要在配置文件中添加以下内容来启用它:

[actions]
ENABLED=true

如果你想了解更多配置信息,或者在配置过程中遇到了问题,请参考Configuration Cheat Sheet

配置 Runner

我们有一个基础版本的 Gitea Actions Runner,叫做 act runner。 它是基于 act 项目实现的.

目前,安装 runner 的方式仅限于自行编译或者使用预编译的二进制文件。 我们还没有提供 Docker 镜像或其他基于包管理安装方式。 启动 runner 也是简单地通过命令行执行。 当然,你也可以自行将这个二进制文件包装成比如系统服务、supervisord 或 Docker 容器的来方便运行。

但在你包装它开始之前,我们建议你先将它作为命令行运行一下,以确保它能够在你的环境中正常工作,特别是如果你是在本地主机上运行 runner。 此外,如果条件允许,我们建议将 runner 部署在独立的机器上,以避免与 Gitea 实例争用资源而影响后者运行。

另外,确保你已经安装了 Docker。 虽然 Docker 不是严格必须的,但是通常情况下我们需要它。 我们将在后面讨论这个问题。

在启动一个 runner 执行任务之前,你需要先将它注册到你的 Gitea 实例上,命令如下:

./act_runner register --no-interactive --instance <instance> --token <token>

这里有两个必需参数,instancetoken

instance 是你的 Gitea 实例的地址,比如 http://192.168.8.8:3000https://gitea.com。 runner 自身和 job 容器(runner 启动的用于执行任务的容器)将会连接到这个地址。 这意味着它可以与 Gitea 实例的 ROOT_URL 不一样,后者是用于 web 访问的。 注意,使用 127.0.0.1localhost 这样的回环地址是一个糟糕的做法,我们将在后面讨论为什么。 如果你不确定使用该哪个地址,那么使用局域网地址通常情况下不会有错。

token 是用于识别和认证的,形如 P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23。 它是一次性的,不能用于注册多个 runner。 你可以在 your_gitea.com/admin/runners 页面获取 token。

注册 runner

如果你不能看到这个页面,那么可能是你使用的 Gitea 版本不正确,或者没有启用 Actions。 请检查上面的步骤。

注册完成后,一个名为 .runner 的新文件将会出现在当前目录。 这个文件存储了注册信息。 请不要手动编辑它。 如果这个文件丢失或损坏,你可以直接删除它并重新注册。

最后,我们可以启动 runner 了。

./act_runner daemon

然后,你可以在 Gitea 实例的管理页面看到新的 runner:

查看 Runner

你想了解更多的注册参数,可以查看 gitea/act_runner

使用 Actions 执行任务

即使 Actions 功能在实例上已经启用,代码仓库仍然是默认禁用 Actions 的。

你可以在仓库的设置页面启用它:

启用 Actions

接下来就比较复杂了,你需要学习 workflow 语法 并编写你想要的 workflow 文件。

但别担心,我们可以先从一个简单的例子开始:

name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ gitea.status }}."

你可以将这个文件保存为 YAML 文件并上传到仓库的 .gitea/workflows/ 目录下,例如 .gitea/workflows/demo.yaml。 你可以已经注意到了,这个文件和 Quickstart for GitHub Actions 中的示例文件非常相似。 这是因为 Gitea Actions 在设计上就是为了与 GitHub Actions 尽可能兼容。

注意,这个示例文件使用了一些 emoji。 请确保你的数据库支持它们,特别是在使用 MySQL 时。 如果字符集不是 utf8mb4,将会出现错误,例如 Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1。 请参阅 Database Preparation。 或者,你可以使用不带 emoji 的这个示例:

name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "This job's status is ${{ gitea.status }}."

on: [push] 一行表示这个 workflow 会在你推送提交时触发。 然而,当你上传这个文件时,它本身就会触发一个提交,所以你应该在 Actions 标签页中看到已经存在一个新的任务。

查看任务

干得漂亮!你已经成功用上 Actions 了。

问答

为什么 Actions 不是默认启用的?

我们知道启用 Actions(尤其是每个仓库都要单独启用)很烦人,但是,并不是每个人都喜欢或需要这个功能。 我们希望让用户自己决定是否启用它。

我们应该在 workflow 文件中使用 ${{ github.xyz }} 还是 ${{ gitea.xyz }}

你可以使用 github.xyz,Gitea 也能正常工作。 正如上面提到的,Gitea Actions 的设计目标就是与 GitHub Actions 尽可能兼容。
但是,我们建议你使用 gitea.xyz,以避免存在歧义(确保这个 workflow 在 Gitea 上使用,而不是 GitHub)。 不过还是得说明下,这是可选的,因为两个写法到目前为止效果是一样的。

可以只给特定的组织或仓库注册 runner 吗?

当然可以,这取决于你从哪里获取注册令牌。

  • /admin/runners: 这是实例级 runner,它会为实例中的所有仓库运行任务。
  • /org/<org>/settings/runners: 这是组织级 runner,它会为组织中的所有仓库运行任务。
  • /<owner>/<repo>/settings/runners: 这是仓库级 runner,它只会为它们所属的仓库运行任务。

如果你无法看到设置页面,请确保你有正确的权限并且已经启用了 Actions。

请注意,即使仓库有自己的仓库级 runner,它仍然可能使用实例级或组织级 runner。 我们可能会在未来提供选项来控制这一行为。

可以为特定的用户(而不是组织)注册 runner 吗?

目前还不行。 技术上是可以实现的,但我们需要讨论一下是否有必要支持这个功能。

Runner 的标签是干什么用的?

你可能已经注意到了,每个 runner 都形如有 ubuntu-latestubuntu-22.04ubuntu-20.04ubuntu-18.04 这样的标签。 这些标签是用于匹配相应的任务的。

举例来说,runs-on: ubuntu-latest 表示这个任务会在一个带有 ubuntu-latest 标签的 runner 上运行。 你也可以在注册 runner 时为它添加自定义标签,我们稍后会讨论这个。

当使用一个 action(比如 actions/checkout@v3)时,runner 会从哪里下载脚本?

你可能已经注意到,GitHub 上有成千上万的 marketplace actions。 但是,当你写 uses: actions/checkout@v3 时,它实际上会从 gitea.com/actions/checkout 下载脚本(不是 GitHub)。 这是 github.com/actions/checkout 的镜像。但是我们无法镜像所有的 action。 所以当你尝试使用一些没有被镜像的 action 时,可能会遇到失败。

好消息是,你可以指定 URL 前缀来从任何地方使用 action。 这是 Gitea Actions 的额外语法。 例如:

  • uses: https://github.com/xxx/xxx@xxx
  • uses: https://gitea.com/xxx/xxx@xxx
  • uses: http://your_gitea_instance.com/xxx@xxx

注意,https://http:// 前缀是必须的!

又或者,你想让你的 runner 默认就从 GitHub 或你自己的 Gitea 实例下载 action,你可以通过设置 [actions].DEFAULT_ACTIONS_URL 来配置它。 参考 Configuration Cheat Sheet.

这是 GitHub Actions 和 Gitea Actions 的一个区别,但它能让用户更灵活地运行 Actions。

如何限制 runner 的权限?

Runner 其实没什么权限(有权限连接到 Gitea 实例不算)。 当某 runner 收到一个任务时,它会暂时获得与任务相关的仓库权限,且这个权限也是有限的。 如果你想给 runner 更多的权限,比如让它访问更多的私有仓库或外部系统,你可以通过 secrets 传递给它。

对 Actions 进行更精细的权限控制是一个复杂的工作。 在未来,我们会为 Gitea 添加更多的选项,使其更加可配置,比如允许更多对仓库的写入权限或对同一组织中的所有仓库的读取权限。

如何避免被黑客攻击?

这里有两种可能的攻击方式:未知的 runner 从你的仓库中窃取代码或 Secrets,或者恶意脚本控制你的 runner。

避免前者的方法很简单,就是不要让你不信任的的人为你的仓库、组织或实例注册 runner。

但是后者就有点复杂了。 首先,如果你使用的是私有的 Gitea 实例,那么你可能不需要担心安全问题,因为你信任你的同事并且让他们对自己的行为负责。

对于公共实例,情况就有点不同了。 我们来看下在 gitea.com 上是怎么做的。

  • 我们只为 gitea 组织注册 runner,所以我们的 runner 不会执行其他仓库的任务。
  • 我们的 runner 总是使用隔离的容器来运行任务。虽然直接在主机上运行是可以的,但我们选择不这样做,以获得更好的安全性。
  • 来自 fork 仓库的 pull request 运行 actions 之前需要批准。参考 #22803
  • 如果有人为他们的仓库或组织在 gitea.com 上注册了自己的 runner,我们没有意见,但是我们不会在我们的组织中使用它。但是,他们应该注意确保 runner 不会被其他他们不认识的用户使用。

Act runner 支持哪些操作系统?

Act runner 支持 Linux、macOS 和 Windows。 虽然理论上也支持其他操作系统,但需要进一步测试。

有一点需要注意的是,如果你选择在主机上直接运行任务而不是在任务容器中运行,操作系统之间的环境差异可能会导致意想不到的失败。

举例来说,Windows 上通常没有 bash,而 act 默认使用 bash 来运行脚本。 因此,你需要在工作流文件中指定 powershell 作为默认 shell。

defaults:
run:
shell: powershell

设计细节

Gitea Actions 功能由多个部分组成。 让我们逐一解释它们。

Act

nektos/act 是一个棒极了的工具,它允许你在本地运行 GitHub Actions。 我们受到了这个项目的启发,联想到它可能可以为 Gitea 运行 actions。

然而,nektos/act 是被设计成一个命令行工具的,而我们实际上需要的是一个 Go 库,并为 Gitea 做一些适配。 所以我们将其 fork 为 gitea/act

这是一个软 fork,它会定期跟随上游。 虽然添加了一些自定义的提交,但我们会尽力避免改变太多原始代码。

这个 fork 的目的是为了适配 Gitea 的特定用法。 比如说这样的一些提交:

  • 输出执行日志到 logger 钩子,以便将其报告给 Gitea;
  • 禁用 GraphQL URL,因为 Gitea 不支持;
  • 为每个任务启动一个新的容器,而不是重用,以确保隔离。

这些修改没有理由合并到上游,如果用户只是想在本地运行受信任的操作,它们没有意义。

然而,如果未来有一些交叉的工作,比如对上游也适用 bug 修复或功能实现,我们会将这些修改提交给上游。

Act runner

Gitea 的 runner 被取名为 act runner,就是因为它基于 act。

和其他 CI runner 一样,我们将它设计为 Gitea 的外部部分,这意味着它应该在 Gitea 之外的服务器上运行。

为了确保 runner 连接到正确的 Gitea 实例,我们需要使用 token 进行注册。 此外,runner 还需要向 Gitea 做"自我介绍",声明它可以运行哪些类型的任务,这是通过上报它的标签来实现的。

前文中我们提到过,工作流文件中的 runs-on: ubuntu-latest 意味着该任务将在带有 ubuntu-latest 标签的 runner 上运行。 但是 runner 是怎么知道要如何运行 ubuntu-latest 的呢?答案就在于将标签映射到具体的运行环境上。 这就是为什么在注册 runner 时,如果想添加自定义标签时,你需要输入一长串内容,形如 my_custom_label:docker://centos:7。 这个例子的含义就是:runner 可以接受需要在 my_custom_label 上运行的任务,并通过 docker 容器来运行,且容器的镜像为 centos:7

然而,Docker 并不是唯一的选择。 act runner 也支持直接在主机上运行任务。 如果想实现在主机上运行任务,只需设置 linux_arm:host 这样的标签即可。 这个标签表示 runner 可以接受需要在 linux_arm 上运行的任务,并直接在主机上运行。

在设计上,标签的格式为 label[:schema[:args]]。 如果省略了 schema,那么它默认为 host。 所以:

  • my_custom_label:docker://node:18: 运行标签为 my_custom_label 的任务时,使用 Docker 运行,并以 node:18 作为镜像。
  • my_custom_label:host: 运行标签为 my_custom_label 的任务时,直接在主机上运行。
  • my_custom_label: 等同于 my_custom_label:host
  • my_custom_label:vm:ubuntu-latest: (仅作示例,未实现)运行标签为 my_custom_label 的任务时,使用虚拟机运行,并以 ubuntu-latest 作为镜像。

通信协议

由于 act runner 与 Gitea 是分离的,我们需要一种协议来让 runner 与 Gitea 实例进行通信。 但是我们不希望 Gitea 监听一个新的端口,而是复用已经监听的 HTTP 端口,这就需要一个与 HTTP 兼容的协议。 最后,我们选择了 gRPC over HTTP。

我们使用 actions-proto-defactions-proto-go 来将它们连接起来。 更多关于 gRPC 的信息可以在它的网站上找到。

网络架构

让我们来看看整体的网络架构。 这将有助于你解决一些问题,并解释为什么使用 Gitea 实例的回环地址注册 runner 是一个坏主意。

网络架构

这里有四个网络连接,箭头的方向表示建立连接的方向。

连接 1,act runner 到 Gitea 实例

act runner 需要连接到 Gitea 实例,以接收任务并将执行结果发送回去。

连接 2,任务容器到 Gitea 实例

任务容器有和 runner 拥有不同的网络命名空间,即使它们在同一台机器上。 它们需要连接到 Gitea 实例,例如工作流文件中有 actions/checkout@v3,则需要连接 Gitea 实例以获取代码。 当然,不是所有任务都需要获取代码,但在大多数情况下是必需的。

如果你使用回环地址来注册 runner,那么 runner 可以在同一台机器上连接到 Gitea,这没问题。 但是,如果任务容器尝试从 localhost 获取代码,问题就来了,它将无法连接上,因为 Gitea 不在同一个容器中。

连接 3,act runner 到互联网

当你使用某些 actions,比如 actions/checkout@v3,act runner 将下载用于执行操作脚本(注意不是在任务容器中下载)。 默认情况下,它从是 gitea.com 下载,因此需要访问互联网。 它还要下载一些 docker 镜像,默认是从 Docker Hub 下载,所以也需要访问互联网。

但即便如此,互联网访问也不是必须的。 你可以通过配置,使其从你的内网设施中所需的资源。

事实上,你的 Gitea 实例可以同时充当 actions 市场和镜像仓库。 你可以将 actions 仓库从 GitHub 镜像到你的 Gitea 实例,并一样地使用它们。 而且 Gitea Container Registry 也可以作为 Docker 镜像仓库使用。

连接 4,任务容器到互联网

当使用 actions/setup-go@v4 这样的 actions 时,可能需要从互联网下载资源以设置 Go 语言环境。 因此,如果想这些 actions 的成功执行,需要 runner 能够访问互联网。

不过,这也不是必须的。 你可以使用自定义 actions 来避免依赖互联网,或者使用打包好的、包含了所有依赖的 Docker 镜像来运行任务。

总结

使用 Gitea Actions 只需要确保 runner 可以连接到 Gitea 实例。 互联网访问是可选的,但如果没有它,将需要一些额外的工作。 换句话说:runner 最好能够连接到互联网,但你不需要将其暴露给互联网上。

如果你在使用 Gitea Actions 时遇到了网络问题,希望上面的图片可以帮助你解决它们。

问答

为什么选择 GitHub Actions?为什么不选择与 GitLab CI/CD 兼容?

@lunny实现 actions 的 issue中解释了这一点。 Actions 不仅仅是一个 CI/CD 系统,还是一个自动化工具。

此外,现在已经有了很多开源的 actions。 如果能复用它们,那就太激动人心了。

Gitea Actions 已经完全兼容 GitHub Actions 了吗?

还没有,我们正在努力实现它。

@ChristopherHX这条评论 中提供了有关 Gitea Actions 和 GitHub Actions 之间差异的精彩总结。

如果一个运行在多个标签上,例如runs-on:[label_a,label_b],会怎样?

这在语法上是有效的。 这意味着它应该在具有 label_a label_b 标签的 runner 上运行,参考 Workflow syntax for GitHub Actions。 不幸的是,act runner 不是这样工作的。 正如上面提到的,我们会将标签映射到具体的环境:

  • ubuntuubuntu:22.04
  • centoscentos:8

但是,我们真正要做的是将标签组映射到环境,如下所示:

  • [ubuntu]ubuntu:22.04
  • [with-gpu]linux:with-gpu
  • [ubuntu, with-gpu]ubuntu:22.04_with-gpu

不仅如此,我们还需要重新设计如何将任务分配给 runner。 具有 ubuntucentoswith-gpu 标签的 runner 并不一定表示它可以接受具有 [centos, with-gpu] 标签的任务。 因此,runner 应该告知 Gitea 实例,它只能接受具有 [ubuntu][centos][with-gpu][ubuntu, with-gpu] 标签的任务。 这不是技术问题,只是在早期设计中被忽略了。 请参见 runtime.go#L65

当前,act runner 尝试匹配所有标签,并使用找到的第一个匹配项。

客户端标签(agent labels)和自定义标签(custom labels)有什么区别?

标签

客户端标签(agent labels)是在 runner 注册时由 runner 上报给 Gitea 实例的。 而自定义标签(custom labels)则是由 Gitea 管理员、组织或仓库的所有者(取决于 runner 的级别)手动添加的。

但是,这里的设计需要改进,因为它目前有一些无法处理好的边缘情况。 你可以将自定义标签(custom labels)添加到已注册的 runner 上,例如 centos,这意味着 runner 将接收具有 runs-on: centos 的任务。 但是,runner 可能不知道该使用哪个环境(注册时并未指定),从而导致它使用默认镜像,否则就走进了逻辑死胡同。 而这个默认镜像可能与用户的预期不符。 参见 runtime.go#L71

在现在情况下,如果你想要更改它的标签,我们建议最好重新注册 runner。

未来会有更多的 Gitea Actions runner 实现吗?

虽然我们希望提供更多的选项,但是我们有限的人力意味着 act runner 将是唯一官方支持的 runner。 但是,Gitea 和 act runner 都是完全开源的,因此任何人都可以创建新的/更好的实现。 无论你如何决定,我们都会支持你的选择,。 而如果你 fork 了 act runner 并创建了自己的版本,在条件允许的情况下,我们希望你能将更改贡献回来,这会帮助到更多的人。

进一步了解

祝你和 Gitea Actions 玩的开心! 虽然它可能还不完美,但我们可以一起改进它。 你可以通过提交 issue 来上报问题,或加入我们的聊天室一起讨论。 当然,一如既往地,欢迎提交 pull request!