Skip to content

[INFRA] 운영에 prod 프로파일이 꺼져 있다 — fail-fast 미작동, API 문서가 외부에 열려 있다 #101

Description

@minyongP

인프라 작업이 필요한 항목이라 백엔드 단독으로 진행하지 않고 이슈로 남긴다. @cherry-go-round

요약

운영 배포에 JWT_PRIVATE_KEY가 주입되지 않는다. 그런데 prod 프로파일도 활성화되어 있지 않아서, 이 누락을 잡아내려고 만든 기동 실패(fail-fast)가 동작하지 않는다. 결과적으로 파드는 정상적으로 뜨고 서명 키를 스스로 만들어 쓴다 — 배포할 때마다 로그인 세션이 전부 끊긴다.

왜 이 값이 필요한가

세션 JWT는 RS256으로 서명한다(BD-31). 키가 주입되지 않으면 JwtKeyProvider가 기동할 때 임시 키쌍을 만든다. 프로세스가 하나뿐인 로컬에서는 문제가 없다 — 발급한 키와 검증하는 키가 항상 같다.

운영에서는 같은 동작이 이렇게 나타난다.

상황 결과
롤링 배포 중 (maxSurge: 1, maxUnavailable: 0) 새 파드와 기존 파드가 다른 키로 동시에 트래픽을 받는다 → 요청이 어느 파드로 가느냐에 따라 간헐적 401
배포·재시작 완료 후 기존 토큰이 전부 무효 → 전체 사용자 로그아웃
replicaCount를 2 이상으로 올리면 배포 중이 아니어도 간헐적 401이 상시 발생

재현이 안 되는 로그인 실패로 나타나기 때문에, 원인을 찾기 어려운 종류의 고장이다. BD-31이 운영에서 기동 실패를 택한 이유가 이것이다 — 조용히 망가지는 쪽보다 뜨지 않는 쪽이 낫다고 판단했다.

확인한 것

확인 대상 상태
back/src/main/resources/application.yml:72 private-key: ${JWT_PRIVATE_KEY:} — 기본값은 빈 문자열
infra/apps/prod/back/values.yaml (env: 53~68행) JWT_PRIVATE_KEY 없음. DB 비밀번호만 secretKeyRef로 들어온다
infra/secrets/prod/ ghcr-back-pull, postgres-credentials 둘뿐. JWT 키용 SealedSecret 없음
back/Dockerfile:26 ENTRYPOINT ["java", "-jar", "/app.jar"] — 프로파일 지정 없음
infra 전체 SPRING_PROFILES_ACTIVE 설정이 어디에도 없음

back/src/main/resources/application-prod.yml이 운영에서 적용되지 않고 있다. DB가 정상 동작하는 것은 인프라가 SPRING_DATASOURCE_* 환경변수로 직접 주입하기 때문이고(환경변수가 프로파일 파일보다 우선한다), 그래서 프로파일이 꺼져 있다는 사실이 지금까지 드러나지 않았다.

이 프로파일이 꺼져 있어서 함께 어긋난 것이 하나 더 있다. application-prod.ymlspringdoc.api-docs.enabled: false로 API 문서를 끄는데, 프로파일이 적용되지 않으므로 운영 파드에서 문서 경로가 켜진 상태다(springdoc-openapi-starter-webmvc-ui 의존성 있음). backingress.enabled: false라 외부에서 실제로 닿는지는 라우팅에 달려 있어서, 이 부분은 확인이 필요하다.

해야 할 일 (인프라)

1. 키 만들기 — 저장소가 public이므로 PEM 원본을 커밋하지 않는다.

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt-prod.pem

2. SealedSecret으로 봉인 — 예: infra/secrets/prod/pinlog-back-jwt.sealedsecret.yaml, 키 이름 private-key.

PEM은 실제 개행이 들어간 그대로 넣으면 된다. JwtKeyProvider가 파싱 전에 공백·개행을 모두 지운다. 반대로 \n 두 글자 문자열로 바꿔 넣으면 역슬래시가 남아 파싱이 깨진다 — 로컬 .env\n으로 적는 방식(properties 파서가 실제 개행으로 되돌린다)과 다르다.

3. infra/apps/prod/back/values.yamlenv:에 추가

  - name: JWT_PRIVATE_KEY
    valueFrom:
      secretKeyRef:
        name: pinlog-back-jwt
        key: private-key
  - name: SPRING_PROFILES_ACTIVE
    value: prod

SPRING_PROFILES_ACTIVE를 같이 넣어야 fail-fast가 살아난다. 이게 없으면 다음에 또 키가 빠져도 같은 방식으로 조용히 넘어간다.

4. 순서 주의 — 키보다 프로파일을 먼저 켜면 파드가 기동에 실패한다. 두 값을 같은 커밋으로 넣거나, Secret을 먼저 만든 뒤 프로파일을 켠다.

프로파일을 켜도 DB 접속은 지금과 같다. 환경변수가 application-prod.yml보다 우선하므로 주소·계정·비밀번호는 인프라가 주는 값이 계속 이긴다.

알아 둘 것

키 회전은 아직 구현하지 않았다. kid 헤더만 심어 뒀고 여러 키를 동시에 검증하지 않는다. 한번 정한 키를 바꾸면 그 시점에 전체 로그아웃이 된다. 키를 잃어버리는 경우도 같다.

참고

  • 결정 근거: docs/backend/decisions/BD-31-jwt-rs256-key-management.md
  • 설정 규약: docs/development/configuration.md (JWT_PRIVATE_KEY — 운영 필수)
  • 인증 계약: docs/development/authentication.md 8장 (로컬에서 키를 고정하는 방법)
  • 구현 보고: docs/backend/implements/BI-18-2026-07-28-jwt-cookie-session.md — "인프라에 JWT_PRIVATE_KEY 요청" 항목으로 남아 있던 건이다

Metadata

Metadata

Labels

chore빌드·설정·잡무

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions