
같은글 노션링크 | 코드 변경 전후를 명확하게 보실수있습니다.
1.1 CI, CD, CDep
1.2 CI/CD 구축의 필요성
1.3 도입 목표
2.1 현재 브랜치 전략
2.2 CI/CD 흐름 생각해보기
3.1 워크플로우 트리거 시점 정하기
3.2 CI
3.3 CDep
3.4 CI 속도 개선하기 (의존성 캐싱적용)
CI/CD는 애플리케이션 개발(빌드, 테스트, 배포) 단계를 자동화하여 보다 짧은 주기로 앱을 고객에게 제공하는 방법을 말한다.
CI(지속적인통합)는 소스코드의 새로운 변경사항을 빌드하고 테스트한뒤 코드를 병합(통합)하는 프로세스를 자동화하는 작업이다.
CD와 CDep는 CI 의 연장선인데, CI 를 성공하고나서 팀 사정에 따라 CD 까지 혹은 CDep까지 자동화 수준을 결정하면 되는 것으로 이해했다.
Code - Build - Test - Staging - Deploy
←————————→ CI
←—————————————→ CD
←———————————————————>CDep
머지, 빌드, 테스트, 배포는 매번 수동으로 하면 리소스 낭비가 큰 작업이다. 즉, 휴먼에러가 발생하기 좋은 작업들이어서 수동으로 하다가 에러가 터지면 해결하기까지 시간이 오래걸리고, 앱의 개선은 느려지게 되고, 고객은 그 사이에 이탈할 것이며 서비스는 망하게 된다.
새로운 코드 변경사항에 버그가 있진않은지 검사하고, 안전하고 빠르게 앱을 업데이트 할 수 있는 방법이 CI/CD 인 것이다.
현재 vercel에 배포 중이고, 브랜치는 dev(개발브랜치)와 main이 존재한다. 또한, 현재 main브랜치에 대해 PR을 생성하면 CI 없이 곧바로 vercel을 통해 스테이징 환경에 알아서 자동 배포되고 있다.
이에 나는 CI가 성공해야 Vercel이 프로덕션 환경에 자동배포를 하도록 CDep 프로세스를 제어하고 싶다.
즉, 코드 변경 사항이 CI 프로세스의 모든 테스트를 통과하자마자 main브랜치 코드가 자동으로 프로덕션에 배포되게 만들고 싶다.
본 프로젝트의 브랜치 전략을 논하기 전에,
보다 일반적인 팀의 브랜치 전략과 그에 따른 CI/CD 구축 시나리오를 생각해보자
💡 <————branch———>feat —— dev —— main —— production
본프로젝트는 dev, main 두개의 브랜치가 존재한다.
<—branch—>
dev —— main —— production
로컬dev에서 새로운 코드 변경사항을 원격dev로 푸쉬한다.
개발자가 PR(dev to main)을 생성한다.
PR 생성시, CI(빌드, 테스트)가 이뤄진다.
3-1. 빌드-테스트에 성공하면, 자동으로 PR Merge
3-2. 빌드 또는 테스트에 실패하면 어떤 단계에서 실패한건지 코멘트가 달리고, PR Close
PR Merge(main으로 push) 되면 vercel을 통해 앱이 자동배포된다.
1) PR 생성시 CI 워크플로우가 진행돼야한다.
# .github/workflow/CI.YML
on:
pull_request:
branches: [main]
2) PR Merge(main으로 push)시 CDep 워크플로우가 진행돼야한다.
# .github/workflow/deploy CI.YML
on:
push:
branches: [main]
어떤 단계에서 에러가 발생한 것인지 파악하기 위해서 build, test job을 따로 작성했다.
name: CI
on:
pull_request:
branches: ['main']
jobs:
# 1. 빌드
build:
runs-on: ubuntu-latest # 실행환경
steps:
- name: Checkout code # 플젝코드 소스코드 내려받기
uses: actions/checkout@v3
- name: Install dependencies # 종속성 설치
run: yarn install
- name: Build # 빌드
run: yarn build
- name: Close PR, if build fails # 빌드 실패시 pr 닫기
if: ${{ failure() }} # 이전 step이 실패한 경우에만 이 step을 실행시키는 syntax
uses: actions/github-script@v6
with: # actions(uses)의 파라미터 역할
github-token: ${{ github.TOKEN }}
# octokit 문법 참고
script: |
const pull_number = ${{ github.event.pull_request.number }}
const updated_title = `[BUILD FAIL] ${{ github.event.pull_request.title }}`
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
body: '빌드에 실패했습니다.',
event: 'REQUEST_CHANGES'
})
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
title: updated_title,
state: 'closed'
})
# 2. 테스트
test:
runs-on: ubuntu-latest
needs: build # build 테스트 이후에 test job 이 이뤄지도록 순서 제어
env:
REACT_APP_BASE_URL: ${{ secrets.REACT_APP_BASE_URL }}
REACT_APP_API_KEY: ${{ secrets.REACT_APP_API_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: yarn install
- name: Run test
run: yarn test
- name: Close PR, If test fail
if: ${{ failure() }}
uses: actions/github-script@v6
with:
github-token: ${{ github.TOKEN }}
script: |
const pull_number = ${{ github.event.pull_request.number }}
const updated_title = `[TEST FAIL] ${{ github.event.pull_request.title }}`
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
body: '테스트에 실패했습니다.',
event: 'REQUEST_CHANGES'
})
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
title: updated_title,
state: 'closed'
})
- name: Approve PR, If test passes # 테스트 통과시, PR 머지
uses: actions/github-script@v6
with:
github-token: ${{ github.TOKEN }}
script: |
const pull_number = ${{ github.event.pull_request.number }}
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
body: '테스트에 통과해 main 브랜치로 자동 머지합니다.',
event: 'REQUEST_CHANGES'
})
await github.rest.pulls.merge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number,
})
해당 액션이 github api를 이용하는 액션이므로, github token을 생성해서 제공해줘야 깃 리포지토리의 정보에 대해 http 요청을 날릴 수 있다.
에러 : Input required and not supplied: github-token

토큰이름을 secret.GIHUB_ACTIONS 에서 secret.TOKEN 으로 바꿨더니 해결됐다. 오타가 있었던것같다.
- 변경전 토큰이름

- 변경후 토큰이름


구글링을 하니까, 워크플로우 퍼미션을 write 도 할 수 있게 해주라는 솔루션이 있었다.
즉, ‘워크플로우 동작으로 레포지토리에 영향(내 경우 PR Close)을 줘도 된다’는 옵션으로 변경하라는 말
레포지토리 - Setting - 사이드바의 action - general

해결됐다.


에러모달 테스트가 실패한다. 
에러 모달이 제대로 뜨지 않는 것 같다. createPortal 과 연관 돼있는듯하다.

가짜 커밋을 푸쉬, main에 PR 생성했다. 아래와 같이 YAML 파일이 정상적으로 동작하고, 테스트도 통과가 잘됐다.
PR을 그대로 둔채, 만들었던 가짜 컴포넌트를 제거하고 다시 커밋, 푸쉬 했다. 다시 YAML 파일이 동작했다.

main 브랜치로 머지가 완료되고나서(push to main), 배포가 이뤄져야 한다.
문제: vercel 자동배포가 CI 프로세스보다 먼저 이뤄지고있다.
아래 사진에서 빨강네모는 배포가 먼저 이뤄진 것을 보여주고, 파랑네모는 github action 의 CI.YAML이 build job 을 진행중임을 보여준다. 즉, 순서가 안맞는다.


name: deploy CI
on:
push:
branches: ['main']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to Vercel Action
uses: BetaHuhn/deploy-to-vercel-action@latest
with:
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN_DEPLOY }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

이슈: CI가 너무 느리다.
원인: 워크플로우가 매번 새로운 가상환경에서 실행되기 때문에, 의존성을 job (build, test) 마다 설치하기 때문이다. 비슷한 의존성을 반복적으로 설치하는 것은 네트워크 사용량을 증가시키고 런타임을 늘린다. → 시간과 자원의 낭비
GitHub Actions에서 제공하는 의존성 캐싱을 적용하면 워크플로우의 실행 시간을 단축할 수 있다.
목표: 의존성들을 캐시하고 워크플로우 속도를 개선하자.
과정
의존성 캐싱 방법(2)
1) actions/cache를 사용해서 직접 설정로직을 작성하거나
2) actions/cache를 내장하고있는 actions/setup-node을 이용하는 방법이 있다.
처음에 전자를 시도해보다가 CLI 가 어렵게 느껴져서 후자 방법으로 전환했다. (전자는 나중에 해보기)
**actions/setup-node을 이용해 의존성 캐싱적용하기 (Built-in 의존성 캐싱)**
💡 워크플로우 셋업이 용이하도록 GitHub에서 제공하는 [action]은 대부분 [actions/cache]를 내장하고 있어 복잡한 설정 없이 의존성 캐싱을 적용할 수 있습니다.
아래 코드를 추가하기만 하면 된다.
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: npm# or yarn, pnpm
Built in 의존성 캐싱 - ci.yml
name: CI
on:
pull_request:
branches: ['main']
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
# 추가
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: yarn
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
...
test:
runs-on: ubuntu-latest
needs: build
env:
REACT_APP_BASE_URL: ${{ secrets.REACT_APP_BASE_URL }}
REACT_APP_API_KEY: ${{ secrets.REACT_APP_API_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: yarn
- name: Install dependencies
run: yarn install
- name: Run test
run: yarn test
...
build job, test job 직렬 순서로 워크플로우가 동작하는데 build job은 항상 의존성을 순정으로 설치한다. build job에서 저장된 캐싱은 test job에서 재사용한다.
아래사진을 통해 test job에서 캐시 hit하고 의존성 설치에 19초가 소요됨을 알 수 있다. (build job은 의존성 설치에 32초정도 소요됨)

캐싱 로직을 적용하기전에 test job의 기존 러닝타임은 55초였고, 캐싱 힛트로 인해 36초로 19초가 단축됐다. 전체 러닝타임은 캐싱적용전 1분49초, 후가 1분 38초이다.
캐싱로직 적용 전
- ci 전체 시간: 1분 49초
- 캐싱 적용되지않은 test job 소요 시간: 55초

캐싱로직 적용 후
- ci 전체 시간: 1분 38초
- 캐싱 적용한 test job 소요 시간: 36초

전체 시간이 극적으로 단축된 것은 아니어서 처음엔 실망할뻔 했다가, 캐싱파일을 사용한 Test job의 수행시간이 크게 19초나 줄어든 것을 보고, test Job 이외에 다른 Job들이 추가될 큰 프로젝트일수록 캐싱의 효용이 더욱 드러날 것으로 생각됐다.
actions/cache 를 이용하면 위의 UI 에서 사이드바 하단의 Run details 메뉴에 caches 라는 메뉴가 나타난다. 해당 메뉴에는 캐시된 해시값키가 보여지는데. 아마 그 캐시를 이용하면 build job도 캐싱이 적용돼서 시간을 더 단축할 수 있을것같다는 추측이다. 마음의 여유가 생기면 도전 해봐야겠다.
깃액션을 다룰때마다 떨리는 것 같다. GUI가 없고, 주로 명령어를 작성하거나, 명령어를 많이 읽어야한다거나,, 해서 인 것 같다. 또 깃 사용 경험이 적어서인 것도 있다. 한번 기간을 잡고 명령어, 깃을 공부해야할 것같다. 다양한 에러상황을 테스트 해보면서 깃에 대한 항마력(?)을 키워야겠다.
학습
참고
[Github Actions] CI - 빌드 실패 시 Pull Request 닫고 코멘트 등록하기
s3, cloudfront + github actons
Github actions를 이용한 빌드 및 배포 자동화 - netlify
github-actions, Elastic beanstalk
if문으로 실패 제어
카카오엔터프라이즈가 GitHub Actions를 사용하는 이유
[CI/CD] Husky, GitHub Actions 로 팀 프로젝트 코드를 지속적으로 통합/배포하기 (ESLint, Prettier, Jest, gh-pages)
PR에서 테스팅 후 성공시에만 자동 Merge하기
**PR Github actions의 결과에 따라 자동 comment 남기기**
**[Github Actions] CI - 빌드 실패 시 Pull Request 닫고 코멘트 등록하기**
Github Actions 를 이용한 CI 테스트 자동화
**[Github Actions] Github API를 사용하는 스크립트 모음**
https://octokit.github.io/rest.js/v18#pulls-request-reviewers
https://velog.io/@couchcoding/CICD-Github-Actions으로-내-포트폴리오에-CICD를-적용하기-1
내가 사용하는 GitHub Actions - Vercel.👏🏻
marketplace - actions | deploy-to-vercel-action
vercel | how-can-i-use-github-actions-with-vercel
더빠른 워크플로우
더 빠른 워크플로우를 향해
[Github Action] 데이터 좀 맡길게! 나중에 그대로 다시줄래? - action cache
https://velog.io/@lingodingo/배포-속도를-올려보자-CI
깃허브 닥스 | caching-dependencies-to-speed-up-workflows
Github Actions를 이용한 CI 구축하기(Prettier, ESLint, TSC)