Skip to content

필사 모드: CI/CD 베스트 프랙티스 2025: 팀을 위한 파이프라인 설계, 자동화, 보안까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

2025년, CI/CD는 더 이상 선택이 아닌 필수입니다. Google의 DORA(DevOps Research and Assessment) 보고서에 따르면, Elite 수준의 팀은 하루에 여러 번 배포하면서도 변경 실패율 5% 미만을 유지합니다. 반면 Low 수준의 팀은 한 달에 한 번 배포하며 실패율이 46%에 달합니다.

이 격차의 핵심은 **파이프라인 설계**에 있습니다. 단순히 CI/CD 도구를 도입하는 것이 아니라, 테스트 자동화, 보안 통합, 점진적 배포, 그리고 관찰 가능성까지 포함하는 종합적인 전략이 필요합니다.

이 글에서는 CI/CD 파이프라인을 설계하고 운영하는 데 필요한 모든 것을 다룹니다. 플랫폼 비교부터 파이프라인 설계 원칙, 테스트 전략, Docker 빌드 최적화, GitOps, 보안, 배포 전략, 롤백, 그리고 모니터링까지 실전 중심으로 정리했습니다.

1. 2025년 CI/CD 현황

1.1 DORA 메트릭으로 보는 팀 성과

DORA 메트릭은 소프트웨어 딜리버리 성과를 측정하는 4가지 핵심 지표입니다.

| 지표 | Elite | High | Medium | Low |

|------|-------|------|--------|-----|

| 배포 빈도 | 하루 여러 번 | 주 1회~월 1회 | 월 1회~6개월 1회 | 6개월 이상 |

| 리드 타임 (커밋→배포) | 1시간 미만 | 1일~1주 | 1주~1개월 | 1개월~6개월 |

| 변경 실패율 | 0~5% | 5~10% | 10~15% | 46~60% |

| 복구 시간 (MTTR) | 1시간 미만 | 1일 미만 | 1일~1주 | 6개월 이상 |

1.2 시프트 레프트 전략

시프트 레프트(Shift Left)는 테스트와 보안을 개발 초기 단계로 당기는 전략입니다.

전통적 접근:

Code → Build → Test → Security → Deploy → Monitor

↑ 여기서 문제 발견

시프트 레프트:

Code + Test + Security → Build → Deploy → Monitor

↑ 여기서 문제 발견 (비용 10x 절감)

핵심 원칙:

- **커밋 전 검증**: pre-commit hook으로 린트, 포맷, 시크릿 스캔

- **PR 단계 테스트**: 단위 테스트 + 통합 테스트 + SAST 자동 실행

- **빌드 시 보안**: 컨테이너 이미지 스캔, 의존성 취약점 검사

- **배포 전 검증**: 스모크 테스트, 카나리 분석

1.3 2025년 주요 트렌드

- **플랫폼 엔지니어링**: 개발자 셀프 서비스 플랫폼으로 CI/CD 표준화

- **AI 기반 CI/CD**: 테스트 실패 예측, 자동 롤백 결정, 플레이키 테스트 탐지

- **eBPF 기반 관찰 가능성**: 파이프라인 성능 모니터링의 새 패러다임

- **Supply Chain Security**: SBOM, SLSA, Sigstore 기반 소프트웨어 공급망 보안

2. CI/CD 플랫폼 비교

2.1 주요 플랫폼 비교표

| 기능 | GitHub Actions | Jenkins | GitLab CI | CircleCI |

|------|---------------|---------|-----------|----------|

| 호스팅 | SaaS/Self-hosted | Self-hosted | SaaS/Self-hosted | SaaS |

| 설정 방식 | YAML | Groovy/YAML | YAML | YAML |

| 생태계 | Marketplace 15,000+ | Plugin 1,800+ | Built-in 통합 | Orbs 3,000+ |

| 컨테이너 지원 | 네이티브 | 플러그인 | 네이티브 | 네이티브 |

| 셀프러너 | 지원 | 기본 | 지원 | 지원 |

| 가격 | 2,000분 무료/월 | 무료(OSS) | 400분 무료/월 | 6,000크레딧 무료/월 |

| 학습 곡선 | 낮음 | 높음 | 중간 | 낮음 |

| 캐싱 | 10GB/리포 | 플러그인 | 네이티브 | 네이티브 |

2.2 GitHub Actions

.github/workflows/ci.yml

name: CI Pipeline

on:

pull_request:

branches: [main]

push:

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

test:

runs-on: ubuntu-latest

needs: lint

strategy:

matrix:

shard: [1, 2, 3, 4]

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 {{ '}}' }}/4

build:

runs-on: ubuntu-latest

needs: test

steps:

- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v5

with:

context: .

push: true

tags: myapp:latest

cache-from: type=gha

cache-to: type=gha,mode=max

2.3 Jenkins Pipeline

// Jenkinsfile (Declarative Pipeline)

pipeline {

agent any

environment {

DOCKER_REGISTRY = 'registry.example.com'

IMAGE_NAME = 'myapp'

}

stages {

stage('Checkout') {

steps {

checkout scm

}

}

stage('Lint & Test') {

parallel {

stage('Lint') {

steps {

sh 'npm run lint'

}

}

stage('Unit Test') {

steps {

sh 'npm test -- --coverage'

}

post {

always {

junit 'reports/junit.xml'

publishHTML([

reportDir: 'coverage',

reportFiles: 'index.html',

reportName: 'Coverage Report'

])

}

}

}

}

}

stage('Build & Push') {

steps {

script {

def image = docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")

docker.withRegistry("https://${DOCKER_REGISTRY}", 'registry-credentials') {

image.push()

image.push('latest')

}

}

}

}

}

post {

failure {

slackSend(

channel: '#ci-alerts',

color: 'danger',

message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"

)

}

}

}

2.4 GitLab CI

.gitlab-ci.yml

stages:

- lint

- test

- build

- deploy

variables:

DOCKER_HOST: tcp://docker:2376

lint:

stage: lint

image: node:20-alpine

cache:

key: npm-cache

paths:

- node_modules/

script:

- npm ci

- npm run lint

test:

stage: test

image: node:20-alpine

parallel: 4

script:

- npm ci

- npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'

artifacts:

reports:

junit: reports/junit.xml

coverage_report:

coverage_format: cobertura

path: coverage/cobertura-coverage.xml

build:

stage: build

image: docker:24

services:

- docker:24-dind

script:

- docker build -t myapp:latest .

- docker push myapp:latest

only:

- main

3. 파이프라인 설계 원칙

3.1 빠른 피드백 루프

개발자가 PR을 올리고 결과를 기다리는 시간은 직접적으로 생산성에 영향을 미칩니다.

목표 시간:

├── 린트 + 포맷 체크: 30초 이내

├── 단위 테스트: 2분 이내

├── 통합 테스트: 5분 이내

├── 빌드: 3분 이내

└── 전체 파이프라인: 10분 이내

현실 (최적화 전): 30분+

현실 (최적화 후): 8~10분

3.2 병렬 처리

병렬 파이프라인 예시

jobs:

1단계: 린트/보안은 독립적으로 병렬 실행

lint:

runs-on: ubuntu-latest

...

security-scan:

runs-on: ubuntu-latest

...

2단계: 테스트는 shard로 병렬 분할

test:

needs: [lint]

strategy:

matrix:

shard: [1, 2, 3, 4]

...

3단계: 빌드는 테스트 통과 후

build:

needs: [test, security-scan]

...

3.3 캐싱 전략

npm 캐싱 (GitHub Actions)

- uses: actions/cache@v4

with:

path: ~/.npm

key: npm-${{ '{{' }} hashFiles('**/package-lock.json') {{ '}}' }}

restore-keys: |

npm-

Docker 레이어 캐싱

- uses: docker/build-push-action@v5

with:

cache-from: type=gha

cache-to: type=gha,mode=max

Gradle 캐싱

- uses: actions/cache@v4

with:

path: |

~/.gradle/caches

~/.gradle/wrapper

key: gradle-${{ '{{' }} hashFiles('**/*.gradle*') {{ '}}' }}

3.4 멱등성 (Idempotency)

파이프라인은 같은 입력에 대해 항상 같은 결과를 내야 합니다.

나쁜 예: 타임스탬프 기반 태그 (재실행 시 다른 결과)

IMAGE_TAG: my-app:build-20250323-142000

좋은 예: 커밋 SHA 기반 태그 (항상 동일)

IMAGE_TAG: my-app:abc1234

좋은 예: 시맨틱 버전 (결정적)

IMAGE_TAG: my-app:v1.2.3

4. CI에서의 테스트 전략

4.1 테스트 피라미드

/ E2E \ 느리지만 높은 신뢰도

/ (5~10%) \

/ Integration \ 중간 속도, 중간 신뢰도

/ (15~25%) \

/ Unit Tests \ 빠르고 많이

/ (65~80%) \

/________________________\

4.2 테스트 분할 (Test Splitting)

Jest 테스트 샤딩

test:

strategy:

matrix:

shard: [1, 2, 3, 4]

steps:

- run: npx jest --shard=${{ '{{' }} matrix.shard {{ '}}' }}/4

Cypress 병렬 실행

e2e:

strategy:

matrix:

container: [1, 2, 3]

steps:

- uses: cypress-io/github-action@v6

with:

record: true

parallel: true

group: 'e2e-tests'

4.3 플레이키 테스트 관리

플레이키(Flaky) 테스트는 같은 코드에서 때때로 성공하고 때때로 실패하는 테스트입니다.

// 플레이키 테스트 감지 및 격리 전략

// jest.config.js

module.exports = {

// 실패 시 자동 재시도

retryTimes: 2,

// 플레이키 테스트 리포터

reporters: [

'default',

['jest-flaky-reporter', {

outputFile: 'flaky-tests.json',

threshold: 3 // 3번 이상 플레이키하면 보고

}]

]

};

CI에서 플레이키 테스트 격리

test-stable:

runs-on: ubuntu-latest

steps:

- run: npx jest --testPathIgnorePatterns="flaky"

test-flaky:

runs-on: ubuntu-latest

continue-on-error: true # 실패해도 파이프라인 계속

steps:

- run: npx jest --testPathPattern="flaky" --retries=3

4.4 테스트 커버리지 게이트

커버리지 임계값 설정

test:

steps:

- run: npx jest --coverage

- name: Check coverage threshold

run: |

COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.statements.pct')

if (( $(echo "$COVERAGE < 80" | bc -l) )); then

echo "Coverage $COVERAGE% is below 80% threshold"

exit 1

fi

5. Docker 빌드 최적화

5.1 멀티 스테이지 빌드

Stage 1: 의존성 설치

FROM node:20-alpine AS deps

WORKDIR /app

COPY package.json package-lock.json ./

RUN npm ci --production

Stage 2: 빌드

FROM node:20-alpine AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules

COPY . .

RUN npm run build

Stage 3: 프로덕션 이미지

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production

보안: non-root 사용자

RUN addgroup --system --gid 1001 nodejs && \

adduser --system --uid 1001 nextjs

COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next

COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules

COPY --from=builder /app/package.json ./

USER nextjs

EXPOSE 3000

CMD ["npm", "start"]

5.2 레이어 캐싱 최적화

나쁜 예: 소스 변경 시 npm ci 재실행

COPY . .

RUN npm ci

RUN npm run build

좋은 예: 의존성 파일만 먼저 복사

COPY package.json package-lock.json ./

RUN npm ci

COPY . .

RUN npm run build

5.3 BuildKit과 Buildx

GitHub Actions에서 BuildKit 사용

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v5

with:

context: .

push: true

tags: myapp:latest

cache-from: type=gha

cache-to: type=gha,mode=max

platforms: linux/amd64,linux/arm64

로컬에서 BuildKit 사용

DOCKER_BUILDKIT=1 docker build .

5.4 Kaniko (Docker 데몬 없는 빌드)

Kubernetes에서 Kaniko로 이미지 빌드

apiVersion: v1

kind: Pod

metadata:

name: kaniko-build

spec:

containers:

- name: kaniko

image: gcr.io/kaniko-project/executor:latest

args:

- "--dockerfile=Dockerfile"

- "--context=git://github.com/myorg/myapp"

- "--destination=registry.example.com/myapp:latest"

- "--cache=true"

- "--cache-repo=registry.example.com/myapp/cache"

5.5 이미지 크기 최적화

이미지 크기 비교:

├── node:20 → 1.1GB

├── node:20-slim → 220MB

├── node:20-alpine → 140MB

├── distroless/nodejs → 120MB

└── 멀티스테이지 최적화 → 80~100MB

6. GitOps와 ArgoCD

6.1 GitOps 원칙

GitOps는 Git 리포지토리를 단일 진실의 소스(Single Source of Truth)로 사용하는 운영 모델입니다.

GitOps 워크플로:

1. 개발자가 Git에 변경 Push

2. CI가 이미지 빌드 및 테스트

3. CI가 배포 매니페스트의 이미지 태그 업데이트

4. ArgoCD가 Git과 클러스터 상태 비교

5. 차이가 있으면 자동 동기화 (또는 수동 승인)

6. 클러스터가 Git 상태와 일치

┌────────┐ Push ┌────────┐ Detect ┌────────┐

│ Dev │ ──────────> │ Git │ <────────> │ ArgoCD │

└────────┘ └────────┘ └───┬────┘

│ Sync

┌────▼────┐

│ K8s │

│ Cluster │

└─────────┘

6.2 ArgoCD App of Apps 패턴

apps/root-app.yaml

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: root-app

namespace: argocd

spec:

project: default

source:

repoURL: https://github.com/myorg/gitops-config

targetRevision: main

path: apps

destination:

server: https://kubernetes.default.svc

namespace: argocd

syncPolicy:

automated:

prune: true

selfHeal: true

apps/api-service.yaml

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: api-service

spec:

project: default

source:

repoURL: https://github.com/myorg/gitops-config

path: services/api

targetRevision: main

destination:

server: https://kubernetes.default.svc

namespace: production

syncPolicy:

automated:

prune: true

selfHeal: true

syncOptions:

- CreateNamespace=true

6.3 Argo Rollouts (점진적 배포)

apiVersion: argoproj.io/v1alpha1

kind: Rollout

metadata:

name: api-service

spec:

replicas: 10

strategy:

canary:

canaryService: api-canary

stableService: api-stable

trafficRouting:

istio:

virtualService:

name: api-vsvc

steps:

- setWeight: 10

- pause:

duration: 5m

- analysis:

templates:

- templateName: success-rate

- setWeight: 30

- pause:

duration: 5m

- analysis:

templates:

- templateName: success-rate

- setWeight: 60

- pause:

duration: 5m

- setWeight: 100

AnalysisTemplate

apiVersion: argoproj.io/v1alpha1

kind: AnalysisTemplate

metadata:

name: success-rate

spec:

metrics:

- name: success-rate

interval: 60s

successCondition: result[0] >= 0.95

provider:

prometheus:

address: http://prometheus:9090

query: |

sum(rate(http_requests_total{status=~"2.*",app="api-service",version="canary"}[5m]))

/

sum(rate(http_requests_total{app="api-service",version="canary"}[5m]))

7. CI/CD 보안

7.1 보안 스캔 통합

CI/CD 보안 레이어:

┌─────────────────────────────────────────────┐

│ Layer 1: Pre-commit │

│ - Secret scanning (gitleaks, detect-secrets)│

│ - Lint (security rules) │

├─────────────────────────────────────────────┤

│ Layer 2: PR / Build │

│ - SAST (Semgrep, CodeQL, SonarQube) │

│ - SCA (Dependabot, Snyk, Trivy) │

│ - License compliance │

├─────────────────────────────────────────────┤

│ Layer 3: Container Build │

│ - Image scanning (Trivy, Grype) │

│ - Base image policy (distroless, alpine) │

│ - SBOM generation (Syft) │

├─────────────────────────────────────────────┤

│ Layer 4: Deploy │

│ - Policy enforcement (OPA/Kyverno) │

│ - Signing (cosign, Sigstore) │

│ - Runtime security (Falco) │

└─────────────────────────────────────────────┘

7.2 시크릿 관리

GitHub Actions에서 시크릿 사용

deploy:

steps:

- name: Deploy

env:

AWS_ACCESS_KEY_ID: ${{ '{{' }} secrets.AWS_ACCESS_KEY_ID {{ '}}' }}

AWS_SECRET_ACCESS_KEY: ${{ '{{' }} secrets.AWS_SECRET_ACCESS_KEY {{ '}}' }}

run: |

aws ecs update-service --cluster prod --service api

OIDC 기반 인증 (시크릿 없는 방식 - 권장)

permissions:

id-token: write

contents: read

steps:

- uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789012:role/github-actions

aws-region: ap-northeast-2

7.3 SBOM과 Supply Chain Security

Syft로 SBOM 생성

- name: Generate SBOM

uses: anchore/sbom-action@v0

with:

image: myapp:latest

format: spdx-json

output-file: sbom.spdx.json

cosign으로 이미지 서명

- name: Sign image

run: |

cosign sign --key env://COSIGN_PRIVATE_KEY myapp:latest

cosign으로 서명 검증

- name: Verify signature

run: |

cosign verify --key cosign.pub myapp:latest

7.4 시크릿 스캔 자동화

pre-commit 설정

.pre-commit-config.yaml

repos:

- repo: https://github.com/gitleaks/gitleaks

rev: v8.18.0

hooks:

- id: gitleaks

CI에서 gitleaks 실행

security:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

with:

fetch-depth: 0

- uses: gitleaks/gitleaks-action@v2

env:

GITHUB_TOKEN: ${{ '{{' }} secrets.GITHUB_TOKEN {{ '}}' }}

8. 배포 전략 비교

8.1 전략 비교표

| 전략 | 다운타임 | 위험도 | 리소스 비용 | 롤백 속도 | 복잡도 |

|------|---------|--------|------------|----------|--------|

| Recreate | 있음 | 높음 | 1x | 느림 | 낮음 |

| Rolling Update | 없음 | 중간 | 1x~1.25x | 중간 | 낮음 |

| Blue-Green | 없음 | 낮음 | 2x | 즉시 | 중간 |

| Canary | 없음 | 매우 낮음 | 1.1x | 즉시 | 높음 |

| A/B Testing | 없음 | 매우 낮음 | 1.1x | 즉시 | 매우 높음 |

8.2 Blue-Green 배포

Kubernetes Blue-Green 배포

service.yaml

apiVersion: v1

kind: Service

metadata:

name: api-service

spec:

selector:

app: api

version: green # blue에서 green으로 전환

ports:

- port: 80

targetPort: 8080

Blue-Green 전환 과정:

1. Blue(v1) 운영 중 → Green(v2) 배포

2. Green 헬스체크 및 스모크 테스트

3. Service selector를 Green으로 전환

4. 문제 시 Blue로 즉시 롤백

5. 안정화 후 Blue 리소스 정리

[Users] → [LB] → [Blue v1] ✓ Active

[Green v2] ← 준비 중

[Users] → [LB] → [Blue v1] ← 대기

[Green v2] ✓ Active

8.3 카나리 배포

Istio VirtualService로 카나리 배포

apiVersion: networking.istio.io/v1beta1

kind: VirtualService

metadata:

name: api-service

spec:

hosts:

- api-service

http:

- route:

- destination:

host: api-service

subset: stable

weight: 90

- destination:

host: api-service

subset: canary

weight: 10

8.4 Feature Flags

// LaunchDarkly 또는 자체 Feature Flag 시스템

async function handleRequest(req: Request) {

const userId = req.user.id;

if (await featureFlags.isEnabled('new-checkout-flow', userId)) {

return newCheckoutFlow(req);

}

return legacyCheckoutFlow(req);

}

Feature Flag 기반 배포:

1. 코드에 새 기능을 플래그로 감싸서 배포

2. 내부 사용자에게만 활성화

3. 점진적으로 비율 확대 (1% → 5% → 25% → 100%)

4. 문제 시 플래그만 끄면 즉시 비활성화

5. 배포와 릴리스를 분리

9. 롤백 전략

9.1 자동 롤백

Argo Rollouts 자동 롤백

spec:

strategy:

canary:

steps:

- setWeight: 10

- analysis:

templates:

- templateName: error-rate-check

분석 실패 시 자동 롤백

abortScaleDownDelaySeconds: 30

Kubernetes Deployment 자동 롤백

apiVersion: apps/v1

kind: Deployment

spec:

progressDeadlineSeconds: 300 # 5분 내 완료 안 되면 실패

minReadySeconds: 30

strategy:

rollingUpdate:

maxSurge: 25%

maxUnavailable: 0

9.2 서킷 브레이커 패턴

// 배포 서킷 브레이커

class DeploymentCircuitBreaker {

private errorThreshold = 0.05; // 5% 에러율

private windowSize = 300; // 5분 윈도우

async shouldRollback(metrics: DeploymentMetrics): Promise<boolean> {

const errorRate = metrics.errors / metrics.totalRequests;

const p99Latency = metrics.p99LatencyMs;

return (

errorRate > this.errorThreshold ||

p99Latency > 3000 // 3초 초과

);

}

async executeRollback(deployment: string) {

console.log(`Rolling back ${deployment}`);

// kubectl rollout undo deployment/api-service

await exec(`kubectl rollout undo deployment/${deployment}`);

// 알림 전송

await notify({

channel: '#deployments',

message: `Auto-rollback triggered for ${deployment}`,

severity: 'critical'

});

}

}

9.3 데이터베이스 마이그레이션 롤백

안전한 DB 마이그레이션 전략:

1. Expand-Contract 패턴

Phase 1 (Expand): 새 컬럼 추가, 양쪽 모두 쓰기

Phase 2 (Migrate): 기존 데이터 마이그레이션

Phase 3 (Contract): 이전 컬럼 제거

2. 롤백 가능한 마이그레이션만 적용

- 컬럼 추가 (롤백 가능)

- 인덱스 추가 (롤백 가능)

- 컬럼 삭제 (롤백 불가 → Expand-Contract 사용)

- 타입 변경 (롤백 불가 → 새 컬럼 추가 후 전환)

-- 안전한 마이그레이션 예시

-- Step 1: 새 컬럼 추가 (롤백 가능)

ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;

-- Step 2: 데이터 마이그레이션 (백그라운드)

UPDATE users SET email_verified = TRUE

WHERE verified_at IS NOT NULL;

-- Step 3: 앱 코드에서 새 컬럼 사용 전환

-- Step 4: 이전 컬럼 삭제 (별도 마이그레이션)

-- ALTER TABLE users DROP COLUMN verified_at;

10. 파이프라인 헬스 모니터링

10.1 핵심 메트릭

파이프라인 헬스 대시보드:

┌─────────────────────────────────────────┐

│ Build Time Trend │

│ ██████████████ 8m (avg) │

│ Target: < 10m │

├─────────────────────────────────────────┤

│ Success Rate │

│ ████████████████████ 94% │

│ Target: > 95% │

├─────────────────────────────────────────┤

│ Flaky Test Rate │

│ ██ 3% │

│ Target: < 2% │

├─────────────────────────────────────────┤

│ Mean Time to Recovery (MTTR) │

│ ████ 25min │

│ Target: < 30min │

└─────────────────────────────────────────┘

10.2 빌드 시간 추적

빌드 시간을 Datadog에 보고

- name: Report build metrics

if: always()

run: |

END_TIME=$(date +%s)

DURATION=$((END_TIME - START_TIME))

curl -X POST "https://api.datadoghq.com/api/v1/series" \

-H "DD-API-KEY: $DD_API_KEY" \

-d "{

\"series\": [{

\"metric\": \"ci.build.duration\",

\"points\": [[$END_TIME, $DURATION]],

\"tags\": [

\"repo:myapp\",

\"branch:$GITHUB_REF_NAME\",

\"status:$JOB_STATUS\"

]

}]

}"

10.3 실패 분석 자동화

빌드 실패 자동 분류 스크립트

from enum import Enum

class FailureCategory(Enum):

FLAKY_TEST = "flaky_test"

DEPENDENCY = "dependency"

COMPILATION = "compilation"

INFRASTRUCTURE = "infrastructure"

TIMEOUT = "timeout"

UNKNOWN = "unknown"

def categorize_failure(log: str) -> FailureCategory:

patterns = {

FailureCategory.FLAKY_TEST: [

r"retry.*failed",

r"intermittent",

r"flaky"

],

FailureCategory.DEPENDENCY: [

r"npm ERR!.*404",

r"Could not resolve dependencies",

r"ECONNRESET"

],

FailureCategory.COMPILATION: [

r"error TS\d+",

r"SyntaxError",

r"TypeError"

],

FailureCategory.INFRASTRUCTURE: [

r"runner.*offline",

r"disk space",

r"out of memory"

],

FailureCategory.TIMEOUT: [

r"timed out",

r"deadline exceeded"

]

}

for category, regexes in patterns.items():

for pattern in regexes:

if re.search(pattern, log, re.IGNORECASE):

return category

return FailureCategory.UNKNOWN

11. 실전 파이프라인 통합 예시

11.1 풀스택 CI/CD 파이프라인

.github/workflows/production.yml

name: Production Pipeline

on:

push:

branches: [main]

permissions:

id-token: write

contents: read

packages: write

jobs:

Phase 1: 코드 품질

quality:

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

Phase 2: 보안 스캔

security:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Run Semgrep

uses: returntocorp/semgrep-action@v1

- name: Run Trivy (filesystem)

uses: aquasecurity/trivy-action@master

with:

scan-type: 'fs'

severity: 'HIGH,CRITICAL'

Phase 3: 테스트

test:

needs: quality

runs-on: ubuntu-latest

strategy:

matrix:

shard: [1, 2, 3, 4]

services:

postgres:

image: postgres:16

env:

POSTGRES_DB: testdb

POSTGRES_PASSWORD: testpass

ports:

- 5432:5432

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 {{ '}}' }}/4

env:

DATABASE_URL: postgresql://postgres:testpass@localhost:5432/testdb

Phase 4: 빌드 및 푸시

build:

needs: [test, security]

runs-on: ubuntu-latest

outputs:

image-tag: ${{ '{{' }} steps.meta.outputs.tags {{ '}}' }}

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 {{ '}}' }}

- id: meta

uses: docker/metadata-action@v5

with:

images: ghcr.io/myorg/myapp

- uses: docker/build-push-action@v5

with:

push: true

tags: ${{ '{{' }} steps.meta.outputs.tags {{ '}}' }}

cache-from: type=gha

cache-to: type=gha,mode=max

Phase 5: 배포 매니페스트 업데이트 (GitOps)

update-manifest:

needs: build

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

with:

repository: myorg/gitops-config

token: ${{ '{{' }} secrets.GITOPS_TOKEN {{ '}}' }}

- name: Update image tag

run: |

cd services/api

kustomize edit set image myapp=${{ '{{' }} needs.build.outputs.image-tag {{ '}}' }}

- name: Commit and push

run: |

git config user.name "CI Bot"

git config user.email "ci@example.com"

git add .

git commit -m "chore: update api-service image"

git push

12. 면접 질문 모음

기본 개념

**CI(Continuous Integration)**는 개발자들이 코드 변경을 자주 메인 브랜치에 통합하는 관행입니다. 각 통합은 자동화된 빌드와 테스트로 검증합니다.

**CD**는 두 가지 의미가 있습니다:

- **Continuous Delivery**: 코드가 항상 배포 가능한 상태를 유지. 프로덕션 배포는 수동 승인.

- **Continuous Deployment**: 모든 변경이 자동으로 프로덕션에 배포. 수동 개입 없음.

핵심 차이: CI는 "통합"에, CD는 "전달/배포"에 초점. CI 없이 CD는 불가능하지만, CI만 하고 CD는 안 할 수 있습니다.

1. **배포 빈도(Deployment Frequency)**: 프로덕션에 얼마나 자주 배포하는가

2. **리드 타임(Lead Time for Changes)**: 커밋에서 프로덕션 배포까지 걸리는 시간

3. **변경 실패율(Change Failure Rate)**: 배포 중 실패하거나 롤백이 필요한 비율

4. **복구 시간(MTTR - Mean Time to Recovery)**: 장애 발생 후 복구까지 걸리는 시간

Elite 팀: 하루 여러 번 배포, 1시간 미만 리드 타임, 5% 미만 실패율, 1시간 미만 복구

1. **선언적 기술(Declarative)**: 시스템 상태를 선언적으로 정의

2. **버전 관리(Versioned)**: Git을 단일 진실의 소스로 사용

3. **자동 적용(Automated)**: 승인된 변경이 자동으로 시스템에 적용

4. **자가 치유(Self-Healing)**: 실제 상태가 선언된 상태와 다르면 자동 복구

장점: 감사 추적, 롤백 용이, PR 기반 변경 관리, 재현 가능한 환경

**Blue-Green**: 두 개의 동일한 환경(Blue/Green)을 운영. 새 버전을 Green에 배포 후 트래픽을 한 번에 전환. 롤백은 Blue로 즉시 전환.

- 장점: 즉시 롤백, 간단한 구현

- 단점: 리소스 2배 필요, 데이터베이스 동기화 복잡

**Canary**: 새 버전을 소수(1~10%)에게 먼저 배포. 메트릭 분석 후 점진적 확대.

- 장점: 위험 최소화, 실제 트래픽으로 검증

- 단점: 구현 복잡, 모니터링 필수

테스트와 보안을 개발 라이프사이클의 왼쪽(초기 단계)으로 이동시키는 전략입니다.

적용 예시:

- pre-commit hook으로 코드 린트, 포맷, 시크릿 스캔

- PR 단계에서 SAST, SCA, 단위 테스트 실행

- 빌드 시 컨테이너 이미지 스캔

- IDE 플러그인으로 개발 중 실시간 피드백

효과: 결함을 일찍 발견할수록 수정 비용이 지수적으로 감소 (프로덕션 대비 10~100배 절감)

심화 질문

1. **감지**: 같은 코드에서 반복 실행 시 결과가 달라지는 테스트 식별

2. **격리**: 플레이키 테스트를 별도 test suite로 분리, continue-on-error 적용

3. **재시도**: jest의 retryTimes, pytest-rerunfailures 등으로 자동 재시도

4. **추적**: 플레이키 테스트 대시보드로 빈도, 패턴 분석

5. **근본 원인 해결**: 타이밍 이슈, 공유 상태, 외부 의존성 등 원인 제거

6. **정책**: 일정 기간 내 수정 안 되면 비활성화 또는 삭제

1. **멀티 스테이지 빌드**: 빌드 도구를 최종 이미지에서 제외

2. **레이어 캐싱 최적화**: 자주 변경되는 파일을 뒤에 COPY

3. **경량 베이스 이미지**: alpine, distroless 사용

4. **.dockerignore**: 불필요한 파일 제외

5. **BuildKit 사용**: 병렬 빌드, 캐시 마운트

6. **의존성 분리**: package.json을 먼저 복사하여 npm ci 캐싱

7. **Kaniko**: Docker 데몬 없이 빌드 (CI/CD 보안 향상)

1. **절대 Git에 커밋하지 않기**: gitleaks, detect-secrets로 pre-commit 검사

2. **OIDC 기반 인증**: 장기 시크릿 대신 임시 토큰 사용

3. **시크릿 매니저 활용**: AWS Secrets Manager, HashiCorp Vault, Doppler

4. **최소 권한 원칙**: 필요한 최소한의 권한만 부여

5. **시크릿 로테이션**: 정기적으로 시크릿 갱신 자동화

6. **감사 로그**: 시크릿 접근 기록 추적

7. **환경 분리**: dev/staging/prod 시크릿 분리

**Expand-Contract 패턴** 사용:

Phase 1 (Expand):

- 새 컬럼/테이블 추가

- 앱이 이전 스키마와 새 스키마 모두 호환되도록 코드 수정

- 새 스키마에도 데이터 쓰기 시작

Phase 2 (Migrate):

- 기존 데이터를 새 스키마로 마이그레이션 (백그라운드)

- 앱을 새 스키마만 사용하도록 전환

Phase 3 (Contract):

- 이전 컬럼/테이블 제거 (별도 배포)

- 이 단계만 롤백 불가

핵심: 각 단계가 독립적으로 롤백 가능해야 합니다.

1. **Supply Chain Security**: SBOM 생성, 이미지 서명(cosign), SLSA 준수

2. **시크릿 관리**: OIDC, Vault, 환경변수 최소화

3. **SAST/DAST/SCA**: Semgrep, Trivy, Dependabot 통합

4. **컨테이너 보안**: 비루트 사용자, distroless 베이스, 이미지 스캔

5. **정책 준수**: OPA/Kyverno로 배포 정책 강제

6. **접근 제어**: 최소 권한, 브랜치 보호 규칙

7. **감사**: 모든 배포 기록 추적, 변경 이력 유지

**GitHub Actions**:

- 장점: GitHub 네이티브 통합, SaaS로 유지보수 불필요, Marketplace 생태계, YAML 기반 간편 설정

- 단점: GitHub 종속성, 커스터마이징 한계, 복잡한 워크플로 관리 어려움

**Jenkins**:

- 장점: 완전한 자유도, 1800+ 플러그인, 자체 호스팅 제어, Groovy 스크립팅

- 단점: 높은 유지보수 비용, 복잡한 설정, 보안 패치 관리, 스케일링 어려움

선택 기준: 소규모/GitHub 중심 프로젝트는 Actions, 복잡한 엔터프라이즈/멀티 SCM은 Jenkins

Progressive Delivery는 새 버전을 점진적으로 배포하면서 자동화된 분석으로 안전성을 검증하는 방식입니다.

Argo Rollouts 워크플로:

1. 카나리 10% 트래픽 할당

2. AnalysisTemplate으로 성공률, 지연 시간 검증 (5분)

3. 통과 시 30%로 확대, 다시 분석

4. 60%, 100%로 점진적 확대

5. 분석 실패 시 자동 롤백

핵심 구성 요소:

- Rollout: 배포 전략 정의

- AnalysisTemplate: 검증 조건 정의 (Prometheus, Datadog 등)

- TrafficRouting: Istio, Nginx, ALB 등과 연동

1. **병렬 처리**: 독립적인 작업을 동시 실행

2. **테스트 분할**: 샤딩으로 테스트를 여러 러너에 분배

3. **캐싱**: 의존성, Docker 레이어, 빌드 결과 캐싱

4. **선택적 실행**: 변경된 파일에 따라 필요한 작업만 실행

5. **증분 빌드**: 전체 빌드 대신 변경된 부분만 빌드

6. **리소스 최적화**: 러너 크기, 동시성 제한 조정

7. **피드백 루프 단축**: 빠른 검사를 먼저, 느린 검사는 나중에

**장점:**

- 배포와 릴리스 분리: 코드를 배포하되, 기능은 나중에 활성화

- 빠른 롤백: 코드 롤백 없이 플래그만 끄면 됨

- 점진적 출시: 사용자 비율을 점진적으로 확대

- A/B 테스트: 기능별 실험 가능

**단점:**

- 기술 부채: 오래된 플래그 정리 필요

- 복잡성: 플래그 조합으로 테스트 경우의 수 증가

- 코드 가독성: 조건문 증가로 코드 복잡화

- 일관성: 사용자마다 다른 경험으로 버그 재현 어려움

1. **영향 범위 분석**: 변경된 파일로부터 영향받는 패키지만 빌드/테스트

2. **도구 활용**: Turborepo, Nx, Bazel 등으로 의존성 그래프 기반 빌드

3. **캐싱**: 원격 캐시(Turborepo Remote Cache)로 빌드 결과 공유

4. **선택적 배포**: 변경된 서비스만 배포

5. **병렬 처리**: 독립적인 패키지 동시 빌드/테스트

Turborepo 예시

turbo run build --filter=...[HEAD~1]

HEAD 이후 변경된 패키지와 의존 패키지만 빌드

13. 퀴즈

**정답: 하루에 여러 번 (On-demand, multiple deploys per day)**

Elite 팀은 하루에 여러 번 배포하면서도 변경 실패율 5% 미만, 복구 시간 1시간 미만을 유지합니다.

**정답: Contract 단계 (이전 컬럼/테이블 삭제)**

Expand(추가)와 Migrate(마이그레이션)는 롤백 가능하지만, Contract(삭제)는 데이터가 사라지므로 롤백이 불가능합니다. 따라서 Contract는 충분한 안정화 기간 후에 별도로 진행합니다.

**정답: Git 리포지토리**

GitOps에서 Git 리포지토리는 시스템의 원하는 상태(Desired State)를 정의하는 유일한 소스입니다. 클러스터의 실제 상태는 항상 Git에 선언된 상태와 일치해야 하며, ArgoCD 같은 도구가 이를 자동으로 감지하고 동기화합니다.

**정답: AnalysisTemplate에 정의된 메트릭 기준 (성공률, 지연 시간 등)**

Argo Rollouts의 AnalysisTemplate에서 Prometheus, Datadog 등의 메트릭을 쿼리하여 성공률이 기준(예: 95%) 이하이거나, P99 지연 시간이 기준을 초과하면 자동으로 롤백을 트리거합니다.

**정답: 소프트웨어에 포함된 모든 구성 요소(라이브러리, 의존성)의 목록을 제공하여 공급망 보안을 강화하는 것**

SBOM은 소프트웨어의 "재료 목록"으로, 취약점 발견 시 영향 범위를 빠르게 파악하고, 라이선스 컴플라이언스를 확인하며, 공급망 공격(예: Log4Shell)에 대한 대응을 돕습니다. Syft, Trivy 등의 도구로 자동 생성할 수 있습니다.

14. 참고 자료

공식 문서

- [GitHub Actions 공식 문서](https://docs.github.com/en/actions)

- [Jenkins Pipeline 문서](https://www.jenkins.io/doc/book/pipeline/)

- [GitLab CI/CD 문서](https://docs.gitlab.com/ee/ci/)

- [ArgoCD 공식 문서](https://argo-cd.readthedocs.io/)

- [Argo Rollouts 문서](https://argoproj.github.io/argo-rollouts/)

DORA 및 DevOps

- [DORA State of DevOps Report](https://dora.dev/)

- [Accelerate (DORA 저자 도서)](https://itrevolution.com/product/accelerate/)

- [Google Cloud DORA Metrics](https://cloud.google.com/blog/products/devops-sre/the-2024-dora-report-is-here)

보안

- [SLSA Supply Chain Security](https://slsa.dev/)

- [Sigstore / cosign 문서](https://docs.sigstore.dev/)

- [Trivy 취약점 스캐너](https://aquasecurity.github.io/trivy/)

- [Semgrep SAST](https://semgrep.dev/docs/)

배포 전략

- [Kubernetes Deployment Strategies](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)

- [Flagger Progressive Delivery](https://flagger.app/)

- [LaunchDarkly Feature Flags](https://launchdarkly.com/)

도구 및 생태계

- [BuildKit 문서](https://docs.docker.com/build/buildkit/)

- [Kaniko 빌드 도구](https://github.com/GoogleContainerTools/kaniko)

- [Turborepo 모노레포](https://turbo.build/)

현재 단락 (1/926)

2025년, CI/CD는 더 이상 선택이 아닌 필수입니다. Google의 DORA(DevOps Research and Assessment) 보고서에 따르면, Elite 수준의 팀은...

작성 글자: 0원문 글자: 22,431작성 단락: 0/926