코드가 푸시되면 자동으로 배포하기(Travis CI 배포 자동화) - 루타블의 개발일기

김주영·2022년 7월 21일
0
post-thumbnail

[본 글은 프로젝트 과정을 기록할 목적으로 작성되었으며 아래 교재에 기반하여 작성됨]

🌱 CI & CD 소개


코드 버전 관리를 하는 VCS 시스템(Git, SVN 등) 에 PUSH가 되면 자동으로 테스트와 빌드가 수행되어 안정적인 배포 파일을 만드는 과정CI(Continuous Integration - 지속적 통합)라고 하며, 이 빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정을 CD(Continuous Deployment - 지속적인 배포)라고 한다.

일반적으로 CI만 구축되어 있지는 않고, CD도 함께 구축된 경우가 대부분이다. 여기서 주의할 점은 단순히 CI 도구를 도입했다고 해서 CI를 하고 있는 것은 아니다.

  • 마틴 파울러의 4가지 CI 규칙
    • 모든 소스가 살아 있고(현재 실행되고) 누구든 현재의 소스에 접근할 수 있는 단일 지점을 유지할 것
    • 빌드 프로세스를 자동화해서 누구든 소스로부터 시스템을 빌드하는 단일 명령어를 사용할 수 있게 할 것
    • 테스팅을 자동화해서 단일 명령어로 언제든지 시스템에 대한 건전한 테스트 수트를 실행할 수 있게 할 것
    • 누구나 현재 실행 파일을 얻으면 지금까지 가장 완전한 실행 파일을 얻었다는 확신을 하게 할 것

여기서 가장 중요한 것은 테스팅 자동화다. 지속적으로 통합하기 위해서는 무엇보다 이 프로젝트가 완전한 상태임을 보장하기 위해 테스트 코드가 구현되어 있어야만 한다.

🌱 Travis CI 연동


Travis CI는 깃허브에서 제공하는 무료 CI 서비스다. (정책 변경됨)

Travis CI 유료화

Free Plan(Trial Plan)에 대해 현재는 신규 가입자에 한해 30일 동안 무료 체험 가능하고 해당 기간 동안 10,000 크레딧을 모두 사용하면 해당 플랜으로는 더 이상 사용이 불가능하다고 한다.

공식 문서:
https://docs.travis-ci.com/user/billing-overview/

🌿 Travis CI 웹 서비스 설정

🔧 https://travis-ci.org에서 깃허브 계정으로 로그인을 한 뒤, 오른쪽 위에 [계정명 ➡ Settings]를 클릭

🔧 https://travis-ci.com에서 깃허브 계정으로 로그인을 한 뒤, 오른쪽 위에 [계정명 ➡ Settings]를 클릭

🔧 Activate를 누른 후, 저장소를 선택하고 approve&install 클릭

활성화한 저장소를 클릭하면 다음과 같이 저장소 빌드 히스토리 페이지로 이동한다.

Travis CI 웹사이트에서 설정은 이것이 끝이다. 상세한 설정은 프로젝트의 yml 파일로 진행해야 하니, 프로젝트로 돌아가겠다.

🌿 프로젝트 설정

Travis CI의 상세한 설정은 프로젝트에 존재하는 .travis.yml 파일로 할 수 있다. .yml 파일 확장자를 YAML(야믈)이라고 한다.

YAML은 쉽게 말해서 JSON에서 괄호를 제거한 것이다. YAML 이념이 "기계에서 파싱하기 쉽게, 사람이 다루기 쉽게"이다 보니 익숙하지 않은 사람이라도 읽고 쓰기가 쉽다. 그러다 보니 많은 프로젝트와 서비스들이 이 YAML을 적극적으로 사용 중이다. Travis CI 역시 설정을 이 YAML을 통해서 하고 있다.

🔧 프로젝트의 build.gradle과 같은 위치에서 .travis.yml 생성 후 코드 추가

language: java
jdk:
  - openjdk8

branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - '$HOME/.m2/repository'
    - '$HOME/.gradle'

script: "./gradlew clean build"

# CI 실행 완료 시 메일로 알람
notifications:
  email:
    recipients:
      - pink70834@gmail.com

📢 branches

Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정한다. 현재 옵션은 오직 master 브랜치에 push될 때만 수행한다.

📢 cache

gradle을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정한다.

📢 script

master 브랜치에 푸시되었을 때 수행하는 명령어다. 여기서는 프로젝트 내부에 둔 gradlew을 통해 clean & build를 수행한다.

📢 notifications

Travis CI 실행 완료 시 자동으로 알람이 가도록 설정한다.

🔧 master 브랜치에 커밋과 푸시를 하고, 좀 전의 Travis CI 저장소 페이지를 확인

🔧 빌드가 성공한 것이 확인되면 .travis.yml에 등록한 이메일을 확인

빌드가 성공했다는 것을 메일로도 잘 전달받아 확인했다 😀

📝 (에러) Travis CI build error

Travis CI 빌드 시 에러가 발생한다면, gradlew에 실행 권한이 없는 것이 원인일 수 있다. 나의 경우에는 다음의 코드를 추가한 후 재빌드하여 문제가 해결되었다.

...

before_install:
  - chmod +x gradlew

# Travis CI 서버의 Home
...

🌱 Travis CI와 AWS S3 연동


S3란 AWS에서 제공하는 일종의 파일 서버다. 이미지 파일을 비롯한 정적 파일들을 관리하거나 지금 진행하는 것처럼 배포 파일들을 관리하는 등의 기능을 지원한다. 보통 이미지 업로드를 구현한다면 이 S3를 이용하여 구현하는 경우가 많다.

  • S3를 비롯한 AWS 서비스와 Travis CI 연동 후 전체 구조

첫 번째 단계로 Travis CI와 S3를 연동한다. 실제 배포는 AWS CodeDeploy라는 서비스를 이용한다. 하지만, S3 연동이 먼저 필요한 이유는 Jar 파일을 전달하기 위해서다.

CodeDeploy는 저장 기능이 없다. 그래서 Travis CI가 빌드한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 보관할 수 있는 공간이 필요하다. 보통은 이럴 때 AWS S3를 이용한다.

CodeDeploy가 빌드도 하고 배포도 할 수 있다. CodeDeploy에서는 깃허브 코드를 가져오는 기능을 지원하기 때문이다. 하지만 이렇게 할 때 빌드 없이 배포만 필요할 때 대응하기 어렵다.

빌드와 배포가 분리되어 있으면 예전에 빌드되어 만들어진 Jar를 재사용하면 되지만, CodeDeploy가 모든 것을 하게 될 땐 항상 빌드를 하게 되니 확장성이 많이 떨어진다. 그래서 웬만하면 빌드와 배포는 분리하는 것을 추천한다.

🌿 AWS Key 발급

일반적으로 AWS 서비스에 외부 서비스가 접근할 수 없다. 그러므로 접근 가능한 권한을 가진 Key를 생성해서 사용해야 한다. AWS에서는 이러한 인증과 관련된 기능을 제공하는 서비스로 IAM(Identity and Access Management)이 있다.

IAM은 AWS에서 제공하는 서비스의 접근 방식과 권한을 관리한다. 이 IAM을 통해 Travis CI가 AWS의 S3와 CodeDeploy에 접근할 수 있도록 하겠다.

🔧 AWS 웹 콘솔에서 IAM을 검색하여 이동

🔧 IAM 페이지 왼쪽 사이드바에서 [사용자 ➡ 사용자 추가] 버튼을 차례로 클릭

🔧 생성할 사용자의 이름과 액세스 유형을 선택한다. 액세스 유형은 프로그래밍 방식 액세스로 한다.

🔧 권한 설정 방식은 3개 중 [기존 정책 직접 연결]을 선택

🔧 정책 검색 화면에서 s3full로 검색하여 체크하고 다음 권한으로 CodeDeployFull을 검색하여 체크

🔧 태그 추가에서 본인이 인지 가능한 정도의 이름을 지정

🔧 마지막으로 본인이 생성한 권한 설정 항목을 확인한다.

최종 생성 완료되면 다음과 같이 액세스 키와 비밀 액세스 키가 생성된다. 이 두 값이 Travis CI에서 사용될 키다.

🌿 Travis CI에 키 등록

🔧 Travis CI의 설정 화면으로 이동 후 Environment Variables 항목을 찾아 AWS_ACCESS_KEY, AWS_SECRET_KEY를 변수로 해서 IAM 사용자에서 발급받은 키 값들을 등록

여기에 등록된 값들은 이제 .travis.yml 에서 $AWS_ACCESS_KEY, $AWS_SECRET_KEY란 이름으로 사용할 수 있다.

🌿 S3 버킷 생성

AWS의 S3 서비스는 일종의 파일 서버다. 순수하게 파일들을 저장하고 접근 권한을 관리, 검색 등을 지원하는 파일 서버의 역할을 한다.

S3는 보통 게시글을 쓸 때 나오는 첨부파일 등록을 구현할 때 많이 이용한다. 파일 서버의 역할을 하기 때문인데, Travis CI에서 생성된 Build 파일을 저장하도록 구성하겠다. S3에 저장된 Build 파일은 이후 AWS의 CodeDeploy에서 배포할 파일로 가져가도록 구성할 예정이다.

🔧 AWS 서비스에서 S3를 검색하여 이동하고 버킷을 생성

🔧 원하는 버킷명을 작성하고, 이 버킷에 배포할 Zip 파일이 모여있는 장소임을 의미하도록 짓는 것을 추천한다.

🔧 버킷의 보안과 권한 설정 부분에서 모든 퍼블릭 액세스를 차단한다.

현재 프로젝트처럼 깃허브에 오픈 소스로 등록되어 있는 경우 문제없지만, 실제 서비스에서 할 때는 Jar 파일이 퍼블릭일 경우 누구나 내려받을 수 있어 코드나 설정값, 주요 키값들이 다 탈취될 수 있다.

퍼블릭이 아니더라도 IAM 사용자로 발급받은 키를 사용하니 접근 가능하다.

버킷 목록에서 생성된 S3 버킷을 볼 수 있다.

🌿 .travis.yml 추가

생성된 S3로 배포 파일(Jar)을 전달하겠다.

🔧 .travis.yml에 다음 코드를 추가

...

before_deploy:
  - zip -r bulletinboard-webservice-2022 *
  - mkdir -p deploy
  - mv bulletinboard-webservice-2022.zip deploy/bulletinboard-webservice-2022.zip

deploy:
  - provider: s3
    access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
    secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
    bucket: bulletinboard-webservice-2022 # 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:
      - pink70834@gmail.com

📢 before_deploy

deploy 명령어가 실행되기 전에 수행된다. CodeDeploy는 Jar 파일은 인식하지 못하므로 Jar+기타 설정 파일들을 모다 압축(zip)한다.

📢 zip -r bulletinboard-webservice-2022

현재 위치의 모든 파일을 bulletinboard-webservice-2022 이름으로 압축(zip)한다.

📢 mkdir -p deploy

deploy라는 디렉토리를 Travis CI가 실행 중인 위치에서 생성한다. 필요할 경우, 상위 경로도 자동 생성(-p)한다.

📢 mv bulletinboard-webservice-2022.zip deploy/bulletinboard-webservice-2022.zip

zip 파일을 deploy 디렉토리 아래로 이동시킨다.

📢 deploy

S3로 파일 업로드 혹은 CodeDeploy로 배포 등 외부 서비스와 연동될 행위들을 선언한다.

📢 local_dir: deploy

앞에서 생성한 deploy 디렉토리 위치의 파일들만 S3로 전송

before_deploy에서 배포를 위해 모든 파일을 압축하고 Jar 배포 파일들만 보관할 deploy 디렉토리를 생성했다. 그리고 deploy 디렉토리로 압축한 파일을 이동시킨다.

deploy에서 생성된 S3 버킷의 접근 키와 비밀 키, 버킷 이름 등의 정보들을 입력하여 연동 시 사용되도록 한다.

🔧 설정 완료 후 깃허브로 푸시

S3 버킷을 가보면 업로드가 성공한 것을 확인할 수 있다.

Travis CI를 통해 자동으로 S3에 파일이 올려진 것을 확인할 수 있다. 이것으로 Travis CI와 S3 연동이 완료되었다.

🌱 Travis CI와 AWS S3, CodeDeploy 연동


이제 S3에 올려진 파일을 CodeDeploy로 전달하여 배포하도록 하겠다.

AWS의 배포 시스템인 CodeDeploy를 이용하기 전에 배포 대상인 EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 하나 생성하겠다.

🌿 EC2에 IAM 역할 추가하기

🔧 S3와 마찬가지로 IAM을 검색하고, [역할 ➡ 역할 만들기] 탭을 클릭

📢 IAM 사용자와 역할의 차이점

  • 역할
    • AWS 서비스에만 할당할 수 있는 권한
    • EC2, CodeDeploy, SQS 등
  • 사용자
    • AWS 서비스 외에 사용할 수 있는 권한
    • 로컬 PC, IDC 서버 등

앞서 외부 서비스가 AWS에 접근할 수 있도록 접근 가능한 권한을 가진 AWS Key를 생성해서 사용하도록 했었다. (IAM 사용자)

지금 만들 권한은 EC2에서 사용할 것이기 때문에 사용자가 아닌 역할로 처리한다.

🔧 서비스 선택에서는 [AWS 서비스 ➡ EC2]를 차례로 선택

🔧 정책에선 EC2RoleForA를 검색하여 AmazonEC2RoleforAWS-CodeDeploy를 선택

🔧 태그는 본인이 원하는 이름으로 짓는다.

🔧 마지막으로 역할의 이름을 등록하고 나머지 등록 정보를 최종적으로 확인

이렇게 만든 역할을 EC2 서비스에 등록하겠다.

🔧 EC2 인스턴스 목록으로 이동한 뒤, 본인의 인스턴스를 마우스 오른쪽 버튼으로 눌러 [보안 ➡ IAM 역할 수정]를 차례로 선택

🔧 방금 생성한 역할을 선택

🔧 역할 선택이 완료되면 해당 EC2 인스턴스를 재부팅한다. 재부팅을 해야만 역할이 정상적으로 적용된다.

재부팅이 완료되었으면 CodeDeploy의 요청을 받을 수 있게 에이전트를 하나 설치하도록 하겠다.

🌿 CodeDeploy 에이전트 설치

🔧 EC2에 접속해서 다음 명령어 입력

aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2

🔧 install 파일에 실행 권한이 없으니 실행 권한을 추가한다.

chmod +x ./install

🔧 install 파일로 설치를 진행

sudo ./install auto

🔎 에러(ruby: No such file or directory)

해당 오류가 발생하는 원인은 현재 EC2에 루비가 설치되어 있지 않아 해당 파일을 루비가 실행할 수 없기 때문에 발생하는 문제였다.

아래 명령어로 ruby를 설치하도록 한 후, 설치를 재진행한다.

sudo yum install ruby
sudo ./install auto

🔧 다음 명령어로 Agent가 정상적으로 실행되고 있는지 상태 검사를 진행

sudo service codedeploy-agent status

다음과 같이 running 메시지가 출력되면 장상이다.

ref : https://small-stap.tistory.com/107

🌿 CodeDeploy를 위한 권한 생성

CodeDeploy에서 EC2에 접근하려면 마찬가지로 권한이 필요하다. AWS의 서비스이니 IAM 역할을 생성한다.

🔧 [IAM 역할 ➡ 역할 만들기] 선택

🔧 [AWS 서비스 ➡ CodeDeploy] 선택

🔧 CodeDeploy는 권한이 하나뿐이라서 선택 없이 바로 다음으로 넘어가면 된다.

🔧 태그 역시 본인이 원하는 이름으로 지으면 된다.

🔧 CodeDeploy를 위한 역할 이름과 선택 항목들을 확인한 뒤 생성 완료를 한다.

🌿 CodeDeploy 생성

CodeDeploy는 AWS의 배포 삼형제 중 하나다.

  1. Code Commit

    • 깃허브와 같은 코드 저장소의 역할
    • 프라이빗 기능을 지원한다는 강점이 있지만, 현재 깃허브에서 무료로 프라이빗 지원을 하고 있어서 거의 사용되지 않는다.
  2. Code Build

    • Travis CI와 마찬가지로 빌드용 서비스
    • 멀티 모듈을 배포해야 하는 경우 사용해 볼만하지만, 규모가 있는 서비스에서는 대부분 젠킨스/팀시티 등을 이용하니 이것 역시 사용할 일이 거의 없다.
  3. CodeDeploy

    • AWS의 배포 서비스
    • 대체재가 없다.
    • 오토 스케일링 그룹 배포, 블루 그린 배포, 롤링 배포, EC2 단독 배포 등 많은 기능을 지원

이 중에서 현재 진행 중인 프로젝트에서는

Code Commit ➡ GitHub

Code Build ➡ Travis CI

추가로 사용할 서비스는 CodeDeploy다.

🔧 CodeDeploy 서비스로 이동해서 화면 중앙에 있는 [애플리케이션 생성] 버튼을 클릭

🔧 생성할 CodeDeploy의 이름과 컴퓨팅 플랫폼을 선택한다. 컴퓨팅 플랫폼에선 [EC2/온프레미스]를 선택하면 된다.

🔧 생성이 완료되면 배포 그룹을 생성하라는 메시지를 볼 수 있다. 화면 중앙의 [배포 그룹 생성] 버튼을 클릭

🔧 배포 그룹 이름과 서비스 역할을 등록한다. 서비스 역할은 좀 전에 생성한 CodeDeploy용 IAM 역할을 선택하면 된다.

🔧 배포 유형에서는 현재 위치를 선택한다. 만약 본인이 배포할 서비스가 2대 이상이라면 블루/그린을 선택하면 된다. 여기선 1대의 EC2에만 배포하므로 선택하지 않는다.

🔧 환경 구성에서는 [Amazon EC2 인스턴스]에 체크

🔧 마지막으로 다음과 같이 배포 구성을 선택하고 로드밸런싱은 체크 해제한다.

배포 구성이란 한번 배포할 때 몇 대의 서버에 배포할지를 결정한다. 2대 이상이라면 1대씩 배포할지, 30% 혹은 50%로 나눠서 배포할지 등등 여러 옵션을 선택하겠지만, 1대 서버다 보니 전체 배포하는 옵션으로 선택하면 된다.

CodeDeployDefault.AllAtOnce는 한 번에 다 배포하는 것을 의미한다.

배포 그룹까지 생성되었다면 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의 설정은 .traivs.yml로 진행하겠다.

AWS CodeDeploy의 설정은 appspec.yml로 진행한다.

🔧 스프링 부트 프로젝트 아래에 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

기존에 파일들이 있으면 덮어쓸지를 결정한다. 현재 yes라고 했으니 파일들을 덮어쓰게 된다.

🔧 .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: bulletinboard-webservice-2022 # S3 버킷
    key: bulletinboard-webservice-2022.zip # 빌드 파일을 압축해서 전달
    build_type: zip # 압축 확장자
    application: bulletinboard-webservice-2022 # 웹 콘솔에서 등록한 CodeDeploy 애플리케이션
    deployment_group: bulletinboard-webservice-2022-group # 웹 콘솔에서 등록한 CodeDeploy 배포 그룹
    region: ap-northeast-2
    wait-until-deployed: true

S3 옵션과 다른 점은 CodeDeploy의 애플리케이션 이름과 배포 그룹명을 지정한 것이다.

📝 (정리) AWS 도면

Travis CI(.travis.yml)

  1. 프로젝트 파일을 빌드&테스트 후 배포를 위해 압축하고 지정된 폴더로 이동시킴

    (1) 프로젝트 파일을 빌드&테스트 후 배포를 위해 압축하고 지정된 폴더로 이동시킴

    (2) deploy : S3와 CodeDeploy 와 연동 (접근 허용을 위해 IAM 이용)
    IAM -> AWS_ACCESS_KEY + AWS_SECRET_KEY -> AWS 서비스에 접근

🔧 작성을 완료했다면 프로젝트를 커밋하고 푸시

깃허브로 푸시가 되면 Travis CI가 자동으로 시작된다.

🔧 배포가 끝났다면 다음 명령어로 파일들이 잘 도착했는지 확인해 보자

cd /home/ec2-user/app/step2/zip
ll

Travis CI와 S3, CodeDeploy 연동이 완료되었다! 😀😀

🌱 배포 자동화 구성


앞에 작업한 것을 기반으로 실제로 Jar를 배포하여 실행까지 해보도록 하자

🌿 deploy.sh 파일 추가

먼저 step2 환경에서 실행될 deploy.sh를 생성한다. script 디렉토리를 생성해서 여기에 스크립트를 생성한다.

#!/bin/bash

REPOSITORY=/home/ec2-user/app/step2
PROJECT_NAME=bulletinboard-webservice-2022

echo "> Build 파일 복사"

cp $REPOSITORY/zip/*.jar $REPOSITORY/

echo "> 현재 구동 중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -fl bulletinboard-webservice-2022 | 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 &

📢 CURRENT_PID

현재 수행 중인 스프링 부트 애플리케이션의 프로세스 ID를 찾는다. 해당 ID를 통해 해당 프로세스가 실행 중이면 종료하도록 한다.

pgrep으로 process id를 추출하는데 -fl을 통해 명령어의 경로도 출력한다. 그리고 스프링 부트 애플리케이션 이름으로 된 다른 프로그램들이 있을 수 있어 grep jar로 jar 확장자만 찾도록 했다. 마지막으로 awk를 통해 추출할 필드를 id 필드로 지정한다. $1은 공백이나 탭을 기준으로 1번째 필드를 변수로 분리해 인식한다.

📢 chmod +x $JAR_NAME

Jar 파일은 실행 권한이 없는 상태이므로, nohup으로 실행할 수 있게 실행 권한을 부여한다.

📢 $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

nohup 실행 시 CodeDeploy는 무한 대기한다. 이 문제를 해결하기 위해 nohup.out 파일을 표준 입출력용으로 별도로 사용한다. 이렇게 하지 않으면 nohup.out 파일이 생기지 않고, 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/bulletinboard-webservice-2022.zip # deploy로 zip파일 이동
  ...

📢 mkdir -p before-deploy

Travis CI는 S3로 특정 파일만 업로드가 안된다. 디렉토리 단위로만 업로드할 수 있기 때문에 before-deploy 디렉토리는 항상 생성한다.

📢 cp ... before-deploy/

before-deploy에는 zip 파일에 포함시킬 파일들을 저장한다. 여기서는 배포와 관련된 *.jar, *.sh, appspec.yml 등을 저장했다.

📢 zip -r before-deploy *

zip -r 명령어를 통해 before-deploy 디렉토리 전체 파일을 압축한다.

🌿 appspec.yml 파일 수정

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/app/step2/zip/
    overwrite: yes

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 권한으로 실행하게 된다. 그리고 무한 대기를 막기 위해 timeout: 60으로 스크립트 실행 60초 이상 수행되면 실패가 되도록 했다.

📢 ApplicationStart

ApplicationStart 중에 중지된 서비스를 다시 시작하려면 일반적으로 해당 배포 수명 주기 이벤트를 사용한다.

수명 주기 이벤트 Hooks 목록
ref : https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-server

🔧 모든 설정이 완료되면, 깃허브로 커밋과 푸시

🔧 웹 브라우저에서 EC2 도메인을 입력해서 확인

🌿 실제 배포 과정 체험

build.gradle에서 프로젝트 버전을 변경

version '1.0.1-SNAPSHOT'

🔧 간단하게 변경된 내용을 알 수 있게 src/main/resources/templates/index.mustache 내용을 약간 수정한다.

<h1>Welcome To Rootable's Free Board 🍃 Enjoy! </h1>

🔧 깃허브에 커밋과 푸시

🔎 EC2 인스턴스 재부팅 후 톰캣이 내려가는 문제(해결)

정확한 원인은 조사 중이지만, 여러 차례 실험 결과 깃커밋 & 푸시를 수행한 후 Travis CI를 통해 빌드 & 테스트가 수행되고 나서 잠시 기다리면 톰캣이 살아났다. 이 때부터, 퍼블릭 DNS 도 먹히고 EC2에 curl 명령도 정상적으로 작동했다.

🔎 로컬 수정 내용이 즉각 반영되지 않는 문제(해결)

build.gradle 버전을 변경하고, 깃커밋 & 푸시를 여러 차례 진행해도 로컬에서 수행한 수정 내용이 반영되지 않았다. 그런데, 작업 장소를 옮기면서 톰캣이 내려갔다가 다시 부팅했더니 수정 내용이 반영되는 것이었다. 이것으로 볼 때, deploy.sh에서 CURRENT_ID 부분을 보면 실행 중인 이전 배포 파일을 종료하도록 해뒀었는데 이것이 정상적으로 되지 않은 것 같다. 그래서 톰캣이 아예 내려가버리면 강제로 jar 파일도 종료되면서 가장 최근 jar 파일이 배포되고 수정 내용도 반영되는 것으로 판단된다.

📝 Nginx 적용 후 문제 해결

Nginx로 배포된 버전과 배포될 버전을 구분하고, EC2에서 Nginx로 수정된 버전 개시를 컨트롤하게 되면서 로컬 수정이 즉각 반영되지 않던 문제가 해결되었고, 이로 인해 EC2를 강제로 재부팅할 필요가 없어져 톰캣이 내려가는 일도 없어졌다.

🌿 CodeDeploy 로그 확인

CodeDeploy와 같이 AWS가 지원하는 서비스에서는 오류가 발생했을 때 로그 찾는 방법을 모르면 오류를 해결하기 어렵다. 배포가 실패하면 어떤 로그를 봐야하는지 알아보도록 하겠다.

CodeDeploy에 관한 대부분 내용은 /opt/codedeploy-agent/deployment-root에 있다.

🔧 cd /opt/codedeploy-agent/deployment-root로 이동

최상단의 디렉토리명이 CodeDeploy ID이다. 사용자마다 고유한 ID가 생성되어 각자 다른 ID가 발급된다. 해당 디렉토리로 들어가 보면 배포한 단위별로 배포 파일들이 있다. 본인의 배포 파일이 정상적으로 왔는지 확인해 볼 수 있다.

📢 /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent.log

CodeDeploy 로그 파일이다. CodeDeploy로 이루어지는 배포 내용 중 표준 입/출력 내용은 모두 여기에 담겨 있다. 작성한 echo 내용도 모두 표기된다.

이제는 작업이 끝난 내용을 Master 브랜치에 푸시만 하면 자동으로 EC2에 배포가 된다. 하지만 문제가 한 가지 남았다. 배포하는 동안 스프링 부트 프로젝트는 종료 상태가 되어 서비스를 이용할 수 없다는 것이다.

다음 챕터에서는 서비스 중단 없는 배포 방법을 통해 무중단 배포를 진행해 보겠다. 🐱‍👤🐱‍👤

0개의 댓글