▪️코드가 푸시되면 자동으로 배포해 보자
CI는 continuous integration의 약자이다. 번역하자면 '끊임없는 병합'이라고 할 수 있겠다.
CI란, Git와 같은 코드 버전 관리를 하는 시스템에 push를 하면, 자동으로 테스트와 빌드가 수행되도록 하여, 안정적인 배포 파일을 만드는 과정을 의미한다.
CD는, continuous deployment의 약자이다. '끊임없는 배포' 라고 할 수 있다. 즉, CI를 통해 얻은 빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정을 CD라고 한다.
https://travis-ci.com으로 접속하고 github 아이디로 로그인한다.
상단의 계정명에 setting을 클릭 , 활성화할 저장소를 클릭한다.
build.gradle과 같은 디렉토리에 .travis.yml을 생성한 후, 아래와 같이 입력하자.
language: java
jdk:
- openjdk17
branches:
only:
- main
before_install:
- chmod +x gradlew
# Travis CI 서버의 Home
cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
script:
- ./gradlew clean build
# CI 실행 완료 시 메일로 알림
notifications:
email:
recipients:
- 'toyu7870@naver.com'
branches : Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정
cache : 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정
script : master 브랜치에 푸시 되었을 때 수행하는 명령어, 프로젝트 내부에 둔 gradlew를 통해 clean & build를 수행함
notifications : Travis CI 실행 완료 시 자동으로 알람이 가도록 설정
빌드가 성공하면 아래 확인이 가능하다.
아래와 같이 Passed 메일이 온 것을 확인 가능하다.
▪️Travis CI와 AWS S3 연동하기
S3는 AWS에서 제공하는 일종의 파일 서버이다.
첫 번째 단계로 Travis CI와 S3를 연동해보자. 실제 배포는 AWS Code Deploy라는 서비스를 이용한다.
S3연동이 먼저 필요한 이유는 Jar 파일을 전달하기 위해서이다.
CodeDeploy는 저장 기능이 없다. 그래서 Travis CI가 빌드한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 보관할 수 있는 공간이 필요하다.보통은 이럴 때 AWS S3를 이용한다.
AWS Key 발급
일반적으로 AWS 서비스에 외부 서비스가 접근할 수 없다.그러므로 접근 가능한 권한을 가진 Key를 생성해서 사용해야 한다.
IAM을 통해 Travis CI가 AWS S3와 CodeDeploy에 접근할 수 있도록 해보자.
AWS에서 IAM검색후 [사용자->사용자 추가] 버튼을 차례로 클릭한다.
액세스 유형은 [프로그래밍 방식 액세스] 이다.
권한 설정 방식은 [기존 정책 직접 연결] 을 선택한다
정책 필터 칸에서 s3full을 검색하고 AmazonS3FullAccess를 선택, CodeDeployFull을 검색하여 체크해 준다.
실제 서비스 회사에서는 S3과 CodeDeploy 권한을 분리해서 관리하기도 하지만, 여기서는 함께 관리하기로 한다.
태그 추가에서는 간단한 Name 값을 지정한다. 알아볼 수 있는 이름 정도면 충분하다.
최종 사용자 생성을 누르면, 액세스 키와 비밀 키가 발급된다. 이 키를 Travis CI에서 사용할 것이다.
Travis CI에 키 등록
Travis CI를 열고 설정 화면으로 들어간다.
설정 화면을 아래로 조금 내려보면 Environment Variables 항목이 있다.
여기에 이제 access키, secret키를 등록해야 한다.
AWS_ACCESS_KEY,AWS_SECRET_KEY를 변수로 해서 IAM 사용자에서 발급받은 키 값들을 등록해야 한다.
여기에 등록된 값들은 이제 .travis.yml에서 $AWS_ACCESS_KEY, $AWS_SECRET_KEY란 이름으로 사용할 수 있다.
이제 이 키를 사용해서 Jar를 관리할 S3 버킷을 생성해보자.
▪️S3 버킷 생성
S3는 AWS에서 Travis CI에서 생성된 Build 파일을 저장하도록 구성하자.
S3는 일종의 파일 서버이다. 보통 게시물을 쓸 때 나오는 첨부파일 등록을 구현할 때 많이 이용한다.
S3에 Travis CI에서 생성된 Build 파일을 저장하도록 구성해보자.
S3에 저장된 Build 파일을 이후 Code Deploy에서 배포할 파일로 가져가도록 구성할 예정이다.
버킷에는 배포할 Zip 파일이 모여있다.
버킷 이름 설정 -> 모든 퍼블릭 액섹스 차단으로 설정 후 생성한다.
Jar 파일이 퍼블릭일 경우 누구나 내려받을 수 있어 코드나 설정값, 주요 키값들이 다 탈취될 수 있기 때문이다.
그 후 .travis.yml에 아래 코드를 추가한다.
before_deploy:
- zip -r springboot-webservice3-project *
- mkdir -p deploy
- mv springboot-webservice3-project.zip deploy/springboot-webservice3-project.zip *
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
bucket: freelec-springboot-build-hj
region: ap-northeast-2
skip_cleanup: true
acl: private
local_dir: deploy
wait-until-deployed: true
on:
branch: main
before_deploy : deploy 명령어가 실행되기 전 수행된다. CodeDeploy는 Jar 파일은 인식하지 못하므로 Jar+기타 설정파일들을 모아 압축(zip) 한다.
zip -r freelec-springboot2-webservice : 현재 위치의 모든파일을 해당이름으로 압축한다. 명령어의 마지막 위치는 본인 프로젝트 이름이어야 한다.
mkdir -p deploy : deploy라는 디렉토리를 Travis CI가 실행중인 위치에서 생성한다.
mv ~ : zip 파일을 이동시킨다.
deploy : S3로 파일 업로드 혹은 CodeDeploy로 배포등 외부 서비스와 연동될 행위들을 선언한다.
local_dir : 앞에서 생성한 depoy 디렉토리를 지정한다. 해당 위치의 파일들만 S3로 전송한다.
이제 빌드가 성공하면, S3버킷으로가본다.
업로드가 성공한 것을 확인할 수 있다.
Travis CI를 통해 자동으로 파일이 올려진 것을 확인 할 수 있다.
▪️Travis CI와 AWS S3, CodeDeploy 연동하기
배포 대상인 EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 하나 생성하자.
IAM -> 역할 -> 역할 만들기
앞에서 만들었던 IAM의 사용자와 역할의 차이
- 사용자 : AWS 서비스 외에 사용할 수 있는 권한, 로컬PC 등
- 역할 : AWS 서비스에만 할당할 수 있는 권한, EC2 등
지금 만들 권한은 EC2에서 사용할 것이기 때문에 사용자가 아닌 역할로 처리
정책에서 AmazonEC2RoleforAWSCodeDeploy를 선택한다.
이렇게 만든 역할을 EC2 서비스에 등록해보자.
EC2 인스턴스 목록으로 이동 뒤 본인 인스턴스를 마우스 오른쪽 버튼으로 눌러
[인스턴스 보안->IAM 역할 연결/바꾸기]를 선택 후, 방금 생성한 역할을 선택한다.
그 후 해당 EC2 인스턴스를 재부팅한다.
▪️CodeDeploy 에이전트 설치
그 후 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 파일에 실행 권한 추가
chmod +x ./install
install 파일로 설치 진행
sudo ./install auto
설치중 ruby:No such file or directory 와 같은 에러가 발생한다면 sudo yum install ruby로 루비를 설치하면 된다!
▪️CodeDeploy 권한 생성
▪️CodeDeploy 생성
이제, CodeDeploy를 생성할 차례이다. CodeDeploy는 AWS의 배포 시리즈 3개 중 하나이다.
- Code Commit
- GitHub의 코드 저장소와 같은 역할.
- 프라이빗 기능을 지원한다는 강점이 있지만, 현재 깃허브에서 무료로 프라이빗 지원을 하고 있어서 거의 사용되지 않는다.
- Code Build
- Travis CI와 같은 빌드용 서비스
- 멀티 모듈을 배포해야 하는 경우 사용해 볼만하지만, 규모가 있는 서비스에서는 대부분 젠킨스/팀시티 등을 이용하니 이것은 사용할 일이 거의 없다.
- Code Deploy
- AWS의 배포 서비스
- 다른 대체제가 없다.
- 오토 스케일링 그룹 배포, 블루 그린 배포, 롤링 배포, EC2 단독 배포 등 많은 기능을 지원한다.
여기서 코드 커밋 역할은 깃허브가, 코드 빌드의 역할은 Travis CI가 하고있다. 우리는 추가로 CodeDeploy 서비스를 사용한다.
CodeDeploy 서비스로 이동해서 [애플리케이션 생성] 버튼 클릭.
아래와같이 이름 입력, 플랫폼 선택.
[배포 그룹 생성] 버튼 클릭 > CodeDeploy용 IAM 역할을 선택.
배포 유형에서는 현재 위치를 선택. 만약 본인이 배포할 서비스가 2대 이상이라면 블루/그린을 선택하면 된다.
환경 구성에서는 [Amazon EC2 인스턴스]에 체크한다.
마지막으로배포 설정과 로드밸런서체크를 해제한다.
▪️Travis CI, S3, CodeDeploy 연동
먼저 S3에서 넘겨줄 zip 파일을 저장할 디렉토리를 하나 생성한다.
EC2 서버에 접속해서 다음과 같이 디렉토리를 생성한다.
mkdir ~/app/step2 && mkdir ~/app/step2/zip
Travis CI의 빌드가 끝나면 S3에 zip파일이 전송되고,
이 zip파일은 /home/ec2-user/app/step2/zip으로 복사되어 압축을 풀 예정이다.
Travis CI의 설정은 .travis.yml으로 진행했었다.
AWS CodeDeploy의 설정은 appspec.yml으로 진행한다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/app/step2/zip/
overwrite: yes
- version : 0.0 : CodeDeploy 버전을 이야기한다. 프로젝트 버전이 아니므로 0.0 이외의 다른 버전을 사용하면 오류가 발생한다.
- source : CodeDeploy에서 전달해준 파일 중 destination으로 이동 시킬 대상을 지정한다. 루트 파일(/)을 지정하면 전체파일을 이야기한다.
- destination : source에서 지정된 파일을 받을 위치이다. 이후 jar를 실행하는 등은 destination에서 옮긴 파을들로 진행된다.
- overwrite : 기존에 파일들이 있으면 덮어쓸지를 결정한다.
.travis.yml에도 CodeDeploy 내용을 추가한다.
deploy항목에 다음 코드를 추가한다.
- provider: codedeploy
access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
bucket: freelec-springboot-build-hj # S3 버킷
key: springboot-webservice3-project.zip # 빌드 파일을 압축해서 전달
bundle_type: zip # 압축 확장자
application: freelec-springboot3-webservice # 웹 콘솔에서 등록한 codedeploy application
deployment_group: freelec-springboot3-webservice-group # 웹 콘솔에서 등록한 codedeploy 배포그룹
region: ap-northeast-2
wait-until-deployed: true
이제 프로젝트를 커밋하고 푸시한다.
푸시가 되면 Travis CI가 자동으로 시작된다.
Travis CI가 끝나면 CodeDeploy 화면 아래에서 배포가 수행되는 것을 확인할 수 있다.
👀 에러사항
위에서 S3 압축파일까지 잘 생성이 되었지만, CodeDeploy가 정상적으로 배포 작동하지 않아 한참을 헤맸다.
결론적으로, codedeploy 코드에 아래 옵션을 추가해주니 잘 작동 되었다.
on:
branch: main
배포가 끝났다면 아래 명령어로 파일들이 잘 도착했는지 확인해 보자.
cd /home/ec2-user/app/step2/zip
ll
travis CI와 S3, CodeDeploy 가 연동이 완료되었다!
▪️배포 자동화 구성
앞의 과정으로 Travis CI,S3,CodeDeploy 연동까지 구현했다.
이제 이것을 기반으로 실제로 Jar를 배포하여 실행까지 해보자!
deploy.sh 파일 추가
먼저 step2 환경에서 실행될 deploy.sh를 생성하자. scripts 디렉토리를 생성해서 여기에 아래처럼 스크립트를 생성한다.
#!/bin/bash
REPOSITORY=/home/ec2-user/app/step2
PROJECT_NAME=freelec-springboot3-webservice
echo "> Build 파일 복사"
cp $REPOSITORY/ZIP/*.jar $REPOSITORY/
echo "> 현재 구동중인 어플리케이션 pid 확인"
CURRENT_PID=$(pgrep -fl springboot-web-practise | grep jar | awk '{print $1}')
echo "> 현재 구동중인 어플리케이션 pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "> 현재 구동 중인 어플리케이션이 없으므로 종료하지 않습니다."
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 &
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
- nohup 실행 시 CodeDeploy는 무한 대기한다.
- 이 이슈를 해결하기 위해 nohup.out 파일을 표준 입출력용으로 별도로 사용한다.
- 이렇게 하지 않으면 nohup.out 파일이 생기지 않고, CodeDeploy 로그에 표준 입출력이 된다.
- nohup이 끝나기 전까지 CodeDeploy도 끝나지 않으니 꼭 이렇게 해야만 한다.
step1 에서 작성된 deploy.sh와 비슷하지만, git pull을 통해 직접 빌드했던 부분을 제거했다.
그리고 Jar를 실행하는 단계에서 몇가지 코드가 추가되었다.
.travis.yml 파일 수정
현재는 프로젝트의 모든 파일을 zip 파일로 만드는데, 실제로 필요한 파일들은 Jar, appspec.yml, 배포를 위한 스크립트 들이다.
이 외 나머지는 배포에 필요하지 않으니 포함하지 않는다. 그래서 .travis.yml 파일의 before_deploy를 수정한다.
before_deploy:
- mkdir -p before-deploy # zip에 포함시킬 파일들을 담을 디렉토리 생성
- cp scripts/*.sh before-deploy/
- cp appspec.yml before-deploy
- cp build/libs/*.jar before-deploy/
- cd before-deploy && zip -r before-deploy * # before-deploy로 이동 후 전체 압축
- cd ../ && mkdir -p deploy # 상위 디렉토리로 이동 후 deploy 디렉토리 생성
- mv before-deploy/before-deploy.zip deploy/springboot-webservice3-project.zip # deploy로 zip파일 이동
- Travis CI는 S3로 특정 파일만 업로드가 안된다. 디렉토리 단위로만 업로드할 수 있기 때문에 deploy 디렉토리는 항상 생성한다.
- before-deploy에는 zip 파일에 포함시킬 파일들을 저장한다.
- zip -r 명령어를 통해 before-deploy 디렉토리 전체 파일을 압축한다.
이 외 나머지 코드는 수정할 것이 없다. 마지막으로 CodeDeploy의 명령을 담당할 appspec.yml 파일을 수정해보자.
appspec.yml 파일 수정
appspec.yml 파일에 다음 코드를 추가한다. 이때 들여쓰기를 주의하자!
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
hooks:
ApplicationStart:
- location: deploy.sh
timeout: 60
runas: ec2-user
- permissions: CodeDeploy에서 EC2 서버로 넘겨준 파일들을 모두 ec2-user 권한을 갖도록 한다.
- hooks: CodeDeploy 배포 단계에서 실행할 명령어를 지정한다. ApplicationStart라는 단계에서 deploy.sh를 ec2-user 권한으로 실행하게 한다.이때, 스크립트 실행이 60초 이상 수행되면 실패한다.(무한정 기다릴 수 없으니 시간제한을 둬야 한다.)
모든 설정 완료 후 깃허브로 커밋,푸시를 한다.성공적 배포를 확인하면, 웹 브라우저에서 EC2 도메인을 입력해 확인해 본다.
마지막으로, 실제 배포하듯이 진행해보자.
실제 배포 과정 체험
build.gradle에서 프로젝트 버전을 변경한다.
version '1.0.1-SNAPSHOT'
그리고 간단하게나마 변경된 내용을 알 수 있게 index.mustache에서 제목 뒤에 2.0을 붙이고 commit 및 push 해 보자.
CodeDeploy 로그 확인
기존에 로컬에서 배포를 할 때는 콘솔창에 로그가 출력되었다.
step1에서는 nohup.out 파일을 통해 결과를 확인했다.
step2로 넘어오면서 CodeDeploy의 로그를 확인하고 싶은데, AWS의 서비스이기 때문에 로그를 확인할 수 있을지 확인이 어렵다.
CodeDeploy에 관한 대부분 내용은 /opt/codedeploy-agent/deployment-root/ 디렉토리를 확인해 보면 알 수 있다.
1.
최상단의 영문과 대시(-)가 있는 디렉토리명은 CodeDeploy ID입니다.
사용자마다 고유한 ID가 생성되어 각자 다른 ID가 발급되니 본인 서버에는 다른 코드로 되어있습니다.
해당 디렉토리로 들어가면 배포한 단위별로 배포 파일들이 있습니다.
본인의 배포 파일이 정상적으로 왔는지 확인해 볼 수 있습니다.
2.
/opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log
CodeDeploy 로그 파일입니다.
CodeDeploy로 이루어지는 배포 내용 중 표준 입/출력 내용은 모두 여기 담겨있습니다.
작성한 echo 내용도 모두 표기됩니다.
테스트,빌드,배포까지 전부 자동화 되었다!
이제는 작업이 끝난 내용을 main 브랜치에 푸시만 하면 자동으로 EC2 배포가 된다.
하지만, 아직 문제가 한가지 남았다.
배포하는 동안 스프링 부트 프로젝트는 종료 상태가 되어 서비스를 이용할 수 없다는 것이다.
따라서 다음 장에서는 서비스 중단 없는 배포 방법인 무중단 배포에 대해 다룬다.