From 95c5e71189eb93e3f2b5bf4016ede3e69ea6b136 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 14:33:35 +0200 Subject: [PATCH 01/23] fix deployment by making database in memory --- infra/helm/team-devoops/templates/keycloak-deployment.yaml | 4 ++++ .../team-devoops/templates/keycloak-postgres-service.yaml | 2 +- .../team-devoops/templates/keycloak-postgres-statefulset.yaml | 2 +- infra/helm/team-devoops/templates/keycloak-secret.yaml | 4 +++- infra/helm/team-devoops/values.yaml | 4 ++++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index 74d3c17..30653cf 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -15,6 +15,7 @@ spec: labels: {{- include "team-devoops.selectorLabels" (dict "name" "keycloak") | nindent 8 }} spec: + {{- if .Values.keycloak.db.enabled }} initContainers: - name: wait-for-db image: busybox:1.36 @@ -26,6 +27,7 @@ spec: echo "waiting for keycloak-database..." sleep 3 done + {{- end }} containers: - name: keycloak image: {{ .Values.keycloak.image }} @@ -34,6 +36,7 @@ spec: ports: - containerPort: 8080 env: + {{- if .Values.keycloak.db.enabled }} - name: KC_DB value: postgres - name: KC_DB_URL_HOST @@ -45,6 +48,7 @@ spec: secretKeyRef: name: keycloak-credentials key: KC_DB_PASSWORD + {{- end }} - name: KC_BOOTSTRAP_ADMIN_USERNAME value: {{ .Values.keycloak.adminUsername | quote }} - name: KC_BOOTSTRAP_ADMIN_PASSWORD diff --git a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml index f521d3b..6766a36 100644 --- a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml +++ b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.keycloak.enabled }} +{{- if and .Values.keycloak.enabled .Values.keycloak.db.enabled }} # Headless service so the StatefulSet pod is reachable at :5432 inside the cluster. apiVersion: v1 kind: Service diff --git a/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml b/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml index 5aee305..4e228ae 100644 --- a/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml +++ b/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml @@ -1,4 +1,4 @@ -{{- if .Values.keycloak.enabled }} +{{- if and .Values.keycloak.enabled .Values.keycloak.db.enabled }} apiVersion: apps/v1 kind: StatefulSet metadata: diff --git a/infra/helm/team-devoops/templates/keycloak-secret.yaml b/infra/helm/team-devoops/templates/keycloak-secret.yaml index fe41ce6..c4c4a56 100644 --- a/infra/helm/team-devoops/templates/keycloak-secret.yaml +++ b/infra/helm/team-devoops/templates/keycloak-secret.yaml @@ -7,7 +7,9 @@ metadata: {{- include "team-devoops.labels" (dict "name" "keycloak-credentials" "root" $) | nindent 4 }} type: Opaque stringData: + KC_BOOTSTRAP_ADMIN_PASSWORD: {{ .Values.keycloak.adminPassword | quote }} + {{- if .Values.keycloak.db.enabled }} KC_DB_PASSWORD: {{ .Values.keycloak.db.password | quote }} POSTGRES_PASSWORD: {{ .Values.keycloak.db.password | quote }} - KC_BOOTSTRAP_ADMIN_PASSWORD: {{ .Values.keycloak.adminPassword | quote }} + {{- end }} {{- end }} diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 70171b4..8ea07e8 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -65,6 +65,10 @@ keycloak: cpu: 500m memory: 512Mi db: + # enabled: false means Keycloak uses its built-in H2 database (start-dev mode). + # The realm is re-imported from the ConfigMap on every pod start, so no + # user/client data is lost. Set to true only if you need persistent sessions. + enabled: false name: keycloak user: keycloak_user password: keycloak_password From a739b5f522184054030264e3d26276163fbcfa87 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 15:00:00 +0200 Subject: [PATCH 02/23] fix resource rules from cluster --- infra/helm/team-devoops/values.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 8ea07e8..0133c9f 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -29,10 +29,10 @@ database: storageSize: 5Gi resources: requests: - cpu: 250m + cpu: 100m memory: 256Mi limits: - cpu: "1" + cpu: 250m memory: 512Mi # py-genai-helper reads its configuration from a Secret created out-of-band by the @@ -62,13 +62,13 @@ keycloak: cpu: 100m memory: 256Mi limits: - cpu: 500m + cpu: 250m memory: 512Mi db: # enabled: false means Keycloak uses its built-in H2 database (start-dev mode). # The realm is re-imported from the ConfigMap on every pod start, so no # user/client data is lost. Set to true only if you need persistent sessions. - enabled: false + enabled: true name: keycloak user: keycloak_user password: keycloak_password @@ -79,7 +79,7 @@ keycloak: cpu: 100m memory: 128Mi limits: - cpu: 250m + cpu: 150m memory: 256Mi ingress: @@ -101,7 +101,7 @@ resources: cpu: 100m memory: 256Mi limits: - cpu: 500m + cpu: 200m memory: 512Mi # --------------------------------------------------------------------------- From 4064b29e899b680bdde547653807e53ee5b9a508 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 16:25:11 +0200 Subject: [PATCH 03/23] fix keycloak for cluster and vm --- README.md | 15 ++++++--- infra/docker-compose.override.yml | 19 +++++++++++ infra/docker-compose.yml | 33 ++++++++++++++----- .../helm/team-devoops/files/realm-config.json | 10 ++++-- infra/keycloak/realm-config.json | 10 ++++-- .../src/main/resources/application.properties | 4 +-- .../src/main/resources/application.properties | 4 +-- .../src/main/resources/application.properties | 4 +-- .../src/main/resources/application.properties | 4 +-- .../src/main/resources/application.properties | 4 +-- .../src/main/resources/application.properties | 4 +-- web-client/src/features/payments/client.ts | 2 +- web-client/src/lib/keycloak.ts | 2 +- 13 files changed, 84 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 0e7608f..6d5eb8e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ The Spring Boot services and the GenAI service share a **PostgreSQL** database. | GenAI Service | `/api/v1/helper/…` | 5000 | Python 3.12, Flask, LangChain | | Web Client | `/` | 8080 | React, Vite | | Swagger UI | `/docs` | 8080 | swaggerapi/swagger-ui | +| Keycloak | `/auth` | 8080 | Keycloak 26 | | Traefik dashboard | `http://localhost:8080` (local only) | — | Traefik v3 | | PostgreSQL | internal only | 5432 | postgres:15 | @@ -250,20 +251,24 @@ All services are protected by [Keycloak 26](https://www.keycloak.org) via OIDC/J ### Local login -When running with Docker Compose, Keycloak is available at . The realm is auto-imported on first start from [`infra/keycloak/realm-config.json`](infra/keycloak/realm-config.json). +When running with Docker Compose, Keycloak is available at . The realm is auto-imported on first start from [`infra/keycloak/realm-config.json`](infra/keycloak/realm-config.json). The web client redirects to Keycloak automatically (`login-required` strategy). Log in with any of the test users above. +### Production admin console + +Keycloak is publicly accessible via Traefik at . Admin console: `/auth/admin`. + ### Spring services — JWT validation Each Spring service is a stateless OAuth2 resource server. It validates Bearer JWTs against Keycloak's JWK set and extracts roles from the `realm_access.roles` claim, mapping them to Spring `ROLE_*` authorities (e.g. `"admin"` → `ROLE_admin`). -| Environment variable | Purpose | +| Property | Purpose | |---|---| -| `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI` | Validates the `iss` claim in incoming JWTs | -| `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI` | URL to fetch Keycloak's public signing keys | +| `spring.security.oauth2.resourceserver.jwt.issuer-uri` | Validates the `iss` claim in incoming JWTs | +| `spring.security.oauth2.resourceserver.jwt.jwk-set-uri` | URL to fetch Keycloak's public signing keys | -Docker Compose sets these to `http://keycloak:8080/auth/realms/devops/…`. On Kubernetes they are injected via the `env:` block in `infra/helm/team-devoops/values.yaml` using the internal `keycloak` ClusterIP DNS name. +These are set in each service's `src/main/resources/application.properties` as defaults (pointing at the local Keycloak on `localhost:8081/auth`). On the Azure VM, `docker-compose.yml` overrides `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI` with the public HTTPS issuer so it matches the `iss` claim in tokens issued by production Keycloak. The JWK set URI always uses the internal Docker hostname `http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs`. On Kubernetes they are injected via the `env:` block in `infra/helm/team-devoops/values.yaml` using the internal `keycloak` ClusterIP DNS name. ## Docs diff --git a/infra/docker-compose.override.yml b/infra/docker-compose.override.yml index 57f1fc7..0be7631 100644 --- a/infra/docker-compose.override.yml +++ b/infra/docker-compose.override.yml @@ -40,6 +40,8 @@ services: - "traefik.http.services.py-genai-helper.loadbalancer.server.port=5000" organization-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.organization-service.entrypoints=web" @@ -49,6 +51,8 @@ services: - "traefik.http.services.organization-service.loadbalancer.server.port=8080" member-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.member-service.entrypoints=web" @@ -58,6 +62,8 @@ services: - "traefik.http.services.member-service.loadbalancer.server.port=8080" event-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.event-service.entrypoints=web" @@ -67,6 +73,8 @@ services: - "traefik.http.services.event-service.loadbalancer.server.port=8080" feedback-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.feedback-service.entrypoints=web" @@ -76,6 +84,8 @@ services: - "traefik.http.services.feedback-service.loadbalancer.server.port=8080" finance-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.finance-service.entrypoints=web" @@ -85,6 +95,8 @@ services: - "traefik.http.services.finance-service.loadbalancer.server.port=8080" letter-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.letter-service.entrypoints=web" @@ -107,6 +119,13 @@ services: - "traefik.http.routers.web-client.rule=PathPrefix(`/`)" - "traefik.http.services.web-client.loadbalancer.server.port=8080" + keycloak: + environment: + KC_HOSTNAME: "http://localhost:8081/auth" + traefik-forward-auth: labels: !override - "traefik.enable=false" + environment: + - PROVIDERS_OIDC_ISSUER_URL=http://localhost:8081/auth/realms/devops + - INSECURE_COOKIE=true diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index fea6b1c..506e1bc 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -33,6 +33,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.organization-service.entrypoints=websecure" @@ -59,6 +60,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.member-service.entrypoints=websecure" @@ -85,6 +87,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.event-service.entrypoints=websecure" @@ -111,6 +114,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.feedback-service.entrypoints=websecure" @@ -137,6 +141,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.finance-service.entrypoints=websecure" @@ -163,6 +168,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.letter-service.entrypoints=websecure" @@ -211,8 +217,6 @@ services: - py-genai-helper labels: - "traefik.enable=true" - - "traefik.http.routers.web-client.entrypoints=web" - - "traefik.http.routers.web-client.rule=PathPrefix(`/`)" - "traefik.http.routers.web-client.entrypoints=websecure" - "traefik.http.routers.web-client.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`)" - "traefik.http.routers.web-client.tls=true" @@ -261,17 +265,19 @@ services: container_name: traefik-forward-auth environment: - DEFAULT_PROVIDER=oidc - - PROVIDERS_OIDC_ISSUER_URL=http://localhost:8081/realms/devops + - PROVIDERS_OIDC_ISSUER_URL=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops - PROVIDERS_OIDC_CLIENT_ID=traefik-forward-auth - PROVIDERS_OIDC_CLIENT_SECRET=traefik-forward-auth-secret - SECRET=a-random-32-char-secret-changeme! - - INSECURE_COOKIE=true + - INSECURE_COOKIE=false extra_hosts: - - "localhost:host-gateway" + - "team-devoops.uaenorth.cloudapp.azure.com:host-gateway" labels: - "traefik.enable=true" - - "traefik.http.routers.traefik-forward-auth.rule=Path(`/_oauth`)" - - "traefik.http.routers.traefik-forward-auth.entrypoints=web" + - "traefik.http.routers.traefik-forward-auth.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`) && Path(`/_oauth`)" + - "traefik.http.routers.traefik-forward-auth.entrypoints=websecure" + - "traefik.http.routers.traefik-forward-auth.tls=true" + - "traefik.http.routers.traefik-forward-auth.tls.certresolver=le" - "traefik.http.routers.traefik-forward-auth.middlewares=forward-auth@file" - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181" depends_on: @@ -295,6 +301,17 @@ services: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin KC_HEALTH_ENABLED: "true" + KC_HTTP_RELATIVE_PATH: /auth + KC_HTTP_MANAGEMENT_RELATIVE_PATH: / + KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com/auth + KC_PROXY_HEADERS: xforwarded + labels: + - "traefik.enable=true" + - "traefik.http.routers.keycloak.entrypoints=websecure" + - "traefik.http.routers.keycloak.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`) && PathPrefix(`/auth`)" + - "traefik.http.routers.keycloak.tls=true" + - "traefik.http.routers.keycloak.tls.certresolver=le" + - "traefik.http.services.keycloak.loadbalancer.server.port=8080" healthcheck: test: - "CMD-SHELL" @@ -302,7 +319,7 @@ services: interval: 10s timeout: 5s retries: 20 - start_period: 90s + start_period: 30s volumes: - ./keycloak/realm-config.json:/opt/keycloak/data/import/realm-config.json ports: diff --git a/infra/helm/team-devoops/files/realm-config.json b/infra/helm/team-devoops/files/realm-config.json index 916ab4d..091e210 100644 --- a/infra/helm/team-devoops/files/realm-config.json +++ b/infra/helm/team-devoops/files/realm-config.json @@ -68,8 +68,14 @@ "secret": "traefik-forward-auth-secret", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, - "redirectUris": ["http://localhost/_oauth"], - "webOrigins": ["http://localhost"] + "redirectUris": [ + "https://team-devoops.uaenorth.cloudapp.azure.com/_oauth", + "http://localhost/_oauth" + ], + "webOrigins": [ + "https://team-devoops.uaenorth.cloudapp.azure.com", + "http://localhost" + ] } ] } diff --git a/infra/keycloak/realm-config.json b/infra/keycloak/realm-config.json index 916ab4d..091e210 100644 --- a/infra/keycloak/realm-config.json +++ b/infra/keycloak/realm-config.json @@ -68,8 +68,14 @@ "secret": "traefik-forward-auth-secret", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, - "redirectUris": ["http://localhost/_oauth"], - "webOrigins": ["http://localhost"] + "redirectUris": [ + "https://team-devoops.uaenorth.cloudapp.azure.com/_oauth", + "http://localhost/_oauth" + ], + "webOrigins": [ + "https://team-devoops.uaenorth.cloudapp.azure.com", + "http://localhost" + ] } ] } diff --git a/services/spring-event/src/main/resources/application.properties b/services/spring-event/src/main/resources/application.properties index 96d94fd..670e53c 100644 --- a/services/spring-event/src/main/resources/application.properties +++ b/services/spring-event/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=event-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-feedback/src/main/resources/application.properties b/services/spring-feedback/src/main/resources/application.properties index f6a7fe3..d0381b2 100644 --- a/services/spring-feedback/src/main/resources/application.properties +++ b/services/spring-feedback/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=feedback-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-finance/src/main/resources/application.properties b/services/spring-finance/src/main/resources/application.properties index b0711a8..e8af8a3 100644 --- a/services/spring-finance/src/main/resources/application.properties +++ b/services/spring-finance/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=finance-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-letter/src/main/resources/application.properties b/services/spring-letter/src/main/resources/application.properties index f9155b9..d550a7f 100644 --- a/services/spring-letter/src/main/resources/application.properties +++ b/services/spring-letter/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=letter-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-member/src/main/resources/application.properties b/services/spring-member/src/main/resources/application.properties index e3f5a62..acd890e 100644 --- a/services/spring-member/src/main/resources/application.properties +++ b/services/spring-member/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=member-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-organization/src/main/resources/application.properties b/services/spring-organization/src/main/resources/application.properties index f5616e4..8f1688e 100644 --- a/services/spring-organization/src/main/resources/application.properties +++ b/services/spring-organization/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=organization-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/web-client/src/features/payments/client.ts b/web-client/src/features/payments/client.ts index da9ec92..f3c7d00 100644 --- a/web-client/src/features/payments/client.ts +++ b/web-client/src/features/payments/client.ts @@ -1,3 +1,3 @@ import { createApiClient } from '@/lib/keycloak' -export const paymentsClient = createApiClient('/api/v1/finances') +export const paymentsClient = createApiClient('/api/v1/finance') diff --git a/web-client/src/lib/keycloak.ts b/web-client/src/lib/keycloak.ts index d73b355..a52d698 100644 --- a/web-client/src/lib/keycloak.ts +++ b/web-client/src/lib/keycloak.ts @@ -2,7 +2,7 @@ import Keycloak from 'keycloak-js' import axios, { type AxiosInstance } from 'axios' const keycloak = new Keycloak({ - url: import.meta.env.VITE_KEYCLOAK_URL || 'http://localhost:8081', + url: import.meta.env.VITE_KEYCLOAK_URL || 'http://localhost:8081/auth', realm: 'devops', clientId: 'devops-client', }) From f7806c1c1327c1f1180d9f5804207d1c83719b2d Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 16:55:24 +0200 Subject: [PATCH 04/23] fix keycloak again for vm --- infra/ansible/playbook.yml | 2 ++ infra/docker-compose.override.yml | 2 +- infra/docker-compose.yml | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 77259c2..5265ceb 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -81,6 +81,8 @@ src: "{{ genai_env_file }}" dest: "{{ app_dir }}/services/py-genai-helper/.env" mode: "0600" + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" - name: Deploy with docker compose shell: > diff --git a/infra/docker-compose.override.yml b/infra/docker-compose.override.yml index 0be7631..40eba95 100644 --- a/infra/docker-compose.override.yml +++ b/infra/docker-compose.override.yml @@ -121,7 +121,7 @@ services: keycloak: environment: - KC_HOSTNAME: "http://localhost:8081/auth" + KC_HOSTNAME: "http://localhost:8081" traefik-forward-auth: labels: !override diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 506e1bc..01cf0d7 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -285,6 +285,7 @@ services: condition: service_healthy networks: - proxy + restart: on-failure keycloak: image: quay.io/keycloak/keycloak:26.0.0 @@ -303,7 +304,7 @@ services: KC_HEALTH_ENABLED: "true" KC_HTTP_RELATIVE_PATH: /auth KC_HTTP_MANAGEMENT_RELATIVE_PATH: / - KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com/auth + KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com KC_PROXY_HEADERS: xforwarded labels: - "traefik.enable=true" From 82f745fa7d21453984ebcb241410c57504a973f5 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 16:57:55 +0200 Subject: [PATCH 05/23] fix playbook to let remote git pull --- infra/ansible/playbook.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 5265ceb..f8b6f49 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -76,6 +76,13 @@ force: true update: true + - name: Set repository ownership to ansible_user + file: + path: "{{ app_dir }}" + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" + recurse: true + - name: Write py-genai-helper .env file copy: src: "{{ genai_env_file }}" From a63e634b6b8b54bbafe470bb460c4b4f0f565428 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 17:03:14 +0200 Subject: [PATCH 06/23] fix quotas for cluster --- infra/helm/team-devoops/values.yaml | 39 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 0133c9f..e0f8a59 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -29,11 +29,11 @@ database: storageSize: 5Gi resources: requests: - cpu: 100m - memory: 256Mi + cpu: 50m + memory: 128Mi limits: - cpu: 250m - memory: 512Mi + cpu: 150m + memory: 256Mi # py-genai-helper reads its configuration from a Secret created out-of-band by the # pipeline (kubectl create secret --from-env-file). Helm only references it by name. @@ -99,10 +99,10 @@ ingress: resources: requests: cpu: 100m - memory: 256Mi + memory: 128Mi limits: cpu: 200m - memory: 512Mi + memory: 384Mi # --------------------------------------------------------------------------- # Service catalogue. Keys are the Kubernetes object names. @@ -125,6 +125,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" member-service: path: /api/v1/members port: 8080 @@ -134,6 +135,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" event-service: path: /api/v1/events port: 8080 @@ -143,6 +145,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" feedback-service: path: /api/v1/feedback port: 8080 @@ -152,6 +155,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" finance-service: path: /api/v1/finance port: 8080 @@ -161,6 +165,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" letter-service: path: /api/v1/letters port: 8080 @@ -170,6 +175,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" py-genai-helper: path: /api/v1/helper port: 5000 @@ -177,14 +183,35 @@ services: health: /health stripPrefix: true envFromSecret: genai-env + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 200m + memory: 512Mi web-client: path: / port: 8080 db: false health: / stripPrefix: false + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi api-docs: path: /docs port: 8080 db: false stripPrefix: false + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi From fe78f8340d11cd413de5e89f744ab2d53554f8b5 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 17:17:57 +0200 Subject: [PATCH 07/23] fix pre cluster cleanup --- .github/workflows/cd.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ec39b79..97b47b7 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -147,10 +147,22 @@ jobs: --from-env-file=/tmp/genai.env \ --dry-run=client -o yaml | kubectl apply -f - + - name: Unlock stuck Helm release (if any) + run: | + for secret in $(kubectl -n "$NAMESPACE" get secret \ + -l "owner=helm,name=team-devoops" -o name 2>/dev/null); do + status=$(kubectl -n "$NAMESPACE" get "$secret" \ + -o jsonpath='{.metadata.labels.status}' 2>/dev/null || echo "") + if [[ "$status" == pending-* ]]; then + echo "Deleting stuck Helm secret $secret (status=$status)" + kubectl -n "$NAMESPACE" delete "$secret" + fi + done + - name: Helm upgrade run: | helm upgrade --install team-devoops infra/helm/team-devoops \ --namespace "$NAMESPACE" \ --set global.image.tag=${{ github.sha }} \ --set keycloak.hostname=ge83mom-devops26.stud.k8s.aet.cit.tum.de \ - --atomic --timeout 15m + --rollback-on-failure --timeout 15m From e05bbb6873996734a92b7a67340b0c18e59d8f3c Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 17:20:38 +0200 Subject: [PATCH 08/23] fix playbook branch thing --- .github/workflows/cd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 97b47b7..c6b6214 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,6 +53,7 @@ jobs: -i /tmp/inventory.yml \ infra/ansible/playbook.yml \ -e "repo_url=https://github.com/${{ github.repository }}.git" \ + -e "branch=${{ github.ref_name }}" \ -e "genai_env_file=/tmp/genai.env" # ------------------------------------------------------------------ From 394654542860572e7162d11078672e50fb2d190a Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 18:06:07 +0200 Subject: [PATCH 09/23] fix vm forward --- infra/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 01cf0d7..e5f7cdd 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -278,7 +278,6 @@ services: - "traefik.http.routers.traefik-forward-auth.entrypoints=websecure" - "traefik.http.routers.traefik-forward-auth.tls=true" - "traefik.http.routers.traefik-forward-auth.tls.certresolver=le" - - "traefik.http.routers.traefik-forward-auth.middlewares=forward-auth@file" - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181" depends_on: keycloak: From 9bb5ff2402c88240fc85a7e16c7d9e65e016c45c Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 18:20:05 +0200 Subject: [PATCH 10/23] fix again --- infra/docker-compose.override.yml | 2 +- infra/docker-compose.yml | 2 +- infra/helm/team-devoops/templates/deployment.yaml | 2 ++ infra/helm/team-devoops/values.yaml | 8 ++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/infra/docker-compose.override.yml b/infra/docker-compose.override.yml index 40eba95..0be7631 100644 --- a/infra/docker-compose.override.yml +++ b/infra/docker-compose.override.yml @@ -121,7 +121,7 @@ services: keycloak: environment: - KC_HOSTNAME: "http://localhost:8081" + KC_HOSTNAME: "http://localhost:8081/auth" traefik-forward-auth: labels: !override diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index e5f7cdd..f5018eb 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -303,7 +303,7 @@ services: KC_HEALTH_ENABLED: "true" KC_HTTP_RELATIVE_PATH: /auth KC_HTTP_MANAGEMENT_RELATIVE_PATH: / - KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com + KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com/auth KC_PROXY_HEADERS: xforwarded labels: - "traefik.enable=true" diff --git a/infra/helm/team-devoops/templates/deployment.yaml b/infra/helm/team-devoops/templates/deployment.yaml index 4625437..4c855e2 100644 --- a/infra/helm/team-devoops/templates/deployment.yaml +++ b/infra/helm/team-devoops/templates/deployment.yaml @@ -8,6 +8,8 @@ metadata: {{- include "team-devoops.labels" (dict "name" $name "root" $root) | nindent 4 }} spec: replicas: {{ $svc.replicas | default 1 }} + strategy: + {{- toYaml $root.Values.strategy | nindent 4 }} selector: matchLabels: {{- include "team-devoops.selectorLabels" (dict "name" $name) | nindent 6 }} diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index e0f8a59..63880ce 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -95,6 +95,14 @@ ingress: # Adds the cert-manager.io/cluster-issuer annotation on the ingresses. clusterIssuer: letsencrypt-prod +# Rolling update strategy — maxSurge: 0 ensures the old pod is terminated before +# scheduling the new one, which is required to stay within the namespace CPU quota. +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + # Default compute resources applied to every app container (overridable per service). resources: requests: From dcb468c0d076aebebb1699aaf06d5d4574e9dffd Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 18:24:58 +0200 Subject: [PATCH 11/23] fix rate limits --- .github/workflows/cd.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c6b6214..82f1139 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -93,6 +93,8 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + with: + driver: docker - name: Log in to GHCR uses: docker/login-action@v3 From b656c1256214efbae4d6afb1e0734a5c32ac38ac Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 19:09:09 +0200 Subject: [PATCH 12/23] fix debug logs --- .github/workflows/cd.yml | 2 -- infra/docker-compose.yml | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 82f1139..0f65ec4 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -116,8 +116,6 @@ jobs: ${{ steps.img.outputs.repo }}:${{ github.sha }} ${{ steps.img.outputs.repo }}:latest build-args: ${{ matrix.build_args || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max # ------------------------------------------------------------------ # Deploy to the RKE2 Kubernetes cluster via Helm. diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index f5018eb..d593b54 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -270,6 +270,7 @@ services: - PROVIDERS_OIDC_CLIENT_SECRET=traefik-forward-auth-secret - SECRET=a-random-32-char-secret-changeme! - INSECURE_COOKIE=false + - LOG_LEVEL=debug extra_hosts: - "team-devoops.uaenorth.cloudapp.azure.com:host-gateway" labels: From cf968d064c78ab4da9385d9a2290709381bf0faf Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 22:30:44 +0200 Subject: [PATCH 13/23] fix again again --- infra/docker-compose.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index d593b54..bf48a20 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -274,12 +274,7 @@ services: extra_hosts: - "team-devoops.uaenorth.cloudapp.azure.com:host-gateway" labels: - - "traefik.enable=true" - - "traefik.http.routers.traefik-forward-auth.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`) && Path(`/_oauth`)" - - "traefik.http.routers.traefik-forward-auth.entrypoints=websecure" - - "traefik.http.routers.traefik-forward-auth.tls=true" - - "traefik.http.routers.traefik-forward-auth.tls.certresolver=le" - - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181" + - "traefik.enable=false" depends_on: keycloak: condition: service_healthy From ad985128d97af62f4e59695b2b90e3585c0c9b6c Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 22:34:33 +0200 Subject: [PATCH 14/23] fix: pass VITE_KEYCLOAK_URL build arg to web-client on VM --- infra/docker-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index bf48a20..99c83db 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -203,7 +203,10 @@ services: - proxy web-client: - build: ../web-client/ + build: + context: ../web-client/ + args: + VITE_KEYCLOAK_URL: https://team-devoops.uaenorth.cloudapp.azure.com/auth container_name: web-client expose: - 8080 From b4032afc3363d9f675f85485852e5265a9931c45 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 22:41:51 +0200 Subject: [PATCH 15/23] fix cluster finally --- .github/workflows/cd.yml | 2 +- infra/helm/team-devoops/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0f65ec4..1c9e8e6 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -165,5 +165,5 @@ jobs: helm upgrade --install team-devoops infra/helm/team-devoops \ --namespace "$NAMESPACE" \ --set global.image.tag=${{ github.sha }} \ - --set keycloak.hostname=ge83mom-devops26.stud.k8s.aet.cit.tum.de \ + --set keycloak.hostname=https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth \ --rollback-on-failure --timeout 15m diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 63880ce..4b5749e 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -52,7 +52,7 @@ keycloak: enabled: true image: quay.io/keycloak/keycloak:26.0.0 path: /auth - hostname: ge83mom-devops26.stud.k8s.aet.cit.tum.de + hostname: https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth adminUsername: admin adminPassword: admin # Constrain JVM heap so Keycloak stays within its memory limit. From ef123b79d9b324e551ea8582c20f2154b1a7830c Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 22:57:30 +0200 Subject: [PATCH 16/23] fix: add resource limits to keycloak wait-for-db init container for quota --- infra/helm/team-devoops/templates/keycloak-deployment.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index 30653cf..d1ff940 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -27,6 +27,13 @@ spec: echo "waiting for keycloak-database..." sleep 3 done + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi {{- end }} containers: - name: keycloak From 559f3e92fb92142529dfbd9e2598c409807da114 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 23:18:12 +0200 Subject: [PATCH 17/23] fix: increase probe timeouts and JVM heap for k8s spring services --- infra/helm/team-devoops/templates/deployment.yaml | 10 +++++++--- infra/helm/team-devoops/values.yaml | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/infra/helm/team-devoops/templates/deployment.yaml b/infra/helm/team-devoops/templates/deployment.yaml index 4c855e2..08b5907 100644 --- a/infra/helm/team-devoops/templates/deployment.yaml +++ b/infra/helm/team-devoops/templates/deployment.yaml @@ -54,19 +54,23 @@ spec: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 15 + initialDelaySeconds: 30 periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 livenessProbe: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 60 + initialDelaySeconds: 90 periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 {{- else }} readinessProbe: tcpSocket: port: {{ $svc.port }} - initialDelaySeconds: 15 + initialDelaySeconds: 30 periodSeconds: 10 {{- end }} resources: diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 4b5749e..f6a8c7c 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -133,7 +133,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" member-service: path: /api/v1/members port: 8080 @@ -143,7 +143,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" event-service: path: /api/v1/events port: 8080 @@ -153,7 +153,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" feedback-service: path: /api/v1/feedback port: 8080 @@ -163,7 +163,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" finance-service: path: /api/v1/finance port: 8080 @@ -173,7 +173,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" letter-service: path: /api/v1/letters port: 8080 @@ -183,7 +183,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" - JAVA_TOOL_OPTIONS: "-Xmx256m -Xms64m" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" py-genai-helper: path: /api/v1/helper port: 5000 From f066b410b85520a7e4d6e7b7e5bc370f413c0530 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 23:29:28 +0200 Subject: [PATCH 18/23] fix: replace liveness initialDelay with startupProbe for spring services --- infra/helm/team-devoops/templates/deployment.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/infra/helm/team-devoops/templates/deployment.yaml b/infra/helm/team-devoops/templates/deployment.yaml index 08b5907..978269c 100644 --- a/infra/helm/team-devoops/templates/deployment.yaml +++ b/infra/helm/team-devoops/templates/deployment.yaml @@ -50,19 +50,25 @@ spec: {{- end }} {{- end }} {{- if $svc.health }} + startupProbe: + httpGet: + path: {{ $svc.health }} + port: {{ $svc.port }} + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 readinessProbe: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 - failureThreshold: 6 + failureThreshold: 3 livenessProbe: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 90 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 3 From 748fdf7e9266649224fd73b047cefc754d5ed24f Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Tue, 9 Jun 2026 23:33:00 +0200 Subject: [PATCH 19/23] fix: keycloak-database service selector uses wrong label key --- .../helm/team-devoops/templates/keycloak-postgres-service.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml index 6766a36..be5bee3 100644 --- a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml +++ b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml @@ -9,7 +9,7 @@ metadata: spec: clusterIP: None selector: - app: {{ .Values.keycloak.db.host }} + {{- include "team-devoops.selectorLabels" (dict "name" .Values.keycloak.db.host) | nindent 4 }} ports: - port: 5432 targetPort: 5432 From a883fafbd7bc7640d69d93b581faff0577b89ed6 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Wed, 10 Jun 2026 00:07:58 +0200 Subject: [PATCH 20/23] fix: add startupProbe to keycloak to prevent liveness kill during slow start --- .../templates/keycloak-deployment.yaml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index d1ff940..cbcf583 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -77,20 +77,28 @@ spec: - name: realm-config mountPath: /opt/keycloak/data/import readOnly: true + startupProbe: + httpGet: + path: {{ printf "%s/health/ready" .Values.keycloak.path }} + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 40 readinessProbe: httpGet: path: {{ printf "%s/health/ready" .Values.keycloak.path }} port: 8080 - initialDelaySeconds: 120 periodSeconds: 10 - failureThreshold: 12 + timeoutSeconds: 5 + failureThreshold: 3 livenessProbe: httpGet: path: {{ printf "%s/health/live" .Values.keycloak.path }} port: 8080 - initialDelaySeconds: 180 periodSeconds: 20 - failureThreshold: 5 + timeoutSeconds: 5 + failureThreshold: 3 resources: {{- toYaml .Values.keycloak.resources | nindent 12 }} volumes: From 9f17a76e5e7bf29120680d18f3f168416f8d676a Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Wed, 10 Jun 2026 00:19:17 +0200 Subject: [PATCH 21/23] fix: keycloak probes must use management port 9000, not 8080 --- .../templates/keycloak-deployment.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index cbcf583..bda2878 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -79,23 +79,23 @@ spec: readOnly: true startupProbe: httpGet: - path: {{ printf "%s/health/ready" .Values.keycloak.path }} - port: 8080 - initialDelaySeconds: 60 - periodSeconds: 15 + path: /health/ready + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 10 timeoutSeconds: 5 - failureThreshold: 40 + failureThreshold: 60 readinessProbe: httpGet: - path: {{ printf "%s/health/ready" .Values.keycloak.path }} - port: 8080 + path: /health/ready + port: 9000 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: - path: {{ printf "%s/health/live" .Values.keycloak.path }} - port: 8080 + path: /health/live + port: 9000 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 3 From 5197253d367c03705088650ad97221e5cbecdd83 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Wed, 10 Jun 2026 00:28:34 +0200 Subject: [PATCH 22/23] fix: set KC_HTTP_MANAGEMENT_RELATIVE_PATH=/ so health is at port 9000 /health/ready --- infra/helm/team-devoops/templates/keycloak-deployment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index bda2878..ef5cda9 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -67,6 +67,8 @@ spec: value: "true" - name: KC_HTTP_RELATIVE_PATH value: {{ .Values.keycloak.path | quote }} + - name: KC_HTTP_MANAGEMENT_RELATIVE_PATH + value: "/" - name: KC_HOSTNAME value: {{ .Values.keycloak.hostname | quote }} - name: KC_PROXY_HEADERS From e1401465fa0268f471c4727342848541544e00d4 Mon Sep 17 00:00:00 2001 From: Raphael Frank <04.raphael.frank@gmail.com> Date: Wed, 10 Jun 2026 00:40:54 +0200 Subject: [PATCH 23/23] fix: also delete failed helm release secrets in unlock step --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1c9e8e6..ffa8c15 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -154,7 +154,7 @@ jobs: -l "owner=helm,name=team-devoops" -o name 2>/dev/null); do status=$(kubectl -n "$NAMESPACE" get "$secret" \ -o jsonpath='{.metadata.labels.status}' 2>/dev/null || echo "") - if [[ "$status" == pending-* ]]; then + if [[ "$status" == pending-* || "$status" == "failed" ]]; then echo "Deleting stuck Helm secret $secret (status=$status)" kubectl -n "$NAMESPACE" delete "$secret" fi