GitLab CI/CD 통합
읽는 시간: 12분 | 난이도: 고급자
GitLab CI/CD 파이프라인에 Claude Code를 통합하여 개발 워크플로우를 자동화합니다.
개요
GitLab CI/CD와 Claude Code를 통합하면:
- 자동화된 AI 코드 리뷰
- AI 기반 테스트 생성
- 자동 문서 업데이트
- 파이프라인 실패 자동 분석
사전 요구사항
- GitLab 계정 (Self-hosted 또는 GitLab.com)
- Anthropic API 키
- GitLab CI/CD 기본 지식
API 키 설정
GitLab CI/CD 변수 설정
- GitLab 프로젝트 → Settings → CI/CD → Variables
- 다음 변수 추가:
ANTHROPIC_API_KEY = sk-ant-... (Protected, Masked)
기본 파이프라인 설정
.gitlab-ci.yml 기본 구조
stages:
- test
- review
- deploy
variables:
CLAUDE_MODEL: "claude-opus-4-5-20251001"
# AI 코드 리뷰 Job
ai-code-review:
stage: review
image: python:3.11-slim
before_script:
- pip install anthropic
script:
- python scripts/claude-review.py
only:
- merge_requests
allow_failure: true
코드 리뷰 스크립트
# scripts/claude-review.py
import os
import subprocess
import anthropic
def get_mr_diff():
"""MR 변경사항 가져오기"""
result = subprocess.run(
['git', 'diff', 'origin/main...HEAD'],
capture_output=True, text=True
)
return result.stdout
def review_code(diff: str) -> str:
client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])
message = client.messages.create(
model=os.environ.get('CLAUDE_MODEL', 'claude-opus-4-5-20251001'),
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""다음 코드 변경사항을 리뷰해주세요:
```diff
{diff[:8000]} # 토큰 제한 고려
다음 항목을 검토해주세요:
- 버그 및 논리적 오류
- 보안 취약점
- 성능 문제
- 코드 스타일 및 가독성
- 테스트 커버리지
Markdown 형식으로 작성해주세요.""" }] ) return message.content[0].text
def post_comment(review: str): """GitLab MR에 리뷰 코멘트 게시""" import urllib.request import json
gitlab_token = os.environ.get('GITLAB_TOKEN')
project_id = os.environ.get('CI_PROJECT_ID')
mr_iid = os.environ.get('CI_MERGE_REQUEST_IID')
gitlab_url = os.environ.get('CI_SERVER_URL', 'https://gitlab.com')
if not all([gitlab_token, project_id, mr_iid]):
print("GitLab 환경 변수가 설정되지 않았습니다. 리뷰를 출력합니다:")
print(review)
return
url = f"{gitlab_url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/notes"
data = json.dumps({"body": f"## AI 코드 리뷰\n\n{review}"}).encode()
req = urllib.request.Request(url, data=data, headers={
'PRIVATE-TOKEN': gitlab_token,
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
if name == "main": diff = get_mr_diff() if not diff.strip(): print("변경사항 없음") exit(0)
review = review_code(diff)
post_comment(review)
print("코드 리뷰 완료")
## 자동 테스트 생성
```yaml
# .gitlab-ci.yml에 추가
generate-tests:
stage: test
image: node:20
before_script:
- npm install anthropic
script:
- node scripts/generate-tests.js
artifacts:
paths:
- generated-tests/
expire_in: 1 week
only:
- merge_requests
// scripts/generate-tests.js
const Anthropic = require('@anthropic-ai/sdk');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
async function generateTests(sourceFile) {
const code = fs.readFileSync(sourceFile, 'utf8');
const message = await client.messages.create({
model: 'claude-opus-4-5-20251001',
max_tokens: 2048,
messages: [{
role: 'user',
content: `다음 코드에 대한 Jest 단위 테스트를 작성해주세요:
\`\`\`javascript
${code}
\`\`\`
요구사항:
- 모든 public 함수 커버
- 엣지 케이스 포함
- 설명적인 test 이름 사용
- 코드만 출력 (설명 없이)`
}]
});
return message.content[0].text;
}
// 변경된 파일에 대한 테스트 생성
const changedFiles = execSync('git diff --name-only origin/main...HEAD')
.toString().trim().split('\n')
.filter(f => f.endsWith('.js') && !f.includes('test'));
for (const file of changedFiles) {
if (fs.existsSync(file)) {
console.log(`테스트 생성 중: ${file}`);
generateTests(file).then(tests => {
const testDir = 'generated-tests';
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir);
const testFile = path.join(testDir, path.basename(file, '.js') + '.test.js');
fs.writeFileSync(testFile, tests);
});
}
}
파이프라인 실패 분석
analyze-failure:
stage: .post
image: python:3.11-slim
before_script:
- pip install anthropic
script:
- python scripts/analyze-failure.py
when: on_failure
variables:
CI_JOB_LOG: "${CI_JOB_LOG}"
# scripts/analyze-failure.py
import os
import anthropic
def analyze_failure():
client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])
# CI 환경에서 로그 가져오기
job_log = os.environ.get('CI_JOB_LOG', '로그를 가져올 수 없습니다')
job_name = os.environ.get('CI_JOB_NAME', '알 수 없는 Job')
message = client.messages.create(
model='claude-opus-4-5-20251001',
max_tokens=1024,
messages=[{
'role': 'user',
'content': f"""GitLab CI Job '{job_name}'이 실패했습니다.
실패 로그:
{job_log[-4000:]} # 마지막 4000자
분석해주세요:
1. 실패 원인
2. 즉각적인 해결 방법
3. 재발 방지 방법"""
}]
)
analysis = message.content[0].text
print("=" * 60)
print("AI 실패 분석 결과:")
print("=" * 60)
print(analysis)
analyze_failure()
전체 파이프라인 예시
# .gitlab-ci.yml 전체 예시
stages:
- test
- ai-review
- build
- deploy
variables:
CLAUDE_MODEL: "claude-opus-4-5-20251001"
unit-tests:
stage: test
script:
- npm test
ai-code-review:
stage: ai-review
image: python:3.11-slim
before_script:
- pip install anthropic
script:
- python scripts/claude-review.py
only:
- merge_requests
allow_failure: true
ai-security-scan:
stage: ai-review
image: python:3.11-slim
before_script:
- pip install anthropic
script:
- python scripts/security-scan.py
only:
- merge_requests
allow_failure: true
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
deploy-staging:
stage: deploy
script:
- echo "스테이징 배포"
environment:
name: staging
only:
- develop
analyze-failure:
stage: .post
image: python:3.11-slim
before_script:
- pip install anthropic
script:
- python scripts/analyze-failure.py
when: on_failure
GitHub Actions와의 차이점
| 항목 | GitHub Actions | GitLab CI/CD |
|---|---|---|
| 설정 파일 | .github/workflows/*.yml |
.gitlab-ci.yml |
| 환경 변수 | Secrets | CI/CD Variables |
| 러너 | GitHub-hosted / Self-hosted | GitLab-hosted / Self-hosted |
| 아티팩트 | Actions artifacts | GitLab artifacts |
| 캐시 | actions/cache |
cache: 키워드 |
보안 모범 사례
API 키 보호:
variables: ANTHROPIC_API_KEY: value: $ANTHROPIC_API_KEY protected: true masked: true코드 크기 제한: 대형 diff는 분할하여 처리
비용 관리:
allow_failure: true로 AI 리뷰 실패가 파이프라인을 막지 않도록민감한 정보 필터링: API 키, 비밀번호가 로그에 출력되지 않도록 주의
다음 단계
- GitHub Actions 통합 - GitHub 파이프라인
- 모범 사례 - Claude Code 효과적 활용
- 서브에이전트 - 병렬 자동화 처리
