From 173bf789c84f954a22a2cca7db4bd43fccdf2e47 Mon Sep 17 00:00:00 2001 From: Are Almaas Date: Wed, 16 Oct 2024 10:13:14 +0200 Subject: [PATCH 1/3] feat(service): deploy application in container apps --- .azure/applications/service/main.bicep | 161 ++++++++++++++++++ .azure/applications/service/prod.bicepparam | 12 ++ .../applications/service/staging.bicepparam | 12 ++ .azure/applications/service/test.bicepparam | 12 ++ .azure/modules/containerApp/main.bicep | 23 ++- .github/workflows/workflow-deploy-apps.yml | 1 + 6 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 .azure/applications/service/main.bicep create mode 100644 .azure/applications/service/prod.bicepparam create mode 100644 .azure/applications/service/staging.bicepparam create mode 100644 .azure/applications/service/test.bicepparam diff --git a/.azure/applications/service/main.bicep b/.azure/applications/service/main.bicep new file mode 100644 index 000000000..6720067b1 --- /dev/null +++ b/.azure/applications/service/main.bicep @@ -0,0 +1,161 @@ +targetScope = 'resourceGroup' + +@description('The tag of the image to be used') +@minLength(3) +param imageTag string + +@description('The environment for the deployment') +@minLength(3) +param environment string + +@description('The location where the resources will be deployed') +@minLength(3) +param location string + +@description('The suffix for the revision of the container app') +@minLength(3) +param revisionSuffix string + +@description('CPU and memory resources for the container app') +param resources object? + +@description('The name of the container app environment') +@minLength(3) +@secure() +param containerAppEnvironmentName string + +@description('The connection string for Application Insights') +@minLength(3) +@secure() +param appInsightConnectionString string + +@description('The name of the App Configuration store') +@minLength(5) +@secure() +param appConfigurationName string + +@description('The name of the Key Vault for the environment') +@minLength(3) +@secure() +param environmentKeyVaultName string + +var namePrefix = 'dp-be-${environment}' +var baseImageUrl = 'ghcr.io/digdir/dialogporten-' +var tags = { + Environment: environment + Product: 'Dialogporten' +} + +resource appConfiguration 'Microsoft.AppConfiguration/configurationStores@2023-03-01' existing = { + name: appConfigurationName +} + +resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' existing = { + name: containerAppEnvironmentName +} + +var containerAppEnvVars = [ + { + name: 'ASPNETCORE_ENVIRONMENT' + value: environment + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: appInsightConnectionString + } + { + name: 'AZURE_APPCONFIG_URI' + value: appConfiguration.properties.endpoint + } + { + name: 'ASPNETCORE_URLS' + value: 'http://+:8080' + } +] + +resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = { + name: environmentKeyVaultName +} + +var serviceName = 'service' + +var containerAppName = '${namePrefix}-${serviceName}' + +var port = 8080 + +var probes = [ + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Liveness' + httpGet: { + path: '/health/liveness' + port: port + } + } + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Readiness' + httpGet: { + path: '/health/readiness' + port: port + } + } + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Startup' + httpGet: { + path: '/health/startup' + port: port + } + } +] + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: '${namePrefix}-service-identity' + location: location + tags: tags +} + +module keyVaultReaderAccessPolicy '../../modules/keyvault/addReaderRoles.bicep' = { + name: 'keyVaultReaderAccessPolicy-${containerAppName}' + params: { + keyvaultName: environmentKeyVaultResource.name + principalIds: [managedIdentity.properties.principalId] + } +} + +module appConfigReaderAccessPolicy '../../modules/appConfiguration/addReaderRoles.bicep' = { + name: 'appConfigReaderAccessPolicy-${containerAppName}' + params: { + appConfigurationName: appConfigurationName + principalIds: [managedIdentity.properties.principalId] + } +} + +module containerApp '../../modules/containerApp/main.bicep' = { + name: containerAppName + params: { + name: containerAppName + image: '${baseImageUrl}${serviceName}:${imageTag}' + location: location + envVariables: containerAppEnvVars + containerAppEnvId: containerAppEnvironment.id + tags: tags + resources: resources + probes: probes + port: port + revisionSuffix: revisionSuffix + userAssignedIdentityId: managedIdentity.id + // TODO: Once all container apps use user-assigned identities, remove this comment and ensure userAssignedIdentityId is always provided + } + dependsOn: [ + keyVaultReaderAccessPolicy + appConfigReaderAccessPolicy + ] +} + +output name string = containerApp.outputs.name +output revisionName string = containerApp.outputs.revisionName diff --git a/.azure/applications/service/prod.bicepparam b/.azure/applications/service/prod.bicepparam new file mode 100644 index 000000000..7abc5dfbb --- /dev/null +++ b/.azure/applications/service/prod.bicepparam @@ -0,0 +1,12 @@ +using './main.bicep' + +param environment = 'prod' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/applications/service/staging.bicepparam b/.azure/applications/service/staging.bicepparam new file mode 100644 index 000000000..8f45eca13 --- /dev/null +++ b/.azure/applications/service/staging.bicepparam @@ -0,0 +1,12 @@ +using './main.bicep' + +param environment = 'staging' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/applications/service/test.bicepparam b/.azure/applications/service/test.bicepparam new file mode 100644 index 000000000..b3f5fed67 --- /dev/null +++ b/.azure/applications/service/test.bicepparam @@ -0,0 +1,12 @@ +using './main.bicep' + +param environment = 'test' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/modules/containerApp/main.bicep b/.azure/modules/containerApp/main.bicep index 7eb404bef..503a56da9 100644 --- a/.azure/modules/containerApp/main.bicep +++ b/.azure/modules/containerApp/main.bicep @@ -31,6 +31,10 @@ param revisionSuffix string @description('The probes for the container app') param probes array = [] +// TODO: Refactor to make userAssignedIdentityId a required parameter once all container apps use user-assigned identities +@description('The ID of the user-assigned managed identity (optional)') +param userAssignedIdentityId string = '' + // Container app revision name does not allow '.' character var cleanedRevisionSuffix = replace(revisionSuffix, '.', '-') @@ -50,12 +54,19 @@ var ingress = { ipSecurityRestrictions: ipSecurityRestrictions } +var identityConfig = empty(userAssignedIdentityId) ? { + type: 'SystemAssigned' +} : { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } +} + resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { name: name location: location - identity: { - type: 'SystemAssigned' - } + identity: identityConfig properties: { configuration: { ingress: ingress @@ -81,6 +92,10 @@ resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { tags: tags } -output identityPrincipalId string = containerApp.identity.principalId +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(userAssignedIdentityId)) { + name: last(split(userAssignedIdentityId, '/')) +} + +output identityPrincipalId string = empty(userAssignedIdentityId) ? containerApp.identity.principalId : managedIdentity.properties.principalId output name string = containerApp.name output revisionName string = containerApp.properties.latestRevisionName diff --git a/.github/workflows/workflow-deploy-apps.yml b/.github/workflows/workflow-deploy-apps.yml index 79a87e76f..4caf110ff 100644 --- a/.github/workflows/workflow-deploy-apps.yml +++ b/.github/workflows/workflow-deploy-apps.yml @@ -145,6 +145,7 @@ jobs: - name: web-api-eu - name: web-api-so - name: graphql + - name: service environment: ${{ inputs.environment }} permissions: id-token: write From c1ce84b9d9a36bf9146c7834eb62d4a394d3051a Mon Sep 17 00:00:00 2001 From: Are Almaas Date: Wed, 16 Oct 2024 10:18:49 +0200 Subject: [PATCH 2/3] cleanup --- .azure/applications/service/main.bicep | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.azure/applications/service/main.bicep b/.azure/applications/service/main.bicep index 6720067b1..33ab5e65e 100644 --- a/.azure/applications/service/main.bicep +++ b/.azure/applications/service/main.bicep @@ -139,7 +139,9 @@ module containerApp '../../modules/containerApp/main.bicep' = { name: containerAppName params: { name: containerAppName - image: '${baseImageUrl}${serviceName}:${imageTag}' + // todo: make this dynamic based on service name. Using webapi for now. + // image: '${baseImageUrl}${serviceName}:${imageTag}' + image: '${baseImageUrl}webapi:${imageTag}' location: location envVariables: containerAppEnvVars containerAppEnvId: containerAppEnvironment.id From f8b40f52344f21e596a35b58d9e51a6a74e113ca Mon Sep 17 00:00:00 2001 From: Are Almaas Date: Wed, 16 Oct 2024 10:45:19 +0200 Subject: [PATCH 3/3] cleanup --- .azure/applications/service/main.bicep | 2 -- 1 file changed, 2 deletions(-) diff --git a/.azure/applications/service/main.bicep b/.azure/applications/service/main.bicep index 33ab5e65e..d037ce8fe 100644 --- a/.azure/applications/service/main.bicep +++ b/.azure/applications/service/main.bicep @@ -31,12 +31,10 @@ param appInsightConnectionString string @description('The name of the App Configuration store') @minLength(5) -@secure() param appConfigurationName string @description('The name of the Key Vault for the environment') @minLength(3) -@secure() param environmentKeyVaultName string var namePrefix = 'dp-be-${environment}'