【问题标题】:Run only one instance of a build on VSTS在 VSTS 上仅运行一个构建实例
【发布时间】:2018-07-29 08:37:53
【问题描述】:

我在 VSTS 中有一个包含多个代理的代理队列,并且我有一个分配给该队列的构建。有没有办法告诉 VSTS 一次只能运行一个构建实例,而其他实例(计划在其他代理上)应该等到前一个实例完成?

我需要这个,因为测试创建了一个临时数据库,在该数据库上运行集成测试,如果多个实例针对同一个数据库运行,那么它们会相互干扰。

【问题讨论】:

  • 这看起来像是您的应用程序中的一个重要架构问题。因为另一个实例正在运行而阻止同一个作业运行是非常不自然的。事实上,通常情况下,人们的目标恰恰相反,以加快整个管道的速度。我建议您重构测试处理数据库的方式以避免交叉,或者为每个作业运行启动一个新的数据库实例。
  • 你的意思是多个构建使用相同的构建定义?
  • @YanSklyarenko 同意,问题是我只有一个用于运行测试的数据库,我需要为每次测试运行设置它,这意味着我删除了大部分表并重新创建它们。由于这是一个单一的数据库,如果在第一个构建仍在运行时启动另一个构建,它将破坏为第一个构建设置的数据。
  • @starianchen-MSFT 我想做的是能够为给定的构建定义只运行一个构建,而所有其他构建都在队列中,直到前一个完成。
  • 我遇到了同样的问题。在我们的例子中,我们正在考虑创建一个 PowerShell 任务,我们将其添加到构建中,在之前的构建完成之前不允许构建。不过,这并非易事,因为我必须在某处存储一个“队列”,以便确保在第一个构建运行时是否触发了 10 个构建,它们以正确的顺序处理。也许这是你需要考虑的事情?

标签: azure-devops


【解决方案1】:

您无法在构建级别 (You can vote for this feature here) 进行配置,但也许您可以在阶段级别完成您想要的。

查看每个阶段下的并行选项。如果您看到相反的行为,看起来您需要将其设置为“无”。 “无”是新版本的默认选项。

【讨论】:

  • 不幸的是,这并不能解决我的问题。 None 意味着构建将在单个代理上运行,而不是在多个代理上运行相同的构建。它不会阻止在不同的代理上触发同一构建的多个实例。
【解决方案2】:

不,没有这样的设置来控制它,它是由管道控制的:Concurrent build and release pipelines in VSTS

您可以禁用其他代理:

  1. 执行代理队列管理页面
  2. 选择队列
  3. 取消选中代理以禁用它

另一方面,您可以创建一个名称包含内部版本号的临时数据库,这样它们就不会相互干扰。

【讨论】:

  • 是的,团队数据库将是理想的。但我遇到的问题是我只获得了一个用于运行这些测试的数据库,所以我不能只为每个构建创建一个临时数据库。
  • @RaYell 您需要禁用其他代理,或者您可以创建一个新池并仅使用一个代理进行排队。
  • @RaYell,如果您不适合禁用代理,您也可以通过向单个(私有)代理添加自定义功能来实现此目的,然后将您的构建配置为需要此自定义功能。跨度>
  • @raterus 你用这些方法解决这个问题吗? (禁用代理,设置能力)
【解决方案3】:

我们实现了一个自定义 VSTS 任务来执行此操作。它可以游泳。这是代码。

如果您不喜欢创建自定义 VSTS 任务 - 您可以去掉 PowerShell。只需将值作为参数传递即可。

文件结构

/ Project-Folder
    - extension-icon.png
    - extension-manifest.json
    / Tasks
        / KeepBuildSynchronous
            - icon.png
            - task.json
            - task.ps1

扩展清单.json

{
    "manifestVersion": 1,
    "id": "your-task-name",
    "name": "your-package-name",
    "version": "0.0.1",
    "publisher": "your-publisher-account",
    "targets": [
        {
            "id": "Microsoft.VisualStudio.Services"
        }
    ],    
    "description": "your-description",
    "categories": [
        "Build and release"
    ],
    "icons": {
        "default": "extension-icon.png"        
    },
    "files": [
        {
            "path": "Tasks/KeepBuildSynchronous"
        }
    ],
    "contributions": [
        {
            "id": "12345678-0000-0000-0000-000000000000", <-- must match task.json id
            "type": "ms.vss-distributed-task.task",
            "targets": [
                "ms.vss-distributed-task.tasks"
            ],
            "properties": {
                "name": "Tasks/KeepBuildSynchronous"
            }
        }
    ]
}

task.json

{
    "id": "12345678-0000-0000-0000-000000000000",
    "name": "keepBuildSynchronous",
    "friendlyName": "Keep Build Synchronous",
    "description": "Only allow one build with the same build definition to run at a time",
    "helpMarkDown": "",
    "category": "Utility",
    "visibility": [
        "Build"
    ],
    "runsOn": [
        "Agent",
        "DeploymentGroup"
    ],
    "author": "-your team-",
    "version": {
        "Major": 1,
        "Minor": 1,
        "Patch": 1
    },
    "releaseNotes": "Initial release",
    "minimumAgentVersion": "1.91.0",
    "inputs": [
        {
            "name": "waitTimeinMinutes",
            "type": "int",
            "label": "How many minutes do you want to wait before cancelling the build",
            "defaultValue": "15",
            "required": true,
            "helpMarkDown": ""
        }
    ],
    "instanceNameFormat": "Keep Build Synchronous",
    "execution": {
        "PowerShell3": {
            "target": "task.ps1"
        }
    }
}

task.ps1

[CmdletBinding()]
Param ()

Trace-VstsEnteringInvocation $MyInvocation
try {
    Import-VstsLocStrings "$PSScriptRoot\Task.json"

    # Get agent/build variables
    [string]$teamFoundationCollectionUri = Get-VstsTaskVariable -Name "system.teamFoundationCollectionUri"
    [string]$teamProjectId = Get-VstsTaskVariable -Name "system.teamProjectId"
    [string]$buildId = Get-VstsTaskVariable -Name "build.buildId"
    [int]$definitionId = Get-VstsTaskVariable -Name "system.definitionId" -AsInt
    [string]$accessToken = Get-VstsTaskVariable -Name "system.accessToken"

    # Get task inputs
    [int]$waitTimeinMinutes = Get-VstsInput -Name waitTimeinMinutes -AsInt

    [Object[]]$global:buildsByDefinition = $null
    [Object[]]$global:runningBuilds = $null

    function Get-Builds-By-Definition([int]$definitionId) {
        $url = "$teamFoundationCollectionUri/$teamProjectId/_apis/build/builds?api-version=2.0&definitions=$definitionId"

        $type = "application/json"
        $headers = @{
            Authorization = "Bearer $accessToken"
        }
        $global:buildsByDefinition = (Invoke-RestMethod -Uri $url -ContentType $type -Method Get -Headers $headers).value
        $global:runningBuilds = $buildsByDefinition | Where-Object -Property "status" -Value "inProgress" -EQ
    }

    [datetime]$startedAt = Get-Date
    [datetime]$waitUntil = $startedAt.AddMinutes($waitTimeinMinutes)

    Get-Builds-By-Definition -definitionId $definitionId

    [string]$buildDefinitionName = $buildsByDefinition[0].definition.name

    Write-Host ""
    Write-Host "Build definition ..... $buildDefinitionName"
    Write-Host "Current build ........ $buildId"
    Write-Host ""
    Write-Host "Started at ........... $startedAt"
    Write-Host "Willing to wait until  $waitUntil"
    Write-Host ""

    while (1 -eq 1) {

        if ((Get-Date) -gt $waitUntil) {
            Write-Host "Waited too long (cancelling)"
            throw "Waited longer than $waitTime minutes to start.  Cancelling."
        }

        if ($global:runningBuilds -eq $null) {
            Write-Host "No build running (weird, but ok...)"
            break
        }

        [int]$firstToGo = ($global:runningBuilds | Sort-Object -Property "Id")[0].Id

        if ($global:runningBuilds.Count -le 1 -or $buildId -le $firstToGo) {
            Write-Host "Your turn to shine"
            break
        } else {
            Write-Host "$($global:runningBuilds.Count) builds running. $firstToGo is next. (checking again)"
            Start-Sleep -Seconds 15
        }

        Get-Builds-By-Definition -definitionId $definitionId

    }
}
finally {
    Trace-VstsLeavingInvocation $MyInvocation
}

创建 .vsix 文件

tfx extension create --manifest-globs extension-manifest.json --rev-version

我会留下一个小秘密,让您了解如何将其发布到市场并将其安装在您的构建代理上

【讨论】:

  • 好的——这就是我迷路的地方——我已经构建了扩展并上传到了市场。它只是私人的。现在这是添加到 VSTS 构建定义的任务,还是以某种方式添加到构建代理中。最后的说明是构建代理——但这意味着什么——构建代理需要更新吗?以及如何向构建代理添加扩展?
  • 本文档总结了从这里到哪里去。 docs.microsoft.com/en-us/vsts/extend/get-started/…
  • 我会补充一点,您可能希望在执行阶段使用“prejobexecution”而不是“execution”。如果源代码控制操作本身存在问题,那么等待就太晚了。通过使用 prejobexeuction 它确保脚本在源被拉出之前运行。
【解决方案4】:

我觉得这对于手动触发的构建是不可能的(啊)。 但是,对于 CI 构建,可以将 DevOps 配置为在构建运行时将更改捆绑在一起。 转到构建定义的“触发器”选项卡。 检查:

  • 启用持续集成
  • 在构建过程中进行批量更改

【讨论】:

    猜你喜欢
    • 2019-01-07
    • 2016-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-23
    • 1970-01-01
    相关资源
    最近更新 更多