본문 바로가기
Dev Log (개발 문제 & 해결 기록)/Error & Fix (에러 해결 기록)

[오류로그 #1][502 #1] PR 머지 직후 502 Bad Gateway — application.yml 구성 붕괴 (2025-08-22)

by Dev.후크리 2025. 9. 3.
요약(Summary)

 

main 병합 직후 Nginx 502 발생(S1), API 전체 영향, 2025-08-22 21:10 ~ 21:27 (KST).

원인은 application.yml 설정 변경으로 앱 기동 실패 → 업스트림 미응답.

 


환경(Context)

 

  • STACK: Spring Boot 3.5.x, Java 21, MySQL 8.x (AWS RDS), Nginx(RevProxy), Docker, GitHub Actions
  • 배포 흐름: GitHub → Actions 빌드/푸시 → EC2 Docker run → Nginx 리버스 프록시
  • 프로필: application-{dev,prod}.yml (운영은 prod)
  • RDS 연결 정책: EC2 보안그룹 ↔ RDS 3306 인바운드 허용
  • 아키텍처(간단 도식):
flowchart LR
Client --> Nginx --> App(Spring Boot) --> RDS[(MySQL)]
 
 

 
 
증상 & 로그(Symptoms & Logs)

 

Nginx 에러 로그

2025/08/22 21:11:02 [error] 1234#1234: *15 upstream prematurely closed connection while reading response header from upstream,
upstream: http://127.0.0.1:8080/actuator/health, host: "api-bytecookie.click"
 

컨테이너 로그

org.yaml.snakeyaml.error.YAMLException: java.lang.IllegalArgumentException: Failed to bind properties under 'server'...
Caused by: ParserException: while parsing a block mapping; expected <block end>, but found '?'

 

프로세스 상태

docker ps  # 앱 컨테이너 Exited(1)​
 
 

타임라인(Timeline, 예시) 
 
 

21:10 — 모니터링에서 /actuator/health 실패 감지(5xx 스파이크)

21:12 — docker logs로 yml 바인딩 오류 확인

21:14 — 문제 PR 식별(application.yml 다발 수정 포함)

21:16PR Revert 결정(가역·신속 복구 우선)

21:20 — Revert PR 머지 → GHA 자동 재배포 시작

21:25 — 앱 기동 정상, Nginx 200 응답 확인

21:27 — 사건 종료, RCA/액션 항목 수립 착수

 
 

근본 원인(Root Cause Analysis)

 

표면 원인:

  • application.yml의 포트/CORS/DB/프로필 키 변경 중 문법/바인딩 불일치로 Spring Boot 기동 실패.
  • SnakeYAML 파서 단계 혹은 바인딩 단계에서 예외 발생. MediumSnyk

근본 원인: 설정 변경 검증 부재

  • yml 변경에 대한 정적/동적 검증이 파이프라인에 없었음
  • 리뷰/헬스체크 게이트가 yml/diff에 특화돼 있지 않음

카테고리: 구성관리/프로필/리뷰 프로세스 미흡

 
 
 

 
 
재현하기(Reproduction)

전제: 로컬 Docker, JDK 21, prod 프로필용 env 파일 존재

 
 
 
# 1) 문제 커밋 체크아웃
git checkout <problem-commit>

# 2) 로컬 빌드 & 실행(문제 yml로)
./gradlew bootJar
docker run --rm --name app -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -v $PWD/src/main/resources/application-prod.yml:/app/config/application-prod.yml \
  ghcr.io/your/app:<problem-tag>

# 3) 증상 확인
curl -i http://localhost:8080/actuator/health  # 기대: 실패/연결 종료
 
로컬/H2만 통과하는 허점:
운영 프로필(prod) yml이 실제 RDS/프록시 전제와 달라 로컬 성공, 운영 실패 가능.
 
 
 

 

해결(Resolution) & 롤백 전략(Rollback)
  • 실행
    1. GitHub에서 PR Revert(웹 UI Revert) 머지 → GHA 자동 재배포
    2. 배포 완료 후 헬스체크 통과 및 에러율 정상화 확인
    3. 문제 PR 재현/세분화 후, 스테이징에서 Forward Fix로 재반영 준비
    • 참고: GitHub PR Revert/git revert -m 1 <merge-commit>는 머지 커밋 되돌림의 표준 경로. GitHub DocsAtlassian
  • 선택 근거(왜 Revert?)
    • 장애 시 MTTR 최단화 + 가역적/감사 가능한 복구
    • 설정 다발 변경 상황에서 부분 핫픽스 리스크(누락/중복) 회피

Revert vs merge -s ours: ours는 충돌 해결용 전략으로 “내 브랜치 내용 채택”에 가깝고, 원복/추적성 측면에선 Revert가 적합.

 


왜 이게 통했나(Why it worked)
  • Revert는 직전 정상 상태로의 원자적 되돌림 → Nginx 업스트림(App) 응답이 즉시 복귀.
  • 문제는 네트워크가 아니라 애플리케이션 기동 실패였고, Revert로 올바른 yml 구성이 복원되며 파서/바인딩 예외가 제거됨.

검증(Verification) & 스모크 테스트

 

 

# Nginx -> App 경로
curl -i https://api-bytecookie.click/actuator/health

# 컨테이너 상태
docker ps
docker logs -n 200 app

# 리스닝 포트
ss -lntp | grep -E '80|8080'

# RDS 연결성(EC2에서)
telnet <rds-endpoint> 3306
  • Swagger /swagger-ui/index.html 응답 확인
  • 에러율/지표(5xx, 응답시간 P95) 정상화 대시보드 스크린샷 확보 → (내부 링크 예정)

재발 방지(Prevention) 체크리스트

 

정책

  • 운영 프로필(prod)은 spring.jpa.hibernate.ddl-auto: validate 고정(DDL은 마이그레이션 도구로만).
  • 운영 yml 변경은 머지 전 스테이징 기동 + 헬스 스크린샷 첨부 의무화.

도구/자동화

  • YAML 린트 + 스키마 바인딩 테스트:
    • yamllint/action-yamllint로 정적 검사
    • ConfigProperties 바인딩 테스트(Spring Boot test) 추가 — 잘못된 키/타입 시 실패.
  • 스모크 부트: CI에서 SPRING_PROFILES_ACTIVE=prod로 부트 드라이런(앱 15초 부팅/종료)
  • Secrets/Env diff 가드: GHA에서 필수 ENV 존재/형식 검증
  • SnakeYAML 최신 유지 + CVE 워치(Dependabot/Snyk) Java Code Geeks

리뷰 포인트

  • application-*.yml, Dockerfile, GHA 워크플로우 diff는 2인 승인 + 헬스 성공 증빙
  • Nginx 업스트림 포트/호스트와 Spring server.port 일치 재확인

모니터링/알림

  • /actuator/health 실패율, 5xx, 컨테이너 재시작 횟수 알림
  • 배포 후 5분 집중 모니터링 윈도우 운영

부록(Playbooks / Snippets)

 

 

Nginx 업스트림 포트 확인

sudo cat /etc/nginx/conf.d/app.conf  # upstream 포트 확인
sudo nginx -t && sudo systemctl reload nginx

 

docker logs에서 yml 파싱 에러 확인

docker logs app | grep -E "YAMLException|ParserException|Failed to bind"

 

PR Revert → 재배포

# 머지 커밋 기준 되돌림
git revert -m 1 <merge-commit>
git push origin main  # GHA 트리거

#여기서부턴 선택, 직전 정상 이미지 롤백
docker pull ghcr.io/org/app:<last_green>
docker run -d --name app -p 8080:8080 --env-file .env ghcr.io/org/app:<last_green>

 


관련 파일(Related Files)

  • src/main/resources/application-prod.yml (주석으로 변경 의도 명시)
  • /etc/nginx/conf.d/app.conf
  • .github/workflows/deploy.yml
  • Dockerfile
  • com.example.config.* (ConfigProperties 바인딩 클래스)

메타데이터(Metadata)

  • RACI
    • R(실행): 백엔드 온콜
    • A(책임): SRE/플랫폼
    • C(협의): 프론트, 기획
    • I(통지): PO, 경영지원
  • 링크: (내부 링크 예정) — 사건 티켓, Revert PR, 배포 로그, 대시보드
  • TAGS: #AWS #Nginx #SpringBoot #YAML #SnakeYAML #Docker #GitHubActions #RDS #CI_CD