CICD 개선 일대기
2022.12.11 - [프로젝트/웹] - (CICD) CICD 개선 일대기 - 1. 깃허브 액션과 슬랙을 연동시켜보자
2022.12.11 - [프로젝트/웹] - (CICD) CICD 개선 일대기 - 2. 깃허브 액션의 중복 코드를 없애보자
2022.12.12 - [프로젝트/웹] - (CICD) CICD 개선 일대기 - 3. 깃허브 액션 캐시로 node 빌드 속도를 높여보자
들어가기 전에
3번째 글로 찾아왔습니다. 깃허브 액션 CICD 속도 개선에 대해서 아직 해보지 못한 수단들이 많은데 아마 프로젝트 끝나고 쓸 수 있을 것 같아서 이번 글로 일단 해당 시리즈를 마무리 지으려고 합니다.
오늘도 찾아와주셔서 감사합니다. 🎆
느리다
이제 마지막 작업, 속도 개선만이 남았습니다.
속도 개선을 하기 위해서는 캐시를 사용하거나 도커 파일을 수정해야 하기 때문에 러닝 커브가 꽤나 가파를 것으로 예상하고 마지막으로 남겨두었습니다. (도커에 대한 수정은 아마 네 번째 글이 될 것 같습니다.)
캐시 액션
우선 가장 직접적이고 확실한 해결책인 깃허브 액션의 캐시를 사용하기로 했습니다. 다행히도 공식 문서에 캐시에 관한 내용이 존재했습니다.
Caching dependencies to speed up workflows - GitHub Docs
저희는 프론트엔드와 백엔드 모두 typescript를 사용하고 있기 때문에 환경이 같아서 CI 파일을 reusable workflow 파일 하나로 관리할 수 있었습니다. 아래는 코드입니다.
name: "[CI] 클라이언트, 서버 공통"
on:
workflow_call:
inputs:
working-directory:
description: "작업할 디렉토리(client | server | scheduler-server)"
required: true
type: string
jobs:
test:
runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node JS
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ inputs.working-directory }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ inputs.working-directory }}
- name: Install Npm Clean If Cache miss
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Test
run: npm test
build:
runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node JS
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ inputs.working-directory }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ inputs.working-directory }}
- name: Install Npm Clean If Cache miss
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build
run: npm run build
작업할 디렉토리에 대한 입력을 받고 해당 디렉토리에서 테스트와 빌드를 진행합니다. 테스트와 빌드는 서로 병렬적으로 진행해도 문제가 없기 때문에 job을 분리했습니다.
한 가지 아쉬운 점은 테스트와 빌드가 체크아웃부터 npm ci까지의 스텝이 같은데, 해당 로직을 분리하고자 인터넷을 찾아보니 깃허브 액션에서 아직 step을 병렬로 실행하는 기능을 지원하지 않았습니다. 아래는 해당 기능에 대한 discussion 링크입니다.
Steps in parallel? · Discussion #26291 · community
그래서 만약 캐시 미스가 발생하면 두 job 모두 npm ci를 진행한 뒤 같은 캐시 파일을 저장할 것이기 때문에 엔지니어로서 약간 아쉬웠지만, 테스트와 빌드만 병렬적으로 수행해도 현재 수행 시간의 절반 정도를 절약할 수 있을 것이라는 기대감에 일단 진행하기로 했습니다.
name: "[CI] 클라이언트, 서버 공통"
on:
workflow_call:
inputs:
working-directory:
description: "작업할 디렉토리(client | server | scheduler-server)"
required: true
type: string
jobs:
test:
runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node JS
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ format('{0}-node-{1}-{2}', runner.os, inputs.working-directory, hashFiles(format('{0}/package-lock.json', inputs.working-directory))) }}
restore-keys: |
${{ runner.os }}-node-${{ inputs.working-directory }}
- name: Install Npm Clean If Cache miss
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Test
run: npm test
build:
runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node JS
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ format('{0}-node-{1}-{2}', runner.os, inputs.working-directory, hashFiles(format('{0}/package-lock.json', inputs.working-directory))) }}
restore-keys: |
${{ runner.os }}-node-${{ inputs.working-directory }}
- name: Install Npm Clean If Cache miss
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build
run: npm run build
전체 package-lock.json을 해싱하면 서버에서 의존성이 변경될 때 클라이언트도 해시값이 변경되기 때문에 해당 작업 디렉토리 내의 파일만 해싱하도록 했습니다.
그 과정에서 github action가 중첩 변수를 제대로 지원하지 않아서 아래 링크를 참고해서 작성했습니다.
Nested variable substitution · Discussion #26428 · community
속도가 얼마나 개선됐을까 기대하고 결과를 확인하러 갔는데…
또 에러
job이 127 에러코드를 뱉었습니다. 그래서 로그를 보러 들어가봤습니다.
예상컨대 캐시 경로에 문제가 있는 것 같았습니다.
저희가 입력한 경로는 ~/.npm이었습니다. 사실 의미를 잘 모르고 공식 문서를 그대로 사용했기 때문에 반성하며 공식 문서를 꼼꼼히 살펴봤습니다. 문서에 아래처럼 적혀있었습니다.
path - A list of files, directories, and wildcard patterns to cache and restore. See @actions/glob for supported patterns.
해석해보니 캐시에 저장할 대상을 지정하는 키였습니다. 저희는 .npm 디렉토리를 저장했기 때문에 종속성 폴더가 없어 발생하는 일이었습니다. 따라서 아래처럼 수정해줬습니다.
name: "[CI] 클라이언트, 서버 공통"
jobs:
test:
steps:
- name: Checkout
- name: Use Node JS
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
**path: ${{ inputs.working-directory }}/node_modules**
- name: Install Npm Clean If Cache miss
- name: Test
효율 200%의 작업
이제 다시 커밋을 하고 속도를 테스트했습니다.
일단 main 브랜치에 병합하기 전이어서 속도 비교가 어려워, 개인 레포지토리를 하나 만들고 같은 코드로 테스트해봤습니다.
무려 절반 가까이 시간이 절약된 것을 확인할 수 있습니다. 저희는 push할 때마다 CI를 진행하기 때문에 CI가 빈번히 일어납니다. 그래서 CI의 속도가 중요한데 캐시를 통해 유의미한 결과를 도출할 수 있었습니다.
주의할 점)
main 브랜치에서 분기된 브랜치는 부모인 main 브랜치의 캐시에 접근할 수 있지만 반대의 경우는 불가능합니다.
또한, 형제 브랜치의 캐시에도 접근할 수 없습니다.
또한, 풀 리퀘스트에서 생성된 캐시는 같은 풀 리퀘스트가 다시 실행될 때만 복원할 수 있습니다. 즉, 제한된 범위를 가집니다.
제발 결론만 요약해주세요
네. 저는 3가지 작업을 통해 개발 편의성을 개선해봤습니다.
- 슬랙에 깃허브 연동
- reusable workflow로 workflow 파일 내 코드 반복 제거
- 깃허브 액션 캐시를 사용하여 속도 개선(무려 200%)
오늘도 긴 글을 읽어주셔서 감사합니다! 다음 글은 언제가 될 지 모르겠지만 깃허브 액션에서 도커 캐시를 적용하는 방법과 도커 파일을 최적화하는 방법을 공부한 뒤 관련 글로 돌아오겠습니다!
'프로젝트 > 웹' 카테고리의 다른 글
(CICD) 어디 가서 내가 깃허브 액션 만들었다고 말하지 마라~ (1) | 2022.12.15 |
---|---|
(CICD) CICD 개선 일대기 - 2. 깃허브 액션의 중복 코드를 없애보자 (0) | 2022.12.11 |
(CICD) CICD 개선 일대기 - 1. 깃허브 액션과 슬랙을 연동시켜보자 (0) | 2022.12.11 |
(NestJS) 주렁주렁 달린 Swagger 데코레이터를 줄여보자 (2) | 2022.12.08 |
(NestJS) TDD를 하며 배운 것들 (1) | 2022.11.24 |