[TravisCI, SpringBoot] 배포 자동화

Hyebin Lee·2022년 2월 12일
0

졸업프로젝트

목록 보기
1/2

참고한 링크

6) 스프링부트로 웹 서비스 출시하기 - 6. TravisCI & AWS CodeDeploy로 배포 자동화 구축하기

CI : 지속적인 통합

VCS (a.k.a github) 시스템을 통해 새로운/ 변경된 Resource에 대해 자동으로 테스트 또는 빌드 수행 후 안정적인 배포 파일을 생성하는 과정
한 마디로 깃허브 master branch에 새로 pull될 때마다 개발자가 직접 deploy.sh 를 실행시켜 배포하는 것이 아니라 배포를 자동화시키는 것

CD: 지속적인 배포

빌드 결과를 자동으로 운영 서버에 무중단 배포하는 과정

Travis CI

github에서 제공하는 무료 CI 서비스이다.
젠킨스와 같은 CI 툴이 있지만 젠킨스는 설치형이기 때문에 이를 위한 EC2 인스턴스가 하나 더 필요하다. 아직 프로젝트의 규모가 작고 이제 시작하는 서비스이기에 배포를 위한 EC2 인스턴스를 하나 더 추가하는 것이 부담스러워서 오픈소스 웹 서비스인 Traivs CI를 사용하기로 했다.

Travis CI 프로젝트 설정

TravisCi의 상세 설정은 프로젝트에 존재하는 .travis.yml로 할 수 있다. 이 파일은 build.gradle과 같은 위치에 놓여야 하며 만약 repository directory가 repository의 root 경로에 있는 것이 아니라 다른 directory 안에 들어가 있는 경우 .travis.yml은 repository의 root경로에 있어야 한다.

language: java
jdk:
  - openjdk11

branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - '$HOME/.m2/repository'
    - '$HOME/.gradle'
before_install:
  - chmod +x gradlew

script: "./gradlew clean build"

# CI 실행 완료시 메일로 알람
notifications:
  email:
    recipients:
      - xxx@gmail.com  #빌드 결과를 받아 볼 이메일 주소 

travis.yml 파일은 위와 같이 작성했다.
이 파일을 작성한 뒤 master 브랜치에 commit & push 하고 travis Ci 저장소 웹페이지를 확인하면
다음과 같이 build가 잘되어있는 것을 확인할 수 있다! 위에서 작성한 이메일도 들어가보면 빌드가 성공적으로 이루어졌다는 메일이 와있을 것이다.

참고로 travis.yml 파일을 작성하고 push까지 했는데 travis ci 서비스에서 아예 인식조차 못하는 경우가 있다. 예전에는 travis가 완전한 무료 서비스여서 따로 설정이 필요가 없었지만 지금은 유료로 전환되었기 때문에 꼭 내 Account 의 Setting에 들어가서 유료 plan을 선택해주어야 한다. 지금은 30일 무료 체험 플랜이 있으니 그걸 활용해보면 좋다.


이 과정은 테스트와 빌드만 자동화가 된 것이지 배포까지 자동화된 것은 아니다.
배포 자동화는 AWS의 Code Deploy와 연동해서 해주어야 한다.

AWS Code Deploy 연동

Travis CI는 CI툴일 뿐이라 Test와 Build까지 자동화가 되어도 배포가 자동화되진 않는다.
다시 말해, Travis CI가 자동으로 Build 해준 파일이 서버까지 따로 전달되어야 한다는 소리이다.
이 과정을 AWS CodeDeploy가 해준다.

배포 자동화 구조

따라서 이번 졸업프로젝트 전체적인 배포의 흐름은
1. IntelliJ에서 프로젝트를 개발하고 github에 새로 개발된 내용을 push 한다.
2. Travis CI에서 github에 새롭게 push된 내용을 인식하여 해당 내용을 test, build하고 메일로 이를 개발자에게 알린다.
3. build 결과를 AWS의 S3에게 전달하고 S3는 AWS CodeDeploy에 해당 내용을 전달한다.
4. AWS CodeDeploy에서 자동으로 전달받은 build 결과를 가지고 ec2를 배포한다.
5. EC2 인스턴스 내부에서 SpringBoot 프로젝트 (서버)가 실행되며 외부 ip에서 user가 해당 서버에 접근할 수 있다.

AWS Code Deploy 계정 생성

Travis CI & S3 연동

CodeDeploy에는 저장 기능이 없어서 Travis CI가 build한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 저장하여 보관하는 공간이 AWS S3이다.
따라서 .travis.yml파일 하단에 아래와 같은 코드를 추가해서 travis CI가 s3의 버킷에 자신의 build 결과물을 저장할 수 있도록 설정했다.

deploy:
  - provider: s3
    access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
    secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
    bucket: newsum-deploy-s3-bucket # S3 버킷
    region: ap-northeast-2
    skip_cleanup: true
    acl: public_read
    wait-until-deployed: true
    on:
      repo: NLSushi/server #Github 주소
      branch: master

여기서 설정 Accesskey와 Secretkey는 github에 노출되면 안되므로 따로 변수로 설정하고 Travis CI에서 환경변수를 지정해주어야 한다.

다음과 같이 각 AccessKey와 Secretkey의 값을 Travis CI 에서 환경변수 처리 해주었다.

[Error] s3 배포자동화시 The bucket does not allow ACLs

이제 수정된 travis.yml 파일을 push 하고 Travis CI의 빌드 로그를 다시 보려는데,,,

uploading "src/main/java/ewha/nlsushi/newsum/api/ArticleApiController.java" with {:content_type=>"text/x-java-source", :acl=>"public-read"}
/home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call': The bucket does not allow ACLs (Aws::S3::Errors::AccessControlListNotSupported)
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/s3_sse_cpk.rb:19:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/s3_dualstack.rb:24:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/s3_accelerate.rb:34:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:20:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/idempotency_token.rb:18:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/aws-sdk-core/plugins/param_converter.rb:20:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/seahorse/client/plugins/response_target.rb:21:in `call'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/seahorse/client/request.rb:70:in `send_request'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-core-2.11.632/lib/seahorse/client/base.rb:207:in `block (2 levels) in define_operation_methods'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-resources-2.11.632/lib/aws-sdk-resources/services/s3/file_uploader.rb:42:in `block in put_object'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-resources-2.11.632/lib/aws-sdk-resources/services/s3/file_uploader.rb:49:in `open_file'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-resources-2.11.632/lib/aws-sdk-resources/services/s3/file_uploader.rb:41:in `put_object'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-resources-2.11.632/lib/aws-sdk-resources/services/s3/file_uploader.rb:34:in `upload'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/aws-sdk-resources-2.11.632/lib/aws-sdk-resources/services/s3/object.rb:252:in `upload_file'
	from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-s3-1.10.16/lib/dpl/provider/s3.rb:114:in `block (2 levels) in upload_multithreaded'
failed to deploy

로그에 다음과 같은 오류가 발생했다.
이 로그를 읽어보니 버킷에 ACL 권한을 안줘서 그런것 같았다.

해당 S3 버킷의 퍼블릭 액세스 차단을 일단 모두 비활성하고

인증된 사용자 그룹의 나열과 읽기 권한을 주면 버킷이 ACL 권한을 갖게 된다!!


버킷 설정을 다시하고 다시 Travis CI에서 빌드하게 하니까 다음과 같이 S3 버킷에 프로젝트 빌드 결과가 담기게 되었다! 야호 🤸‍♀️
그런데 매번 Travis Ci에서 파일을 하나하나 복사해서 저장하는 것은 시간이 많이 걸리기 때문에 프로젝트 폴더 채로 압축해서 S3로 전달하도록 설정을 조금 추가하는 것이 좋다.

before_deploy:
  - zip -r newsum * #현재 위치의 모든 파일을 newsum 이름으로 압축
  - mkdir -p deploy #deploy 디렉토리를 Travis CI가 실행중인 위치에 생성
  - mv newsum.zip deploy/newsum.zip #deploy 폴더로 이동
  
  deploy:
  - provider: s3
    ...
    local_dir: deploy # before_deploy에서 생성한 디렉토리
    ...

그를 위해 위와 같이 travis.yml파일을 수정해주었다.
나의 최종 travis.yml 파일은 깃허브에서 확인할 수 있다!
다시 push 하면 이제 S3에서 zip 파일이 생긴 것을 확인할 수 있다. zip 파일 외에는 이제 필요 없어서 전부 지워도 괜찮다

Travis CI + S3 + CodeDeploy 연동

이제 Travis CI와 C3는 연동이 되었으니 CodeDeploy까지 같이 연동하면 된다!

CodeDeploy 어플리케이션 생성


  1. 지역이 서울인지 확인
  2. 컴퓨팅 플랫폼: EC2/온프레미스
  3. 배포 그룹 생성
  4. 현재 위치 배포 선택
  5. Amazon EC2 인스턴스 선택
  6. 배포 구성: CodeDeployDefault.OneAtTime
  7. 서비스역할: 기존에 생성해둔 CodeDeployRole 선택

어플리케이션 생성 후 EC2로 접속 (Putty활용)해서 S3에서 zip파일을 받아올 디렉토리를 생성한다.

appspec.yml 설정

.travis.yml과 같은 위치에 appspec.yml파일을 생성하고 설정한다.
이 파일은 AWS 의 Codedeploy를 설정해주는 파일이다.

version: 0.0
os: linux
files:
  - source:  /
    destination: /home/ec2-user/app/travis/build/

appspec.yml파일을 다음과 같이 설정해주고
travis.yml파일을 아래와 같이 수정한다.

language: java
jdk:
  - openjdk11

branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - '$HOME/.m2/repository'
    - '$HOME/.gradle'
before_install:
  - chmod +x gradlew
script: "./gradlew clean build"

# CI 실행 완료시 메일로 알람
notifications:
  email:
    recipients:
      - porori0703@ewhain.net
before_deploy:
  - zip -r newsum * #현재 위치의 모든 파일을 newsum 이름으로 압축
  - mkdir -p deploy #deploy 디렉토리를 Travis CI가 실행중인 위치에 생성
  - mv newsum.zip deploy/newsum.zip #deploy 폴더로 이동

deploy:
  - provider: s3
    access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
    secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
    bucket: newsum-deploy-s3-bucket # S3 버킷
    region: ap-northeast-2
    skip_cleanup: true
    acl: public_read
    local_dir: deploy # before_deploy에서 생성한 디렉토리
    wait-until-deployed: true
    on:
      repo: NLSushi/server #Github 주소
      branch: master
   # 📌수정(추가)된 부분 
  - provider: codedeploy
    access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
    secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
    bucket: newsum-deploy-s3-bucket # S3 버킷
    key: newsum.zip # 빌드 파일을 압축해서 전달
    bundle_type: zip
    application: newsum_codedeploy # 웹 콘솔에서 등록한 CodeDeploy 어플리케이션
    deployment_group: newsum-codedeploy-group # 웹 콘솔에서 등록한 CodeDeploy 배포 그룹
    region: ap-northeast-2
    wait-until-deployed: true
    on:
      repo: NLSushi/server
      branch: master

다시 push하고 Travis CI에서도 build가 pass되면..!
AWS codedeploy 어플리케이션에도 다음과 같이 성공 상태를 볼 수 있다!
이로써 배포가 성공된 것이다.

이제 EC2에 접속해서 zip 파일을 담아두었던 /home/ec2-user/app/travis/build 경로로 가서 파일을 확인해보면

작성한 코드가 git push 만으로 EC2까지 전송된 것을 볼 수 있다!

CodeDeploy로 스크립트 실행

이제 코드가 전송되었으니 jar 파일을 실행시키기만 하면 된다.
이를 위해서는 EC2에 AWS CodeDeploy로 받은 파일을 실행시키는 배포스크립트를 생성해야 한다.

먼저 jar 파일을 모아둘 디렉토리를 하나 생성하고 jar 디렉토리에 옮겨진 application.jar를 실행시킬 deploy.sh파일을 하나 생성한다.

vim /home/ec2-user/app/travis/deploy.sh
#!/bin/bash

REPOSITORY=/home/ec2-user/app/travis

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

CURRENT_PID=$(pgrep -f newsum) //📌jar파일에 들어가는 프로젝트명 

echo "$CURRENT_PID"

if [ -z $CURRENT_PID ]; then
    echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "> kill -15 $CURRENT_PID"
    kill -15 $CURRENT_PID
    sleep 5
fi

echo "> 새 어플리케이션 배포"

echo "> Build 파일 복사"

cp $REPOSITORY/build/build/libs/*.jar $REPOSITORY/jar/

JAR_NAME=$(ls $REPOSITORY/jar/ |grep 'newsum' | tail -n 1) //📌jar파일에 들어가는 프로젝트명 

echo "> JAR Name: $JAR_NAME"

nohup java -jar $REPOSITORY/jar/$JAR_NAME &

이 스크립트를 실행시키고 생성된 nohup.out 파일을 열람하여 스크립트가 잘 실행되어 서버가 돌아가는지 로그를 확인한다.

이제 AWS CodeDeploy 배포가 끝나면 deploy.sh를 실행하도록 설정을 변경해주어야 한다.
appspec.yml에 아래 코드를 추가한다.

hooks:
  AfterInstall: # 배포가 끝나면 아래 명령어를 실행
    - location: execute-deploy.sh
      timeout: 180

CodeDeploy에서 바로 deploy.sh를 실행시킬 수가 없어서 execute-deploy.sh파일을 실행하는 방식으로 우회해서 설정해주어야 한다.
기타 yml파일들과 마찬가지로 같은 위치에 execute-deploy.sh를 프로젝트 내부에 생성해서 CodeDeploy가 실행할 수 있도록 한다.
execute-deploy.sh는 다음과 같이 작성한다.

#!/bin/bash
/home/ec2-user/app/travis/deploy.sh > /dev/null 2> /dev/null < /dev/null &

해당 내용을 push 해주면 이제 모든 설정이 끝났다.
앞으로는 repository의 master branch에 commit+push+ merge 될때마다 자동으로 새 버전이 배포된다~!!!! 🎇💃🎇💃

0개의 댓글