# Next.js-Anwendung. Zustandslos: Buchungen, Kapazitaet, Gutscheine, # Reservierungen, Rate Limits, E-Mail-Ausgang und Zahlungsjobs liegen in # PostgreSQL — beliebig viele Replikate moeglich. # # Migrationen laufen NICHT beim Start, sondern als eigener, kontrollierter # Schritt (jobs.yaml, CronJob "artista-migrate", siehe docs/BETRIEB.md). apiVersion: apps/v1 kind: Deployment metadata: name: artista-app spec: replicas: 2 revisionHistoryLimit: 5 strategy: type: RollingUpdate rollingUpdate: { maxSurge: 1, maxUnavailable: 0 } selector: matchLabels: app.kubernetes.io/name: artista-app template: metadata: labels: app.kubernetes.io/name: artista-app artista/db-client: "true" spec: automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1001 runAsGroup: 1001 fsGroup: 1001 seccompProfile: { type: RuntimeDefault } topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app.kubernetes.io/name: artista-app containers: - name: app image: artista-modelcamp ports: - name: http containerPort: 3000 envFrom: - configMapRef: { name: artista-config } - secretRef: { name: artista-secrets } readinessProbe: httpGet: { path: /api/health/ready, port: http } periodSeconds: 10 timeoutSeconds: 5 livenessProbe: # Bewusst ohne Datenbank: eine DB-Stoerung soll nicht zu # Neustartschleifen fuehren. httpGet: { path: /api/health, port: http } initialDelaySeconds: 20 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 3 resources: requests: { cpu: 200m, memory: 256Mi } limits: { memory: 768Mi } securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: ["ALL"] } volumeMounts: - name: media mountPath: /app/media readOnly: true # Reiner Zwischenspeicher fuer optimierte Bilder: fluechtig ok. - name: next-cache mountPath: /app/.next/cache - name: tmp mountPath: /tmp volumes: - name: media persistentVolumeClaim: { claimName: artista-media } - name: next-cache emptyDir: { sizeLimit: 512Mi } - name: tmp emptyDir: { sizeLimit: 64Mi } --- apiVersion: v1 kind: Service metadata: name: artista-app spec: selector: app.kubernetes.io/name: artista-app ports: - name: http port: 80 targetPort: http --- # Die App nimmt Verbindungen nur vom Ingress-Controller an. Ohne diese Regel # koennte jeder Pod im Cluster den Service direkt ansprechen und per # gefaelschtem X-Real-Ip-Header die Rate Limits (inkl. Admin-Login/2FA) # umgehen. Namespace des Traefik-Controllers anpassen # ("kubectl get pods -A | grep traefik"). apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: artista-app-ingress spec: podSelector: matchLabels: app.kubernetes.io/name: artista-app policyTypes: ["Ingress"] ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik ports: - port: 3000 # Kubelet-Probes kommen vom Knoten und sind von NetworkPolicies je nach # CNI (Canal/Calico in RKE2) ausgenommen. --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: artista-app spec: minAvailable: 1 selector: matchLabels: app.kubernetes.io/name: artista-app