【问题标题】:Azure Pipelines multi stage problems for basic steps基本步骤的 Azure Pipelines 多阶段问题
【发布时间】:2026-02-20 13:20:10
【问题描述】:

我来自 GitLab 及其 .gitlab-ci.yml,我正在试验 Azure DevOps 多阶段管道,但即使在阅读了 https://docs.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops 上的几篇文档文章后,我仍然对它的工作原理以及最佳策略感到非常困惑。 /p>

请允许我就我正在尝试的基本场景提出几个相关问题,即编译、运行单元测试、为整个解决方案打包一个 nuget 包(它可能包含多个项目/ nuGet 包)并将包发布到 nuGet 源(如果分支是主分支,则为发布版本,否则为预发布版本)。 这是我从中获取代码的存储库:https://github.com/sasw-diego/sasw-test-support 它只会生成一个 nuGet 包,但我有其他多项目解决方案应该生成许多 nuGet 包

到目前为止,这是我的azure-pipelines.yml

trigger:
- master
- feature/*

pool:
  vmImage: ubuntu-latest

variables:
  NUGET_FOLDER_NAME: nupkgs
  NUGET_REPOSITORY: https://whatever
  PRERELEASE_SUFFIX: $(Build.BuildId)
  PIPELINE_ARTIFACT_NAME: $(Build.BuildNumber)

stages:
- stage:
  displayName: 'Build'
  jobs:
  - job: 'Build'
    steps:
    - task: NuGetAuthenticate@0
      displayName: 'Authenticate in NuGet feed'
    - script: dotnet restore --no-cache --force
      displayName: 'Restore dependencies'
    - script: dotnet build --configuration Release --no-restore
      displayName: 'Build for Release'
    - script: ls $(System.DefaultWorkingDirectory)
      displayName: 'List content'
    - publish: $(System.DefaultWorkingDirectory)
      artifact: $(PIPELINE_ARTIFACT_NAME)
- stage:
  displayName: 'Automated Tests'
  condition: succeeded()
  jobs:
  - job:
    displayName: 'Unit Tests'
    steps:
    - download: current
      artifact: $(PIPELINE_ARTIFACT_NAME)
    - script: ls -a
      displayName: 'View'
    - script: ls ./test
      displayName: 'View test'
    - script: ls ./test/Sasw.TestSupport.UnitTests
      displayName: 'View folder'
    - script: dotnet vstest test/*UnitTests/bin/Release/**/*UnitTests.dll
      displayName: 'Run unit tests'
- stage:
  displayName: 'NuGet Package'
  condition: succeeded()
  jobs:
  - job:
    displayName: 'Pack Preview Version'
    condition: ne(variables['Build.SourceBranch'], 'refs/heads/master')
    steps:
    - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME)
      displayName: 'Pack'
  - job:
    displayName: 'Pack Stable Version'
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/master')
    steps:
    - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME) --version-suffix $(PRERELEASE_SUFFIX) --include-source --include-symbols -p:SymbolPackageFormat=snupkg
      displayName: 'Pack'

  1. 哪个是多阶段的“最佳”策略?我看到 Azure DevOps 管道具有阶段 > 作业 > 任务的概念,但它们看起来都与我相似。所以我决定把这个过程分成几个阶段,比如Build - AutomatedTests - NuGet Package - Publish 正如你所看到的,这是一个顺序过程,每个阶段都需要前一个阶段的东西。自动化测试需要构建代码(dll),nuGet包也需要访问构建代码,发布需要访问生成的nupkg等。我不知道遵循这个策略是否可以或者最好有具有多个作业的单个阶段,甚至具有多个任务的单个作业。正如我所说,我不完全理解拥有这么多概念的好处以及它们如何满足我的需求。
  2. Azure 多阶段管道是否应该将旧的 Build 和 Release 替换为单独的概念?对我来说,将 CI/CD 放在一个位置并采用多阶段方法并且可编写脚本以使其在源代码控制存储库中进行版本控制对我来说是有意义的。但我仍然将发布概念视为当前在 Azure DevOps 上的一个独立概念。所以可能我应该使用 azure 管道 yml 直到包“步骤”,然后使用 Release 去抓取这个 nupkg 并将它发布到一些提要上。但不确定有什么好处。
  3. 我在将一个阶段的输出作为下一个阶段的输入时遇到问题,可能是因为我没有完全理解它。在上面的 yml 中,构建阶段成功,但自动化测试阶段在其 Run Unit Tests 作业中失败,并出现错误 No test source files were specified。我验证了这是因为根本没有生成文件夹bin。这似乎很奇怪,因为我正在复制前一阶段的所有内容(以及前一阶段使用 dll 生成的 bin 文件夹),但是下一阶段,尽管能够下载所有内容,但它找不到测试。

这是失败阶段的日志:https://gist.github.com/sasw-diego/df66eccf71bbfc044a4d72be96268c9a

如果有人发现我缺少什么以便能够理解这个过程,那将非常有帮助。任何澄清所有这些概念的链接将不胜感激。塔

PS:这是我在 GitLab 中使用的类似通用 CI/CD,用于将 1 个或多个 nuGet 上传到提要: https://gist.github.com/sasw-diego/bf46258cb1ad0aa5241e8d1866b53f48


更新: 感谢您的回答。我成功地创建了一个带有多阶段管道的 CI/CD yml,它可以恢复、构建、执行测试、运行一个容器(例如:一个 eventStore 主机)来针对它运行集成测试,并在其中发布 nuGet文物。所以任务完成了!我把它分成不同的阶段和工作来探讨一些点

trigger:
- master
- feature/*

pool:
  vmImage: ubuntu-18.04

variables:
  - group: sasw-common-variables
  - name: NUGET_FOLDER_NAME
    value: nupkgs
  - name: PIPELINE_ARTIFACT_NAME
    value: $(Build.BuildNumber)
  - name: PATH_PIPELINE_ARTIFACT_NAME
    value: $(Pipeline.Workspace)/$(PIPELINE_ARTIFACT_NAME)
  - name: NUGET_API_KEY
    value: $(nuget-api-key)
  - name: NUGET_FEED
    value: $(nuget-feed)
  - name: PRERELEASE_SUFFIX
    value: $(nuget-prerelease-suffix)

resources:
  containers:
    - container: eventstore
      image: eventstore/eventstore:release-5.0.2
      ports:
      - 1113:1113
      env:
        EVENTSTORE_INT_TCP_PORT: 1113
        EVENTSTORE_EXT_TCP_PORT: 1113
        EVENTSTORE_INT_HTTP_PORT: 2113
        EVENTSTORE_EXT_HTTP_PORT: 2113
        EVENTSTORE_EXT_HTTP_PREFIXES: http://*:2113/

stages:
  - stage:
    displayName: 'Build'
    jobs:
      - job: 'Build'
        displayName: 'Build & Create nuGet Package'
        services: 
            eventstore: eventstore
        steps:
          - task: NuGetAuthenticate@0
            displayName: 'Authenticate in NuGet feed'
          - script: dotnet restore --no-cache --force
            displayName: 'Restore dependencies'
          - script: dotnet build --configuration Release --no-restore
            displayName: 'Build with Release Configuration'
          - script: dotnet vstest test/*UnitTests/bin/Release/**/*UnitTests.dll
            displayName: 'Run unit tests'
          - script: dotnet vstest test/*IntegrationTests/bin/Release/**/*IntegrationTests.dll
            displayName: 'Run integration tests'
          - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME)
            condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
            displayName: 'Create release nuGet'
          - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME) --version-suffix $(PRERELEASE_SUFFIX) --include-source --include-symbols -p:SymbolPackageFormat=snupkg
            condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master'))
            displayName: 'Create pre-release nuGet'
          - publish: $(System.DefaultWorkingDirectory)/$(NUGET_FOLDER_NAME)
            artifact: $(PIPELINE_ARTIFACT_NAME)
            displayName: 'Publish pipeline artifact'
  - stage:
    displayName: 'Release'
    condition: succeeded()
    jobs:
      - job: 'Publish'
        displayName: 'Publish nuGet Package'
        steps:
          - download: current
            artifact: $(PIPELINE_ARTIFACT_NAME)
            displayName: 'Download pipeline artifact'
          - script: ls $(PATH_PIPELINE_ARTIFACT_NAME)
            displayName: 'Display contents of downloaded articacts path'
          - task: NuGetAuthenticate@0
            displayName: 'Authenticate in NuGet feed'
          - task: UseDotNet@2
            displayName: 'Use latest .NET Core sdk 3.x'
            inputs:
              packageType: sdk
              version: 3.x
              includePreviewVersions: true
              installationPath: $(Agent.ToolsDirectory)/dotnet
          - script: dotnet nuget push $(PATH_PIPELINE_ARTIFACT_NAME)/**/*.nupkg --source $(NUGET_FEED) --api-key $(NUGET_API_KEY) --skip-duplicate
            displayName: 'Uploads nuGet packages'

【问题讨论】:

    标签: c# .net-core azure-devops azure-pipelines azure-artifacts


    【解决方案1】:
    1. 您的整个构建定义应该是一个“阶段”。那些Build - AutomatedTests - NuGet Package - Publish 不是阶段,它们是构建的逻辑部分。
    2. 可能在未来某个时间\可能会发生
    3. 那是因为您不应该使用阶段。每个阶段在不同的代理上运行。您应该有一个阶段,并且所有任务都应该在该阶段内运行。

    【讨论】:

    • 据此,每个工作也运行在不同的代理*.com/a/58685968/2948212你的意思是我应该有一个单一的工作和多个任务的单一阶段?谢谢
    • 是的,当您打算部署到不同阶段(dev\stage\prod\etc)时,您应该有多个阶段