diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b572bbd..3f79ebf0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -220,7 +220,9 @@ jobs: shared-infra/blocks/alloy/config.alloy, shared-infra/blocks/alloy/provision-alloy.sh, shared-infra/blocks/healthcheck.sh, - shared-infra/blocks/run_container.sh + shared-infra/blocks/run_container.sh, + shared-infra/blocks/slot_decide.sh, + shared-infra/blocks/slot_switch.sh target: "/tmp/piki-deploy" strip_components: 2 @@ -480,19 +482,14 @@ jobs: docker pull "$IMAGE" - # team3-upstream.conf를 source of truth로 현재 서빙 포트 결정 - # (없으면 8080 기본값 — team3-server가 8080에서 서비스 중인 초기 상태) - if [ -f /etc/nginx/team3-upstream.conf ]; then - ACTIVE_PORT=$(grep -oP '\d+' /etc/nginx/team3-upstream.conf | head -1) - else - ACTIVE_PORT=8080 - echo "server localhost:8080;" | sudo tee /etc/nginx/team3-upstream.conf - fi - if [ "$ACTIVE_PORT" = "8080" ]; then - ACTIVE="blue" INACTIVE="green" INACTIVE_PORT=8081 - else - ACTIVE="green" INACTIVE="blue" INACTIVE_PORT=8080 - fi + # 슬롯 판정은 infra 공용 블록(slot_decide)에 위임 - 상태 파일이 source of truth 이고 값(슬롯 + # 이름·포트)은 이 호출부가 소유한다. 상태 파일이 없으면 초기 상태(8080 서빙 가정)로 먼저 + # 초기화한다 (Apply nginx config 의 부트스트랩과 같은 기본값 - 종전 인라인 판정과 동작 등가). + # "할당 후 eval" 2단계는 블록 헤더가 정한 소비 계약이다 (치환 실패 은폐 방지). + [ -f /etc/nginx/team3-upstream.conf ] || echo "server localhost:8080;" | sudo tee /etc/nginx/team3-upstream.conf + DECIDED=$(bash /tmp/piki-deploy/slot_decide.sh --state-file /etc/nginx/team3-upstream.conf \ + --slot-a blue:8080 --slot-b green:8081) + eval "$DECIDED" echo "================================================" echo " CURRENT : $ACTIVE (port $ACTIVE_PORT) [serving traffic]" echo " DEPLOY : $INACTIVE (port $INACTIVE_PORT) [new version]" @@ -574,13 +571,10 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} script: | - # team3-upstream.conf에서 현재 서빙 포트 읽기 (source of truth) - ACTIVE_PORT=$(grep -oP '\d+' /etc/nginx/team3-upstream.conf | head -1) - if [ "$ACTIVE_PORT" = "8080" ]; then - ACTIVE="blue" INACTIVE="green" INACTIVE_PORT=8081 - else - ACTIVE="green" INACTIVE="blue" INACTIVE_PORT=8080 - fi + # 슬롯 재판정 - 배포 스텝과 같은 공용 블록·같은 값으로 읽는다 (상태 파일이 source of truth) + DECIDED=$(bash /tmp/piki-deploy/slot_decide.sh --state-file /etc/nginx/team3-upstream.conf \ + --slot-a blue:8080 --slot-b green:8081) + eval "$DECIDED" # 새로 뜬 컨테이너(INACTIVE) 헬스체크는 infra 공용 블록(blocks/healthcheck.sh)에 위임한다. # 간격·횟수는 호출부가 소유한다 (블록은 값을 갖지 않는다). 왜 300초(60×5s)인가: @@ -592,9 +586,17 @@ jobs: echo "================================================" echo " HEALTH CHECK PASSED on $INACTIVE" echo "================================================" - # Nginx upstream 전환 - echo "server localhost:$INACTIVE_PORT;" | sudo tee /etc/nginx/team3-upstream.conf - sudo nginx -s reload + # 전환은 infra 공용 블록(slot_switch)에 위임 - 갱신·nginx -t·reload(정지 시 restart 폴백)와 + # 실패 시 이전 upstream 원복까지 블록의 원복 경계 안에서 돈다. 종전 인라인(tee + reload)에는 + # 원복이 없어 전환 실패 시 상태 파일과 실제 서빙이 어긋난 채 남았고, 그 괴리는 다음 배포가 + # 서빙 중인 슬롯을 비활성으로 오판·제거하게 만들었다 (infra#37 에서 실사고로 실증된 결함의 해소). + if ! bash /tmp/piki-deploy/slot_switch.sh \ + --state-file /etc/nginx/team3-upstream.conf --server "localhost:$INACTIVE_PORT"; then + echo "슬롯 전환 실패(이전 upstream 원복됨) - 새 슬롯 정리 후 중단" + timeout 40 docker stop -t 30 "piki-core-$INACTIVE" 2>/dev/null || true + timeout 30 docker rm -f "piki-core-$INACTIVE" 2>/dev/null || true + exit 1 + fi echo " SWITCHED : Nginx now → $INACTIVE (port $INACTIVE_PORT)" # 구버전 종료 (레거시 이름 team3-$ACTIVE·team3-server 포함 — 컨테이너명 개편(#743) 전환기와 # 그 이전 마이그레이션 잔재. 없으면 no-op 라 유지 비용 없음) — switch 는 이미 끝났으므로 best-effort.