Azure DevOps流水线互斥队列:动态环境资源组名称锁存方案咨询
解决方案:基于Azure Blob存储的环境名称锁与状态管理
核心方案:Azure Blob Lease + 状态记录
Azure Blob存储的**排他租约(Lease)**机制天生适配分布式竞争场景,同时可通过Blob内容存储环境状态(可用环境名、是否正在创建新环境),完全覆盖你的需求,且在GitHub Actions中能通过azure/cli快速实现。
具体流程
- 初始化状态Blob
提前在Azure存储容器中创建environment-state.json,初始内容如下:
{ "availableEnv": null, "nextEnvCreating": false }
availableEnv:记录当前就绪、可直接复用的环境(资源组)名称nextEnvCreating:标记是否已有构建在创建下一个环境
- 构建任务核心逻辑
步骤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:根据状态分支处理
- 存在可用环境:
- 将
availableEnv设为null,更新Blob内容 - 释放租约,将该环境名注入当前构建的环境变量
- 直接部署到该环境,同时若
nextEnvCreating为false,触发后台任务创建下一个环境
- 将
- 无可用环境:
- 若
nextEnvCreating为false:标记其为true,更新Blob后释放租约,当前构建负责创建新环境,完成后再将新环境名写入availableEnv、重置nextEnvCreating - 若
nextEnvCreating为true:释放租约,等待10秒后循环重试,直到有可用环境
- 若
- 环境创建完成后的状态更新
新环境创建就绪后,重新获取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
相关产品推荐
相关产品推荐

