[CICD] React 프로젝트 AWS ECS 배포 with Jenkins, CodeDeploy

이아영·2021년 3월 26일
1

CICD

목록 보기
1/1

Dream Coding에서 React 강의를 들었을 때 만들었던 React 프로젝트(Business Card Maker)가 있다. 이 프로젝트를 AWS ECS에 배포했던 과정을 공유하겠다.

소스와 프로젝트에 대한 설명은 여기(Github)에 들어가면 볼 수 있다.

간단하게 설명하면 React 라이브러리를 사용해서 개발했고 webpack과 babel을 세팅해서 빌드하고 Jenkins와 CodeDeploy를 이용하여 AWS ECS에 배포했다.

React 프로젝트는 처음 배포해봤기 때문에 이 글에서 배포하는 방법이 정석적인 방법이 아닐 수도 있으니 참고 바란다.
그리고 요금의 부담 때문에 이제 Business Card Maker를 내리려고 한다. 이 글을 볼 때는 Business Card Maker에 접속할 수 없다...ㅠ


배포 과정은 다음과 같다.
Git Clone부터 Create Deployment까지가 Jenkins Pipeline이다.

  1. Git Clone : Github에서 소스 코드를 가져온다.
  2. Webpack Build : Webpack으로 소스 코드 빌드
  3. Docker Build : Dockerfile만들고 도커 빌드
  4. Docker Push : AWS ECR에 도커 이미지 푸시하고 Task Definition 업데이트
  5. Upload appspec.yml : appspec.yml파일 만들고 S3에 업로드
  6. Create Deployment : AWS CLI를 이용해서 create-deployment

스스로 Pipeline Script를 작성해보고 싶다면 여기까지 보고 혼자 해봐도 될 것 같다.


1. Git Clone

Jenkins Pipeline 구성에서 DefinitionSCM으로 하고 Git에서 Pipeline script를 가져오도록 구성하면 이미 이 과정에서 Jenkinsfile을 가져오기 위해 Git Clone이 일어난다. 따라서 Pipeline script에서 중복으로 Git Clone을 할 필요는 없다.

2. Webpack Build

Webpack Build는 아주 간단하다. npm installnpm run build를 실행해주면 된다.

stage('Webpack Build') {           
    steps {            
        script {
            try {
                sh "npm install"
                sh "npm run build"
                env.webpackBuildResult = true
                
            } catch(Exception e) {
                print(e)
                cleanWs()
                currentBuild.result = 'FAILURE'
            }
        }
    }
}

3. Docker Build & Push

Webpack 빌드 후 번들 파일 및 필요한 파일들을 server 폴더 아래에 복사하고 Docker Build를 진행한다. (나는 server파일에 app을 구동시키는 express 소스가 있어서 이렇게 했다.)

createDockerfile()에서는 다음과 같은 Dockerfile을 만든다.

FROM node:${NODE_VERSION}
WORKDIR /home/server/
COPY server/. ./
RUN npm install
EXPOSE ${CONTAINER_PORT}
CMD [ "node", "index.js" ]

Dockerfile 내용은 간단하다. 필요한 파일들은 모두 server 폴더 아래에 있기 때문에 server 폴더 내용을 통째로 복제해준다. 그리고 node 명령어를 이용해서 express 코드를 실행해준다.

이렇게 만들어진 Dockerfile을 이용해서 Docker Build를 해주고 이 Docker Image를 ECR에 Push 해준다. 그리고 Push한 Docker Image로 ECS의Task Defifnition을 업데이트 해준다. (생략된 코드들은 Github에서 보길 바란다.)

stage('Dockerizing') {
    when {
        expression {
            return env.webpackBuildResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
        }
    }
    steps {
        script {
            try {

                sh "sudo cp -r public server/"
                sh "sudo cp -r dist server/"
                createDockerfile()

                sh "aws ecr get-login --region ap-northeast-2 --no-include-email | sh"
                sh "docker image build -t ${ECR_URL}:${BUILD_NUMBER} ."
                sh "docker push ${ECR_URL}:${BUILD_NUMBER}"
                sh "docker image rm ${ECR_URL}:${BUILD_NUMBER}"
                updateTaskDefinition()

                env.dockerizingResult = true

            } catch(Exception e) {
                print(e)
                cleanWs()
                currentBuild.result = 'FAILURE'
            }
        }
    }
}

4. Upload appspec.yml & Create Deployment

위에서 업데이트한 Task Definition을 바라보도록 appspec.yml을 생성해서 S3에 업로드하고 이 appspec.yml을 바라보도록 create-deployment를 실행한다.
Deploy가 끝났는지 15초 마다 체크하고 Deploy가 모두 끝났을 때 Pipeline이 종료된다.

stage('Deploy') {
when {
    expression {
        return env.dockerizingResult ==~ /(?i)(Y|YES|T|TRUE|ON|RUN)/
    }
}
steps {
    script {
        try {
            // appspec.yaml 생성 및 s3에 업로드
            createAppspecAndUpload()
            
            def cmd = """
aws deploy create-deployment \
--application-name ${JOB_NAME} \
--deployment-config-name CodeDeployDefault.ECSAllAtOnce \
--deployment-group-name ${DEPLOYMENT_GROUP} \
--s3-location bucket=${S3_BUCKET},key=${JOB_NAME}/${DEPLOYMENT_GROUP}/appspec.yaml,bundleType=YAML | jq '.deploymentId' -r
            """

            def deploymentId = withAWS(credentials:"aws-access-key", region: 'ap-northeast-2') {
                return executeAwsCliByReturn(cmd)
            }
            

            cmd = "aws deploy get-deployment --deployment-id ${deploymentId} | jq '.deploymentInfo.status' -r"
            def result = ""
            timeout(unit: 'SECONDS', time: 600) {
                while ("${result}" != "Succeeded") {
                    if ("${result}" == "Failed") {
                        exit 1
                    }
                    result = withAWS(credentials:"aws-access-key", region: 'ap-northeast-2') {
                        return executeAwsCliByReturn(cmd)
                    }
                    print("${result}")
                    sleep(15)
                }
            }

        } catch(Exception e) {
            print(e)
            cleanWs()
            currentBuild.result = 'FAILURE'
        } finally {
            cleanWs()
        }
    }
}

0개의 댓글