Ticket: k8s overlay — Deployment, Service, private Tailscale ingress, SOPS secrets

ticket-westside-ops-k8s-overlay Doc

active backlog westside-ops ticket

Ticket: k8s overlay — Deployment, Service, private Tailscale ingress, SOPS secrets

Story: story-westside-ops-spreadsheet-access
Architecture: arch-deployment-westside-ops
Labels: story:spreadsheet-access,arch:k8s-overlay,type:infra,track:devops,scope:planned
Blocks: Marcus onboarding (needs a live URL)
Blocked by: Postgres role, services-entry, Woodpecker pipeline (needs an image in Harbor)

Purpose

Write the k8s manifests inside ~/westside-ops/k8s/ that ArgoCD syncs: Deployment (pulling the Streamlit image from Harbor), Service, private Tailscale Ingress (tailscale.com/expose: "true" — not funnel), and SOPS-encrypted secrets. ArgoCD's Application resource already exists (created by services-entry) and is pointed at westside-ops repo's k8s path.

Scope

  • Write 5 files under ~/westside-ops/k8s/: kustomization.yaml, deployment.yaml, service.yaml, ingress.yaml, secrets.enc.yaml (SOPS-encrypted)
  • Use SOPS age encryption matching the existing overlays/basketball-api/prod/harbor-creds.enc.yaml pattern — same recipient, same sops config
  • Populate secrets.enc.yaml with: WESTSIDE_OPS_DATABASE_URL, KEYCLOAK_CLIENT_SECRET, COOKIE_SECRET (random 32 bytes)
  • Commit and push to Forgejo — ArgoCD will auto-sync within seconds
  • Verify: pod starts, becomes ready, Ingress gets a Tailscale address, curl -k https://westside-ops.tail5b443a.ts.net returns a Streamlit HTML page (from within the tailnet)

Exact manifests

kustomization.yaml

deployment.yaml

service.yaml

ingress.yaml — the load-bearing file

secrets.enc.yaml (plaintext, to be encrypted with sops -e)

After populating the plaintext, encrypt: sops -e -i secrets.enc.yaml. Verify with sops -d secrets.enc.yaml that it round-trips. Only the .enc.yaml version is committed.

Acceptance Criteria

  • [ ] All 5 files exist in ~/westside-ops/k8s/, committed and pushed to Forgejo main
  • [ ] secrets.enc.yaml is SOPS-encrypted (not plaintext); sops -d round-trips successfully
  • [ ] ArgoCD application westside-ops reports Synced and Healthy
  • [ ] kubectl get pod -n westside-ops shows the pod in Running status
  • [ ] kubectl logs -n westside-ops deploy/westside-ops shows Streamlit bound to 0.0.0.0:8501
  • [ ] kubectl get ingress -n westside-ops shows the ingress with a Tailscale address assigned
  • [ ] curl -k https://westside-ops.tail5b443a.ts.net from a tailnet member returns a Streamlit HTML page (redirect to Keycloak login)
  • [ ] curl -k https://westside-ops.tail5b443a.ts.net from a non-tailnet member returns an error (DNS resolution or connection refused) — confirms the private mode is working
  • [ ] The pod's DB connection to basketball-api works (verify by logging in and loading the Players page — this moves into the Marcus onboarding ticket for end-to-end verification)

Files touched

  • ~/westside-ops/k8s/kustomization.yaml
  • ~/westside-ops/k8s/deployment.yaml
  • ~/westside-ops/k8s/service.yaml
  • ~/westside-ops/k8s/ingress.yaml
  • ~/westside-ops/k8s/secrets.enc.yaml

Rollback

Revert the commit. ArgoCD syncs back to the empty state (the prior commit had k8s/.gitkeep only). Pod is torn down. Namespace and Harbor project persist (owned by services-entry). Full rollback requires services-entry rollback.

Out of scope

  • NetworkPolicy — the existing bases/standard/networkpolicy.yaml is disabled per the kube-router ipset bug comment in existing overlays. Don't add a NetworkPolicy until that bug is fixed cluster-wide.
  • HorizontalPodAutoscaler — single replica is fine for v1, Marcus is the only user
  • ServiceMonitor / Prometheus scraping — Streamlit doesn't expose /metrics by default, and telemetry for Marcus-use isn't load-bearing. Follow-up ticket if needed.
  • Cert-manager or any TLS setup — Tailscale operator handles TLS automatically

Dependencies

Blocked by: services-entry (namespace + harbor-creds secret), Postgres role (database URL password), streamlit-app implementation (the code), Woodpecker pipeline (the image in Harbor).