Cane's Blog

Cane

【CI/CD】基于 Gitea 与 Act Runner 的 CI/CD 流程搭建

6
2025-10-13

前言

在过去很长一段时间,我的 CI/CD 流程是围绕 Gogs + Jenkins 构建的。这套方案初期运行还算稳定,但随着服务器上部署的服务日益增多,有限的硬件配置开始捉襟见肘。此时,Jenkins 这类基于 Java 的内存消耗大户,便让我越看越不顺眼了。况且,抛开 Jenkins 不谈,Gogs 本身因为单一维护者的原因,功能迭代异常缓慢,许多我期待已久的特性迟迟未能更新,顺带着也让体验逐渐落得下乘。

为寻求替代方案,我曾尝试过 Gogs + Drone 的组合,但其使用体验同样未能完全满足我的需求。更重要的是,无论是 Jenkins 还是 Drone,其部分交付流程都强依赖于 Web UI 的手动配置。这种将配置与项目代码割裂开来的方式,不仅繁琐,也背离了我所期望的“CI/CD as Code”理念——即尽可能的将所有流程在项目内完成声明式。

GitHub + Actions 确实是我心目中的理想解决方案。可惜 GitHub 对私有仓库使用 Actions 一直采取收费策略,而且我也不是很愿意将代码完全托管于第三方平台(谁知道他们会不会偷偷拿去训练),所以一直作罢。

最近因为服务器配置的问题,我又开始研究市面上的 CI/CD 工具。好消息是,这次我发现 Gitea 支持 Action了,其语法和运行机制,完全基于 Github 开发,于是我迫不及待的开始将 Gogs + Jenkins 的组合 往 Gitea + Runner 的组合迁移,期间免不了一顿折腾和踩坑,特此记录。

机制

对于不了解 Github Actions 机制的朋友,这里简单介绍下 GitHub Actions 的的核心架构。这个架构的核心在于 控制器执行器 的解耦。

  • GitHub 作为控制器:负责监听仓库事件、解析 Workflow 配置文件,并将作业任务进行调度和分发。

  • Runner 作为执行器:它是作业的实际执行器,Runner 都只负责接收调度指令,在隔离的环境中完成具体的构建、测试或部署任务。

这样的好处是显而易见的

  1. 安全:执行器和代码存储完全分离。Runner 的任何异常,甚至是崩溃,都不会对代码仓库的完整性和可用性产生任何影响。

  2. 灵活:可以根据任务需求,在不同操作系统或硬件架构上部署 Runner,方便水平扩展。

安装

背景

我有一台 4c8g 的主力服务器,还有数台 2c4g、4c8g 的 "不稳定服务器",这些服务器要么是经常变动,要么是可靠性没有保障。我的想法是,将 「Gitea」 和「主Runner」 都用 Docker 安装在主力服务器上,同时酌情在 "不稳定服务器" 上安装其他 Runner,在需要的时候使用。

docker-compose.yaml

version: "3.5"
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - TZ=Asia/Shanghai
    restart: always
    networks:
      - nginx  # 我用来进行方向代理的,不需要可以删除
      - mysql  # 我采用 Mysql 作为数据库,习惯于内网连接,不需要可以删除
      - gitea
    volumes:
      - /home/gitea:/data
#    ports:
#      - "9999:3000"  # 我用了 nginx 进行了反向代理,所以不需要映射端口,有需要的启用

  runner:
    image: gitea/act_runner:latest
    container_name: runner
    restart: always
    depends_on:
      - gitea
    volumes:
      - /home/runner/data:/data
      - /home/runner/cache:/root/.cache  # 持久化缓存目录,方便项目重启的时候不丢失缓存
      - /home/runner/config.yaml:/config.yaml  # Runner 配置模板,见下文补充说明
      - /var/run/docker.sock:/var/run/docker.sock  # 用来让 Runner 获得本机的 Docker 管理权限
    networks:
      - gitea
    environment:
      - GITEA_INSTANCE_URL=http://gitea:3000/
      - GITEA_RUNNER_REGISTRATION_TOKEN=xxxxxxxxx  # 第一次运行,这里先随便填,需要初始化后生成
      - CONFIG_FILE=/config.yaml  # 配置文件路径
      - GITEA_RUNNER_NAME=docker-runner  # 本 Runner 的名称标识
      - GITEA_RUNNER_LABELS=docker  # 本 Runner 的标签

networks:
  nginx:
    external: true
  mysql:
    external: true
  gitea:
    name: gitea

运行

  1. 构建容器

    docker-compose up -d
  2. 访问 gitea WebUI,(如果不进行反向代理,就在docker-compose.yaml 里的启用 ports 的部分),进行初始化配置

  3. 查看 Runner Token (头像 - 管理后台 - Actions - Runner)

    gitea-runner-token

  4. 修改 docker-compose.yaml 中的 GITEA_RUNNER_REGISTRATION_TOKEN=xxxxxxxxx ,然后重启项目

    docker-compose down
    docker-compose up -d

一切正常的话,会在这个获取 Token 的界面看到连接到的 Runner

Gitea

配置

Gitea 的有些配置是无法在「管理后台」面板里设置的,需要通过手动修改配置文件的方式来实现。

以我上面的配置为例,Gitea 配置文件路径为: /home/gitea/gitea/conf/app.ini , 其中我比较关注的有

  • 修改默认分支

    [repository]
    DEFAULT_BRANCH = master  # 修改默认的分支名,默认没有此项配置需要手动添加,(缺省的情况下,默认分支为 main)
    
  • 修改 Token 失效周期

    现代的 Git 凭证管理机制 GCM,会通过向 Gitea 获取 Token 的形式来获得仓库访问授权,但是这个 Token 默认情况下只有 30min,导致长时间不操作仓库以后,第一次推送或者拉取会提示授权失败。可以通过下面的配置把这个失效周期拉长。

    [oauth2]
    JWT_SECRET = xxxxxxxxx
    ACCESS_TOKEN_EXPIRATION_TIME=15552000  ; 单位: 秒,15552000 = 180天 
  • 修改默认使用 Action 的位置

    当你在 workflow 使用第三方 action 时,默认是从 github 拉取的,如果你想默认从你自己的仓库(gitea)拉取,新建下面的配置

    ;从gitea实例拉取action
    ;[actions]
    ;DEFAULT_ACTIONS_URL = self
    ...
    jobs:
      build:
        runs-on: docker
        steps:
          - name: Pull repository
            uses: actions/checkout@v3
            # 不设置 actions 配置的时候,默认是从 https://github.com/actions/checkout@v3 拉取
            # 设置了 actions 配置后,会从自己的 Gitea 实例拉取
    ...

自定义模板

Gitea 中有很多可以自定义的模板,具体可以见: 自定义 Gitea 配置

以我上面的 docker-compose.yaml 为例,在 /home/gitea/gitea 文件夹新建 options,在 options 文件夹里可以放置自定义的 gitignoresreadme 等等。

比如我要修改 Python 的默认 gitignore,则新建一个文件 /home/gitea/gitea/options/gitignore/Python , 里面写我要写的 gitignore 内容

# 文件位置: /home/gitea/gitea/options/gitignore/Python
__pycache__/

#...
.idea/  # 启用针对 Pycharm 的 gitignore
#...

Runner

关于 Runner 也有一些配置可以修改,首先需要先生成一个默认模板。

docker run --entrypoint="" --rm -it docker.io/gitea/act_runner:latest act_runner generate-config > config.yaml

然后挑几个我常用的配置项讲解一下,其他的配置项在 config.yaml 中都注释的很清楚。

log:
  level: info

runner:
  capacity: 2  # 并发数
  timeout: 3h  # Runner 执行 Job 的超时时间
  shutdown_timeout: 0s  # 当 Runner 关闭的时候,如果有正在执行的 Job,Runner 等待 Job 运行多久后才关闭
  fetch_timeout: 5s  # 拉取代码的超时时间
  fetch_interval: 2s  # 拉取代码的间隔
  github_mirror: 'https://ghfast.top/https://github.com'  # 修改拉取 github 仓库时的镜像

cache:
  enabled: true  # 启用缓存
  dir: ""  # 当此项为空时,默认的缓存文件夹路径为 /root/.cache/actcache.

container:
  network: "gitea"  # 创建 docker 时使用的 docker 网络(与gitea保持在同一个网络,否则可能拉取不到代码仓库)
#  privileged: false  # 启动任务容器时是否使用特权模式
#  force_pull: true  # 即使镜像已经存在,也会强制重新拉取
#  force_rebuild: false  # 即使镜像已经存在,也会强制重新建立

workflow

这个讲起来太长了,基本要从 Github Actions 语法开始讲起,又要注意 Gitea 中与 Github Actions 语法的一些小的区别,对 Github Actions 有了解的朋友,可以只看区别的部分: Gitea Actions 与 Github Actions 的异同

放一个我常用的,单容器构建+运行+健康检查+企业微信通知结果的 Demo 吧。

name: Build Docker Image and Run Container

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: docker
    container:
      image: catthehacker/ubuntu:act-22.04
    timeout-minutes: 30

    steps:
      - name: Start build notification
        uses: chf007/action-wechat-work@master
        env:
          WECHAT_WORK_BOT_WEBHOOK: ${{ vars.WECOM_ROBOT_URL }}
        with:
          msgtype: markdown
          content: |
            <font color="info">🎯 ${{ gitea.repository }} 开始构建</font>
            > 构建分支: ${{ gitea.ref_name }}
            > 提交用户: ${{ gitea.actor }}

      - name: Pull repository
        uses: actions/checkout@v3

      - name: Build and run image
        id: build_step
        uses: hicane0/action-docker-build-single@v1

      - name: Prepare notification data
        id: notify_step
        if: always()
        run: |
          if [ "${{ steps.build_step.outcome }}" = "success" ]; then
            echo "title=<font color=\"info\">✅ ${{ gitea.repository }} 构建成功</font>" >> $GITEA_OUTPUT
          else
            echo "title=<font color=\"warning\">❌ ${{ gitea.repository }} 构建失败</font>" >> $GITEA_OUTPUT
          fi
          
          echo "action_url=${{ gitea.event.repository.html_url }}/actions/runs/${{ gitea.run_id }}" >> $GITEA_OUTPUT

      - name: Send build result
        if: always()
        uses: chf007/action-wechat-work@master
        env:
          WECHAT_WORK_BOT_WEBHOOK: ${{ vars.WECOM_ROBOT_URL }}
        with:
          msgtype: markdown
          content: |
              ${{ steps.notify_step.outputs.title }}
              > 构建分支: ${{ gitea.ref_name }}
              > 提交用户: ${{ gitea.actor }}
              > 构建耗时: ${{ steps.build_step.outputs.duration }}
              > 详细日志: [${{ gitea.job }}](${{ steps.notify_step.outputs.action_url }})