이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • CI와 CD를 분리해야 하는 이유는 무엇인가?
  • Spring 백엔드 pipeline에서 검증 단계와 배포 단계는 어떤 책임을 가지는가?
  • CI는 성공했는데 운영 장애가 난 경우 어떤 증거를 확인해야 하는가?

개요

CI와 CD는 모두 pipeline에 들어가지만 책임이 다르다.

CI는 코드 변경이 합쳐져도 되는지 검증하는 단계다.

CD는 검증된 산출물을 특정 환경에 배포하고 배포 결과를 확인하는 단계다.

둘을 분리하지 않으면 테스트 실패, 빌드 실패, 권한 실패, 배포 실패, 배포 후 장애가 모두 같은 “pipeline 실패”로 뭉개진다.

CI의 책임

CI는 변경된 코드가 기본 품질 기준을 만족하는지 확인한다.

Spring 백엔드에서는 보통 compile, unit test, slice test, integration test, static analysis, package 생성을 포함한다.

name: ci
on:
  pull_request:
  push:
    branches: [main]
 
jobs:
  verify:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'
      - run: ./gradlew clean test bootJar

CI는 빠르게 실패해야 한다.

개발자가 PR에서 고칠 수 있는 문제를 운영 배포 단계까지 보내지 않는 것이 목적이다.

CD의 책임

CD는 이미 검증된 artifact를 환경에 반영한다.

staging, production, canary 같은 환경별 승인과 권한이 들어간다.

name: deploy
on:
  workflow_dispatch:
    inputs:
      image_tag:
        required: true
 
jobs:
  deploy-prod:
    environment: production
    permissions:
      contents: read
      id-token: write
    steps:
      - run: ./scripts/deploy.sh "$IMAGE_TAG"
      - run: ./scripts/smoke-test.sh https://api.example.com

CD는 느려도 된다.

중요한 것은 승인, 배포 이력, smoke test, rollback 가능성이 남는 것이다.

분리 기준

CI job은 운영 Secret을 몰라도 돼야 한다.

CI가 운영 DB password나 production deploy token을 갖고 있다면 PR 코드가 민감 권한에 접근할 위험이 커진다.

CD job은 protected branch, protected environment, reviewer approval 같은 통제 아래 실행한다.

배포 실패는 코드 검증 실패와 다른 방식으로 다뤄야 한다.

Spring 관점의 검증

Spring 백엔드 CI는 단순 compile보다 더 많은 계약을 확인한다.

@SpringBootTest가 모든 변경마다 무겁다면 slice test와 integration test를 나눌 수 있다.

DB migration이 있다면 test container나 ephemeral DB로 migration 적용 가능성을 확인한다.

Actuator health와 smoke test는 CD에서 배포 후 검증으로 사용한다.

운영 체크리스트

  • PR CI와 production CD가 분리되어 있는가?
  • CI job에 production Secret이 주입되지 않는가?
  • CD job은 protected branch 또는 protected environment에서만 실행되는가?
  • artifact를 다시 빌드하지 않고 검증된 산출물을 배포하는가?
  • 배포 후 smoke test가 pipeline 결과에 반영되는가?
  • CD 실패와 CI 실패의 알림 대상이 구분되어 있는가?
  • rollback 또는 redeploy 절차가 CD 문서에 연결되어 있는가?

장애 신호

  • PR에서 실행되는 job이 production Secret에 접근할 수 있다.
  • CI는 성공했지만 CD에서 image pull, registry auth, deploy permission이 실패한다.
  • 배포 job 성공 직후 핵심 API smoke test가 실행되지 않는다.
  • 운영 장애가 났는데 어떤 pipeline run이 배포했는지 모른다.
  • main branch는 초록색인데 production에 반영된 artifact가 다르다.
  • 실패한 CD job을 rerun하면서 같은 artifact인지 확인하지 않는다.

안전한 완화 조치

CI와 CD가 섞여 있다면 먼저 권한부터 분리한다.

CI에는 코드 읽기와 test에 필요한 권한만 둔다.

CD에는 배포에 필요한 권한을 두되 environment approval과 audit log를 붙인다.

운영 장애 중에는 새 CI run으로 artifact를 다시 만들기보다 현재 배포된 artifact와 직전 정상 artifact를 확인한다.

배포 후 검증이 없어서 장애가 늦게 발견됐다면 smoke test를 CD 결과에 포함한다.

인프라 담당자와 공유할 자료

문제: CI 성공 후 production 배포 실패
CI run: 1821, commit abc1234, bootJar 성공
CD run: 431, image tag api:abc1234, registry auth 실패
권한: deploy job id-token write 있음, package read 누락
영향: production 변경 없음
필요 조치: deploy role package read 권한 추가 후 같은 image tag로 재실행

공유 자료에는 실패 단계가 CI인지 CD인지, artifact가 새로 만들어졌는지 같은지, 운영 반영 여부를 분명히 적는다.

실전 팁

  • CI는 PR에서 재현 가능한 피드백을 주는 데 집중한다.
  • CD는 승인, 환경, 권한, 배포 후 검증에 집중한다.
  • PR pipeline에 production Secret을 넣지 않는다.
  • deploy job은 workflow_dispatch나 release tag 기반으로 통제할 수 있다.
  • 배포된 artifact와 검증된 artifact가 같다는 증거를 남긴다.

위험 신호!

  • test job과 deploy job이 같은 Secret 묶음을 공유한다.
  • CI에서 새 artifact를 만들고 CD에서 또 다른 artifact를 만든다.
  • CD job 성공만으로 배포 성공이라고 판단한다.
  • production 배포가 아무 branch에서나 실행된다.
  • 장애 중 최신 main을 다시 빌드해 덮어쓴다.

확인 질문

확인 질문

  • CI와 CD를 분리해야 하는 가장 큰 이유는 무엇인가?
    • 코드 검증과 운영 변경은 권한, 승인, 실패 대응이 다르기 때문에 같은 job으로 묶으면 원인과 책임 경계가 흐려진다.
  • CD에서 smoke test가 필요한 이유는 무엇인가?
    • deploy 명령 성공은 프로세스 반영만 뜻할 수 있으므로 실제 endpoint가 사용자 요청을 처리하는지 확인해야 한다.
  • CI job에 production Secret을 넣으면 왜 위험한가?
    • PR 코드나 test script가 민감 값에 접근할 수 있고, 로그나 artifact를 통해 노출될 가능성이 생기기 때문이다.

참고 문서