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

Azure DevOps流水线互斥队列:动态环境资源组名称锁存方案咨询

解决方案:基于Azure Blob存储的环境名称锁与状态管理

核心方案:Azure Blob Lease + 状态记录

Azure Blob存储的**排他租约(Lease)**机制天生适配分布式竞争场景,同时可通过Blob内容存储环境状态(可用环境名、是否正在创建新环境),完全覆盖你的需求,且在GitHub Actions中能通过azure/cli快速实现。

具体流程

  1. 初始化状态Blob
    提前在Azure存储容器中创建environment-state.json,初始内容如下:
{
  "availableEnv": null,
  "nextEnvCreating": false
}
  • availableEnv:记录当前就绪、可直接复用的环境(资源组)名称
  • nextEnvCreating:标记是否已有构建在创建下一个环境
  1. 构建任务核心逻辑

步骤1:获取Blob排他租约

确保同一时间仅一个构建能修改状态Blob,租期设为60秒(可按需续期):

LEASE_ID=$(az storage blob lease acquire --container-name <你的容器名> --name environment-state.json --account-name <你的存储账户名> --lease-duration 60 --output tsv)

步骤2:读取并解析当前状态

az storage blob download --container-name <你的容器名> --name environment-state.json --file state.json --account-name <你的存储账户名>
AVAILABLE_ENV=$(cat state.json | jq -r '.availableEnv')
NEXT_CREATING=$(cat state.json | jq -r '.nextEnvCreating')

步骤3:根据状态分支处理

  • 存在可用环境:
    1. 将availableEnv设为null,更新Blob内容
    2. 释放租约,将该环境名注入当前构建的环境变量
    3. 直接部署到该环境,同时若nextEnvCreating为false,触发后台任务创建下一个环境
  • 无可用环境:
    • 若nextEnvCreating为false:标记其为true,更新Blob后释放租约,当前构建负责创建新环境,完成后再将新环境名写入availableEnv、重置nextEnvCreating
    • 若nextEnvCreating为true:释放租约,等待10秒后循环重试,直到有可用环境
  1. 环境创建完成后的状态更新
    新环境创建就绪后,重新获取Blob租约,将availableEnv设为新资源组名称,nextEnvCreating设为false,最后释放租约。

竞争问题解决

Blob排他租约从底层保证了同一时间仅一个构建能修改状态,彻底避免并发读写冲突。若处理耗时超过租期,可通过az storage blob lease renew命令续期。

GitHub Actions适配示例

利用azure/login和azure/cli action实现核心逻辑,Ubuntu runner默认自带jq工具用于JSON解析:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      
      - name: Fetch or create environment
        run: |
          while true; do
            # 获取租约
            LEASE_ID=$(az storage blob lease acquire --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-duration 60 --output tsv)
            # 下载状态文件
            az storage blob download --container-name env-state-container --name environment-state.json --file state.json --account-name yourstorageaccount
            
            AVAILABLE_ENV=$(cat state.json | jq -r '.availableEnv')
            NEXT_CREATING=$(cat state.json | jq -r '.nextEnvCreating')
            
            if [ "$AVAILABLE_ENV" != "null" ]; then
              # 分配可用环境,更新状态
              echo '{"availableEnv": null, "nextEnvCreating": false}' > state.json
              az storage blob upload --container-name env-state-container --name environment-state.json --file state.json --account-name yourstorageaccount
              az storage blob lease release --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-id $LEASE_ID
              echo "USE_ENV=$AVAILABLE_ENV" >> $GITHUB_ENV
              break
            else
              if [ "$NEXT_CREATING" == "false" ]; then
                # 标记开始创建新环境
                echo '{"availableEnv": null, "nextEnvCreating": true}' > state.json
                az storage blob upload --container-name env-state-container --name environment-state.json --file state.json --account-name yourstorageaccount
                az storage blob lease release --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-id $LEASE_ID
                echo "CREATE_NEW_ENV=true" >> $GITHUB_ENV
                break
              else
                # 释放租约后重试
                az storage blob lease release --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-id $LEASE_ID
                sleep 10
              fi
            fi
          done
        shell: bash
      
      # 后续部署或创建环境的步骤
      - name: Deploy to existing environment
        if: env.USE_ENV != ''
        run: |
          echo "Deploying to environment ${{ env.USE_ENV }}"
          # 你的部署命令
      
      - name: Create new environment
        if: env.CREATE_NEW_ENV == 'true'
        run: |
          NEW_ENV="env-$(date +%Y%m%d%H%M%S)"
          az group create --name $NEW_ENV --location eastus
          # 其他环境初始化操作
          # 更新状态Blob
          LEASE_ID=$(az storage blob lease acquire --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-duration 60 --output tsv)
          echo "{\"availableEnv\": \"$NEW_ENV\", \"nextEnvCreating\": false}" > state.json
          az storage blob upload --container-name env-state-container --name environment-state.json --file state.json --account-name yourstorageaccount
          az storage blob lease release --container-name env-state-container --name environment-state.json --account-name yourstorageaccount --lease-id $LEASE_ID

替代方案:Azure Table Storage乐观锁

若觉得Blob租约过重,可使用Table Storage的ETag实现乐观锁:

  • 在Table中创建一个实体(PartitionKey: EnvManager, RowKey: CurrentState),存储availableEnv和nextEnvCreating字段
  • 更新实体时指定--if-match参数为当前ETag,仅当ETag匹配时更新成功,否则说明已有其他构建修改状态,需重试
  • 该方式更轻量,但需自行处理重试逻辑,适合极简场景

为什么不选Service Bus/队列?

Service Bus或队列更适合任务分发排队,但你的核心需求是环境状态的分布式同步与锁,而非任务调度。用队列需额外维护环境就绪状态,反而增加复杂度,不如Blob租约机制直接简洁。

内容的提问来源于stack exchange,提问作者Chris B. Behrens

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:04:52