Skip to content

[ci/cd] main에 머지되면 온프레미스 서버에 자동 배포한다 - #57

Open
yeondon125 wants to merge 2 commits into
mainfrom
add/onprem-cd
Open

yeondon125 wants to merge 2 commits into
mainfrom
add/onprem-cd

Conversation

@yeondon125

Copy link
Copy Markdown
Contributor

개요

배포를 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"로 등록했습니다. 이 키로는 배포 스크립트 말고 다른 명령이나 포트 포워딩을 할 수 없습니다.
  • Secrets: 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초. 외부 주소 200
    • 같은 커밋 다시 실행: 건너뜀
    • FORCE=1: 새 폴더에 다시 빌드한 뒤 교체
    • 응답 확인 주소를 일부러 틀리게 줘서 '새 버전이 응답하지 않음'을 만들었습니다. 이전 버전으로 되돌아가 다시 200으로 응답했습니다(실패한 폴더 정리도 이때 추가).
  • 배포 키로 whoami를 보내면 무시되고 배포 스크립트만 실행되는 것을 확인했습니다.
  • Actions → 서버 경로: 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

yeondon125 and others added 2 commits November 12, 2026 11:33
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>
@vercel

vercel Bot commented Nov 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontend Ready Ready Preview Nov 12, 2026 2:35am UTC

@yeondon125 yeondon125 added the enhancement New feature or request label Nov 12, 2026
Comment thread deploy/deploy.sh
flock 9

mkdir -p "$RELEASES"
SHA=$(git ls-remote "$REPO" "refs/heads/$BRANCH" | cut -c1-12)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[버그] 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행의 "아래 명령은 무시되지만"이라는 주석.

Comment thread README.md
| 배포 스크립트 | `/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` 링크를

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안] 폴더 이름이 스크립트와 다릅니다. deploy.sh는 같은 커밋을 다시 빌드할 수 있도록 releases/<시각>-<커밋>을 만듭니다(TARGET=$RELEASES/$(date +%Y%m%d-%H%M%S)-$SHA). 바로 아래 문단은 손으로 되돌릴 때 releases/의 폴더를 고르라고 안내하는데, 문서대로라면 실제 폴더를 찾기 헷갈립니다.

Suggested change
배포 스크립트는 `origin/main`을 `releases/<커밋>`에 따로 받아 `npm ci` · `npm run build`를 하고, 끝나면 `current` 링크를
배포 스크립트는 `origin/main`을 `releases/<시각>-<커밋>`에 따로 받아 `npm ci` · `npm run build`를 하고, 끝나면 `current` 링크를

@claude

claude Bot commented Nov 12, 2026

Copy link
Copy Markdown

main의 CI가 성공하면 workflow_run으로 온프레미스 서버의 배포 스크립트를 SSH로 실행하는 CD를 추가합니다. 배포 스크립트는 릴리스 폴더에 빌드하고 링크를 바꾼 뒤 재시작하며, 응답이 없으면 이전 버전으로 되돌립니다. systemd 설정과 README의 '배포' 절도 함께 들어 있습니다.

지적: 버그 1 · 제안 1

  • 머지 전에 고칠 것: [버그] deploy/deploy.sh가 CI가 검증한 커밋(workflow_run.head_sha)이 아니라 그 시점의 main 최신 커밋을 배포합니다. 커밋이 연달아 들어오면 CI가 아직 돌고 있거나 실패한 커밋이 배포될 수 있습니다.
  • [제안] README의 릴리스 폴더 이름(releases/<커밋>)이 스크립트와 다릅니다. 스크립트는 releases/<시각>-<커밋>을 만듭니다.

[확인 필요]

  • 서버의 ubuntu 계정이 비밀번호 없이 sudo systemctl restart gikipedia-frontend를 실행할 수 있어야 합니다. 서버의 sudoers 설정이라 저장소에서는 확인하지 못했습니다(PR 본문에는 직접 실행해 확인했다고 되어 있습니다).
  • 응답 확인을 /의 200으로 하는데, 루트 loading.tsx 때문에 응답이 스트리밍되어 렌더링 중 오류가 나도 200이 나갈 수 있습니다. 확인 범위는 '프로세스가 떠서 HTTP에 응답한다'까지로 보입니다. 실제 오류 화면에서 상태 코드가 어떻게 나가는지는 서버에서 띄워 보지 않아 확인하지 못했습니다.

리뷰 범위: 바뀐 파일 5개(.gitattributes, .github/workflows/cd.yml, README.md, deploy/deploy.sh, deploy/gikipedia-frontend.service)를 모두 리뷰했습니다. 건너뛴 파일은 없습니다.

This branch was successfully deployed

1 active deployment
Preview — 7fc25a40 Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant