|
| 1 | +# Azure Web App with Azure Database for MySQL flexible server |
| 2 | + |
| 3 | +This sample demonstrates a Python Flask single-page web application called *Vacation Planner* hosted on an [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview). The app runs on an Azure App Service Plan and stores activity data in the `activities` table of the `PlannerDB` database on an [Azure Database for MySQL flexible server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/overview). The server is reached through a [Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `mysqlServer`) with the `privatelink.mysql.database.azure.com` Private DNS Zone, while a permissive server-level firewall rule lets the deploy machine run the post-create mysql bootstrap that creates the application user and seeds the schema. |
| 4 | + |
| 5 | +## Architecture |
| 6 | + |
| 7 | +```mermaid |
| 8 | +flowchart LR |
| 9 | + user([User]) |
| 10 | +
|
| 11 | + subgraph rg["Resource Group: local-rg"] |
| 12 | + direction LR |
| 13 | + law["Log Analytics<br/>Workspace"] |
| 14 | + nat["NAT Gateway"] |
| 15 | + dns["Private DNS Zone<br/>privatelink.mysql.database.azure.com"] |
| 16 | + asp["App Service Plan<br/>S1 · Linux"] |
| 17 | + mysql[("MySQL Flexible Server<br/>8.0.21 · Burstable B1ms<br/>DB: PlannerDB")] |
| 18 | +
|
| 19 | + subgraph vnet["Virtual Network 10.0.0.0/8"] |
| 20 | + direction TB |
| 21 | + subgraph appsub["app-subnet 10.0.0.0/24 — delegated to Microsoft.Web/serverFarms"] |
| 22 | + webapp["Web App<br/>Vacation Planner<br/>Flask + gunicorn"] |
| 23 | + end |
| 24 | + subgraph pesub["pe-subnet 10.0.1.0/24"] |
| 25 | + pe["Private Endpoint<br/>group: mysqlServer"] |
| 26 | + end |
| 27 | + end |
| 28 | + end |
| 29 | +
|
| 30 | + user -->|HTTP| webapp |
| 31 | + webapp -->|"MYSQL_HOST:MYSQL_PORT<br/>(resolved via Private DNS)"| pe |
| 32 | + pe -->|Private Link| mysql |
| 33 | + dns -.->|A record| pe |
| 34 | + dns -.->|linked| vnet |
| 35 | + appsub -->|outbound| nat |
| 36 | + webapp -.->|hosted on| asp |
| 37 | + webapp -.->|diagnostics| law |
| 38 | + mysql -.->|diagnostics| law |
| 39 | +``` |
| 40 | + |
| 41 | +The web app enables users to plan and manage vacation activities; all data is persisted in MySQL. The solution is composed of the following Azure resources: |
| 42 | + |
| 43 | +1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample. |
| 44 | +2. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview): Hosts two subnets: |
| 45 | + - *app-subnet*: Delegated to `Microsoft.Web/serverFarms` for regional VNet integration of the Web App. |
| 46 | + - *pe-subnet*: Hosts the Private Endpoint to the MySQL flexible server. |
| 47 | +3. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone) `privatelink.mysql.database.azure.com`, linked to the VNet. The Private Endpoint's DNS-zone group auto-registers the `A` record for the server, so the Web App resolves the server's private IP through the VNet. |
| 48 | +4. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `mysqlServer`): Secures access to the MySQL flexible server from the VNet. |
| 49 | +5. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview): Deterministic outbound connectivity for both subnets. |
| 50 | +6. [Azure Network Security Group](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): One NSG per subnet. |
| 51 | +7. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview): Centralizes diagnostic logs and metrics. |
| 52 | +8. [Azure Database for MySQL flexible server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/overview): Public-access server hosting the `PlannerDB` database. Burstable `Standard_B1ms`, version 8.0.21, 32 GiB storage, 7-day backup retention, HA disabled. A permissive firewall rule (`0.0.0.0–255.255.255.255`) is created so the deploy machine can run the post-create mysql bootstrap; the Web App itself reaches the server through the Private Endpoint. |
| 53 | +9. [MySQL database](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-create-manage-databases) `PlannerDB`: Created at provisioning time; the post-deploy mysql step creates the `activities` table and seeds the demo rows. |
| 54 | +10. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The underlying compute tier that hosts the web application. |
| 55 | +11. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Runs the Python Flask *Vacation Planner* app with regional VNet integration into *app-subnet*. The Web App connects to MySQL using a dedicated application user (`testuser`) — the server-admin login is never used at runtime. |
| 56 | +12. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): *(Optional)* Configures continuous deployment from a public GitHub repository. |
| 57 | + |
| 58 | +The deploy scripts follow the same pattern as the sibling [`web-app-postgresql-flexible-server`](../../web-app-postgresql-flexible-server/python/) sample: after provisioning, they (i) connect as the server admin via the public endpoint + firewall rule, (ii) create the application user `testuser` with its own password, (iii) grant privileges on `PlannerDB`, (iv) create the `activities` table, (v) seed sample rows, and (vi) write `MYSQL_USER=testuser` + `MYSQL_PASSWORD` onto the Web App's app settings. The server-admin login is never written into the Web App's runtime configuration. |
| 59 | + |
| 60 | +## Prerequisites |
| 61 | + |
| 62 | +- [Azure Subscription](https://azure.microsoft.com/free/) |
| 63 | +- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) |
| 64 | +- [Python 3.11+](https://www.python.org/downloads/) |
| 65 | +- [Flask](https://flask.palletsprojects.com/) |
| 66 | +- [PyMySQL](https://pymysql.readthedocs.io/) |
| 67 | +- [MySQL client tools](https://dev.mysql.com/downloads/) (`mysql`) — required by the deploy scripts to create the application user and seed data |
| 68 | +- [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep |
| 69 | +- [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform |
| 70 | + |
| 71 | +## Deployment |
| 72 | + |
| 73 | +Set up the Azure emulator using the LocalStack for Azure Docker image. Before starting, ensure you have a valid `LOCALSTACK_AUTH_TOKEN`. Refer to the [Auth Token guide](https://docs.localstack.cloud/getting-started/auth-token/) to obtain yours. Pull and start the emulator: |
| 74 | + |
| 75 | +```bash |
| 76 | +docker pull localstack/localstack-azure |
| 77 | + |
| 78 | +export LOCALSTACK_AUTH_TOKEN=<your_auth_token> |
| 79 | +IMAGE_NAME=localstack/localstack-azure localstack start -d |
| 80 | +localstack wait -t 60 |
| 81 | + |
| 82 | +# Route all Azure CLI calls to the LocalStack Azure emulator |
| 83 | +azlocal start-interception |
| 84 | +``` |
| 85 | + |
| 86 | +Deploy the application using one of these methods: |
| 87 | + |
| 88 | +- [Azure CLI Deployment](./scripts/README.md) |
| 89 | +- [Bicep Deployment](./bicep/README.md) |
| 90 | +- [Terraform Deployment](./terraform/README.md) |
| 91 | + |
| 92 | +All three variants provision the same topology: VNet + pe-subnet hosting a Private Endpoint targeting a public-access MySQL flexible server, with a Private DNS Zone linked to the VNet. |
| 93 | + |
| 94 | +> **Note** |
| 95 | +> When you deploy the application to LocalStack for Azure for the first time, the initialization process pulls and builds Docker images (LocalStack itself plus the `mysql:8` backing container for the flexible-server emulator). This is a one-time operation — subsequent deployments are much faster. |
| 96 | +
|
| 97 | +## Test |
| 98 | + |
| 99 | +1. Retrieve the port published and mapped to port 80 by the Docker container hosting the emulated Web App. |
| 100 | +2. Open a web browser and navigate to `http://localhost:<published-port>`. |
| 101 | +3. If the deployment was successful, you will see the *Vacation Planner* UI with the seeded activities and can add, edit, and remove activities. |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | +You can use the `scripts/call-web-app.sh` Bash script to call the web app from outside the emulator. The script demonstrates four call paths: |
| 106 | + |
| 107 | +1. **Through the LocalStack for Azure emulator** via the default hostname. |
| 108 | +2. **Via localhost and host port** mapped to the container's port `80`. |
| 109 | +3. **Via container IP address** on port `80`. |
| 110 | +4. **Via the default hostname** `<web-app-name>.azurewebsites.azure.localhost.localstack.cloud:4566`. |
| 111 | + |
| 112 | +## MySQL Tooling |
| 113 | + |
| 114 | +You can use [MySQL Workbench](https://www.mysql.com/products/workbench/) to explore and manage the deployed database. Connect using: |
| 115 | + |
| 116 | +| Field | Value | |
| 117 | +| -------- | ------------------------------------------------------------------------------ | |
| 118 | +| Host | `localhost` | |
| 119 | +| Port | (see `docker ps` for the host-mapped port of the backing `mysql:8` container) | |
| 120 | +| Database | `PlannerDB` | |
| 121 | +| Username | `testuser` *(or `myadmin` for admin operations)* | |
| 122 | +| Password | `TestP@ssw0rd123` *(or `P@ssw0rd1234!` for the admin)* | |
| 123 | + |
| 124 | +Or use the [`mysql`](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) command-line client: |
| 125 | + |
| 126 | +```bash |
| 127 | +MYSQL_PWD='TestP@ssw0rd123' mysql -h localhost -P <port> -u testuser PlannerDB |
| 128 | +mysql> SELECT id, username, activity, created_at FROM activities; |
| 129 | +``` |
| 130 | + |
| 131 | +## References |
| 132 | + |
| 133 | +- [Azure Web Apps Documentation](https://learn.microsoft.com/en-us/azure/app-service/) |
| 134 | +- [Azure Database for MySQL — flexible server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/) |
| 135 | +- [Quickstart: Python Flask on Azure](https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cbrowser) |
| 136 | +- [PyMySQL documentation](https://pymysql.readthedocs.io/) |
| 137 | +- [LocalStack for Azure](https://docs.localstack.cloud/azure/) |
0 commit comments