최근 프로젝트 개발 단계를 마무리하고 이후 고도화 단계를 거치며 추가적인 학습을 이어나가고 있습니다. 해당 과정에서 vercel의 수동 배포의 불편함
을 느끼게 되었고 개선을 위해 서칭 중 CI/CD 구축 과정에 대해 알게 되었습니다.
CI/CD 구축을 통한 서비스 배포 자동화 과정에 대해 자세히 알아보고 겪은 트러블 슈팅들을 공유해보려 합니다.
CI/CD
는 지속적 통합(Continuous Integration)
/ 지속적 배포(Continuous Deployment)
의 약자로 소프트웨어 개발 프로세스를 자동하여 더 빠르고 안정적으로 서비스를 제공할 수 있게 합니다.
CI(지속적 통합)
는 개발자가 서비스의 버그 수정이나 새로운 코드 변경을 진행하여 레포지토리에 병합하는 과정에서 주기적으로 빌드
와 테스트
가 자동으로 수행되면서 통합되는 것을 의미합니다. 이 과정을 통해 코드 변경 사항에 대한 버그를 조기에 발견하고 해결할 수 있습니다. CI는 한마디로 빌드 및 테스트 자동화 과정이라 얘기할 수 있습니다.
CD(지속적 배포)
는 CI 과정을 통해 테스트를 통과한 코드를 자동으로 배포하는 단계를 얘기합니다. 빌드와 테스트가 자동으로 수행되고 문제가 없다면 배포를 통해 실제 서비스에 자동으로 변경사항을 적용합니다. CD는 한마디로 배포 자동화 과정이라 얘기할 수 있습니다.
CI/CD 파이프라인 구축은 개발 및 배포 과정에서 오류를 최소화 하여 서비스의 안정성을 도모할 수 있고 팀원간 개발 효율성도 극대화할 수 있기에 최근 소프트웨어 개발에서 필수적인 요소로 자리잡고 있습니다.
CI/CD 파이프라인을 구축할 때 사용할 수 있는 툴들은 Jenkins
, Travis CI
, CircleCI
, GitHub Actions
등이 있습니다. 각자의 장단점과 특징이 있는데 요구 사항 및 서비스를 고려하여 결정해야 합니다. 각 툴들을 간단히 알려드리겠습니다.
Jenkins
는 소프트웨어 빌드, 테스트 제공 및 배포와 관련된 모든 종류의 작업을 자동화하는데 사용할 수 있는 독립형 오픈 소스 자동화 서버입니다.
다양한 플러그인을 제공하기 때문에 확장성이 뛰어나고 복잡한 파이프라인을 구성할 수 있지만, 설정과 관리가 상대적으로 복잡하기에 소규모 프로젝트 경우 리소스와 비용 낭비가 발생할 수 있습니다.
Travis CI
는 클라우드 기반 오픈 소스 CI/CD 서비스로, 설정이 간단하고 Github와의 편한 연동으로 소규모 프로젝트나 간단한 파이프라인 구축에 적합합니다.
Travis가 서버를 운영해주고 Github 연동이 편하고 다양한 레퍼런스가 있지만 앞서 언급한 Jenkins에 비해 플러그인 종류가 적고 유료 서비스의 가격이 비싸다는 단점이 있습니다.
CircleCI
는 Travis와 마찬가지로 클라우드 기반 CI/CD 서비스로 VCS(Version Control System)와 연동하여 빌드, 테스트 및 배포 자동화를 지원합니다.
클라우드에서 직접 서버를 운영하고 호스팅해주기 때문에 관리 부담이 적고 빠른 빌드와 테스트 속도를 제공하지만, 무료 플랜이 제한적이며 높은 트래픽이나 대규모 프로젝트에는 추가 비용이 많이 발생한다는 단점이 있습니다.
GitHub Actions
은 GitHub에서 제공하는 CI/CD 서비스로 코드 저장소와 직접 통합되어 사용자가 작성한 workflow 파일 기반으로 빌드, 테스트, 배포 등의 작업을 자동화할 수 있습니다.
GitHub에서 제공하는 서비스이기 때문에 직접 연동되어 쉬운 설정 및 관리가 용이하다는 장점이 있지만, 초심자에게는 초기 설정이 다소 복잡할 수 있으며 무료 플랜에서는 제한된 자원과 기능만을 제공하기에 대규모 프로젝트에서는 추가 요금이 발생한다는 단점이 있습니다.
우선 저는 CI/CD 구축 툴로 GitHub Actions
을 선택했습니다. 개인적으로는 러닝 커브가 가장 낮기도 하고 상업용 서비스도 아닌 소규모 팀 프로젝트이기 때문에 GitHub Actions을 선택했습니다. 특히 storybook, vercel CI/CD 구축 작업은 간단하기도 하며 레퍼런스도 많고 GitHub Actions에서 제공하는 마켓플레이스 툴들도 적합한게 많았기 때문입니다.
❗️해당 서비스는 Next.js, TypeScript, Yarn berry 기반 프로젝트입니다.
우선 서비스 CI 자동화를 먼저 진행하기로 했습니다. 현재 프로젝트 브랜치 전략은 다음과 같습니다.
feat -> develop -> main
feat 브랜치에서 작업을 마친 후 develop 브랜치로 PR을 요청하며 빌드 및 테스트에 문제가 없으면 develop에 머지 되고 최종적으로 main에 병합되어 배포되는 방식입니다.
전체적인 시나리오를 생각해봤을 때 PR이 작성되고 merge가 이루어지기 전까지 변경 사항은 계속 생길 것이고 merge 전까지는 배포가 이루어지면 안됩니다. 하지만 PR 과정에서도 서비스를 계속 확인할 수 있는 배포 단계 또한 필요하다고 생각했고 Preview
단계와 Production
단계의 CI/CD를 따로 구축하기로 결정했습니다.
공식 문서에서 제공하는 vercel preview
배포 workflows는 다음과 같습니다. 단순하게 프로젝트에 vercel 설치 후 환경 변수를 작성하고 자동으로 배포가 이루어지는 식입니다.
name: Vercel Preview Deployment
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
push:
branches-ignore:
- main
jobs:
Deploy-Preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy Project Artifacts to Vercel
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
저는 preview CI/CD 시나리오를 생각했을 때 다음과 같은 3번의 단계로 나누어 작성하기로 구상했습니다.
프로젝트 빌드, 테스트
-> Vercel Preview 배포
-> PR에 Preview 링크 및 시간 Comment 작성
공식 문서에서는 2번째 과정인 vercel preview 배포 코드만 제공하는 것이고 해당 과정에서 빌드가 이루어지기도 하고 빌드가 실패하면 배포가 이루어지지 않습니다.
다만 빌드와 테스트 과정은 배포와 따로 이루어져야 한다고 생각했고 빌드 및 테스트가 문제 없을 때 배포가 진행되어야 한다고 생각했고 그 이후 배포 링크가 자동으로 팀원에게 보여지면 DX
관점에서 좋을 거 같다 생각하여 위 단계와 같이 구성했습니다.
우선 프로젝트 루트 기준 .github/workflows
경로에 ci-preview.yml
파일을 생성한 후 다음과 같이 작성해주었습니다.
# ci-preview.yml
name: Preview CI
on:
pull_request:
branches:
- develop
jobs:
project-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Run build
run: yarn build
- name: Run unit test
run: yarn test
해당 서비스는 yarn berry를 사용하며 PnP 방식이기에 의존성 설치 과정에서 cache 사용을 진행했습니다. 전체적인 flow는 다음과 같습니다.
코드 탐색 -> node 설치 -> 의존성 설치 -> 빌드 -> 테스트
해당 과정뿐만 아니라 앞으로 설명할 전체 workflow 과정에서 actions의 버전을 v1,v2 등 구버전으로 사용시 해당 버전들의 node 버전도 구버전이기 때문에 action 실행 과정에서 다음과 같은 warning이 발생하게 됩니다.
그렇기 때문에 actions 버전은 최신 버전인 latest를 사용하시거나, 꼭 node 구버전이 아닌 버전을 사용하시길 권장합니다. 관련 안내 공지 링크도 같이 알려드립니다.
첫 번째 단계인 프로젝트 빌드, 테스트 단계가 문제 없이 동작하기에 이후에 vercel preview 배포 workflow가 작동하도록 구성했습니다. 코드는 다음과 같습니다.
# ci-preview.yml
name: Preview CI
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
on:
pull_request:
branches:
- develop
jobs:
project-build:
...
deploy-to-vercel:
runs-on: ubuntu-latest
outputs:
statis: ${{ job.status }}
preview_url: ${{ steps.deploy-vercel.outputs.preview_url }}
currnent_time: ${{ steps.current-time.outputs.formattedTime }}
needs: project-build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Deploy to Vercel
id: deploy-vercel
run: |
npm install --global vercel@latest
vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
vercel build --token=${{ secrets.VERCEL_TOKEN }}
vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > vercel-output.txt
echo "preview_url=$(cat vercel-output.txt)" >> $GITHUB_OUTPUT
- name: Get current time
uses: josStorer/get-current-time@v2
id: current-time
with:
format: "YYYY년 MM월 DD일 HH시 mm분"
utcOffset: "+09:00"
Install dependencies까지의 단계는 project-build
단계와 동일합니다. 이후 vercel 배포 과정과 배포 완료 시간을 가져오는 과정만 추가했습니다.
위 사진 코드를 보시면 해당 과정에서 환경 변수로 vercel
의 token
이 필요합니다. 토큰 발급은 vercel 개인 설정에서 가능합니다. 아래 사진처럼 TOKEN_NAME은 편한걸로 적어주시고 scope는 개인 계정에만 하셔도 되고 full account는 전체 계정 기준입니다. expiration은 토큰의 만료 기간인데 저는 우선 무제한으로 설정했습니다.
❗️ 발급된 토큰을 꼭 복사해서 다른 곳에 적어놓으시기 바랍니다. 이후에는 토큰값을 확인할 수 없습니다.
이후에 해당 프로젝트 깃허브 레포지토리로 돌아와 settings
탭에 Secrets and variables -> actions 페이지로 들어오시면 해당 프로젝트에 대한 환경 변수 값을 등록할 수 있습니다.
여기서 New repository secret
을 클릭하셔서 secret 환경 변수 값을 등록해주시면 됩니다. 이때, Name 값은 이후에 yml 파일에서 입력할 값과 동일해야 합니다.
이후 마지막 단계로 PR comment에 preview 링크와 시간 작성을 했습니다. comment 작성은 이미 만들어진 툴을 이용했습니다.
# ci-preview.yml
name: Preview CI
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
on:
pull_request:
branches:
- develop
jobs:
project-build:
...
deploy-to-vercel:
...
github-privew-comment:
runs-on: ubuntu-latest
needs: [deploy-to-vercel]
steps:
- name: Comment PR
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: ${{ github.event.number }}
message: |
🧷 Preview: ${{ needs.deploy-to-vercel.outputs.preview_url }}
⏰ Update: ${{ needs.deploy-to-vercel.outputs.currnent_time }}
해당 과정에서 에러가 발생하는 경우가 있을 수 있습니다. 해당 에러는 깃허브 관련 권한
문제일 가능성이 큽니다.
프로젝트 레포지토리에서 settings -> actions -> general에 보시면 최하단에 아래 사진과 같이 Workflow 권한 관련 내용이 있습니다. read and write 권한이 정상적으로 있어야 comment 작성이 가능하므로 아래 사진 처럼 변경해주시면 됩니다.
이러면 최종적으로 PR 작성 이후 commit 과정에서 아래 사진과 같이 미리보기 배포 링크와 시간이 갱신되는 것을 확인할 수 있습니다.
ci-preview.yml
전체 코드입니다.
name: Preview CI
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
on:
pull_request:
branches:
- develop
jobs:
project-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Run build
run: yarn build
- name: Run unit test
run: yarn test
deploy-to-vercel:
runs-on: ubuntu-latest
outputs:
statis: ${{ job.status }}
preview_url: ${{ steps.deploy-vercel.outputs.preview_url }}
currnent_time: ${{ steps.current-time.outputs.formattedTime }}
needs: project-build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Deploy to Vercel
id: deploy-vercel
run: |
npm install --global vercel@latest
vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
vercel build --token=${{ secrets.VERCEL_TOKEN }}
vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > vercel-output.txt
echo "preview_url=$(cat vercel-output.txt)" >> $GITHUB_OUTPUT
- name: Get current time
uses: josStorer/get-current-time@v2
id: current-time
with:
format: "YYYY년 MM월 DD일 HH시 mm분"
utcOffset: "+09:00"
github-privew-comment:
runs-on: ubuntu-latest
needs: [deploy-to-vercel]
steps:
- name: Comment PR
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: ${{ github.event.number }}
message: |
🧷 Preview: ${{ needs.deploy-to-vercel.outputs.preview_url }}
⏰ Update: ${{ needs.deploy-to-vercel.outputs.currnent_time }}
ci-preview
는 PR 작성 및 커밋 과정에서 이루어지는 작업이라면 ci-production
은 실제 검증이 끝나고 PR이 close 되면서 main에 병합될 때 실배포가 이루어지게 작성했습니다.
preview 과정과 동일하지만 차이점이 있다면, PR 코멘트 코드가 빠진 점, vercel 배포 시 preview가 아닌 production으로 배포된다는 점이 있습니다.
# ci-production.yml
name: Production CI
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
on:
pull_request:
types:
- closed
branches:
- main
jobs:
project-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Run build
run: yarn build
- name: Run unit test
run: yarn test
deploy-to-vercel:
runs-on: ubuntu-latest
needs: project-build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "yarn"
- name: Set yarn version
id: set-version
run: |
yarn set version 4.1.1
echo "YARN_VERSION=$(yarn -v)" >> $GITHUB_OUTPUT
- name: Yarn cache - PnP
uses: actions/cache@v4
with:
path: |
.yarn/cache
.pnp.*
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ steps.set-version.outputs.YARN_VERSION }}
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
run: yarn
- name: Deploy to Vercel
run: |
npm install --global vercel@latest
vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
이후 PR을 작성하고 CI 과정을 끝낸 후 main으로 머지하게 되면 ci-production action이 실행되면서 정상적으로 vercel에 배포가 이루어지는걸 확인할 수 있습니다.
서비스를 제작한다고 모든 일이 끝나는 건 아니라고 생각합니다. 사용자에게 제공되는 서비스라면 배포 이후 여러 정보를 수집하면서 유지보수 및 관리를 이어나가야 하기 때문이고 해당 과정에서 개발자라면 자동화의 중요성을 뼈저리게 느낄 거라 생각합니다.
CI/CD에 대해 알아보면서 많은 툴들도 있고 다양한 방법이 존재하는 만큼 정답이 없고 여러 방법이 존재한다는 것을 알 수 있었습니다. 아무래도 실 서비스들은 vercel로 배포되는 서비스는 거의 없을 것입니다.
다음에는 aws에도 관심을 한번 가져보고 배포를 이어나가보면 좋지 않을까 생각을 하면서 학습이 더 필요함을 느낄 수 있는 시간이였던 거 같습니다.
CI/CD 위키백과
https://ko.wikipedia.org/wiki/CI/CD
Jenkins 공식 문서
https://www.jenkins.io/doc/
Travis CI 공식 문서
https://docs.travis-ci.com/user/onboarding/
CircleCI 공식 문서
https://circleci.com/docs/
GitHub Actions 공식 문서
https://docs.github.com/ko/actions
Vercel 공식 문서
https://vercel.com/guides/how-can-i-use-github-actions-with-vercel