저는 요즘에도 혼자서 MyGarden
프로젝트를 계속해서 업데이트 하고 있습니다.
코드를 업데이트 하면서 매번 느끼는 점은 CI
및 CD
는 정말 귀찮은 일이라는 것입니다.
그래서 저는 MyGarden
프로젝트에 GitHub Actions
랑 Docker
를 활용해서 CI
및 CD
를 도입했습니다.
제 프로젝트에
CI
및CD
를 도입한 관련 글은 [Vue + Spring] Github Actions를 이용한 CI/CD 구축하기 (+ Jacoco PR Comment 기능)를 봐주시면 됩니다.😊
CI
및CD
를 도입하고 나서는 코드 수정만 하면Test
,Build
모두 알아서 진행이 되니, 생산성이 많이 올라갔습니다.※ 개인 프로젝트라서 아직 도입을 안 하셨더라도, 꼭 도입해보시기를 추천드립니다!!
CI/CD
를 도입하고 나서는, PR
을 머지하고 배포
가 완료될 때까지 기다리기만 하면 됩니다.
근데 가끔씩 지금 내가 수정한 기능이 정상적으로 동작 하는지를, 직접 눈으로 결과를 보고 싶을 때가 있습니다. 그럴 때는 어쩔 수 없이 모니터 앞에서 CI/CD
가 끝날 때까지 기다려야 했습니다.
이를 기다리는 시간은 정말 지루하고 아깝습니다.
그래서 이 시간을 지금보다 더 줄여보기로 했습니다.
시간을 줄이기 위해서 GitHub Actions
의 Workflow
들을 살펴봤습니다.
Workflow
에 대해서 살펴보기 전에, 현재 프로젝트에 적용중인CI/CD
에 관해서 간단히 설명드리겠습니다.
CI
Front(vue)
쪽 코드를 수정했을 때,node
를 사용하여npm run build
가 정상적으로 되는지 확인합니다.Back(spring)
쪽 코드를 수정했을 떄,gradle
을 통해Test
및Build
가 정상적으로 되는지 확인합니다.
CD
PR
이 정상적으로master 브랜치
에 머지가 되면, 다음의 로직을 따라서배포
가 됩니다.
gradle
을 통해Build
Docker
로 이미지를 만든 후ghcr.io (Github Action Container Registry)
에Push
GitHub Actions Runner
를 통해AWS EC2
에Docker
이미지를Pull
하고 실행
제일 먼저 CI
에서 Front
쪽 코드를 수정했을 때, 진행되는 Workflow
를 점검 했습니다.
해당 전체 과정이 총 17초
정도 걸리는 것을 볼 수 있으며, Install Dependencies
에서 무려 7초
나 걸린다는 사실을 알 수 있습니다.
Install Dependencies
를 해결하면, 적어도 약30%
이상의 성능 향상이 있을 것 같습니다.
아무래도 Front
쪽 Dependencies
는 자주 변경이 되지 않을 것 같아서, 캐시를 활용하면 좋을 것 같다는 생각이 들었습니다.
GitHub Actions
의 npm cache
관련해서 검색을 하다보니, actions/cache
를 알게 되었습니다.
GitHub Actions
에서는 쉽게 cache
를 사용할 수 있도록 cache
관련 action
이 있습니다. (actions/cache GitHub)
매번 Install Dependencies
하는 것 대신에, 의존성의 변경이 있을 때만 딱 1번 의존성을 설치하고 해당 의존성을 캐싱해두면 문제를 해결할 수 있을 것 같습니다.
(※의존성을 캐싱하기 위해서는 node_modules
폴더를 캐싱하면 되겠습니다.)
사용 방법은 간단했습니다.
해당 step
만 추가해주면 됩니다.
주석으로 설명을 달아뒀습니다.
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
# 캐싱할 대상을 정합니다. (대상의 path를 지정)
# 여기서는 npm에서 의존성이 설치되는 디렉터리인 node_modules를 지정
path: '**/node_modules'
# 캐싱의 기준은 key를 기준으로 합니다.
# 의존성이 변경되면, 함께 변경되는 파일인 package-lock.json을 key로 잡아줍니다.
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# key가 유효하지 않은 경우 runner의 운영체제 값과 node라는 suffix를 key로 복구합니다.
# 결과적으로 package-lock.json이 변경되지 않았다면 캐싱된 node_modules를 사용합니다.
# 만약 복구될 캐시가 없다면 아래에서 사용할 cache-hit는 false가 됩니다.
restore-keys: |
${{ runner.os }}-node-
cache action
을 추가하고 나서는, 다음 step
에서 cache hit
를 기준으로 Install Dependencies
를 할지 말지 결정하면 됩니다.
- name: Install Dependencies
# cache-hit이 true가 아니면 의존성을 설치합니다.
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build with npm
run: npm run build
해당 workflow
가 정상적으로 동작하게 된다면, Install Dependencies
를 매번 하지 않아도 되기 때문에 큰 성능 향상이 있을 것 같습니다.
name: Vue build test
on:
pull_request:
branches:
- master
paths:
- my-garden-fe/** # 해당 경로의 파일이 수정되었을 때만, 해당 workflow 실행
env:
NODE_VERSION: 20
jobs:
front-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./my-garden-fe # npm을 실행할 기본 경로 지정
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm # npm 캐싱
cache-dependency-path: my-garden-fe/package-lock.json
- name: Install Dependencies
run: npm install
- name: Build with npm
run: npm run build
name: Vue build test
on:
pull_request:
branches:
- master
paths:
- my-garden-fe/** # 해당 경로의 파일이 수정되었을 때만, 해당 workflow 실행
env:
NODE_VERSION: 20
jobs:
front-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./my-garden-fe # npm을 실행할 기본 경로 지정
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache node modules
id: cache
uses: actions/cache@v4
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 ci
- name: Build with npm
run: npm run build
setup-node
action의 cache
기능 제거# 변경 전
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm # npm 캐싱
cache-dependency-path: my-garden-fe/package-lock.json
# ========================================================================
# 변경 후
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
setup-node
에서 cache
기능을 추가하게 되면, /home/runner/.npm
를 캐싱합니다. (.npm
안에는 패키지 캐시와 관련된 정보만을 담고 있습니다.)
패키지 캐시
란 ?
패키지 캐시
는 패키지 매니저가 인터넷에서 패키지를 다운로드하거나 로컬에서 패키지를 설치할 때, 이미 다운로드한 패키지를 로컬에 저장하여 같은 패키지를 다시 다운로드하지 않고 이전에 다운로드한 것을 사용함으로써 시간과 대역폭을 절약할 수 있게 도와준다.- 패키지 캐시는 주로
.npm
또는.yarn
디렉토리에 저장되며, 이 디렉토리는 패키지 매니저가 자동으로 생성하고 관리- 캐시된 패키지는 해당 패키지의 버전과 메타데이터를 포함하며, 필요할 때 패키지 매니저가 이를 사용하여 종속성을 설치하거나 업데이트
- 패키지 캐시의 존재는 패키지 매니저가 빌드 시간을 줄이고 패키지를 효율적으로 관리할 수 있도록 도와줌
.npm
폴더가 있으면 의존성을 설치할 때 시간을 줄여주긴 하지만, 저희는 의존성 폴더 자체를 캐싱해서 사용할 예정이기 때문에 그다지 필요가 없습니다.
.npm
캐싱 key
는 package-lock.json
입니다.
의존성이 추가될 때마다 package-lock.json
가 변경되기 때문에, 해당 캐시는 초기화가 됩니다.
저희는 의존성이 추가되면, 바로 해당 의존성 폴더를 캐싱해서 사용합니다.
캐싱된 의존성 폴더가 있으면 의존성을 재설치 하지 않아도 됩니다.
그러므로 의존성을 설치할 때 시간을 줄일 필요가 없으며, 해당 .npm
캐시를 내려 받는 시간도 손해입니다.
(약 40MB
를 내려받는데, 1초
정도 걸리는 것 같습니다. → 1초
를 손해봄)
node_modules
폴더 캐싱 추가 & cache-hit
에 따라서 Install Dependencies
진행# 변경 전
- name: Install Dependencies
run: npm install
# =========================================================================
# 변경 후
- name: Cache node modules
id: cache
uses: actions/cache@v4
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 ci
cache action
을 추가했습니다.cache hit
를 기준으로 Install Dependencies
를 할지 말지 결정합니다.npm install
대신에 npm ci
사용- name: Install Dependencies
run: npm install
# =========================================================================
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
CI/CD
에서는 npm ci
가 더 옳은 명령어라고 해서 수정했습니다.npm install
과 npm ci
차이npm install
은 종속성을 추가하거나 업데이트하기 위해 사용npm ci
는 package-lock.json
에 정의된 정확한 종속성으로 빠르고 일관된 설치를 보장하기 위해 CI 환경에서 사용node modules
의존성이 캐싱되는 첫 workflow
를 제외하고 비교하면, 17s
→ 12s
(약 30%
의 성능 향상)workflow
기준상황 | 설명 | 시간 (초) |
---|---|---|
cache action 추가 전 | npm 의존성 캐싱 X, .npm 캐시 적용 | 17 |
cache action 추가 후 첫 빌드 | npm 의존성 캐싱 X, .npm 캐시 적용 | 27 |
cache action 추가 후 두번째 빌드 | npm 의존성 캐싱 O, .npm 캐시 적용 | 13 |
cache action 추가 후 세번째 빌드 | npm 의존성 캐싱 O, .npm 캐시 제거 | 12 |
cache action
추가 전 (npm 의존성 캐싱 X, .npm
캐시 적용) → 17초cache action
추가 후 첫 빌드(npm 의존성 캐싱 X, .npm
캐시 적용) → 27초node_modules
를 캐싱하는 과정이 있습니다.Post Cache node modules
→ node moudle
을 캐시로 등록하기 위해 업로드하는 시간이 7초
걸림 cache action
추가 후 두번째 빌드 (npm 의존성 캐싱 O, .npm
캐시 적용) → 13초node module
을 캐시로 가져왔기 때문에 Install Dependencies
가 진행되지 않은 것을 보실 수 있습니다.Cache node modules
→ node module
을 캐시로 가져오는 시간이 2초 걸림
(cache-hit
이 true
라서, Install Dependencies
가 진행되지 않음)
여기서 Post Cache node modules
는 진행한 job에 대해서 cleanup
을 합니다.
cache action
추가 후 세번째 빌드 (npm 의존성 캐싱 O, .npm
캐시 제거) → 12초.npm
폴더에 대한 캐싱을 삭제했더니, 해당 폴더를 다운로드 하는 시간이 줄어들어 1초
더 빨라졌습니다.