Add Vacation Planner: in-cluster PostgreSQL sample - #4
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reworks the web-app-in-cluster-postgresql sample to be self-contained by running PostgreSQL inside the AKS cluster as a 3-replica StatefulSet (1 primary + 2 streaming standbys), removing the dependency on Azure Database for PostgreSQL flexible server and updating scripts/manifests/docs accordingly.
Changes:
- Add Kubernetes manifests for an in-cluster PostgreSQL StatefulSet with headless/primary/read Services, plus deployment-time DB/user/schema/seed provisioning via
kubectl port-forward+psql. - Add/refresh the Vacation Planner Flask app source + HTML/CSS UI assets for this sample.
- Update documentation and top-level README to include the new in-cluster PostgreSQL sample.
Reviewed changes
Copilot reviewed 25 out of 66 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/web-app-in-cluster-postgresql/src/templates/index.html | New Vacation Planner UI template (table + modals + client-side interactions). |
| samples/web-app-in-cluster-postgresql/src/static/style.css | Styling for the new UI, including dark mode and responsive layout. |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-utilities.rtl.min.css | Added Bootstrap static asset (utilities, RTL, minified). |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-utilities.min.css | Added Bootstrap static asset (utilities, minified). |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.rtl.min.css | Added Bootstrap static asset (reboot, RTL, minified). |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.rtl.css | Added Bootstrap static asset (reboot, RTL). |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.min.css.map | Added source map for bootstrap reboot minified CSS. |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.min.css | Added Bootstrap static asset (reboot, minified). |
| samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.css | Added Bootstrap static asset (reboot). |
| samples/web-app-in-cluster-postgresql/src/requirements.txt | Python dependencies for the Flask + psycopg2 app container. |
| samples/web-app-in-cluster-postgresql/src/gunicorn.conf.py | Gunicorn worker signal handling configuration. |
| samples/web-app-in-cluster-postgresql/src/database.py | PostgreSQL client helper used by the Flask app. |
| samples/web-app-in-cluster-postgresql/src/app.py | Flask app wiring: routes for listing/adding/updating/deleting activities. |
| samples/web-app-in-cluster-postgresql/scripts/statefulset.yml | In-cluster PostgreSQL Secret/ConfigMap/Services/StatefulSet with replication init scripts. |
| samples/web-app-in-cluster-postgresql/scripts/service.yml | ClusterIP Service exposing the web app inside the cluster. |
| samples/web-app-in-cluster-postgresql/scripts/secret.yml | App Secret template (PG password + Flask SECRET_KEY injected at deploy time). |
| samples/web-app-in-cluster-postgresql/scripts/namespace.yml | Namespace manifest for the sample resources. |
| samples/web-app-in-cluster-postgresql/scripts/Dockerfile | Multi-stage image build for the Flask app container. |
| samples/web-app-in-cluster-postgresql/scripts/deployment.yml | Web app Deployment (env from ConfigMap/Secret, probes, resources). |
| samples/web-app-in-cluster-postgresql/scripts/configmap.yml | App ConfigMap (PG host/port/db/user, login name, debug). |
| samples/web-app-in-cluster-postgresql/scripts/05-deploy-app.sh | Deploys StatefulSet, provisions DB/user/schema, seeds data, then deploys app. |
| samples/web-app-in-cluster-postgresql/scripts/04-push-docker-image.sh | Pushes the built image to ACR. |
| samples/web-app-in-cluster-postgresql/scripts/03-run-docker-container.sh | Optional local smoke test via port-forward to in-cluster primary. |
| samples/web-app-in-cluster-postgresql/scripts/02-build-docker-image.sh | Builds the app image from the src/ build context. |
| samples/web-app-in-cluster-postgresql/scripts/01-deploy-resources.sh | Provisions RG + ACR only (no managed PostgreSQL). |
| samples/web-app-in-cluster-postgresql/scripts/00-variables.sh | Central variables for resource names, DB topology, credentials, and k8s settings. |
| samples/web-app-in-cluster-postgresql/README.md | Sample README rewritten for in-cluster PostgreSQL workflow. |
| README.md | Adds the in-cluster PostgreSQL sample to the repo’s sample list. |
Files not reviewed (4)
- samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-grid.min.css: Language not supported
- samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-grid.rtl.min.css: Language not supported
- samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.css: Language not supported
- samples/web-app-in-cluster-postgresql/src/static/bootstrap/css/bootstrap-reboot.min.css: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+20
to
+22
| import psycopg2 | ||
| from psycopg2.errors import OperationalError | ||
|
|
Comment on lines
+10
to
+13
| Connection is sourced from env vars: PG_HOST, PG_PORT, PG_USER, PG_PASSWORD, PG_DATABASE. | ||
| A retry loop is used on startup because the flex server can take a few seconds to become | ||
| reachable on the first deploy (especially under LocalStack where the postgres container is | ||
| spun up on first server creation). |
Comment on lines
+72
to
+74
| <td class="col-btn"> | ||
| <form method="post" action="{{ url_for('delete', activity_id=loop.index0) }}" class="delete-form"> | ||
| <button type="button" class="btn-delete" title="Delete activity" aria-label="Delete {{ activity[1] | e }}"> |
Comment on lines
+81
to
+86
| @app.route("/delete/<int:activity_id>", methods=["POST"]) | ||
| def delete(activity_id: int): | ||
| if 0 <= activity_id < len(activities): | ||
| db_client.delete_activity(activities[activity_id][0]) | ||
| flash("Activity deleted.") | ||
| return redirect(url_for("index")) |
Comment on lines
+1
to
+9
| apiVersion: v1 | ||
| kind: Secret | ||
| metadata: | ||
| name: pg-postgres-secret | ||
| namespace: vacation-planner-postgres | ||
| type: Opaque | ||
| stringData: | ||
| POSTGRES_PASSWORD: "SuperStrongPass123" | ||
| REPL_PASSWORD: "ReplStrongPass123" |
Comment on lines
+32
to
+38
| # Local port used by `kubectl port-forward` to reach the in-cluster DB from the | ||
| # host (scripts 03 and 06). | ||
| PG_LOCAL_PORT='5432' | ||
|
|
||
| # Application config — must match the seed-row `username` in 06-create-test-data.sh. | ||
| # PostgreSQL `=` is case-sensitive (unlike SQL Server), so this stays lowercase. | ||
| LOGIN_NAME='paolo' |
Comment on lines
+57
to
+61
| # The PostgreSQL database now runs in-cluster as a StatefulSet (statefulset.yml), | ||
| # deployed by 05-deploy-app.sh. Database provisioning and test data are handled by | ||
| # 06-create-test-data.sh. No Azure managed PostgreSQL resource is created here. | ||
| echo "Resource group and Azure Container Registry are ready." | ||
| echo "Next: build (02) and push (04) the image, deploy the app + in-cluster PostgreSQL (05), then seed data (06)." |
Comment on lines
+6
to
+9
| # The database runs in-cluster (statefulset.yml). Reach it from the host by | ||
| # port-forwarding the primary (write) Service to localhost:$PG_LOCAL_PORT. | ||
| # Requires 05-deploy-app.sh (deploys the DB) and 06-create-test-data.sh | ||
| # (creates PlannerDB + testuser) to have run first. |
Comment on lines
+220
to
+222
| accessModes: ["ReadWriteOnce"] | ||
| storageClassName: managed-csi-premium | ||
| resources: |
Comment on lines
+67
to
+68
| except (ConnectionError, ValueError) as e: | ||
| logger.error("Error writing activity: %s", e) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The
web-app-in-cluster-postgresqlsample previously persisted the Vacation Planner app's data in an Azure Database for PostgreSQL flexible server, a managed Azure data service. This PR reworks the sample to instead run PostgreSQL in-cluster, as a Kubernetes StatefulSet deployed alongside the app on AKS.This demonstrates a fully self-contained, in-cluster data tier (no managed Azure database dependency): a PostgreSQL primary with two streaming-replica standbys, backed by Azure managed-disk
PersistentVolumeClaims and exposed through headless / primary (write) / readClusterIPservices. It complements the existing managed-service samples and gives a reference for teams that prefer running their own database inside the cluster.Changes
statefulset.yml): PostgreSQL 16 primary + 2 streaming replicas, managed-disk PVCs, and headless/primary/read services. The app connects to the primary (write) servicepg-postgres-primary.00-variables.sh— dropped the flex-server variables; added in-cluster variables (StatefulSet/primary pod & service names, superuser bootstrap credentials kept in sync with the StatefulSet secret, local port-forward port).01-deploy-resources.sh— now provisions only the resource group and ACR (no managed database).05-deploy-app.sh— deploys the StatefulSet and waits for it to become ready, then (over akubectl port-forwardto the primary) creates thePlannerDBdatabase, the dedicatedtestuserrole and grants, theactivitiestable, and seeds the sample data — before deploying the app. The database/role/seed provisioning runs before the app Deployment so pods can authenticate on first start.03-run-docker-container.sh— the optional local smoke test now reaches the in-cluster database through akubectl port-forwardto the primary service instead of an Azure FQDN.configmap.yml/secret.yml— point at the in-cluster primary service and thetestusercredential.PG_HOST/PG_PORT/PG_USER/PG_PASSWORD/PG_DATABASE), so the existing Docker image works as-is.samples/web-app-in-cluster-postgresql/README.md, and added the sample to the backing-store list and the Samples table in the top-levelREADME.md.Test
The sample was successfully deployed to a real Azure Kubernetes Service (AKS) cluster on Azure and verified end-to-end:
pg-postgres-0/1/2)Running.05-deploy-app.shcreatedPlannerDB, thetestuserrole and grants, theactivitiestable, and seeded all 9 sample rows (confirmed by the finalSELECT).1/1 Runningwith0restarts; logs showPostgreSQL schema initializedand readiness probes returning200.Todo