VotingApp Deployment
This note shows how to deploy a small microservice system to Kubernetes using Deployments and Services. The application components are:
- voting-app: Python Flask web app where users submit votes
- redis: in-memory store used as a queue for votes
- worker: background consumer that reads votes from Redis and writes to PostgreSQL
- db: PostgreSQL database (stateful)
- result-app: Python Flask web app that reads aggregated results from PostgreSQL
The manifests below assume you already have container images for each component (for example your-registry/voting-app:latest
, your-registry/result-app:latest
, etc.). Replace image names with your actual images before applying.
This demo creates a namespace, a Secret for DB credentials, a PVC for Postgres, Deployments for stateless services, a Deployment + PVC for the database, and Services to expose the components internally. For external access we expose result-app
via a NodePort example and recommend adding an Ingress in production.
Namespace
apiVersion: v1
kind: Namespace
metadata:
name: voting-demo
Secrets and Config
Create a secret for Postgres credentials (base64-encoded values or use kubectl create secret
):
apiVersion: v1
kind: Secret
metadata:
name: pg-credentials
namespace: voting-demo
type: Opaque
data:
POSTGRES_USER: cG9zdGdyZXM= # base64 for 'postgres'
POSTGRES_PASSWORD: c2VjcmV0 # base64 for 'secret'
POSTGRES_DB: dm90aW5n # base64 for 'voting'
Alternatively create secret from literals:
kubectl -n voting-demo create secret generic pg-credentials \
--from-literal=POSTGRES_USER=postgres \
--from-literal=POSTGRES_PASSWORD=secret \
--from-literal=POSTGRES_DB=voting
PersistentVolumeClaim for Postgres
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-data
namespace: voting-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Note: On cloud providers this will dynamically provision a PV. For local clusters (kind/minikube), ensure a storage class is available or use hostPath for demos.
PostgreSQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: voting-demo
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_PASSWORD
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_DB
ports:
- containerPort: 5432
volumeMounts:
- name: pg-data
mountPath: /var/lib/postgresql/data
volumes:
- name: pg-data
persistentVolumeClaim:
claimName: pg-data
Create a Service for Postgres (ClusterIP):
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: voting-demo
spec:
ports:
- port: 5432
targetPort: 5432
selector:
app: postgres
type: ClusterIP
Redis Deployment + Service
Redis will be used as a simple queue for votes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: voting-demo
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: voting-demo
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
type: ClusterIP
Voting app Deployment + Service
The voting-app
receives user votes and pushes them to Redis. Replace the image with your built image.
apiVersion: apps/v1
kind: Deployment
metadata:
name: voting-app
namespace: voting-demo
spec:
replicas: 2
selector:
matchLabels:
app: voting-app
template:
metadata:
labels:
app: voting-app
spec:
containers:
- name: voting-app
image: your-registry/voting-app:latest
env:
- name: REDIS_HOST
value: redis
- name: REDIS_PORT
value: "6379"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: voting-app
namespace: voting-demo
spec:
selector:
app: voting-app
ports:
- port: 80
targetPort: 80
type: ClusterIP
Worker Deployment
The worker reads votes from Redis and writes into Postgres. Configure DB connection via env and Secrets.
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: voting-demo
spec:
replicas: 1
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: your-registry/worker:latest
env:
- name: REDIS_HOST
value: redis
- name: REDIS_PORT
value: "6379"
- name: PGHOST
value: postgres
- name: PGUSER
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_DB
Result app Deployment + Service
The result-app
reads aggregated results from PostgreSQL and serves a web UI.
apiVersion: apps/v1
kind: Deployment
metadata:
name: result-app
namespace: voting-demo
spec:
replicas: 2
selector:
matchLabels:
app: result-app
template:
metadata:
labels:
app: result-app
spec:
containers:
- name: result-app
image: your-registry/result-app:latest
env:
- name: PGHOST
value: postgres
- name: PGUSER
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_DB
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: result-app
namespace: voting-demo
spec:
selector:
app: result-app
ports:
- port: 80
targetPort: 80
type: NodePort
# nodePort: 30080 # optionally pin a nodePort
For external access (example): find the NodePort and access http://<node-ip>:<nodePort>
.
Database initialization
If your worker or apps assume DB tables exist, initialize the schema by running a job or connect and run migrations. Example local one-off job:
apiVersion: batch/v1
kind: Job
metadata:
name: db-init
namespace: voting-demo
spec:
template:
spec:
containers:
- name: migrate
image: your-registry/db-migrate:latest
env:
- name: PGHOST
value: postgres
- name: PGUSER
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: pg-credentials
key: POSTGRES_DB
restartPolicy: OnFailure
Apply order and commands
Recommended apply order:
- Namespace
- Secrets
- PVC
- Postgres Deployment + Service
- Redis Deployment + Service
- Voting app Deployment + Service
- Worker Deployment
- Result app Deployment + Service
- DB init Job (if needed)
Commands:
kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f pg-pvc.yaml
kubectl apply -f postgres-deployment.yaml
kubectl apply -f redis.yaml
kubectl apply -f voting-app.yaml
kubectl apply -f worker.yaml
kubectl apply -f result-app.yaml
kubectl apply -f db-init-job.yaml
Health checks and readiness
- Add readiness probes to
voting-app
,result-app
, andworker
so unhealthy Pods are removed from Service endpoints. - Example readiness probe snippet (HTTP):
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Monitoring and logs
- Use
kubectl logs
andkubectl describe
to inspect Pods and Services for issues. - Monitor resource usage with
kubectl top
(requires metrics-server) or add Prometheus for production-grade monitoring.
Security notes
- Do not store credentials in image or plain YAML; use
Secrets
and RBAC. - Use network policies to restrict access between namespaces and services if needed.
Cleanup
kubectl delete namespace voting-demo