Azure Pipeline 部署重置应用程序配置
Azure Pipeline deployment resets App configuration
我创建了一个简单的 Azure Functions,我想用它来练习良好的 DevOps 实践。我准备了一个 azure-pipelines.yml
,它执行以下操作:
- 构建应用代码
- 运行测试
- 将二进制文件发布为工件
- 创建 Azure 资源(应用服务计划、Azure 函数、存储帐户、App Insights)
- 将代码部署到 Azure 函数。
我听说过很多关于基础架构即代码的信息,我真的很想尝试一下,这就是第 4 点存在的原因。
这是我的 azure-pipelines.yml
:
trigger:
- master
variables:
azureServiceConnection: service-connection
appName: az-func-123456123
resourceGroup: rg-1223456123
location: North Europe
buildConfiguration: Release
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: CI
jobs:
- job: Azure_Function
displayName: 'Azure Functions'
steps:
- checkout: self
- task: DotNetCoreCLI@2
displayName: Restore
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Test
inputs:
command: test
projects: '**/*Tests/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Zip Artifact
inputs:
command: publish
publishWebProjects: false
arguments: '--no-build --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: True
workingDirectory: src/az-function-with-deployment
- publish: $(Build.ArtifactStagingDirectory)
artifact: AzureFunction
- stage: Deployment
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
jobs:
- deployment: Deploy
environment: test-env
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: AzureResourceGroupDeployment@2
displayName: Deploy Azure resources
inputs:
deploymentScope: 'Resource Group'
ConnectedServiceName: '$(azureServiceConnection)'
action: 'Create Or Update Resource Group'
resourceGroupName: $(resourceGroup)
location: $(location)
templateLocation: 'Linked artifact'
csmFile: 'templates/function-app-deployment.json'
deploymentMode: 'Incremental'
- task: AzureFunctionApp@1
displayName: Deploy Azure Function
inputs:
azureSubscription: $(azureServiceConnection)
resourceGroupName: $(resourceGroup)
appType: functionAppLinux
appName: $(appName)
package: $(Pipeline.Workspace)/AzureFunction/*.zip
这是 AzureResourceGroupDeployment@2
任务使用的我的 templates/function-app-deployment.json
:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appName": {
"type": "string",
"defaultValue": "az-func-123456123",
"metadata": {
"description": "The name of the function app that you wish to create."
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS"
],
"metadata": {
"description": "Storage Account type"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"appInsightsLocation": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for Application Insights"
}
},
"runtime": {
"type": "string",
"defaultValue": "dotnet",
"allowedValues": [
"node",
"dotnet",
"java"
],
"metadata": {
"description": "The language worker runtime to load in the function app."
}
}
},
"variables": {
"functionAppName": "[parameters('appName')]",
"hostingPlanName": "[parameters('appName')]",
"applicationInsightsName": "[parameters('appName')]",
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
"functionWorkerRuntime": "[parameters('runtime')]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"kind": "Storage"
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2020-06-01",
"name": "[variables('hostingPlanName')]",
"location": "[parameters('location')]",
"kind": "linux",
"sku": {
"tier": "Dynamic",
"name": "Y1"
},
"properties": {
"name": "[variables('hostingPlanName')]",
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2020-06-01",
"name": "[variables('functionAppName')]",
"location": "[parameters('location')]",
"kind": "functionapp",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('functionAppName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "[variables('functionWorkerRuntime')]"
}
],
"linuxFxVersion": "dotnet|3.1"
}
}
},
{
"type": "microsoft.insights/components",
"apiVersion": "2020-02-02-preview",
"name": "[variables('applicationInsightsName')]",
"location": "[parameters('appInsightsLocation')]",
"tags": {
"[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource"
},
"properties": {
"ApplicationId": "[variables('applicationInsightsName')]",
"Request_Source": "IbizaWebAppExtensionCreate"
}
}
]
}
部署成功,我创建了资源。但是,我注意到了一些问题:
- 每次部署时,我的应用程序的配置都会重置!目前,在部署之后,我手动将所有配置值添加到我的 Azure 函数中,这非常不方便。我想我可以在管道本身中进行配置,但是,我不想在那里硬编码任何值!好像不太对。我还没有找到任何关于在云中配置应用程序的 resource/best 实践。我错过了什么?
- 我的管道中的一些值是硬编码的(比如应用程序的名称)。我目前真的没有看到任何问题,但我想知道,事情应该如何?我可以改进什么吗?
总的来说,我很高兴看到任何关于我发布的 YAML 和 JSON 的评论。我相信还有很多需要改进的地方。
问题是你运行每次ARM部署都要部署函数。所以它只为您提供在模板中声明的 AppSettings。请看here - Don't delete AppSettings not declared in a template.
你可以做些什么来解决这个问题?!
将 ARM 模板部署移动到单独的管道 - 专门用于更新管道基础结构的管道。每次要部署代码时都无需重新部署基础架构。您可以使用 path filter 来确保只有在进行了适当的更改后,您的管道才会 运行。但是,这并不能解决删除应用程序设置的问题。它使它发生的频率降低。
要解决已删除设置的问题,您需要执行以下操作之一:
- 在 ARM 模板中处理它们
- 在 运行将 ARM 模板连接到 store appsetings/connection strings in file, deploy ARM template, run Azute CLI to update appsettings/连接字符串之前调用 Azure CLI
我创建了一个简单的 Azure Functions,我想用它来练习良好的 DevOps 实践。我准备了一个 azure-pipelines.yml
,它执行以下操作:
- 构建应用代码
- 运行测试
- 将二进制文件发布为工件
- 创建 Azure 资源(应用服务计划、Azure 函数、存储帐户、App Insights)
- 将代码部署到 Azure 函数。
我听说过很多关于基础架构即代码的信息,我真的很想尝试一下,这就是第 4 点存在的原因。
这是我的 azure-pipelines.yml
:
trigger:
- master
variables:
azureServiceConnection: service-connection
appName: az-func-123456123
resourceGroup: rg-1223456123
location: North Europe
buildConfiguration: Release
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: CI
jobs:
- job: Azure_Function
displayName: 'Azure Functions'
steps:
- checkout: self
- task: DotNetCoreCLI@2
displayName: Restore
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Test
inputs:
command: test
projects: '**/*Tests/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Zip Artifact
inputs:
command: publish
publishWebProjects: false
arguments: '--no-build --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: True
workingDirectory: src/az-function-with-deployment
- publish: $(Build.ArtifactStagingDirectory)
artifact: AzureFunction
- stage: Deployment
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
jobs:
- deployment: Deploy
environment: test-env
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: AzureResourceGroupDeployment@2
displayName: Deploy Azure resources
inputs:
deploymentScope: 'Resource Group'
ConnectedServiceName: '$(azureServiceConnection)'
action: 'Create Or Update Resource Group'
resourceGroupName: $(resourceGroup)
location: $(location)
templateLocation: 'Linked artifact'
csmFile: 'templates/function-app-deployment.json'
deploymentMode: 'Incremental'
- task: AzureFunctionApp@1
displayName: Deploy Azure Function
inputs:
azureSubscription: $(azureServiceConnection)
resourceGroupName: $(resourceGroup)
appType: functionAppLinux
appName: $(appName)
package: $(Pipeline.Workspace)/AzureFunction/*.zip
这是 AzureResourceGroupDeployment@2
任务使用的我的 templates/function-app-deployment.json
:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appName": {
"type": "string",
"defaultValue": "az-func-123456123",
"metadata": {
"description": "The name of the function app that you wish to create."
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS"
],
"metadata": {
"description": "Storage Account type"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"appInsightsLocation": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for Application Insights"
}
},
"runtime": {
"type": "string",
"defaultValue": "dotnet",
"allowedValues": [
"node",
"dotnet",
"java"
],
"metadata": {
"description": "The language worker runtime to load in the function app."
}
}
},
"variables": {
"functionAppName": "[parameters('appName')]",
"hostingPlanName": "[parameters('appName')]",
"applicationInsightsName": "[parameters('appName')]",
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
"functionWorkerRuntime": "[parameters('runtime')]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"kind": "Storage"
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2020-06-01",
"name": "[variables('hostingPlanName')]",
"location": "[parameters('location')]",
"kind": "linux",
"sku": {
"tier": "Dynamic",
"name": "Y1"
},
"properties": {
"name": "[variables('hostingPlanName')]",
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2020-06-01",
"name": "[variables('functionAppName')]",
"location": "[parameters('location')]",
"kind": "functionapp",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('functionAppName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "[variables('functionWorkerRuntime')]"
}
],
"linuxFxVersion": "dotnet|3.1"
}
}
},
{
"type": "microsoft.insights/components",
"apiVersion": "2020-02-02-preview",
"name": "[variables('applicationInsightsName')]",
"location": "[parameters('appInsightsLocation')]",
"tags": {
"[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource"
},
"properties": {
"ApplicationId": "[variables('applicationInsightsName')]",
"Request_Source": "IbizaWebAppExtensionCreate"
}
}
]
}
部署成功,我创建了资源。但是,我注意到了一些问题:
- 每次部署时,我的应用程序的配置都会重置!目前,在部署之后,我手动将所有配置值添加到我的 Azure 函数中,这非常不方便。我想我可以在管道本身中进行配置,但是,我不想在那里硬编码任何值!好像不太对。我还没有找到任何关于在云中配置应用程序的 resource/best 实践。我错过了什么?
- 我的管道中的一些值是硬编码的(比如应用程序的名称)。我目前真的没有看到任何问题,但我想知道,事情应该如何?我可以改进什么吗?
总的来说,我很高兴看到任何关于我发布的 YAML 和 JSON 的评论。我相信还有很多需要改进的地方。
问题是你运行每次ARM部署都要部署函数。所以它只为您提供在模板中声明的 AppSettings。请看here - Don't delete AppSettings not declared in a template.
你可以做些什么来解决这个问题?!
将 ARM 模板部署移动到单独的管道 - 专门用于更新管道基础结构的管道。每次要部署代码时都无需重新部署基础架构。您可以使用 path filter 来确保只有在进行了适当的更改后,您的管道才会 运行。但是,这并不能解决删除应用程序设置的问题。它使它发生的频率降低。
要解决已删除设置的问题,您需要执行以下操作之一:
- 在 ARM 模板中处理它们
- 在 运行将 ARM 模板连接到 store appsetings/connection strings in file, deploy ARM template, run Azute CLI to update appsettings/连接字符串之前调用 Azure CLI