포스트

[이제와서 시작하는 Claude AI 마스터하기 #14] CI/CD 파이프라인 구축

[이제와서 시작하는 Claude AI 마스터하기 #14] CI/CD 파이프라인 구축

자동화된 개발 워크플로우 구축

Claude Code는 단순한 코드 작성을 넘어 전체 CI/CD 파이프라인을 설계하고 구현할 수 있습니다. 코드 커밋부터 프로덕션 배포까지 모든 과정을 자동화합니다.

CI/CD 파이프라인 설계

1. 파이프라인 아키텍처

graph LR
    A[Code Push] --> B[CI Trigger]
    B --> C[Code Analysis]
    C --> D[Build]
    D --> E[Test]
    E --> F[Security Scan]
    
    F --> G{Pass?}
    G -->|Yes| H[Deploy to Staging]
    G -->|No| I[Notify Developer]
    
    H --> J[E2E Tests]
    J --> K{Pass?}
    K -->|Yes| L[Deploy to Production]
    K -->|No| M[Rollback]
    
    L --> N[Monitor]
    N --> O[Alert if Issues]

2. GitHub Actions 파이프라인 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Claude가 생성한 .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '18.x'
  DOCKER_REGISTRY: ghcr.io

jobs:
  # 1. 코드 품질 검사
  code-quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: $
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
      
      - name: Run TypeScript check
        run: npm run type-check
      
      - name: Code complexity analysis
        run: npx code-complexity . --max 10

  # 2. 테스트 실행
  test:
    runs-on: ubuntu-latest
    needs: code-quality
    strategy:
      matrix:
        test-suite: [unit, integration, e2e]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup test environment
        uses: ./.github/actions/setup-test-env
        with:
          test-type: $
      
      - name: Run $ tests
        run: npm run test:$
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          flags: $

  # 3. 보안 스캔
  security:
    runs-on: ubuntu-latest
    needs: code-quality
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: $
      
      - name: Run OWASP dependency check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'my-app'
          path: '.'
          format: 'HTML'
      
      - name: Upload security reports
        uses: actions/upload-artifact@v3
        with:
          name: security-reports
          path: reports/

  # 4. 빌드 및 도커 이미지 생성
  build:
    runs-on: ubuntu-latest
    needs: [test, security]
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: $
          username: $
          password: $
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            $/$:latest
            $/$:$
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # 5. 스테이징 배포
  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/develop'
    environment:
      name: staging
      url: https://staging.example.com
    
    steps:
      - name: Deploy to Kubernetes
        uses: azure/k8s-deploy@v4
        with:
          manifests: |
            k8s/staging/
          images: |
            $/$:$
          namespace: staging

  # 6. 프로덕션 배포
  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    
    steps:
      - name: Create deployment
        uses: chrnorm/deployment-action@v2
        id: deployment
        with:
          token: $
          environment: production
      
      - name: Deploy to production
        uses: ./.github/actions/deploy-prod
        with:
          image-tag: $
      
      - name: Update deployment status
        if: always()
        uses: chrnorm/deployment-status@v2
        with:
          token: $
          deployment-id: $
          state: $

3. GitLab CI/CD 파이프라인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# Claude가 생성한 .gitlab-ci.yml
stages:
  - validate
  - test
  - build
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""

# 캐시 설정
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/

# 1. 코드 검증
lint:
  stage: validate
  image: node:18-alpine
  script:
    - npm ci --cache .npm --prefer-offline
    - npm run lint
    - npm run format:check
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

# 2. 테스트
test:unit:
  stage: test
  image: node:18-alpine
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
  script:
    - npm ci --cache .npm --prefer-offline
    - npm run test:unit -- --coverage
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    paths:
      - coverage/

test:integration:
  stage: test
  image: node:18-alpine
  services:
    - postgres:14
    - redis:7-alpine
  variables:
    POSTGRES_DB: test_db
    POSTGRES_USER: test_user
    POSTGRES_PASSWORD: test_pass
  script:
    - npm ci --cache .npm --prefer-offline
    - npm run test:integration

# 3. 보안 스캔
security:dependencies:
  stage: test
  image: node:18-alpine
  script:
    - npm audit --production
  allow_failure: true

security:secrets:
  stage: test
  image: trufflesecurity/trufflehog:latest
  script:
    - trufflehog git file://. --since-commit HEAD~5

# 4. 도커 빌드
build:docker:
  stage: build
  image: docker:24.0.5
  services:
    - docker:24.0.5-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
    - develop

# 5. 배포
deploy:staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n staging
    - kubectl rollout status deployment/app -n staging
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

deploy:production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n production
    - kubectl rollout status deployment/app -n production
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

고급 CI/CD 기능

1. 멀티 환경 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Claude가 생성한 배포 설정 관리자
interface Environment {
  name: string;
  url: string;
  branch: string;
  autoDeply: boolean;
  approvers?: string[];
}

const environments: Environment[] = [
  {
    name: 'development',
    url: 'https://dev.example.com',
    branch: 'develop',
    autoDeply: true
  },
  {
    name: 'staging',
    url: 'https://staging.example.com',
    branch: 'release/*',
    autoDeply: true
  },
  {
    name: 'production',
    url: 'https://example.com',
    branch: 'main',
    autoDeply: false,
    approvers: ['team-lead', 'devops-lead']
  }
];

// 환경별 설정 파일 생성
environments.forEach(env => {
  generateDeploymentConfig(env);
  generateKubernetesManifests(env);
  generateSecretsConfig(env);
});

2. 블루-그린 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Claude가 생성한 블루-그린 배포 스크립트
name: Blue-Green Deployment

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options:
          - staging
          - production

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Setup deployment
        id: setup
        run: |
          if [ "$" == "production" ]; then
            echo "NAMESPACE=prod" >> $GITHUB_OUTPUT
            echo "DOMAIN=example.com" >> $GITHUB_OUTPUT
          else
            echo "NAMESPACE=staging" >> $GITHUB_OUTPUT
            echo "DOMAIN=staging.example.com" >> $GITHUB_OUTPUT
          fi
      
      - name: Deploy to Green environment
        run: |
          # 새 버전을 Green 환경에 배포
          kubectl apply -f k8s/app-green.yaml -n $
          kubectl set image deployment/app-green app=$IMAGE:$TAG -n $
          
          # Green 환경 준비 확인
          kubectl wait --for=condition=ready pod -l app=app-green -n $ --timeout=300s
      
      - name: Run smoke tests
        run: |
          # Green 환경에 대한 스모크 테스트
          npm run test:smoke -- --url https://green.$
      
      - name: Switch traffic to Green
        run: |
          # 트래픽을 Green으로 전환
          kubectl patch service app -n $ -p '{"spec":{"selector":{"version":"green"}}}'
      
      - name: Monitor new deployment
        run: |
          # 5분간 모니터링
          npm run monitor -- --duration 5m --threshold 0.1
      
      - name: Cleanup Blue environment
        if: success()
        run: |
          # 이전 Blue 환경 정리
          kubectl delete deployment app-blue -n $
          kubectl label deployment app-green version=blue --overwrite -n $

3. 카나리 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Claude가 생성한 카나리 배포 구성
const canaryDeployment = {
  stages: [
    {
      name: "1% traffic",
      percentage: 1,
      duration: "5m",
      metrics: {
        errorRate: { threshold: 0.1 },
        latency: { p99: 1000 }
      }
    },
    {
      name: "10% traffic",
      percentage: 10,
      duration: "10m",
      metrics: {
        errorRate: { threshold: 0.05 },
        latency: { p99: 800 }
      }
    },
    {
      name: "50% traffic",
      percentage: 50,
      duration: "30m",
      metrics: {
        errorRate: { threshold: 0.01 },
        latency: { p99: 500 }
      }
    }
  ],
  rollback: {
    automatic: true,
    conditions: [
      "error_rate > threshold",
      "latency_p99 > threshold",
      "health_check_failures > 5"
    ]
  }
};

// Kubernetes 매니페스트 생성
const generateCanaryManifest = (stage) => ({
  apiVersion: "flagger.app/v1beta1",
  kind: "Canary",
  metadata: {
    name: "app-canary"
  },
  spec: {
    targetRef: {
      apiVersion: "apps/v1",
      kind: "Deployment",
      name: "app"
    },
    progressDeadlineSeconds: 60,
    service: {
      port: 80
    },
    analysis: {
      interval: "1m",
      threshold: 5,
      maxWeight: stage.percentage,
      stepWeight: stage.percentage / 5
    },
    metrics: Object.entries(stage.metrics).map(([name, config]) => ({
      name,
      threshold: config.threshold || config.p99,
      interval: "30s"
    }))
  }
});

모니터링과 알림

1. 파이프라인 모니터링 대시보드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Claude가 생성한 모니터링 설정
interface PipelineMetrics {
  buildTime: number;
  testCoverage: number;
  deploymentFrequency: number;
  leadTime: number;
  mttr: number; // Mean Time To Recovery
  changeFailureRate: number;
}

class PipelineMonitor {
  async collectMetrics(): Promise<PipelineMetrics> {
    const metrics = await Promise.all([
      this.getAverageBuildTime(),
      this.getTestCoverage(),
      this.getDeploymentFrequency(),
      this.getLeadTime(),
      this.getMTTR(),
      this.getChangeFailureRate()
    ]);
    
    return {
      buildTime: metrics[0],
      testCoverage: metrics[1],
      deploymentFrequency: metrics[2],
      leadTime: metrics[3],
      mttr: metrics[4],
      changeFailureRate: metrics[5]
    };
  }
  
  async sendAlert(metric: string, value: number, threshold: number) {
    if (value > threshold) {
      await this.notificationService.send({
        channel: 'slack',
        message: `⚠️ ${metric} exceeded threshold: ${value} > ${threshold}`,
        severity: 'warning'
      });
    }
  }
}

2. 자동 롤백 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 자동 롤백 워크플로우
name: Auto Rollback

on:
  workflow_run:
    workflows: ["Deploy to Production"]
    types: [completed]

jobs:
  monitor-and-rollback:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    
    steps:
      - name: Monitor application health
        id: health-check
        run: |
          for i in {1..10}; do
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/health)
            if [ $STATUS -ne 200 ]; then
              echo "Health check failed: $STATUS"
              echo "healthy=false" >> $GITHUB_OUTPUT
              exit 0
            fi
            sleep 30
          done
          echo "healthy=true" >> $GITHUB_OUTPUT
      
      - name: Check error rates
        id: error-check
        run: |
          ERROR_RATE=$(curl -s https://monitoring.example.com/api/error-rate)
          if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
            echo "High error rate detected: $ERROR_RATE"
            echo "errors_ok=false" >> $GITHUB_OUTPUT
          else
            echo "errors_ok=true" >> $GITHUB_OUTPUT
          fi
      
      - name: Rollback if needed
        if: steps.health-check.outputs.healthy == 'false' || steps.error-check.outputs.errors_ok == 'false'
        run: |
          # 이전 버전으로 롤백
          kubectl rollout undo deployment/app -n production
          
          # 알림 전송
          curl -X POST $ \
            -H 'Content-type: application/json' \
            -d '{"text":"🚨 Production deployment rolled back due to health check failures"}'

성능 최적화

1. 병렬 처리 최적화

1
2
3
4
5
6
7
8
9
10
11
12
13
# 병렬 작업 최적화
jobs:
  # 매트릭스 전략으로 병렬 테스트
  test:
    strategy:
      matrix:
        node-version: [16.x, 18.x, 20.x]
        os: [ubuntu-latest, windows-latest, macos-latest]
        test-chunk: [1, 2, 3, 4]
    runs-on: $
    steps:
      - name: Run test chunk $
        run: npm run test:chunk:$

2. 캐싱 전략

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 효율적인 캐싱 설정
- name: Cache dependencies
  uses: actions/cache@v3
  with:
    path: |
      ~/.npm
      ~/.cache
      node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      $-node-
      
- name: Cache Docker layers
  uses: docker/build-push-action@v4
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

보안 강화

1. 시크릿 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Claude가 생성한 시크릿 관리 시스템
class SecretManager {
  private vault: HashiCorp.Vault;
  
  async getSecrets(environment: string): Promise<Secrets> {
    const path = `secret/data/${environment}`;
    const response = await this.vault.read(path);
    
    return {
      database: {
        host: response.data.db_host,
        password: response.data.db_password,
        user: response.data.db_user
      },
      api: {
        key: response.data.api_key,
        secret: response.data.api_secret
      }
    };
  }
  
  async rotateSecrets(environment: string): Promise<void> {
    // 시크릿 자동 순환
    const newSecrets = await this.generateNewSecrets();
    await this.vault.write(`secret/data/${environment}`, newSecrets);
    await this.updateApplications(environment, newSecrets);
  }
}

2. 취약점 스캔 자동화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 종합적인 보안 스캔
security-scan:
  runs-on: ubuntu-latest
  steps:
    - name: Container scan
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: $
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: SAST scan
      uses: github/super-linter@v4
      env:
        DEFAULT_BRANCH: main
        GITHUB_TOKEN: $
    
    - name: License scan
      run: |
        npx license-checker --production --failOn "GPL"

다음 편 예고

다음 편에서는 “디버깅과 문제 해결”을 다룰 예정입니다. Claude Code를 활용한 효율적인 디버깅 방법과 문제 해결 전략을 알아보겠습니다.


💡 오늘의 과제: 현재 프로젝트에 기본적인 CI/CD 파이프라인을 Claude Code로 구축해보세요. 테스트, 빌드, 배포 단계를 포함하여 자동화해보세요!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.