본문으로 건너뛰기

🚀 CI/CD란?

📖 정의

CI/CD는 Continuous Integration(지속적 통합)과 Continuous Deployment(지속적 배포)의 약자로, 코드 변경사항을 자동으로 테스트하고 배포하는 개발 방법론입니다. CI는 코드를 병합할 때마다 자동으로 빌드하고 테스트하며, CD는 테스트를 통과한 코드를 자동으로 프로덕션 환경에 배포합니다. 이를 통해 버그를 빠르게 발견하고 배포 주기를 단축할 수 있습니다.

🎯 비유로 이해하기

공장 자동화 라인

전통적인 개발 (수동)
개발자 1 → 코드 작성
개발자 2 → 코드 작성
개발자 3 → 코드 작성

일주일 후

팀장이 수동으로 병합

충돌 발생! 😱

수동으로 테스트

버그 발견! 😱

수동으로 배포

배포 실패! 😱

CI/CD (자동화)
개발자 1 → 코드 푸시 → 자동 테스트 ✅ → 자동 배포 🚀
개발자 2 → 코드 푸시 → 자동 테스트 ✅ → 자동 배포 🚀
개발자 3 → 코드 푸시 → 자동 테스트 ❌ → 즉시 알림!
(문제를 바로 발견하고 수정)

음식 배달

수동 배포 = 직접 배달
1. 음식 만들기 (개발)
2. 포장 확인 (테스트)
3. 주소 확인 (설정)
4. 직접 운전해서 배달 (배포)
5. 고객 확인 (검증)
→ 시간 오래 걸림, 실수 가능

CI/CD = 자동 배달 시스템
1. 음식 만들기 (개발)
2. 자동 포장 (자동 빌드)
3. 자동 품질 검사 (자동 테스트)
4. 로봇이 자동 배달 (자동 배포)
5. 실시간 추적 (모니터링)
→ 빠르고 정확함

⚙️ 작동 원리

1. CI/CD 파이프라인

코드 푸시 (Push)

┌─────────────────────────────┐
│ CI: 지속적 통합 │
│ │
│ 1. 코드 체크아웃 │
│ 2. 의존성 설치 │
│ 3. 린트 검사 │
│ 4. 빌드 │
│ 5. 유닛 테스트 │
│ 6. 통합 테스트 │
│ │
│ ✅ 모두 성공 │
│ ❌ 하나라도 실패 → 중단 │
└─────────────────────────────┘

┌─────────────────────────────┐
│ CD: 지속적 배포 │
│ │
│ 1. 스테이징 환경 배포 │
│ 2. E2E 테스트 │
│ 3. 프로덕션 배포 │
│ 4. 헬스 체크 │
│ 5. 알림 전송 │
└─────────────────────────────┘

🎉 배포 완료!

2. 전통적인 방식 vs CI/CD

전통적인 방식
┌─────────────────────────────────────┐
│ 월요일 │
│ - 개발자 A: 기능 1 개발 │
│ - 개발자 B: 기능 2 개발 │
│ - 개발자 C: 버그 수정 │
├─────────────────────────────────────┤
│ 화-목요일 │
│ - 각자 개발 계속 │
│ - 충돌 가능성 증가 😰 │
├─────────────────────────────────────┤
│ 금요일 (통합의 날) │
│ - 모든 코드 병합 │
│ - 충돌 해결에 하루 소요 😱 │
│ - 테스트 실행 → 많은 버그 발견 │
│ - 주말까지 야근 😭 │
└─────────────────────────────────────┘

CI/CD 방식
┌─────────────────────────────────────┐
│ 매일, 매 커밋마다 │
│ │
│ 개발자 A: 코드 푸시 │
│ → 자동 테스트 ✅ │
│ → 자동 배포 🚀 │
│ → 3분 후 프로덕션 반영 │
│ │
│ 개발자 B: 코드 푸시 │
│ → 자동 테스트 ❌ │
│ → 즉시 알림 │
│ → 바로 수정 (5분 소요) │
│ │
│ - 충돌 최소화 │
│ - 버그 빠른 발견 │
│ - 정시 퇴근 😊 │
└─────────────────────────────────────┘

3. CI/CD 구성 요소

┌──────────────────────────────────────┐
│ Version Control (버전 관리) │
│ Git, GitHub, GitLab │
└───────────────┬──────────────────────┘
↓ Push
┌──────────────────────────────────────┐
│ CI Server (지속적 통합 서버) │
│ GitHub Actions, Jenkins, CircleCI │
│ - 코드 체크아웃 │
│ - 자동 빌드 │
│ - 자동 테스트 │
└───────────────┬──────────────────────┘
↓ Pass
┌──────────────────────────────────────┐
│ Artifact Repository (빌드 저장소) │
│ Docker Hub, npm Registry │
└───────────────┬──────────────────────┘
↓ Deploy
┌──────────────────────────────────────┐
│ Deployment (배포) │
│ AWS, Heroku, Vercel, Netlify │
└───────────────┬──────────────────────┘
↓ Monitor
┌──────────────────────────────────────┐
│ Monitoring (모니터링) │
│ Sentry, DataDog, CloudWatch │
└──────────────────────────────────────┘

💡 실제 예시

GitHub Actions 기본 예시

# .github/workflows/ci.yml
# Node.js 프로젝트 CI/CD 파이프라인

name: CI/CD Pipeline

# 트리거: main 브랜치에 푸시되거나 PR이 생성될 때
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

# 환경 변수
env:
NODE_VERSION: '18'

# 작업 정의
jobs:
# ========== Job 1: 테스트 ==========
test:
name: 테스트 및 린트
runs-on: ubuntu-latest # Ubuntu 환경에서 실행

steps:
# 1. 코드 체크아웃
- name: 코드 체크아웃
uses: actions/checkout@v3

# 2. Node.js 설정
- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm' # npm 캐시 사용

# 3. 의존성 설치
- name: 의존성 설치
run: npm ci # npm install보다 빠르고 안정적

# 4. 린트 검사
- name: 린트 실행
run: npm run lint

# 5. 유닛 테스트
- name: 테스트 실행
run: npm test

# 6. 코드 커버리지
- name: 커버리지 업로드
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

# ========== Job 2: 빌드 ==========
build:
name: 빌드
needs: test # test 작업이 성공해야 실행
runs-on: ubuntu-latest

steps:
- name: 코드 체크아웃
uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: 의존성 설치
run: npm ci

# 프로덕션 빌드
- name: 프로덕션 빌드
run: npm run build
env:
NODE_ENV: production

# 빌드 결과물 업로드
- name: 빌드 결과물 업로드
uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/
retention-days: 7

# ========== Job 3: 배포 ==========
deploy:
name: 프로덕션 배포
needs: build # build 작업이 성공해야 실행
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # main 브랜치만 배포

steps:
- name: 코드 체크아웃
uses: actions/checkout@v3

# 빌드 결과물 다운로드
- name: 빌드 결과물 다운로드
uses: actions/download-artifact@v3
with:
name: build-files
path: dist/

# Vercel에 배포
- name: Vercel 배포
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'

# Slack 알림
- name: Slack 알림
if: always() # 성공/실패 상관없이 실행
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '배포가 ${{ job.status }}했습니다!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Docker 빌드 및 배포

# .github/workflows/docker.yml
# Docker 이미지 빌드 및 배포

name: Docker CI/CD

on:
push:
branches: [ main ]
tags:
- 'v*' # v1.0.0 같은 태그가 푸시되면 실행

env:
DOCKER_IMAGE: myapp
DOCKER_REGISTRY: docker.io

jobs:
docker:
name: Docker 빌드 및 푸시
runs-on: ubuntu-latest

steps:
- name: 코드 체크아웃
uses: actions/checkout@v3

# Docker 메타데이터 설정
- name: Docker 메타데이터 추출
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

# Docker Buildx 설정 (멀티 플랫폼 빌드)
- name: Docker Buildx 설정
uses: docker/setup-buildx-action@v2

# Docker Hub 로그인
- name: Docker Hub 로그인
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

# Docker 이미지 빌드 및 푸시
- name: Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # GitHub Actions 캐시 사용
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64 # 멀티 플랫폼

# 배포 (SSH로 서버 접속하여 컨테이너 재시작)
- name: 서버 배포
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
-p 3000:3000 \
--restart unless-stopped \
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker system prune -af

풀 리퀘스트 자동 검증

# .github/workflows/pr.yml
# PR 생성 시 자동으로 검증

name: Pull Request Check

on:
pull_request:
types: [ opened, synchronize, reopened ]

jobs:
# ========== 코드 품질 검사 ==========
lint:
name: 린트 및 포맷 검사
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 의존성 설치
run: npm ci

# ESLint
- name: ESLint 실행
run: npm run lint

# Prettier
- name: Prettier 검사
run: npm run format:check

# TypeScript 타입 검사
- name: TypeScript 타입 검사
run: npm run type-check

# ========== 테스트 ==========
test:
name: 테스트
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16, 18, 20] # 여러 Node 버전에서 테스트

steps:
- uses: actions/checkout@v3

- name: Node.js ${{ matrix.node-version }} 설정
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: 의존성 설치
run: npm ci

- name: 테스트 실행
run: npm test -- --coverage

- name: 테스트 결과 업로드
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-${{ matrix.node-version }}
path: coverage/

# ========== 보안 검사 ==========
security:
name: 보안 검사
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'

# npm audit
- name: npm 보안 검사
run: npm audit --audit-level=moderate

# Snyk 보안 스캔
- name: Snyk 보안 스캔
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

# ========== 성능 테스트 ==========
performance:
name: 성능 테스트
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 의존성 설치
run: npm ci

- name: 빌드
run: npm run build

# Lighthouse CI
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
http://localhost:3000
uploadArtifacts: true

# ========== PR 코멘트 ==========
comment:
name: PR 코멘트
needs: [lint, test, security]
runs-on: ubuntu-latest
if: always()

steps:
- name: PR 코멘트 작성
uses: actions/github-script@v6
with:
script: |
const statuses = {
lint: '${{ needs.lint.result }}',
test: '${{ needs.test.result }}',
security: '${{ needs.security.result }}'
};

let message = '## 🤖 CI 검사 결과\n\n';

for (const [job, status] of Object.entries(statuses)) {
const emoji = status === 'success' ? '✅' : '❌';
message += `${emoji} **${job}**: ${status}\n`;
}

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});

스케줄 기반 작업

# .github/workflows/scheduled.yml
# 정기적으로 실행되는 작업

name: Scheduled Tasks

on:
schedule:
# 매일 오전 9시 (UTC 0시)
- cron: '0 0 * * *'
workflow_dispatch: # 수동 실행도 가능

jobs:
# ========== 의존성 업데이트 확인 ==========
dependency-check:
name: 의존성 업데이트 확인
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'

# npm outdated 확인
- name: 오래된 패키지 확인
run: npm outdated || true

# Dependabot 대안
- name: 업데이트 가능한 패키지 확인
run: |
npx npm-check-updates

# ========== 백업 ==========
backup:
name: 데이터베이스 백업
runs-on: ubuntu-latest

steps:
- name: 데이터베이스 백업
run: |
# 실제로는 데이터베이스 백업 스크립트 실행
echo "데이터베이스 백업 중..."

# S3에 업로드
- name: S3에 백업 업로드
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2

- name: S3 업로드
run: |
aws s3 cp backup.sql s3://my-backups/$(date +%Y%m%d).sql

# ========== 성능 모니터링 ==========
performance-monitor:
name: 성능 모니터링
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Lighthouse 실행
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://myapp.com
uploadArtifacts: true

# 성능 저하 시 Slack 알림
- name: 성능 저하 알림
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: '⚠️ 웹사이트 성능이 저하되었습니다!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

멀티 환경 배포

# .github/workflows/multi-env.yml
# 개발, 스테이징, 프로덕션 환경별 배포

name: Multi-Environment Deployment

on:
push:
branches:
- develop # 개발 환경
- staging # 스테이징 환경
- main # 프로덕션 환경

jobs:
deploy:
name: 배포
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 의존성 설치
run: npm ci

# 환경별 빌드
- name: 빌드
run: npm run build
env:
NODE_ENV: ${{
github.ref == 'refs/heads/main' && 'production' ||
github.ref == 'refs/heads/staging' && 'staging' ||
'development'
}}

# 개발 환경 배포
- name: 개발 환경 배포
if: github.ref == 'refs/heads/develop'
run: |
echo "개발 환경에 배포 중..."
# 개발 서버 배포 명령
env:
API_URL: https://api-dev.myapp.com

# 스테이징 환경 배포
- name: 스테이징 환경 배포
if: github.ref == 'refs/heads/staging'
run: |
echo "스테이징 환경에 배포 중..."
# 스테이징 서버 배포 명령
env:
API_URL: https://api-staging.myapp.com

# 프로덕션 환경 배포
- name: 프로덕션 환경 배포
if: github.ref == 'refs/heads/main'
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: myapp-service
cluster: production-cluster
wait-for-service-stability: true
env:
API_URL: https://api.myapp.com

# 배포 후 헬스 체크
- name: 헬스 체크
run: |
sleep 30 # 30초 대기

URL=${{
github.ref == 'refs/heads/main' && 'https://myapp.com' ||
github.ref == 'refs/heads/staging' && 'https://staging.myapp.com' ||
'https://dev.myapp.com'
}}

STATUS=$(curl -s -o /dev/null -w "%{http_code}" $URL/health)

if [ $STATUS -eq 200 ]; then
echo "✅ 헬스 체크 성공"
else
echo "❌ 헬스 체크 실패: $STATUS"
exit 1
fi

# 배포 성공 알림
- name: 배포 알림
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: |
🚀 배포 완료!
환경: ${{ github.ref_name }}
커밋: ${{ github.sha }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

🤔 자주 묻는 질문

Q1. CI/CD를 도입하면 어떤 이점이 있나요?

A:

✅ 주요 이점:

1. 빠른 버그 발견
전통적: 일주일 후 통합 시 발견
CI/CD: 커밋 후 5분 내 발견
→ 수정 비용 90% 감소

2. 배포 시간 단축
전통적: 수동 배포에 2시간
CI/CD: 자동 배포에 5분
→ 하루에 여러 번 배포 가능

3. 코드 품질 향상
- 자동 린트, 테스트
- 코드 리뷰 자동화
- 버그 감소

4. 개발자 생산성 향상
- 수동 작업 제거
- 반복 작업 자동화
- 개발에만 집중

5. 안정성 향상
- 자동 테스트
- 자동 롤백
- 일관된 배포 프로세스

6. 협업 개선
- 코드 충돌 최소화
- 빠른 피드백
- 투명한 프로세스

실제 사례:
회사 A: CI/CD 도입 전
- 배포: 월 1회
- 배포 시간: 4시간
- 배포 실패율: 30%
- 버그 발견 시간: 평균 3일

회사 A: CI/CD 도입 후
- 배포: 하루 10회
- 배포 시간: 5분
- 배포 실패율: 5%
- 버그 발견 시간: 평균 10분

Q2. GitHub Actions vs Jenkins vs CircleCI?

A:

// ========== GitHub Actions ==========
장점:
- GitHub과 완벽한 통합
- 무료 (공개 저장소,2000분 무료)
- 설정 간단 (YAML 파일)
- Marketplace에 다양한 액션

단점:
- GitHub에 종속적
- 복잡한 워크플로우는 제한적

적합한 경우:
- GitHub 사용 중
- 간단한 CI/CD
- 빠른 시작 필요

예시:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test

// ========== Jenkins ==========
장점:
- 가장 오래되고 성숙함
- 플러그인 생태계 방대
- 완전한 커스터마이징
- 자체 호스팅 (비용 없음)

단점:
- 설정 복잡
- UI가 오래됨
- 유지보수 필요
- 학습 곡선 높음

적합한 경우:
- 온프레미스 환경
- 복잡한 파이프라인
- 레거시 시스템 통합

// ========== CircleCI ==========
장점:
- 빠른 빌드 속도
- Docker 지원 우수
- 병렬 처리 강력
- UI 직관적

단점:
- 유료 (무료 티어 제한적)
- GitHub/GitLab에만 지원

적합한 경우:
- 성능 중요
- Docker 많이 사용
- 병렬 테스트 필요

// ========== 비교표 ==========
특성 | GitHub Actions | Jenkins | CircleCI
--------------|----------------|---------|----------
가격 | 무료(제한적) | 무료 | 유료
설정 난이도 | 쉬움 | 어려움 | 보통
성능 | 보통 | 높음 | 높음
통합성 | GitHub 완벽 | 범용 | 범용
유지보수 | 불필요 | 필요 | 불필요
Docker 지원 | 보통 | 좋음 | 매우 좋음

추천:
- 소규모 팀, GitHub 사용 → GitHub Actions
- 대규모 팀, 복잡한 요구사항 → Jenkins
- 성능 중요, 예산 있음 → CircleCI

Q3. 비밀 키(Secrets)는 어떻게 관리하나요?

A:

# ========== GitHub Secrets 설정 ==========
# Repository → Settings → Secrets and variables → Actions

# 사용 방법
- name: 배포
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy

# ========== 환경별 Secrets ==========
# Repository → Settings → Environments

# 프로덕션 환경
environment: production
env:
API_URL: ${{ secrets.PROD_API_URL }}

# 스테이징 환경
environment: staging
env:
API_URL: ${{ secrets.STAGING_API_URL }}

# ========== 보안 모범 사례 ==========

# ✅ 좋은 예
- name: 배포
env:
# Secrets에서 가져오기
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
run: |
# 환경변수 사용
aws s3 sync dist/ s3://my-bucket

# ❌ 나쁜 예
- name: 배포
run: |
# 코드에 하드코딩 (절대 금지!)
export AWS_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

# ========== .env 파일 생성 ==========
- name: .env 파일 생성
run: |
cat << EOF > .env
API_URL=${{ secrets.API_URL }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
EOF

# ========== Docker Secrets ==========
- name: Docker 빌드
env:
DOCKER_BUILDKIT: 1
run: |
docker build \
--secret id=api_key,env=API_KEY \
--secret id=db_url,env=DATABASE_URL \
-t myapp .

# Dockerfile
# syntax=docker/dockerfile:1
FROM node:18
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) npm install

# ========== Vault 사용 (고급) ==========
- name: HashiCorp Vault에서 Secrets 가져오기
uses: hashicorp/vault-action@v2
with:
url: https://vault.mycompany.com
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/production api_key | API_KEY ;
secret/data/production db_url | DATABASE_URL

Q4. 테스트가 실패하면 어떻게 되나요?

A:

# ========== 기본 동작: 파이프라인 중단 ==========
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 테스트 실행
run: npm test
# 테스트 실패 시 → 여기서 중단
# 이후 단계 실행 안 됨

deploy:
needs: test # test가 성공해야만 실행
runs-on: ubuntu-latest
steps:
- name: 배포
run: npm run deploy
# test 실패 시 이 단계는 실행되지 않음 ✅

# ========== 실패해도 계속 실행 ==========
- name: 테스트 실행
run: npm test
continue-on-error: true # 실패해도 계속
# ⚠️ 권장하지 않음

# ========== 조건부 실행 ==========
- name: 실패 시 알림
if: failure() # 이전 단계가 실패한 경우에만
uses: 8398a7/action-slack@v3
with:
status: failure
text: '❌ 테스트 실패!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

- name: 성공 시 배포
if: success() # 모든 단계가 성공한 경우에만
run: npm run deploy

- name: 항상 실행
if: always() # 성공/실패 상관없이
run: npm run cleanup

# ========== 재시도 ==========
- name: 불안정한 테스트 (재시도)
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: npm run test:e2e

# ========== 실패 처리 예시 ==========
- name: 테스트
id: test
run: npm test

- name: 테스트 실패 시 이슈 생성
if: failure() && steps.test.outcome == 'failure'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '❌ 테스트 실패',
body: '테스트가 실패했습니다. 확인이 필요합니다.',
labels: ['bug', 'ci']
});

# ========== 롤백 ==========
- name: 배포
id: deploy
run: npm run deploy

- name: 배포 실패 시 롤백
if: failure() && steps.deploy.outcome == 'failure'
run: |
echo "배포 실패! 이전 버전으로 롤백 중..."
npm run rollback

Q5. 비용을 절감하려면?

A:

# ========== 캐싱 활용 ==========
- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm' # npm 캐싱 (빌드 시간 50% 단축)

- name: 의존성 캐싱
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

# ========== 조건부 실행 ==========
on:
push:
branches: [ main ]
paths: # 특정 파일 변경 시에만 실행
- 'src/**'
- 'package.json'
# README.md 변경 시에는 실행 안 함 → 비용 절감

# ========== 동시 실행 제한 ==========
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # 이전 실행 취소
# → 불필요한 중복 실행 방지

# ========== Self-hosted Runner ==========
# 자체 서버 사용 (무료)
jobs:
build:
runs-on: self-hosted # GitHub Actions 무료 분 사용 안 함
steps:
- run: npm run build

# ========== 짧은 작업만 실행 ==========
- name: 변경된 파일 확인
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'

- name: 프론트엔드 테스트
if: steps.changes.outputs.frontend == 'true'
run: npm run test:frontend
# 프론트엔드 변경 시에만 실행 → 시간 절약

- name: 백엔드 테스트
if: steps.changes.outputs.backend == 'true'
run: npm run test:backend

# ========== 비용 예시 ==========
# GitHub Actions 무료 티어:
# - 공개 저장소: 무제한
# - 비공개 저장소: 월 2000분

# 최적화 전:
# - 매 커밋마다 전체 테스트 (20분)
# - 하루 10번 푸시
# - 월 사용량: 10 × 20 × 30 = 6000분
# - 초과 비용: (6000 - 2000) × $0.008 = $32

# 최적화 후:
# - 캐싱으로 10분 단축
# - 변경된 부분만 테스트
# - 평균 빌드 시간 5분
# - 월 사용량: 10 × 5 × 30 = 1500분
# - 비용: $0 (무료 티어 내)
# → 월 $32 절약!

🎓 다음 단계

CI/CD를 이해했다면, 다음을 학습해보세요:

  1. Git이란? (문서 작성 예정) - 버전 관리 기초
  2. Docker란? (문서 작성 예정) - 컨테이너화 배포
  3. TDD란? (문서 작성 예정) - 테스트 주도 개발

실습해보기

# ========== 1. GitHub Actions 시작하기 ==========

# 1) 저장소에 워크플로우 파일 생성
mkdir -p .github/workflows
cat > .github/workflows/ci.yml << 'EOF'
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
EOF

# 2) 커밋 및 푸시
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push

# 3) GitHub에서 Actions 탭 확인
# → 자동으로 실행되는 워크플로우 확인!

# ========== 2. 로컬에서 테스트 (act) ==========
# act를 사용하면 로컬에서 GitHub Actions 테스트 가능

# act 설치 (macOS)
brew install act

# 워크플로우 실행
act push

# 특정 job만 실행
act -j test

# ========== 3. 배포 자동화 예시 ==========

# Vercel 배포
npm install -g vercel
vercel login
vercel --prod

# Netlify 배포
npm install -g netlify-cli
netlify login
netlify deploy --prod

# Heroku 배포
heroku login
git push heroku main

🎬 마무리

CI/CD는 현대 개발의 필수 도구입니다:

  • CI: 코드 변경사항을 자동으로 테스트하고 통합
  • CD: 테스트를 통과한 코드를 자동으로 배포
  • 장점: 빠른 피드백, 안정성 향상, 생산성 증가
  • 도구: GitHub Actions, Jenkins, CircleCI 등

자동화로 개발에만 집중하고 더 빠르게 배포하세요! 🚀