【发布时间】:2021-10-29 19:51:53
【问题描述】:
为了快速扩展功能应用程序,我们希望能够通过 IaC 部署它们,然后在其上部署代码包。不幸的是,这在 Azure DevOps 中无法通过 YAML 管道动态实现,因此我不得不求助于 Azure CLI。
您可以在下面看到我想出的 PowerShell 脚本,用于将代码部署到我事先通过 Terraform 部署的函数应用程序池中。为了加快速度,我打开了ForEach-Object 循环的并行处理,因为单个实例之间没有依赖关系。这在一定程度上也可以正常工作,但由于 Azure CLI 的古怪,我遇到了麻烦。将非错误信息写入 StdErr 似乎是设计使然。这与其他一些奇怪的行为相结合会导致以下情况:
- 按顺序运行通常可以完美运行,如果出现问题,我会看到任何错误输出。我也不需要设置
powerShellErrorActionPreference: 'continue'。这当然会大大减慢部署速度。 - 如果没有设置
powerShellErrorActionPreference: 'continue',并行运行总是会失败。失败的原因没有输出到控制台。即使没有像continue那样发生真正的错误,这似乎也会发生,也没有错误输出到控制台。如果管道在发生真正错误的情况下失败(应该通过检查ChildJobs的状态来处理 - 但事实并非如此。
所以我在这里是在一块岩石和一个坚硬的地方之间。有人看到我的实施中的缺陷吗?任何建议都非常感谢。
- task: AzureCLI@2
displayName: 'Functions deployment'
env:
AZURE_CORE_ONLY_SHOW_ERRORS: 'True'
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
ARM_CLIENT_ID: $(AzureApplicationId)
ARM_CLIENT_SECRET: $(AzureApplicationSecret)
ARM_SUBSCRIPTION_ID: $(AzureSubscriptionId)
ARM_TENANT_ID: $(AzureTenantId)
inputs:
azureSubscription: 'MySubscription'
scriptType: 'pscore'
scriptLocation: 'inlineScript'
inlineScript: |
Write-Output -InputObject "INFO: Get Function App names"
$appNames = terragrunt output -json all_functionapp_names | ConvertFrom-Json
Write-Output -InputObject "INFO: Loop over Function Apps"
$jobs = $appNames | ForEach-Object -Parallel {
$name = $_
try
{
Write-Output -InputObject "INFO: $name`: start slot"
az functionapp start --resource-group $(ResourceGroup) --name "$name" --slot Stage --verbose
Write-Output -InputObject "INFO: $name`: deploy into slot"
az functionapp deploy --resource-group $(ResourceGroup) --name "$name" --slot Stage --src-path "$(System.ArtifactsDirectory)/drop/MyCodePackage.zip" --type zip --verbose
Write-Output -InputObject "INFO: $name`: deploy app settings"
az functionapp config appsettings set --resource-group $(ResourceGroup) --name "$name" --slot Stage --settings "@$(Build.ArtifactStagingDirectory)/appsettings.json" --verbose
Write-Output -InputObject "INFO: $name`: swap slot with production"
az functionapp deployment slot swap --resource-group $(ResourceGroup) --name "$name" --slot Stage --action swap --verbose
}
catch
{
Write-Output -InputObject "ERROR: $name`: An error occured during deployment"
Write-Output -InputObject ($_.Exception | Format-List -Force)
}
finally
{
try
{
Write-Output -InputObject "INFO: $name`: stop slot"
az functionapp stop --resource-group $(ResourceGroup) --name "$name" --slot Stage --verbose
}
catch
{
Write-Output -InputObject "ERROR: $name`: could not stop slot"
}
}
} -AsJob
[int]$pollingInterval = 10
[int]$elapsedSeconds = 0
while ($jobs.State -eq "Running") {
$jobs.ChildJobs | ForEach-Object {
Write-Output -InputObject "---------------------------------"
Write-Output -InputObject "INFO: $($_.Name) output [$($elapsedSeconds)s]"
Write-Output -InputObject "---------------------------------"
$_ | Receive-Job
Write-Output -InputObject "---------------------------------"
Write-Output -InputObject ""
}
$elapsedSeconds += $pollingInterval
[Threading.Thread]::Sleep($pollingInterval * 1000)
}
$jobs.ChildJobs | Where-Object { $_.JobStateInfo.State -eq "Failed" } | ForEach-Object {
Write-Output -InputObject "ERROR: At least one of the deployments failed with the following reason:"
Write-Output -InputObject $_.JobStateInfo.Reason
}
if ($jobs.State -eq "Failed")
{
exit 1
}
else
{
exit 0
}
powerShellErrorActionPreference: 'continue'
workingDirectory: './infrastructure/environments/$(TerraFormEnvironmentName)'
编辑 1
要从 ChildJobs 获取所有输出,我必须像这样更改代码:
[int]$pollingInterval = 10
[int]$elapsedSeconds = 0
$lastResultsRead = false
while ($jobs.State -eq "Running" -or !$lastResultsRead)
{
$lastResultsRead = $jobs.State -ne "Running"
$jobs.ChildJobs | ForEach-Object {
Write-Output -InputObject "---------------------------------"
Write-Output -InputObject "INFO: $($_.Name) output [$($elapsedSeconds)s]"
Write-Output -InputObject "---------------------------------"
$_ | Receive-Job
Write-Output -InputObject "---------------------------------"
Write-Output -InputObject ""
}
$elapsedSeconds += $pollingInterval
if (!$lastResultsRead)
{
[Threading.Thread]::Sleep($pollingInterval * 1000)
}
希望这可以帮助每个想要实现类似目标的人。
【问题讨论】:
-
脚本很好用。我有两个问题?您是否尝试在本地运行它?您是否考虑过将运行拆分为单独的作业,以便每个作业处理一个功能?
-
@KrzysztofMadej 谢谢。是的,其实我是先在本地开发脚本的。我刚刚获取了当前版本并再次在本地运行。仍然按预期工作。没有错误。将其拆分为单独的工作对我的情况有何帮助?当我们谈论大约 2-6 次并行执行时,多个作业比一个具有多个子作业的作业有什么优势吗?
-
单独的工作,实际上意味着单独的机器 - 如果 azure cli 调用之间存在奇怪的交互,这可能会有所帮助 - 这不应该发生,但我们就在我们所在的地方。
-
@KrzysztofMadej 正如我试图在我的帖子中描述的那样,这是不可能的。我首选的实现方式是创建一个 YAML 数组,其中每个部署都作为单独的条目。但为此,Azure Pipelines 需要预先了解这些元素。无法动态创建 YAML 数组。因此,管道中的单独作业是不可能的。此外,部署本身现在工作正常。只是 Azure CLI 的报告与 Azure CLI 任务如何处理导致所述问题的 StdErr 输出相结合。
-
好奇:代理上安装了哪个版本的 PowerShell Core?我相信
-Parallel是在 PowerShell 7.0 Preview 3(及更高版本)中添加的。
标签: powershell azure-devops azure-cli