개요
CI/CD는 코드를 변경하면 자동으로 빌드, 테스트, 배포까지 이어지게 만드는 자동화 파이프라인입니다.
이 글에서는 도구에 상관없이 공통으로 알아야 할 CI/CD 개념, 파이프라인 구조, 환경 분리, Secrets 관리를 정리합니다.
1. CI와 CD는 뭐가 다른가
정리 보기
CI (Continuous Integration) — 지속적 통합
개발자가 코드를 push하면 자동으로:
- 빌드 (컴파일, 이미지 생성)
- 테스트 (유닛, 통합)
- 정적 분석 (린트, 보안 스캔)
“코드가 깨지지 않았는지 매번 확인하는 것"입니다.
CD (Continuous Delivery / Deployment) — 지속적 전달/배포
CI를 통과하면 자동으로:
- 스테이징 환경에 배포
- (승인 후) 프로덕션 배포
| 용어 | 차이 |
|---|---|
| Continuous Delivery | 프로덕션 배포 전에 수동 승인 필요 |
| Continuous Deployment | 승인 없이 자동으로 프로덕션까지 배포 |
두 용어의 차이는 프로덕션 배포에 사람의 승인이 들어가는지 여부입니다. Continuous Delivery는 프로덕션 배포를 수동 승인으로 두고, Continuous Deployment는 승인 없이 자동 배포합니다.
전체 흐름
개발자 push
→ CI: 빌드 → 테스트 → 이미지 생성 → Registry push
→ CD: dev 자동 배포 → stg 자동 배포 → prod 수동 승인 후 배포2. 파이프라인 구조
정리 보기
파이프라인 = 스테이지의 연속
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Build │ → │ Test │ → │ Deploy │ → │ Notify │
│ (빌드) │ │ (테스트) │ │ (배포) │ │ (알림) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘각 스테이지는 이전 스테이지가 성공해야 실행됩니다. 하나라도 실패하면 파이프라인 중단.
일반적인 스테이지 구성
| 스테이지 | 하는 일 | 예시 |
|---|---|---|
| Checkout | 코드 가져오기 | git clone |
| Build | 빌드/컴파일 | npm run build, docker build |
| Test | 테스트 실행 | pytest, jest |
| Lint | 코드 품질 검사 | eslint, flake8 |
| Scan | 보안 취약점 스캔 | trivy, snyk |
| Push | 이미지를 Registry에 업로드 | docker push |
| Deploy | 환경에 배포 | kubectl apply, helm upgrade |
| Notify | 결과 알림 | Slack, 이메일 |
트리거 — 언제 실행되냐
| 트리거 | 동작 |
|---|---|
| push | 특정 브랜치에 push하면 실행 |
| merge request / PR | MR 생성/업데이트 시 실행 |
| schedule | 정해진 시간에 실행 (야간 빌드) |
| manual | 버튼 클릭 시 실행 (프로덕션 배포) |
| tag | 태그 생성 시 실행 (릴리즈) |
3. 환경 분리 (dev / stg / prod)
정리 보기
왜 분리하냐
- 환경마다 데이터와 설정이 다르므로 검증 결과가 달라질 수 있음
- 환경별로 DB, API 주소, 설정이 다름
일반적인 환경 구성
| 환경 | 용도 | 배포 방식 |
|---|---|---|
| dev | 개발자가 자유롭게 테스트 | push하면 자동 배포 |
| stg (staging) | 프로덕션과 동일 환경에서 최종 검증 | dev 통과 후 자동 or 수동 |
| prod (production) | 실제 사용자가 쓰는 환경 | 수동 승인 후 배포 |
브랜치 전략과 연결
feature/* → develop 브랜치 → dev 환경 자동 배포
develop → main(또는 release) 브랜치 → stg 환경 배포
main + 태그 → prod 환경 배포 (수동 승인)환경별 설정 분리 방법
방법 1: 환경변수로 분리
- dev: DB_HOST=dev-db.internal
- prod: DB_HOST=prod-db.internal
방법 2: 설정 파일 분리
- config/dev.yaml
- config/prod.yaml
방법 3: Helm values 분리 (K8s)
- values-dev.yaml
- values-prod.yaml4. Secrets 관리
정리 보기
Secrets = DB 비밀번호, API 키, 인증서처럼 저장소와 분리해 관리하는 값
코드에 하드코딩하면:
- Git 히스토리에 영구 기록
- 레포 접근 가능한 사람 전부 볼 수 있음
- 커밋 기록에서 값을 제거하려면 히스토리 재작성이 필요함
도구별 Secrets 관리
| 도구 | 방법 |
|---|---|
| Jenkins | Credentials 메뉴에서 등록 → Jenkinsfile에서 참조 |
| GitLab CI | Settings > CI/CD > Variables에서 등록 |
| GitHub Actions | Settings > Secrets and variables > Actions에서 등록 (레포/환경/조직 단위) |
| K8s | Secret 오브젝트 or 외부 연동 |
GitHub Actions에서는 ${{ secrets.NAME }}으로 참조하고, 워크플로가 fork된 레포에서 트리거된 경우 GITHUB_TOKEN을 제외한 시크릿은 러너로 전달되지 않습니다. 클라우드 자격증명이라면 OIDC를 지원하는 클라우드 제공자에 직접 인증해서 장기 시크릿 저장을 없앨 수 있습니다.
외부 Secrets Manager
| 도구 | 특징 |
|---|---|
| AWS Secrets Manager | 시크릿 자동 교체(rotation) 지원, AWS SDK/서비스에서 직접 조회 |
| AWS Parameter Store | 키-값 저장. 표준 티어는 추가 비용 없이 계정·리전당 10,000개, 값 최대 4KB. advanced 티어는 100,000개, 8KB이며 과금 대상 |
| HashiCorp Vault | 온프레미스·클라우드·하이브리드 환경 모두 대상. 중앙화된 시크릿 관리와 정책 기반 접근 제어, 모든 접근을 감사 로그로 남김 |
5. 도구 비교 — Jenkins vs GitLab CI
정리 보기
| 항목 | Jenkins | GitLab CI |
|---|---|---|
| 설치 | 별도 서버 필요 (직접 운영) | GitLab에 내장 (서버 불필요) |
| 설정 파일 | Jenkinsfile | .gitlab-ci.yml |
| 실행 환경 | Controller가 Agent(Node)에 작업 분배 | Runner (Docker/Kubernetes 등 executor) |
| 확장 방식 | 플러그인 설치로 기능 추가 | 내장 기능 + 컴포넌트 |
| UI | 웹 대시보드 | GitLab UI 통합 |
| 확장성 | Agent 추가로 확장 | Runner 추가로 확장 |
| 설정 언어 | Groovy 기반 Jenkinsfile | YAML 기반 .gitlab-ci.yml |
Jenkins 용어는 controller와 agent입니다. 예전에 쓰인 master/slave 표기는 같은 대상을 가리키는 옛 표기입니다.
언제 뭘 쓰냐
| 상황 | 선택 |
|---|---|
| 회사가 GitLab 쓰고 있음 | GitLab CI |
| 레거시 시스템 + 복잡한 빌드 | Jenkins |
| GitHub 쓰는 오픈소스/개인 | GitHub Actions |
6. 파이프라인 구성
정리 보기
파이프라인 실행 시간
- 스테이지는 순차 실행이므로 스테이지가 늘어나면 전체 실행 시간도 늘어남
- GitHub Actions와 GitLab CI 모두 의존성·빌드 산출물 캐시 기능을 제공함 (npm cache, Docker layer cache)
- 테스트를 여러 Job으로 나누면 Job은 병렬로 실행됨
실패 알림
- 스테이지가 실패하면 파이프라인이 중단되고, 실패한 Job과 로그가 도구 UI에 남음
- GitLab CI는 파이프라인 결과를 이메일과 Slack 등으로 알리는 통합을 제공하고, GitHub Actions는 워크플로 실행 결과를 알림 설정과 API로 전달함
멱등성
- 같은 코드로 같은 파이프라인 돌리면 같은 결과
- 컨테이너 이미지로 빌드 환경을 고정하면 러너 호스트의 설치 상태에 따른 차이가 줄어듦
롤백과 이미지 태그
- 이미지 태그를 git commit SHA로 지정하면 태그와 코드 리비전이 1:1로 대응함
- 릴리즈 태그를 쓰면 배포된 버전이 어느 릴리즈인지 태그로 식별됨
이미지 태그 예시:
myapp:abc123f (commit SHA 앞 7자리)
myapp:v1.2.3 (릴리즈 태그)시크릿과 이미지
- GitLab CI의 masked variable과 GitHub Actions의 시크릿은 Job 로그에 값이 그대로 출력되지 않도록 마스킹됨
- 멀티 스테이지 빌드에서 최종 이미지에는 마지막 스테이지에 복사한 파일만 포함되므로 빌드 단계의 소스 코드는 남지 않음
7. 파이프라인 디버깅
정리 보기
디버깅 방법
- Jenkins: Replay 기능으로 커밋하지 않고 스크립트를 고쳐서 다시 실행 가능
- GitLab CI: 특정 Job만 retry 가능
- GitLab CI: 파이프라인 에디터의 Validate 탭에서 CI Lint로 문법·로직 검증, “Simulate pipeline creation"으로 needs·rules 관련 문제까지 확인 가능. 파이프라인 에디터는 문법을 자동으로 검증합니다.
- 예전에 로컬 실행에 쓰던
gitlab-runner exec는 deprecated된 뒤 제거되어, 현재 GitLab Runner 공식 명령 목록에 없습니다. - Jenkins: CLI/HTTP POST로 Declarative Pipeline을 실행 전에 lint할 수 있습니다.
- GitHub Actions는 커뮤니티 도구
act로 로컬 실행을 시도해볼 수 있습니다 (공식 도구 아님)