Skip to content

Commit 24558f9

Browse files
Add Terraform modules for Azure resources in web app with MySQL flexible server
1 parent 6e37203 commit 24558f9

105 files changed

Lines changed: 66147 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/run-samples.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ jobs:
120120
# jq: for parsing JSON responses from Azure CLI.
121121
# zip: for packaging function/web apps.
122122
# unixodbc-dev & libsnappy-dev: required for Python database drivers (pyodbc, pymongo).
123+
# default-mysql-client: provides the `mysql` CLI used by the MySQL flexible-server sample
124+
# to create the application user and seed the database schema in the local emulator.
125+
# postgresql-client: provides the `psql` CLI used by the PostgreSQL flexible-server sample
126+
# to create the application role and seed the database schema in the local emulator.
123127
run: |
124128
sudo apt-get update
125-
sudo apt-get install -y jq zip unixodbc-dev libsnappy-dev
129+
sudo apt-get install -y jq zip unixodbc-dev libsnappy-dev default-mysql-client postgresql-client
126130
find . -name "*.sh" -exec chmod +x {} +
127131
128132
- name: Install Terraform

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ This repository contains comprehensive sample projects demonstrating how to deve
3535
| [Web App and Managed Identities](./samples/web-app-managed-identity/python/README.md) | Azure Web App using Managed Identities |
3636
| [Web App and SQL Database ](./samples/web-app-sql-database/python/README.md) | Azure Web App using SQL Database |
3737
| [Web App and PostgreSQL Database ](./samples/web-app-postgresql-flexible-server/python/README.md) | Azure Web App using PostgreSQL Database |
38+
| [Web App and MySQL Database ](./samples/web-app-mysql-flexible-server/python/README.md) | Azure Web App using MySQL Database |
3839
| [Web App with Custom Docker Image](./samples/web-app-custom-image/python/README.md) | Azure Web App running a custom Docker image |
3940
| [ACI and Blob Storage](./samples/aci-blob-storage/python/README.md) | Azure Container Instances with ACR, Key Vault, and Blob Storage |
4041
| [Azure Service Bus with Spring Boot](./samples/servicebus/java/README.md) | Azure Service Bus used by a Spring Boot application |

run-samples.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ SAMPLES=(
3737
"samples/web-app-cosmosdb-mongodb-api/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
3838
"samples/web-app-managed-identity/python|bash scripts/user-assigned.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
3939
"samples/web-app-sql-database/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/get-web-app-url.sh"
40+
"samples/web-app-postgresql-flexible-server/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
41+
"samples/web-app-mysql-flexible-server/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
4042
"samples/web-app-custom-image/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
4143
"samples/aci-blob-storage/python|bash scripts/deploy.sh|bash scripts/validate.sh"
4244
)
@@ -50,6 +52,8 @@ TERRAFORM_SAMPLES=(
5052
"samples/web-app-cosmosdb-mongodb-api/python/terraform|bash deploy.sh"
5153
"samples/web-app-managed-identity/python/terraform|bash deploy.sh"
5254
"samples/web-app-sql-database/python/terraform|bash deploy.sh"
55+
"samples/web-app-postgresql-flexible-server/python/terraform|bash deploy.sh"
56+
"samples/web-app-mysql-flexible-server/python/terraform|bash deploy.sh"
5357
"samples/aci-blob-storage/python/terraform|bash deploy.sh"
5458
)
5559

@@ -62,6 +66,8 @@ BICEP_SAMPLES=(
6266
"samples/function-app-storage-http/dotnet/bicep|bash deploy.sh"
6367
"samples/web-app-cosmosdb-mongodb-api/python/bicep|bash deploy.sh"
6468
"samples/web-app-managed-identity/python/bicep|bash deploy.sh"
69+
"samples/web-app-postgresql-flexible-server/python/bicep|bash deploy.sh"
70+
"samples/web-app-mysql-flexible-server/python/bicep|bash deploy.sh"
6571
"samples/aci-blob-storage/python/bicep|bash deploy.sh"
6672
)
6773

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
![Vacation Planner UI](./images/vacation-planner.png)
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/)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Bicep Deployment
2+
3+
This directory contains the Bicep template and a deployment script for provisioning the sample's Azure resources. For details about the sample application, see [Azure Web App with Azure Database for MySQL flexible server](../README.md).
4+
5+
## Prerequisites
6+
7+
- [LocalStack for Azure](https://docs.localstack.cloud/azure/)
8+
- [Visual Studio Code](https://code.visualstudio.com/) + [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)
9+
- [Docker](https://docs.docker.com/get-docker/)
10+
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) + [Azlocal CLI](https://azure.localstack.cloud/user-guides/sdks/az/)
11+
- [Python 3.12+](https://www.python.org/downloads/)
12+
- [MySQL client (`mysql`)](https://dev.mysql.com/downloads/)
13+
- [`jq`](https://jqlang.org/)
14+
15+
```bash
16+
pip install azlocal
17+
```
18+
19+
## Architecture Overview
20+
21+
The [`deploy.sh`](deploy.sh) script creates the resource group while the Bicep modules create:
22+
23+
1. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview) with two subnets:
24+
- *app-subnet*: delegated to `Microsoft.Web/serverFarms` for the Web App's regional VNet integration.
25+
- *pe-subnet*: hosts the Private Endpoint to the MySQL flexible server.
26+
2. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone) `privatelink.mysql.database.azure.com`, linked to the VNet.
27+
3. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `mysqlServer`).
28+
4. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview).
29+
5. [Network Security Groups](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): one per subnet.
30+
6. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview).
31+
7. [Azure Database for MySQL flexible server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/overview): public-access mode, Burstable `Standard_B1ms`, version 8.0.21, 32 GiB, HA disabled. A permissive firewall rule (`0.0.0.0–255.255.255.255`) lets the deploy machine reach the server for the post-create mysql bootstrap.
32+
8. [MySQL database](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-create-manage-databases) `PlannerDB` (utf8mb4 / `utf8mb4_unicode_ci`).
33+
9. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans).
34+
10. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview) with regional VNet integration into *app-subnet*. The Bicep template sets `MYSQL_HOST`, `MYSQL_PORT`, and `MYSQL_DATABASE` on the Web App but **does not** set `MYSQL_USER` or `MYSQL_PASSWORD` — those are written by `deploy.sh` after the mysql client creates the application user.
35+
36+
## Configuration
37+
38+
Update [`main.bicepparam`](main.bicepparam) before deploying. The defaults are:
39+
40+
```bicep
41+
using 'main.bicep'
42+
43+
param prefix = 'local'
44+
param suffix = 'test'
45+
param runtimeName = 'python'
46+
param runtimeVersion = '3.13'
47+
param databaseName = 'PlannerDB'
48+
param username = 'paolo'
49+
50+
param mysqlAdminLogin = 'myadmin'
51+
param mysqlAdminPassword = readEnvironmentVariable('MYSQL_ADMIN_PASSWORD', '')
52+
param mysqlVersion = '8.0.21'
53+
param mysqlSkuTier = 'Burstable'
54+
param mysqlSkuName = 'Standard_B1ms'
55+
param mysqlStorageSizeGB = 32
56+
param mysqlBackupRetentionDays = 7
57+
```
58+
59+
`mysqlAdminPassword` is read from the `MYSQL_ADMIN_PASSWORD` env var. `deploy.sh` sets a default (`P@ssw0rd1234!`) if not provided; override for non-dev deployments.
60+
61+
## Deployment
62+
63+
```bash
64+
# default values
65+
bash deploy.sh
66+
67+
# override admin and app-user secrets
68+
MYSQL_ADMIN_PASSWORD='<your-admin-password>' \
69+
MYSQL_APP_PASSWORD='<your-app-password>' \
70+
bash deploy.sh
71+
```
72+
73+
The script will:
74+
75+
1. Ensure the resource group exists.
76+
2. Validate `main.bicep`.
77+
3. Deploy the template, passing `mysqlAdminPassword`.
78+
4. Use the `mysql` client (connected via the public endpoint + firewall rule) to create the `testuser` user, the `activities` table, and the demo rows.
79+
5. Set the Web App's `MYSQL_USER`/`MYSQL_PASSWORD` to `testuser` / `<app-password>` — the server admin login is never written to the Web App.
80+
6. Zip the application source under `../src` and deploy it.
81+
82+
## Verification
83+
84+
```bash
85+
MYSQL_PWD='TestP@ssw0rd123' mysql -h <fqdn> -P <port> -u testuser PlannerDB \
86+
-e "SELECT id, username, activity, created_at FROM activities;"
87+
```
88+
89+
`<port>` is `3306` in real Azure, or the port suffix of the server's FQDN in LocalStack:
90+
91+
```bash
92+
az mysql flexible-server show \
93+
--resource-group local-rg --name local-mysqlflex-test \
94+
--query fullyQualifiedDomainName --output tsv
95+
```

0 commit comments

Comments
 (0)