24시간 365일 운영되는 서비스에 배포 환경 구축은 필수 과제 중 하나입니다. 여러 개발자의 코드가 실시간으로 병합되고, 테스트가 수행되는 환경, master 브랜치가 푸시되면 배포가 자동으로 이루어지는 환경을 구축하지 않으면 실수할 여지가 너무나 많습니다. 이번엔 이런 배포 환경을 구성해 보겠습니다!
CI & CD 소개
저는 개인적으로 CI(Continuous Integration 지속적인 통합)와 CD(Continuous Deployment(지속적인 배포)가 되게 생소합니다. 그래서 간략하게 정리해보았습니다. CI란 코드 버전 관리를 하는 VCS 시스템(Git 등)에 PUSH가 되면 자동으로 테스트와 빌드가 수행되어 안정적인 배포 파일을 만드는 과정을 말합니다. CD란 CI의 빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정을 말합니다.
현대의 웹 서비스 개발에서는 하나의 프로젝트를 여러 개발자가 함께 개발을 진행합니다. 그러다 보니 각자가 개발한 코드가 합쳐야 할 때마다 큰 일이었습니다. 그래서 매주 병합일(코드 Merge만 하는 날)을 정하여 이날은 각자가 개발한 코드를 합치는 일만 진행했다고 합니다.
이런 수작업 때문에 생산성이 좋을 수가 없었으며 개발자들은 지속해서 코드가 통합되는 환경인 CI를 구축하게 되었다고 합니다. 개발자 각자가 원격 저장소(Git)로 푸시가 될 때마다 코드를 병합하고, 테스트 코드와 빌드를 수행하면서 자동으로 코드가 통합되어 더는 수동으로 코드를 통합할 필요가 없어지면서 자연스럽게 개발자들 역시 개발에만 집중할 수 있게 되었습니다.
CD 역시 마찬가지입니다. 한 두 대의 서버에 개발자가 수동으로 배포를 할 수 있지만, 수십 대 수백 대의 서버에 배포를 해야 하거나 긴박하게 당장 배포를 해야 하는 상황이 오면 더는 수동으로 배포할 수가
없습니다. 그래서 이 역시 자동화하게 되었고, 개발자들이 개발에만 집중할 수 있게 되었습니다.
CI와 CI 모두 등장 배경은 개발자들이 개발에만 집중할 수 있도록 하기위해 등장했다고 보면 좋을 것 같습니다.
여기서 주의할 점은 단순히 CI 도구를 도입했다고 해서 CI를 하고 있는 것은 아닙니다. 마틴 파울러의 블로그(http://bit.ly/2Yv0vFp)를 참고해보면 CI에 대해 아래와 같은 4가지 규칙을 이야기합니다.
- 모든 소스 코드가 살아있고(현재 실행되고) 누구든 현재의 소스에 접근할 수 있는 단일 지점을 유지할 것
- 빌드 프로세스를 자동화해서 누구든 소스로부터 시스템을 빌드하는 단일 명령어를 사용할 수 있게 할 것
- 테스팅을 자동화해서 단일 명령어로 언제든지 시스템에 대한 건전한 테스트 수트를 실행할 수 있게 할 것
- 누구나 현재 실행 파일을 얻으면 지금까지 가장 완전한 실행 파일을 얻었다는 확신을 하게 할 것
특히 중요한 것은 테스팅 자동화입니다. 지속적으로 통합하기 위해서는 무엇보다 프로젝트가 완전한 상태임을 보장하기 위해 테스트 코드가 구현되어 있어야만 합니다.
Travis CI 연동하기
Travis CI는 Github에서 제공하는 무료 CI 서비스입니다. 젠킨스와 같은 CI 도구도 있지만, 젠킨스는 설치형이기 때문에 이를 위한 EC2 인스턴스가 하나 더 필요합니다. 이제 시작하는 서비스에서 배포를 위한 EC2 인스턴스는 부담스럽기 때문에 오픈소스 웹 서비스인 Travis CI를 사용하겠습니다.
Travis CI 웹 서비스 설정
https://travis-ci.com/에서 깃허브 계정으로 로그인을 한 뒤, 오른쪽 위에 계정명->setting을 클릭합니다. 설정 페이지 아래쪽에 깃허브 저장소 검색창에 저장소 이름을 입력해서 찾은 다음 클릭해줍시다. 그 후 build history 페이지를 눌러줍시다. Travis CI 웹사이트에서 설정은 이것이 끝입니다. 상세한 설정을 프로젝트의 yml 파일로 해야합니다.
프로젝트 설정
Travis CI의 상세한 설정은 프로젝트에 존재하는 .travis.yml 파일로 할 수 있습니다. yml 확장자는 YAML으로 JSON에서 괄호를 제거한 것 이라고 보면 편합니다. Travis CI 설정을 YAML을 통해서 하고 있습니다.
프로젝트의 build.gradle과 같은 위치에서 .travis.yml을 생성한 후 아래의 코드를 추가해줍니다.
language: java
jdk:
- openjdk8
branches:
only:
- main
# Travis CI 서버의 Home
cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
before_install:
- chmod +x gradlew
script: "./gradlew clean build"
# CI 실행 완료 시 메일로 알람
notifications:
email:
recipients:
- 본인 메일 주소
- branches
- Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정합니다.
- 현재 옵션은 오직 master 브랜치에 push될 때만 수행합니다.
- cache
- 그레이들을 통해 의존성을 받게되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정합니다.
- script
- master 브랜치에 푸시되었을 때 수행하는 명령어입니다.
- 여기서는 프로젝트 내부에 둔 gradlew을 통해 clean & build를 수행합니다.
- notifications
- Travis CI 실행 완료 시 자동으로 알림이 가도록 설정합니다.
여기까지 마친 뒤, master 브랜치에 커밋과 푸시를 하고, Travis CI 저장소 페이지를 확인해봅시다. 여기서 바로 확인이 되지않는데, 시간을 두고 다시 보시면 바뀐다고 합니다. 하지만 저는 아래와 같은 에러가 발생했습니다.
찾아보니 비자카드를 등록하고 무료 플랜을 가입해야했습니다. setting에 Plan에서 Trial Plan이라는 무료 플랜을 등록했습니다. 이후에도 잘 되지않아서 찾아보니 branch를 master -> main으로 바꿔주어야 했습니다. (git branch 기본이 master -> main 으로 바뀜)
적어놓은 메일로도 정상작동 된다는 메일이 온 것을 볼 수 있을 것 입니다.
Travis CI와 AWS S3 연동하기
S3란 AWS에서 제공하는 일종의 파일 서버입니다. 이미지 파일을 비롯한 정적 파일들을 관리하거나 지금 진행하는 것처럼 배포 파일들을 관리하는 등의 기능을 지원합니다. 보통 이미지 업로드를 구현한다면 S3를 이용하여 구현하는 경우가 많습니다. S3를 비롯한 AWS 서비스와 Travis CI를 연동하게 되면 전체 구조는 아래와 같습니다.
Travis CI와 S3를 연동해봅시다. 실제 배포는 AWS CodeDeploy라는 서비스를 이용합니다. S3 연동이 먼저 필요한 이유는 Jar 파일을 전달하기 위해서 입니다.
CodeDeploy는 저장 기능이 없습니다. 그래서 Travis CI가 빌드한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 보관할 수 있는 공간이 필요합니다. 보통 이럴 때 S3를 사용합니다.
AWS Key 발급
일반적으로 AWS 서비스에 외부 서비스가 접근할 수 없습니다. 그러므로 접근 가능한 권한을 가진 Key를 생성해서 사용해야 합니다. AWS에서는 이러한 인증과 관련된 기능을 제공하는 서비스로 IAM(Identity And AccessManagement)이 있습니다.
IAM은 AWS에서 제공하는 서비스의 접근 방식과 권한을 관리합니다. IAM을 통해 Travis CI가 AWS S3와 CodeDeploy에 접근할 수 있도록 하겠습니다. AWS 웹 콘솔에서 IAM을 검색하여 이동합니다.
- IAM 페이지 왼쪽 사이드바에서 사용자->사용자 추가 버튼을 차례로 클릭해줍시다.
- 사용자 이름과 엑세스 유형은 프로그래밍 방식을 선택한 뒤 다음버튼을 눌러줍시다. 기존 정책 직접 연결을 선택합니다. 아래 정책 검색 창에서 s3full과 codedeployfull을 검색한 뒤 둘다 체크해줍니다.
- 태그는 Name 값을 지정하는데, 본인이 인지 가능한 정도의 이름으로 만들어줍시다.
- 마지막으로 본인이 생성한 권한 설정 항목을 확인합니다.
최종 생성 완료되면 엑세스 키와 비밀 엑세스 키가 생성 됩니다. 이 두 값이 Travis CI에서 사용될 키 입니다. 이제 이 키를 Travis CI에 등록해줍시다.
Travis CI에 키 등록
Travis CI 설정 화면으로 이동합니다. 프로젝트 좌측 More Option에서 Setting을 눌러주면됩니다. 아래 Environment Variables 항목이 있습니다. 아래처럼 변수명으로 하여 엑세스 키값과 시크릿 키 값을 추가해줍니다.
이렇게 등록된 값은 .travis.yml에서 $AWS_ACCESS_KEY, $AWS_SECRET_KEY란 이름으로 이용할 수 있습니다. 이제 해당 키를 사용해서 Jar를 관리할 S3 버킷을 생성해줍시다.
S3 버킷 생성
파일 서버의 역할을 하기 때문에, Travis CI에서 생성된 Build 파일을 저장하도록 구성하겠습니다. S3에 저장된 Build 파일은 이후 AWS의 CodeDeploy에서 배포할 파일로 가져가도록 구성할 예정입니다. AWS 웹에서 S3를 검색하여 이동해줍니다.
- 버킷 만들기를 눌러줍니다.
- 원하는 버킷명을 작성합니다. 이 버킷에 배포할 Zip 파일이 모여있는 장소임을 의미하도록 짓는 것을 추천합니다.
- 다음으로 버전관리를 설정하는데, 별다른 설정을 할 것이 없어서 바로 넘어갑니다.
S3가 생성되었으니 이제 해당 S3로 배포 파일을 전달해봅시다.
.travis.yml 추가
travis CI에 빌드하여 만든 Jar 파일을 S3에 올릴 수 있도록 .travis.yml에 아래 코드를 추가해줍시다.
...
before_deploy:
- zip -r SpringBootWebService ./*
- mkdir -p deploy
- mv SpringBootWebService.zip deploy/SpringBootWebService.zip
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
bucket: qazyj-springboot-build #S3 버킷 이름
region: ap-northeast-2
skip_cleanup: true
acl: private #zip 파일 접근 private으로
local_dir: deploy #before_deploy에서 생성한 디렉토리
wait_until_deployed : true
...
전체 코드는 아래와 같습니다.
language: java
jdk:
- openjdk8
branches:
only:
- main
# Travis CI Server's Home
cache:
- directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
script: "./gradlew clean build"
before_deploy:
- zip -r SpringBootWebService ./*
- mkdir -p deploy
- mv SpringBootWebService.zip deploy/SpringBootWebService.zip
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
bucket: qazyj-springboot-build #S3 버킷 이름
region: ap-northeast-2
skip_cleanup: true
acl: private #zip 파일 접근 private으로
local_dir: deploy #before_deploy에서 생성한 디렉토리
wait_until_deployed : true
# CI 실행 완료 시 메일로 알람
notifications:
email:
recipients:
- qazyj@naver.com
- before_deploy
- deploy 명령어가 실행되기 전에 수행됩니다.
- CodeDeploy는 Jar 파일을 인식하지 못하므로 Jar+기타 설정 파일들을 모아 압축(zip)합니다.
- zip -r SpringBootWebService
- 현재 위치의 모든 파일을 springboot2-webservice 이름으로 압축합니다.
- 명령어의 마지막 위치는 본인의 프로젝트 이름이어야합니다.
- mkdir -p deploy
- deploy라는 디렉토리를 Travis CI가 실행중인 위치에서 생성합니다.
- mv SpringBootWebService.zip deploy/SpringBootWebService.zip
- SpringBootWebService.zip 파일을 deploy/SpringBootWebService.zip 으로 이동시킵니다.
- deploy
- S3로 파일 업로드 혹은 CodeDeploy로 배포 등 외부 서비스와 연동될 행위들을 선언합니다.
- local_dir: deploy
- 앞에 생성한 deploy 디렉토리를 지정합니다.
- 해당 위치의 파일들만 S3로 전송합니다.
설정이 다 되었으면 푸쉬합니다. 그리고 S3 버킷을 가보면 업로드가 성공한 것을 확인할 수 있습니다.
이제 CodeDeploy로 배포까지 완료해봅시다.
Travis CI와 AWS S3, CodeDeploy 연동하기
AWS의 배포 시스템인 CodeDeploy를 이용하기 전에 배포 대상인 EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 하나 생성해주겠습니다.
EC2에 IAM 역할 추가하기
S3와 마찬가지로 IAM을 검색해줍시다.
- 역할 탭을 클릭해서 이동합니다.
- 역할 만들기 버튼을 클릭합니다.
- EC2에서 사용할 것이기 때문에 AWS 서비스를 누른뒤, EC2를 눌러줍니다. 다음 버튼을 누릅시다.
- 정책에선 EC2RoleForA를 검색해서 AmazonEC2RoleforAWSCodeDeploy를 선택합니다. 다음 버튼을 누릅시다.
- 태그틑 본인이 원하는 이름으로 짓습니다. 다음 버튼을 누릅시다.
- 최종적으로 확인한 뒤, 만들어줍니다.
이렇게 만든 역할을 EC2 서비스에 등록하겠습니다. EC2 인스턴스 목록으로 이동한 뒤, 본인의 인스턴스를 마우스 오른쪽 버튼으로 눌러 보안 -> IAM 역할 수정을 선택해줍니다. IAM 역할에 방금 만든 역할을 추가해줍니다. 그 후 EC2 인스턴스를 재부팅 합니다. 재부팅을 해야만 역할이 정상적으로 적용되니 재부팅 해줍시다!
재부팅이 완료되었으면 CodeDeploy의 요청을 받을 수 있도록 에이전트를 하나 설치해줍시다.
CodeDeploy 에이전트 설치
ssh로 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
위의 코드가 안되면 아래의 코드처럼 루비를 설치한 뒤 위의 코드를 적어줍시다.
sudo yum install ruby
설치가 끝났으면 Agent가 정상적으로 실행되고 있는지 상태 검사를 해줍시다.
sudo service codedeploy-agent status
아래와 같이 running 메시지가 출력된다면 정상적인 상태입니다.
The AWS CodeDeploy agent is running as PID 4573
CodeDeploy를 위한 권한 생성
CodeDeploy에서 EC2에 접근하려면 마찬가지로 권한이 필요합니다. AWS의 서비스이니 IAM 역할을 생성합니다. EC2에 IAM 역할을 만들어 준 것처럼 CodeDeploy를 선택해줍니다. CodeDeploy는 역할이 하나 뿐이라서 선택 없이 바로 다음으로 넘어가면 됩니다.
CodeDeploy 생성
CodeDeploy는 AWS의 배포 삼형제 중 하나입니다. 배포 삼형제에 대해 간단하게 소개하자면 아래와 같습니다.
- Code Commit
- 깃허브와 같은 코드 저장소의 역할을 합니다.
- 프라이빗 기능을 지원한다는 강점이 있지만, 현재 깃허브에서 무료로 프라이빗 지원을 하고 있어서 거의 사용되지 않습니다.
- Code Build
- Travis CI와 마찬가지로 빌드용 서비스입니다.
- 멀티 모듈을 배포해야 하는 경우 사용해 불편하지만, 규모가 있는 서비스에서는 대부분 젠킨스/팀시티 등을 이용하니 이것 역시 사용할 일이 거의 없습니다.
- CodeDeploy
- AWS의 배포 서비스입니다.
- 앞에서 언급한 다른 서비들은 대체재가 있고, 딱히 대체재보다 나은 점이 없지만, CodeDeploy는 대체재가 없습니다.
- 오토 스케일링 그룹 배포, 블루 그린 배포, 롤링 배포, EC2 단독 배포 등 많은 기능을 지원합니다.
이 중 현재 진행 중인 프로젝트에서는 Code Commit의 역할은 깃 허브가, Code Build의 역할은 Travis CI가 하고 있습니다. CodeDeploy 서비스로 이동해서 화면 중앙에 있는 애플리케이션 생성 버튼을 클릭해줍시다. 이름은 원하시는 이름으로, 컴퓨팅은 아래와 같이 EC2/온프레미스를 선택해줍시다.
생성이 완료되면 배포 그룹을 생성하라는 메시지를 볼 수 있습니다. 화면 중앙의 배포 그룹 생성 버튼을 클릭합니다. 이름은 원하시는 이름으로 해주시고, 서비스 역할 선택에서 이전에 만들어 주었던 codedeploy IAM 역할을 선택해 줍시다. 배포 유형은 현재 위치를 선택합니다. 환경 구성에서는 Amazon EC2 인스턴스를 체크합니다. 태그 그룹은 아래와 같이 해줍니다.
마지막으로 아래와 같이 배포 구성을 선택하고 로드밸런싱은 체크해제합니다.
이제 배포 그룹까지 생성되었다면 CodeDeploy 설정은 끝입니다. 이제 Travis CI와 CodeDeploy를 연동해 보겠습니다.
Travis CI, S3, CodeDeploy 연동
먼저 S3에 넘겨줄 zip 파일을 저장할 디렉토리를 하나 생성해줍시다. EC2 서버에 접속해서 아래 명령어를 입력해 디렉토리를 생성해줍시다.
mkdir ~/app/step2 && mkdir ~/app/step2/zip
Travis CI의 build가 끝나면 S3에 zip 파일이 전송되고, 이 zip 파일은 /home/ec2-user/app/step2/zip 경로에 복사되어 압축을 풀 예정입니다.
Travis CI 설정은 .travis.yml에서 진행했습니다.
AWS CodeDeploy의 설정은 appspec.yml으로 진행합니다. 코드는 아래와 같습니다.
version: 0.0 # CodeDeploy 버전을 명시한다.
os: linux
files:
- source: / # 전체 파일을 나타낸다.
destination: /home/ec2-user/app/step2/zip/ # source로 지정된 전체 파일이 받을 위치이다.
overwrite: yes # 기존 파일들이 있으면 덮어 쓸지를 결정한다.
- version: 0.0
- CodeDeploy 버전을 이야기합니다.
- 프로젝트 버전이 아니므로 0.0 외에 다른 버전을 사용하면 오류가 발생합니다.
- source
- CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정합니다.
- 루트 경로(/)를 지정하면 전체 파일을 이야기합니다.
- destination
- source에서 지정된 파일을 받을 위치입니다.
- 이후 Jar를 실행하는 등은 destination에서 옮긴 파일들로 진행됩니다.
- overwrite
- 기존에 파일들이 있으면 덮어쓸지를 결정합니다.
- yes라고 했으니 파일들을 덮어쓰게 됩니다.
.travis.yml에도 CodeDeploy 내용을 추가합니다. deploy 항목에 다음 코드를 추가합니다.
language: java
jdk:
- openjdk8
branches:
only:
- main
# Travis CI Server's Home
cache:
- directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
script: "./gradlew clean build"
before_deploy:
- zip -r SpringBootWebService *
- mkdir -p deploy
- mv SpringBootWebService.zip deploy/SpringBootWebService.zip
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
bucket: qazyj-springboot-build #S3 버킷 이름
region: ap-northeast-2
skip_cleanup: true
acl: private #zip 파일 접근 private으로
local_dir: deploy #before_deploy에서 생성한 디렉토리
wait_until_deployed : true
on:
branch: main
## 새롭게 추가된 부분
- provider: codedeploy
access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
bucket: qazyj-springboot-build # S3 버킷
key: SpringBootWebService.zip # 빌드 파일을 압축해서 전달
bundle_type: zip # 압축 확장자
application: springboot2-webservice # 웹 콘솔에서 등록한 CodeDeploy 어플리케이션
deployment_group: springboot2-webservice-group # 웹 콘솔에서 등록한 CodeDeploy 배포 그룹
region: ap-northeast-2
wait-until-deployed: true
on:
branch: main
## 새롭게 추가된 부분
# CI 실행 완료 시 메일로 알람
notifications:
email:
recipients:
- qazyj@naver.com
모든 내용을 작성했다면 프로젝트를 커밋하고 푸쉬합니다. Travis CI가 끝나면 CodeDeploy 화면 아래에서 배포가 수행되는 것을 확인할 수 있습니다. on: branch:main을 추가안해주면
위의 에러가 뜨며 정상적으로 작업이 진행되지 않습니다.
여러번의 실패 끝에 on: branch:main을 추가해서 성공적으로 배포가 되었습니다.
이제 Travis CI, S3, CodeDeploy가 연동되었습니다.
배포 자동화 구성
이제 실제로 Jar를 배포하여 실행까지 해봅시다.
deploy.sh 파일 추가
먼저 step 환경에 실행될 deploy.sh를 생성하겠습니다. scripts 디렉토리를 생성해서 여기에 deploy.sh 스크립트를 생성합니다. 코드는 아래와 같습니다.
#!/bin/bash
REPOSITORY=/home/ec2-user/app/step2
PROJECT_NAME=SpringBootWebService
echo "> Build 파일 복사"
cp $REPOSITORY/zip/*.jar $REPOSITORY/
echo "> 현재 구동중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -fl SpringBootWebService | grep java | 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 &
다음은 .travis.yml 파일을 수정해줍시다.
.travis.yml 파일 수정
현재는 프로젝트의 모든 파일을 zip 파일로 만드는데, 실제로 필요한 파일은 Jar, appspec.yml, 배포를 위한 스크립트들 입니다. 이 외 나머지는 배포에 필요하지 않으니 포함하지 않도록 before_deploy를 아래와 같이 수정합니다.
language: java
jdk:
- openjdk8
branches:
only:
- main
# Travis CI Server's Home
cache:
- directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
script: "./gradlew clean build"
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/SpringBootWebService.zip # deploy로 zip파일 이동
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
bucket: qazyj-springboot-build #S3 버킷 이름
region: ap-northeast-2
skip_cleanup: true
acl: private #zip 파일 접근 private으로
local_dir: deploy #before_deploy에서 생성한 디렉토리
wait_until_deployed : true
on:
branch: main
## 새롭게 추가된 부분
- provider: codedeploy
access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
bucket: qazyj-springboot-build # S3 버킷
key: SpringBootWebService.zip # 빌드 파일을 압축해서 전달
bundle_type: zip # 압축 확장자
application: springboot2-webservice # 웹 콘솔에서 등록한 CodeDeploy 어플리케이션
deployment_group: springboot2-webservice-group # 웹 콘솔에서 등록한 CodeDeploy 배포 그룹
region: ap-northeast-2
wait-until-deployed: true
on:
branch: main
## 새롭게 추가된 부분
# CI 실행 완료 시 메일로 알람
notifications:
email:
recipients:
- qazyj@naver.com
- Travis CI는 S3로 특정 파일만 업로드가 안됩니다.
- 디렉토리 단위로만 업로드할 수 있기 때문에 before-deploy 디렉토리는 항상 생성합ㄴ디ㅏ.
- before-deploy에는 zip 파일에 포함시킬 파일들을 저장합니다.
- zip- r 명령어를 통해 before-deploy 디렉토리 전체 파일을 압축합니다.
마지막으로 CodeDeploy의 명령을 담당할 appspec.yml 파일을 수정합니다.
appspec.yml 파일 수정
appspec.yml 파일에 아래 코드를 추가합니다. location, timeout, runas의 들여쓰기를 주의해야 합니다. 잘못될 경우 배포가 실패합니다.
version: 0.0 # CodeDeploy 버전을 명시한다.
os: linux
files:
- source: / # 전체 파일을 나타낸다.
destination: /home/ec2-user/app/step2/zip/ # source로 지정된 전체 파일이 받을 위치이다.
overwrite: yes # 기존 파일들이 있으면 덮어 쓸지를 결정한다.
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
hooks:
ApplicationStart:
- location: deploy.sh # 엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작합니다.
timeout: 60
runas: ec2-user
- permissions
- CodeDeploy에서 EC2 서버로 넘겨준 파일들을 모두 ec2-user 권한을 갖도록합니다.
- hooks
- CodeDeploy 배포 단계에서 실행할 명령어를 지정합니다.
- ApplicationStart라는 단계에서 deploy.sh를 ec2-user권한으로 실행하게 합니다.
- timeout: 60으로 스크립트 실행 60초 이상 수행되면 실패가 됩니다.(무한정 기다릴 수 없으니 시간 제한을 둬야만 합니다.)
모든 설정이 완료되었으니 커밋과 푸시를 합니다.
CodeDeploy에 배포가 성공하면 웹브라우저에서 EC2 도메인을 이용해서 확인해봅시다. 마지막으로 실제 배포하듯이 진행해 보겠습니다.
실제 배포 과정 체험
build.gradle에서 프로젝트 버전을 아래와 같이 변경합니다.
version '1.0.1-SNAPSHOT'
간단하게나마 변경된 내용을 알 수 있게 src/main/resources/templates/index.mustache 내용에 아래와 같이 Ver.3 텍스트를 추가합니다.
<h1>스프링부트로 시작하는 웹 서비스 Ver.3</h1>
그리고 깃허브로 커밋과 푸시를 합니다. 변경된 코드가 배포된 것을 확인할 수 있습니다.
CodeDeploy 로그 확인
CodeDeploy와 같이 AWS가 지원하는 서비스에서는 오류가 발생했을 때 로그 찾는 방법을 모르면 오류를 해결하기가 어렵습니다. 그래서 배포가 실패할 경우 어느 로그를 봐야할지 간단하게 배워봅시다.
CodeDeploy에 관한 대부분 내용은 /opt/codedeploy-agent/deployment-root에 있습니다. 해당 디렉터리로 이동(cd /opt/codedeploy-agent/deployment-root) 한 뒤 ll 명령어를 입력하면 아래와 같은 내용을 확인할 수 있습니다.
drwxr-xr-x 7 root root 101 1월 7 00:41 18e713ab-302e-4750-ac92-3fa4f156e15b
drwxr-xr-x 2 root root 247 1월 7 00:41 deployment-instructions
drwxr-xr-x 2 root root 46 1월 7 00:36 deployment-logs
drwxr-xr-x 2 root root 6 1월 7 00:41 ongoing-deployment
- 최상단의 영문과 대시(-)가 있는 디렉토리명은 CodeDeploy ID입니다.
- 사용자마다 고유한 ID가 생성되어 각자 다른 ID가 발급되니 본인의 서버에는 다른 코드로 되어있습니다.
- 해당 디렉토리로 들어가보면 배포한 단위별로 배포 파일들이 있습니다.
- 본인의 배포 파일이 정상적으로 왔는지 확인해 볼 수 있습니다.
- /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log
- CodeDeploy 로그 파일입니다.
- CodeDeploy로 이루어지는 배포 내용 중 표준 입/출력 내용은 모두 여기에 담겨 있습니다.
- 작성한 echo 내용도 모두 표기됩니다.
테스트, 빌드, 배포까지 전부 자동화되었습니다. 이제는 작업이 끝난 내용을 Master 브랜치에 푸시만하면 자동으로 EC2에 배포가 됩니다.
하지만, 문제가 한 가지 남았습니다. 배포하는 동안 스프링 부트 프로젝트는 종료 상태가 되어 서비스를 이용할 수 없다는 것 입니다. 어떻게 하면 배포하는 동안에도 서비스는 계속 유지될 수 있을까요??
다음 장에서는 서비스 중단 없는 배포 방법에 대해서 배워봅시다.
출처
'spring > 스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
[Spring] 10. 24시간 365일 중단 없는 서비스를 만들자 (0) | 2022.01.07 |
---|---|
[Spring] 8. EC2 서버에 프로젝트 배포 (0) | 2022.01.01 |
[Spring] 7. AWS RDS (0) | 2022.01.01 |
[Spring] 6. AWS 서버 환경 구축 - AWS EC2 (0) | 2021.12.30 |
[Spring] 5. 스프링시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 (0) | 2021.12.29 |