【DevOps云实践】使用Azure Pipeline部署.NET应用到Azure App Service
@TOC
推荐超级课程:
在开发过程中,包含许多步骤,其中之一是交付部分 - 应用程序需要放置在某个地方,以供最终用户使用并运行并提供实际功能。这是DevOps文化和一般SDLC周期的一部分。
这部分通常被称为构建和部署,或者更现代化的方式是CI(持续集成)和CD(持续交付)。主要思想是尽可能自动化这个过程,每当我们推送新代码时,构建和发布过程可以自动运行,以将更改交付到源位置。
这里将讨论以下主题:
- Azure App Service - 将应用程序作为服务托管在Azure上。
- Azure DevOps - 一些配置事项。
- Azure Pipelines - 构建和部署配置。
1 Microsoft Azure
根据统计数据 和用法 ,这是第二受欢迎的云服务提供商。云提供了许多服务和功能。其中最常用和受欢迎的之一是应用服务(用于托管Web应用程序、函数、作业)。
1.1 在Azure门户中创建服务
要创建新资源,需要前往portal.azure.com,创建新的资源组、创建应用服务计划、应用服务,所有这些都在这个组中。复制名称或记住,因为稍后需要在变量中使用它来指定部署应用程序的位置。
对于本示例,将使用Windows操作系统用于应用服务。其他可能的类型是Linux或容器。
带有资源的示例资源组
建议在组和所有资源上使用强命名约定。对于某些服务,比如存储账户,需要去除空格和其他特殊字符。因此,建议阅读关于此主题的文档和文章。
2 Azure DevOps
这是主要可用于一些项目的服务,用于存储代码并删除工作。假设有包含代码(至少)的存储库。
在系统中,我们需要在项目设置中开启Pipeline。此设置可通过创建项目时选择或稍后使用所需功能。选择“项目设置” → “概述”。
对于我们的目的和工作,我们需要:
- Azure Repos - 用于具有代码的Git存储库。
- Azure Pipelines - 用于CI/CD等事项。
其他服务一般不需要在此文章的主题内操作。
2.1 服务连接
要使用部署功能,需要配置服务连接以在所有Pipeline中部署到Azure。前往“项目设置” → “服务连接”。
在创建部署Pipeline之前需要完成此配置!
项目中添加的服务连接示例
要添加新连接,需要拥有Azure帐户,通常是所有服务(DevOps、Azure等)的Microsoft帐户。
要添加新连接,需要选择“新服务连接” → “Azure Resource Manager”。还可以使用其他服务,但大多适用于旧的和更自定义的事项与手动配置。取决于您拥有的权限,配置选项可能会有所不同。
添加到Azure的新服务连接
选择“服务主体(自动)”用于最基本的使用(如果您是Azure中拥有所有权限的管理员)。
选择Azure的身份验证方法
提供连接详细信息。在这里需要从Azure选择活动订阅(它将根据您的帐户自动加载),添加名称(唯一),描述。不需要选择资源组 - 仅在连接需要仅在特定组内使用时才需要。还要勾选授予所有Pipeline访问权限的框 - 这将有助于在您构建的所有Pipeline中使用此连接。
提供连接设置和名称(唯一)
经过这些步骤后,新连接就可以在Pipeline中用于部署到Azure。或者也可以在经典发布中用作部署,因为内部使用相同的逻辑和任务。
3 Azure Pipeline
文档为该服务提供了简单描述:
AzurePipeline支持持续集成(CI)和持续交付(CD),以持续测试、构建和部署您的代码。您可以通过定义Pipeline来实现这一点。
构建Pipeline的最新方法是使用YAMLPipeline编辑器 。您还可以使用Classic编辑器 中的ClassicPipeline。
这意味着我们可以使用它来为最终用户提供应用程序,并自动化一些过程,如构建、测试、部署等。它可以以非常不同的方式进行配置,以实现良好且强大的DevOpsPipeline和一般开发流程(简单、快速、一切均为代码)。
这里将仅涵盖使用YAMLPipeline,它允许使用代码和Git管理文件和历史记录,并在可能的情况下共享功能。
与Pipeline一起工作的基本流程如下:
您可以在名为azure-pipelines.yml
的YAML文件中定义Pipeline。
其中有2个主要步骤:
- 构建 - 构建应用程序,可以运行代码检查、测试等。结果是包含应用程序以供运行或部署的构件。
- 部署 - 将应用程序构件部署到托管位置。
其中有不同的概念,但非常简短:
- Pipeline可以包含阶段
- 阶段可以逐个执行
- 阶段可以依赖于先前的阶段
- 每个阶段可以包含工作
- 工作可以并行执行
- 每个工作可以包含步骤
- 步骤是要执行的基本操作(如安装包、构建、发布等)
- 步骤是具有参数的可用任务列表中的任务
下面的图像提供了Pipeline结构和元素的简单描述:阶段、工作、步骤(任务)。
Pipeline结构和元素的可视化描述
因此,如果我们在Pipeline中查看,可以使用阶段 - 每个阶段中的步骤按顺序运行。
为了更好地允许自定义步骤,可以使用模板功能 - 每个步骤都可以存储为单独的文件作为模板。更多详细信息请参阅文档 。
为准备Pipeline,需要执行一些步骤:
- 创建存储库 - 在此点,我们已经有了存储库。
- 创建环境 - 准备用于部署应用程序的环境定义的配置,配置它们(如批准等)。
- 创建具有变量组的库 - 以在Pipeline中使用变量,并在不更改代码的情况下进行编辑。
- 创建Pipeline - 在存储库中准备文件,并为新Pipeline选择它们。
- 配置安全性 - 访问库、Pipeline权限以便能够使用它们。
这里我们将介绍使用YAML和环境进行部署的现代方式。这与使用另一种方法和配置的经典构建和发布不同。
3.1 创建环境
要从Pipeline部署,需要配置要用于执行部署作业的环境列表 - 一种特殊的工作类型,用于处理环境配置。
要创建新环境需前往“项目” → “Pipeline” → “环境”并点击“创建环境”按钮。
创建新环境
之后,我们可以看到环境列表以及最新状态(如果已经部署了一些东西)。
还可以配置,以便需要得到某些环境的批准才能进行部署。例如,让我们创建配置以指定在部署到“QA”环境时,需要某用户批准。为此,单击QA环境,转到配置 → “批准和检查”。
选择批准和检查
如果尚未配置任何内容,将看到空白屏幕。
环境的配置列表为空
要添加批准者,需要从目录中选择用户,并允许他们批准部署至该环境。
配置批准者
如果选择了多个用户,还可以配置是否需要所有用户批准,或者是否需要其中一个、两个或任意数量的用户批准。
3.2 创建库
在我们的示例中,我们将使用库来存储一些Pipeline变量,以便为Pipeline提供不同环境的不同值。这将有助于使用一个代码库,但具有不同的输入。
要创建该组,需前往“Pipeline” → “库”。
创建新组
配置组:名称、变量、描述等。
创建变量组并定义变量
为所有环境创建变量组。
创建的示例变量组
这是在所有环境中拥有单独的参数/值的主要思想。
3.3 创建Pipeline
首先需要创建带有逻辑和代码的文件。之后配置Pipeline并从存储库中选择适当的文件 - azure-pilelines.yml。
3.3.1 Pipeline代码
.NET应用程序的示例Pipeline包含存储库中的几个文件。所有文件都放置在存储库根目录中的build文件夹中,其中包含解决方案和其他文件夹和项目。
包含构建和部署阶段的Pipeline示例。
此示例文件(作为模板):
- azure-pipelines.yml - 提供结构和工作流的主文件。
- pipelines/build.yml - 用于定义构建步骤和需要运行的工作的阶段。
- pipelines/release.yml - 用于部署功能的阶段,定义工作和规则。
- pipelines/build-dotnet.yml - 具有常用步骤的模板文件,用于使用dotnet cli构建.NET应用程序。
- pipelines/deploy-webapp.yml - 具有常用步骤的模板文件,用于从包(.zip归档文件)部署应用程序到Azure应用服务。
所有用于构建.NET应用程序的步骤都基于使用dotnet cli 。您可以在文档中了解更多信息。但基本上在本地开发中使用的命令相同。
文件 azure-pipelines.yml:
# Pipeline的参数
parameters:
- name: environment
displayName: Environment
type: string
default: BuildOnly
values:
- BuildOnly
- Development
- name: projectName
displayName: ProjectName
type: string
default: <Your_project_name> # <-replace value here!
- name: skipLint
displayName: SkipLint
type: boolean
default: false
# 没有自动触发器
trigger:
- none
variables:
- group: Deploy-Environment-Shared
# 阶段 - 逐个执行的工作列表
stages:
# 构建和检查(以及其他内容)阶段
- template: pipelines/build.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}
SkipLint: ${{ parameters.SkipLint }}
- ${{ if not(eq(parameters.Environment,'BuildOnly')) }}:
- template: pipelines/release.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}
文件 pipelines/build.yml:
stages:
- stage: Build
displayName: Build${{ parameters.ProjectName }}
# 使用多个作业,使 linter 可以并行工作以完成构建。
# 这还允许在 Linux 上运行 Linter,而您的构建可以在 Windows 或 Mac 上运行。
jobs:
# 使用来自 GitHub 的 Super-Linter 对代码进行 Lint
- job: lint
displayName: Lint 代码库
condition: eq(${{ parameters.SkipLint }}, false)
pool:
vmImage: 'ubuntu-latest'
steps:
- script: docker pull github/super-linter:latest
displayName: 拉取 GitHub Super-Linter 镜像
- script: >-
docker run
-e RUN_LOCAL=true
-e VALIDATE_JSCPD=false
-e VALIDATE_MARKDOWN=false
-e VALIDATE_EDITORCONFIG=false
-v $(System.DefaultWorkingDirectory):/tmp/lint
github/super-linter
displayName: '运行 GitHub Super-Linter'
continueOnError: true
# 进行主要构建操作
- job: build
displayName: 构建和测试,创建构件
pool:
vmImage: 'windows-latest'
variables:
BuildConfiguration: 'Release'
steps:
- template: build-dotnet.yml
parameters:
ProjectServiceName: ${{ parameters.ProjectName }}
RestoreBuildProjects: '**/*.csproj'
TestProjects: 'tests/**/*.csproj'
DotnetVersion: '7.x'
ServicePublishProjects: '**/<your project name>.csproj' # 替换此处的值
文件 pipelines/build-dotnet.yml:
steps:
- checkout: self
clean: true
# 安装并缓存 .NET SDK
- task: UseDotNet@2
displayName: '安装 Dotnet Core cli'
inputs:
version: ${{ parameters.DotnetVersion }}
# 恢复 NuGet packages
- task: DotNetCoreCLI@2
displayName: 'Dotnet 恢复'
inputs:
command: restore
projects: |
${{ parameters.RestoreBuildProjects }}
${{ parameters.TestProjects }}
# 构建项目
- task: DotNetCoreCLI@2
displayName: 'Dotnet 构建'
inputs:
projects: ${{ parameters.ServicePublishProjects }}
arguments: --configuration $(BuildConfiguration) --no-restore
# 运行测试
- task: DotNetCoreCLI@2
condition: ne('${{ parameters.TestProjects }}', '')
displayName: 'Dotnet 测试'
inputs:
command: test
projects: ${{ parameters.TestProjects }}
arguments: '--configuration $(BuildConfiguration) --no-restore --collect "Code coverage"'
# 发布 web 应用程序(以便以后部署)
- task: DotNetCoreCLI@2
displayName: 发布
inputs:
command: publish
publishWebProjects: false
zipAfterPublish: true
projects: ${{ parameters.ServicePublishProjects }}
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory) --no-restore'
# 作业完成后在Pipeline中发布构件
- task: PublishPipelineArtifact@1
displayName: 发布Pipeline构件
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifactName: '${{ parameters.ProjectServiceName }}'
publishLocation: 'pipeline'
文件 pipelines/release.yml:
stages:
# 每个选定环境的主要部署阶段
- stage: Deploy
displayName: 部署${{ parameters.ProjectName }}
variables:
- group: Deploy-Environment-${{ parameters.Environment }}
jobs:
# 实际将 web 应用程序部署到 Azure
- deployment: DeployWebApp
displayName: 将 WebApp 部署到 Azure
pool:
vmImage: 'windows-latest'
environment: ${{ parameters.Environment }}
strategy:
runOnce:
deploy:
steps:
- template: deploy-webapp.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}
PackageName: '<your project name for package (zip)>' # 替换此处的值
文件 pipelines/deploy-webapp.yml:
steps:
- checkout: self
clean: true
# 下载Pipeline构件
- task: DownloadPipelineArtifact@2
inputs:
artifactName: '${{ parameters.ProjectName }}'
buildType: 'current'
targetPath: '$(Build.ArtifactStagingDirectory)${{ parameters.ProjectName }}'
# 部署到 Azure App Service
- task: AzureWebApp@1
displayName: '部署到 $(AppServiceNameBackend)'
inputs:
azureSubscription: '$(AzureServiceConnection)'
appType: 'webApp'
appName: '$(AppServiceNameBackend)'
package: '$(Build.ArtifactStagingDirectory)${{ parameters.ProjectName }}${{ parameters.PackageName }}.zip'
这些Pipeline文件可以用于大多数项目,只需要更新一些变量。当然,如果需要添加一些特殊定制,可以根据需要添加和修改。
3.3.2 创建Pipeline
要在存储库中使用代码,还需要进行一步操作 — 创建实际的Pipeline运行并查看结果。
创建新的Pipeline
选择要使用的存储库。可以使用外部服务,但在我们的示例和流程中,我们使用内部存储库以简化操作。主要思想是相同的,因为大多数其他服务提供了自己的 CI/CD 工具(GitHub Actions 等)。
选择源文件位置
从列表中选择存储库。通常情况下,您将只有一个与项目名称相同的存储库(默认情况下),但可能会有多个,因为项目作为所有相关工作的聚合。它可以是后端、前端、基础设施等。
具有应用程序的示例存储库
选择现有的Pipeline(如果所有代码已经推送到存储库中)或者可以从模板中选择。在这种情况下,Pipeline文件将仅包含一个根文件(azure-pipelines.yml)带有示例代码。
从模板或现有文件中选择
从存储库中的某个分支选择文件。示例中的文件位于具有所有相关文件的“build”文件夹中。
从特定分支选择新Pipeline
最后需要审查结果并能“运行”或“保存”。保存后,可以像往常一样运行。因为没有配置自动触发器,Pipeline需要手动运行。
审查并保存新Pipeline
此步骤准备好使用Pipeline,并将显示在列表中。
新Pipeline
我建议也使用文件夹来组织Pipeline定义。转到“所有”并单击“新建文件夹”按钮。
根据主题或组创建新文件夹
这将有助于搜索所需的Pipeline,按照您的意愿进行分组,将旧的移动到文件夹中,将新的移动到新的文件夹中等。
要移动到文件夹,需要选择Pipeline并选择“重命名/移动”。
选择要重命名或移动到文件夹的Pipeline
选择要移动的文件夹
3.4 附加配置
为了运行Pipeline并成功部署,需要配置一些安全事项:
- 用于库的Pipeline权限 — 用于每个使用的组。
- 环境的安全性 — 用于每个环境。
完成这些步骤后,Pipeline将准备就绪并可以运行。
3.4.1 配置库访问权限
每个变量组需要能够在Pipeline中使用。因此,需要进入每个Pipeline设置权限并选择所有变量。
配置Pipeline权限以使用组中的变量
从列表中添加Pipeline。
选择所有 YAML Pipeline以访问变量
选择后,选定的Pipeline将可以访问变量。
3.4.2 配置环境
选择环境、设置、安全性。这将打开带有用户和Pipeline权限的页面。
环境的安全性
将一个或多个Pipeline添加到列表中。
在“+”上选择所有Pipeline的列表
完成所有这些步骤后,新的Pipeline就可以运行并进行工作了。
3.5 运行Pipeline
要运行Pipeline,需要转到选择的Pipeline并单击“运行Pipeline”按钮。
由于Pipeline使用参数,它会在 UI 中向用户显示这些参数,以便用户可以选择并进行更改。
带有参数的运行Pipeline
当Pipeline成功验证编译的 YAML 后,它将显示阶段和作业。
如果 YAML 验证失败,它会显示错误并不会运行Pipeline。对于一些情况,如果验证完成但有安全性问题(权限) — 它会创建Pipeline但显示错误消息(在问题未解决之前将显示失败状态)。
阶段视图
作业视图
结果可能因您的项目、代码和其他方面而有所不同。但对于大多数情况,将显示日志、警告(如果有)、作业步骤,甚至测试结果(如果有)甚至更多 — 一些扩展可能会添加具有诸如代码分析等详细信息的附加选项卡。
运行作业后,可以查看流程中的每个步骤的步骤列表,其中包含所有日志和每个步骤的状态。因此,可以简单地查看每个步骤的更多详细信息,甚至下载原始日志文件。
包含每个步骤和日志的结果
选定的部署环境
下面的图片展示了当 QA 环境需要批准但用户拒绝时,它会停止Pipeline并以失败的状态结束。
如果Pipeline失败,这些构件就不能在发布中使用。只有成功的Pipeline可以稍后与构件一起使用。因此,可以在发布中使用它们以部署到多个环境中。
QA 部署在开发完成后失败(多环境的另一个示例)
接下来的示例是用户选择并批准 QA — 成功部署。
QA 部署成功
当然,在不同的项目和不同的配置中,可以看到相同的结果或不同的结果。但主要思想是一样的 — 需要逐步进行操作,并使用文档获取更多细节,了解如何使用和实现所需的行为。
3.5 Pipeline审查
这里提供了Pipeline的简短摘要以及所使用元素的可视结构:库、环境。
Azure DevOps 门户中的依赖关系
下图显示了文件的可视依赖关系 — 在这里我们可以看到这里有哪些阶段和作业。它们都保存在各自的文件中。
模板中的文件
接下来的图片提供了详细查看用于构建 .NET 应用程序的实际Pipeline模板。
包含步骤细节的详细Pipeline
希望这些图片能帮助您更好地了解Pipeline及其内部发生的情况。
结论
使用 Azure Pipeline是将 CI/CD 实施到项目中甚至是简单的方法。在本文中,我们看到了如何为 .NET 应用程序实现Pipeline(但还可以用于其他类型,只需修改构建模板 — 构件是用于后续部署的实际结果)使用 Azure DevOps(存储库)中的存储库、配置、使用模板、环境、库。