배포 자동화 - Travis CI

박찬미·2022년 5월 30일
0

Spring Boot

목록 보기
16/17
  • CI
    코드 버전 관리를 하는 VCS 시스템(Git, SVN 등)에 PUSH가 되면 자동으로 테스트와 빌드가 수행되어 안정적인 배포 파일을 만드는 과정
  • CD
    빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정

개발자 각자가 원격 저장소로 푸시할 때마다 코드를 병합하고, 테스트 코드와 빌드를 수행하면서 자동으로 코드가 통합 또한 배포가 자동

CI의 규칙

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

1. Travis CI 연동하기

Travis CI : 깃허브에서 제공하는 무료 CI 서비스

Travis CI 웹 서비스 설정

  1. https://travis-ci.org/ 접속
  2. 깃허브 계정으로 로그인
  3. 프로젝트 선택

    Travis CI 웹사이트에서 설정은 끝났다.

프로젝트 설정

  • .travis.yml 생성
    build.gradle 와 동일한 위치에 yml 파일 생성

    소스설명
    - cache
    그레이들을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정
    - script
    main 브랜치에 푸시되면 수행하는 명령어
    gradlew을 통해 clean & build 수행
    - notifications
    Travis CI 실행완료 시 자동으로 알람

이제 푸시한다.

처음에는 이 부분에서 에러가 났다.
Travis가 과거에는 무료였으나 지금은 카드를 등록하고 무료 플랜을 설정해줘야 이용이 가능하다. (무료라며,,,왜 돈 나갔는데!!)

이제 다시 깃 푸시를 해줬다.
근데 이번엔 빌드 에러가 났다.

./gradle permission 오류

⇒ 해결방법
.travis.yml에 before_install 추가


성공

2. Travis CI와 AWS S3 연동

AWS에서 제공하는 일종의 파일 서버
이미지 파일을 비롯한 정적 파일들을 관리하거나 배포 파일들을 관리하는 등의 기능 지원

  • Travis CI 연동시 구조

먼저 Travis가 깃허브가 푸시하면 자동으로 빌드해주고 그 결과 jar 파일을 S3에 보내준다. ⇒ 그러므로 S3 연동 먼저한다.

S3에 저장된 파일을 이용해 나중에 CodeDeploy가 CD(배포)까지 해줄 것이다.

  • AWS Key 발급

일반적으로 AWS 서비스에 외부 서비스가 접근할 수 없다. 그러므로 접근 권한을 가진 키를 생성해 사용한다.

인증 관련된 서비스인 IAM(Identity and Access Management)를 들어간다.


위 사진처럼 설정한다.

이제 생성한 키는 Travis CI에 등록한다.
나중에 쓸 일을 대비해 csv를 다운받았다.

  • Travis CI에 키 등록


    이제 이렇게 등록된 값들은 .travis.yml에서 변수로 가져와서 사용할 수 있다.

  • S3 버킷 생성
    위에서 생성한 키를 이용해 jar을 관리할 S3 버킷을 생성한다.



    이외에 건드릴 거 없고, 생성한다.

버킷 설정 설명
퍼블릭 액세스를 열어두면 실제 서비스에서 Jar 파일이 퍼블릭 액세스가 가능하므로 코드나 설정값, 주요키값들이 모두 탈취될 우려가 있다. 어차피 나는 IAM을 이용해 액세스 가능하니 이외 모든 액세스는 차단한다.

이제 S3가 생성됐으니 여기로 배포 파일을 전달해보겠다.

  • .travis.yml jar파일 올리는 코드 추가

이전에는 빌드하고 실행 완료 시 메일로 알람까지 해줬다. 이제 빌드로 만든 jar 파일을 s3에 넘겨주는 코드를 아래에 추가한다.

language: java
jdk:
  - openjdk8

branches:
  only:
    - master

# ./gradlew 권한 변경
before_install:
  - chmod +x gradlew

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

# master 브랜치에 푸시되었을 때 수행하는 명렁어, clean & build
script: "./gradlew clean build"

# deploy 전에 실행
before_deploy: 
  - zip -r springboot-project-webservice *
  - mkdir -p deploy
  - mv springboot-project-webservice.zip deploy/springboot-project.zip
    
deploy: 
  - provider: s3
    access_key_id: $AWS_ACCESS_KEY # Travis에 설정해줬음
    secret_access_key: $AWS_SECRET_KEY
    bucket: springboot-project-s3build
    region: ap-northeast-2
    skip_cleanup: true
    acl: private # zip 파일 접근 private
    local_dir: deploy # before_deploy에서 생성한 디렉토리
    wait-until-deployed: true
  
# CI 실행 완료 시 메일로 알람
notifications:
  email:
    recipients:
      - cksal4278@gmail.com

설명
- before_deploy
codeDeploy는 Jar 파일을 인식하지 못해서 Jar과 다른 기타 설정 파일들을 모아 압축한다.(zip)
- zip -r springboot-project-webservice
현재 위치의 모든 파일을 위 이름으로 압축(zip)
명령어의 마지막 위치는 내 프로젝트 이름이어야 한다.
- mkdir -p deploy
deploy라는 디렉토리를 Travis CI가 실행 중인 위치에서 생성
- mv springboot-project webservice.zip ~
springboot-project-webservice.zip 파일을 deploy/~로 이동시킨다.
- deploy
s3로 파일 업로드 혹은 codeDeploy로 배포 등 외부 서비스와 연동될 행위들 선언
- local_dir: deploy
앞에서 생성한 deploy 디렉토리 지정
해당 위치의 파일들만 S3로 전송

이제 깃허브로 푸시한다.

짠 압축된 파일이 들어왔다.

3. Travis CI와 AWS S3, CodeDeploy 연동

이제 S3에 있는 파일을 CodeDeploy에서 받아서 배포할 수 있도록 해줘야 한다.

  • EC2에 IAM 역할 추가

CodeDeploy와 EC2에서 연동되도록 IAM 역할을 생성한다.

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

ec2-CodeDeploy권한은 ec2에서만 쓸거라서 역할로 만든다.




생성완료
이제 ec2에 역할을 등록하겠다.

저장 후 재부팅한다.

  • CodeDeploy 에이전트 설치

이제 CodeDeploy의 요청을 받을 수 있도록 에이전트를 설치한다.

ec2를 접속해서 명령어를 입력한다.

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


위 사진의 결과가 출력된다.

install 파일을 다운로드 받았는데 이 파일에 아직 실행 권한이 없어서 권한을 추가한다.

# 권한
chmod +x ./install

# 설치
sudo ./install auto

에러
설치 명령어 실행 후 위 사진과 같은 에러가 발생했다면 ruby를 설치한다.
sudo yum install ruby

# agent 정상 실행 상태 검사
sudo service codedeploy-agent status


위의 결과가 뜨면 정상

  • CodeDeploy에 EC2 접근 권한(역할) 생성



  • CodeDeploy 생성

    AWS에서 많이 쓰이는 배포 관련 서비스
    - Code Commit
    깃허브와 같은 코드 저장소 역할
    프라이빗 기능을 지원하지만, 깃허브에서 무료로 지원하므로 거의 사용X
    - Code Build
    Travis CI와 마찬가지로 빌드용 서비스
    멀티 모듈을 배포해야 하는 경우 사용해 볼만하지만, 규모가 있는 서비스는 대부분 젠킨스/팀시티 이용해 거의 사용X
    - CodeDeploy
    오토 스케일링 그룹 배포, 블루 그린 배포, 롱링 배포, EC2 단독 배포 등 많은 기능 지원

위 서비스들 중 commit은 깃허브가, build는 Travis CI 가 하기 때문에 CodeDeploy를 쓴다.



배포 유형은 현재 위치인데, 만약 배포할 서비스가 2대 이상이라면 블루/그린을 선택하면 된다. 나는 1대이므로 현재 위치


배포 구성이란
한 번 배포할 때 몇 디의 서버에 배포할지 결정
ex : 1대씩, 30%, 50% ~

S3에서 ec2에 넘겨줄 zip 파일을 저장할 디렉토리 생성한다.

mkdir step2 && mkdir step2/zip

  • AWS CodeDeploy 설정 파일 appspec.yml 생성

Travis CI 가 build하고 zip 파일을 S3에 넘기고 CodeDeploy가 ec2에 위 경로로 파일을 복사하여 압축을 풀 것이다.

version: 0.0 # CodeDeploy 버전
os: linux
files:
  - source: / # 전체 파일 지정해서
    destination: /home/ec2-user/app/step2/zip/ # 목적 위치로 파일 이동
    overwrite: yes
  • ./travis.yml에 CodeDeploy 내용 추가

    이제 깃허브에 푸시한다.


돌고..에러남ㅋ

에러 이유를 찾았으나 travis에는 제대로 된 로그가 나오지 않았다.

/var/log/aws/codedeploy-agent

에 들어가서 codedeploy 로그를 봤을 때 포괄적인 에러만 나오고(로그가 너무 많아서 가독성이 떨어짐) 정확한 답을 얻을 수 없었다…후..

정확한 비교적 정확한 에러 이유를 알려면
codedeploy group에서 배포 그룹 배포 내역에서 실패한 배포 id를 클릭하면 그 아래 배포 수명 주기 이벤트가 있는데 이벤트 View events 를 클릭한다.

그리고 중간에 실패한 이벤트의 오류코드 링크를 클릭하면 appspec file에서 뭔가 설정을 잘못했다고 나온다. 버전 확인하라고 한다.

나의 문제
나는 이 부분에서 파일을 다시 보니 오타가 있었다..ㅎ…정말,,이런 건 확인을 잘 해야하나부다..


여러 시도 끝에 드디어 배포에 성공했다!

4. 배포 자동화 구성

이제 모두 연동이 되었다.
실제 Jar를 배포하여 실행까지 해보겠다.

step2 환경에서 실행될 deploy.sh 생성한다.

#!/bin/bash

# 변수 선언(자주 쓰고 길어서), 실제 디렉토리 이름을 넣어야 함
REPOSITORY=/home/ec2-user/app/step2
PROJECT_NAME=SpringBoot-Project

echo "> Build 파일 복사"
cp $REPOSITORY/zip/*.jar $REPOSITORY/

echo "> 현재 구동 중인 애플리케이션pid 확인"
CURRENT_PID=$(pgrep -fl SpringBoot-Project | 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 &

소스설명
- git pull 해서 직접 빌드하는 코드는 삭제
- CURRENT_PID
현재 수행 중인 애플리케이션 프로세스 아이디를 찾아 종료한다.
스프링 부트 애플리케이션 이름(Springboot-Project)으로 된 다른 프로그램들이 있을 수 있어 Springboot-Project로 된 jar 프로세스를 찾은 뒤 id를 찾는다.
- chmod +x ~
jar 파일은 실행 권한이 없는 상태이므로 권한을 부여한다.
- $JAR_NAME > $REPOSITORY~
nohup 실행 시 codeDeploy는 무한 대기한다. 이 문제를 해결하기 위해 nohup.out 파일을 표준 입출력용으로 별도로 사용한다.
이렇게 하지 않으면 nohup.out 파일이 생기지 않고 codedeploy 로그에 표준 입출력이 된다.
nohup이 끝나기 전까지 codeDeploy도 끝나지 않으니 꼭 이렇게 해야 한다.

  • .travis.yml 수정

프로젝트의 모든 파일을 zip 파일로 만들고 있는데, 실제로 필요한 파일은 Jar, appspec.yml, 배포를 위한 스크립트들이므로 나머지는 포함하지 않는다.


# deploy 전에 실행
before_deploy:
  - mkdir -p deploy-deploy
  - cp scripts/*.sh before-deploy/
  - cp appspec.yml before-deploy/
  - cp build/libs/*.jar before-deploy/
  - cd before-deploy && zip -r before-deploy *
  - cd ../ && mkdir -p deploy
  - mv before-deploy/before-deploy.zip deploy/springboot-project-webservice.zip

소스 설명
- Travis CI는 S3로 특정 파일만 업로드 할 수 없음
디렉토리 단위로만 업로드 가능하므로 before-deploy 디렉토리 항상 생성
- before-deploy에는 zip 파일에 포함시킬 파일들을 저장
- 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초 이상 실행되면 실패(무한정 제한)

이제 코드를 수정해보겠다.

  • build.gradle
version '1.0.1-SNAPSHOT'

이렇게 하면 수정된 내용을 잘 알 수 없으니
화면을 변경해보겠다.

  • index.mustache

이렇게 수정하고 깃허브에 커밋하면 자동으로 build, test, deploy가 모두 된다.

근데 중간에 배포는 오류없이 완료됐지만, 수정사항이 제대로 반영되지 않았다.

nohup.out 파일을 살펴보니

8080 포트가 겹치는 문제가 발생했다. 분명 쉘스크립트에서 실행 중인 애플리케이션을 종료하도록 했는데 제대로 잡히지 않았던 것이다.

프로세스를 계속 검색해보니 jar는 pid가 안나오고 java에서 나온다.

그래서 위 코드에서 grep jar 부분을 java로 바꿔주었다.

수정된 화면이 제대로 배포되었다.

이제 깃허브 Master 브랜치에 푸시만 하면 자동으로 ec2에 배포가 된다.

하지만 여기서 문제는 배포되는 동안 프로젝트가 종료가 되어 서비스를 이용할 수 없다는 것이다.(나도 갑자기 안뜨길래 당황했다.)

다음에는 무중단 배포 서비스를 만들어보겠다.

0개의 댓글