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이다.
Git Clone
: Github에서 소스 코드를 가져온다.Webpack Build
: Webpack으로 소스 코드 빌드Docker Build
:Dockerfile
만들고 도커 빌드Docker Push
: AWS ECR에 도커 이미지 푸시하고Task Definition
업데이트Upload appspec.yml
:appspec.yml
파일 만들고 S3에 업로드Create Deployment
: AWS CLI를 이용해서create-deployment
스스로 Pipeline Script를 작성해보고 싶다면 여기까지 보고 혼자 해봐도 될 것 같다.
Jenkins Pipeline 구성에서 Definition
을 SCM
으로 하고 Git
에서 Pipeline script를 가져오도록 구성하면 이미 이 과정에서 Jenkinsfile을 가져오기 위해 Git Clone
이 일어난다. 따라서 Pipeline script에서 중복으로 Git Clone
을 할 필요는 없다.
Webpack Build는 아주 간단하다. npm install
및 npm 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'
}
}
}
}
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'
}
}
}
}
위에서 업데이트한 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()
}
}
}