본문

Build-Once, Deploy-Many

"개발자가 코드를 올리면 자동으로 테스트되고, 보안 검사를 거쳐, 운영 서버까지 배포된다." 이 과정을 사람 손 없이 자동화하는 것이 CI/CD입니다. 오늘은 이 자동화 파이프라인을 어떻게 하면 안전하고 효율적으로 만들 수 있는지, 모범사례를 공유합니다.


CI/CD가 뭔가요?

CI/CD를 처음 접하면 용어부터 낯섭니다. 풀어서 설명하면 이렇습니다.

  • CI (Continuous Integration, 지속적 통합): 개발자가 코드를 작성하면 자동으로 빌드하고 테스트하는 과정
  • CD (Continuous Delivery/Deployment, 지속적 배포): 테스트를 통과한 코드를 자동으로 서버에 배포하는 과정

사람이 일일이 "이 코드 문제없어? 배포해도 돼?" 확인하지 않아도, 기준을 통과한 코드만 자동으로 앞으로 나아갑니다 🚕🚙~


핵심 전략 1 — "한 번만 빌드한다" (Build-Once, Deploy-Many)

 왜 이게 중요한가?

처음 CI/CD를 구성하면 흔히 이런 방식을 사용합니다.

  • 개발 서버 배포 → 개발 환경에서 빌드
  • 스테이징 서버 배포 → 스테이징 환경에서 다시 빌드
  • 운영 서버 배포 → 운영 환경에서 또 빌드

얼핏 보면 자연스럽지만, 여기에 큰 문제가 숨어있습니다.

"개발에서 잘 됐는데 운영에서 왜 안 되지?"

 

환경마다 다시 빌드하면 같은 코드라도 결과물이 달라질 수 있습니다.

빌드 시점의 라이브러리 버전, 환경 변수, 의존성 등이 미묘하게 다를 수 있기 때문입니다.

 

✅ 해결책 — 딱 한 번만 빌드하고, 그 결과물을 그대로 이동

개발 환경에서 검증된 완성품 이미지를 스테이징, 운영 환경으로 그대로 올려보냅니다. 이 방식을 이미지 프로모션(Image Promotion) 이라고 부릅니다.

마치 공장에서 완제품을 만들어 창고에 넣어두고, 필요한 곳(국내 매장, 해외 매장)으로 동일한 제품을 배송하는 것과 같아요.

 

 실제 파이프라인에서는 이렇게 동작합니다

# develop 브랜치에서만 빌드
build:
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
  script:
    - docker build -t my-app:$COMMIT_ID .   # 빌드
    - docker push my-app:$COMMIT_ID         # 저장소에 보관
    - docker push my-app:latest             # latest 태그도 함께 갱신

# stage/main에서는 빌드 없이 배포만
deploy:
  rules:
    - if: '$CI_COMMIT_BRANCH == "stage"'
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - docker pull my-app:latest    # 개발에서 만든 이미지 가져오기
    - docker push target/my-app    # 해당 환경 저장소로 복사
    - 서버 재배포 실행

핵심 전략 2 — 배포 전 보안 검사 (이미지 취약점 스캔)

 보안 검사를 자동화해야 하는 이유

애플리케이션은 수십~수백 개의 외부 라이브러리(패키지)를 가져다 씁니다. 내가 만든 코드가 완벽해도, 가져다 쓴 라이브러리에 보안 취약점이 있으면 전체가 위험해집니다.

금융권처럼 보안이 중요한 환경에서는 이 검사를 사람이 수동으로 할 수 없습니다. 배포할 때마다 자동으로 검사하는 체계가 필요합니다.

 

 AWS ECR Enhanced Scanning

AWS의 컨테이너 이미지 저장소(ECR)는 Amazon Inspector와 연동하여 이미지를 자동으로 스캔하는 기능을 제공합니다. 별도의 보안 도구를 설치하지 않아도, 이미지를 저장소에 올리는 순간 자동으로 취약점 분석이 시작됩니다.

 

파이프라인에서는 스캔이 끝날 때까지 기다렸다가, CRITICAL 취약점이 발견되면 배포를 자동으로 막습니다.

코드를 올리고 커피 한 잔 마시는 사이, 보안팀이 수동으로 며칠씩 걸리던 검사가 자동으로 완료됩니다~ ☕️

scan:
  script:
    # 스캔 완료까지 폴링 (최대 10분)
    - while 스캔_진행중; do sleep 20; done

    # 결과 조회
    - CRITICAL=$(스캔_결과에서_CRITICAL_수_추출)

    # CRITICAL 있으면 파이프라인 중단
    - if [ "$CRITICAL" -gt 0 ]; then exit 1; fi

핵심 전략 3 — 배포 실패 시 자동 롤백

아무리 잘 만든 파이프라인도 배포가 실패하는 순간은 옵니다. 중요한 건 실패했을 때 얼마나 빠르게 이전 상태로 돌아오느냐입니다.

 

 자동 롤백 로직

배포 시작
    ↓
새 버전으로 서비스 교체
    ↓
10분 안에 정상화되는지 대기
    ├── ✅ 정상 → 배포 완료
    └── ❌ 실패 → 이전 버전으로 자동 롤백
deploy:
  script:
    # 배포 후 안정화 대기 (최대 10분)
    - if ! timeout 600 aws ecs wait services-stable; then
        # 실패 시 이전 버전으로 자동 롤백
        aws ecs update-service --task-definition 이전_버전
        exit 1
      fi

서비스가 비정상 상태로 10분이 지나면, 운영자가 새벽에 깨어나 손으로 롤백하지 않아도 파이프라인이 알아서 이전 버전으로 되돌립니다.


핵심 전략 4 — 환경별 권한 분리 (Assume Role)

 왜 계정을 분리하는가

일반적으로 개발(Dev), 스테이징(Stage), 운영(Production) 환경은 AWS 계정 자체를 분리합니다. 이유는 단순합니다.

"개발자의 실수가 운영 서비스에 영향을 주면 안 된다."

 

문제는 파이프라인이 각 환경에 배포하려면 각 계정의 권한이 필요하다는 점입니다.

 

 해결책 — Assume Role (권한 임시 위임)

장기 자격증명(ID/PW처럼 영구적인 키)을 파이프라인에 심어두면 탈취 위험이 있습니다. 대신 필요한 순간에만, 제한된 권한을 임시로 빌려쓰는 방식을 사용합니다.

파이프라인 서버 (EC2 Instance Profile 보유)
        ↓
"개발 계정 배포 역할 잠깐 빌려줘" (Assume Role 요청)
        ↓
임시 자격증명 발급 (1시간짜리)
        ↓
개발 계정에 배포 수행
        ↓
자격증명 만료 → 자동 폐기

만료 시간이 지나면 자동으로 사라지기 때문에, 설령 탈취되더라도 피해를 최소화할 수 있습니다.


전체 파이프라인 흐름 정리

지금까지 소개한 전략을 하나로 묶으면 이런 파이프라인이 완성됩니다.


마치며

CI/CD 파이프라인은 한 번 잘 만들어두면 개발팀 전체의 속도와 안정성을 동시에 높여주는 인프라입니다. 처음에는 복잡하게 느껴지지만, 결국 핵심은 간단합니다.

  • 한 번만 빌드하고 → 동일한 결과물을 모든 환경에 배포
  • 자동으로 검사하고 → 보안 취약점은 사람 손 없이 걸러내기
  • 실패하면 되돌리고 → 문제 생겼을 때 빠르게 복구
  • 권한은 최소화하고 → 필요한 순간에만, 필요한 만큼만

이 네 가지 원칙만 잘 지켜도 금융권 수준의 안정적인 배포 파이프라인을 구성할 수 있습니다 🙂

공유

댓글

Cloud & AI Engineering | 임승한

design by tokiidesu. powerd by AXZ.