# 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 } # 쿠버네티스는 같은 네임스페이스의 Service 마다 Docker link 시절의 # 환경변수를 자동 주입한다: REDIS_PORT=tcp://10.43.57.116:6379. # 그것이 application.yml 의 ${REDIS_PORT:6379} 를 덮어써서 기동이 실패했다. # Failed to bind properties under 'spring.data.redis.port' to int: # Value: "tcp://10.43.57.116:6379" # 이 주입 자체를 끄는 것이 근본 처방이다. 이름을 바꿔 피하면 다음 사람이 # 같은 함정에 다시 빠진다. enableServiceLinks: false 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 # echo 는 header-lab 네임스페이스의 8081 이다. 다른 네임스페이스의 # 서비스는 ..svc 로 부른다. 이름을 틀리면 500 이 나는데 # 원인은 UnresolvedAddressException 이지 토큰 문제가 아니다. - name: RESOURCE_API_BASE_URL value: http://echo.header-lab.svc:8081 - 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 # B-1: Application Session 을 Redis 로 옮긴다. # OAuth2AuthorizedClient 는 이것으로 옮겨지지 않는다 — 조회 키가 # 다르기 때문이며, B-0 에서 확인한 사실이다. - name: SPRING_SESSION_STORE_TYPE value: redis - name: REDIS_HOST value: redis.keycloak-lab.svc - name: REDIS_PORT value: "6379" # B-2: authorized client 는 PostgreSQL 로. 세션(Redis)과 다른 # 저장소를 쓰는 것이 Q3 가 말한 "각각 설계한다"의 실물이다. - name: BFF_DB_URL value: jdbc:postgresql://postgres.keycloak-lab.svc:5432/keycloak - name: BFF_DB_USER value: keycloak - name: BFF_DB_PASSWORD valueFrom: secretKeyRef: { name: keycloak-lab-secrets, key: POSTGRES_PASSWORD } - 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