금요일 오후 5시, 배포 버튼을 누르는 것이 두렵지 않다면
"금요일 배포 금지"라는 불문율이 있는 팀이 많습니다. 수동 배포 과정에서 실수가 발생하면 주말 내내 장애에 대응해야 하니까요. 하지만 잘 설계된 CI/CD 파이프라인이 있다면 이야기가 달라집니다. 자동화된 테스트가 코드 품질을 보장하고, 카나리 배포가 위험을 최소화하며, 롤백이 버튼 하나로 완료되기 때문입니다.
DORA(DevOps Research and Assessment)의 2024 State of DevOps Report에 따르면, 엘리트 수준의 팀은 하루에도 여러 번 배포하면서도 변경 실패율은 5% 미만을 유지합니다. 이것은 뛰어난 엔지니어 덕분이 아니라, 자동화된 파이프라인 덕분입니다.
---
CI와 CD — 무엇이 다른가
CI(Continuous Integration)는 코드 변경을 자주 메인 브랜치에 통합하고, 매 통합마다 자동으로 빌드와 테스트를 실행하는 관행입니다. 핵심은 "문제를 빨리 발견하는 것"이죠.
CD는 두 가지 의미를 가집니다. Continuous Delivery는 프로덕션 배포 "준비"까지 자동화하되 최종 배포는 수동 승인을 거치는 것이고, Continuous Deployment는 테스트를 통과한 모든 변경이 자동으로 프로덕션에 배포되는 것입니다.
대부분의 팀은 Continuous Delivery에서 시작하여, 신뢰도가 쌓이면 점진적으로 Continuous Deployment로 전환합니다.
---
GitHub Actions 기반 CI 파이프라인
기본 워크플로우 구조
```yaml # .github/workflows/ci.yml name: CI Pipeline
on: push: branches: [main, develop] pull_request: branches: [main]
concurrency: group: ci-${{ github.ref }} cancel-in-progress: true # 같은 브랜치의 이전 실행 취소
jobs: lint: runs-on: ubuntu-latest steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: node-version: 20 cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
test: runs-on: ubuntu-latest needs: lint # lint 통과 후 실행 strategy: matrix: shard: [1, 2, 3] # 테스트 병렬 실행 steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: node-version: 20 cache: 'npm'
- run: npm ci
- run: npm test -- --shard=${{ matrix.shard }}/3
- uses: actions/upload-artifact@v4
if: failure() with: name: test-results-${{ matrix.shard }} path: test-results/
build: runs-on: ubuntu-latest needs: [lint, test] steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: node-version: 20 cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with: name: build-output path: dist/ ```
캐싱 전략으로 빌드 시간 단축
```yaml
- uses: actions/cache@v4
with: path: | ~/.npm node_modules/.cache .next/cache key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-build- ```
`hashFiles('**/package-lock.json')`는 의존성이 변경되지 않으면 캐시를 재사용합니다. Next.js의 `.next/cache`도 캐싱하면 빌드 시간을 40~60% 줄일 수 있죠.
---
CD 파이프라인 — 배포 자동화
Docker 이미지 빌드 & 푸시
```yaml deploy-staging: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/develop' environment: staging steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with: push: true tags: ghcr.io/${{ github.repository }}:staging-${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ```
프로덕션 배포 — 수동 승인 게이트
```yaml deploy-production: runs-on: ubuntu-latest needs: deploy-staging if: github.ref == 'refs/heads/main' environment: name: production url: https://example.com steps:
- uses: actions/checkout@v4
# 배포 스크립트 실행
- run: |
echo "Deploying ${{ github.sha }} to production..." # kubectl set image deployment/web-app web-app=ghcr.io/repo:${github.sha} ```
GitHub의 `environment` 설정에서 "Required reviewers"를 지정하면, 프로덕션 배포 전 지정된 팀원의 승인이 필요합니다. 이 승인 게이트가 Continuous Delivery의 핵심이죠.
---
배포 전략 — 위험을 최소화하는 방법
Rolling Update
기존 인스턴스를 하나씩 새 버전으로 교체합니다. Kubernetes의 기본 배포 전략이며, 무중단 배포를 보장합니다.
Blue-Green 배포
새 버전(Green)을 기존 환경(Blue)과 동일하게 구성한 후, 트래픽을 한 번에 전환합니다. 문제 발생 시 트래픽을 Blue로 되돌리면 즉시 롤백됩니다. 인프라 비용이 두 배이지만, 가장 안전한 전략입니다.
카나리 배포
새 버전에 전체 트래픽의 일부(예: 5%)만 보내고, 에러율과 성능을 모니터링한 후 점진적으로 비율을 높입니다. Netflix, Google 등이 사용하는 방식으로, 대규모 서비스에서 위험을 최소화하는 데 효과적이죠.
---
테스트 자동화 피라미드
효과적인 CI 파이프라인은 적절한 테스트 전략에 기반합니다. Martin Fowler가 제안한 테스트 피라미드 모델을 따르는 것이 좋습니다.
단위 테스트 (70%): 개별 함수/컴포넌트 단위 테스트. 빠르고 안정적이며, 가장 많은 비중을 차지해야 합니다.
통합 테스트 (20%): API 엔드포인트, 데이터베이스 연동 등 여러 모듈의 상호작용을 검증합니다.
E2E 테스트 (10%): Playwright나 Cypress로 실제 사용자 시나리오를 검증합니다. 느리고 불안정할 수 있으므로, 핵심 사용자 경로(로그인, 결제 등)에만 적용하세요.
---
시크릿 관리와 환경 분리
CI/CD에서 가장 주의해야 할 것이 시크릿(비밀 정보) 관리입니다. API 키, 데이터베이스 비밀번호 같은 정보가 로그에 노출되거나, 코드에 하드코딩되는 사고가 빈번하게 발생합니다.
GitHub Actions에서는 Repository Secrets와 Environment Secrets를 분리하여 사용하세요. 환경별 시크릿은 해당 환경(staging, production)에서만 접근 가능하므로, 스테이징 파이프라인이 프로덕션 데이터베이스에 접근하는 사고를 방지할 수 있습니다.
CI/CD는 "도구"가 아니라 "문화"입니다. 파이프라인을 구축하는 것보다 중요한 것은, 팀 전체가 "작은 변경을 자주 통합하고, 자동 테스트를 신뢰하는" 습관을 기르는 것입니다. 처음부터 완벽한 파이프라인을 만들려 하지 말고, lint + 단위 테스트부터 시작해서 점진적으로 확장해나가시길 권합니다.