Mere에서 Github actions를 활용하여 CI/CD 환경을 구축하고 있는데, CI, CD 시간이 너무 오래 걸린다는 문제가 발생하여서 이를 개선한 경험에 대해 공유해보겠습니다.
📕 목차
0. 들어가며
1. 문제 분석하기
2. 패키지 의존성 캐시를 활용하여 성능 개선하기
3. Artifact를 활용하여 성능 개선하기
저희 팀은 프론트엔드 팀원이 2명밖에 없습니다. 그런데도 굳이 CI/CD 파이프라인을 구축한 이유는 디자이너님과 더 원활한 소통을 하기 위해서였습니다.
저희 팀은 현재 학생들로 이루어져있어 각자 작업 시간이 다르기에, 매번 디자이너님과 디스코드의 화면공유 기능을 통해 소통하기에는 힘들었고 노션/깃허브 PR에 이미지를 추가하더라도 버튼 활성화/비활성화와 같은 인터랙션을 확인하기도 힘들었습니다.
그래서 디자이너님과 더 원활히 소통하기 위해 CD 파이프라인을 구축을 예정하고 있었는데, 이왕 하는김에 테스트 코드를 추가하진 못하더라도 lint를 이용해서 CI 까지 한번 해보자! 라는 생각으로 CI/CD 파이프라인을 구축하게 되었습니다!
name: CI
on:
push:
branches: ['develop']
pull_request:
branches: ['develop']
jobs:
lint:
# ... lint script
build:
# ... build script
기존에는 lint, build 라는 2개의 jobs을 구성한 상태로 workflow는 아래와 같이 구성되어 있었습니다.
시간이 오래 걸리는 문제점을 확인해보니, 각 job들이 독립된 환경에서 node_modules
들을 각각 다운받고 있었습니다.
실제로, 초기에는 프로덕션 코드의 양이 많지 않음에도 CI를 진행하는데 1분 36초라는 시간이 소요되고 있었습니다. develop 브런치에 PR을 하고 1분 36초를 기다려야 한다는 말인데, 만약 workflow에 CD가 추가되게 된다면 2배 이상인 최소 3분이 걸리게 될 것이라고 생각하였고 개선이 필요하다고 생각하였습니다.
각각의 job에서 node_modules들을 모두 새로 다운 받을필요 없이, 이전 버전과 동일하다면 최신 버전의 node_modules을 가져와 시간을 단축하는 캐싱 전략을 추가하였습니다.
name: CI
on:
pull_request:
branches: ['develop']
jobs:
lint:
runs-on: ubuntu-latest
steps:
# ... 버전 설정
# Cache 의존성 검사
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm install
# ... 린트 실행
build:
runs-on: ubuntu-latest
steps:
# ... 버전 설정
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm install
# ... 빌드 실행
패키지 의존성 캐시를 workflow에 추가한 후에, 실제로 ci에 걸리는 시간이 1분 36초에서 36초로 무려 1분이나 줄어든 것을 확인할 수 있었습니다.
내부에서 어떻게 동작하는지 확인해보니 dependencies들을 캐시해오면서 시간을 단축한 것을 확인할 수 있습니다.
잠깐 workflow script가 무엇인지 정확히 짚고 넘어가봅시다. 함께 스크립트를 따라가면서 이해해봅시다!
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
runner.os
), 문자열 “-node-
” 그리고 package-lock.json
의 해시 값을 조합하여 생성합니다.restore-keys: | ${{ runner.os }}-node-
npm install
과정의 시간을 줄여줄 수 있습니다.if: steps.cache.outputs.cache-hit != 'true' run: npm install
이제 firebase에서 제공해주는 기능 중 하나인 preview channels와 deploy를 추가해보겠습니다. lint와 build의 경우에는 실제로 아래와 같이 Dependencies가 캐싱이 잘 되어 오고 있었지만,
firebase에서의 호스팅 시간이 20~30초가 걸리는 것을 감안하고 확인을 해도, firebase preview hosting 이전 시간이 22초나 걸려서 총 53초가 소요되는 것을 확인하였습니다.
현재 문제점을 확인해보니 캐시 파일을 가져올 때 너무 오랜 시간이 걸린다는 점과 이전에 build job에서 build를 했음에도 불구하고 새로운 build를 진행하는 것이 문제였습니다.
현재 build와 build_and_preview가 하나의 job에서 진행되는 것이 아닌 각자 독립된 환경에서 실행되고 있기 때문에 Artifact를 이용하여 빌드 결과물을 저장하고 외부 패키지를 다운 받도록 하였습니다.
Artifact
- 각 Job들은 독립된 환경을 가지고 독립적으로 실행됩니다. 하지만 독립된 환경에서 외부의 패 키지를 받을 수 있도록 하기 위해, GitHub Actions Artifacts를 이용할 수 있습니다.
- GitHub Actions Artifacts를 이용하면, CI/CD 워크플로우를 실행할 때 다른 단계나 job에 전달할 수 있는 기능을 제공해 이전에 빌드된 파일을 다른 Job에서 가져와서 사용할 수 있습니다.
Artifact는 크게 두 가지의 기능을 가집니다.
actions/upload-artifact
actions/download-artifact
upload-artifact
는 build된 파일들을 artifact에 업로드해주는 역할을 하는 github action이며, download-artifact는 업로드된 artifact에서 build된 파일을 불러올 수 있는 github action입니다.
build 파일을 artifact에 저장하고, build_and_preview에서 이를 다운로드 받아서 사용하는 방식을 활용하였더니 32초로 줄어든 것을 확인할 수 있었습니다.
cache 기능을 활용한 것이 아니기에 cache dependencies 로그는 없어진 것을 확인할 수 있고, build파일을 artifact에서 가져오는 방식을 이용해서 firebase preview 호스팅 이전 시간이 22초에서 7초나 줄어들었습니다!. 총 시간은 53초에서 32초로 줄어들게 되었네요!
이제 개발에만 집중할 수 있겠군요! 히히 😆