[이제와서 시작하는 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 라이센스를 따릅니다.