Skip to content

FIX : 실제로 도는 배포로 교체 (라우터 API 의존 제거, SSH 키 로드) - #97

Merged
RosieOh merged 2 commits into
mainfrom
fix/deploy-pipeline-ssh-and-alerts
Aug 30, 2026
Merged

FIX : 실제로 도는 배포로 교체 (라우터 API 의존 제거, SSH 키 로드)#97
RosieOh merged 2 commits into
mainfrom
fix/deploy-pipeline-ssh-and-alerts

Conversation

@RosieOh

@RosieOhRosieOh commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

🏫 관련 이슈

#94

시크릿 값과 서버 준비는 저장소 밖이라 이 PR 로 닫히지 않는다. 아래 "머지 후 할 일" 참고.

🏫 PR 타입

  • 기능 추가
  • 기능 수정
  • 기능 삭제
  • 버그 수정
  • 의존성, 환경 변수, 빌드 관련 코드 업데이트

🏫 반영 브랜치

fix/deploy-pipeline-ssh-and-alerts -> main

🏫 변경 사항

main 에 머지해도 배포가 되지 않는다. #78, #80, #96 세 번 모두 실패했다.
시크릿 미설정이 눈에 보이는 원인이지만, 채워도 완주할 수 없는 구조였다.

1. 존재하지 않는 라우터 API 에 의존하고 있었다 ⭐

Blue/Green 전환의 마지막 단계가 HTTP 엔드포인트 두 개를 호출한다.

CURRENT_ENV=$(curl -fsS "$ROUTER_STATUS_URL" || echo "blue")curl -X POST "$ROUTER_SWITCH_URL" -H "Authorization: Bearer $ROUTER_TOKEN" ...

이 API 를 제공하는 구현이 조직 어디에도 없다.
별도 저장소의 블루/그린 도구 CareCode_Nohub_Deploy

requirements.txt: requests, python-dotenv, paramiko ← 웹 프레임워크 없음
docker_ops.py: subprocess.run(["docker-compose", ...]) ← 서버에서 로컬 실행

paramiko 로 서버에 붙어 nginx conf 를 고치는 CLI 다. HTTP 서버가 아니다.

더 나쁜 건 실패 지점이다. 시크릿을 다 채우면 Deploy 단계가 성공해서 서버 컨테이너를
이미 갈아치운 뒤
Switch traffic 에서 멈춘다. 새 컨테이너는 떠 있는데 트래픽은
넘어가지 않은 어중간한 상태로 끝난다.

→ 검증 후 교체 방식으로 바꿨다.

새 이미지 pull
→ 예비 포트(127.0.0.1:18082)에서 먼저 기동 운영 컨테이너는 그대로
→ /actuator/health 가 UP 될 때까지 대기 (최대 200초)
└ 실패 → 컨테이너 로그 100줄 출력 후 중단 운영은 무사
→ 통과 → 교체 (여기서 짧은 순단)
→ 재확인 → 외부 URL 로 최종 확인

무중단은 잃지만 깨진 이미지가 운영에 올라가지 않는다는 성질은 유지된다.
진짜 무중단은 nginx 를 제어할 수 있을 때 되돌리면 된다.

2. SSH 개인키를 올리는 스텝이 아예 없었다 ⭐

ssh -o StrictHostKeyChecking=no ${{ secrets.PRODUCTION_DEPLOY_USER }}@...

키를 로드하는 곳이 워크플로 어디에도 없다. 시크릿을 전부 채워도 인증에서 막힌다.
키를 env 로 받아 ~/.ssh/deploy_key 에 쓰고 -i 로 지정한다.
호스트키는 *_SSH_KNOWN_HOSTS 가 있으면 고정, 없으면 ssh-keyscan + 경고.
StrictHostKeyChecking=no 는 제거했다.

3. 레지스트리 로그인이 없었다

서버가 GHCR 에서 이미지를 받으려면 로그인이 필요할 수 있다.
잡 토큰을 stdin 으로 흘려보낸다. ssh 인자로 주면 서버 프로세스 목록에 그대로 보인다.

4. 무엇이 없는지 알 수 없었다

test -n "${{ secrets.X }}" 는 실패 이유를 남기지 않는다.
로그에 PRODUCTION_HEALTH_URL: 이 공란으로 찍히는 게 전부였다.

::error::운영 배포에 필요한 시크릿이 없습니다: PRODUCTION_DEPLOY_HOST PRODUCTION_SSH_KEY

5. 배포 실패가 아무에게도 닿지 않았다

성공이든 실패든 echo "Production deployment completed" 하나였다.
잡 요약에 남기고 실패는 ::error:: 로 올린다. OPS_SLACK_WEBHOOK_URL 이 있으면 슬랙으로도 보낸다.

6. 그 밖

  • 시크릿을 run: 본문에 ${{ secrets.* }} 로 직접 박아 쓰던 것을 전부 env:
  • 전역 env 의 헬스 URL 제거 — test/scan 잡 환경에까지 실릴 이유가 없다
  • 컨테이너에 --restart unless-stopped — 서버 재부팅에도 살아남는다
  • 예전 carecode-blue / carecode-green 이 남아 포트를 잡고 있으면 교체가 실패하므로 함께 정리
  • 외부 확인 실패는 메시지를 구분 — 서버 안에서 UP 인데 밖에서 안 보이면 프록시/보안그룹 문제다

필요한 시크릿이 8개 → 4개

이전지금
_DEPLOY_HOST_DEPLOY_USER_HEALTH_URL_ROUTER_STATUS_URL_ROUTER_SWITCH_URL_ROUTER_TOKEN_TARGET_HEALTH_URL_TEMPLATE (+ 없던 SSH 키)_DEPLOY_HOST_DEPLOY_USER_SSH_KEY_HEALTH_URL

🏫 테스트 결과

워크플로는 CI 에서 직접 돌려볼 수 없어 정적으로 확인했다.

YAML 파싱 OK
deploy-production -> 7 steps (기존 7, 내용 전면 교체)
deploy-staging -> 6 steps (기존 3)
run 블록 16개 + 원격 배포 스크립트 → bash -n 전부 OK

앱 코드는 건드리지 않았으므로 기존 테스트 380건은 그대로다.

🏫 머지 후 할 일 (#94)

저장소 밖이라 여기서 할 수 없다.

시크릿 4개

gh secret set PRODUCTION_DEPLOY_HOST --repo CareCode-Repo/CareCode_Interface
gh secret set PRODUCTION_DEPLOY_USER --repo CareCode-Repo/CareCode_Interface
gh secret set PRODUCTION_SSH_KEY --repo CareCode-Repo/CareCode_Interface <~/.ssh/id_ed25519
gh secret set PRODUCTION_HEALTH_URL --repo CareCode-Repo/CareCode_Interface

서버

준비되면 Actions 에서 workflow_dispatchproduction 으로 한 번 돌려보면 된다.

🏫 To Reviewer

머지해도 배포가 되지 않는 상태였다. 저장소 쪽에서 고칠 수 있는 부분을 정리한다.
(시크릿 값 자체는 저장소 밖이라 #94 에 남겨둔다.)
1) SSH 개인키를 올리는 스텝이 아예 없었다.
ssh -o StrictHostKeyChecking=no 로 바로 접속하고 있어서, 시크릿을 채워도
인증 단계에서 막힌다. 키를 env 로 받아 ~/.ssh 에 쓰고 -i 로 지정한다.
호스트키는 *_SSH_KNOWN_HOSTS 가 있으면 고정하고, 없으면 keyscan 으로
대체하되 경고를 남긴다.
2) 시크릿이 없을 때 무엇이 없는지 알 수 없었다.
각 스텝의 test -n "..." 는 실패 이유를 남기지 않는다. 잡 시작 시점에
필요한 이름을 전부 확인하고, 빠진 것을 이름으로 찍어 실패시킨다.
3) 롤백이 트래픽 전환 전에도 돌았다.
조건이 failure() 뿐이라, 배포·헬스체크에서 죽어 트래픽이 움직인 적도 없는데
롤백을 시도했고 그 스텝마저 실패해 로그에 실패가 두 번 찍혔다.
steps.switch.outcome == 'success' 를 함께 본다.
4) 알림이 echo 뿐이라 배포 실패가 아무에게도 닿지 않았다.
잡 요약에 남기고, 실패는 ::error:: 로 올린다.
OPS_SLACK_WEBHOOK_URL 이 있으면 슬랙으로도 보낸다.
5) 시크릿을 run 본문에 ${{ secrets.* }} 로 직접 박아 쓰던 것을 env 로 옮겼다.
값이 스크립트 텍스트에 들어가면 셸 메타문자에 취약하다.
6) 전역 env 의 헬스 URL 을 제거했다. 배포와 무관한 test/scan 잡 환경에까지
값이 실릴 이유가 없다. 필요한 스텝만 시크릿을 받는다.
모든 run 블록을 bash -n 으로 문법 검사했다.
Blue/Green 전환의 마지막 단계가 라우터 HTTP API 두 개를 호출했다.
PRODUCTION_ROUTER_STATUS_URL 현재 활성 색 조회
PRODUCTION_ROUTER_SWITCH_URL 트래픽 전환
이 API 를 제공하는 구현이 조직 어디에도 없다. 별도 저장소의 블루/그린 도구
(CareCode_Nohub_Deploy)는 paramiko 로 서버에 붙어 nginx conf 를 고치는 CLI 이고
의존성이 requests/python-dotenv/paramiko 뿐이다. 웹 서버가 아니다.
그래서 시크릿을 다 채워도 전환 단계에서 반드시 멈췄다. 게다가 그 시점에는 이미
Deploy 단계가 서버의 컨테이너를 갈아치운 뒤라, 새 컨테이너는 떠 있는데 트래픽은
넘어가지 않은 어중간한 상태로 끝났다.
무중단은 nginx 를 제어할 수 있어야 성립한다. 그때까지는 검증 후 교체로 간다.
새 이미지 pull
→ 예비 포트(127.0.0.1:18082)에서 먼저 기동 운영 컨테이너는 그대로
→ 헬스체크. 실패하면 로그를 남기고 중단 운영은 건드리지 않음
→ 통과하면 교체 (짧은 순단)
→ 재확인 → 외부 URL 로 최종 확인
깨진 이미지가 운영에 올라가지 않는다는 성질은 유지하고 순단만 감수한다.
검증 포트는 예전 blue/green 의 8083 과 겹치지 않게 18082 로 잡았다.
함께 정리한 것
- 레지스트리 로그인을 추가했다. 토큰은 stdin 으로 넘긴다.
ssh 인자로 주면 서버의 프로세스 목록에 그대로 보인다.
- 필요한 시크릿이 8개에서 4개로 줄었다.
HOST / USER / SSH_KEY / HEALTH_URL 이면 된다.
- 컨테이너에 --restart unless-stopped 를 붙였다. 서버가 재부팅돼도 살아난다.
- 예전 워크플로가 만들던 carecode-blue / carecode-green 이 남아 포트를 잡고 있으면
교체가 실패하므로 함께 정리한다.
- 외부 확인 실패는 메시지를 따로 준다. 서버 안에서는 UP 인데 밖에서 안 보이면
프록시나 보안그룹 문제지 애플리케이션 문제가 아니다.
라우터 방식으로 돌아가려면 전환 API 를 실제로 만든 뒤에 되돌리면 된다.
워크플로 run 블록 16개와 원격 스크립트를 bash -n 으로 문법 검사했다.
@RosieOhRosieOh changed the title FIX : 배포 잡에 SSH 키 로드와 실패 알림 추가FIX : 실제로 도는 배포로 교체 (라우터 API 의존 제거, SSH 키 로드)Aug 24, 2026
@RosieOh
RosieOh merged commit 4cb9a4f into mainAug 30, 2026
7 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@RosieOh