blog

Jenkins를 사용하여 AWS ECS Fargate 에 배포하기 (ECR, CodeDeploy, HTTPS, 로드 밸런서)

간단 요약

전제조건

1. 도메인 세팅

2. EC2

3. ECR

4. ECS

5. Code Deploy

6. Jenkins

기타 세부 절차

1. AWS 설정 준비

1.1 AWS IAM Role 생성

1.2 ECR (Elastic Container Registry) 설정

2. Jenkins 구성

2.1 Jenkins 설치 및 플러그인 설정

2.2 AWS 자격 증명 구성

3. HTTPS 인증서 및 도메인 설정

3.1 도메인 설정 (Route 53)

3.2 ACM (AWS Certificate Manager)로 HTTPS 인증서 발급

4. ECS Fargate 및 로드 밸런서 설정

4.1 ECS 클러스터 및 서비스 생성

4.2 로드 밸런서 생성 (ALB)

4.3 ECS 서비스에 ALB 연결

5. CodeDeploy 설정

5.1 CodeDeploy 애플리케이션 및 배포 그룹 생성

6. Jenkins 파이프라인 구성

6.1 Jenkins Pipeline 스크립트 작성

pipeline {
    agent any

    environment {
        // 개발환경 및 서버타입 지정
        DEPLOY_ENV = "dev"
        DEPLOY_ENV = "stage"
        LAUNCH_TYPE = "FARGATE_SPOT"

        DEPLOY_ENV = "prod"
        LAUNCH_TYPE = "FARGATE"

        BUILD_ID = "${env.BUILD_ID}"
        BRANCH = "${params.BRANCH}"

        GIT_CREDENTIAL_ID = "silva"
        GIT_URL = "https://github.com/silva/silva-web.git"
        S3_BUCKET_NAME = "silva-bucket"

        AWS_REGION = "ap-northeast-2"
        ACCOUNT_ID = "123123123123"
        ECR_REPO_URI = "123123123123.dkr.ecr.ap-northeast-2.amazonaws.com"
        ECR_REPO_NAME = "silva-web-${DEPLOY_ENV}"
        CLUSTER_NAME = "silva-web"
        SERVICE_NAME = "silva-web-${DEPLOY_ENV}"
        CODE_DEPLOY_APP_NAME = "silva-web-ecs"
        CODE_DEPLOY_GROUP_NAME = "silva-web-${DEPLOY_ENV}"
    }

    stages {
        stage('AWS ECR Login'){
            steps {
                script {
                    sh "aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REPO_URI}"
                }
            }
        }
        
        stage('Git Clone') {
            steps {
                git branch: BRANCH,
                credentialsId: GIT_CREDENTIAL_ID,
                url: GIT_URL
                
                script {
                    sh "rm -rf .git"
                    sh "cp dotenv/${DEPLOY_ENV}.conf .env"
                    sh "echo \"\nBUILD_ID=${BUILD_ID}\nDEPLOY_ENV=${DEPLOY_ENV}\" >> .env"
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                script {
                    sh "docker build -t ${SERVICE_NAME}:${BUILD_ID} ."
                }
            }
        }
        
        stage('ECR Push') {
            steps {
                script {
                    sh "docker tag ${SERVICE_NAME}:${BUILD_ID} ${ECR_REPO_URI}/${ECR_REPO_NAME}:${BUILD_ID}"
                    sh "docker tag ${SERVICE_NAME}:${BUILD_ID} ${ECR_REPO_URI}/${ECR_REPO_NAME}:latest"
                    sh "docker push ${ECR_REPO_URI}/${ECR_REPO_NAME}:${BUILD_ID}"
                    sh "docker push ${ECR_REPO_URI}/${ECR_REPO_NAME}:latest"
                }
            }
        }
        
        stage('Clean Docker Image') {
            steps {
                script {
                    sh "docker rmi -f ${SERVICE_NAME} ${ECR_REPO_URI}/${ECR_REPO_NAME}:${BUILD_ID}"

                }
            }
        }
        
        stage ('Upload Appspec') {
            steps {
                script {
                    // 최신 태스크 정의를 불러와서 재활용
                    def NEW_TASK_DEFINITION = sh(
                        script: """
                            aws ecs list-task-definitions \
                            --family-prefix ${SERVICE_NAME} \
                            --region ${AWS_REGION} \
                            --sort DESC \
                            --query 'taskDefinitionArns[0]' \
                            --output text
                        """,
                        returnStdout: true
                    ).trim()
                    echo "Latest Task Definition: ${NEW_TASK_DEFINITION}"

                    sh """
                        cat > ${BUILD_ID}.yaml<<-EOF
version: 1
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: ${NEW_TASK_DEFINITION}
        LoadBalancerInfo:
          ContainerName: ${SERVICE_NAME}
          ContainerPort: 80
        CapacityProviderStrategy:
          - CapacityProvider: ${LAUNCH_TYPE}
            Weight: 1
EOF"""
                    sh "aws s3 cp ${BUILD_ID}.yaml s3://${S3_BUCKET_NAME}/${SERVICE_NAME}/appspec/${BUILD_ID}.yaml"
                }
            }
        }

        stage ('Deploy') {
            steps {
                script {
                    sh"""
                        aws deploy create-deployment \
                        --application-name ${CODE_DEPLOY_APP_NAME} \
                        --deployment-group-name ${CODE_DEPLOY_GROUP_NAME} \
                        --region ${AWS_REGION} \
                        --s3-location bucket=${S3_BUCKET_NAME},bundleType=YAML,key=${SERVICE_NAME}/appspec/${BUILD_ID}.yaml \
                        --output json > deployment.json
                    """
                    def DEPLOYMENT = readJSON file: './deployment.json'
                    
                    RESULT = "Pending"
                    
                    while( "$RESULT" != "Succeeded") {
                        sleep(10)
                        
                        RESULT = sh(
                            script:"aws deploy get-deployment \
                            --query \"deploymentInfo.status\" \
                            --region ${AWS_REGION} \
                            --deployment-id ${DEPLOYMENT.deploymentId}",
                            returnStdout: true
                        ).trim().replaceAll("\"", "")
                        
                        echo "${RESULT}"
                        
                        if ("$RESULT" == "Failed") {
                            throw new Exception("CodeDeploy Failed")
                            break
                        }
                        
                        if ("$RESULT" == "Stopped") {
                            throw new Exception("CodeDeploy Stopped")
                            break
                        }
                    }
                }
            }
        }
    }
}

7. 배포 및 테스트