diff --git a/Makefile b/Makefile index 8754fdd..e6384e6 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,11 @@ version-set: /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/webapp/backend/deployment.yaml && \ /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/frontend/deployment.yaml && \ /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/backend/deployment.yaml && \ + /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/database/statefulset-primary.yaml && \ + /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/database/deployment-replica.yaml && \ + /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/database/cronjob-rollup-daily.yaml && \ + /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/database/cronjob-rollup-weekly.yaml && \ + /usr/bin/sed -i '' "s/podinfo:$$current/podinfo:$$next/g" deploy/bases/database/cronjob-backup-daily.yaml && \ /usr/bin/sed -i '' "s/$$current/$$next/g" timoni/podinfo/values.cue && \ echo "Version $$next set in code, deployment, module, chart and kustomize" diff --git a/deploy/bases/backend/deployment.yaml b/deploy/bases/backend/deployment.yaml index 994bc50..00a3207 100644 --- a/deploy/bases/backend/deployment.yaml +++ b/deploy/bases/backend/deployment.yaml @@ -12,14 +12,14 @@ spec: type: RollingUpdate selector: matchLabels: - app: backend + app.kubernetes.io/name: backend template: metadata: annotations: prometheus.io/scrape: "true" prometheus.io/port: "9797" labels: - app: backend + app.kubernetes.io/name: backend spec: containers: - name: backend diff --git a/deploy/bases/backend/service.yaml b/deploy/bases/backend/service.yaml index 14857c4..9ca5367 100644 --- a/deploy/bases/backend/service.yaml +++ b/deploy/bases/backend/service.yaml @@ -5,7 +5,7 @@ metadata: spec: type: ClusterIP selector: - app: backend + app.kubernetes.io/name: backend ports: - name: http port: 9898 diff --git a/deploy/bases/cache/deployment.yaml b/deploy/bases/cache/deployment.yaml index da3991c..89676d0 100644 --- a/deploy/bases/cache/deployment.yaml +++ b/deploy/bases/cache/deployment.yaml @@ -5,11 +5,11 @@ metadata: spec: selector: matchLabels: - app: cache + app.kubernetes.io/name: cache template: metadata: labels: - app: cache + app.kubernetes.io/name: cache spec: containers: - name: redis diff --git a/deploy/bases/cache/service.yaml b/deploy/bases/cache/service.yaml index 6f9f67d..22a497e 100644 --- a/deploy/bases/cache/service.yaml +++ b/deploy/bases/cache/service.yaml @@ -5,7 +5,7 @@ metadata: spec: type: ClusterIP selector: - app: cache + app.kubernetes.io/name: cache ports: - name: redis port: 6379 diff --git a/deploy/bases/database/README.md b/deploy/bases/database/README.md new file mode 100644 index 0000000..de1dd11 --- /dev/null +++ b/deploy/bases/database/README.md @@ -0,0 +1,76 @@ +# Database Setup + +This directory contains the Kubernetes manifests to simulate a database setup +with a primary database, read replicas, and scheduled maintenance tasks using CronJobs. + +## Components + +### Core Resources + +| Resource | File | Description | +|----------|------|-------------| +| ServiceAccount | `serviceaccount.yaml` | Shared service account for all database workloads | +| PVC | `pvc-primary.yaml` | 1Gi persistent storage for primary database | +| StatefulSet | `statefulset-primary.yaml` | Primary database with persistent storage at `/data` | +| Deployment | `deployment-replica.yaml` | Read replica deployment | +| Service (Headless) | `service-primary.yaml` | Headless service for StatefulSet | +| Service | `service-replica.yaml` | ClusterIP service for replicas | +| HPA | `hpa-replica.yaml` | Autoscaler for replicas (2-3 pods, 99% CPU) | + +### CronJobs + +| CronJob | Schedule | Duration | TTL Cleanup | Description | +|---------|----------|----------|-------------|-------------| +| `rollup-daily` | Every 10 min | ~1 min | 1 hour | Daily rollup simulation (6 iterations) | +| `rollup-weekly` | Every 30 min | ~2 min | 1 day | Weekly rollup simulation (12 iterations) | +| `backup-daily` | Daily at midnight | ~1 min | 1 day | Backup simulation (configured to fail) | + +### Scripts + +Located in `scripts/` directory: + +- `rollup.sh` - Rollup simulation script with configurable steps via `ROLLUP_STEPS` env var +- `backup.sh` - Backup simulation script with configurable exit code via `BACKUP_EXIT` env var + +## Labels + +All resources use Kubernetes recommended labels: + +- `app.kubernetes.io/name` - Component name +- `app.kubernetes.io/part-of: database` - Part of database application + +## Configuration + +### Primary Database +- **Port**: 3306 (MySQL standard) +- **Storage**: 1Gi PersistentVolumeClaim mounted at `/data` +- **Service**: Headless (`clusterIP: None`) for StatefulSet + +### Replica Database +- **Port**: 3306 +- **Scaling**: HPA with 2-3 replicas at 99% CPU utilization +- **Service**: ClusterIP + +### CronJob Scripts + +The scripts check database-replica health before running: + +```sh +podcli check http database-replica:3306/readyz +``` + +## Usage + +Deploy with Kustomize: + +```bash +kubectl apply -k deploy/bases/database +``` + +Or include in an overlay: + +```yaml +# kustomization.yaml +resources: + - ../../bases/database +``` diff --git a/deploy/bases/database/cronjob-backup-daily.yaml b/deploy/bases/database/cronjob-backup-daily.yaml new file mode 100644 index 0000000..d07d77d --- /dev/null +++ b/deploy/bases/database/cronjob-backup-daily.yaml @@ -0,0 +1,48 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backup-daily +spec: + # Runs every day at midnight for 1 minute + schedule: "0 0 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + # Cleanup after 1 day + ttlSecondsAfterFinished: 86400 + backoffLimit: 1 + template: + metadata: + labels: + app.kubernetes.io/name: backup-daily + app.kubernetes.io/part-of: database + spec: + serviceAccountName: database + restartPolicy: Never + containers: + - name: backup + image: ghcr.io/stefanprodan/podinfo:6.9.4 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - /scripts/backup.sh + env: + - name: BACKUP_EXIT + value: "1" + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + volumeMounts: + - name: scripts + mountPath: /scripts + volumes: + - name: scripts + configMap: + name: backup-script + defaultMode: 0755 diff --git a/deploy/bases/database/cronjob-rollup-daily.yaml b/deploy/bases/database/cronjob-rollup-daily.yaml new file mode 100644 index 0000000..ec625ef --- /dev/null +++ b/deploy/bases/database/cronjob-rollup-daily.yaml @@ -0,0 +1,48 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: rollup-daily +spec: + # Runs every 10 minutes for 1 minute + schedule: "*/10 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + # Cleanup after 1 hour + ttlSecondsAfterFinished: 3600 + backoffLimit: 3 + template: + metadata: + labels: + app.kubernetes.io/name: rollup-daily + app.kubernetes.io/part-of: database + spec: + serviceAccountName: database + restartPolicy: OnFailure + containers: + - name: healthcheck + image: ghcr.io/stefanprodan/podinfo:6.9.4 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - /scripts/rollup.sh + env: + - name: ROLLUP_STEPS + value: "6" + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + volumeMounts: + - name: scripts + mountPath: /scripts + volumes: + - name: scripts + configMap: + name: rollup-script + defaultMode: 0755 diff --git a/deploy/bases/database/cronjob-rollup-weekly.yaml b/deploy/bases/database/cronjob-rollup-weekly.yaml new file mode 100644 index 0000000..b77fd1c --- /dev/null +++ b/deploy/bases/database/cronjob-rollup-weekly.yaml @@ -0,0 +1,48 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: rollup-weekly +spec: + # Runs every 30 minutes for 2 minutes + schedule: "*/30 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + # Cleanup after 1 day + ttlSecondsAfterFinished: 86400 + backoffLimit: 3 + template: + metadata: + labels: + app.kubernetes.io/name: rollup-weekly + app.kubernetes.io/part-of: database + spec: + serviceAccountName: database + restartPolicy: OnFailure + containers: + - name: healthcheck + image: ghcr.io/stefanprodan/podinfo:6.9.4 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - /scripts/rollup.sh + env: + - name: ROLLUP_STEPS + value: "12" + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + volumeMounts: + - name: scripts + mountPath: /scripts + volumes: + - name: scripts + configMap: + name: rollup-script + defaultMode: 0755 diff --git a/deploy/bases/database/deployment-replica.yaml b/deploy/bases/database/deployment-replica.yaml new file mode 100644 index 0000000..3dd460b --- /dev/null +++ b/deploy/bases/database/deployment-replica.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: database-replica +spec: + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app.kubernetes.io/name: database-replica + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app.kubernetes.io/name: database-replica + app.kubernetes.io/part-of: database + spec: + serviceAccountName: database + containers: + - name: database + image: ghcr.io/stefanprodan/podinfo:6.9.4 + imagePullPolicy: IfNotPresent + ports: + - name: db + containerPort: 3306 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + command: + - ./podinfo + - --port=3306 + - --port-metrics=9797 + - --level=info + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:3306/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:3306/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 2000m + memory: 512Mi + requests: + cpu: 100m + memory: 32Mi diff --git a/deploy/bases/database/hpa-replica.yaml b/deploy/bases/database/hpa-replica.yaml new file mode 100644 index 0000000..518fbbb --- /dev/null +++ b/deploy/bases/database/hpa-replica.yaml @@ -0,0 +1,18 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: database-replica +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: database-replica + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 99 diff --git a/deploy/bases/database/kustomization.yaml b/deploy/bases/database/kustomization.yaml new file mode 100644 index 0000000..7b8fd9c --- /dev/null +++ b/deploy/bases/database/kustomization.yaml @@ -0,0 +1,24 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - serviceaccount.yaml + - pvc-primary.yaml + - statefulset-primary.yaml + - deployment-replica.yaml + - service-primary.yaml + - service-replica.yaml + - hpa-replica.yaml + - cronjob-rollup-daily.yaml + - cronjob-rollup-weekly.yaml + - cronjob-backup-daily.yaml +configMapGenerator: + - name: rollup-script + files: + - scripts/rollup.sh + options: + disableNameSuffixHash: true + - name: backup-script + files: + - scripts/backup.sh + options: + disableNameSuffixHash: true diff --git a/deploy/bases/database/pvc-primary.yaml b/deploy/bases/database/pvc-primary.yaml new file mode 100644 index 0000000..7072e0d --- /dev/null +++ b/deploy/bases/database/pvc-primary.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: database-primary +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/deploy/bases/database/scripts/backup.sh b/deploy/bases/database/scripts/backup.sh new file mode 100644 index 0000000..2b8f90f --- /dev/null +++ b/deploy/bases/database/scripts/backup.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +# This is a simulation of a backup process. + +EXIT_CODE=${BACKUP_EXIT:-0} + +echo "Starting backup (estimated run time: 60s)" +podcli check http database-replica:3306/readyz +sleep 60 +echo "Backup finished" +exit $EXIT_CODE diff --git a/deploy/bases/database/scripts/rollup.sh b/deploy/bases/database/scripts/rollup.sh new file mode 100644 index 0000000..5688d50 --- /dev/null +++ b/deploy/bases/database/scripts/rollup.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +# This is a simulation of a rollup process. + +STEPS=${ROLLUP_STEPS:-6} +echo "Starting rollup with $STEPS steps (estimated run time: $((STEPS * 10))s)" +podcli check http database-replica:3306/readyz +i=1 +while [ $i -le $STEPS ]; do + echo "Running rollup iteration $i of $STEPS" + sleep 10 + i=$((i + 1)) +done +echo "Rollup finished" diff --git a/deploy/bases/database/service-primary.yaml b/deploy/bases/database/service-primary.yaml new file mode 100644 index 0000000..1b55d0c --- /dev/null +++ b/deploy/bases/database/service-primary.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: database-primary +spec: + type: ClusterIP + clusterIP: None + selector: + app.kubernetes.io/name: database-primary + ports: + - name: db + port: 3306 + protocol: TCP + targetPort: db diff --git a/deploy/bases/database/service-replica.yaml b/deploy/bases/database/service-replica.yaml new file mode 100644 index 0000000..609f096 --- /dev/null +++ b/deploy/bases/database/service-replica.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: database-replica +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: database-replica + ports: + - name: db + port: 3306 + protocol: TCP + targetPort: db diff --git a/deploy/bases/database/serviceaccount.yaml b/deploy/bases/database/serviceaccount.yaml new file mode 100644 index 0000000..38787e4 --- /dev/null +++ b/deploy/bases/database/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: database diff --git a/deploy/bases/database/statefulset-primary.yaml b/deploy/bases/database/statefulset-primary.yaml new file mode 100644 index 0000000..f11ebeb --- /dev/null +++ b/deploy/bases/database/statefulset-primary.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: database-primary +spec: + serviceName: database-primary + replicas: 1 + minReadySeconds: 3 + revisionHistoryLimit: 5 + selector: + matchLabels: + app.kubernetes.io/name: database-primary + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app.kubernetes.io/name: database-primary + app.kubernetes.io/part-of: database + spec: + serviceAccountName: database + containers: + - name: database + image: ghcr.io/stefanprodan/podinfo:6.9.4 + imagePullPolicy: IfNotPresent + ports: + - name: db + containerPort: 3306 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + command: + - ./podinfo + - --port=3306 + - --port-metrics=9797 + - --level=info + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:3306/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:3306/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 2000m + memory: 512Mi + requests: + cpu: 100m + memory: 32Mi + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: database-primary diff --git a/deploy/bases/frontend/deployment.yaml b/deploy/bases/frontend/deployment.yaml index 9a04c77..eb79737 100644 --- a/deploy/bases/frontend/deployment.yaml +++ b/deploy/bases/frontend/deployment.yaml @@ -12,14 +12,14 @@ spec: type: RollingUpdate selector: matchLabels: - app: frontend + app.kubernetes.io/name: frontend template: metadata: annotations: prometheus.io/scrape: "true" prometheus.io/port: "9797" labels: - app: frontend + app.kubernetes.io/name: frontend spec: containers: - name: frontend diff --git a/deploy/bases/frontend/service.yaml b/deploy/bases/frontend/service.yaml index 8bf71a7..a82c6fe 100644 --- a/deploy/bases/frontend/service.yaml +++ b/deploy/bases/frontend/service.yaml @@ -5,7 +5,7 @@ metadata: spec: type: ClusterIP selector: - app: frontend + app.kubernetes.io/name: frontend ports: - name: http port: 80 diff --git a/deploy/overlays/dev/kustomization.yaml b/deploy/overlays/dev/kustomization.yaml index 4298d7c..93da8dd 100644 --- a/deploy/overlays/dev/kustomization.yaml +++ b/deploy/overlays/dev/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../bases/backend - ../../bases/frontend - ../../bases/cache + - ../../bases/database - namespace.yaml transformers: - labels.yaml diff --git a/deploy/overlays/dev/labels.yaml b/deploy/overlays/dev/labels.yaml index c8bbb19..1f2a7b9 100644 --- a/deploy/overlays/dev/labels.yaml +++ b/deploy/overlays/dev/labels.yaml @@ -3,8 +3,8 @@ kind: LabelTransformer metadata: name: labels labels: - env: dev - instance: webapp + app.kubernetes.io/environment: dev + app.kubernetes.io/instance: webapp fieldSpecs: - path: metadata/labels create: true diff --git a/deploy/overlays/production/kustomization.yaml b/deploy/overlays/production/kustomization.yaml index 0097d07..ea4c83b 100644 --- a/deploy/overlays/production/kustomization.yaml +++ b/deploy/overlays/production/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../bases/backend - ../../bases/frontend - ../../bases/cache + - ../../bases/database - namespace.yaml transformers: - labels.yaml diff --git a/deploy/overlays/production/labels.yaml b/deploy/overlays/production/labels.yaml index 104bf8a..d326b27 100644 --- a/deploy/overlays/production/labels.yaml +++ b/deploy/overlays/production/labels.yaml @@ -3,8 +3,8 @@ kind: LabelTransformer metadata: name: labels labels: - env: production - instance: webapp + app.kubernetes.io/environment: production + app.kubernetes.io/instance: webapp fieldSpecs: - path: metadata/labels create: true diff --git a/deploy/overlays/staging/kustomization.yaml b/deploy/overlays/staging/kustomization.yaml index 3627a51..53c30f7 100644 --- a/deploy/overlays/staging/kustomization.yaml +++ b/deploy/overlays/staging/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../bases/backend - ../../bases/frontend - ../../bases/cache + - ../../bases/database - namespace.yaml transformers: - labels.yaml diff --git a/deploy/overlays/staging/labels.yaml b/deploy/overlays/staging/labels.yaml index d910cd9..4d889b1 100644 --- a/deploy/overlays/staging/labels.yaml +++ b/deploy/overlays/staging/labels.yaml @@ -3,8 +3,8 @@ kind: LabelTransformer metadata: name: labels labels: - env: staging - instance: webapp + app.kubernetes.io/environment: staging + app.kubernetes.io/instance: webapp fieldSpecs: - path: metadata/labels create: true