개발이군고구마

빌드(CI)와 배포(CD) 본문

Cloud Deploy

빌드(CI)와 배포(CD)

김구황 2026. 3. 10. 07:24
728x90

 

  전체 배포 파이프라인 — 2단계 구조
⬡ 1단계 · 프리뷰 배포 (PREV)
1
프리뷰 브랜치 생성 PREV_20250101 배포 담당자가 GitLab에서 직접 생성
2
개발자 브랜치 → PREV 머지 feature/login → PREV_20250101 feature/payment → PREV_20250101
3
Jenkins 수동 클릭 http://jenkins.mycompany.com Job: deploy-prev / Set: B 선택
4
GitLab에서 Jenkinsfile 로드 scripts/Jenkinsfile-prev
5
PREV 브랜치 체크아웃 & 빌드 → app-pc.jar, app-mo.jar, app-admin.jar
6
빌드 결과물 S3 업로드 (zip) s3://myco-deploy/PREV_20250101/
7
Launch Template → EC2 신규 생성 B-SET / PC·MO·Admin 각각
8
EC2 User Data 자동 실행 S3 → 다운로드 → 앱 실행
QA 검증 완료 → 운영배포 진행
⬢ 2단계 · 운영 배포 (PRD)
1
운영 브랜치 생성 PRD_20250101 PREV 내용 기반으로 생성
2
Jenkins 수동 클릭 Job: deploy-prd / 신세트: B 선택
3
운영 전용 Jenkinsfile 로드 scripts/Jenkinsfile-prd ⚠ 빌드 없음 — S3 파일 재사용
4
B-SET 스케일 아웃 PC / MO / Admin EC2 각각 신규 생성 User Data → S3에서 prod 파일 실행
5
B-SET 스모크 테스트 확인
6
구세트 A 절체 시작 ① WEB(Nginx) 먼저 내림 ② WAS(앱) 내림 ③ ALB 트래픽 → B-SET 전환
7
배치 작업 완료 대기 (수 시간) A-SET에서 돌던 배치 Job 종료 확인
A-SET 스케일 인 (EC2 종료) 다음 배포 때 A가 신세트가 됨
브랜치 생성 & 머지
GitLab
📁
브랜치 구조
main
└── PRD_20250101  ← 운영 브랜치
└── PREV_20250101 ← 프리뷰 브랜치
    ├── feature/login
    ├── feature/payment
    └── feature/mypage
🔀
머지 대상
이번 배포에 포함될 개발 브랜치들을
배포 담당자가 PREV_20250101로 직접 머지하거나,
각 개발자에게 PR 머지를 요청해요.
Jenkins 파이프라인 실행
jenkins.mycompany.com
Checkout
PREV_20250101
브랜치 받아오기
Build
gradlew build
jar 3개 생성
S3 Upload
zip 압축 후
S3 업로드
Deploy
Launch Template
→ EC2 생성
💡 Jenkins가 "어느 Jenkinsfile을 사용할지"는 Job 설정에 저장되어 있어요. 담당자가 수동으로 파일을 고르는 게 아니라, Job 자체가 GitLab의 특정 브랜치에서 Jenkinsfile을 자동으로 읽어옵니다.
📂 GitLab 저장소 안에 배포 스크립트들이 별도 브랜치 또는 폴더로 관리돼요. 프리뷰용과 운영용 스크립트는 파일이 다릅니다.

이 문서에서 설명하는 배포 구조는 Blue/Green + ASG 혼합 방식이에요. Jenkins는 직접 서버를 만드는 역할이 아니라, 새 버전을 빌드하고, S3에 올리고, ASG가 새 인스턴스를 교체·운영하도록 지시하는 오케스트레이터에 가깝습니다.
🧭 이 섹션의 핵심: Jenkinsfile의 목적은 "새 버전 배포를 시작하는 것"이고, 실제로 새 EC2가 뜨고 교체되는 일은 ASG + Launch Template가 담당합니다. 그래서 sec3에서 이어서 보게 될 Launch Template / User Data는 바로 이 Jenkins 배포 흐름의 다음 단계라고 이해하면 됩니다.
파일 경로 용도 빌드 S3 업로드 배포(ASG) 절체
scripts/Jenkinsfile-prev 프리뷰 전용 ✔ 있음 ✔ 있음 ✔ 있음 — 없음
scripts/Jenkinsfile-prd 운영 전용 — 없음 — 없음 ✔ 있음 ✔ 있음
scripts/Jenkinsfile-build PR 머지 시 자동 검증 ✔ 있음 — 없음 — 없음 — 없음
🗺
Jenkinsfile이 배포 전체에서 맡는 역할
Blue/Green + ASG 혼합 구조 기준
🧭 배포 흐름 한눈에 보기 flow
1. Jenkins가 소스 코드를 체크아웃한다
2. Jenkins가 애플리케이션을 빌드한다 (프리뷰만)
3. Jenkins가 결과물을 S3에 업로드한다
4. Jenkins가 Blue/Green 중 새 세트(New Set)에 연결된 ASG를 갱신한다
5. ASG가 Launch Template 기준으로 새 EC2를 순차적으로 생성한다
6. 새 EC2는 부팅하면서 User Data를 실행한다
7. 애플리케이션이 정상 기동되고 헬스 체크를 통과한다
8. ALB가 트래픽을 새 세트(New Set)로 전환한다
9. 이전 세트(Old Set)는 드레이닝 후 축소 또는 종료한다
왜 Jenkinsfile 설명에서 ASG를 먼저 잡고 가는가?
sec3에서 보게 될 Launch Template과 User Data는 단독으로 움직이지 않기 때문이에요. Jenkins가 배포를 시작하면, 그다음 실제 서버 생성과 부팅은 ASG → Launch Template → EC2 → User Data 순서로 이어집니다. 즉, sec2는 "배포를 누가 시작하느냐"를 설명하고, sec3는 "새 서버가 어떻게 태어나고 부팅하느냐"를 설명하는 구조예요.
📄
scripts/Jenkinsfile-prev — 프리뷰 배포 스크립트
GitLab: myrepo / branch: scripts
🔎 프리뷰 배포의 목적은 새 버전을 먼저 검증 가능한 세트에 올려보는 것이에요. Jenkins는 코드를 빌드하고 S3에 배포 파일을 올린 뒤, 프리뷰용 ASG가 새 인스턴스를 교체하도록 refresh를 시작합니다.
📄 scripts/Jenkinsfile-prev Groovy
// Jenkins Job 설정에서 이 파일을 GitLab에서 읽어옴
// GitLab: https://gitlab.mycompany.com/myteam/myrepo.git
// Branch: scripts   Script Path: scripts/Jenkinsfile-prev

pipeline {
    agent any

    parameters {
        // 배포 담당자가 Jenkins 화면에서 입력
        string(name: 'BRANCH', defaultValue: 'PREV_20250101')
        choice(name: 'TARGET_SET', choices: ['A-SET', 'B-SET'])
    }

    environment {
        S3_BUCKET     = 'myco-deploy'
        AWS_REGION    = 'ap-northeast-2'

        // 프리뷰용 ASG
        PREV_PC_ASG   = 'prev-pc-asg'
        PREV_MO_ASG   = 'prev-mo-asg'
        PREV_ADM_ASG  = 'prev-admin-asg'
    }

    stages {

        stage('Checkout') {
            steps {
                // 여기서 PREV_20250101 브랜치를 체크아웃
                git branch: "${params.BRANCH}",
                    credentialsId: 'gitlab-jenkins-token',
                    url: 'https://gitlab.mycompany.com/myteam/myrepo.git'
            }
        }

        stage('Build') {
            steps {
                sh '''
                    ./gradlew :pc-server:build    -x test
                    ./gradlew :mo-server:build    -x test
                    ./gradlew :admin-server:build -x test
                '''
                // 결과: build/pc-server/app-pc.jar
                //       build/mo-server/app-mo.jar
                //       build/admin-server/app-admin.jar
            }
        }

        stage('S3 Upload') {
            steps {
                sh """
                    zip -j deploy.zip \
                      build/pc-server/app-pc.jar \
                      build/mo-server/app-mo.jar \
                      build/admin-server/app-admin.jar

                    aws s3 cp deploy.zip \
                      s3://${S3_BUCKET}/${params.BRANCH}/deploy.zip \
                      --region ${AWS_REGION}
                """
                // S3 경로: s3://myco-deploy/PREV_20250101/deploy.zip
            }
        }

        stage('Refresh Preview ASG') {
            steps {
                sh """
                    # 프리뷰 세트에 연결된 ASG가 새 인스턴스를 순차적으로 교체하도록 refresh 시작

                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PREV_PC_ASG} \
                      --preferences MinHealthyPercentage=50,InstanceWarmup=120 \
                      --region ${AWS_REGION}

                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PREV_MO_ASG} \
                      --preferences MinHealthyPercentage=50,InstanceWarmup=120 \
                      --region ${AWS_REGION}

                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PREV_ADM_ASG} \
                      --preferences MinHealthyPercentage=50,InstanceWarmup=120 \
                      --region ${AWS_REGION}
                """
            }
        }

        stage('Wait for Preview Health Check') {
            steps {
                sh """
                    echo "Waiting for preview instances to pass health checks..."
                    sleep 60
                """
            }
        }
    }
}
프리뷰에서 왜 ASG를 쓰는가?
운영만 ASG로 하고 프리뷰는 다르게 가면, 두 환경의 동작 방식이 달라져요. 프리뷰도 ASG 기반으로 맞춰두면 운영과 최대한 비슷한 조건에서 검증할 수 있어서, 배포 안정성이 훨씬 좋아집니다.
📄
scripts/Jenkinsfile-prd — 운영 배포 스크립트
빌드 없음 · S3 파일 재사용 · Blue/Green 절체
운영 배포는 프리뷰 때 이미 검증된 S3 파일을 그대로 씁니다. 빌드를 다시 하지 않는 이유는 "검증한 결과물 그대로 운영에 반영한다"는 원칙을 지키기 위해서예요.

운영에서는 새 세트(New Set)의 ASG를 먼저 갱신해 충분히 준비시킨 뒤, 마지막에 ALB가 트래픽을 넘기는 방식으로 절체가 일어납니다.
📄 scripts/Jenkinsfile-prd Groovy
pipeline {
    agent any

    parameters {
        string(name: 'PREV_BRANCH', defaultValue: 'PREV_20250101')
        string(name: 'PRD_BRANCH',  defaultValue: 'PRD_20250101')
        choice(name: 'NEW_SET',     choices: ['B-SET', 'A-SET'])
        choice(name: 'OLD_SET',     choices: ['A-SET', 'B-SET'])
    }

    environment {
        S3_BUCKET     = 'myco-deploy'
        AWS_REGION    = 'ap-northeast-2'

        // 운영용 ASG
        PRD_PC_ASG    = 'prd-pc-asg'
        PRD_MO_ASG    = 'prd-mo-asg'
        PRD_ADM_ASG   = 'prd-admin-asg'

        // 운영용 Launch Template
        PC_LT_NAME    = 'prd-pc-server-template'
        MO_LT_NAME    = 'prd-mo-server-template'
        ADM_LT_NAME   = 'prd-admin-server-template'
    }

    stages {
        // Build 단계 없음 — 프리뷰에서 검증된 산출물 그대로 사용

        stage('Promote Artifact Metadata') {
            steps {
                sh """
                    # 실제 환경에서는 S3 copy, parameter store, launch template versioning 등을 함께 사용 가능
                    echo "Promote artifact from ${params.PREV_BRANCH} to ${params.PRD_BRANCH}"
                """
            }
        }

        stage('Create Launch Template Version') {
            steps {
                sh """
                    # 새 버전 설명만 예시로 표현
                    # 실제로는 새 AMI ID 또는 User Data 변경분을 반영할 수 있음

                    aws ec2 create-launch-template-version \
                      --launch-template-name ${PC_LT_NAME} \
                      --source-version '$Latest' \
                      --version-description "${params.PRD_BRANCH}" \
                      --region ${AWS_REGION}

                    aws ec2 create-launch-template-version \
                      --launch-template-name ${MO_LT_NAME} \
                      --source-version '$Latest' \
                      --version-description "${params.PRD_BRANCH}" \
                      --region ${AWS_REGION}

                    aws ec2 create-launch-template-version \
                      --launch-template-name ${ADM_LT_NAME} \
                      --source-version '$Latest' \
                      --version-description "${params.PRD_BRANCH}" \
                      --region ${AWS_REGION}
                """
            }
        }

        stage('Update New Set ASG') {
            steps {
                sh """
                    # 새 세트(New Set)에 연결된 ASG가 새 Launch Template 버전을 사용하도록 갱신

                    aws autoscaling update-auto-scaling-group \
                      --auto-scaling-group-name ${PRD_PC_ASG}-${params.NEW_SET} \
                      --launch-template LaunchTemplateName=${PC_LT_NAME},Version='$Latest' \
                      --region ${AWS_REGION}

                    aws autoscaling update-auto-scaling-group \
                      --auto-scaling-group-name ${PRD_MO_ASG}-${params.NEW_SET} \
                      --launch-template LaunchTemplateName=${MO_LT_NAME},Version='$Latest' \
                      --region ${AWS_REGION}

                    aws autoscaling update-auto-scaling-group \
                      --auto-scaling-group-name ${PRD_ADM_ASG}-${params.NEW_SET} \
                      --launch-template LaunchTemplateName=${ADM_LT_NAME},Version='$Latest' \
                      --region ${AWS_REGION}
                """
            }
        }

        stage('Refresh New Set') {
            steps {
                sh """
                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PRD_PC_ASG}-${params.NEW_SET} \
                      --preferences MinHealthyPercentage=75,InstanceWarmup=180 \
                      --region ${AWS_REGION}

                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PRD_MO_ASG}-${params.NEW_SET} \
                      --preferences MinHealthyPercentage=75,InstanceWarmup=180 \
                      --region ${AWS_REGION}

                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name ${PRD_ADM_ASG}-${params.NEW_SET} \
                      --preferences MinHealthyPercentage=50,InstanceWarmup=180 \
                      --region ${AWS_REGION}
                """
            }
        }

        stage('Wait for Health Check') {
            steps {
                sh """
                    # 실제 운영에서는 서비스별 target group 상태를 개별 확인
                    echo "Waiting for new set instances to pass health checks..."
                    sleep 120
                """
            }
        }

        stage('ALB Traffic Switch') {
            steps {
                sh """
                    # ALB가 기본 트래픽을 새 세트(New Set)로 넘김
                    aws elbv2 modify-listener \
                      --listener-arn arn:aws:elasticloadbalancing:ap-northeast-2:... \
                      --default-actions Type=forward,TargetGroupArn=arn:...:targetgroup/${params.NEW_SET}
                """
            }
        }

        stage('Drain Old Set') {
            steps {
                sh """
                    # 이전 세트(Old Set)는 즉시 종료하지 않고 드레이닝 후 축소
                    echo "Old set ${params.OLD_SET} is now draining..."
                """
            }
        }

        // 스케일 인은 운영 확인 후 수동 또는 별도 Job으로 처리 가능
    }
}
운영 배포에서 가장 중요한 포인트는?
핵심은 기존 세트를 바로 건드리지 않고, 새 세트를 먼저 완성한 뒤 마지막에 트래픽만 전환한다는 점이에요. 이게 Blue/Green의 안정성과 ASG의 자동 교체 능력을 함께 쓰는 방식이고, 그래서 운영 배포가 더 안전해집니다.
🔗
sec3로 자연스럽게 이어지는 포인트
다음 섹션에서 볼 내용 미리 잡기
여기까지가 "Jenkins가 배포를 어떻게 시작하느냐"에 대한 설명이었다면, 다음 sec3는 "ASG가 새 EC2를 만들 때 정확히 무엇을 참고하느냐"를 설명하는 파트예요.

즉, sec2의 마지막 장면은 ASG Refresh 시작이고, sec3의 첫 장면은 Launch Template을 참고해 EC2가 생성되고, 그 안에서 User Data가 실행되는 순간이라고 보면 됩니다.
🔑 핵심 원리: Launch Template = EC2의 "설계도". 그 안의 User Data = EC2가 처음 켜질 때 AWS가 자동으로 실행해주는 shell 스크립트. Jenkins는 "이 설계도로 EC2 만들어줘" 라고 AWS에 API 요청만 할 뿐, 이후는 EC2가 스스로 다 처리합니다.

정확한 순서는 (1) Jenkins가 ASG에 배포/교체 요청(2) ASG가 Launch Template을 참고해 EC2 생성(3) EC2가 부팅하면서 User Data 실행 입니다.
🖥
Launch Template 목록 (예시)
prev-pc-server-template
prev-mo-server-template
prev-admin-server-template
prd-pc-server-template
prd-mo-server-template
prd-admin-server-template
📋
Launch Template이 담고 있는 정보
AMI ID (OS 이미지)
인스턴스 타입 (c5.2xlarge 등)
IAM Role (S3 접근 권한)
보안 그룹 ID
서브넷 설정
User Data (부팅 스크립트)
즉, "이 사양의 서버를 이렇게 부팅해"라는 생성 규격 묶음
🧭
Jenkins → ASG → Launch Template → EC2 → User Data 흐름
누가 무엇을 하는지 한 번에 이해하기
이렇게 이해하면 정확합니다: Jenkins가 "새 버전으로 배포해"라고 요청하면, ASG가 Launch Template을 기준으로 EC2를 만들고, 새 EC2는 부팅하면서 User Data를 실행해 애플리케이션을 서비스 가능한 상태로 올립니다.
🗺 동작 순서도 flow
┌─────────┐
│ Jenkins │
└────┬────┘
     │
     │ 배포 시작 / ASG 갱신 / Instance Refresh 요청
     ▼
┌────────────────────┐
│ Auto Scaling Group │
└────┬───────────────┘
     │
     │ "어떤 사양으로 새 서버를 만들까?"
     ▼
┌──────────────────┐
│ Launch Template  │
└────┬─────────────┘
     │
     │ AMI / 타입 / IAM / SG / User Data 제공
     ▼
┌──────────────────┐
│   EC2 Instance   │
└────┬─────────────┘
     │
     │ 첫 부팅
     ▼
┌──────────────────┐
│   User Data      │
│ - 패키지 설치      │
│ - S3에서 파일 다운로드 │
│ - jar 실행         │
└────┬─────────────┘
     │
     ▼
┌──────────────────┐
│ 서비스 가능 상태   │
└──────────────────┘
그럼 User Data가 EC2를 만드는 건가?
아닙니다. User Data는 이미 만들어진 EC2 안에서 실행되는 초기화 스크립트예요. EC2 생성은 ASG가 Launch Template을 참고해서 하고, User Data는 그 EC2가 처음 켜질 때 실행됩니다.
🏨
비유로 이해하기 — 호텔 객실을 여는 과정
개념이 헷갈릴 때 가장 쉬운 설명
🧑‍💼
등장인물 매칭
Jenkins = 본사 운영팀
ASG = 객실 운영 매니저
Launch Template = 객실 표준 설계도
EC2 = 실제 객실
User Data = 객실 오픈 전 비품 세팅
💡
핵심 포인트
객실을 실제로 여는 주체는 운영 매니저(ASG)이고,
설계도(Launch Template)는 기준을 제공하며,
객실이 열린 뒤 내부 세팅(User Data)이 진행됩니다.
📝 즉, User Data는 객실을 짓는 단계가 아니라, 이미 열린 객실을 바로 운영할 수 있게 만드는 마지막 준비 작업입니다.
📈
Auto Scaling Group과의 관계
Launch Template이 혼자 일하지 않는 이유
Launch Template만 있으면 EC2가 자동으로 생기나?
아니에요. Launch Template은 설계도일 뿐, 스스로 EC2를 만들지 않습니다. 누군가 이 설계도를 사용해야 하는데, 그 대표적인 주체가 ASG입니다.
🧱
Launch Template
"어떤 스펙으로 만들지?"를 정의
예: AMI, 인스턴스 타입, 보안 그룹, User Data
🛠
Auto Scaling Group
"몇 대를 유지할지?"를 관리
필요하면 Launch Template을 참고해 새 EC2 생성
🚀
Jenkins
배포의 시작점
ASG 설정 변경, desired capacity 조절, Instance Refresh 트리거
🔄 실무에서는 Jenkins가 직접 EC2를 하나하나 만드는 것보다, ASG의 Launch Template 버전을 교체하거나 Instance Refresh를 시작해서 새 인스턴스가 순차적으로 교체되도록 만드는 경우가 많습니다.
🔁
배포 시 실제로 벌어지는 일
롤링 배포 관점에서 보기
📦 배포 흐름 예시 deploy
1. Jenkins가 새 버전을 빌드한다
2. Jenkins가 결과물을 S3에 업로드하거나 새 AMI를 만든다
3. Jenkins가 ASG에 새 Launch Template 버전을 사용하도록 설정한다
4. Jenkins가 Instance Refresh를 시작한다
5. ASG가 기존 인스턴스를 순서대로 교체한다
6. 새 EC2가 Launch Template 기준으로 생성된다
7. 새 EC2는 부팅하면서 User Data를 실행한다
8. 앱이 정상 기동되고 헬스 체크를 통과하면 트래픽을 받는다
왜 이 구조가 좋은가?
새 서버가 완전히 준비된 뒤에만 서비스에 붙일 수 있어서 무중단에 가까운 롤링 배포가 가능하고, 기존 서버에 문제가 생겨도 ASG가 다시 같은 설계도로 새 서버를 만들어 복구 자동화가 가능해집니다.
💻
prd-pc-server-template — User Data (PC 서버용)
EC2 부팅 시 AWS가 자동 실행
EC2는 "나는 PC 서버야"라는 걸 어떻게 알지?
Launch Template 자체가 역할별로 따로 존재해요. prd-pc-server-template으로 만들어진 EC2는 User Data에 PC 전용 jar를 받아오도록 이미 쓰여 있어요. MO 서버용 Template은 MO jar를, Admin용은 Admin jar를 받아오죠. EC2는 태어날 때부터 역할이 정해져 있습니다.
⚡ User Data — prd-pc-server-template bash
#!/bin/bash
# =====================================================
# 이 스크립트는 EC2 첫 부팅 시 AWS가 자동으로 실행합니다
# Launch Template: prd-pc-server-template
# 역할: PC 웹 서버 (Spring Boot)
# =====================================================

# 1. 환경 변수 설정
DEPLOY_BRANCH="PRD_20250101"         # Jenkins에서 넘겨받거나 고정값
S3_BUCKET="myco-deploy"
APP_DIR="/opt/myapp"
LOG_DIR="/var/log/myapp"

# 2. 필수 패키지 설치
yum update -y
yum install -y java-17-amazon-corretto awscli unzip

# 3. 디렉토리 생성
mkdir -p $APP_DIR $LOG_DIR

# 4. S3에서 빌드 결과물 다운로드
#    Jenkins가 올려둔 경로: s3://myco-deploy/PRD_20250101/deploy.zip
aws s3 cp s3://${S3_BUCKET}/${DEPLOY_BRANCH}/deploy.zip $APP_DIR/deploy.zip

# 5. 압축 해제
unzip -o $APP_DIR/deploy.zip -d $APP_DIR/

# 6. PC 서버 전용 jar 실행
#    ↑ MO 서버라면 app-mo.jar, Admin이라면 app-admin.jar를 실행
java -jar $APP_DIR/app-pc.jar \
  --spring.profiles.active=prod \
  --server.port=8080 \
  --logging.file.name=$LOG_DIR/pc-server.log \
  > $LOG_DIR/startup.log 2>&1 &

echo "PC Server started. PID: $!"
📱
prd-mo-server-template — User Data (MO 서버용)
PC와 다른 jar를 실행하는 것이 핵심
⚡ User Data — prd-mo-server-template bash
#!/bin/bash
DEPLOY_BRANCH="PRD_20250101"
S3_BUCKET="myco-deploy"
APP_DIR="/opt/myapp"

yum install -y java-17-amazon-corretto awscli unzip
mkdir -p $APP_DIR

aws s3 cp s3://${S3_BUCKET}/${DEPLOY_BRANCH}/deploy.zip $APP_DIR/deploy.zip
unzip -o $APP_DIR/deploy.zip -d $APP_DIR/

# ↓ PC 서버와 다른 점: app-mo.jar 실행 + MO 전용 포트
java -jar $APP_DIR/app-mo.jar \
  --spring.profiles.active=prod \
  --server.port=8081 \
  > /var/log/myapp/mo-server.log 2>&1 &
🪣
S3 버킷 구조
s3://myco-deploy/
☁ s3://myco-deploy/ S3
s3://myco-deploy/
├── PREV_20241201/            # 이전 프리뷰 (롤백 보관용)
│   └── deploy.zip
├── PREV_20250101/            # 이번 프리뷰 빌드 결과물
│   └── deploy.zip            # ← app-pc.jar + app-mo.jar + app-admin.jar 포함
├── PRD_20241201/             # 이전 운영 (롤백 시 재사용 가능)
│   └── deploy.zip
└── PRD_20250101/             # 이번 운영 배포 (PREV와 동일 파일)
    └── deploy.zip            # ← 프리뷰 때 업로드한 파일 그대로 사용
날짜별로 폴더가 나뉘어 있어서, 운영 배포 후 문제가 생기면 이전 버전 경로의 파일로 User Data를 수정하고 EC2를 다시 띄우는 것만으로 롤백이 가능해요.
마지막으로 꼭 구분해야 하는 것
헷갈리기 쉬운 포인트 정리
맞는 이해
Jenkins가 ASG에 배포/교체를 요청한다
ASG가 Launch Template을 참고해 EC2를 만든다
EC2는 부팅하면서 User Data를 실행한다
틀리기 쉬운 이해
User Data가 EC2를 만든다
Launch Template이 혼자 EC2를 만든다
Jenkins가 모든 서버 내부 작업까지 직접 수행한다
🚀
B-SET 스케일 아웃 — 신세트 EC2 생성
현재 A-SET이 운영 중인 상태에서 진행
🔵 A-SET (현재 운영 중)
pc-server × 4대 (구버전 운영)
mo-server × 4대 (구버전 운영)
admin-server × 2대 (구버전 운영)
실 사용자 트래픽 처리 중
배포 중 건드리지 않음
🟣 B-SET (신규 생성 중)
prd-pc-server-template → 4대 생성
prd-mo-server-template → 4대 생성
prd-admin-server-template → 2대 생성
User Data 실행 → S3 다운로드 → 앱 기동
헬스체크 통과 후 트래픽 전환 대기
🔄
절체 — WEB 먼저, WAS 나중에 내리는 이유
순서가 틀리면 사용자에게 에러 발생
WAS 먼저 내리면? 사용자 요청 → Nginx(살아있음) → WAS(죽음) → 502 Bad Gateway 에러가 사용자에게 노출돼요!
WEB 먼저 내리면? Nginx가 꺼지면서 이 서버로 신규 요청이 들어오지 않아요. 이미 처리 중인 요청만 WAS가 마저 처리하고, 그 후 WAS를 내리면 에러 없이 깔끔하게 종료됩니다.
절체 순서 — 구세트 A 순서도
① WEB(Nginx) 종료
   systemctl stop nginx             # A-SET WEB 서버 전체
   → 이 시점부터 신규 요청은 B-SET으로만 감 (ALB가 A제외)

② WAS(Spring Boot 앱) 종료
   systemctl stop app               # A-SET WAS 서버 전체
   → 처리 중이던 요청 graceful shutdown 후 종료

③ ALB 리스너 Target Group 변경
   A-SET Target Group → B-SET Target Group
   → 사용자는 이제 전부 신버전(B) 서비스 이용

④ 배치 작업 완료 대기
   A-SET에서 돌던 스케줄러/배치 Job이 끝날 때까지 EC2는 살려둠
   예: 매일 새벽 2시 정산 배치가 돌고 있다면 그게 끝날 때까지 대기

⑤ A-SET 스케일 인
   aws ec2 terminate-instances --instance-ids i-0abc123... i-0def456...
   → A-SET EC2 전부 종료 (비용 절감)
🔁
A/B-SET 다음 배포 때 역할이 바뀌는 구조
Blue/Green 배포 전략
배포 회차별 A/B 역할 변화 흐름
# 1차 배포
A-SET: 구버전 운영 → 절체 후 종료
B-SET: 신규 생성 → 운영 중

# 2차 배포 (다음 배포)
B-SET: 현재 운영 중 → 절체 후 종료
A-SET: 신규 생성 → 운영 중

# 3차 배포
A-SET: 현재 운영 중 → 절체 후 종료
B-SET: 신규 생성 → 운영 중

# → 이렇게 번갈아 쓰는 전략 = Blue/Green 배포
#   항상 한 쪽이 살아서 트래픽을 받고 있어서 서비스 중단이 없음
Jenkins 스크립트는 스크립트 파일이 있는 깃 브랜치를 "호출"한다는 게 무슨 뜻?
Jenkins Job 설정 화면에 "SCM(소스 코드 관리)" 항목이 있어요. 여기에 GitLab 주소 + 브랜치 + Jenkinsfile 경로가 저장되어 있어요. 배포 담당자가 버튼을 클릭하면 Jenkins가 그 설정을 보고 GitLab에서 Jenkinsfile을 가져와서 실행하는 구조예요.
# Jenkins Job 설정 (UI에서 저장된 값)
Pipeline script from SCM
  SCM: Git
  Repository URL: https://gitlab.mycompany.com/myteam/myrepo.git
  Credentials:    gitlab-jenkins-token
  Branch:         */scripts              ← 스크립트 전용 브랜치
  Script Path:    scripts/Jenkinsfile-prev  ← Jenkinsfile 위치
브랜치(PREV_20250101)는 어떻게 체크아웃 받는 거지? 이것도 Job에 써있나?
맞아요, 전부 Jenkinsfile 안에 명시되어 있어요. Jenkinsfile의 Checkout stage에서 git branch: "${params.BRANCH}" 로 받아오는데, 이 BRANCH 값은 Jenkins 실행 시 파라미터로 입력돼요. 사람이 수동으로 git clone하는 게 아니에요.
운영 배포 때 "스크립트 배포 Job이 실행된다 — 따로 스크립트 작성 없이?"
배포 로직이 이미 Jenkinsfile-prd의 Deploy Stage 안에 작성되어 있어요. 담당자는 버튼 하나만 클릭하면 되고, 그 이후 EC2 생성 → 절체 → ALB 전환까지 자동으로 실행돼요. 새로운 스크립트를 그때그때 작성하는 게 아니라, 미리 만들어둔 스크립트가 파라미터(어떤 브랜치? 몇 세트?) 만 받아서 실행되는 구조예요.
EC2가 S3 파일 받고 부팅하는 것도 다 Job에 써있는 건가?
반반이에요. EC2 생성 명령은 Jenkinsfile에 있고, EC2가 부팅하면서 S3에서 파일 받고 앱을 실행하는 로직은 Launch Template의 User Data에 있어요. Jenkins가 EC2에 직접 SSH 접속해서 명령하는 게 아니라, EC2가 부팅되면서 AWS 인프라가 User Data를 자동으로 실행해줍니다.
각 EC2가 PC/MO/Admin 중 어떤 앱을 받아와야 하는지 어떻게 알지?
Launch Template이 역할별로 따로 있어요. prd-pc-server-template의 User Data에는 app-pc.jar를 받아서 실행하는 스크립트가, prd-mo-server-template에는 app-mo.jar를 받는 스크립트가 각각 저장되어 있어요. EC2는 어떤 Template으로 만들어졌느냐에 따라 역할이 결정돼요.
프리뷰와 운영 배포 스크립트(Jenkinsfile)는 다 다른 건가?
맞아요. 목적이 다르기 때문에 파일 자체가 달라요. 프리뷰(Jenkinsfile-prev)는 빌드 + S3 업로드 + EC2 생성까지 다 포함하고, 운영(Jenkinsfile-prd)은 빌드 없이 이미 S3에 올라간 파일을 쓰면서 EC2 생성 + 절체 + ALB 전환까지 담당해요. 환경 변수(spring.profiles.active)도 preview vs prod로 달라요.
Build & Deploy OJT Guide · Jenkins · GitLab · AWS EC2 · S3 · Launch Template · Blue/Green