如何使用 Azure 和 NX 部署多个应用程序 (monorepo)

How to deploy multiple apps (monorepo) with Azure and NX

我正在使用 NX 工具来管理具有多个应用程序的 monorepo,我正在努力了解如何使用 Azure 进行部署和发布管道。

免责声明:我对 Azure 和 devops 非常陌生。

我的理解是这样的: 我创建了一个管道(不是发布管道,如果有意义的话只是一个 "regular one")并向其插入一个 yml。此外,管道链接到 Azure Repos 上的一个存储库,这意味着每次我推送到这个存储库时,它都会触发管道和 运行 yaml 命令。 在这个命令中,我 运行 lint、测试和构建。

这是我能做的,能理解的,下面就比较晦涩了

如果我 pusing/merging 在我可以调节的 master 上,那么构建作业应该会创建一个工件。 现在我可以创建一个发布管道,当它链接到 repo 时将触发该管道将创建一个工件。然后,此发布管道可以将此工件发送到应用程序服务,该应用程序服务是应用程序所在的插槽。

好的,但我使用的是 monorepo,这意味着构建将生成多个应用程序,并且这些应用程序中的每一个都应该部署到正确的应用程序服务。

经过一些研究,I found that 总体思路是为每个应用程序创建一个发布管道。这些发布管道都链接到同一个 monorepo,但它们有一个过滤器,它是一个构建标签。使用 yml 文件构建应用程序时添加构建标签。

所以这些基本上就是我对这一切的理解。下面是问题:

  1. 构建标签到底是什么?它位于何处?它是否以某种方式与神器相关联?
  2. 我们的想法是为每个工件创建一个构建标签,对吗?
  3. 我未能创建构建标签,我该怎么做?
  4. 压缩和发布工件的正确方法是什么?

    这是我正在使用的 yaml:

jobs:
  - job: Lint
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '12.x'
        displayName: 'Install Node.js'
      - task: Npm@1
        displayName: 'Npm install'
      - pwsh: 'npm run nx affected -- --target=lint --parallel --base=origin/master --maxParallel=4'
        displayName: 'Running lint'

  - job: Test
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '12.x'
        displayName: 'Install Node.js'
      - task: Npm@1
        displayName: 'npm install'
      - pwsh: 'npm run nx affected -- --target=test --parallel --code-coverage --base=origin/master --maxParallel=4'
        displayName: 'Running tests'

  - job: Build
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '12.x'
        displayName: 'Install Node.js'
      - task: Npm@1
        displayName: 'npm install'
      - pwsh: 'npm run nx affected -- --target=build --parallel --base=origin/master --prod'
        displayName: 'Running build'
      - pwsh: |
          npm run nx affected:apps -- --base=HEAD~1  --head=HEAD | grep -E '( - )(\w|-|\d|_)+' | sed -E 's/ - /##vso[build.addbuildtag]/g'
        displayName: 'Adding build tags'

当 运行 执行此操作时,测试、lint 和构建工作正常,但我不认为它添加了构建标签,这是日志:

好像什么都没发生...如何正确添加标签并触发发布管道?

我还找到了这个片段来压缩和发布工件,但我不知道我是否可以使用它,因为在 monorepo 中我们应该 - 我认为 - 创建多个工件。

5) 所以最后一个问题是:我怎样才能创建多个工件,这甚至是一件好事吗?

非常感谢您的帮助,我知道这很无聊 post 帮助菜鸟可能很无聊,但我坚持了很长时间...

1,您可以从 UI 页面(构建摘要页面,见下文)或使用 Rest api 为构建添加标签。它可用于在构建之间进行过滤。如果您的构建管道生成了多个工件,构建标签将无法过滤在一个构建中生成的工件。

2,因此您可以考虑如何为管道中的每个应用程序分离构建工件。

您可以使用 Archive files task to zip your build artifacts and publish using publish build artifacts task.

参见下面的 yaml 示例:我使用两个存档文件任务分别打包 app1 和 app2 的构建工件。并将压缩的工件保存在文件夹 $(Build.ArtifactStagingDirectory) 中。 然后发布构建工件任务将工件发布到 azure devops 云。 (发布管道将下载工件以部署到您的应用服务器)

- task: ArchiveFiles@2
      inputs:
        rootFolderOrFile: $(system.defaultworkingdirectory)/app1/dist
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/app1/dist1.zip'
        includeRootFolder: false
      enabled: true
    - task: ArchiveFiles@2
      inputs:
        rootFolderOrFile: $(system.defaultworkingdirectory)/app2/dist
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/app2/dist2.zip'
        includeRootFolder: false
      enabled: true


    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: $(Build.ArtifactStagingDirectory)/
        artifactName: build

3、然后你可以在你的发布管道中使用多个阶段并使用Azure App Service Deploy task。对于以下示例:

在第一阶段添加 Azure App Service Deploy task 并将程序包设置为 $(System.DefaultWorkingDirectory)/**/app1/dist1.zip 以部署 app1。在第二阶段将其设置为 $(System.DefaultWorkingDirectory)/**/app2/dist2.zip 以部署 app2。

在 monorepo 中部署多个应用程序的另一种解决方法是创建多个 build/release 管道,每个应用程序一个。并在构建管道中使用 path filter 让构建管道仅在更新对应的应用程序时触发。

trigger:
  paths:
    include:
    - root/app1/*

希望以上内容对您有所帮助!

@levi Lu-MSFT 的回答对我帮助很大,基本上我们就是这样做的,但他们并没有真正给出完整的 yaml 代码。

我们设置发布管道的方式是每个应用程序一个发布管道,查看发布触发器的构建标签。我们目前将所有应用程序捆绑在一个工件中,因此缺点是当应用程序的主分支被触发时,发布过程将下载所有应用程序。

我动态添加构建标签的方式是从这个 nx 命令开始的:

npx nx affected:apps --base=origin/main --plain

打印出已更改的应用程序列表。从那里,我遍历应用程序并动态创建构建标签。

这里我有两个工作,一个在 PR 上运行,一个在主分支上运行。

trigger:
  branches:
    include:
      - main
pool:
  name: My-Pool
  demands: Agent.OS -equals Windows_NT

jobs:
  - job: NX_AFFECTED_PR
    pool:
      name: WBOmega-Pool
      demands: Agent.OS -equals Windows_NT
    condition: eq(variables['Build.Reason'], 'PullRequest')
    steps:
      - task: Npm@1
        displayName: 'npm install'
        inputs:
          command: 'install'
          verbose: true

      - powershell: |
          npx nx affected:apps --base=origin/main --plain | Tee-Object -Variable output

          [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
          foreach ($appName in ($output -replace '\s+', ' ').Split()) {
                  Write-Host "this is the app-name: $appName"
                  if($appName.Trim() -eq ""){
                    Write-Host "App name is blank"
                  } else {
                    $url="https://dev.azure.com/YOUR_ORG/$(System.TeamProject)/_apis/build/builds/$(build.buildid)/tags/" + $appName + "?api-version=6.0"
                    Write-Host $url
                    $result = Invoke-RestMethod -Uri $url -Headers @{Authorization = "Bearer $(System.AccessToken)"} -Method Put
                  }
          }
        name: set_build_tags
      - powershell: |
          npx nx affected --target=build --base=origin/main --parallel --max-parallel=3
        name: build
      - powershell: |
          npx nx affected --target=lint --base=origin/main --parallel --max-parallel=3
        name: lint
      - powershell: |
          npx nx affected --target=test --base=origin/main --parallel --max-parallel=3
        name: test

  - job: NX_AFFECTED_MAIN
    pool:
      name: My-Pool
      demands: Agent.OS -equals Windows_NT
    condition: ne(variables['Build.Reason'], 'PullRequest')
    steps:
      - checkout: self
        persistCredentials: true
      - powershell: |
          npx nx affected:apps --base=HEAD~1 --plain | Tee-Object -Variable output
          [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
          foreach ($appName in ($output -replace '\s+', ' ').Split()) {
                  Write-Host "this is the app-name: $appName"
                  if($appName.Trim() -eq ""){
                    Write-Host "App name is blank"
                  } else {
                    $url="https://dev.azure.com/YOUR_ORG/$(System.TeamProject)/_apis/build/builds/$(build.buildid)/tags/" + $appName + "?api-version=6.0"
                    Write-Host $url
                    $result = Invoke-RestMethod -Uri $url -Headers @{Authorization = "Bearer $(System.AccessToken)"} -Method Put
                  }
          }
        name: set_build_tags
      - powershell: |
          npx nx affected --target=build --base=HEAD~1 --parallel --max-parallel=3
        name: build
      - powershell: |
          npx nx affected --target=lint --base=HEAD~1 --parallel --max-parallel=3
        name: lint
      - powershell: |
          npx nx affected --target=test --base=HEAD~1 --parallel --max-parallel=3
        name: test
      - task: CopyFiles@2
        displayName: 'Copy Files to: $(build.artifactstagingdirectory)\apps\'
        inputs:
          SourceFolder: '$(Build.SourcesDirectory)\dist\apps\'
          TargetFolder: '$(build.artifactstagingdirectory)\apps\'
          CleanTargetFolder: true
          OverWrite: true

      - task: PublishBuildArtifacts@1
        displayName: 'Publish Artifact'
        inputs:
          PathtoPublish: '$(Build.ArtifactStagingDirectory)\apps'
          ArtifactName: 'Apps'