diff --git a/.gitignore b/.gitignore index b850124..0f9167b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.env -deploy/k8s/ \ No newline at end of file +# Cluster manifests stay out of the repo, except the LMR deployment itself. +deploy/k8s/* +!deploy/k8s/lmr.yaml diff --git a/README.md b/README.md index ae4a672..68e9c13 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,25 @@ Docker/systemd, pass it with `env_file` / `EnvironmentFile` instead. | `GET /healthz` | Liveness — process is up | | `GET /readyz` | Readiness — database reachable (pings the pool) | +## Deploy + +A push to `master` runs the Woodpecker pipeline in `.woodpecker.yml`. It builds +the root `Dockerfile` and pushes `registry.govekar.net/lovrog05/localmodelroutex` +with the tags `latest` and the commit SHA. + +The gateway runs on k3s from [`deploy/k8s/lmr.yaml`](deploy/k8s/lmr.yaml). The +manifest expects `configmap/lmr-config`, `secret/lmr-secret` and the pull secret +`regcred2` to exist in the cluster. To roll out a new image: + +```bash +kubectl apply -f deploy/k8s/lmr.yaml # only when the manifest changed +kubectl rollout restart deployment/localmodelroutex +kubectl rollout status deployment/localmodelroutex +``` + +`compose.yml` and `compose.bundled.yml` build from source for single-host use. +The bundled variant runs Postgres inside the same container. + ## Database & migrations SQL migrations live in [`backend/migrations/`](backend/migrations/) using diff --git a/deploy/k8s/lmr.yaml b/deploy/k8s/lmr.yaml new file mode 100644 index 0000000..8243f3f --- /dev/null +++ b/deploy/k8s/lmr.yaml @@ -0,0 +1,90 @@ +# LMR gateway on k3s. Config and secrets live in the cluster: +# configmap/lmr-config GATEWAY_* settings +# secret/lmr-secret GATEWAY_DB_URL, GATEWAY_OIDC_CLIENT_SECRET, GATEWAY_SECURITY_ENCRYPTION_KEY +# secret/regcred2 registry pull credentials +# Apply with: kubectl apply -f deploy/k8s/lmr.yaml +# Roll a new image with: kubectl rollout restart deployment/localmodelroutex +apiVersion: apps/v1 +kind: Deployment +metadata: + name: localmodelroutex + namespace: default + labels: + app: localmodelroutex +spec: + replicas: 3 + revisionHistoryLimit: 5 + selector: + matchLabels: + app: localmodelroutex + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + template: + metadata: + labels: + app: localmodelroutex + spec: + imagePullSecrets: + - name: regcred2 + terminationGracePeriodSeconds: 30 + containers: + - name: localmodelroutex + image: registry.govekar.net/lovrog05/localmodelroutex:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 8080 + envFrom: + - configMapRef: + name: lmr-config + - secretRef: + name: lmr-secret + # Startup covers OIDC discovery and the first database connection. + # Up to 2 minutes before the liveness probe takes over. + startupProbe: + httpGet: + path: /healthz + port: http + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 24 + # /healthz only checks that the process answers. A database or + # network blip must not restart the pod, so this never touches the DB. + livenessProbe: + httpGet: + path: /healthz + port: http + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + # /readyz pings the database with a 3 second internal timeout. The + # pod leaves the Service while the database is unreachable and + # returns when it recovers, without a restart. + readinessProbe: + httpGet: + path: /readyz + port: http + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: lmr + namespace: default + labels: + app: localmodelroutex +spec: + type: LoadBalancer + selector: + app: localmodelroutex + ports: + - name: http + port: 80 + targetPort: http + protocol: TCP