Azure Function App infra redeployment makes existing functions in app fail because of missing requirements and also delete previous invocations data

Tim 156 Reputation points
2022-10-31T16:17:31.72+00:00

I'm facing quite a big problem. I have a function app that I deploy by Azure Bicep in the following fashion:

param environmentType string  
param location string  
param storageAccountSku string  
param vnetIntegrationSubnetId string  
param kvName string  
  
/*  
This module contains the IaC for deploying the Premium function app  
*/  
  
/// Just a single minimum instance to start with and max scaling of 3 for dev, 5 for prd ///  
var minimumElasticSize = 1  
var maximumElasticSize = ((environmentType == 'prd') ? 5 : 3)  
var name = 'nlp'  
var functionAppName = 'function-app-${name}-${environmentType}'  
  
/// Storage account for service ///  
resource functionAppStorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {  
  name: 'st4functionapp${name}${environmentType}'  
  location: location  
  kind: 'StorageV2'  
  sku: {  
    name: storageAccountSku  
  }  
  properties: {  
    allowBlobPublicAccess: false  
    accessTier: 'Hot'  
    supportsHttpsTrafficOnly: true  
    minimumTlsVersion: 'TLS1_2'  
  }  
}  
  
/// Premium app plan for the service ///  
resource servicePlanfunctionApp 'Microsoft.Web/serverfarms@2021-03-01' = {  
  name: 'plan-${name}-function-app-${environmentType}'  
  location: location  
  kind: 'linux'  
  sku: {  
    name: 'EP1'  
    tier: 'ElasticPremium'  
    family: 'EP'  
  }  
  properties: {  
    reserved: true  
    targetWorkerCount: minimumElasticSize  
    maximumElasticWorkerCount: maximumElasticSize  
    elasticScaleEnabled: true  
    isSpot: false  
    zoneRedundant: ((environmentType == 'prd') ? true : false)  
  }  
}  
  
// Create log analytics workspace  
resource logAnalyticsWorkspacefunctionApp 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {  
  name: '${name}-functionapp-loganalytics-workspace-${environmentType}'  
  location: location  
  properties: {  
    sku: {  
      name: 'PerGB2018' // Standard  
    }  
  }  
}  
  
/// Log analytics workspace insights ///  
resource applicationInsightsfunctionApp 'Microsoft.Insights/components@2020-02-02' = {  
  name: 'application-insights-${name}-function-${environmentType}'  
  location: location  
  kind: 'web'  
  properties: {  
    Application_Type: 'web'  
    Flow_Type: 'Bluefield'  
    publicNetworkAccessForIngestion: 'Enabled'  
    publicNetworkAccessForQuery: 'Enabled'  
    Request_Source: 'rest'  
    RetentionInDays: 30  
    WorkspaceResourceId: logAnalyticsWorkspacefunctionApp.id  
  }  
}  
  
// App service containing the workflow runtime ///  
resource sitefunctionApp 'Microsoft.Web/sites@2021-03-01' = {  
  name: functionAppName  
  location: location  
  kind: 'functionapp,linux'  
  identity: {  
    type: 'SystemAssigned'  
  }  
  properties: {  
    clientAffinityEnabled: false  
    httpsOnly: true  
    serverFarmId: servicePlanfunctionApp.id  
    siteConfig: {  
      linuxFxVersion: 'python|3.9'  
      minTlsVersion: '1.2'  
      pythonVersion: '3.9'  
      use32BitWorkerProcess: true  
      appSettings: [  
        {  
          name: 'FUNCTIONS_EXTENSION_VERSION'  
          value: '~4'  
        }  
        {  
          name: 'FUNCTIONS_WORKER_RUNTIME'  
          value: 'python'  
        }  
        {  
          name: 'AzureWebJobsStorage'  
          value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorage.name};AccountKey=${listKeys(functionAppStorage.id, '2019-06-01').keys[0].value};EndpointSuffix=core.windows.net'  
        }  
        {  
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'  
          value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorage.name};AccountKey=${listKeys(functionAppStorage.id, '2019-06-01').keys[0].value};EndpointSuffix=core.windows.net'  
        }  
        {  
          name: 'WEBSITE_CONTENTSHARE'  
          value: 'app-${toLower(name)}-functionservice-${toLower(environmentType)}a6e9'  
        }  
        {  
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'  
          value: applicationInsightsfunctionApp.properties.InstrumentationKey  
        }  
        {  
          name: 'ApplicationInsightsAgent_EXTENSION_VERSION'  
          value: '~2'  
        }  
        {  
          name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'  
          value: applicationInsightsfunctionApp.properties.ConnectionString  
        }  
        {  
          name: 'ENV'  
          value: toUpper(environmentType)  
        }  
      ]  
    }  
  }  
  
  /// VNET integration so flows can access storage and queue accounts ///  
  resource vnetIntegration 'networkConfig@2022-03-01' = {  
    name: 'virtualNetwork'  
    properties: {  
      subnetResourceId: vnetIntegrationSubnetId  
      swiftSupported: true  
    }  
  }  
}  
  
/// Outputs for creating access policies ///  
output functionAppName string = sitefunctionApp.name  
output functionAppManagedIdentityId string = sitefunctionApp.identity.principalId  

Output is used for giving permissions to blob/queue and some keyvault stuff. This code is a single module called in a main.bicep module and deployed via an Azure Devops pipeline.

I have a second repository in which I have some functions and which I also deploy via Azure Pipelines. This one contains three .yaml files for deploying, 2 templates (CI and CD) and 1 main pipeline called azure-pipelines.yml pulling it all together:

functions-ci.yml:

parameters:  
- name: environment  
  type: string  
  
jobs:  
- job:  
  displayName: 'Publish the function as .zip'  
  steps:  
  - task: UsePythonVersion@0  
    inputs:  
      versionSpec: '$(pythonVersion)'  
    displayName: 'Use Python $(pythonVersion)'  
  
  - task: CopyFiles@2  
    displayName: 'Create project folder'  
    inputs:  
      SourceFolder: '$(System.DefaultWorkingDirectory)'  
      Contents: |  
          **  
      TargetFolder: '$(Build.ArtifactStagingDirectory)'  
  
  - task: Bash@3  
    displayName: 'Install requirements for running function'  
    inputs:  
      targetType: 'inline'  
      script: |  
        python3 -m pip install --upgrade pip  
        pip install setup  
        pip install --target="./.python_packages/lib/site-packages" -r ./requirements.txt  
      workingDirectory: '$(Build.ArtifactStagingDirectory)'  
  
  - task: ArchiveFiles@2  
    displayName: 'Create project zip'  
    inputs:  
      rootFolderOrFile: '$(Build.ArtifactStagingDirectory)'  
      includeRootFolder: false  
      archiveType: 'zip'  
      archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'  
      replaceExistingArchive: true  
  
  - task: PublishPipelineArtifact@1  
    displayName: 'Publish project zip artifact'  
    inputs:  
      targetPath: '$(Build.ArtifactStagingDirectory)'  
      artifactName: 'functions$(environment)'  
      publishLocation: 'pipeline'  

functions-cd.yml:

parameters:  
- name: environment  
  type: string  
- name: azureServiceConnection  
  type: string  
  
jobs:  
- job: worfklowsDeploy  
  displayName: 'Deploy the functions'  
  steps:  
  # Download created artifacts, containing the zipped function codes  
  - task: DownloadPipelineArtifact@2  
    inputs:  
      buildType: 'current'  
      artifactName: 'functions$(environment)'  
      targetPath: '$(Build.ArtifactStagingDirectory)'  
  
  # Zip deploy the functions code  
  - task: AzureFunctionApp@1  
    inputs:  
      azureSubscription: $(azureServiceConnection)  
      appType: functionAppLinux  
      appName: function-app-nlp-$(environment)  
      package: $(Build.ArtifactStagingDirectory)/**/*.zip  
      deploymentMethod: 'zipDeploy'  

They are pulled together in azure-pipelines.yml:

trigger:  
  branches:  
    include:  
      - develop  
      - main  
  
pool:  
  name: "Hosted Ubuntu 1804"  
  
variables:  
  ${{ if notIn(variables['Build.SourceBranchName'], 'main') }}:  
    environment: dev  
    azureServiceConnection: SC-NLPDT  
  ${{ if eq(variables['Build.SourceBranchName'], 'main') }}:  
    environment: prd  
    azureServiceConnection: SC-NLPPRD  
    pythonVersion: '3.9'  
  
stages:  
  # Builds the functions as .zip  
  - stage: functions_ci  
    displayName: 'Functions CI'  
    jobs:  
    - template: ./templates/functions-ci.yml  
      parameters:  
        environment: $(environment)  
    
  # Deploys .zip workflows  
  - stage: functions_cd  
    displayName: 'Functions CD'  
    jobs:  
    - template: ./templates/functions-cd.yml  
      parameters:  
        environment: $(environment)  
        azureServiceConnection: $(azureServiceConnection)  

So this successfully deploys my function app the first time around when I have also deployed the infra code. The imports are done well, the right function app is deployed, and the code runs when I trigger it.

But, when I go and redeploy the infra (bicep) code, all of a sudden I the newest version of the functions is gone and is replaced by a previous version.
Also, running this previous version doesn't work anymore since all my requirements that were installed in the pipeline (CI part) via pip install --target="./.python_packages/lib/site-packages" -r ./requirements.txt suddenly cannot be found anymore, giving import errors (i.e. Result: Failure Exception: ModuleNotFoundError: No module named 'azure.identity'). Mind you, this version did work previously just fine.

This is a big problem for me since I need to be able to update some infra stuff (like adding an APP_SETTING) without this breaking the current deployment of functions.

I had thought about just redeploying the function automatically after an infra update, but then I still miss the previous invocations which I need to be able to see.

Am I missing something in the above code because I cannot figure out what would be going wrong here that causes my functions to change on infra deployment...

Azure Functions
Azure Functions
An Azure service that provides an event-driven serverless compute platform.
4,495 questions
{count} votes