You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

同一Windows服务器上多GitHub自托管运行器的配置问题及优化方案咨询

GitHub Actions自托管Runner多作业并行与工作流隔离问题解决方案

我来帮你梳理下这个问题——你遇到的核心痛点其实是自托管Runner的工作区隔离特性和GitHub Actions作业上下文传递的设计逻辑没匹配上。先拆解问题根源,再给你针对性的解决方案:

为什么会出现“第二个Runner找不到仓库”的问题?

不管是同一机器还是不同机器上的自托管Runner,每个实例都是完全独立的:它们有自己的配置文件、独立的工作目录,GitHub Actions不会自动在不同Runner实例之间共享工作区内容。哪怕是同一工作流的不同作业,只要分配到了不同的Runner,后执行的作业就无法直接访问前序作业在另一个Runner工作区里克隆的代码或生成的产物——这和GitHub托管Runner的逻辑不一样(托管Runner会为同一工作流的作业临时共享工作区)。

你的两个方案分析

先说说你提到的两个方案:

  • 方案A:迁移到共享根目录:其实这个方案是可行的,只是你可能没注意到Runner工作目录的自定义方式。你可以在注册Runner时通过--work参数指定同一个共享目录,让两个Runner共用同一根工作区。但要注意Windows的文件锁问题(比如前序作业在写文件时,后续作业读可能报错),以及要确保两个Runner的运行账户对共享目录有完整的读写权限。
  • 方案B:重新映射$Env:GITHUB_WORKSPACE:这个确实不可行,因为这个环境变量是Runner启动时就确定的,属于Runner的全局配置,无法在工作流作业中动态修改。

更优的解决方案

1. 优先用GitHub Actions标准方式:工件(Artifacts)传递

这是GitHub官方推荐的跨作业/跨Runner传递内容的方案,完全适配自托管Runner的隔离特性,也避免了共享目录的冲突风险。核心逻辑是:前序作业生成的产物(代码、编译输出等)通过upload-artifact上传为工件,后续依赖作业通过download-artifact下载后再执行操作。

举个适配你.NET项目的工作流示例:

jobs:
  checkout-build:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - name: 还原依赖
        run: dotnet restore
      - name: 编译项目
        run: dotnet build --configuration Release --no-restore
      - name: 上传编译产物
        uses: actions/upload-artifact@v4
        with:
          name: release-build
          path: ./bin/Release/net6.0/ # 根据你的.NET版本调整路径

  test:
    runs-on: self-hosted
    needs: checkout-build # 依赖编译作业完成
    steps:
      - uses: actions/checkout@v4 # 测试需要代码,直接重新克隆更简单
      - name: 还原依赖
        run: dotnet restore
      - name: 运行单元测试
        run: dotnet test --no-restore --verbosity normal

  deploy:
    runs-on: self-hosted
    needs: checkout-build # 依赖编译作业完成
    steps:
      - name: 下载编译产物
        uses: actions/download-artifact@v4
        with:
          name: release-build
          path: ./deploy/
      - name: 部署到IIS
        run: |
          # 替换成你的部署脚本
          Copy-Item ./deploy/* -Destination "C:\inetpub\wwwroot\YourApp" -Recurse -Force

这个方案的好处是完全符合GitHub Actions的设计逻辑,不需要修改Runner配置,也不用担心多Runner之间的冲突。

2. 配置共享工作目录(改进方案A)

如果你坚持要让同一工作流的作业共享目录,可以按以下步骤操作:

  1. 注册Runner时指定共享工作目录:注册两个Runner都使用同一个目录,比如:
    # 注册第一个Runner
    ./config.cmd --url https://github.com/你的组织/你的仓库 --token 你的注册令牌 --work "D:\SharedGitHubWork"
    # 注册第二个Runner时执行同样的命令,指定同一work目录
    
  2. 设置作业依赖避免冲突:用needs关键字确保有依赖关系的作业按顺序执行,避免两个Runner同时读写同一目录。
  3. 限制Runner并发数:每个Runner的config.yml文件里,把concurrency设置为1(默认就是1),确保每个Runner一次只处理一个作业,减少文件锁冲突。

3. 退而求其次:每个工作流分配一个Runner

如果只想实现“一个工作流独占一个Runner”,可以通过标签+并发控制实现:

  1. 给Runner添加唯一标签:比如给两个Runner分别添加runner-1runner-2的标签。
  2. 工作流中指定标签并控制并发
    # 确保同一分支的同一工作流只能有一个实例在运行
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: false
    
    jobs:
      checkout:
        runs-on: [self-hosted, runner-1] # 指定使用runner-1
        steps:
          - uses: actions/checkout@v4
          # 其他步骤...
    
      build:
        runs-on: [self-hosted, runner-1]
        needs: checkout
        # 其他步骤...
    
    这样整个工作流的所有作业都会在runner-1上执行,另一个工作流可以指定runner-2,实现多工作流并行。如果不需要指定具体Runner,也可以让工作流自动分配,但通过concurrency确保同一工作流的作业不会分散到不同Runner。

你遗漏的关键点

  1. 自托管Runner的隔离性:每个Runner实例都是独立的,默认不会共享任何资源,哪怕在同一机器上。
  2. GitHub Actions的作业上下文传递逻辑:跨Runner的作业必须通过工件传递内容,needs关键字只控制执行顺序,不会自动共享工作区。
  3. Runner工作目录的可配置性:你可以在注册Runner时自定义工作目录,不是固定的默认路径。

内容的提问来源于stack exchange,提问作者Airn5475

火山引擎 最新活动