EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 생성합니다.
먼저 설정하기 전에 사용자를 추가하는 것과 역할을 만드는 것은 다르다는 것을 이해할 필요가 있습니다.
IAM 역할이란 무엇입니까?
IAM 역할은 신뢰하는 개체에 권한을 부여하는 안전한 방법입니다. 개체의 예는 다음을 포함합니다.
- 다른 계정의 IAM 사용자
- AWS 리소스에서 작업을 수행해야 하는 EC2 인스턴스에서 실행 중인 애플리케이션 코드
- 계정 내 리소스에서 작업을 수행하여 기능을 제공해야 하는 AWS 서비스
- SAML을 통해 인증 연동을 사용하는 사내 디렉토리의 사용자
IAM 역할은 권한을 부여하는 더욱 안전한 방법으로 짧은 기간 동안 유효한 키를 발행합니다.
지금 만들 권한은 EC2에서 사용할 것이기 때문에 사용자가 아닌 역할로 처리합니다.
EC2RoleForA정도까지 검색하여 AmazonEC2RoleforAWS-CodeDeploy를 선택합니다.
본인이 원하는 이름으로 짓습니다.
이름은 어떤 역할인지 알아보기 쉽게 짓습니다.
해당 EC2 인스턴스 설정에서 [IAM 역할 연결/바꾸기]를 선택합니다.
생성한 역할을 선택합니다.
역할을 선택한 후 해당 인스턴스를 재부팅해야 역할이 정상적으로 작동합니다.
재부팅이 완료되었으면 CodeDeploy의 요청을 받을 수 있에 에이전트를 설치해야합니다.
Amazon Linux 2 AMI (centos)환경에서 작업하였습니다.
EC2에 접속해서 다음 명령어를 입력합니다.
$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
다음과 같은 메시지가 콘솔창에 출력됩니다.
download: s3://aws-codedeploy-ap-northeast-2/latest/install to ./install
install 파일에 실행 권한을 추가하고 설치를 진행하는데, 설치가 안된다면 ruby를 설치하시면 됩니다.
chmod +x ./install && sudo ./install auto
Agent가 정상적으로 실행되고 있는지 상태 검사를 합니다.
다음과 같이 나오면 정상입니다.
$ sudo service codedeploy-agent status
The AWS CodeDeploy agent is running as PID 3530
CodeDeploy에서 EC2에 접근하려면 마찬가지로 권한이 필요합니다.
AWS의 서비스이니 조금전과 마찬가지로 IAM역할을 생성합니다.
CodeDeploy는 권한이 하나뿐이라서 선택 없이 바로 다음으로 넘어가면 됩니다.
여기까지 진행하면 IAM의 역할에 두 역할이 생성됩니다.
CodeDeploy는 AWS의 배포 삼형제 중 하나입니다.
현재 프로젝트에서 Commit은 github, build는 Circle CI 또는 Travis CI가 하고 있기 때문에 추가로 사용할 서비스는 CodeDeploy입니다.
CodeDeploy서비스로 이동해서 [애플리케이션 생성] 버튼을 클릭하여 CodeDeploy의 이름과 컴퓨팅 플랫폼을 선택합니다.
컴퓨팅 플랫폼에선 [EC2/온프레미스]를 선택합니다.
생성이 완료되면 배포 그룹을 생성하라는 메시지를 볼 수 있습니다. 배포 그룹을 생성할 때 서비스 역할은 좀 전에 생성한 CodeDeploy용 IAM역할을 선택합니다.
배포 유형에서는 1대의 EC2에만 배포하므로 현재 위치를 선택합니다. 배포할 서비스가 2대 이상이라면 블루/그린을 선택하면 됩니다.
환경 구성은 [Amazon EC2 인스턴스]에 체크합니다.
배포 설정과 로드밸런서를 선택합니다. 배포 구성이란 한번 배포할 때 몇 대의 서버에 배포할지를 결정합니다.
2대 이상이라면 1대씩 배포할지, 30% 혹은 50%로 나눠서 배포할지 등등 여러 옵션이 있지만, 1대 서버다 보니 전체 배포하는 옵션으로 선택합니다. CodeDeployDefaultAllAtOnce는 한 번에 다 배포하는 것을 의미합니다.
먼저 S3에서 넘겨 줄 zip파일을 저장할 디렉토리를 하나 생성하겠습니다. EC2서버에 접속해서 다음과 같이 디렉토리를 생성합니다.
mkdir ~/app/step2 && mkdir ~/app/step2/zip
CIrcle CI의 Build가 끝나면 S3에 zip파일이 전송되고, 이 zip파일은 /home/ec2-user/app/setp2/zip로 복사되어 압축을 풀 예정입니다. AWS CodeDeploy의 설정은 appspec.yml로 진행합니다.
# appspec.yml
version: 0.0 # CodeDeploy 버전
os: linux
files:
- source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
destination: /home/ec2-user/app/step2/zip/ # source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
overwrite: yes
.circleci 디렉토리의 config.yml파일도 deploy부분에 CodeDeploy내용을 추가합니다.
version: 2.1
orbs:
aws-s3: circleci/aws-s3@1.0.13
aws-code-deploy: circleci/aws-code-deploy@0.0.12
jobs:
build:
# OOM오류를 피하기 위해 JVM 및 Gradle을 구성
environment:
_JAVA_OPTIONS: "-Xmx3g" # Heap memory
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"
docker:
- image: circleci/openjdk:8-jdk
working_directory: ~/springboot2-webservice
steps:
# workflow에서 트리거된 git branch를 checkout
- checkout
# Download and cache depen에dencies, keys에 저장된 키로 캐시에 저장된 내용을 복사
- restore_cache:
keys:
- v1-dependencies-{{ checksum "build.gradle" }}
- run:
name: Downloading Dependencies
command: ./gradlew dependencies
- save_cache:
paths:
- ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle" }} # build.gradle 파일에서 무언가가 변경 될 때마다 캐시가 재생성되며,이 프로젝트의 다른 브랜치는 동일한 캐시 키를 생성합니다.
- run: ./gradlew clean build
deploy:
docker:
- image: circleci/python:2.7
steps:
- checkout
- run:
name: before_deploy
command: |
zip -r springboot2-webservice *
mkdir -p deploy
mv springboot2-webservice.zip deploy/springboot2-webservice.zip
- aws-s3/copy:
from: deploy/*
to: 's3://hwany-springboot-build'
aws-region: AWS_DEFAULT_REGION
- aws-code-deploy/deploy-bundle:
application-name: hwany-springboot2-webservice
deployment-group: hwany-springboot2-webservice-group
deployment-config: CodeDeployDefault.AllAtOnce
bundle-bucket: hwany-springboot-build
bundle-key: springboot2-webservice # Travis CI와 다르게 확장자는 제거해야 합니다.
bundle-type: zip # default
workflows:
version: 2.1
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: master
commit하고 push를하면 Circle CI에서 build와 deploy를 수행합니다.
Circle CI가 끝나면 CodeDeploy화면 아래에서 배포가 수행된 것을 확인할 수 있습니다.
배포가 끝났다면 파일들이 잘 도착했는지 확인합니다.
$ cd app/step2/zip/
$ ll
합계 28
-rw-r--r-- 1 root root 458 1월 26 01:19 README.md
-rw-r--r-- 1 root root 374 1월 26 01:19 appspec.yml
-rw-r--r-- 1 root root 2327 1월 26 01:19 build.gradle
drwxr-xr-x 3 root root 21 1월 26 01:19 gradle
-rwxr-xr-x 1 root root 5305 1월 26 01:19 gradlew
-rw-r--r-- 1 root root 2269 1월 26 01:19 gradlew.bat
-rw-r--r-- 1 root root 45 1월 26 01:19 settings.gradle
drwxr-xr-x 4 root root 30 1월 26 01:19 src
들어온 것을 확인하니 잘 연동이 되는 줄 알았지만 문제가 발생했습니다!
build job에서 빌드 한 build디렉토리가 포함되지 않는 것이 문제였습니다. build와 deploy는 서로 다른 docker환경에서 실행되기 때문에 단순히 deploy에서 check out 후에 빌드하지 않고 zip으로 압축하는게 전부였습니다. 딱히 병렬로 수행할 작업이 없기 때문에 아래 그림처럼 BUILD JOB과 DEPLOY JOB을 같은 환경으로 묶어주기로 하였습니다.
코드를 다음과 같이 수정하였습니다.
version: 2.1
orbs:
aws-s3: circleci/aws-s3@1.0.13
aws-code-deploy: circleci/aws-code-deploy@0.0.12
executors:
my-executor:
docker:
- image: circleci/openjdk:8-jdk
working_directory: ~/springboot2-webservice
jobs:
build:
executor: my-executor
# OOM오류를 피하기 위해 JVM 및 Gradle을 구성
environment:
_JAVA_OPTIONS: "-Xmx3g" # Heap memory
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"
steps:
# workflow에서 트리거된 git branch를 checkout
- checkout
# Download and cache depen에dencies, keys에 저장된 키로 캐시에 저장된 내용을 복사
- restore_cache:
keys:
- v1-dependencies-{{ checksum "build.gradle" }}
- run:
name: Downloading Dependencies
command: ./gradlew dependencies
- save_cache:
paths:
- ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle" }} # build.gradle 파일에서 무언가가 변경 될 때마다 캐시가 재생성되며,이 프로젝트의 다른 브랜치는 동일한 캐시 키를 생성합니다.
- run: ./gradlew clean build
- run:
name: before_deploy
command: |
zip -r springboot2-webservice *
mkdir -p deploy
mv springboot2-webservice.zip deploy/springboot2-webservice.zip
- persist_to_workspace:
root: .
paths: deploy
deploy:
executor: my-executor
steps:
- attach_workspace:
at: .
- aws-s3/copy:
from: deploy/*
to: 's3://hwany-springboot-build'
aws-region: AWS_DEFAULT_REGION
- aws-code-deploy/deploy-bundle:
application-name: hwany-springboot2-webservice
deployment-group: hwany-springboot2-webservice-group
deployment-config: CodeDeployDefault.AllAtOnce
bundle-bucket: hwany-springboot-build
bundle-key: springboot2-webservice # 확장자 제거해야 함
bundle-type: zip # default
workflows:
version: 2.1
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: master
합계 28
drwxrwxr-x 6 ec2-user ec2-user 171 1월 26 22:41 .
drwxrwxr-x 3 ec2-user ec2-user 17 1월 25 20:29 ..
-rw-r--r-- 1 root root 442 1월 26 22:41 README.md
-rw-r--r-- 1 root root 374 1월 26 22:41 appspec.yml
drwxr-xr-x 8 root root 96 1월 26 22:41 build
-rw-r--r-- 1 root root 2327 1월 26 22:41 build.gradle
drwxr-xr-x 3 root root 21 1월 26 22:41 gradle
-rwxr-xr-x 1 root root 5305 1월 26 22:41 gradlew
-rw-r--r-- 1 root root 2269 1월 26 22:41 gradlew.bat
-rw-r--r-- 1 root root 45 1월 26 22:41 settings.gradle
drwxr-xr-x 4 root root 30 1월 26 22:41 src
이렇게 해서 Circle CI와 S3, CodeDeploy가 연동이 완료되었습니다!
Circle CI, S3, CodeDeploy연동이 완료되었다면, 이것을 기반으로 실제로 Jar를 배포하여 실행까지 해보겠습니다.
먼저 step2환경에서 실행 될 deploy.sh를 생성하겠습니다.
step1까지는 서버에 저장하였지만 step2부터는 프로젝트에 있어야 합니다. Circle CI는 외부 서비스이니 EC2 서버에 직접 접근이 안됩니다. script 디렉토리를 만들고 deploy.sh를 포함시키면 됩니다.
step1의 deploy.sh와 다른 점은 Circle CI로 배포를 하면 다시 받아 빌드를 할 필요가 없기 때문에 git pull로 직접 빌드 했던 부분을 제거하였습니다.
#!/bin/bash
REPOSITORY = /home/ec2-user/app/step2
PROJECT_NAME = springboot2-webservice
echo "> Build 파일 복사"
cp $REPOSITORY/zip/build/libs/*.jar $REPOSITORY/
echo "> 현재 구동 중인 애플리케이션 pid 확인"
# 실행 중이면 종료하기 위해서 현재 수행 중인 프로세스id를 찾습니다.
# springboot2-webservice으로 된 다른 프로그램들이 있을 수 있어 springboot2-webservice된 jar 프로세스를 찾은 뒤 id를 찾습니다(awk '{print $1}').
CURRENT_PID = $(pgrep -fl springboot2-webservice | grep java | awk '{print $1}')
echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
ehco "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
echo "> 새 애플리케이션 배포"
JAR_NAME = $(ls -tr $REPOSITORY/*.jar | tail -n 1)
echo "> JAR name: JAR_NAME"
echo "> $JAR_NAME에 실행 권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"
nohup java -jar \
-Dspring.config.location=classpath:/application.properties,classpath:/application-real.properties,/home/ec2-user/app/application-oauth.properties,/home/ec2-user/app/application-real-db.properties \
-Dspring.profiles.active=real \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 & # nohup실행 시 CodeDeploy는 무한 대기 합니다. 이 이슈를 해결하기 위해 nohub.out파일을 표준 입출력용으로 별도로 사용합니다. 이렇게 하지 않으면 nohup.out파일이 생기지 않고, CodeDeploy 로그에 표준 입출력이 출력됩니다. nohub이 끝나기 전까지 CodeDeploy도 끝나지 않으니 꼭 이렇게 해야합니다.
현재까지는 프로젝트의 모든 파일을 zip파일로 만드는데, 실제로 필요한 파일들은 Jar, appspec, yml, 배포 scipt들입니다.
...(생략)...
jobs:
build:
.....(생략).....
- run:
name: before_deploy
command: |
tar cvzf springboot2-webservice.tgz scripts/*.sh appspec.yml build/libs/*.jar
- persist_to_workspace:
root: .
paths: .
deploy:
executor: my-executor
steps:
- attach_workspace:
at: .
- aws-s3/copy:
from: springboot2-webservice.tgz
to: 's3://hwany-springboot-build'
aws-region: AWS_DEFAULT_REGION
- aws-code-deploy/deploy-bundle:
application-name: hwany-springboot2-webservice
deployment-group: hwany-springboot2-webservice-group
deployment-config: CodeDeployDefault.AllAtOnce
bundle-bucket: hwany-springboot-build
bundle-key: springboot2-webservice # 확장자 제거해야 함
bundle-type: tgz
workflows:
.....(생략).....
Travis CI와 다르게 디렉토리없이 파일만 업로드 가능하기 때문에 파일만 업로드 하도록 수정하고, code-deploy부문에서 bundle-type은 tar.gz대신 tgz를 사용해야 동작합니다.
appspec.yml 파일에 다음 코드를 추가합니다. location, timeout, runas의 들여쓰기를 주의해야 합니다. 들여쓰기가 잘못될 경우 배포가 실패합니다.
version: 0.0 # CodeDeploy 버전
os: linux
files:
- source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
destination: /home/ec2-user/app/step2/zip/ # source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
overwrite: yes
permissions: # CodeDeploy에서 EC2서버로 넘겨준 파일들을 모두 ec2-user권한을 갖도록 합니다.
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
hooks: # CodeDeploy배포 단계에서 실행할 명령어를 지정합니다.
ApplicationStart: # deploy.sh를 ec2-user권한으로 실행합니다.
- location: scripts/deploy.sh
timeout: 60 # 스크립트 실행 60초 이상 수행되면 실패가 됩니다.
runas: ec2-user
여기까지 진행하면 배포에 성공한 것을 확인할 수 있습니다.
build.gradle에서 프로젝트 버전을 변경합니다.
version '1.0.1-SNAPSHOT'
변경된 내용을 확인할 수 있게 index.mustache의 내용을 바꾸고, push를 하면 아래와 같이 변경된 코드가 배포된 것을 확인할 수 있습니다.
CodeDeploy에 관한 내용은 대부분 /opt/codedeploy-agent/deployment-root
에 있습니다.
/opt/codedeploy-agent/deployment-root/deployment-group-ID/deployment-ID/logs/scripts.log
이 곳의 로그를 확인하면 아래와 같이 확인할 수 있습니다.
2020-01-27 05:22:00 LifecycleEvent - ApplicationStart
2020-01-27 05:22:00 Script - scripts/deploy.sh
2020-01-27 05:22:00 [stdout]> Build 파일 복사
2020-01-27 05:22:00 [stdout]> 현재 구동 중인 애플리케이션 pid 확인
2020-01-27 05:22:00 [stdout]현재 구동 중인 애플리케이션 pid: 16658
2020-01-27 05:22:00 [stdout]> kill -15 16658
2020-01-27 05:22:05 [stdout]> 새 애플리케이션 배포
2020-01-27 05:22:05 [stdout]> JAR name: /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar
2020-01-27 05:22:05 [stdout]> /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar 에 실행 권한 추가
2020-01-27 05:22:05 [stdout]> /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar 실행
*참고
https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
Very wonderful. Your post presented me with a wealth of information, allowing me to broaden my horizons. Thank you very much
Five Nights At Freddy's