{"id":7667,"date":"2019-05-16T09:30:04","date_gmt":"2019-05-16T07:30:04","guid":{"rendered":"https:\/\/blog.redbaronofazure.com\/?p=7667"},"modified":"2019-05-16T13:03:21","modified_gmt":"2019-05-16T11:03:21","slug":"ci-cd-pipeline-for-a-python-app-being-deployed-to-azure-app-services","status":"publish","type":"post","link":"https:\/\/blog.redbaronofazure.com\/?p=7667","title":{"rendered":"CI\/CD pipeline for a Python app being deployed to Azure App Services"},"content":{"rendered":"\n<p>Deploying code to Azure App Services is simple and nothing new and Python is an interpreted language, which means it can&#8217;t be that hard to  deploy a Python app, right? Well, if your app has some requirements that needs to be installed, you need to get &#8220;pip install&#8221; to run on the App Services side. I will explain how you do that.<\/p>\n\n\n\n<h2>Recent changes to keep track of<\/h2>\n\n\n\n<p>First, Python support is deprecated on App Services on Windows and Linux is the supported environment going forward if you read the docs. This isn&#8217;t a major change but it means you need to brush up your deployment scripts so that they create a Linux based App Plan.<\/p>\n\n\n\n<p>Second, the App Services team have rebuilt the build system inside the Kudo SCM of App Services. The new build system is called Oryx. This isn&#8217;t a major change either, but it is worth becoming familiar with it because it will help you troubleshoot failing deployments. It is Oryx that will receive the zip deployment of your app and do post processing such as &#8220;pip install -r requirements&#8221; in case of a Python app.<\/p>\n\n\n\n<h2>Deployment techniques<\/h2>\n\n\n\n<p>Deployment techniques in Microsoft&#8217;s documentation consists of using the CLI command &#8220;az webapp up&#8221; or setting up continuous deployment via a github repository. The &#8220;az webapp up&#8221; is a beginners approach that does a all-in-one deployment that isn&#8217;t very well suited for CI\/CD pipelines. For instance it currently wacks all app settings during deployment which as we shall see below is a major problem for us. The technique of setting up continous deployment in App Services means that it will be SCM detecting git commits and do the build\/deploy. <\/p>\n\n\n\n<p>I want to use Azure DevOps or Jenkins and the deployment technique used must be able to run inside these tools. For this reason I&#8217;m going to stick to Azure CLI commands and do a zip deploy. To instruct the new Oryx build that I need it&#8217;s help I&#8217;m going to set an app setting of name  SCM_DO_BUILD_DURING_DEPLOYMENT  to true. When the runtime of the App Services app is set to Python, Oryx will then run the &#8220;pip install -r requirements.txt&#8221; command for me.<\/p>\n\n\n\n<h2>Sample Python app<\/h2>\n\n\n\n<p>The sample Python app will be the same as in the previous blog post, ie a REST API implementing authorization using an access token issued by Azure AD. You&#8217;ll find the code here  <br><a rel=\"noreferrer noopener\" aria-label=\"https:\/\/github.com\/cljung\/py-rest-api\/  (opens in a new tab)\" href=\"https:\/\/github.com\/cljung\/py-rest-api\/\" target=\"_blank\">https:\/\/github.com\/cljung\/py-rest-api\/ <\/a>. <\/p>\n\n\n\n<p>The Python app has a few dependencies, where asn1crypto, cryptography and cffi, for instance, has to do with that the sample app uses RSA256 cryptography to decode a JWT token. These libraries do not exist on vanilla Linux App Service and we need to install them. On your local laptop you would run &#8220;pip install -r requirements.txt&#8221; and this we need to make happen in the App Services during deployment.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>asn1crypto==0.24.0\ncffi==1.11.5\ncryptography==2.3.1\nidna==2.7\npycparser==2.19\nPyJWT==1.6.4\nsix==1.11.0\nrequests==2.21.0\ncryptography==2.3.1\nFlask==1.0.2\nWerkzeug==0.14.1\ngevent==1.4.0\nconfigparser==3.7.4<\/code><\/pre>\n\n\n\n<p>In the deploy folder, there is a bash script named az-webapp-create-py.sh that I will use in both Azure DevOps and in Jenkins. It creates the resource group, app plan and app if it doesn&#8217;t exist already, zips the code and deploys it to Azure.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rm -f .\/deploy.zip\nzip -r .\/deploy.zip $ZIPFOLDER\n\n# create the RG if it doesn't exists\nif [ $(az group exists -n $AZRGNAME) == 'false' ]; then\n    echo \"create ResourceGroup=$AZRGNAME\"\n    az group create -n $AZRGNAME -l \"$AZLOCATION\"\nfi\n\n# create the AppService Plan if it doesn't exists\nVAR0=$(az appservice plan show -n \"$AZAPPPLAN\" -g \"$AZRGNAME\" --query \"name\" -o tsv)\nif [ -z \"$VAR0\" ]; then\n    echo \"create AppPlan=$AZAPPPLAN\"\n    az appservice plan create -g \"$AZRGNAME\" -n \"$AZAPPPLAN\" -l \"$AZLOCATION\" --is-linux --sku FREE #B1\nfi\n\n# create the AppService if it doesn't exists\nVAR0=$(az webapp show -n \"$AZAPPNAME\" -g \"$AZRGNAME\" --query \"defaultHostName\" -o tsv )\nif [ -z \"$VAR0\" ]; then\n    echo \"create AppService=$AZAPPNAME\"\n    az webapp create -g \"$AZRGNAME\" -p \"$AZAPPPLAN\" -n \"$AZAPPNAME\" -r \"python|3.7\" \n    az webapp config set -g \"$AZRGNAME\" -n \"$AZAPPNAME\" --min-tls-version 1.2 --linux-fx-version \"PYTHON|3.7\"\n    az webapp log config -g \"$AZRGNAME\" -n \"$AZAPPNAME\" --web-server-logging filesystem --docker-container-logging filesystem\nfi\n\naz webapp config appsettings set -g \"$AZRGNAME\" -n \"$AZAPPNAME\" --settings \"SCM_DO_BUILD_DURING_DEPLOYMENT=true\" \"AZTENANTID=$AZTENANTID\" \"AZAPPID=$AZAPPID\"\naz webapp deployment source config-zip -g \"$AZRGNAME\" -n \"$AZAPPNAME\" --src .\/deploy.zip\n<\/code><\/pre>\n\n\n\n<p>The trick is in the last two lines where we add SCM_DO_BUILD_DURING_DEPLOYMENT first and then do the zip deployment. If you deploy a Python app without this setting, the zip deployment will succeed but your Python app will throw an error since all requirements are not installed. With this setting, the requirements.txt file will be processed during deployment.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" width=\"759\" height=\"240\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-03.png\" alt=\"\" class=\"wp-image-7674\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-03.png 759w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-03-300x95.png 300w\" sizes=\"(max-width: 759px) 100vw, 759px\" \/><\/figure>\n\n\n\n<h2>Azure DevOps<\/h2>\n\n\n\n<p>To deploy the Python app in an Azure DevOps release pipeline can be done via adding prebuilt tasks, but here I use an Azure CLI task where I execute the bash script. The arguments, like name of the Resource Group, App Name, etc, is defined as Variables.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" width=\"1024\" height=\"672\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-01-1024x672.png\" alt=\"\" class=\"wp-image-7670\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-01-1024x672.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-01-300x197.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-01-768x504.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-01.png 1696w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2>Jenkins Pipeline<\/h2>\n\n\n\n<p>The script can be reused if you are using Jenkins for your CI\/CD pipeline. If you create a Pipeline item, you control the build via the Jenkinsfile that exists in the deploy folder.  <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" width=\"1024\" height=\"798\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-02-1024x798.png\" alt=\"\" class=\"wp-image-7671\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-02-1024x798.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-02-300x234.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-02-768x599.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-02.png 1184w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The build relies on variables that you need to define in the Jenkins pipeline configuration. It then sets these as environment variables that can be picked up by the script. The Deploy stage in Jenkinsfile logs in to Azure via a service principal &#8220;az login &#8211;service-principal&#8221; and then invokes the same deployment script<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any \n    environment {\n        AZRGNAME = \"${params.AZRGNAME}\"\n        AZLOCATION = \"${params.AZLOCATION}\"\n        AZAPPPLAN = \"${params.AZAPPPLAN}\"\n        AZAPPNAME = \"${params.AZAPPNAME}\"\n        AZTENANTID = \"${params.AZTENANTID}\"\n        AZAPPID = \"${params.AZAPPID}\"\n    }\n    stages {\n        stage('Deploy') {\n            steps {\n                withCredentials([azureServicePrincipal('AzureServicePrincipalID')]) {\n                    sh 'az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET -t $AZURE_TENANT_ID'\n                    sh 'az account set -s $AZURE_SUBSCRIPTION_ID'\n                    sh 'chmod +x .\/deploy\/az-webapp-create-py.sh'\n                    sh '.\/deploy\/az-webapp-create-py.sh -z . -b $BUILD_TAG'\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" width=\"827\" height=\"687\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-04.png\" alt=\"\" class=\"wp-image-7675\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-04.png 827w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-04-300x249.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-04-768x638.png 768w\" sizes=\"(max-width: 827px) 100vw, 827px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" width=\"576\" height=\"255\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-05.png\" alt=\"\" class=\"wp-image-7676\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-05.png 576w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2019\/05\/python-cicd-05-300x133.png 300w\" sizes=\"(max-width: 576px) 100vw, 576px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Deploying code to Azure App Services is simple and nothing new and Python is an interpreted language, which means it can&#8217;t be that hard to deploy a Python app, right? Well, if your app has some requirements that needs to be installed, you need to get &#8220;pip install&#8221; to run on the App Services side. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[231,121,101,442],"tags":[443,434],"_links":{"self":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7667"}],"collection":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7667"}],"version-history":[{"count":5,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7667\/revisions"}],"predecessor-version":[{"id":7677,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7667\/revisions\/7677"}],"wp:attachment":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7667"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}