GitLab CI/CD 파이프라인 도입기
이직 후 새 프로젝트에 합류했을 때, 팀의 개발 프로세스에서 몇 가지 문제를 발견했다. 빠른 개발 속도를 위해 master 브랜치에 직접 머지하는 방식으로 운영되고 있었고, 코드 리뷰와 문서화가 자연스럽게 생략되면서 코드 일관성이 무너지고 있었다. 결정적으로 빌드 실패가 반복되기 시작했다.
"최소한의 안전장치"가 필요하다고 판단해 CI/CD 도입을 제안했다.
환경
- GitLab을 원격 저장소로 사용 중
- pnpm + 모노레포 구조
- Node 20.14
- 배포는 AWS CDK 기반
GitLab을 쓰고 있었기 때문에 별도 서비스 없이 GitLab CI/CD를 그대로 활용하기로 했다.
CI 구성
기본 설정
image: node:20.14
before_script:
- corepack enable
- corepack prepare pnpm@latest --activate
stages:
- test
- lint
- buildimage로 파이프라인 실행 환경을 고정하고, before_script에서 pnpm을 활성화했다. corepack을 사용하면 별도 설치 없이 패키지 매니저를 준비할 수 있다.
각 Job 정의
test-job:
stage: test
script:
- cd apps/mlbland-client
- pnpm install --frozen-lockfile
- pnpm run test
only:
changes:
- apps/mlbland-client/**/*
interruptible: true
lint-job:
stage: lint
script:
- cd apps/mlbland-client
- pnpm install --frozen-lockfile
- pnpm run lint --max-warnings=1000
only:
changes:
- apps/mlbland-client/**/*
interruptible: true
build-job:
stage: build
script:
- cd apps/mlbland-client
- pnpm install --frozen-lockfile
- pnpm run build
interruptible: truetest-job은 유틸 함수와 컴포넌트 단위 테스트를 실행한다. only.changes로 해당 앱 디렉토리에 변경이 있을 때만 실행되도록 제한했다. 모노레포 구조에서 관계없는 변경으로 파이프라인이 낭비되는 것을 막기 위해서다.
build-job은 다른 패키지의 변경도 영향을 줄 수 있어 조건 없이 항상 실행한다.
lint-job의 --max-warnings=1000은 초기 도입 시 기존 코드베이스의 warning을 한꺼번에 해결하기 어려워 임시로 설정한 값이다. 단계적으로 낮춰가는 것을 목표로 한다.
interruptible: true는 같은 브랜치에서 새 push가 발생하면 이전 파이프라인을 자동으로 중단시킨다. 불필요한 Runner 사용량을 줄이는 데 효과적이다.
캐시 설정
cache:
key:
files:
- pnpm-lock.yaml
- apps/mlbland-client/package.json
paths:
- node_modules
- apps/mlbland-client/node_moduleslockfile과 package.json 기준으로 캐시를 관리한다. 종속성에 변경이 없으면 캐시된 node_modules를 재사용해서 파이프라인 실행 시간을 크게 줄일 수 있다. GitLab Runner의 월간 사용량 제한을 고려하면 이 설정이 꽤 중요하다.
CD 구성
CI가 안정적으로 동작한 뒤 CD까지 확장했다. 배포는 AWS CDK를 사용하고, Docker 이미지를 빌드해 푸시한 뒤 CDK로 배포하는 흐름이다.
stages:
- test
- lint
- build
- deploy
deploy-template:
stage: deploy
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""
before_script:
- apt-get update
- apt-get install -y docker.io awscli
- aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
- aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
- if [ "$STAGE" = "qa" ]; then export AWS_REGION="$AWS_REGION_QA"; else export AWS_REGION="$AWS_REGION_DEFAULT"; fi
- aws configure set region "$AWS_REGION"
script:
- npm run docker:build:$STAGE
- npm run docker:push:$STAGE
- cd apps/mlbland-client/cdk
- npm install
- npm run deploy:$STAGE
when: manual
needs: ["build-job"]
deploy-dev-job:
extends: deploy-template
variables:
STAGE: "dev"
deploy-alpha-job:
extends: deploy-template
variables:
STAGE: "alpha"
deploy-qa-job:
extends: deploy-template
variables:
STAGE: "qa"when: manual로 배포는 자동이 아닌 수동 트리거로 설정했다. 빌드가 성공한 파이프라인에 한해 GitLab UI에서 버튼 하나로 원하는 환경에 배포할 수 있다.
needs: ["build-job"]으로 빌드가 성공한 경우에만 배포 버튼이 활성화된다.
AWS 키는 GitLab의 CI/CD Variables에 등록해 코드에 노출되지 않도록 했다.
도입 결과
빌드 안정성 확보
push마다 테스트, 린트, 빌드가 자동으로 실행되면서 문제를 즉시 발견할 수 있게 됐다. 빌드 실패 시점이 파이프라인 기록에 남아 추적도 쉬워졌다.
배포 프로세스 단순화
기존에는 로컬에서 빌드하고, AWS 설정이 된 특정 머신에서 CLI로 배포해야 했다. 이제는 파이프라인에서 버튼 하나로 끝난다. 새 팀원이 합류해도 배포를 위한 환경 설정이 필요 없어졌다.
코드 리뷰 정착
빌드 성공 후에만 머지할 수 있도록 규칙을 설정했더니 자연스럽게 PR 기반 코드 리뷰 프로세스가 자리잡혔다. 강제하지 않아도 흐름이 만들어진 것이 가장 인상적이었다.
피드백 루프 단축
누구나 안정적인 버전을 빠르게 배포할 수 있게 되면서 디자이너, 기획자에게 결과물을 공유하는 주기가 짧아졌다. 최종 오차가 줄어드는 효과로 이어졌다.
팁: gitlab-ci-local
.gitlab-ci.yml을 수정할 때마다 원격에 push해서 파이프라인을 트리거하는 건 번거롭고 Runner 사용량도 낭비다. gitlab-ci-local을 사용하면 로컬에서 특정 Job만 바로 실행해볼 수 있다.
# 특정 job만 실행
gitlab-ci-local lint-job설정 초기에 특히 유용했다.