☁️ 3단계 : CD 파이프라인 구축 - 100-hours-a-week/7-team-ddb-wiki GitHub Wiki
CI 단계에서 빌드와 테스트는 자동화되었지만, 스테이징에서 운영 환경으로의 코드 전달 과정은 여전히 수동 스크립트와 사람 손에 의존하고 있다.
이번 단계에서는 Continuous Delivery(CD) 모델을 도입하여, 스테이징 환경까지는 완전 자동 배포가 이루어지고, 운영 환경은 승인 버튼 한 번으로 배포가 가능하도록 CD 파이프라인을 설계한다. 이를 통해 배포 주기의 단축, 무중단 업데이트, 신속한 롤백을 동시에 달성하고자 한다.
항목 | 설명 |
---|---|
수작업 배포의 한계 | - 빅뱅 방식으로 한 번에 배포하다 보니 개발 완료 이후 실제 릴리스까지 시간이 과도하게 소요되고, 운영 서버 접속 및 스크립트 실행 과정에서 인적 오류로 인한 다운타임이나 장애 발생 위험이 큼. - 현재는 프론트엔드(FE), 백엔드(BE), AI 서버의 세 가지 컴포넌트를 각각 수동으로 배포해야 하므로, 서비스 단위의 배포가 매우 비효율적임. |
빠른 릴리스 주기의 필요성 | - 경쟁 서비스를 따라잡기 위해 기능 출시 속도를 높여야 하나, 수작업 중심의 배포 절차로 인해 운영 반영이 지연되면서 사용자 피드백 루프가 느려지고 있음. - Jenkins 기반의 CI는 구축되어 있지만, 빌드 아티팩트를 각 VM에 수동으로 배포하는 방식으로는 짧은 배포 주기를 유지하기 어려움. |
운영 안정성과 민첩성 확보 | - Jenkins와 SSH, 자동 배포 스크립트를 결합한 CD 파이프라인을 구성하면, 단일 버튼 클릭이나 PR 머지를 통해 운영 환경까지 자동 반영이 가능해져 릴리스 속도를 크게 높일 수 있음. - 스테이징 환경에서의 자동 테스트와 검증을 선행한 뒤 운영 승인 절차를 두면, "문제 없는 코드만 운영에 간다"는 확신을 바탕으로 품질을 높이고 장애를 예방할 수 있음. |
즉각적인 롤백의 필요성 | - 현재는 롤백 절차가 정형화되어 있지 않아 장애 발생 시 복구에 시간이 오래 걸리며, 이로 인해 서비스 전체에 영향이 미칠 수 있음. - 배포 실패 감지 및 자동 롤백 전략을 CD 파이프라인에 포함함으로써, 장애 전파를 최소화하고 MTTR(Mean Time To Recovery)을 단축할 수 있음. |
-
목표
CI 산출물을 개발환경에 자동 배포 → 자동 검증 → “Deploy to Prod” 승인 → 운영 반영 까지 전체 흐름을 10 분 이내에 완료
-
배포 전략
Continuous Delivery + Rolling Update (MIG 인스턴스를 1 대씩 순차 교체)
-
적용 범위
- 개발 환경: 100 % 자동화 배포 및 검증
- 운영 환경: Discord 승인 (✅ 버튼) 후 Jenkins 파이프라인으로 자동 배포
-
Jenkins
Git 태그 생성 또는 PR 병합이 발생하면 파이프라인이 자동으로 시작되어 스테이징 배포 → 검증 → 운영 승인 요청까지 전 과정을 오케스트레이션한다. 승인 후에는 운영 MIG에 롤링 업데이트를 진행하고, 배포 실패 시 자동으로 롤백이 수행된다.
-
MIG (Managed Instance Group)
백엔드와 프론트엔드 서버를 각각 MIG로 구성하여 롤링 업데이트로 한 대씩 교체해 무중단 배포를 수행한다. 신규 인스턴스가 헬스체크를 통과하지 못할 경우 자동으로 이전 버전으로 복귀되어 서비스 가용성을 유지한다.
-
ALB (Application Load Balancer)
헬스체크를 통과한 인스턴스에만 트래픽을 분산시켜 배포 중에도 사용자 체감 장애를 방지한다. 또한, TLS 오프로드·경로 기반 라우팅 등 L7 계층의 기능을 통해 보안성과 유연성을 동시에 확보한다.
-
Discord
Jenkins가 전송하는 스테이징 검증 결과에 대해 운영 배포 승인(✅)/거절(❌) 버튼을 클릭함으로써 파이프라인 흐름을 제어할 수 있다. 배포 성공·실패·롤백 등 주요 이벤트가 실시간으로 팀에 공유되어 빠른 의사결정과 대응이 가능하다.
단계 | 자동/수동 | 작업 내용 | 주체 | 성공 기준 |
---|---|---|---|---|
1 | 자동 | Jenkins가 Git Tag 감지 → Bucket에 아티팩트 업로드 | Jenkins | 업로드 완료 |
2 | 자동 | 스테이징 MIG 롤링 업데이트 | Jenkins + MIG | 새 인스턴스 전부 Healthy |
3 | 자동 | 통합 테스트 / Smoke Test 실행 | Jenkins | 모든 테스트 통과 |
4 | 수동 | 운영 MIG 롤링 업데이트 → ALB 헬스체크 | Jenkins + MIG | 새 인스턴스 Healthy |
5 | 자동 | 실패 감지 시 이전 버전으로 자동 롤백 | Jenkins + MIG | 복구 완료 |
BE
/*------------------------------------------------------------------
Jenkins Declarative Pipeline
- Gradle로 JAR 빌드 → GCS 업로드
- Discord 수동 승인
- GCP MIG 롤링 배포 + 실패 시 이전 템플릿 자동 롤백
------------------------------------------------------------------*/
pipeline {
/*--------------- 기본 설정 ---------------*/
agent any // 어떤 에이전트(노드)에서도 실행
/*--------------- 파라미터 ---------------*/
parameters {
// Discord 버튼 클릭 후 재실행 때 true 로 변경
booleanParam(name: 'APPROVED', defaultValue: false,
description: 'Discord ✅ 승인 후 true 로 재실행')
}
/*--------------- 전역 환경변수 ---------------*/
environment {
/* GCP 관련 */
PROJECT_ID = 'my-gcp-project' // GCP 프로젝트 ID
REGION = 'asia-northeast3' // 리전
MIG_NAME = 'my-prod-mig' // Managed Instance Group 이름
BUCKET = 'gs://ddb-be-artifacts' // 빌드 산출물 저장 GCS 버킷
/* 아티팩트 경로 설정 */
BUILD_TAG = "${env.BUILD_NUMBER}" // 빌드 번호
ARTIFACT_NAME = "ddb-be-${env.BUILD_TAG}.jar" // 업로드할 파일명
ARTIFACT_PATH = "${env.BUCKET}/${env.ARTIFACT_NAME}" // GCS 최종 경로
}
/*--------------- 단계 정의 ---------------*/
stages {
/* 1) Git 체크아웃 */
stage('Git Checkout') {
steps {
// main 브랜치의 소스를 clone
git branch: 'main',
url: 'https://github.com/100-hours-a-week/7-team-ddb-be.git'
}
}
/* 2) Gradle 빌드 */
stage('Build (Gradle)') {
steps {
sh '''
chmod +x gradlew # gradlew 실행권한 부여
./gradlew clean bootJar -x test # 테스트 제외하고 JAR 빌드
'''
}
}
/* 3) (선택) 단위 테스트 단계가 필요하면 추가 */
/* 4) 빌드 산출물 GCS 업로드 */
stage('Upload Artifact → GCS') {
steps {
// GCP 서비스 계정 JSON을 Jenkins Credentials로부터 읽어옴
withCredentials([file(credentialsId: 'GCP-SA-JSON',
variable: 'GCLOUD_KEY')]) {
sh '''
# gcloud 인증
gcloud auth activate-service-account --key-file=$GCLOUD_KEY
# 빌드된 JAR을 지정한 GCS 버킷에 업로드
gsutil cp build/libs/*.jar $ARTIFACT_PATH
echo "📦 Uploaded → $ARTIFACT_PATH"
'''
}
}
}
/* 5) Discord 수동 승인 요청 */
stage('Ask Approval (Discord)') {
steps {
// Discord Notifier 플러그인 호출 (buttons 옵션 지원)
discordSend message: "새 빌드 #${BUILD_NUMBER} 배포 승인?",
buttons: '✅,❌',
webhookURL: credentials('Discord-Webhook')
}
}
/* 6) MIG 롤링 배포 + 자동 롤백 */
stage('Deploy & Verify (MIG Rolling)') {
when { expression { params.APPROVED == true } } // APPROVED==true 일 때만 실행
steps {
withCredentials([file(credentialsId: 'GCP-SA-JSON',
variable: 'GCLOUD_KEY')]) {
sh '''
set -e # 스크립트 오류 시 즉시 실패 처리
# 6‑1. gcloud 인증
gcloud auth activate-service-account --key-file=$GCLOUD_KEY
# 6‑2. 현재 MIG가 사용하는 템플릿(롤백 대비) 조회
OLD_TMPL=$(gcloud compute instance-groups managed describe $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--format="value(versions[0].instanceTemplate.basename)")
echo "현재 템플릿 → $OLD_TMPL"
# 6‑3. 새 startup‑script 작성 (GCS 아티팩트 다운로드 + 서비스 재시작)
STARTUP_FILE="/tmp/startup-${BUILD_TAG}.sh"
cat > $STARTUP_FILE <<'EOF'
#!/bin/bash
set -e
JAR_URL="${ARTIFACT_PATH}" # Jenkins env var가 heredoc 내부로 치환됨
sudo systemctl stop ddb-be || true # 기존 서비스 중지 (없으면 무시)
mkdir -p /opt/ddb # 배포 디렉터리
/usr/bin/gsutil cp $JAR_URL /opt/ddb/ddb-be.jar
sudo systemctl restart ddb-be # systemd 유닛 재시작
EOF
chmod +x $STARTUP_FILE
# 6‑4. 새 인스턴스 템플릿 생성 (startup‑script 포함)
NEW_TMPL="tmpl-${MIG_NAME}-${BUILD_TAG}"
gcloud compute instance-templates create $NEW_TMPL \
--project=$PROJECT_ID \
--region=$REGION \
--machine-type=e2-small \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=10GB \
--metadata-from-file startup-script=$STARTUP_FILE
# 6‑5. MIG 롤링 업데이트 시작
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$NEW_TMPL \
--type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
echo "▶️ Rolling update with $NEW_TMPL 시작"
# 6‑6. 최대 15분 동안 새 버전 안정화 대기
if ! gcloud compute instance-groups managed wait-until $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--stable --timeout=900 ; then
echo "❌ 새 버전 불안정 → 롤백 실행"
# 6‑7. 롤백: 이전 템플릿으로 다시 롤링 업데이트
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$OLD_TMPL \
--type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
exit 1 # 빌드 실패 처리 → post { failure } 트리거
fi
echo "✅ 배포 완료 · 안정화 확인"
'''
}
}
}
}
/*--------------- 후처리 (Discord 알림) ---------------*/
post {
// 빌드 성공 시
success {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "$DISCORD"
}
}
// 빌드 실패 시 (롤백 포함)
failure {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "$DISCORD"
}
}
}
}
AI
/*------------------------------------------------------------------
FastAPI – Jenkins Declarative Pipeline
- 앱 소스 tar.gz 로 패키징 → GCS 업로드
- Discord 수동 승인 → GCP MIG 롤링 배포 + 실패 시 자동 롤백
------------------------------------------------------------------*/
pipeline {
/*--------------- 기본 설정 ---------------*/
agent any // 어떤 Jenkins 노드에서도 실행
/*--------------- 파라미터 ---------------*/
parameters {
booleanParam(name: 'APPROVED', defaultValue: false,
description: 'Discord ✅ 승인 후 true 로 재실행')
}
/*--------------- 전역 환경변수 ---------------*/
environment {
/* GCP */
PROJECT_ID = 'my-gcp-project'
REGION = 'asia-northeast3'
MIG_NAME = 'fastapi-prod-mig'
BUCKET = 'gs://fastapi-artifacts'
/* 아티팩트 정보 */
BUILD_TAG = "${env.BUILD_NUMBER}"
ARTIFACT_NAME = "fastapi-${BUILD_TAG}.tar.gz"
ARTIFACT_PATH = "${BUCKET}/${ARTIFACT_NAME}"
/* 가상환경 경로 (인스턴스 내부) */
VENV_DIR = "/opt/fastapi/venv"
}
/*--------------- 단계 정의 ---------------*/
stages {
/* 1) Git 체크아웃 */
stage('Git Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/<org>/fastapi-app.git'
}
}
/* 2) Build & Package */
stage('Build & Package') {
steps {
sh '''
# 가상환경 생성 및 의존성 설치
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
deactivate
# 앱 코드 + requirements.txt를 tar.gz 로 묶어 아티팩트 생성
tar -czf ${ARTIFACT_NAME} app requirements.txt
'''
}
}
/* 3) GCS 업로드 */
stage('Upload Artifact → GCS') {
steps {
withCredentials([file(credentialsId: 'GCP-SA-JSON', variable: 'KEY')]) {
sh '''
gcloud auth activate-service-account --key-file=$KEY
gsutil cp ${ARTIFACT_NAME} ${ARTIFACT_PATH}
echo "📦 Uploaded → ${ARTIFACT_PATH}"
'''
}
}
}
/* 4) Discord 수동 승인 */
stage('Ask Approval (Discord)') {
steps {
discordSend message: "[FastAPI] Build #${BUILD_NUMBER} 배포 승인?",
buttons: '✅,❌',
webhookURL: credentials('Discord-Webhook')
}
}
/* 5) MIG 롤링 배포 + 롤백 */
stage('Deploy & Verify (MIG)') {
when { expression { params.APPROVED == true } }
steps {
withCredentials([file(credentialsId: 'GCP-SA-JSON', variable: 'KEY')]) {
sh '''
set -e
# gcloud 인증
gcloud auth activate-service-account --key-file=$KEY
# 현재 템플릿(롤백용) 확보
OLD_TMPL=$(gcloud compute instance-groups managed describe $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--format="value(versions[0].instanceTemplate.basename)")
# startup‑script (아티팩트 다운로드 & 서비스 재시작) 생성
STARTUP="/tmp/startup-${BUILD_TAG}.sh"
cat > $STARTUP <<'EOS'
#!/bin/bash
set -e
gsutil cp ${ARTIFACT_PATH} /tmp/fastapi.tar.gz
mkdir -p /opt/fastapi
tar -xzf /tmp/fastapi.tar.gz -C /opt/fastapi
python3 -m venv ${VENV_DIR}
/opt/fastapi/venv/bin/pip install -r /opt/fastapi/requirements.txt
systemctl restart fastapi # systemd 유닛: /etc/systemd/system/fastapi.service
EOS
chmod +x $STARTUP
# 새 템플릿 생성
NEW_TMPL="tmpl-${MIG_NAME}-${BUILD_TAG}"
gcloud compute instance-templates create $NEW_TMPL \
--project=$PROJECT_ID --region=$REGION \
--machine-type=e2-small \
--image-family=debian-12 \
--image-project=debian-cloud \
--metadata-from-file startup-script=$STARTUP
# 롤링 업데이트 시작
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$NEW_TMPL --type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
# 안정화 대기 (15 분)
if ! gcloud compute instance-groups managed wait-until $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--stable --timeout=900 ; then
echo "❌ 새 버전 불안정 → 롤백"
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$OLD_TMPL --type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
exit 1
fi
'''
}
}
}
}
/*--------------- 후처리 (Discord 알림) ---------------*/
post {
// 빌드 성공 시
success {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "$DISCORD"
}
}
// 빌드 실패 시 (롤백 포함)
failure {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "$DISCORD"
}
}
}
}
FE
/*------------------------------------------------------------------
Next.js – Jenkins Declarative Pipeline
- 빌드 결과(.next, public) tar.gz → GCS 업로드
- Discord 수동 승인 → GCP MIG 롤링 배포 + 실패 시 자동 롤백
------------------------------------------------------------------*/
pipeline {
agent any
parameters {
booleanParam(name: 'APPROVED', defaultValue: false,
description: 'Discord ✅ 승인 후 true 로 재실행')
}
environment {
PROJECT_ID = 'my-gcp-project'
REGION = 'asia-northeast3'
MIG_NAME = 'nextjs-prod-mig'
BUCKET = 'gs://nextjs-artifacts'
BUILD_TAG = "${env.BUILD_NUMBER}"
ARTIFACT_NAME = "nextjs-${BUILD_TAG}.tar.gz"
ARTIFACT_PATH = "${BUCKET}/${ARTIFACT_NAME}"
}
stages {
/* 1) Git 체크아웃 */
stage('Git Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/<org>/nextjs-app.git'
}
}
/* 2) 의존성 설치 & 빌드 */
stage('Install Deps & Build') {
steps {
sh '''
npm ci # lockfile 기반 설치
npm run build # .next 폴더 생성
tar -czf ${ARTIFACT_NAME} .next public package.json
'''
}
}
/* 3) GCS 업로드 */
stage('Upload Artifact → GCS') {
steps {
withCredentials([file(credentialsId: 'GCP-SA-JSON', variable: 'KEY')]) {
sh '''
gcloud auth activate-service-account --key-file=$KEY
gsutil cp ${ARTIFACT_NAME} ${ARTIFACT_PATH}
echo "📦 Uploaded → ${ARTIFACT_PATH}"
'''
}
}
}
/* 4) Discord 수동 승인 */
stage('Ask Approval (Discord)') {
steps {
discordSend message: "[Next.js] Build #${BUILD_NUMBER} 배포 승인?",
buttons: '✅,❌',
webhookURL: credentials('Discord-Webhook')
}
}
/* 5) MIG 롤링 배포 + 롤백 */
stage('Deploy & Verify (MIG)') {
when { expression { params.APPROVED == true } }
steps {
withCredentials([file(credentialsId: 'GCP-SA-JSON', variable: 'KEY')]) {
sh '''
set -e
gcloud auth activate-service-account --key-file=$KEY
OLD_TMPL=$(gcloud compute instance-groups managed describe $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--format="value(versions[0].instanceTemplate.basename)")
STARTUP="/tmp/startup-${BUILD_TAG}.sh"
cat > $STARTUP <<'EOS'
#!/bin/bash
set -e
gsutil cp ${ARTIFACT_PATH} /tmp/nextjs.tar.gz
mkdir -p /opt/nextjs
tar -xzf /tmp/nextjs.tar.gz -C /opt/nextjs
cd /opt/nextjs
npm ci --only=production
# PM2 로 애플리케이션 시작/재시작
pm2 restart nextjs || pm2 start 'npm start' --name nextjs
EOS
chmod +x $STARTUP
NEW_TMPL="tmpl-${MIG_NAME}-${BUILD_TAG}"
gcloud compute instance-templates create $NEW_TMPL \
--project=$PROJECT_ID --region=$REGION \
--machine-type=e2-small \
--image-family=debian-12 \
--image-project=debian-cloud \
--metadata-from-file startup-script=$STARTUP
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$NEW_TMPL --type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
if ! gcloud compute instance-groups managed wait-until $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--stable --timeout=900 ; then
echo "❌ 새 버전 불안정 → 롤백"
gcloud compute instance-groups managed rolling-action start-update $MIG_NAME \
--project=$PROJECT_ID --region=$REGION \
--version=template=$OLD_TMPL --type=proactive \
--max-surge=1 --max-unavailable=1 --min-ready=30
exit 1
fi
'''
}
}
}
}
/*--------------- 후처리 (Discord 알림) ---------------*/
post {
// 빌드 성공 시
success {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "$DISCORD"
}
}
// 빌드 실패 시 (롤백 포함)
failure {
withCredentials([string(credentialsId: 'Discord-Webhook',
variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL,
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "$DISCORD"
}
}
}
}
-
Jenkins 설치 & 기본 플러그인
- Git Plugin, Pipeline Plugin, Discord Notification Plugin 등
-
Credential & 환경 변수 설정
- Git 접근 토큰, Discord WebHook URL
- 빌드 도구(예: Gradle, npm) PATH 등
항목 | 설명 |
---|---|
운영 승인 지연 | 운영 승인자를 제때 확보하지 못할 경우, 배포가 중단될 수 있으므로 근무 시간 외 자동 승인을 위한 정책 검토가 필요하다. |
MIG 롤백 타이밍 | 오류 임계값 설정이 적절하지 않으면 과도한 롤백이 발생하거나 장애가 전파될 수 있으므로 SLO 기반의 정밀한 기준 설정 및 튜닝이 필요하다. |
Jenkins 단일 지점 장애 | Jenkins에 장애가 발생하면 CD 전체가 중단될 수 있으므로 HA 구성 또는 백업용 컨트롤러 도입 검토가 필요하다. |
지표 | 기존(수동) | CD 도입 후(목표) |
---|---|---|
평균 다운타임 | 3 분 | 무중단 |
롤백 소요 | 10 분 수작업 | < 2 분 자동 |
도입 후 첫 달은 위 세 지표를 집중 모니터링해 배포 빈도↑ / 안정성↑ 효과를 수치로 검증할 예정이다.
결론
- CD 파이프라인 구축으로 “한 줄의 코드가 운영까지 안전하게 전달되는 시간”을 대폭 단축한다.
향후 계획
- Docker를 활용한 컨테이너 기반 배포로 전환하여, 이미지를 빌드‑푸시‑배포하는 전 과정을 표준화하고 무중단 롤링 업데이트·카나리 배포까지 확장한다.
- 서비스 성장 속도를 유지하면서도 환경 간 일관성과 배포 안정성을 확보한다.