✍️ 필사 모드: 현대 CI/CD 파이프라인 완전 분해 — GitHub Actions, GitOps, Argo CD, BuildKit, SLSA, Sigstore, SBOM (2025)
한국어"Continuous delivery is the ability to get changes of all types into production — safely and quickly in a sustainable way." — Jez Humble (Continuous Delivery, 2010)
CI/CD는 지루해 보인다. "빌드 돌리고, 테스트 돌리고, 배포한다." 그러나 대형 팀과 소형 팀을 가르는 가장 큰 차이가 CI/CD 파이프라인의 질이라는 건 놀라운 사실이다. Google은 "커밋 → 배포"를 분 단위로, 대부분의 한국 기업은 일 단위로 한다. 이 격차는 단순한 도구 차이가 아니라, 철학과 축적된 자동화의 차이다.
2005년의 Jenkins부터 2025년의 GitHub Actions + Argo CD + Sigstore까지. 이 글은 현대 CI/CD의 지도를 그린다.
1. CI/CD의 간단한 역사
2001 — XP와 Continuous Integration
- Kent Beck의 Extreme Programming이 CI를 처음 제창
- "하루 여러 번 통합"
- CruiseControl (2001) — 최초의 오픈소스 CI 서버
2005 — Hudson의 탄생
- Sun 마이크로시스템즈의 Kohsuke Kawaguchi가 개발
- Java 기반, 플러그인 천국
- 쉽고 빠르게 전 세계로 확산
2011 — Hudson → Jenkins 분할
- Oracle의 Sun 인수 후 상표권 분쟁
- 커뮤니티 대다수가 Jenkins로 이동
- 10년 이상 CI의 사실상 표준
2014 — 클라우드 CI 1세대
- Travis CI — GitHub 네이티브, YAML 설정
- CircleCI — 병렬화, Docker 기반 혁신
- "Jenkins 안 깔아도 된다"는 각성
2019 — GitHub Actions
- GitHub이 직접 CI를 제공
- Marketplace 액션으로 재사용 가능
- 2025년 현재 사실상 시장 1위
2020s — GitOps와 공급망 보안
- Argo CD (2018), Flux (2019) — Pull 기반 배포
- Sigstore (2021), SLSA (2021)
- SBOM 의무화 (미국 행정명령 14028, 2021)
- AI 기반 자동 수정 (Dependabot, Renovate, Copilot)
2. Pipeline as Code — 철학의 전환
GUI의 시대
- Jenkins Freestyle 프로젝트는 UI 클릭으로 설정
- 재현 불가, 버전 관리 불가, 리뷰 불가
Jenkinsfile (2016) — 첫 걸음
- Groovy DSL로 파이프라인 정의
- 리포지토리에
Jenkinsfile커밋 - 여전히 복잡하고 Groovy 문법
YAML의 승리
- Travis → CircleCI → GitHub Actions 모두 YAML
- 선언적, 간결, 익숙함
.github/workflows/*.yml파일이 표준
예시 — GitHub Actions
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
재사용성 — Composite Action, Reusable Workflow
Composite Action: 여러 step을 하나로 묶음. Reusable Workflow: 전체 워크플로우를 다른 워크플로우에서 호출.
# 호출
jobs:
build:
uses: myorg/shared/.github/workflows/node-build.yml@v1
with:
node-version: 20
조직 전체에 일관된 빌드 표준을 강제할 수 있음.
모범 사례
- pin to SHA —
actions/checkout@8e5e7e5(버전보다 안전, Dependabot 관리) - Matrix build — 여러 Node/Python 버전 동시 테스트
- if conditions — skip 로직
- concurrency group — 같은 PR에 여러 커밋 오면 이전 취소
3. 빌드 캐시 — CI 속도의 핵심
왜 캐시인가
- 빌드는 결정론적 — 같은 입력 → 같은 출력
- 입력 해시로 출력을 조회하면 재빌드 불필요
로컬 캐시
- npm/pnpm의
node_modules캐시 - Maven
~/.m2, Gradle build cache actions/cache@v4로 CI 간에 공유
원격 빌드 캐시 — 모노레포의 구원
- Turborepo (Vercel) — 원격 캐시로 "다른 개발자가 이미 빌드한 것"을 재사용
- Nx (Nrwl) — 유사한 원격 캐시 + 분산 실행
- Bazel — Google 내부에서 시작, 가장 강력한 Hermetic build
Bazel의 Remote Execution
- 빌드 자체를 원격 클러스터에서
- 수천 코어 병렬
- Google이 모노레포에서 하루 20억 빌드하는 비결
예시 — Turborepo
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
}
}
}
입력 해시 = 파일 내용 + dependsOn 결과의 해시. 변경 없으면 0초에 완료.
4. Container 기반 빌드 — BuildKit과 Nixpacks
Dockerfile의 진화
- 초기: 각
RUN이 레이어, 순서가 중요 - 현재: BuildKit (2018+)이 기본
- 병렬 빌드
- 캐시 마운트 (
--mount=type=cache) - 시크릿 마운트
- 멀티 아키텍처
Multi-stage Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-slim AS runtime
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
최종 이미지에는 소스/빌드 도구 없음. 크기 10배 감소.
BuildKit Cache Mount
RUN \
npm ci
레이어 간에 캐시 유지 → 재빌드 속도 ↑
Nixpacks / Buildpacks
- Cloud Native Buildpacks — Dockerfile 없이 언어 감지 → 빌드
- Nixpacks (Railway) — Rust로 쓴 빠른 빌더
- 단순 앱에는 훌륭, 복잡하면 Dockerfile로
Distroless 이미지
gcr.io/distroless/nodejs20-debian12- Shell 없음, 공격면 최소
- 구글 표준, 보안 감사에 유리
5. Test 병렬화 — CI 시간 반으로
Sharding
- 테스트를 N개 조각으로 분할
- 각 조각을 독립 runner에서 실행
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx jest --shard=${{ matrix.shard }}/4
지능적 할당
- 이전 실행 시간 기반으로 균등 분배 (Jest
--shard, Nx 분산 실행) - hot shard 문제 방지
Flaky Test 대응
- Flaky = 같은 코드인데 때때로 실패
- 원인: 타이밍, 공유 상태, 네트워크
- 전략:
- 재시도 (선택적, 남용 금지)
- 격리 (Flaky 목록 분리 실행)
- 뿌리 수정 — 가장 중요
E2E 병렬화
- Playwright
workers옵션 - 독립 DB 스키마, 독립 포트
- 상태 격리가 핵심
6. Artifact Management
OCI Registry — 이미지 저장소
- GitHub Container Registry (GHCR) — 무료, GitHub 연동
- Docker Hub — 역사적, 레이트 리밋
- AWS ECR, Google Artifact Registry, Azure ACR — 클라우드 네이티브
- Harbor, Artifactory — 엔터프라이즈
OCI는 이미지 이상을 저장한다
- Helm chart
- Python wheel (oci-python)
- Terraform module
- SBOM, provenance
OCI = 콘텐츠 주소 가능한 저장소 프로토콜이 되고 있음.
Semantic Tagging
v1.2.3 — 고정 버전
v1.2 — minor 고정
v1 — major 고정
latest — 최신 (prod에서 금지!)
sha-abc1234 — 커밋 기반 (추적 가능)
Pull-through Cache
- 사내 registry가 외부 registry를 캐시
- Docker Hub 레이트 리밋 회피
- 네트워크 지연 감소
7. Secrets Management — OIDC의 혁명
구시대 — 장기 자격증명
AWS_ACCESS_KEY_ID를 CI secrets에 저장- 유출되면 재앙
- 갱신 부담
OIDC 기반 — Keyless
- GitHub Actions가 자신을 증명하는 OIDC 토큰 발급
- AWS/GCP/Azure가 그 토큰을 신뢰 → 임시 자격증명 발급
- 장기 비밀이 아예 존재하지 않음
예시 — AWS
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/GitHubActions
aws-region: us-east-1
Vault / External Secrets
- HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager
- External Secrets Operator — K8s에서 자동 주입
- Secret rotation이 실제로 작동
SOPS — 암호화된 시크릿 git에 저장
- Mozilla SOPS (Secrets Operations)
- KMS/PGP로 암호화된 YAML/JSON
- GitOps 친화적
8. GitOps — Pull 모델의 우아함
Push vs Pull 배포
Push (전통): CI가 kubectl apply로 직접 배포
- CI가 클러스터 자격 증명을 가져야 함 (보안 위험)
- 상태 drift 탐지 어려움
Pull (GitOps): 클러스터가 git을 주기적으로 확인
- 자격 증명 CI에 노출 안됨
- Git = 단일 진실 소스
- drift 탐지/자동 복구
4원칙 (OpenGitOps)
- Declarative — 원하는 상태를 선언
- Versioned & Immutable — git에 기록
- Pulled Automatically — 에이전트가 적용
- Continuously Reconciled — 항상 감시
Argo CD
- CNCF Graduated
- 웹 UI가 강력 — 앱별 상태, diff, sync
- Application CRD로 선언
- Kustomize/Helm 네이티브 지원
- Multi-cluster 지원
Flux
- CNCF Graduated
- CLI 중심, Argo보다 가벼움
- GitOps Toolkit로 모듈화
예시 — Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
source:
repoURL: https://github.com/org/infra
path: apps/my-app
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
Argo Rollouts — Progressive Delivery와 통합
앞선 글 "Feature Flag & Progressive Delivery"와 연결:
- Canary/Blue-Green을 Kubernetes 네이티브로
- Analysis Template로 자동 rollback
9. Supply Chain Security — 2020년 이후 대격변
SolarWinds 충격 (2020)
- 해커가 빌드 시스템에 침투
- 악성 코드를 정상 소프트웨어에 주입
- 18,000+ 고객에 배포
- CI/CD 자체가 공격 대상임을 드러냄
SLSA — Supply chain Levels for Software Artifacts
Google 주도, OpenSSF로 이관. 4단계 성숙도:
| 레벨 | 요구사항 |
|---|---|
| L0 | 요구사항 없음 |
| L1 | 빌드가 자동화 + 기본 provenance |
| L2 | 버전 관리 + provenance 생성 서명 |
| L3 | Hardened build (isolated, hermetic) |
| L4 | 2-party review + hermetic, reproducible |
대부분의 기업은 L2-L3이 현실적 목표.
Provenance — 무엇을, 어떻게 빌드했나
- 어떤 소스(git SHA)
- 어떤 빌더(GitHub Actions runner)
- 어떤 의존성
- 어떤 파라미터
- in-toto attestation 형식
Sigstore — "무료 서명 인프라"
- Linux Foundation 프로젝트
- Cosign — 이미지/아티팩트 서명
- Fulcio — keyless 인증서 발급 (OIDC 기반)
- Rekor — 투명성 로그
cosign sign --yes ghcr.io/org/image:v1.0.0
cosign verify --certificate-identity "..." ghcr.io/org/image:v1.0.0
키 관리 없이도 서명 가능 — Keyless 서명의 혁명.
SBOM — Software Bill of Materials
- 빌드 결과물에 포함된 모든 의존성 목록
- 포맷: SPDX, CycloneDX
- 미국 행정명령 14028 — 연방 정부 납품 필수
- EU CRA (Cyber Resilience Act) — 2025-2027 시행
도구
- Syft — SBOM 생성 (Anchore)
- Grype — CVE 스캔 (SBOM 기반)
- Trivy — 통합 스캐너 (Aqua)
- Dependency-Track — SBOM 집중 관리
Provenance 검증 in Kubernetes
- Kyverno / OPA Gatekeeper 정책으로 "서명되지 않은 이미지 거부"
- 클러스터 수준에서 enforce
10. 배포 전략 — 안전하게 푸는 기술
(상세는 앞선 "Feature Flag & Progressive Delivery" 글 참조, 여기서는 CI/CD 관점)
Rolling Update
- K8s 기본값, 점진적 pod 교체
- maxSurge/maxUnavailable 튜닝
- 간단하지만 rollback이 단순 이미지 교체
Blue/Green
- 두 환경 병행, LB 스위칭
- 빠른 rollback (LB만 전환)
- 비용 2배
Canary
- 1%, 5%, 25%, 100% 단계적 노출
- 지표 확인 + 자동 진행/회귀
- Argo Rollouts, Flagger 활용
Feature Flag
- 코드는 모두 배포, 플래그로 노출 제어
- 가장 유연, 코드 복잡도 증가
11. Dev Loop Speed — "5분 철학"
구글 내부 원칙 중 하나: "커밋 → 배포 가능 상태까지 5분 이내."
기법
- Incremental build — 변경된 것만
- Test selection — 관련 테스트만 (Bazel, Nx)
- Distributed execution — Remote execution
- Parallel pipeline — 독립 job 동시 실행
- Warm runner — self-hosted + 캐시 볼륨
- Pre-commit hook — 로컬에서 lint/format
측정 — DORA Metrics
2014년 Google DORA 팀이 제시한 4대 지표:
- Deployment Frequency — 배포 빈도
- Lead Time for Changes — 커밋 → 프로덕션 시간
- Change Failure Rate — 배포 실패율
- Time to Restore Service — 장애 복구 시간
Elite 팀:
- 배포: 하루 여러 번
- Lead Time: 1시간 미만
- 실패율: 15% 미만
- 복구: 1시간 미만
12. 실전 예시 — 현대적 풀스택 파이프라인
name: Full CI/CD
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
packages: write
attestations: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test --coverage
- uses: codecov/codecov-action@v5
build:
needs: test
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
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: build
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
sign:
needs: build
runs-on: ubuntu-latest
steps:
- uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
deploy:
needs: sign
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/infra-gitops
token: ${{ secrets.GITOPS_TOKEN }}
- name: Update image tag
run: |
yq -i '.image.tag = "${{ github.sha }}"'
apps/my-app/values.yaml
- run: |
git config user.name github-actions
git config user.email github-actions@github.com
git commit -am "deploy: ${{ github.sha }}"
git push
# Argo CD가 이후 자동 sync
이 한 파일에:
- 테스트 + 커버리지
- 멀티 스테이지 빌드 + GHA 캐시
- Provenance + SBOM 자동 생성
- Keyless 서명 (Cosign + Fulcio)
- GitOps 푸시 → Argo CD가 배포
13. 안티패턴 TOP 10
- Latest 태그 배포 — 재현 불가, 디버그 지옥
- CI에 장기 자격 증명 저장 — OIDC 써라
- 메인 브랜치 직접 빌드/배포 — Branch protection 필수
- Flaky test 재시도로 가림 — 근본 원인 수정
npm install(vsnpm ci) — lockfile 존중 안 함- CI에서
sudo apt install— 빌드 환경 오염, 느림 - Secret을 로그에 echo — GitHub이 일부 마스킹하지만 주의
- Pipeline 없이 개별 CI 스크립트 — 일관성 부재
- 서명 없는 이미지 배포 — SolarWinds 재현 가능
- 빌드 시간 30분 이상 방치 — 개발 속도 마비
14. 현대 CI/CD 체크리스트
- Pipeline as Code — YAML이 리포지토리에
- Branch protection + required status checks
- Concurrency control — PR 중복 실행 취소
- Build cache — actions/cache 또는 Turborepo/Nx
- Multi-stage Docker build — 작은 이미지
- OIDC 인증 — 장기 key 제거
- 테스트 병렬화 — shard + 재시도 전략
- SBOM + Provenance 자동 생성
- Cosign keyless signing
- Argo CD/Flux — pull 기반 배포
- DORA 메트릭 대시보드
- 배포 rollback 15분 이내 가능 훈련
마치며 — CI/CD는 문화다
CI/CD는 도구 선택이 아니라 문화다. GitHub Actions냐 Jenkins냐보다, "커밋하면 15분 안에 staging에 가는가?" "금요일 오후 6시에도 배포할 수 있는가?" "배포 실패 시 1시간 안에 복구되는가?"가 훨씬 중요하다.
공급망 보안의 시대에는 한 걸음 더 나아가야 한다. "내가 빌드한 이것이 정확히 어떤 소스에서, 어떤 경로로 나왔는가?"를 서명된 증거로 남기는 것. SolarWinds 이후, 이 질문에 답하지 못하는 조직은 규제/고객 감사에서 걸러진다.
"Slow is smooth, and smooth is fast. But in CI/CD, fast is safe — because you catch problems before they compound." — Charity Majors (Honeycomb)
다음 글 예고 — 프론트엔드 번들러의 내전 — Webpack, Vite, esbuild, Turbopack, Rspack, Rolldown (2026)
CI/CD가 배포의 혈관이라면, 번들러는 프론트엔드 빌드의 심장이다. 다음 글에서는:
- 번들링의 역사 — Browserify → Webpack의 독점 → JS 런타임 전쟁
- Webpack — 왜 승자였고, 왜 내려가는가
- esbuild — Go의 위력, 100배 빠른 빌드
- Vite — Evan You의 "개발은 ESM, 빌드는 Rollup"
- Turbopack — Vercel의 Rust 베팅
- Rspack — 바이트댄스의 Rust Webpack
- Rolldown — Vite의 다음 진화 (2025)
- Bun bundler — 번들 + 런타임 + 패키지 매니저 통합
- Tree-shaking, Code splitting, HMR — 기저 기술
- Module Federation — 마이크로 프론트엔드
- ESM in production — import maps의 부상
"빌드 시간 3초" 시대를 가능케 한 Rust/Go 혁명을 해부하는 여정.
"The best CI/CD pipeline is the one you don't notice. It just works, every time, for everyone." — Kelsey Hightower
현재 단락 (1/381)
CI/CD는 지루해 보인다. "빌드 돌리고, 테스트 돌리고, 배포한다." 그러나 대형 팀과 소형 팀을 가르는 가장 큰 차이가 **CI/CD 파이프라인의 질**이라는 건 놀라운 사실...