Infra

401을 성공으로 취급하는 헬스체크 — 배포 스크립트를 서브커맨드 오케스트레이터로 재설계

훅: 헬스체크가 401을 실패로 보고 있었다

기존 헬스체크 동작

  • 앱 재시작 후 /monitor/health 호출
  • 응답이 오면 성공 처리
until "${SSH_CMD[@]}" "$REMOTE_USER@$REMOTE_HOST" \
  "curl -fsS 'http://127.0.0.1:$APP_PORT$HEALTH_ENDPOINT' >/dev/null"; do
  ...
done
  • curl -f — 4xx/5xx를 실패로 처리
  • 해당 엔드포인트 — Spring Security 인증이 걸려 있어 401 반환
  • 즉, 애플리케이션이 완전히 정상 기동한 상태에서 401

401이 실제로 증명하는 것

  • 요청이 애플리케이션에 도달
  • 서블릿 컨테이너 생존
  • 필터 체인 동작

  • 헬스체크가 알고 싶은 것 — “앱이 떴는가” — 은 이미 충족
  • 인증 실패 여부는 그 다음 층위의 얘기

200만이 성공이라고 가정한 게 문제였다.

  • 헬스체크의 질문 — “이 앱이 요청을 처리할 수 있는가”
  • 헬스체크의 질문이 아닌 것 — “내가 이 리소스에 접근할 권한이 있는가”

조치 — 성공으로 인정할 코드를 목록화

HEALTH_SUCCESS_CODES="${HEALTH_SUCCESS_CODES:-200,401}"

until false; do
  HTTP_CODE="$(... "curl -s -o /dev/null -w '%{http_code}' 'http://127.0.0.1:$APP_PORT$HEALTH_ENDPOINT' || true")"
  if [[ ",$HEALTH_SUCCESS_CODES," == *",$HTTP_CODE,"* ]]; then
    echo "헬스체크 성공: HTTP $HTTP_CODE"
    break
  fi
  echo "헬스체크 대기중: HTTP $HTTP_CODE"
  ...
done

같이 고친 것

  • 기존 타임아웃 동작 — “수동 점검 필요” 출력 후 exit 1. 실패한 배포본이 서버에 그대로 올라간 채 사람을 기다리는 상태
  • 변경 — 타임아웃 시 rollback 즉시 호출

자동 롤백이 없는 자동 배포는 절반만 자동이다.

  • curl 실패로 코드를 못 읽는 경우 대비 || true 추가
  • 이유: set -euo pipefail 아래에서는 curl 실패가 스크립트 전체를 죽여 롤백 로직에 미도달

더 큰 문제: 스크립트가 두 벌이었다

  • deploy.sh 존재
  • scripts/deploy/deploy-to-web2.sh — 266줄짜리가 별도 존재
  • 두 파일이 SSH 접속·빌드·전송·재시작을 각자의 방식으로 중복 구현

  • 이 상태에서 헬스체크 수정 시 한쪽만 반영
  • 실증 — 위의 401 수정도 한쪽에만 적용된 상태

어떻게 고쳤나: 서브커맨드 오케스트레이터

deploy.sh를 진입점 하나로 통일 + 실제 로직은 파일로 분리

deploy.sh <backend | frontend admin|user|all | all>
   │
   ├── scripts/deploy/_common.sh     SSH 실행, 로그 출력
   ├── scripts/deploy/_backend.sh    빌드 → 전송 → 헬스체크 → 롤백
   └── scripts/deploy/_frontend.sh   git 사전 검증 → 원격 pull/build/restart
  • _ 접두사 — “직접 실행하는 파일이 아니라 source되는 라이브러리”라는 표시
  • 진입점은 deploy.sh 하나뿐이라는 걸 파일명만 보고 판별 가능

분리 기준 — 배포 대상이 달라지면 절차가 달라진다

대상 절차
백엔드 로컬에서 jar 빌드 → 전송
프론트엔드 원격에서 git pull → 빌드
  • 공통은 SSH와 로그뿐 → _common.sh는 54줄로 작음. 억지로 더 공통화하지 않음
  • 집계 — 396줄 삭제, 418줄 추가

  • 총량은 거의 동일
  • 얻은 것 — 중복 한 벌 제거 + 파일 경계 확보

프론트엔드에는 git 사전 검증을 붙였다

  • FE 배포 방식 — 원격에서 git pull 후 빌드
  • 함정 — 로컬에서 아무리 확인해도, 푸시하지 않은 커밋은 서버에 안 감
로컬: 수정하고 확인 → "잘 되네" → 배포 실행
원격: git pull → 변경 없음 → 이전 코드 그대로 빌드
결과: 배포는 성공했는데 아무것도 안 바뀜

배포 전 검사 3종

검사 막는 상황
uncommitted 변경 커밋 안 한 수정이 반영 안 됨
브랜치 확인 다른 브랜치에서 배포
unpushed 커밋 로컬에만 있는 커밋이 반영 안 됨
  • 셋 다 “배포는 성공하는데 결과가 틀린” 부류의 실패
  • 에러 발생 시 — 인지 가능
  • 이 부류 — 조용히 통과
  • 결론: 사전 검증으로 막는 게 값이 큼

남는 교훈

헬스체크는 무엇을 확인하는지 정확히 정의해야 한다.

  • “200이면 정상” — 정의가 아니라 가정
  • 이 엔드포인트에서 앱 생존의 증거 = 401
  • 성공 조건의 분기점 — 프로세스 생존 / 요청 처리 가능성 / 의존 서비스 연결 중 무엇을 보느냐

자동 배포의 완결 조건은 자동 롤백이다.

  • 실패 감지 후 사람 호출 vs 실패 감지 후 되돌리기
  • 둘의 격차 = 장애 지속 시간 전부

중복된 스크립트는 수정을 반쪽으로 만든다.

  • 증거 — 401 수정이 한쪽에만 반영된 사실
  • 스크립트 두 벌 → 버그도 두 벌
  • 고치는 사람이 아는 것 — 한 벌뿐

조용한 실패를 사전 검증으로 바꾼다.

  • unpushed 커밋 배포 — 에러 0건, 결과만 오답
  • 이 부류는 사후 감지 난이도 높음
  • 유일하게 싼 방법 — 앞에서 차단