MyAI(마이아이)개발 · 제품 · 운영 학습 노트

CI/CD Pipeline: Commit에서 Production까지

CI/CD는 "자동으로 배포하는 Script"가 아니라 변경이 Production에 도달하기까지의 품질 관문을 코드로 만든 것이다.

용어 구분

용어 의미
CI (Continuous Integration) 변경을 자주 합치고, 합칠 때마다 Build와 Test를 자동 실행
Continuous Delivery 언제든 Production에 내보낼 수 있는 상태를 유지. 최종 배포는 사람이 승인할 수 있다
Continuous Deployment 관문을 통과한 변경이 사람 개입 없이 Production까지 배포
Artifact Build 결과물. Container Image, 앱 번들, 정적 파일 묶음 등
Environment Dev, Staging, Production처럼 Artifact가 실행되는 장소

표준 흐름

flowchart LR
    P[Push / PR] --> B[Build]
    B --> T[Test / Lint]
    T --> S[Security Scan]
    S --> A[Artifact + Attestation]
    A --> ST[Deploy Staging]
    ST --> V{검증 통과?}
    V -->|예| PR[Deploy Production]
    V -->|아니오| X[중단 / 알림]
    PR --> M[관측 / Rollback 준비]

핵심 원칙은 한 번 Build한 Artifact를 모든 Environment에 승격하는 것이다. Environment마다 다시 Build하면 Staging에서 검증한 것과 Production에 나간 것이 달라질 수 있다.

설정은 코드 밖에

Twelve-Factor App은 배포마다 달라지는 값(DB 주소, 외부 서비스 자격 증명, Hostname 등)을 코드가 아닌 Environment에 두라고 권한다. 같은 Artifact가 Environment만 바꿔 여러 곳에서 돌 수 있어야 한다. 최근에는 Secret을 Volume으로 Mount하는 방식도 함께 논의된다.

Environment 설계

항목 Preview (PR마다) Staging Production
목적 변경을 눈으로 확인하고 Review 배포 절차 · Migration · 외부 연동 검증 실제 사용자
Data Seed 또는 가짜 Data 합성 Data 또는 익명화한 일부 실제 Data
누가 배포 PR을 열면 자동 main Merge 시 자동 승인 후 CI만
보호 규칙 짧은 수명, PR 종료 시 삭제, 결제 · 메일 · Push는 Sandbox 사내 접근만 허용, Production Secret 없음 필수 Reviewer, 대기 시간, 배포 가능 Branch 제한

Environment별 설정과 Secret

Production 같은 Data의 함정

작은 팀이 Staging을 건너뛰어도 되는 조건

다음이 모두 참이면 Preview → Production으로 바로 가도 된다.

  1. PR마다 Preview가 Production과 같은 Artifact · 설정 구조로 뜬다.
  2. DB Migration이 Expand → Contract로 하위 호환된다(04 문서).
  3. Rollback을 수 분 안에 할 수 있고 실제로 해 봤다.
  4. 위험한 기능은 Feature Flag로 노출을 제어한다.

하나라도 아니면 Staging을 둔다. 특히 되돌릴 수 없는 Migration, 결제 · 인증 · 외부 연동 변경이 잦다면 필수다.

GitHub Actions 예시

name: deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@<commit-sha>
      - run: npm ci && npm test
      - run: npm run build
      - run: ./scripts/deploy.sh

장기 Secret 대신 OIDC

GitHub Actions의 OpenID Connect를 쓰면 Workflow가 실행될 때마다 Cloud 제공사로부터 짧은 수명의 Token을 받는다. AWS, Azure, Google Cloud, HashiCorp Vault 등이 지원한다.

나쁜 예: Cloud Access Key를 Repository Secret에 저장하고 몇 년째 교체하지 않는다.
좋은 예: OIDC Trust를 "이 Repository의 main Branch, production Environment"로 좁히고
        Job이 끝나면 만료되는 Token만 사용한다.

AWS 예시. Workflow 쪽에는 id-token: write와 Role만 있고 Access Key는 없다.

permissions:
  id-token: write
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: aws-actions/configure-aws-credentials@<commit-sha>
        with:
          role-to-assume: arn:aws:iam::123456789012:role/deploy-production
          aws-region: ap-northeast-2
      - run: aws sts get-caller-identity

AWS IAM Role의 Trust Policy는 어느 Workflow가 이 Role을 받을 수 있는지를 sub Claim으로 좁힌다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:ORG/REPO:environment:production"
        }
      }
    }
  ]
}

사고 사례: CI도 공격 대상이다

GitOps: Pull 방식 배포

OpenGitOps 원칙은 네 가지다.

  1. Declarative: 원하는 상태를 선언적으로 표현한다.
  2. Versioned and Immutable: 원하는 상태를 버전과 전체 이력이 남는 곳에 저장한다.
  3. Pulled Automatically: Agent가 원하는 상태를 자동으로 가져온다.
  4. Continuously Reconciled: Agent가 실제 상태를 계속 관찰하고 원하는 상태로 맞춘다.

CI가 Cluster에 직접 Push하는 대신, Cluster 안의 Agent가 Git을 보고 따라가게 하면 Drift(누군가 손으로 바꾼 설정)가 자동으로 드러난다.

Infra도 코드로

Console에서 손으로 만든 Infra(Click-ops)는 누가 무엇을 왜 바꿨는지 남지 않고, 같은 Environment를 다시 만들 수도 없다. IaC(Infrastructure as Code) 는 Infra를 Application 코드처럼 Review하고 Version을 남기고 재현한다.

IaC로 관리 Click-ops로 남겨도 되는 것
Network(VPC, Subnet), DNS Record, DB · Queue · Bucket, IAM Role과 Policy, Environment별 Service 설정, 경보와 Dashboard 최초 1회 Bootstrap(State 저장소, 관리 계정), 결제와 조직 설정, 장애 중 긴급 조치(끝나면 코드에 반영), 탐색용 개인 Sandbox

도구 한 줄 비교

Remote State와 Locking

State는 코드와 실제 Resource의 대응표다. 로컬 PC에 두면 팀원끼리 덮어쓰고, Resource 속성에 들어 있는 Secret이 평문으로 남을 수 있다. 원격 Backend에 암호화해 두고 접근을 제한한다. 두 사람이 동시에 apply하면 State가 깨지므로 Lock도 건다. Terraform의 S3 Backend는 1.10에서 S3 자체 Lock(use_lockfile)을 도입했고, 1.11에서 정식 기능이 되면서 DynamoDB 기반 Lock은 Deprecated되었다.

terraform {
  backend "s3" {
    bucket       = "acme-tfstate"
    key          = "prod/network.tfstate"
    region       = "ap-northeast-2"
    encrypt      = true
    use_lockfile = true
  }
}

resource "aws_s3_bucket" "assets" {
  bucket = "acme-prod-assets"
}

plan을 PR Check로

flowchart LR
    PR[PR 생성] --> PL[plan 결과를 PR에 게시]
    PL --> RV{Review 승인?}
    RV -->|예| AP[Merge 후 CI가 apply]
    RV -->|아니오| FX[코드 수정]
    FX --> PL
    AP --> DR[매일 Drift 검사]

PR마다 plan을 돌려 "무엇이 생기고, 바뀌고, 지워지는지"를 Review에 붙인다. destroy가 보이면 멈춘다. apply는 Merge 뒤 CI만 하고, CI는 OIDC로 받은 Role을 쓴다(위 절).

      - run: terraform init -input=false
      - run: terraform plan -input=false -out=tfplan

Drift 검사

main에는 적용되지 않은 코드가 없어야 하므로, 매일 main에서 terraform plan -detailed-exitcode를 돌린다. Exit Code 0은 변화 없음, 1은 오류, 2는 차이 있음이다. 2가 나오면 누군가 Console에서 바꾼 것이니 코드에 반영하거나 되돌린다. 사람에게는 Production Console 읽기 권한만 주면 Drift 자체가 줄어든다.

성과 측정: DORA 지표

지표 의미
Deployment Frequency Production에 성공적으로 배포하는 빈도
Change Lead Time Commit부터 Production 배포 성공까지 걸리는 시간
Failed Deployment Recovery Time 배포로 생긴 장애에서 회복하는 데 걸리는 시간
Change Fail Rate 배포 직후 Rollback이나 Hotfix 같은 즉각 개입이 필요한 비율
Deployment Rework Rate 계획되지 않은(버그 수정용) 배포의 비율

DORA는 Change Fail Rate와 Deployment Rework Rate를 Instability(불안정성)를 보는 두 지표로 쓰며, Deployment Rework Rate는 2024년에 다섯 번째 지표로 추가되었다. 지표는 팀을 비교하는 순위표가 아니라 개선 방향을 찾는 신호로 쓴다.

단계적 검증 설계

PR Check     빠른 Lint, Unit Test (수 분 이내)
Main Merge   Integration Test, Image Build, Staging 배포
Release      Smoke Test, 승인, Production 배포
Nightly      무거운 E2E, 성능, 의존성 Scan

모든 검사를 PR마다 돌리면 느려지고, 아무것도 안 돌리면 위험하다. 빠른 것은 앞에, 무거운 것은 뒤에 둔다.

참고 자료