Cloud 과제 3단계: CD(지속적 배포) 파이프라인 구축 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 업무 개요

업무 제목: CD 파이프라인 구축

업무 설명:

본 업무는 CI 파이프라인의 결과물(build artifact) 을 실제 서버에 자동으로 배포하는 CD(지속적 배포) 환경을 구축하는 것입니다.

개발 완료된 기능을 운영 서버에 신속하고 안전하게 배포함으로써, 릴리즈 속도와 서비스 품질을 동시에 확보하는 것이 목표입니다.

이 단계는 DevOps 환경 구축의 3단계에 해당하며, 실제 사용자에게 전달되는 릴리즈 흐름의 자동화를 구현하는 핵심 작업입니다.

2. CD 도입 필요성

실무 관점

문제 CD 도입 전 CD 도입 후
배포 소요 시간 수작업으로 30분 이상 자동화로 5~10분
배포 시 실수 위험 파일 누락, 포트 충돌 스크립트 자동화로 최소화
사용자 경험 다운타임 존재 무중단 배포 가능 (Blue-Green)

3. 설계 과제

3-1. CD 적용 범위 및 방식 결정

  • 적용 범위:
    • Prod 서버 승인 후 수동 배포 → Continuous Delivery 방식
  • 배포 전략:
    • Blue-Green 배포 (VM 기반) 선택
    • 무중단 배포 가능, 실패 시 Nginx 포트 스위칭만으로 신속 복구 가능
  • 선택 이유:
    • 단일 인스턴스 기반 구조에 적합하며, 다운타임 없이 안정적인 운영 가능

3-2. CD 파이프라인 설계

  • 🔁 전체 흐름
  1. GitHub main 브랜치에 PR이 머지되면 GitHub Actions가 자동 트리거
  2. SSH를 통해 원격 VM에 접속
  3. 기존 jar 및 프론트 빌드 파일 백업 (자동)
  4. Git Pull 후 새 버전 빌드 & 실행
  5. 프론트는 Nginx 정적 디렉토리에 복사
  6. Nginx 재시작 → 트래픽 스위칭
  7. 문제 발생 시 백업 파일로 수동 롤백
  • 배포 스크립트 사용이유
    1. 서버 내부 환경에 최적화된 로직
    • 온기 서비스는 단일 GCP VM 기반의 배포 구조로, 백엔드 실행(JAR), 프론트 파일 복사, Nginx 설정 변경 등 서버 내부에서만 수행 가능한 작업들이 존재
    • 이를 GitHub Actions 워크플로우 내부에서 모두 작성하면 복잡도가 증가하며, 서버 환경 변경에 유연하게 대응하기 어려움
    • 배포 스크립트는 이러한 작업들을 서버 환경에 맞게 통합하여 구성함으로써 실행 안정성운영 유연성을 확보할 수 있음
    1. 로컬 상태 기반 동작
    • GitHub Actions는 외부 실행 환경으로, 서버 내 현재 상태(예: 실행 중인 포트, 기존 jar 백업, Nginx 설정 상태 등)를 실시간으로 파악하기 어려움
    • 반면 배포 스크립트는 서버 내에서 동작하기 때문에, 실행 중인 프로세스 확인, 포트 충돌 방지, 백업 파일 자동 생성, 롤백 처리현실적인 운영 흐름을 반영할 수 있음
    1. 보안 관리
    • 배포 스크립트를 사용하면 GitHub에는 SSH 키만 저장하고, 모든 민감한 로직은 서버 내부에 유지함으로써 보안 리스크를 최소화할 수 있음

3-3. 파이프라인 구성 명세

  • 트리거 조건
이벤트 대상 브랜치 처리 방식
Push main 운영용 CD 트리거
Pull Request main 병합 승인 후 CD 트리거
  • 주요 구성 요소
항목 도구 설명
원격 배포 SSH, Git VM에 SSH 접속 후 Git Pull & 실행
백업 Shell Script 배포 전 jar, 프론트 build 자동 백업
프론트 배포 Nginx /var/www/html로 복사 후 reload
백엔드 배포 Java 기존 프로세스 종료 후 jar 재실행
롤백 수동 백업 디렉토리에서 jar & build 복원
알림 선택 Discord/Webhook 연동 가능
  • 무중단 배포 도입 (Blue-Green Deployment)

블루그린

초기 Big Bang 배포 방식은 배포 시 약 2~3분의 다운타임이 발생하여 사용자 경험에 영향을 주었습니다.

따라서 트래픽만 전환하는 Blue-Green 배포 전략으로 안정적인 무중단 배포를 도입하였습니다.

Blue-Green 배포 는 새로운 애플리케이션 버전을 배포할 때, 기존 버전(Blue)과 새 버전(Green)을

동시에 실행한 뒤, 새 버전이 정상적으로 동작하는 것을 확인한 후에 트래픽을 전환하는 무중단 배포 전략입니다.

  • 백엔드 Blue-Green 배포 전략
  1. 포트 분리: 8081, 8082 두 포트를 번갈아 사용하여, 기존의 서비스를 유지한 채 새 버전 실행
  2. 동시 기동: 새 포트로 Spring Boot 서버를 실행하면서 기존 프로세스는 그대로 유지
  3. 헬스 체크: 새 버전 /health 엔드포인트에 curl 요청으로 정상 여부 확인
  4. 트래픽 전환: Nginx 설정 파일의 프록시 포트를 기존 → 새 포트로 변경 후 reload
  5. 롤백 처리: 헬스체크 실패 시, 새 포트의 프로세스를 종료하고 이전 서비스 유지
  • 프론트엔드 Blue-Green 배포 전략
  1. 현재 사용 중인 디렉토리 확인: /var/www/current_front 파일을 읽어서 현재 사용 중인 디렉토리(blue 또는 green) 확인

  2. 반대편 디렉토리 결정: 현재가 bluegreen을, 현재가 green이면 blue를 새 디렉토리로 지정

  3. 프론트엔드 프로젝트 빌드: npm install, npm run build 명령으로 새 정적 파일 생성

  4. 새 디렉토리에 정적 파일 복사: build/* 파일을 /var/www/html-green 또는 /var/www/html-blue에 복사, 기존 파일은 삭제 후 새 파일 복사

  5. Nginx 설정 파일 수정: /etc/nginx/sites-available/ongi.confroot 경로를 새 디렉토리로 변경

    예: root /var/www/html-blue;root /var/www/html-green;

  6. 현재 디렉토리 상태 파일 갱신: /var/www/current_front 파일을 새 디렉토리 이름으로 덮어씀

  7. Nginx 재시작: sudo systemctl reload nginx 명령으로 설정 반영

  • Blue-Green 배포 Back 스크립트
#!/bin/bash

APP_NAME="ongi-server"
JAR_NAME="ongi-server-0.0.1.jar"
JAR_PATH="./build/libs/$JAR_NAME"
PORT1=8081
PORT2=8082
LOG_DIR="./logs"

# 로그 디렉토리 생성
mkdir -p $LOG_DIR

# 최초 배포 감지: 두 포트 모두 비어있을 경우
if ! lsof -i :$PORT1 | grep LISTEN && ! lsof -i :$PORT2 | grep LISTEN; then
  echo "최초 배포 감지 → 8081, 8082 모두 실행"

  nohup java -jar $JAR_PATH --server.port=$PORT1 > $LOG_DIR/$APP_NAME-$PORT1.log 2>&1 &
  sleep 5
  nohup java -jar $JAR_PATH --server.port=$PORT2 > $LOG_DIR/$APP_NAME-$PORT2.log 2>&1 &
  
  echo "8081, 8082 실행 완료 (최초 배포)"
  exit 0
fi

# 실행 중인 포트 감지
if lsof -i :$PORT1 | grep LISTEN >/dev/null; then
  OLD_PORT=$PORT1
  NEW_PORT=$PORT2
else
  OLD_PORT=$PORT2
  NEW_PORT=$PORT1
fi

echo "현재 실행 중인 포트: $OLD_PORT → 새 포트: $NEW_PORT"

# 새 버전 실행
nohup java -jar $JAR_PATH --server.port=$NEW_PORT > $LOG_DIR/$APP_NAME-$NEW_PORT.log 2>&1 &
sleep 10

# 헬스 체크
if curl -s http://localhost:$NEW_PORT/healthz | grep OK; then
  echo "새 버전 정상 작동 확인됨"

  # Nginx 포트 전환
  sudo sed -i "s/$OLD_PORT/$NEW_PORT/g" /etc/nginx/sites-available/ongi.conf
  sudo systemctl reload nginx
  echo "Nginx 프록시 전환 완료"

  # 이전 버전 종료
  OLD_PID=$(lsof -ti tcp:$OLD_PORT)
  if [ ! -z "$OLD_PID" ]; then
    kill -9 $OLD_PID
    echo "이전 포트 $OLD_PORT 프로세스 종료 완료"
  fi
else
  echo "헬스체크 실패 → 롤백 수행"
  NEW_PID=$(lsof -ti tcp:$NEW_PORT)
  [ ! -z "$NEW_PID" ] && kill -9 $NEW_PID
  exit 1
fi
  • Blue-Green 배포 Front 스크립트
#!/bin/bash

# 디렉토리 설정
FRONT_DIR="/home/ubuntu/ongi/frontend"
CURRENT_FILE="/var/www/current_front"

BLUE_DIR="/var/www/html-blue"
GREEN_DIR="/var/www/html-green"

# 현재 사용 중인 디렉토리 확인
CURRENT=$(cat $CURRENT_FILE 2>/dev/null || echo "blue")

# 새로 사용할 디렉토리 결정
if [ "$CURRENT" == "blue" ]; then
  NEW="green"
  TARGET_DIR=$GREEN_DIR
else
  NEW="blue"
  TARGET_DIR=$BLUE_DIR
fi

echo "현재 서빙 디렉토리: $CURRENT → 새 디렉토리: $NEW"

# 프론트 빌드
echo "프론트 빌드 시작"
cd $FRONT_DIR || exit 1
npm install
npm run build

# 정적 파일 복사
echo "정적 파일을 $TARGET_DIR 로 복사 중"
sudo rm -rf $TARGET_DIR/*
sudo cp -r build/* $TARGET_DIR/

# Nginx 설정 변경
echo "Nginx 설정 변경 중"
NGINX_CONF="/etc/nginx/sites-available/ongi.conf"
sudo sed -i "s/html-$CURRENT/html-$NEW/g" $NGINX_CONF

# 현재 상태 저장
echo "$NEW" | sudo tee $CURRENT_FILE

# Nginx reload
echo "Nginx 재시작"
sudo systemctl reload nginx

echo "프론트 Blue-Green 무중단 배포 완료"
  • /var/www/html-blue, /var/www/html-green 두 디렉토리를 미리 생성
  • /var/www/current_front 파일은 초기값으로 blue 또는 green을 넣어두면 됨
  • Nginx 설정 파일에서 root /var/www/html-blue; 혹은 html-green; 중 하나로 시작하도록 작성되어 있어야 함

- nginx 설정 파일 /etc/nginx/sites-available/ongi.conf

React로 빌드된 정적 파일을 Nginx가 / 경로에서 서빙

Spring Boot 서버의 API 요청(/api/)은 Nginx가 백엔드 포트(8081 또는 8082)로 프록시

배포 시 Blue-Green 전략에 따라 Nginx 프록시 포트를 자동 전환하여 무중단 배포 구현

server {
    listen 80;
    server_name localhost;

    root /var/www/html-blue;  # ← 초기값 (스크립트에서 html-blue ↔ html-green 전환)

    index index.html;

    location / {
        try_files $uri /index.html;
    }
    
    location /api/ {
        proxy_pass http://localhost:8081/api/;  # ← backend도 Blue-Green 구조 사용 시 8081 ↔ 8082 전환
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

4. CD Architecture

image

  1. CI 과정을 통해 생성된 build 파일을 배포에 활용합니다.
  2. Dev 또는 Prod 브랜치에 Pull Request가 생성되면, 자동으로 빌드가 시작됩니다.
  3. Build 파일을 배포 서버(Green)에 전송 및 실행
  4. Nginx가 Green 서버에 대해 Health Check 수행
  5. Green 서버가 정상 응답 시 Nginx가 트래픽 전환
  6. 구버전 Process Kill

CD 도입 효과 요약

항목 도입 전 도입 후
배포 시간 30분 이상 5~10분
다운타임 있음 (2~3분) 없음 (Blue-Green 적용 시)
롤백 전략 없음 수동 백업/복원 구조 확보
배포 안정성 낮음 Git 기반 자동화로 향상

예상 성과

  • 릴리즈 속도 증가 → 하루 2~3회 이상 배포 가능
  • 오류 발생 시 빠른 롤백 → 서비스 가용성 향상
  • 반복 가능한 배포 프로세스 → 팀 협업 및 유지보수 효율화

추가 고려사항

  • .env와 같은 시크릿은 GitHub Secrets 또는 서버 내 안전한 위치에서 관리
  • 이후 고도화 시, Docker 이미지 기반으로 구조 개선 + Blue-Green 인프라 자동화 가능
  • Slack/Discord 알림 연동, 헬스체크 자동화도 도입 가능

MVP CD의 한계점 & 개선방안

  1. 자동화 범위 제한

    GitHub Actions는 단순히 deploy.sh를 트리거할 뿐, 배포 로직 자체는 여전히 수작업 기반

    → Docker 이미지 빌드 → 레지스트리 푸시 → 배포 자동화 도입 (CI/CD 완전 통합)

  2. 상태 의존성

    서버 포트 상태(lsof), 파일 경로 등에 강하게 의존

    → 컨테이너로 분리

  3. 롤백 유연성 부족

    → 헬스 체크 결과에 따라 실패 시 자동 롤백 + Discode 알림 연동

  4. 확장성 한계

    단일 서버에서만 동작하는 구조

    → K8s 환경 기반으로 Blue-Green 외에도 Canary, Rolling 도입 가능성 확보

  5. 배포 이력 관리 미흡

    → Git 태그 or Docker 이미지 태그 or Github Actions Artifacts 배포 이력 관리 도입