1. CI/CD란 무엇인가
정의
CI = Continuous Integration (지속적 통합)
→ 개발자가 코드를 push할 때마다 자동으로 빌드 + 테스트
CD = Continuous Delivery (지속적 전달)
→ CI를 통과한 코드를 언제든 배포 가능한 상태로 유지
→ 배포 자체는 사람이 버튼을 누름 (수동 승인)
CD = Continuous Deployment (지속적 배포)
→ CI를 통과하면 자동으로 프로덕션까지 배포
→ 사람 개입 없음 (완전 자동화)
Continuous Delivery와 Continuous Deployment는 같은 약자(CD)를 쓰지만 의미가 다르다:
| 구분 | Continuous Delivery | Continuous Deployment |
|---|---|---|
| 배포 시점 | 수동 승인 후 배포 | 자동 배포 |
| 적합한 환경 | 금융, 의료 등 규제 산업 | SaaS, 스타트업 |
| CGV에서 | Prod (수동 Sync) | Dev (자동 Sync) |
CGV 대기열 시스템은 두 가지를 모두 사용한다:
- Dev 환경: Continuous Deployment — push하면 자동 배포
- Prod 환경: Continuous Delivery — 운영자가 ArgoCD UI에서 수동 Sync
왜 CI/CD가 필요한가
CI/CD가 없는 세상:
개발자 A: 기능 개발 완료 (2주간 혼자 작업)
↓
로컬에서 빌드: "내 PC에서는 되는데..."
↓
수동으로 서버에 JAR 복사: scp app.jar user@server:/app/
↓
서버에서 수동 재시작: ssh server "kill -9 PID && java -jar app.jar &"
↓
개발자 B: "어? 내 코드랑 충돌나는데?"
↓
3일 동안 머지 충돌 해결
↓
프로덕션 장애: "누가 이 코드 배포했어?"
CI/CD가 있는 세상:
개발자 A: git push origin feature/queue-optimization
↓ (30초)
CI 자동 실행:
1. 빌드 (Maven/Gradle) → 컴파일 오류 즉시 발견
2. 단위 테스트 (JUnit) → 로직 오류 즉시 발견
3. 정적 분석 (SonarQube) → 코드 품질 확인
4. Docker 이미지 빌드 → ECR에 자동 푸시
↓ (5분)
CD 자동 실행:
5. ArgoCD가 새 이미지 감지 → Dev 환경 자동 배포
6. Canary 10% → 에러율 확인 → 50% → 100% (Prod)
↓
7. 문제 발생 시 자동 롤백
핵심 가치:
- 속도: 코드 작성 → 프로덕션 반영이 수십 분 이내
- 품질: 매 push마다 테스트가 실행되어 버그가 축적되지 않음
- 추적성: 누가, 언제, 무엇을 배포했는지 Git 히스토리로 추적
- 자신감: 롤백이 보장되므로 배포에 대한 두려움 제거
2. CI (Continuous Integration) — 빌드와 테스트 자동화
CI 파이프라인의 구성 요소
┌─────────────────────────────────────────────────────────┐
│ CI 파이프라인 │
│ │
│ Stage 1 Stage 2 Stage 3 Stage 4 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Test │ ──→ │Build │ ──→ │ Scan │ ──→ │ Push │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ 단위테스트 JAR/WAR 빌드 정적분석 이미지 푸시 │
│ 통합테스트 의존성 해결 보안 스캔 ECR/Docker Hub│
│ │
│ ← ← ← ← ← ← Artifact 전달 → → → → → → → │
└─────────────────────────────────────────────────────────┘
핵심 개념
| 개념 | 설명 | CGV 예시 |
|---|---|---|
| Stage | 파이프라인의 단계 (순차 실행) | test → build → docker → notify |
| Job | Stage 안에서 실행되는 작업 단위 | mvn test, docker build |
| Runner/Worker | Job을 실행하는 환경 (컨테이너 또는 VM) | GitLab Runner, GitHub Actions Runner |
| Artifact | Job 간 전달되는 산출물 | JAR 파일, Docker 이미지, 테스트 결과 |
| Trigger | 파이프라인을 시작하는 이벤트 | git push, PR 생성, 스케줄, 수동 |
| Cache | 반복 다운로드 방지 (빌드 속도 향상) | Maven .m2/, npm node_modules/ |
CI에서 무엇을 검증하는가
1. 컴파일 (Build)
→ "코드가 문법적으로 올바른가?"
→ mvn compile / gradle build / npm run build
2. 단위 테스트 (Unit Test)
→ "각 메서드가 의도대로 동작하는가?"
→ mvn test / pytest / jest
→ CGV: QueueService.enter() 정원 초과 시 QUEUED 반환 확인
3. 통합 테스트 (Integration Test)
→ "컴포넌트 간 연동이 올바른가?"
→ Redis 연결 + WebSocket 메시지 전달 확인
→ CGV: Testcontainers로 Redis 띄우고 Lua 스크립트 실행
4. 정적 분석 (Static Analysis)
→ "코드 품질이 기준을 충족하는가?"
→ SonarQube, ESLint, Checkstyle
→ 커버리지 80% 미만이면 실패 처리
5. 보안 스캔 (Security Scan)
→ "알려진 취약점이 있는가?"
→ Trivy (컨테이너 이미지), OWASP Dependency-Check
→ CVE-2024-XXXX 발견 시 빌드 실패
6. Docker 이미지 빌드 + 푸시
→ "배포 가능한 이미지가 만들어지는가?"
→ docker build → docker push ECR
Docker-in-Docker (DinD) vs Docker Socket Mount
CI 환경에서 Docker 이미지를 빌드하려면 Docker Engine이 필요하다. 두 가지 방법이 있다:
방법 1: Docker-in-Docker (DinD)
┌───────────────────────────┐
│ Runner 컨테이너 │
│ ┌─────────────────────┐ │
│ │ Job 컨테이너 │ │
│ │ docker build ... │ │
│ │ ↓ │ │
│ │ [Docker Engine] │ │ ← 컨테이너 안에 별도 Docker 실행
│ └─────────────────────┘ │
└───────────────────────────┘
장점: 격리성 (호스트 영향 없음)
단점: 느림 (이중 가상화), 보안 설정 복잡 (privileged 필요)
방법 2: Docker Socket Mount
┌───────────────────────────┐
│ 호스트 │
│ [Docker Engine] ←────────── /var/run/docker.sock
│ ↑ │
│ ┌─────────────────────┐ │
│ │ Job 컨테이너 │ │
│ │ docker build ... ──── 호스트 Engine에 명령 전달
│ └─────────────────────┘ │
└───────────────────────────┘
장점: 빠름 (호스트 Engine 직접 사용)
단점: 보안 위험 (호스트 Docker 접근 가능)
CGV 이전 개발계: Docker Socket Mount 사용 (GitLab Runner가 호스트의 Docker Engine을 공유)
3. CD (Continuous Delivery/Deployment) — 배포 자동화
Push 방식 vs Pull 방식 (GitOps)
CD를 구현하는 근본적으로 다른 두 가지 접근법이 있다:
Push 방식 (전통적)
────────────────────────────────────────────
CI 서버 (Jenkins, GitLab CI)
│
│ kubectl apply -f deployment.yaml
│ helm upgrade release ./chart
↓
┌──────────┐
│ K8s 클러스터 │ ← CI 서버가 kubeconfig 보유
└──────────┘
문제점:
1. CI 서버가 클러스터 접근 권한을 가짐 → 해킹 시 클러스터 탈취
2. CI 서버가 SPOF → CI 장애 시 배포 불가
3. "누가 kubectl로 직접 바꿨는지" 추적 불가
Pull 방식 (GitOps)
────────────────────────────────────────────
Git 저장소 (진실의 원천, Source of Truth)
↑ │
│ 이미지 태그 커밋 │ 주기적 감시 (3분 폴링)
│ ↓
CI 서버 ──→ ECR ArgoCD (클러스터 내부)
│
│ kubectl apply (내부에서)
↓
┌──────────┐
│ K8s 클러스터 │
└──────────┘
장점:
1. CI 서버는 클러스터 접근 권한 불필요 (ECR push만)
2. Git이 유일한 진실 → 모든 변경 이력 추적 가능
3. 클러스터 내부에서 배포 → 보안 경계 분리
4. Drift Detection: 누가 kubectl로 바꿔도 Git 상태로 복원
CGV 대기열 시스템은 Pull 방식(GitOps)을 채택했다.
CI(빌드/테스트/이미지 푸시)와 CD(배포)가 완전히 분리되어,
CI 도구를 교체해도 CD에 영향이 없다.
GitOps의 4가지 원칙
| 원칙 | 설명 | CGV 적용 |
|---|---|---|
| 선언적(Declarative) | 원하는 상태를 코드로 선언 | Helm values.yaml, Rollout YAML |
| 버전 관리(Versioned) | 모든 변경이 Git에 기록 | 이미지 태그 변경도 Git 커밋 |
| 자동 반영(Automated) | Git 변경 → 자동 배포 | ArgoCD 자동 Sync (Dev) |
| 자가 치유(Self-healing) | 실제 상태 ≠ Git → 자동 복원 | selfHeal: true 설정 |
Drift Detection (드리프트 감지)
시나리오: 누군가 kubectl로 직접 Pod를 5개로 스케일
Git: replicas: 2 (선언된 상태)
↓ ArgoCD 배포
클러스터: Pod 2개 (실제 상태)
↓
운영자: kubectl scale deployment cgv-api --replicas=5
↓
클러스터: Pod 5개 (Git과 불일치!)
↓
ArgoCD: "OutOfSync" 감지
↓ (selfHeal: true인 경우)
클러스터: Pod 2개로 자동 복원 ✅
→ Git에 없는 변경은 존재할 수 없다 = "진실의 원천"
4. 배포 전략 — Rolling, Blue-Green, Canary
4가지 배포 전략 비교
1. Recreate (재생성)
─────────────────────
v1 ████████████ → (다운타임) → v2 ████████████
모든 Pod 삭제 후 새 Pod 생성. 다운타임 발생.
용도: DB 스키마 변경 등 호환성 없는 업데이트
2. Rolling Update (순차 교체)
─────────────────────
v1 ████████████
v1 ████████ v2 ████
v1 ████ v2 ████████
v2 ████████████████
하나씩 교체. 다운타임 없음. K8s 기본값.
CGV Dev 환경에서 사용.
3. Blue-Green (청록 배포)
─────────────────────
Blue (v1) ████████████ ← 현재 트래픽
Green(v2) ████████████ ← 대기 (트래픽 없음)
↓ 스위치
Blue (v1) ████████████ ← 대기
Green(v2) ████████████ ← 전체 트래픽
두 배 인프라 필요. 즉시 롤백 가능.
용도: 대규모 변경 시 안전한 전환.
4. Canary (카나리 배포)
─────────────────────
v1 ████████████████████ (100%)
v1 ██████████████████ v2 ██ (10%)
↓ 에러율 확인
v1 ██████████ v2 ██████████ (50%)
↓ 에러율 확인
v2 ████████████████████████ (100%)
단계적으로 트래픽 이동. 각 단계에서 검증.
CGV Prod 환경에서 사용.
상세 비교표
| 특성 | Recreate | Rolling Update | Blue-Green | Canary |
|---|---|---|---|---|
| 다운타임 | 있음 | 없음 | 없음 | 없음 |
| 롤백 속도 | 느림 (재배포) | 느림 (재배포) | 즉시 (스위치) | 빠름 (비율 복원) |
| 리소스 | 1배 | 1~1.25배 | 2배 | 1.1배 |
| 위험도 | 높음 | 중간 | 낮음 | 최저 |
| 검증 | 없음 | 없음 | 수동 확인 | 자동 메트릭 |
| 복잡도 | 낮음 | 낮음 | 중간 | 높음 |
| K8s 리소스 | Deployment | Deployment | Deployment+Service | Rollout (Argo) |
| CGV 사용 | - | Dev 환경 | - | Prod 환경 |
CGV의 Canary 배포 흐름
개발자: git push origin main
↓
CI: 빌드 → 테스트 → 이미지 ECR 푸시 (v1.2.0)
↓
ArgoCD Image Updater: 새 이미지 감지 → values.yaml 태그 업데이트 → Git 커밋
↓
ArgoCD: Git 변경 감지 → Prod는 수동 Sync
↓
운영자: ArgoCD UI에서 "Sync" 버튼 클릭
↓
Argo Rollouts: Canary 시작
┌──────────────────────────────────────────────┐
│ Step 1: setWeight: 10 │
│ → 트래픽 10%를 v1.2.0으로 라우팅 │
│ → pause 2분 (안정화 대기) │
│ │
│ Step 2: analysis (error-rate-check) │
│ → Prometheus 조회: 에러율 < 5%? │
│ → 3회 연속 실패 시 자동 롤백 │
│ │
│ Step 3: setWeight: 50 │
│ → 트래픽 50%로 확대 │
│ → pause 5분 │
│ │
│ Step 4: analysis (error-rate-check) │
│ → 한 번 더 에러율 확인 │
│ │
│ Step 5: setWeight: 100 │
│ → 전체 트래픽 v1.2.0으로 전환 │
└──────────────────────────────────────────────┘
Argo Rollouts의 AnalysisTemplate이 Prometheus 메트릭을 자동으로 확인한다.
에러율이 5% 이상이면 사람 개입 없이 자동 롤백된다.
5. CI 도구 비교
주요 CI 도구 개요
┌───────────────────────────────────────────────────────────┐
│ CI 도구 생태계 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ GitHub │ │ GitLab CI │ │ Jenkins │ │
│ │ Actions │ │ │ │ │ │
│ │ (SaaS) │ │ (SaaS/Self) │ │ (Self-host) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AWS │ │ CircleCI │ │ Tekton │ │
│ │ CodePipeline│ │ │ │ (K8s 네이티브)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────────┘
핵심 3가지 비교
| 특성 | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| 호스팅 | SaaS (GitHub.com) | SaaS or Self-hosted | Self-hosted only |
| 설정 파일 | .github/workflows/*.yml |
.gitlab-ci.yml |
Jenkinsfile |
| 무료 티어 | 2,000분/월 | 400분/월 | 무제한 (인프라 비용) |
| Runner | GitHub-hosted / Self-hosted | GitLab.com Shared / Self-hosted | Agent (Node) |
| Docker 빌드 | 기본 지원 | DinD 또는 Kaniko | 플러그인 |
| Git 저장소 | GitHub 필수 | GitLab 필수 | 아무 Git |
| Container Registry | ghcr.io (무료) | GitLab CR (내장) | 없음 |
| 보안 스캔 | 별도 Action 설치 | SAST/DAST 내장 (Ultimate) | 플러그인 |
| 학습 곡선 | 낮음 | 중간 | 높음 |
| 플러그인 생태계 | Marketplace (수만 개) | 템플릿 (수백 개) | 플러그인 (수천 개) |
| K8s 연동 | OIDC로 AWS 인증 | Runner를 K8s Pod로 실행 가능 | K8s Plugin |
| 현업 주류 | 스타트업, 오픈소스 | 대기업 DevOps팀 | 레거시, 금융 |
| ArgoCD 연동 | GitOps (이미지 태그 커밋) | GitOps (동일) | GitOps (동일) |
Self-hosted Runner 실행 환경 비교
CI 도구를 선택한 후, Runner(빌드 실행기)를 어디서 돌릴지도 중요한 설계 결정이다.
| 실행 환경 | GitHub Actions (GitHub-hosted) | GitHub Actions (ARC on K8s) | GitLab Runner (EC2 단일) | GitLab Runner (K8s Executor) |
|---|---|---|---|---|
| 인프라 관리 | 없음 (GitHub 제공) | ARC 설치/관리 | EC2 관리 | Runner Helm chart |
| 빌드 격리 | VM 수준 | Pod 수준 | 컨테이너 (공유 Docker) | Pod 수준 (ephemeral) |
| 스케일링 | 자동 (GitHub) | Pod auto-scale | 불가 (EC2 1대) | Pod auto-scale |
| VPC 접근 | 불가 | 가능 | 가능 | 가능 |
| ECR 인증 | OIDC (간편) | OIDC + IRSA | Access Key (관리 부담) | IRSA (Pod 단위) |
| 캐시 | GitHub Storage (10GB) | S3 가능 | 로컬 디스크 | S3 분산 캐시 |
| Docker 빌드 | 기본 지원 | DinD or Kaniko | Docker Socket Mount | Kaniko (unprivileged) |
| 보안 | GitHub이 VM 관리 | Pod SecurityContext | EC2 수준 | Pod SecurityContext |
| 비용 | 2,000분 무료 | Runner EC2 비용 | EC2 상시 가동 | 빌드 시만 Pod |
| 성숙도 | 2019~ | ARC GA 2023 | 2012~ | K8s Executor 2016~ |
| CGV | - | - | 이전 개발계 | 리팩토링 후 |
CGV에서 GitLab Runner K8s Executor를 선택한 이유:
GitHub Actions의 GitHub-hosted Runner가 가장 간편하지만 VPC 내부 리소스에 접근할 수 없다.
Self-hosted Runner를 쓸 거라면, GitLab Runner의 Kubernetes Executor가 GitHub ARC보다 성숙하다 (2016 vs 2023 GA).
job마다 ephemeral Pod를 생성하여 빌드 환경을 완전히 격리하고, IRSA로 ECR 접근 권한을 Pod 단위로 최소화하며,
S3 분산 캐시로 빌드 시간을 최적화한다.
각 도구 상세
GitHub Actions
# .github/workflows/ci.yml
name: CGV CI Pipeline
on:
push:
branches: [develop, main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
- run: mvn test
build-and-push:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC 토큰 발급
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/github-actions-role
aws-region: ap-northeast-2
- uses: aws-actions/amazon-ecr-login@v2
- run: |
docker build -t $ECR_URI:${{ github.sha }} .
docker push $ECR_URI:${{ github.sha }}
핵심 특징:
- OIDC로 AWS 인증 → Access Key 불필요 (보안)
needs: test→ Stage 간 의존성 명시runs-on: ubuntu-latest→ GitHub이 Runner 제공 (인프라 관리 불필요)- Self-hosted Runner도 가능 → EKS Pod에서 실행하면 VPC 내부에서 ECR 접근
GitLab CI
# .gitlab-ci.yml
stages:
- test
- build
- docker
- notify
test:
stage: test
image: maven:3.9-eclipse-temurin-17
script:
- mvn verify
artifacts:
paths:
- target/surefire-reports/
build:
stage: build
image: maven:3.9-eclipse-temurin-17
script:
- mvn package -DskipTests
artifacts:
paths:
- target/*.jar
docker:
stage: docker
image: docker:24.0.5
services:
- docker:24.0.5-dind
script:
- docker build -t $ECR_URI:$CI_COMMIT_SHORT_SHA .
- docker push $ECR_URI:$CI_COMMIT_SHORT_SHA
핵심 특징:
services: docker:dind→ Docker-in-Docker로 이미지 빌드artifacts→ Stage 간 파일 전달 (JAR, 테스트 결과)- Self-hosted: EC2에 GitLab + Runner 설치 → Private Subnet에서 운영 가능
- 내장 Container Registry → ECR 대신 GitLab CR 사용 가능
Jenkins
// Jenkinsfile
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Build') {
steps {
sh 'mvn package -DskipTests'
}
}
stage('Docker') {
steps {
sh 'docker build -t $ECR_URI:$BUILD_NUMBER .'
sh 'docker push $ECR_URI:$BUILD_NUMBER'
}
}
}
}
핵심 특징:
- Groovy 기반
Jenkinsfile→ 프로그래밍 수준의 유연성 - 모든 것이 플러그인 → 설정/유지보수 복잡
- 완전 자체 호스팅 → 인프라 관리 부담
AWS CodePipeline (참고)
| 특성 | 설명 |
|---|---|
| 장점 | AWS 네이티브 → IAM, ECR, ECS 자동 연동 |
| 단점 | AWS 종속 (멀티클라우드 불가), ArgoCD 연동 어려움 |
| 비용 | 파이프라인 $1/월 + CodeBuild $0.005/분 |
| 적합 | ECS/Fargate 환경, AWS 올인 프로젝트 |
| CGV | EKS + ArgoCD 사용 → CodePipeline 불필요 |
6. CD 도구 비교
주요 CD 도구 (GitOps)
| 특성 | ArgoCD | Flux | Spinnaker |
|---|---|---|---|
| 호스팅 | K8s 내부 | K8s 내부 | 별도 서버 |
| UI | 웹 UI 제공 | CLI only | 웹 UI 제공 |
| Git 감시 | 폴링 (3분) + Webhook | 폴링 + Webhook | Webhook |
| 멀티클러스터 | 지원 | 지원 | 지원 |
| Helm 지원 | 네이티브 | Helm Controller | 지원 |
| Canary | Argo Rollouts | Flagger | 내장 |
| 이미지 자동 업데이트 | Image Updater | Image Automation | 없음 |
| 학습 곡선 | 중간 | 낮음 | 높음 |
| 커뮤니티 | CNCF Graduated | CNCF Graduated | Netflix OSS |
| 현업 주류 | 가장 많이 사용 | GitOps 순수주의 | 대규모 엔터프라이즈 |
왜 ArgoCD인가 (CGV 선택 근거)
1. 웹 UI
→ Flux는 CLI만 지원. 운영자가 배포 상태를 시각적으로 확인하려면 UI 필수
→ 포트폴리오에서 면접관에게 데모할 때도 UI가 있어야 설득력
2. Argo Rollouts (Canary)
→ Flux의 Flagger도 Canary를 지원하지만, Argo 생태계 통일이 유리
→ ArgoCD + Argo Rollouts = 같은 프로젝트, 같은 CRD 체계
3. Image Updater
→ ECR에 새 이미지가 올라오면 자동으로 Git의 values.yaml 업데이트
→ CI → ECR → Image Updater → Git → ArgoCD → K8s (완전 자동화)
4. CNCF Graduated
→ 프로덕션 검증 완료. 대부분의 기업에서 채택
→ 면접에서 "ArgoCD 경험 있습니다" = 큰 플러스
7. 컨테이너와 이미지 레지스트리
Docker 이미지 기초
Dockerfile → docker build → 이미지 → docker push → 레지스트리 → docker pull → 컨테이너
┌─────────────────────────┐
│ Docker 이미지 구조 │
│ │
│ Layer 4: COPY app.jar │ ← 변경 시 이 레이어만 재빌드
│ Layer 3: RUN mvn build │
│ Layer 2: RUN apt install│
│ Layer 1: FROM eclipse- │ ← 베이스 이미지
│ temurin:17 │
└─────────────────────────┘
이미지 = 레이어의 스택 (읽기 전용)
컨테이너 = 이미지 + 쓰기 가능 레이어 (런타임)
이미지 태그 전략
❌ 나쁜 예: latest
→ 어떤 버전인지 알 수 없음
→ 롤백 불가능
✅ 좋은 예:
1. Git SHA: cgv-api:a1b2c3d
→ 정확한 커밋 추적 가능
2. 시맨틱 버전 + 타임스탬프: cgv-api:1.0.0-250601-143000
→ 버전 의미 + 빌드 시점 추적
→ CGV에서 채택한 방식
3. Git SHA + Branch: cgv-api:develop-a1b2c3d
→ 브랜치별 분리 + 커밋 추적
이미지 레지스트리 비교
| 특성 | AWS ECR | Docker Hub | GitLab CR | Harbor |
|---|---|---|---|---|
| 호스팅 | AWS 관리형 | SaaS | GitLab 내장 | Self-hosted |
| 비용 | $0.10/GB/월 | Free (제한적) | GitLab에 포함 | 인프라 비용 |
| IAM 연동 | 네이티브 | 없음 | GitLab 인증 | LDAP/OIDC |
| 이미지 스캔 | 내장 (Trivy) | 유료 | 유료 (Ultimate) | 내장 (Trivy) |
| EKS 연동 | VPC 내부 풀 | 인터넷 필요 | 인터넷 필요 | VPC 내부 가능 |
| CGV 선택 | ✅ | - | - | - |
CGV가 ECR을 선택한 이유: EKS 워커 노드가 VPC 내부에서 ECR 이미지를 풀받으므로
NAT Gateway 트래픽 비용이 들지 않는다 (VPC Endpoint 사용).
8. CGV 대기열 시스템의 CI/CD
8.1 이전 개발계 CI/CD 구조 (리팩토링 전)
┌─────────────────────────────────────────────────────────────┐
│ EC2 인스턴스 1대 (Private Subnet) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GitLab │ │ GitLab │ │ Docker │ │
│ │ Server │ │ Runner │ │ Engine │ │
│ │ (Git+CI) │ │ (Job실행)│ │ (빌드) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↕ ↕ ↕ │
│ └──────── 모두 같은 서버 ──────┘ │
│ │
│ .gitlab-ci.yml: │
│ test → build → login → docker → (deploy=ArgoCD) → notify │
└─────────────────────────────────────────────────────────────┘
↓ ECR push
┌──────────────┐
│ AWS ECR │
└──────────────┘
↓ Image Updater 감지
┌──────────────┐ ┌──────────────┐
│ ArgoCD │ ────→ │ EKS 클러스터│
│ (infra 노드) │ │ │
└──────────────┘ └──────────────┘
장점:
- 하나의 EC2에서 모든 것이 동작 → 네트워크 레이턴시 최소
- Private Subnet → 외부 접근 차단 (보안)
- GitLab 내장 CI → 별도 CI 도구 설치 불필요
단점:
- SPOF: EC2 1대가 죽으면 Git + CI + Runner 모두 중단
- 리소스 경합: GitLab 서버 (메모리 4GB+)와 Runner가 같은 EC2에서 경쟁
- 스케일 불가: 동시에 여러 파이프라인 실행 시 병목
- 유지보수: GitLab 버전 업데이트, SSL 인증서, 백업 모두 수동
- 비용: t3.large 이상 필요 (~$0.083/hr = $60/월)
8.2 리팩토링 후 CI/CD 아키텍처
확정: GitLab SaaS + Self-hosted Runner on EKS + ArgoCD (CD)
gitlab.com (SaaS)
├── Git 저장소 (소스 코드 호스팅, HA는 GitLab이 보장)
├── .gitlab-ci.yml (파이프라인 오케스트레이션)
│ └── "이 job을 실행해" 명령을 Runner에 전달
│
└─── 등록된 Runner에 job 디스패치
│
▼
┌──── EKS Cluster (Private Subnet) ────────────────────┐
│ │
│ infra 노드풀 │
│ ┌────────────────────┐ │
│ │ GitLab Runner │ ← Helm으로 배포 (replicas:1) │
│ │ (Kubernetes Exec.) │ │
│ └────────┬───────────┘ │
│ │ job마다 ephemeral Pod 생성 │
│ ▼ │
│ ┌────────────────────┐ │
│ │ Build Pod │ Kaniko 이미지 빌드 │
│ │ (ephemeral) │ IRSA로 ECR push │
│ │ 끝나면 자동 삭제 │ S3 cache (Gradle 의존성) │
│ └────────────────────┘ │
│ │ │
│ ▼ ECR push (VPC Endpoint, NAT 불필요) │
│ ┌────────────────────┐ │
│ │ ArgoCD │ Image Updater → Git 커밋 │
│ │ Argo Rollouts │ Dev: auto-sync (Rolling) │
│ │ (infra 노드) │ Prod: manual-sync (Canary) │
│ └────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ cgv-dev │ │ cgv-prod │ │
│ │ (Rolling)│ │ (Canary) │ │
│ └──────────┘ └──────────┘ │
└───────────────────────────────────────────────────────┘
이전 개발계 → 리팩토링 비교:
| 항목 | 이전 개발계 | 리팩토링 후 |
|---|---|---|
| GitLab 서버 | EC2 1대 (SPOF) | SaaS (HA는 GitLab이 보장) |
| Runner | 같은 EC2 (리소스 경합) | EKS Pod (Kubernetes Executor) |
| 빌드 격리 | Docker Socket Mount (호스트 공유) | Kaniko (unprivileged, Pod 격리) |
| ECR 인증 | AWS Access Key (평문 변수) | IRSA (Pod 단위 임시 토큰) |
| 빌드 캐시 | 로컬 디스크 (EC2) | S3 분산 캐시 |
| 스케일링 | 불가 (EC2 1대) | Pod auto-scale (K8s) |
| 비용 | ~$60/월 (t3.large 상시) | $0 (SaaS 무료 + Runner는 빌드 시만) |
| 이중화 | 없음 | Runner: 1 (빌드 지연만), gitlab.com: HA |
8.3 왜 GitLab CI인가 — 정직한 선택 근거
GitHub Actions가 더 간편하고 저렴한 것은 사실이다. 그럼에도 GitLab CI를 선택한 이유는 기능적 우위가 있기 때문이다:
1. Kubernetes Executor — Self-hosted Runner의 성숙도
VPC Private Subnet에서 빌드하려면 Self-hosted Runner가 필요
이 영역에서 GitLab Runner의 Kubernetes Executor(2016~)는 GitHub ARC(2023 GA)보다 성숙하다.
| 기능 | GitLab K8s Executor | GitHub ARC |
|---|---|---|
| GA 시점 | 2016 | 2023 |
| Pod spec 커스터마이징 | 네이티브 (resources, securityContext, serviceAccount) | 제한적 |
| Service Container | 네이티브 (services: 키워드) |
별도 설정 |
| S3 Cache | 네이티브 ([runners.cache.s3]) |
actions/cache (GitHub Storage) |
| Helper Image | 커스터마이징 가능 | 고정 |
2. 이전 개발계 아키텍처의 발전
단일 EC2 GitLab CE(SPOF, 스케일 불가, Docker Socket Mount)의 한계를 경험했고, SaaS + K8s Runner로 전환하여 모든 문제를 해결했다. 면접에서 "이전 문제를 어떻게 해결했는가"에 대한 구체적 답변이 된다.
3. Runner 운영 자체가 DevOps 실무 역량
- Helm Chart로 Runner를 K8s에 배포하고 관리
- IRSA로 ECR 접근 권한을 Pod 단위로 최소화
- Kaniko로 Docker 없이 unprivileged 이미지 빌드
- S3 분산 캐시로 빌드 시간 최적화
- Pod SecurityContext로 빌드 환경 보안 강화
이것들은 GitHub Actions SaaS를 사용하면 경험할 수 없는 영역이다.
4. 단일 파이프라인 파일 (.gitlab-ci.yml)
stages, jobs, rules, environments, cache, artifacts를 한 파일에 정의한다. 단일 앱 프로젝트에서 GitHub Actions의 다중 workflow 파일보다 관리가 간결하다.
인정할 점: GitHub Actions가 OIDC 설정, Marketplace, 포트폴리오 가시성, 비용($0)에서 더 우수하다.
GitLab SaaS 무료 티어는 Shared Runner 400분/월이지만, Self-hosted Runner는 분 제한 없이 무료이므로 비용 차이는 없다.
선택의 핵심은 "Self-hosted Runner를 직접 운영하면서 K8s 네이티브 CI를 경험하겠다"는 DevOps 학습 목표다.
8.4 전체 배포 흐름 (CI 도구 무관)
개발자 → git push
│
↓
┌──────────┐
│ GitLab CI│ (Self-hosted Runner on EKS, K8s Executor)
│ │
│ 1. test │ mvn test (단위 테스트)
│ 2. build │ mvn package (JAR 생성)
│ 3. scan │ SonarQube / Trivy (품질/보안)
│ 4. push │ Kaniko build + push ECR (IRSA)
└──────────┘
│
↓ 이미지가 ECR에 올라감
┌──────────────────┐
│ ArgoCD Image │
│ Updater │ ECR 폴링 → 새 태그 감지
│ │ → values.yaml 업데이트
│ │ → Git 자동 커밋
└──────────────────┘
│
↓ Git 변경 감지
┌──────────────────┐
│ ArgoCD │
│ │ Dev: 자동 Sync (Continuous Deployment)
│ │ Prod: 수동 Sync (Continuous Delivery)
└──────────────────┘
│
↓
┌──────────────────┐
│ Argo Rollouts │
│ │ Dev: Rolling Update (즉시)
│ │ Prod: Canary 10% → 50% → 100%
│ │ + AnalysisTemplate (에러율 자동 확인)
└──────────────────┘
│
↓ 문제 발생 시
자동 롤백 (이전 버전으로 복원)
9. 면접 대비 Q&A
Q. CI/CD가 무엇인지 설명해주세요.
CI는 Continuous Integration으로, 개발자가 코드를 push할 때마다 자동으로 빌드와 테스트를 실행하여 통합 오류를 조기에 발견하는 것입니다. CD는 Continuous Delivery 또는 Deployment로, CI를 통과한 코드를 자동으로 배포 가능한 상태로 만들거나(Delivery), 실제로 프로덕션까지 배포(Deployment)하는 것입니다. CGV 프로젝트에서는 Dev 환경은 Continuous Deployment(자동 배포), Prod 환경은 Continuous Delivery(수동 승인 후 배포)를 적용했습니다.
Q. GitOps란 무엇이고 왜 사용하나요?
GitOps는 Git 저장소를 인프라와 애플리케이션의 "진실의 원천(Source of Truth)"으로 사용하는 운영 방법론입니다. 모든 변경은 Git 커밋으로 이루어지고, 클러스터 내부의 에이전트(ArgoCD)가 Git 상태와 실제 상태를 동기화합니다. 전통적인 Push 방식(CI 서버가 kubectl로 직접 배포)과 달리, GitOps의 Pull 방식에서는 CI 서버가 클러스터 접근 권한을 갖지 않아 보안이 강화됩니다. CGV에서는 ArgoCD가 Git을 3분마다 폴링하여 변경을 감지하고, Drift Detection으로 수동 kubectl 변경도 자동 복원합니다.
Q. Canary 배포와 Blue-Green 배포의 차이는?
Blue-Green은 현재 버전(Blue)과 동일한 새 환경(Green)을 구성한 후 트래픽을 한 번에 전환합니다. 즉시 롤백이 가능하지만 인프라가 2배 필요합니다. Canary는 새 버전에 트래픽을 단계적으로(10% → 50% → 100%) 전환하면서 각 단계에서 메트릭을 확인합니다. CGV에서는 Argo Rollouts의 Canary 전략을 사용하여 Prometheus 에러율이 5% 미만인지 자동 검증하고, 초과 시 자동 롤백합니다. 인프라 오버헤드가 10%에 불과하고 점진적 검증이 가능해서 Canary를 선택했습니다.
Q. ArgoCD를 선택한 이유는?
첫째, 웹 UI가 있어 배포 상태를 시각적으로 확인할 수 있습니다. Flux는 CLI만 지원합니다. 둘째, Argo Rollouts와 같은 프로젝트여서 Canary 배포와의 통합이 자연스럽습니다. 셋째, CNCF Graduated 프로젝트로 프로덕션 검증이 완료되었고 커뮤니티가 가장 큽니다. 넷째, Image Updater로 ECR의 새 이미지를 자동 감지하여 Git 커밋까지 자동화할 수 있습니다.
Q. CI와 CD를 분리한 이유는?
CI(빌드/테스트/이미지 푸시)와 CD(배포)를 같은 도구로 처리하면 CI 도구에 클러스터 접근 권한을 줘야 합니다. 이는 보안 위험이고, CI 도구 장애 시 배포도 불가능해집니다. CGV에서는 CI의 최종 산출물이 ECR의 Docker 이미지이고, CD는 클러스터 내부의 ArgoCD가 담당합니다. 이렇게 분리하면 CI 도구를 GitLab에서 GitHub Actions로 교체해도 CD에 전혀 영향이 없습니다. "ECR에 이미지만 올라가면 된다"가 CI와 CD의 인터페이스입니다.
Q. Docker-in-Docker(DinD)가 무엇인가요?
CI 환경에서 Docker 이미지를 빌드하려면 Docker Engine이 필요합니다. DinD는 컨테이너 안에서 별도의 Docker Engine을 실행하는 방식입니다. 격리성이 좋지만 privileged 모드가 필요하고 이중 가상화로 느립니다. 대안으로 Docker Socket Mount가 있는데, 호스트의 Docker Engine을 직접 사용하므로 빠르지만 호스트 접근 권한을 공유하는 보안 위험이 있습니다. 최근에는 Kaniko(Docker 없이 이미지 빌드)나 Buildah 같은 도구도 사용됩니다.
Q. 이미지 태그 전략은 어떻게 하나요?
latest태그는 절대 사용하지 않습니다. 어떤 코드가 배포되었는지 추적할 수 없고, 롤백도 불가능하기 때문입니다. CGV에서는{version}-{date}-{time}형식(예: 1.0.0-250601-143000)을 사용합니다. 시맨틱 버전으로 의미를 전달하고, 타임스탬프로 빌드 시점을 추적합니다. Git SHA(커밋 해시)를 태그로 쓰는 것도 좋은 방법이며, 정확한 코드 추적이 가능합니다.
Q. 배포 실패 시 롤백은 어떻게 하나요?
Argo Rollouts의 AnalysisTemplate이 Prometheus에서 에러율을 30초 간격으로 조회합니다. 에러율 5% 이상이 3회 연속(90초) 감지되면 자동으로 이전 버전으로 롤백합니다. 수동 롤백은 ArgoCD UI에서 "Rollback" 버튼을 누르거나, Git에서 이전 이미지 태그로 커밋하면 ArgoCD가 자동 Sync합니다. GitOps에서는 Git 히스토리가 곧 배포 히스토리이므로
git revert로 롤백이 가능합니다.
Q. 왜 GitHub Actions 대신 GitLab CI를 선택했나요?
VPC Private Subnet에서 빌드하려면 Self-hosted Runner가 필요합니다. GitLab Runner의 Kubernetes Executor가 GitHub ARC보다 성숙하고(2016 vs 2023 GA), Pod spec 커스터마이징, S3 분산 캐시, Service Container를 네이티브로 지원합니다. 또한 이전 개발계에서 단일 EC2 GitLab CE의 SPOF 문제를 경험했고, SaaS + K8s Runner로 전환하면서 그 한계를 해결하는 과정이 DevOps 역량 성장을 보여줍니다. GitLab SaaS의 Self-hosted Runner는 분 제한 없이 무료이므로 비용 차이도 없습니다.
Q. GitLab Runner는 왜 이중화하지 않았나요?
Runner는 CI 빌드 실행기이지 서비스 인프라가 아닙니다. Runner가 죽으면 gitlab.com이 job을 큐에 보관하고, Kubernetes가 Pod를 30초 내에 재시작하면 자동 재개됩니다. 사용자 트래픽에 영향이 전혀 없습니다. ArgoCD나 Prometheus처럼 장애 시 배포/모니터링이 중단되는 컴포넌트와 달리, Runner 장애는 빌드 지연만 발생합니다. 모든 컴포넌트를 동일 수준으로 이중화하는 것은 비용 낭비이며, SLA에 따라 이중화 수준을 달리 하는 것이 운영 효율적입니다.
'Cloudwave > cgv-project' 카테고리의 다른 글
| 테라폼 (1) | 2026.02.18 |
|---|---|
| 메시징 시스템 (0) | 2026.02.16 |
| WebSocket (0) | 2026.02.12 |
| 3. CI/CD 파이프라인 구축과 자동화 (0) | 2026.02.02 |
| 2. CGV 대기열 시스템 (0) | 2026.02.01 |