본문 바로가기

CICD/ECS

ECS CICD Automation - 5. Jenkins Job (Blue/Green)

Blue Green 배포형태의 서비스를 만들어 보도록 하겠습니다.

 

먼저, CodeDeployRole에 다음 정책을 추가해줍니다.

 

 

이후 블루그린 배포는 2개의 타겟그룹을 가지고 있어야 하기 때문에, 한개 더 동일한 세팅의 타겟그룹을 만들어줍니다.

 

 

 

ALB 리스너는 그대로 하나만 바라봐야합니다.

 


 

자 이제 Service를 만들어보도록 하겠습니다.

앞서 만든 두개의 타겟그룹을 선택해주면 되겠습니다.

 

 

 

만들어진 코드 디플로이 배포그룹에 들어가서 대기시간을 수정해줍니다.

 

 


 

그리고 젠킨스에서 롤링잡을 카피해서 만들어 주신 후

 

서비스 변수와 전체 스크립트만 바꿔서 진행해주시면 됩니다.

아래 코드에서 깃 repository와 task definition에 있는 container 이름은 꼭 맞춰주시기 바랍니다.

 

pipeline {
    agent any
    stages {
        stage('Git Clone') {
            steps {
                script {
                    try {
                        git url: "https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/cb-test-api", branch: "master", credentialsId: "$GIT_CREDENTIALS_ID"
                        sh "sudo rm -rf ./.git"
                        env.cloneResult=true
                    } catch (error) {
                        print(error)
                        env.cloneResult=false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
        }
        stage('Build JAR') {
            when {
                expression {
                    return env.cloneResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
                }                
            }
            steps {
                script{
                    try {
                        sh """
                        rm -rf deploy
                        mkdir deploy
                        """ 
                        sh "sudo sed -i \"s/module_name=.*/module_name=${env.JOB_NAME}\\:${env.BUILD_NUMBER}/g\" /var/lib/jenkins/workspace/${env.JOB_NAME}/src/main/resources/application.properties"
                        sh "cat /var/lib/jenkins/workspace/${env.JOB_NAME}/src/main/resources/application.properties"
                        sh 'mvn -Dmaven.test.failure.ignore=true clean install'
                        sh """
                        cd deploy
                        cp /var/lib/jenkins/workspace/${env.JOB_NAME}/target/*.jar ./${env.JOB_NAME}.jar
                        """
                        env.mavenBuildResult=true
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.mavenBuildResult=false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    slackSend channel: '#pipeline-deploy', color: 'good', message: "The pipeline ${currentBuild.fullDisplayName} stage Build JAR successfully."
                }
                failure {
                    slackSend channel: '#pipeline-deploy', color: 'danger', message: "The pipeline ${currentBuild.fullDisplayName} stage Build JAR failed."
                }
            }
        }
        stage('Docker Build'){
            when {
                expression {
                    return env.mavenBuildResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
                }
            }
            steps {
                script{
                    try {
                        sh"""
                        #!/bin/bash
                        cd ./deploy
                        cat>Dockerfile<<-EOF
FROM ${ECR_BASE_URL}:latest
ADD ${env.JOB_NAME}.jar /home/${env.JOB_NAME}.jar
CMD nohup java -jar /home/${env.JOB_NAME}.jar 1> /dev/null 2>&1
EXPOSE 9000
EOF"""
                        sh"""
                        cd ./deploy
                        docker rmi -f \$(docker images -q)
                        \$(aws ecr get-login --no-include-email --region ap-northeast-2)
                        docker build -t ${SERVICE_NAME.toLowerCase()} .
                        docker tag ${SERVICE_NAME.toLowerCase()}:latest ${ECR_TASK_URL}:ver${env.BUILD_NUMBER}
                        docker push ${ECR_TASK_URL}:ver${env.BUILD_NUMBER}
                        """
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.dockerBuildResult=true
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.dockerBuildResult=false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    slackSend channel: '#jenkins', color: 'good', message: "The pipeline ${currentBuild.fullDisplayName} stage Docker Build successfully."
                }
                failure {
                    slackSend channel: '#jenkins', color: 'danger', message: "The pipeline ${currentBuild.fullDisplayName} stage Docker Build failed."
                }
            }
        }
        stage('Create Task Definition'){
            when {
                expression {
                    return env.dockerBuildResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
                }
            }
            steps {
                script{
                    try {
                        withAWS(credentials:"$AWS_CREDENTIALS") {
                            sh "aws ecs describe-task-definition --task-definition ${TASK_DEFINITION} --region ap-northeast-2 --query \"taskDefinition.{\"family\": family, \"containerDefinitions\": containerDefinitions, \"executionRoleArn\": executionRoleArn,\"requiresCompatibilities\": requiresCompatibilities}\" --output json > task-definition.json"
                            def task_repository_name = sh(
                                    script:"""
                                    echo \"${ECR_TASK_URL}\" | awk \'{ split(\$0, arr, \"/\"); print arr[2] }\'
                                    """,
                                    returnStdout: true
                                ).trim()
                            sh "sudo sed -i \"s/${task_repository_name}:.*/${task_repository_name}:ver${env.BUILD_NUMBER}\\\",/g\" task-definition.json"
                            sh "cat task-definition.json"
                            sh "aws ecs register-task-definition --cli-input-json file://task-definition.json --region ap-northeast-2"
                        }
                        env.serviceUpdateResult = true
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.serviceUpdateResult = false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    slackSend channel: '#jenkins', color: 'good', message: "The pipeline ${currentBuild.fullDisplayName} stage create task definition successfully."
                }
                failure {
                    slackSend channel: '#jenkins', color: 'danger', message: "The pipeline ${currentBuild.fullDisplayName} stage create task definition failed."
                }
            }
        }
        stage('Appspec Upload'){
            when {
                expression {
                    return env.serviceUpdateResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
                }
            }
            steps {
                script{
                    try {
                        def task_arn = sh(
                            script:"aws ecs describe-task-definition --task-definition ${TASK_DEFINITION} --region ap-northeast-2 --query \"taskDefinition.taskDefinitionArn\"",
                            returnStdout: true
                        ).trim()
                        echo "${task_arn}"
                        sh"""
                        #!/bin/bash
                        mkdir appspec
                        cd ./appspec
                        cat>appspec.yaml<<-EOF
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: ${task_arn}
        LoadBalancerInfo:
          ContainerName: "cb-test-api"
          ContainerPort: 9000
EOF"""
                        withAWS(credentials:"$AWS_CREDENTIALS") {
                            s3Upload(path:"${env.JOB_NAME}/${env.BUILD_NUMBER}/", file:"/var/lib/jenkins/workspace/${env.JOB_NAME}/appspec/appspec.yaml", bucket:'cb-test-deploy')
                            env.uploadResult=true
                        }
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.uploadResult=true
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        env.uploadResult=false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    slackSend channel: '#jenkins', color: 'good', message: "The pipeline ${currentBuild.fullDisplayName} stage Appspec Upload successfully."
                }
                failure {
                    slackSend channel: '#jenkins', color: 'danger', message: "The pipeline ${currentBuild.fullDisplayName} stage Appspec Upload failed."
                }
            }
        }
        stage('Deploy'){
            when {
                expression {
                    return env.uploadResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
                }
            }
            steps {
                script{
                    try {
                        withAWS(credentials:"$AWS_CREDENTIALS") {
                            sh"""
                                aws deploy create-deployment \
                                --application-name AppECS-${CLUSTER_NAME}-${SERVICE_NAME} \
                                --deployment-group-name DgpECS-${CLUSTER_NAME}-${SERVICE_NAME} \
                                --region ap-northeast-2 \
                                --s3-location bucket=cb-test-deploy,bundleType=YAML,key=${env.JOB_NAME}/${env.BUILD_NUMBER}/appspec.yaml \
                                --output json > DEPLOYMENT_ID.json
                            """
                        }
                        def DEPLOYMENT_ID = readJSON file: './DEPLOYMENT_ID.json'
                        echo "${DEPLOYMENT_ID.deploymentId}"
                        sh "rm -rf ./DEPLOYMENT_ID.json"
                        def DEPLOYMENT_RESULT = ""
                        while("$DEPLOYMENT_RESULT" != "\"Succeeded\"") {
                            DEPLOYMENT_RESULT = withAWS(credentials:"$AWS_CREDENTIALS") {
                                sh(
                                    script:"aws deploy get-deployment \
                                    --query \"deploymentInfo.status\" \
                                    --region ap-northeast-2 \
                                    --deployment-id ${DEPLOYMENT_ID.deploymentId}",
                                    returnStdout: true
                                ).trim()
                            }
                            echo "$DEPLOYMENT_RESULT"
                            if ("$DEPLOYMENT_RESULT" == "\"Failed\"") {
                                throw new Exception("CodeDeploy Failed")
                                break
                            }
                            sleep(15)
                        }
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    slackSend channel: '#jenkins', color: 'good', message: "The pipeline ${currentBuild.fullDisplayName} successfully."
                }
                failure {
                    slackSend channel: '#jenkins', color: 'danger', message: "The pipeline ${currentBuild.fullDisplayName} failed."
                }
            }
        }
    }
}

 

컨테이너가 정상적으로 하나 더 뜨고,

 

 

ALB 리스너 또한 모두 TG2로 바뀐것을 볼 수 있습니다.

 

 

URL로 페이지 접근 시 배포가 정상적으로 수행 된 것을 볼 수 있습니다.

 

 

ECS Blue/Green 배포 자동화에 대해 알아봤습니다.

 

다음 포스트에서는 ECS Fargate 자동화 배포에 대해서 알아보도록 하겠습니다.