Skip to content

Latest commit

 

History

History
83 lines (57 loc) · 7.97 KB

File metadata and controls

83 lines (57 loc) · 7.97 KB

Vacation Planner: Azure Blob Storage with Microsoft Entra Workload ID

A .NET version of this sample lives in ../dotnet.

This sample demonstrates a Python Flask single-page web application called Vacation Planner hosted on an Azure Kubernetes Service (AKS) cluster in the cloud on Azure or locally in the LocalStack emulator for Azure. The app runs in a dedicated namespace and stores activity data in the activities container of an Azure Blob Storage account.

Unlike the web-app-blob-storage sample, which uses a connection string, this sample authenticates to the storage account without any secret, using Microsoft Entra Workload ID. A user-assigned managed identity is federated with a Kubernetes service account, so the pod obtains Microsoft Entra tokens through the cluster's OIDC issuer and accesses the storage account with its RBAC role assignment.

Optionally, when DEPLOY_GATEWAY="true" in 00-variables.sh, the sample also exposes the app on a public hostname through the Gateway API, with an A record created in an Azure DNS zone and a TLS certificate issued via cert-manager.

Before installing the sample, make sure to create an Azure Kubernetes Service (AKS) cluster by using one of the following scripts:

Both scripts enable the OIDC issuer and workload identity that this sample relies on. If you enable the Gateway path, also install the Gateway API and cert-manager add-ons from the root scripts/ folder. All commands below are run from this sample's scripts/ folder.

Running on LocalStack? Install the lstk CLI and run lstk az start-interception to route Azure CLI calls to the emulator. See Run against LocalStack for the full setup.

Architecture

The following diagram illustrates the architecture of the solution:

Architecture Diagram

Deployment workflow

Run the numbered scripts in order from the scripts/ folder:

cd scripts
./01-deploy-resources.sh
./02-build-docker-image.sh
./03-run-docker-container.sh   # optional local smoke test
./04-push-docker-image.sh
./05-deploy-app.sh

Scripts and manifests

File Description
00-variables.sh Defines the variables shared across the other scripts (resource names, image tag, managed identity and federated credential names, storage account, optional DNS/Gateway settings, Kubernetes namespace, …). The other scripts load these values by sourcing this file.
01-deploy-resources.sh Deploys the Azure resources used by this sample: the resource group, the Azure Container Registry (ACR), the Azure Blob Storage account and activities container, and the user-assigned managed identity with its role assignment on the storage account.
02-build-docker-image.sh Builds the Docker image for the web app from the src/ folder.
03-run-docker-container.sh Runs the web app in a local Docker container (no Kubernetes) to validate that it starts and connects to the storage account as expected.
04-push-docker-image.sh Tags and pushes the Docker image to the Azure Container Registry, on Azure or in the LocalStack emulator.
05-deploy-app.sh Creates the workload-identity service account and the federated identity credential that links it to the managed identity, then uses the YAML manifests below (templated with yq) to deploy the app. When DEPLOY_GATEWAY="true", it also applies the Issuer, Gateway, and HTTPRoute and updates the Azure DNS A record.
Dockerfile Builds the Docker image of the web app.
namespace.yml Creates the Kubernetes namespace.
configmap.yml Creates the ConfigMap holding non-secret input values (blob container name, storage account URL, managed identity client ID, tenant ID) passed to the app as environment variables.
secret.yml Creates the Secret holding sensitive values (the Flask secret key, and the optional connection string / client secret fallback) passed to the app as environment variables.
deployment.yml Creates the Kubernetes Deployment, including the pod specification and the workload-identity service account reference. The liveness and readiness probes call GET /health.
service.yml Creates the ClusterIP Service that exposes the web app inside the cluster.
issuer.yml (Gateway path) cert-manager Issuer that solves the ACME HTTP-01 challenge through a Gateway API HTTPRoute.
gateway.yml (Gateway path) Gateway API Gateway that exposes the app on the configured public hostname.
httproute.yml (Gateway path) Gateway API HTTPRoute that routes the hostname's traffic to the Service.

Accessing the web app

By default the app is exposed through a ClusterIP service, which is only reachable from inside the cluster. Port-forward it to a local port to open it from your machine:

kubectl port-forward service/vacation-planner-identity 8080:80 -n vacation-planner-identity

Then browse to http://localhost:8080. Alternatively, use a tool such as k9s to start the port-forward interactively.

The app also exposes GET /health, the endpoint the liveness and readiness probes call: it returns {"status": "ok"} when the blob container is reachable and 503 with {"status": "unavailable"} otherwise.

curl http://localhost:8080/health

If you deployed the Gateway path (DEPLOY_GATEWAY="true"), the app is instead reachable directly at the public hostname configured in 00-variables.sh (https://<subdomain>.<dns-zone>), with no port-forward required.

Logs

The app logs one line per request — gunicorn writes an access log line for every call, the probes included, because its command passes --access-logfile - — plus one line per blob read, uploaded or deleted and one line for every activity added, updated or deleted. The store operations are printed to stdout, so kubectl logs shows them interleaved with the access log. The .NET version writes the same trace, timestamped.

kubectl logs deployment/vacation-planner-identity -n vacation-planner-identity --tail=50