Repository navigation
[ci/cd] main에 머지되면 온프레미스 서버에 자동 배포한다 - #57
yeondon125 wants to merge 2 commits into
Conversation
Vercel은 rewrites로 WebSocket 업그레이드를 넘기지 않아 공동편집이 되지 않아 gsmsv VM으로 옮겼고, 그동안은 서버에서 손으로 배포 스크립트를 돌렸다. CI가 통과한 main 커밋을 배포 전용 키로 접속해 배포한다. - deploy/deploy.sh: origin/main을 releases/<시각>-<커밋>에서 빌드하고 current 링크를 바꿔 재시작한다. 빌드가 실패하면 링크를 바꾸지 않고, 재시작 뒤 응답이 없으면 이전 버전으로 되돌린다 - deploy/gikipedia-frontend.service: current를 실행하는 systemd 설정 - 서버는 배포 키에 이 스크립트만 허용한다(restrict,command=). Actions는 호스트 키 지문도 확인한다 - .gitattributes: deploy/는 서버에서 bash로 실행하므로 항상 LF로 받는다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
개발 서버의 redirect_uri가 배포 주소로 바뀌어 localhost에서는 로그인이 끝까지 되지 않는다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| flock 9 | ||
|
|
||
| mkdir -p "$RELEASES" | ||
| SHA=$(git ls-remote "$REPO" "refs/heads/$BRANCH" | cut -c1-12) |
There was a problem hiding this comment.
[버그] CI가 통과한 커밋 대신 그 시점의 main 최신 커밋을 배포합니다. 그래서 cd.yml의 "CI가 실패한 커밋 … 은 배포하지 않습니다"가 지켜지지 않습니다.
상황: 커밋 A와 B가 짧은 간격으로 main에 들어갑니다(PR 두 개를 연달아 머지하는 경우 등). A의 CI가 먼저 성공해 workflow_run이 뜨면, 이 줄은 ls-remote로 B를 읽어 배포합니다. B의 CI는 아직 도는 중이거나 이미 실패했을 수 있습니다. 서버에서 다시 하는 검사는 npm run build뿐이라, lint·typecheck에서 깨진 B도 그대로 나갑니다. 나중에 B의 CI가 실패해도 이미 돌고 있는 B를 되돌리지 않습니다.
ls-remote(26행)와 clone --branch(39행) 사이에 push가 들어오는 경우도 있습니다. 그러면 폴더 이름의 SHA와 실제 빌드된 내용이 달라집니다.
수정 방안: CI가 검증한 SHA를 서버로 넘기세요. command=로 고정된 키여도 클라이언트가 보낸 명령은 SSH_ORIGINAL_COMMAND로 받을 수 있습니다.
cd.yml:script: /srv/gikipedia-frontend/deploy.sh ${{ github.event.workflow_run.head_sha }}(workflow_dispatch이면 빈 값)deploy.sh:WANT=${SSH_ORIGINAL_COMMAND##* } # 마지막 인자 if [[ "$WANT" =~ ^[0-9a-f]{40}$ ]] && [ "${WANT:0:12}" != "$SHA" ]; then echo "검증된 $WANT 는 main 최신이 아니다. 최신 커밋의 CI가 끝나면 그쪽이 배포한다"; exit 0 fi
SSH_ORIGINAL_COMMAND는 검증된 값만 받고 셸로 실행하지 않습니다. 이렇게 하면 CI를 통과한 커밋이main최신일 때만 배포됩니다. clone한 뒤에는git rev-parse HEAD가$SHA로 시작하는지 확인해 위의 경쟁 상황도 막습니다.
함께 고칠 곳: cd.yml 46행의 "아래 명령은 무시되지만"이라는 주석.
| | 배포 스크립트 | `/srv/gikipedia-frontend/deploy.sh` (`/srv/deploy.sh`는 이 파일로 가는 링크) | | ||
| | 환경 변수 | `/srv/gikipedia-frontend/shared/.env.local` (`API_BASE_URL`). 배포 때마다 빌드 폴더로 복사된다 | | ||
|
|
||
| 배포 스크립트는 `origin/main`을 `releases/<커밋>`에 따로 받아 `npm ci` · `npm run build`를 하고, 끝나면 `current` 링크를 |
There was a problem hiding this comment.
[제안] 폴더 이름이 스크립트와 다릅니다. deploy.sh는 같은 커밋을 다시 빌드할 수 있도록 releases/<시각>-<커밋>을 만듭니다(TARGET=$RELEASES/$(date +%Y%m%d-%H%M%S)-$SHA). 바로 아래 문단은 손으로 되돌릴 때 releases/의 폴더를 고르라고 안내하는데, 문서대로라면 실제 폴더를 찾기 헷갈립니다.
| 배포 스크립트는 `origin/main`을 `releases/<커밋>`에 따로 받아 `npm ci` · `npm run build`를 하고, 끝나면 `current` 링크를 | |
| 배포 스크립트는 `origin/main`을 `releases/<시각>-<커밋>`에 따로 받아 `npm ci` · `npm run build`를 하고, 끝나면 `current` 링크를 |
|
지적: 버그 1 · 제안 1
[확인 필요]
리뷰 범위: 바뀐 파일 5개( |
개요
배포를 Vercel에서 온프레미스 서버(
https://gikipedia.https.gsmsv.site, gsmsv VM)로 옮겼습니다. Vercel은 rewrites로 WebSocket 업그레이드를 넘기지 않아(헤더를 떼서 핸드셰이크가 400) 공동편집과 문서 수정이 되지 않았습니다. 그동안은 서버에서 배포 스크립트를 손으로 돌렸는데, 이 PR로main에 머지되면 CI가 통과한 뒤 자동으로 배포됩니다. 백엔드 저장소의 CD와 같은 방식(appleboy/ssh-action, 저장소 Secrets)입니다.변경 사항
.github/workflows/cd.yml:main에 대한 CI가 성공하면(workflow_run) 배포 전용 키로 서버에 접속해 배포 스크립트를 실행합니다. Actions 탭에서 손으로 실행할 수도 있습니다. 배포는 하나씩만 돌고 앞의 것을 취소하지 않습니다. 호스트 키 지문도 확인합니다.deploy/deploy.sh: 서버의 배포 스크립트 원본입니다.origin/main을releases/<시각>-<커밋>에 따로 받아 빌드하고, 끝나면current링크를 바꾼 뒤 재시작합니다. 빌드하는 동안에도 사이트는 이전 버전으로 돕니다.FORCE=1이면 다시 빌드). 최근 3개 버전만 남기고,flock으로 동시 실행을 막습니다.deploy/gikipedia-frontend.service:current를 실행하는 systemd 설정입니다..gitattributes:deploy/는 서버에서 bash로 실행하므로 Windows에서 받아도 LF로 둡니다.README.md: 배포 절을 추가했습니다. 개발 서버의 로그인 주소가 배포 주소로 바뀌어localhost에서는 로그인이 끝까지 되지 않는다는 것도 고쳤습니다.서버와 저장소 설정은 이미 해 두었습니다(PR 밖).
authorized_keys에restrict,command="/srv/gikipedia-frontend/deploy.sh"로 등록했습니다. 이 키로는 배포 스크립트 말고 다른 명령이나 포트 포워딩을 할 수 없습니다.DEPLOY_SSH_HOST·DEPLOY_SSH_PORT·DEPLOY_SSH_USERNAME·DEPLOY_SSH_KEY·DEPLOY_SSH_FINGERPRINT. 지문은 ECDSA 호스트 키 값입니다. Actions의 SSH 라이브러리(Go)가 ED25519보다 ECDSA를 먼저 골라서, ED25519 지문으로는host key fingerprint mismatch가 났습니다.확인 사항
/srv/frontend→ 새 구조): 빌드, 교체, 재시작까지 31초. 외부 주소 200FORCE=1: 새 폴더에 다시 빌드한 뒤 교체whoami를 보내면 무시되고 배포 스크립트만 실행되는 것을 확인했습니다.workflow_run은main에 있어야 동작해서, 임시 브랜치(test/cd-dry-run, 삭제함)에 push 트리거를 잠깐 넣어 같은 job을 돌렸습니다. Secrets로 접속해 서버에서 "이미 bb860b7 가 배포되어 있다"로 성공했습니다.workflow_run으로 실제 배포가 시작되는 것은 머지해야 확인됩니다. 머지 후 Actions의 CD 실행을 보겠습니다.체크리스트
CLAUDE.md의 구조·스타일 규칙을 지켰습니다. (소스 코드 변경 없음)npm run lint,npm run typecheck,npm run build가 통과합니다. (소스 변경 없음, 서버 배포 빌드로 확인)CLAUDE.md와README.md를 고쳤습니다.🤖 Generated with Claude Code