Ken Muse

Connecting Azure API Management (APIM) and Application Insights Using Bicep


If you’re using Bicep templates, then you are probably trying to create idempotent definitions. You want each deployment to result in the same resources being published in the same state. Unfortunately, some resources need more help than others. Azure API Management (APIM) is one of those. To illustrate, let’s state with a few simple resources: Application Insights (connected to a Log Analytics workspace) and APIM:

param location string = resourceGroup().location

// Create a workspace
resource workspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
  name: 'myworkspace'
  location: location
}

// Create an App Insights resources connected to the workspace
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: 'apiminsights'
  location: location
  kind: 'web'
  properties:{
    Application_Type:'web'
    WorkspaceResourceId: workspace.id
  }
}

// Create an APIM 
resource apim 'Microsoft.ApiManagement/service@2019-12-01' = {
  name: 'myapim'
  location: location
  sku:{
    capacity: 0
    name: 'Consumption'
  }
  identity:{
    type:'SystemAssigned'
  }
  properties:{
    publisherName: 'DemoOps'
    publisherEmail: 'publisher@demoops.com'
  }
}

So far, easy enough. Next, you might think the right thing to do is to create a Logger in your template. For example:

resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-04-01-preview' = {
  parent: apim
  name: 'apimlogger'
  properties:{
    resourceId: appInsights.id
    description: 'Application Insights for APIM'
    loggerType: 'applicationInsights'
    credentials:{
      instrumentationKey: appInsights.properties.InstrumentationKey
    }
  }
}

This approach works and will deploy correctly, but it has a side effect. Each time the template is deployed, you’ll notice a new Named Value entry added to the APIM instance. Worse than that, each new entry will have the same value! Clearly, this isn’t an idempotent deployment. Time for a new strategy.

The best way to solve this issue is to create a Named Value in your template. You can then reference the Named Value in the Logger using the reference notation, {{namedValueName}}. Adding this to our example, we can create a Named Value called instrumentationKey and then reference it as {{instrumentationKey}} in the Logger:

resource namedValueAppInsightsKey 'Microsoft.ApiManagement/service/namedValues@2020-06-01-preview' = {
  parent: apim
  name: 'instrumentationKey'
  properties: {
    tags: []
    secret: false
    displayName: 'instrumentationKey'
    value: appInsightsApim.properties.InstrumentationKey
  }
}

resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-04-01-preview' = {
  parent: apim
  name: 'apimlogger'
  properties:{
    resourceId: appInsights.id
    description: 'Application Insights for APIM'
    loggerType: 'applicationInsights'
    credentials:{
      instrumentationKey: {{instrumentationKey}}
    }
  }
}

Now, each deployment will behave exactly as expected. We’ll have a single NamedValue entry, even after multiple deployments.

If we want to create a Named Value from a Key Vault entry, then we just need to make two adjustments. First, we mark the key as a secret (using secret: true). Next, we add a keyVault.secretIdentifier and provide a Secret URI for the value to be retrieved from Key Vault. The code looks like this:

resource namedValueAppInsightsSecret 'Microsoft.ApiManagement/service/namedValues@2020-06-01-preview' = {
  parent: apim
  name: 'instrumentationKey'
  properties: {
    tags: []
    secret: true
    keyVault: {
      secretIdentifier: secretAppInsights.properties.secretUri
    }
    displayName: 'instrumentationKey'
  }
}

// Create a Key Vault instance
resource keyvault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = {
  name: keyVaultName
  location: location
  properties: {
    enabledForDeployment: true
    enabledForTemplateDeployment: true
    enableRbacAuthorization: true
    enableSoftDelete: true
    softDeleteRetentionInDays: 7
    networkAcls: {
      defaultAction: 'Allow'
      bypass: 'AzureServices'
    }
    sku: {
      name: 'standard'
      family: 'A'
    }
    tenantId: tenant().tenantId
  }
}

// Create a Secret within the KeyVault
resource secretAppInsights 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = {
  name: 'AppInsightsKeyApim'
  parent: keyvault
  properties: {
    value: appInsights.properties.InstrumentationKey
  }
}

That’s all there is to referencing App Insights from a Bicep template. Pretty simple, right?

Have fun working with Bicep and Happy DevOp’ing!