Files
gscMy/k8s/deployment.yaml
Super User 9b0a1847c2 deploy: gscMy v0.1.5 (picks up kit withLocale no-op)
Same locale-prefix 404 fix as gscAdmin: kit AdminShell no longer
prepends /{locale} to internal URLs, since gscMy's app tree has no
[locale] segment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:32:41 +02:00

224 lines
7.1 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-ui
namespace: gsc-my
labels:
app: my-ui
component: frontend
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: my-ui
template:
metadata:
labels:
app: my-ui
component: frontend
# Egress to the in-cluster web-proxy (Squid) for NextAuth's
# Keycloak issuer-discovery fetch to auth.gosec.cloud.
egress-internet: "true"
# Egress to the internal-gateway proxy (Envoy) so the app can
# reach postgresql.internal-gateway.svc.cluster.local:5432.
# Selected by the allow-internal-gateway GlobalNetworkPolicy.
egress-internal: "true"
spec:
containers:
- name: my-ui
image: registry.gosec.internal/gsc-my/ui:v0.1.5
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
env:
- name: NODE_ENV
value: "production"
# Route Node fetch() through Squid for Keycloak discovery.
- name: HTTP_PROXY
value: "http://web-proxy.web-proxy.svc.cluster.local:3128"
- name: HTTPS_PROXY
value: "http://web-proxy.web-proxy.svc.cluster.local:3128"
- name: NO_PROXY
value: "localhost,127.0.0.1,.cluster.local,.svc,.gosec.internal"
- name: NEXT_PUBLIC_APP_URL
value: "https://my.gosec.internal"
# Postgres (gsc_core — admin/nav/public/shell schemas).
- name: PGHOST
valueFrom:
secretKeyRef:
name: gsc-my-db
key: host
- name: PGPORT
valueFrom:
secretKeyRef:
name: gsc-my-db
key: port
- name: PGUSER
valueFrom:
secretKeyRef:
name: gsc-my-db
key: user
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: gsc-my-db
key: password
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: gsc-my-db
key: database
- name: DATABASE_URL
value: "postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):$(PGPORT)/$(PGDATABASE)"
# NextAuth + Keycloak (gosecCloud realm).
- name: AUTH_KEYCLOAK_ID
valueFrom:
secretKeyRef:
name: my-ui
key: keycloak-client-id
- name: AUTH_KEYCLOAK_SECRET
valueFrom:
secretKeyRef:
name: my-ui
key: keycloak-client-secret
- name: AUTH_KEYCLOAK_ISSUER
value: "https://auth.gosec.cloud/realms/gosecCloud"
- name: NEXTAUTH_URL
value: "https://my.gosec.internal"
# NextAuth v5 prefers AUTH_URL over NEXTAUTH_URL. Set both
# so a stale .env baked into a future image build can't
# shadow the runtime config and emit form actions pointing
# at the wrong host.
- name: AUTH_URL
value: "https://my.gosec.internal"
- name: AUTH_TRUST_HOST
value: "true"
- name: NEXTAUTH_SECRET
valueFrom:
secretKeyRef:
name: my-ui
key: nextauth-secret
# gsc-ops-api (mTLS) for chat contacts route. Cert files are
# mounted from a separate secret if/when the route is used;
# leaving the URL unset disables the contacts provider
# gracefully — see src/app/api/chat/contacts/route.ts.
# - name: OPS_API_URL
# - name: OPS_API_KEY
resources:
requests:
memory: "384Mi"
cpu: "100m"
limits:
memory: "768Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: nextjs-cache
mountPath: /app/.next/cache
volumes:
- name: tmp
emptyDir: {}
- name: nextjs-cache
emptyDir: {}
imagePullSecrets:
- name: registry-credentials
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-ui
topologyKey: kubernetes.io/hostname
---
apiVersion: v1
kind: Service
metadata:
name: my-ui
namespace: gsc-my
labels:
app: my-ui
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
protocol: TCP
name: http
selector:
app: my-ui
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ui
namespace: gsc-my
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# ModSecurity + OWASP CRS is enabled cluster-wide; the chunked
# NextAuth session cookie (>4 KB of base64 JWE) trips CRS rules
# and returns 403 before the request reaches the pod. Same fix
# applied to crm-ui / support-ui / chronos / gsc-meet / etc.
nginx.ingress.kubernetes.io/enable-modsecurity: "false"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
# Hardening headers (CSP comes from cluster ConfigMap).
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: SAMEORIGIN";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
spec:
ingressClassName: nginx
tls:
- hosts:
- my.gosec.internal
secretName: my-tls-internal
rules:
- host: my.gosec.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-ui
port:
number: 3000