You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过GitHub Actions实现Azure虚拟机自动启停适配流水线场景

GitHub Actions 3台Windows自托管运行器自动启停落地方案

核心约束梳理

先对齐要满足的规则和已知坑点,避免走回头路:

  • 流水线触发时自动唤醒对应虚拟机,无需手动提前开机
  • 流水线执行完成后自动关闭虚拟机,但关机前必须确认机器上没有其他正在运行的任务,不能打断其他作业
  • 云平台计费规则坑:Azure虚拟机仅在操作系统内执行关机不会停止计费,必须通过平台API/门户触发解除分配式关机才会终止计费;AWS同理,必须走API触发实例停止,OS内关机无效
  • 关机操作绝对不能在待关闭的自托管运行器上执行:运行器被关机时会强制中断Runner进程,导致流水线作业状态上报失败、标记为异常,必须把关机步骤放到其他运行器上(GitHub官方托管的ubuntu-latest就够用,免费额度完全覆盖这类轻量操作)

具体实现步骤

1. 前期准备

  • 给3台Windows自托管Runner分别打唯一标识标签,比如win-runner-01/win-runner-02/win-runner-03,把每个标签对应云平台的虚拟机实例ID、云API访问凭证全部存到GitHub仓库的Secret里,不要硬编码在流水线中
  • 给仓库默认的GITHUB_TOKEN开通actions:read权限,方便后续查询Runner的运行状态
  • 所有启停操作的控制面步骤,全部固定跑在GitHub官方提供的ubuntu-latest托管运行器上,不占用自托管Runner资源,也不会出现关机把自身进程中断的问题

2. 流水线触发自动开机

在所有需要用到Windows自托管Runner的流水线最前端,加一个前置启动Job,逻辑如下:

  • 先根据调度规则(比如轮询、按标签匹配)选定本次要跑任务的目标Runner,拿到对应的标签和云虚拟机ID
  • 调用对应云平台的开机API启动VM,轮询等待VM进入运行状态、Runner服务成功连接GitHub后,再放行后续的实际业务Job
    参考代码片段:
jobs:
  pre-wake-runner:
    runs-on: ubuntu-latest
    outputs:
      target_runner_label: ${{ steps.select-runner.outputs.runner_label }}
      target_vm_id: ${{ steps.select-runner.outputs.vm_id }}
    steps:
      - name: 选定本次任务的目标Runner
        id: select-runner
        run: |
          # 这里可替换成自定义调度逻辑,比如优先选当前空闲的Runner,以下为固定选01号机的示例
          echo "runner_label=win-runner-01" >> $GITHUB_OUTPUT
          echo "vm_id=${{ secrets.AZURE_VM_01_RESOURCE_ID }}" >> $GITHUB_OUTPUT
      - name: 登录云平台启动VM
        uses: azure/login@v1 # AWS环境替换为aws-actions/configure-aws-credentials即可
        with:
          creds: ${{ secrets.AZURE_SERVICE_PRINCIPAL }}
      - name: 等待VM和Runner就绪
        run: |
          az vm start --ids ${{ steps.select-runner.outputs.vm_id }} --no-wait
          # 轮询最多等10分钟,直到VM正常启动
          az vm wait --ids ${{ steps.select-runner.outputs.vm_id }} --created --timeout 600

  # 实际业务构建任务,跑在刚唤醒的自托管Runner上
  actual-build:
    needs: pre-wake-runner
    runs-on: ${{ needs.pre-wake-runner.outputs.target_runner_label }}
    steps:
      - name: 常规构建、测试、部署步骤
        run: Write-Host "执行业务逻辑"

3. 任务结束后安全关机

在流水线最后加一个后置关机Job,必须配置if: always()规则,保证不管前面的构建是成功、失败、还是被手动取消,这个Job都会执行。核心要加一层校验:

  • 调用GitHub API查询目标Runner的busy状态,确认当前Runner上没有任何正在运行、排队的作业
  • 仅当确认无活跃任务时,才调用云平台的停止API关机,否则直接跳过,等后续其他任务跑完再触发校验
    参考代码片段:
post-shutdown-runner:
    needs: [pre-wake-runner, actual-build]
    runs-on: ubuntu-latest
    if: always() # 无论构建成功失败都执行,避免VM空跑计费
    steps:
      - name: 检查目标Runner是否还有活跃任务
        id: check-busy-status
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 查询对应标签的Runner是否处于忙线状态
          IS_BUSY=$(gh api repos/${{ github.repository }}/actions/runners --jq ".runners[] | select(.labels[].name == \"${{ needs.pre-wake-runner.outputs.target_runner_label }}\") | .busy")
          if [ "$IS_BUSY" = "true" ]; then
            echo "skip_shutdown=true" >> $GITHUB_OUTPUT
            echo "检测到Runner上还有其他运行中任务,跳过本次关机"
          else
            echo "skip_shutdown=false" >> $GITHUB_OUTPUT
          fi
      - name: 登录云平台
        if: steps.check-busy-status.outputs.skip_shutdown == 'false'
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_SERVICE_PRINCIPAL }}
      - name: 触发VM解除分配(停止计费)
        if: steps.check-busy-status.outputs.skip_shutdown == 'false'
        run: |
          # 注意Azure这里用deallocate才是停止计费的操作,不要用restart或者OS内shutdown命令
          az vm deallocate --ids ${{ needs.pre-wake-runner.outputs.target_vm_id }} --no-wait

避坑提醒

  • AWS环境替换对应CLI命令即可:开机用aws ec2 start-instances,关机用aws ec2 stop-instances,绝对不要在Windows系统内部执行shutdown /s命令,不会停止计费
  • 不要给自托管Runner本身配置云平台关机权限,避免Runner被入侵后出现误操作
  • 如果3台Runner做负载均衡,可以在选目标Runner的步骤里加逻辑,优先选已经开机且处于空闲状态的Runner,减少开关机等待时间
  • 可以额外加个定时巡检Workflow,每天固定跑几次,把所有处于空闲但开机状态的VM关掉,避免异常情况下VM空跑产生费用

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 09:24:34