Add Kubernetes manifest for the tavolo namespace
Single-file deployment under deploy/k8s/tavolo.yaml: - Namespace, ConfigMap and Secret (placeholders) for the app - Ephemeral Redis Deployment + Service (sessions and live games are disposable, mirroring the docker-compose no-volume choice) - App Deployment with an aerich-migrate initContainer so the schema is upgraded before rollout, /api/health probes and a hardened pod security context - ClusterIP Service; Postgres and the OIDC provider stay in their own namespaces and are referenced by DNS
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
# Tavolo — Kubernetes deployment (namespace: tavolo)
|
||||
#
|
||||
# Single apply:
|
||||
# kubectl apply -f deploy/k8s/tavolo.yaml
|
||||
#
|
||||
# Replace every value marked REPLACE_ME before applying.
|
||||
#
|
||||
# Scope:
|
||||
# - Postgres and the OIDC provider are NOT part of this file; they are
|
||||
# deployed in other namespaces and referenced by DNS name.
|
||||
# - Redis IS included (ephemeral: sessions and live games are disposable,
|
||||
# mirroring docker-compose's no-volume choice).
|
||||
#
|
||||
# Reachability notes:
|
||||
# - The OIDC issuer URL must resolve from the browser AND from the pods
|
||||
# (kaya does discovery lazily on the first login, so startup succeeds
|
||||
# even when the issuer is unreachable, but logins then fail). If the
|
||||
# provider is internal-only, you need split DNS or a public issuer URL.
|
||||
# - The Postgres NetworkPolicy (if any) must allow ingress from the
|
||||
# tavolo namespace.
|
||||
# - This file deploys a ClusterIP Service only. Until you add an Ingress,
|
||||
# reach the app with:
|
||||
# kubectl port-forward -n tavolo svc/tavolo 8080:80
|
||||
# and set OIDC_REDIRECT_URI to match whatever URL the browser uses.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: tavolo-config
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
data:
|
||||
# In-cluster Redis deployed by this file.
|
||||
REDIS_URL: redis://redis.tavolo.svc.cluster.local:6379/0
|
||||
# The image bakes STATIC_DIR=/app/web/dist; repeat it here for clarity.
|
||||
STATIC_DIR: /app/web/dist
|
||||
GAME_TTL_SECONDS: "86400"
|
||||
HAND_ACK_TIMEOUT_SECONDS: "30"
|
||||
TURN_TIMEOUT_SECONDS: "30"
|
||||
# OIDC (provider lives in another namespace).
|
||||
OIDC_CLIENT_ID: tavolo
|
||||
OIDC_POST_LOGIN_REDIRECT: /
|
||||
OIDC_POST_LOGOUT_REDIRECT: /
|
||||
# Issuer URL as seen by the browser AND the pods (see notes above).
|
||||
OIDC_ISSUER: REPLACE_ME
|
||||
# Public callback URL of this deployment, e.g.
|
||||
# http://<host>:<port>/auth/callback (must be allowed at the provider).
|
||||
OIDC_REDIRECT_URI: REPLACE_ME
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: tavolo-secrets
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Postgres lives in another namespace; use its Service DNS name:
|
||||
# postgres://USER:PASS@postgres.<namespace>.svc.cluster.local:5432/<db>
|
||||
DATABASE_URL: REPLACE_ME
|
||||
# Client secret for OIDC_CLIENT_ID at the provider.
|
||||
OIDC_CLIENT_SECRET: REPLACE_ME
|
||||
|
||||
---
|
||||
# Redis: sessions + live game state. Ephemeral by design (emptyDir): worst
|
||||
# case after a restart users log in again and games in progress expire.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: redis
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
replicas: 1
|
||||
# Redis is a singleton here; avoid a window with two masters on rollout.
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: redis
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999 # redis user in the official image
|
||||
fsGroup: 999
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:8-alpine
|
||||
# Snapshots would only live on an emptyDir anyway: skip them.
|
||||
args: ["--save", "", "--appendonly", "no"]
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["redis-cli", "ping"]
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["redis-cli", "ping"]
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 128Mi
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: redis
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: redis
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: redis
|
||||
|
||||
---
|
||||
# The app (backend + compiled SPA + WebSocket endpoint, one image).
|
||||
# The "migrate" initContainer runs `aerich upgrade` before the app starts,
|
||||
# so this single file can be applied safely in any order. Migrations are
|
||||
# idempotent, but if you ever scale above 1 replica, move this to a Job to
|
||||
# avoid concurrent upgrades.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: tavolo
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
# Let WebSocket connections drain on rollout.
|
||||
terminationGracePeriodSeconds: 30
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
initContainers:
|
||||
- name: migrate
|
||||
image: gitea.woggioni.net/woggioni/tavolo:latest
|
||||
# aerich reads [tool.aerich] from pyproject.toml in /app.
|
||||
command: ["aerich", "upgrade"]
|
||||
workingDir: /app
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: tavolo-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
containers:
|
||||
- name: tavolo
|
||||
# Pin a release/* tag for production instead of :latest.
|
||||
image: gitea.woggioni.net/woggioni/tavolo:latest
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: tavolo-config
|
||||
- secretRef:
|
||||
name: tavolo-secrets
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
periodSeconds: 2
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: tavolo
|
||||
namespace: tavolo
|
||||
labels:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
app.kubernetes.io/part-of: tavolo
|
||||
spec:
|
||||
type: ClusterIP
|
||||
# No session affinity needed: sessions and live games live in Redis.
|
||||
selector:
|
||||
app.kubernetes.io/name: tavolo
|
||||
app.kubernetes.io/component: app
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
Reference in New Issue
Block a user