The authorized client repository is AuthenticatedPrincipalOAuth2AuthorizedClientRepository, keyed by principal with no session id in it, which is the mechanism behind the sharing problem Q1 and Q3 describe. Sharing a store does not fix a lookup key. Five problems on the way in: only build output was committed under bff/, a duplicate YAML key broke the image build and was invisible until the full log was captured, env placeholders without defaults broke the tests, actuator was behind the login redirect so a 200 was the login page, and the 117KB beans response failed through the proxy. Deploying two replicas made the login itself fail before any experiment started, because the authorization request lives in per-instance memory and the callback lands elsewhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
5.1 KiB
YAML
162 lines
5.1 KiB
YAML
# BFF (2 replicas) + Redis, for the B-layer experiments.
|
|
#
|
|
# The BFF is deployed FIRST WITHOUT any session store wiring. That is deliberate:
|
|
# B-0 asks what Spring Boot's autoconfiguration actually picks when nothing is
|
|
# configured, and the only honest way to answer is to look at a running instance
|
|
# that has been given nothing. Redis is deployed alongside but left unused until
|
|
# B-1 turns it on.
|
|
#
|
|
# kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
|
#
|
|
# Image comes from the workstation, not a registry:
|
|
# docker build -t keycloak-pattern-bff:lab bff/
|
|
# docker save keycloak-pattern-bff:lab | ssh test-server "ssh kc-lab-1 'sudo k3s ctr images import -'"
|
|
# (repeat for kc-lab-2)
|
|
# so imagePullPolicy must stay Never on both replicas.
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: bff-secrets
|
|
namespace: keycloak-lab
|
|
type: Opaque
|
|
stringData:
|
|
# Matches the client created with kcadm in the keycloak-patterns realm.
|
|
# Base64 in etcd is not encryption — see D-3.
|
|
KEYCLOAK_CLIENT_SECRET: bff-lab-secret
|
|
---
|
|
# Redis. No persistence yet: `--save ""` and no appendonly, so a restart loses
|
|
# everything. B-5 and B-6 compare that against RDB and AOF, which is easier to
|
|
# reason about when the starting point is "nothing survives".
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: redis
|
|
namespace: keycloak-lab
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels: { app: redis }
|
|
template:
|
|
metadata:
|
|
labels: { app: redis }
|
|
spec:
|
|
# Same node as postgres so a node-loss experiment takes both stores at
|
|
# once, matching how A-4 was set up.
|
|
nodeSelector:
|
|
kubernetes.io/hostname: kc-lab-2
|
|
containers:
|
|
- name: redis
|
|
image: redis:7.4-alpine
|
|
args: ["redis-server", "--save", "", "--appendonly", "no"]
|
|
ports:
|
|
- containerPort: 6379
|
|
name: redis
|
|
readinessProbe:
|
|
exec: { command: ["redis-cli", "ping"] }
|
|
initialDelaySeconds: 3
|
|
resources:
|
|
requests: { memory: 32Mi, cpu: 20m }
|
|
limits: { memory: 128Mi }
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: redis
|
|
namespace: keycloak-lab
|
|
spec:
|
|
selector: { app: redis }
|
|
ports:
|
|
- port: 6379
|
|
targetPort: redis
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: bff
|
|
namespace: keycloak-lab
|
|
spec:
|
|
# Two replicas is the whole point: Q1 and Q2 only exist because a request can
|
|
# land on an instance that did not handle the login.
|
|
replicas: 2
|
|
selector:
|
|
matchLabels: { app: bff }
|
|
template:
|
|
metadata:
|
|
labels: { app: bff }
|
|
spec:
|
|
# Spread across both nodes so "the other instance" is genuinely another
|
|
# machine, not another process on the same kernel.
|
|
topologySpreadConstraints:
|
|
- maxSkew: 1
|
|
topologyKey: kubernetes.io/hostname
|
|
whenUnsatisfiable: ScheduleAnyway
|
|
labelSelector:
|
|
matchLabels: { app: bff }
|
|
containers:
|
|
- name: bff
|
|
image: keycloak-pattern-bff:lab
|
|
imagePullPolicy: Never
|
|
ports:
|
|
- containerPort: 8083
|
|
name: http
|
|
env:
|
|
# The browser is redirected to the public name; the BFF calls the
|
|
# token endpoint over the cluster network. Getting these two the same
|
|
# way round is what the 2-hop header experiment was about.
|
|
- name: KC_ISSUER_EXTERNAL
|
|
value: https://auth.hyeonworks.com/realms/keycloak-patterns
|
|
- name: KC_ISSUER_INTERNAL
|
|
value: http://keycloak.keycloak-lab.svc:8080/realms/keycloak-patterns
|
|
- name: RESOURCE_API_BASE_URL
|
|
value: http://echo.keycloak-lab.svc:8080
|
|
- name: KEYCLOAK_CLIENT_SECRET
|
|
valueFrom:
|
|
secretKeyRef: { name: bff-secrets, key: KEYCLOAK_CLIENT_SECRET }
|
|
# Spring needs to know it is behind TLS termination, for the same
|
|
# reason Keycloak needs KC_PROXY_HEADERS. Without it the redirect_uri
|
|
# it builds comes back as http:// and Keycloak rejects it.
|
|
- name: SERVER_FORWARD_HEADERS_STRATEGY
|
|
value: native
|
|
- name: JAVA_TOOL_OPTIONS
|
|
value: "-Xms128m -Xmx320m"
|
|
readinessProbe:
|
|
httpGet: { path: /actuator/health/readiness, port: http }
|
|
initialDelaySeconds: 20
|
|
failureThreshold: 30
|
|
livenessProbe:
|
|
httpGet: { path: /actuator/health/liveness, port: http }
|
|
initialDelaySeconds: 60
|
|
resources:
|
|
requests: { memory: 320Mi, cpu: 100m }
|
|
limits: { memory: 512Mi }
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: bff
|
|
namespace: keycloak-lab
|
|
spec:
|
|
selector: { app: bff }
|
|
ports:
|
|
- port: 8083
|
|
targetPort: http
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: bff
|
|
namespace: keycloak-lab
|
|
spec:
|
|
ingressClassName: traefik
|
|
rules:
|
|
- host: app1.hyeonworks.com
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: bff
|
|
port:
|
|
number: 8083
|