In this blog post, we will explore the process of deploying a Python Flask application to Azure Web App Service (Linux). However, instead of deploying it to the default wwwroot directory, we will deploy it to a subdirectory within the web app. This approach offers flexibility and organization, allowing you to keep your application code separate from other files in the root directory.


Azure App Service

When developing Python applications, it is common to start with a standalone .py file or a Jupyter notebook. However, as the application evolves and becomes more stable, it often becomes necessary to create an API host to facilitate collaboration and sharing within a team. By separating shared libraries from the Flask app, we can create a more modular and reusable solution that can be easily shared across teams.

In such cases, simply placing the Flask app in the wwwroot directory may not be the best choice. The wwwroot directory is typically reserved for web files, and mixing shared libraries with the Flask app can lead to cluttered code and potential conflicts.

Consider an example project structure as follows:

root/
- backend/
  - app.py
  - requirements.txt
- libs/
  - lib.py
- configurations/
  - *.json
- main.py
- jupyter/
  - analysis.ipynb
- others/

libs/* and configurations/* are shared among backend/*, jupyter/*, main.py. To ensure that the lib.py file can be accessed from both app.py and analysis.ipynb, you needed to add the parent path using the following code snippet:

import sys
sys.path.append('..')

By doing so, you were able to import the required library from the libs folder:

from libs.lib import BaseGenerator, Generator
import libs.generator_utils as gu

However, when attempting to deploy your application to Azure Web App Service (Linux), you encountered the need to customize the deployment package, also known as the DevOps process. This is because the Azure App Service Extension with Deploy To Web App feature only packages the contents of the backend/ directory, and you preferred not to include the entire root directory in the deployment package.

So I have to achieve 3 objectives

  • Compress selected directories into a zip file: Create a zip file that includes the necessary directories for deployment. These directories should be the ones you want to include under the root directory. Make sure to exclude any unnecessary files or directories.
  • Specify the main application to run: Within the compressed zip file, ensure that the server runs the main application located at backend/app.py. This can typically be achieved by configuring the entry point in the Azure Web App Service settings or by providing a startup command in the deployment configuration.
  • Include requirements.txt in site/wwwroot: As per Microsoft’s documentation, the requirements.txt file should be placed in the site/wwwroot directory. When creating the zip file for deployment, make sure to include the requirements.txt file in the root location within the compressed package.

“Could not find setup.py or requirements.txt; Not running pip install.”: The Oryx build process failed to find your requirements.txt file.

Connect to the web app’s container via SSH and verify that requirements.txt is named correctly and exists directly under site/wwwroot. If it doesn’t exist, make site the file exists in your repository and is included in your deployment. If it exists in a separate folder, move it to the root.

https://learn.microsoft.com/en-us/azure/app-service/configure-language-python#could-not-find-setuppy-or-requirementstxt

To restructure your project before compressing it into a zip file, you can move all the files to a new folder named .deploy under the root directory. The updated project structure would look like this:

root/
- .deploy/
  - backend/*
  - libs/*
  - configurations/*
  - requirements.txt
- backend/
  - app.py
  - requirements.txt
- libs/
  - lib.py
- configurations/
  - *.json
- main.py
- jupyter/
  - analysis.ipynb
- others/

I plan to utilize PowerShell to handle the copying and zipping tasks, ensuring that the resulting zip file contains the contents of the .deploy folder rather than including the folder itself. The Azure will use this as-in zip for operation


$zipFilePath = "app.zip"

$mainDir = "backend"
$externalDir = @(
    $mainDir,
    "libs",
    "configurations",
    "$mainDir/requirements.txt"
)

# Create new .deploy directory
New-Item -ItemType Directory -Force -Path $deploymentDir | Out-Null

foreach ($dir in $externalDir) {
    Copy-Item -Path "$dir" -Destination "$deploymentDir" -Recurse -Force
}

Compress-Archive -Path $deploymentDir/* -DestinationPath $zipFilePath -Force

Next, you can reference the provided links to access the instructions and guidelines for deploying your application to Azure.

$resourceGroupName = "YOUR_RESOURCE_GROUP_NAME"
$appName = "YOUR_APP_NAME"
$zipFilePath = "app.zip"

az webapp config appsettings set --name "$appName" --resource-group "$resourceGroupName" --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true
az webapp deploy --name $appName --resource-group $resourceGroupName --src-path $zipFilePath --type zip
az webapp config set --name "$appName" --resource-group "$resourceGroupName" --startup-file "gunicorn --bind=0.0.0.0:8000 --chdir $mainDir app:app"

https://learn.microsoft.com/en-us/azure/app-service/deploy-zip?tabs=cli

You need to know your resouce group and app name, or id for your service within Azure Portal.

$resourceGroupName = "YOUR_RESOURCE_GROUP_NAME"
$appName = "YOUR_APP_NAME"

Please note that setting the environment variable SCM_DO_BUILD_DURING_DEPLOYMENT=true in Azure means that Azure will automatically build your application during the deployment process. Additionally, the requirements.txt file will be used to install the required Python libraries.

The second command, az webapp deploy, is used to deploy the previously compressed app.zip file. This command initiates the deployment process and ensures that your application is uploaded to Azure Web App Service.

The third command, az webapp config set, is used to configure the startup command for your application. In this case, the --startup-file flag is set to gunicorn --bind=0.0.0.0:8000 --chdir $mainDir app:app. This command instructs gunicorn - a Python WSGI HTTP Server for UNIX, to start your application on port 8000. The $mainDir variable represents the main directory of your application.

For more information, you can read here: https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Cvscode-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli#1---sample-application

By default, Azure App Service for Linux utilizes gunicorn. You can begin testing your application after the initial deployment of the Flask app, and you can use os.environ to debug and inspect the environment variables.

os.environ.get("SERVER_SOFTWARE", "")
# gunicorn/20.1.0

Back to the third command, --chdir $mainDir will specify backend folder is the main directory, with app:app stands for app.py : app object name of Flask.

Custom Flask main module: By default, App Service assumes that a Flask app’s main module is application.py or app.py. If your main module uses a different name, then you must customize the startup command. For example, if you have a Flask app whose main module is hello.py and the Flask app object in that file is named myapp, then the command is as follows:

gunicorn –bind=0.0.0.0 –timeout 600 hello:myapp

You can read more here: https://learn.microsoft.com/en-us/azure/app-service/configure-language-python#example-startup-commands

Last but not least, the --bind=0.0.0.0 flag without specifying a port can cause an error because the server needs to know the specific port to bind to. By default, when you specify --bind=0.0.0.0, the server will attempt to bind to port 8000. However, in some cases, you may encounter an error indicating that the Docker container cannot bind to port 8000.

To resolve this issue, you correctly specified the port by using the --bind=0.0.0.0:8000 flag. This explicitly tells the server to bind to port 8000, ensuring that it can properly listen for incoming connections.

By providing the port along with the –bind flag, you resolved the issue and allowed the server to bind to the specified port, enabling your application to work correctly.

Finally, it is recommended to clean up certain files and folders before and after the deployment. Here’s a simplified approach:

Before Deployment:

  • Remove the .deploy folder, which contains the files and directories that were organized for deployment.

After Deployment:

  • Remove both the .deploy folder and the app.zip file that were used for the deployment. These files are no longer needed once the application is successfully deployed.
# Remove previous
if (Test-Path -Path $deploymentDir) {
    Remove-Item -Path $deploymentDir -Recurse -Force
}

....


# Clean up the deployment files
Remove-Item -Path $zipFilePath -Force
Remove-Item -Path $deploymentDir -Recurse

By doing that, you can maintain a clean and streamlined deployment process. This helps to avoid clutter and ensures that only the necessary files are present during and after the deployment.

Full powershell code

$resourceGroupName = "YOUR_RESOURCE_GROUP_NAME"
$appName = "YOUR_APP_NAME"
$zipFilePath = "app.zip"

$mainDir = "backend"
$externalDir = @(
    $mainDir,
    "libs",
    "configurations",
    "$mainDir/requirements.txt"
)

# Remove previous
if (Test-Path -Path $deploymentDir) {
    Remove-Item -Path $deploymentDir -Recurse -Force
}

# Create new .deploy directory
New-Item -ItemType Directory -Force -Path $deploymentDir | Out-Null

foreach ($dir in $externalDir) {
    Copy-Item -Path "$dir" -Destination "$deploymentDir" -Recurse -Force
}

Compress-Archive -Path $deploymentDir/* -DestinationPath $zipFilePath -Force

# Deploy
az webapp config appsettings set --name "$appName" --resource-group "$resourceGroupName" --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true
az webapp deploy --name $appName --resource-group $resourceGroupName --src-path $zipFilePath --type zip
az webapp config set --name "$appName" --resource-group "$resourceGroupName" --startup-file "gunicorn --bind=0.0.0.0:8000 --chdir $mainDir app:app"

# Clean up the deployment files
Remove-Item -Path $zipFilePath -Force
Remove-Item -Path $deploymentDir -Recurse

In conclusion, deploying a Flask Python application to Azure Web App Service requires careful consideration of file organization, configuration settings, and deployment steps. By following the recommended practices, such as structuring the project, customizing the deployment package, and configuring the startup command, you can successfully deploy your application to Azure.


Previous Post:
Next Post:
0%