티스토리 뷰

TIL

[CI/CD] 배포 자동화 설정 파일

sayho 2021. 12. 24. 19:59

.travis.yml

  Travis CI 의 설정파일로 build.gradle 과 같은 위치에 있어야 한다.
  .travis.yml 은 Travis CI가 해야할 일들을 알려준다.   

  1. language 빌드 환경을 식별하기 위한 설정 JAVA 11을 사용하는 Linux 운영체제에서 실행된다.

     language: java
     os: linux
     jdk:
       - openjdk11

  2. Travis CI를 master branch에 push 될 때 수행

     branches:
       only:
         - master

  3. Travis CI 서버의 Home
     gradle을 통해 의존성을 받고 명시한 디렉토리에 보관한 뒤 다음 배포 때 다시 받지 않도록 설정

     cache:
       directories:
         - '$HOME/.m2/repository'
         - '$HOME/.gradle'

  4. 배포 전 실행할 명령어. gradlew에 실행권한 부여

     before_install:
       - chmod +x ./gradlew

  5. master branch에 push시 실행되는 명령어 (빌드)

     script: "./gradlew clean build"

  6. before-deploy : 배포 전 실행해야하는 명령어

     before_deploy:
       # zip에 포함시킬 파일들을 담을 디렉토리 생성. before-deploy 폴더 생성.
       - mkdir -p before-deploy 
       # scripts 폴더 밑에있는 스크립트 파일들을 before-deploy 폴더에 복사
       - cp scripts/*.sh before-deploy/
       # appepc.yml 을 before-deploy에 복사
       - cp appspec.yml before-deploy/ 
       # build/libs밑에 있는 jar 파일들을 전부 before-deploy에 복사
       - cp build/libs/*.jar before-deploy/
       # before-deploy 폴더로 이동 후 before-deploy라는 이름으로 압축
       - cd before-deploy && zip -r before-deploy * 
       # 상위 디렉토리로 이동후 deploy 디렉토리 생성
       - cd ../ && mkdir -p deploy
       # before-deploy.zip을 deploy 폴더에 portfolio.zip이라는 이름으로 바꿔서 이동
       - mv before-deploy/before-deploy.zip deploy/portfolio.zip # deploy로 zip파일 이동

  7. 배포 파일을 업로드 또는 codedeploy로 배포 등 외부서비스와 연동될 행위를 선언한다. 

  deploy:
    - provider: s3
      # Travis CI에서 빌드한 결과를 s3에 저장해야한다.
      # s3에 접근하기 위해서는 access_key와 secret_key가 필요하므로 설정해준다.
      access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
      secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
      bucket: myticket-forbuild # S3 버킷
      region: ap-northeast-2
      skip_cleanup: true
      # zip 파일 접근을 private으로
      acl: private 
      # before_deploy에서 생성한 디렉토리
      local_dir: deploy 
      wait-until-deployed: true

    - provider: codedeploy
      access_key_id: $AWS_ACCESS_KEY # Travis repo settings에 설정된 값
      secret_access_key: $AWS_SECRET_KEY # Travis repo settings에 설정된 값
      bucket: myticket-forbuild # Codedeploy가 다운받을 S3 버킷
      key: portfolio.zip # 버킷에 있는 파일 이름
      bundle_type: zip # 버킷에 저장된 확장자
      application: myticket-portfolio-webservice # 웹 콘솔에서 등록한 CodeDeploy 배포 어플리케이션
      deployment_group: myticket-portfolio-webservice-group # 웹 콘솔에서 등록한 CodeDeploy 배포 그룹
      region: ap-northeast-2
      wait-until-deployed: true

  8. CI 실행 완료 시 메일로 알람

  notifications:
    email:
      recipients:
        - 본인메일@gmail.com

appspec.yml

  codedeploy 설정은 appspec.yml 을 이용한다.

1. 프로젝트 버전이 아닌 Codedeploy 버전을 뜻한다. 

   version: 0.0
   os: linux

2. source는 시작 위치 destination은 이동시킬 대상 위치다.

  /은 루트 디렉토리로 / 밑에있는 폴더와 파일 전부를 의미하고
   / 하위에 있는 모든 파일을 /home/ubuntu/app/myticket/zip 으로 이동시킨다. 
   overwrite는 기존에 파일이 있다면 덮어쓰는걸 허용하는 옵션이다.

   files:
     - source:  /
       destination: /home/ubuntu/app/myticket/zip/
       overwrite: yes

3. codedeploy에서 ec2로 넘겨준 파일 전부 ubuntu 권한을 갖도록 한다.

   permissions:
     - object: /
       pattern: "**"
       owner: ubuntu
       group: ubuntu

4. 배포 단계에서 실행할 명령어를 지정해준다.

   AfterInstall 단계에서는 ubuntu 권한으로 stop 스크립트를 실행해준다. 60초가 넘게되면 실패한다.
   ApplicationStart 단계에서는 ubuntu 권한으로  start  스크립트를 실행해준다.
   ValidateServicce 단계에서는 ubuntu 권한으로 health 스크립트를 실행하여 배포가
   성공적으로 되었는지 확인해준다.

   hooks:
     AfterInstall:
       - location: stop.sh
         timeout: 60
         runas: ubuntu
     ApplicationStart:
       - location: start.sh # 엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작합니다.
         timeout: 60
         runas: ubuntu
     ValidateService:
       - location: health.sh
         timeout: 60
         runas: ubuntu

stop.sh

  hooks의 생명주기 중 애플리케이션 구성 또는 파일 권한 변경 작업을 진행하는 AfterInstall 단계에서 실행되는 스크립트다.

  1. bash 쉘로 스크립트를 실행하겠다고 선언

  #!/usr/bin/env bash

  2. 현재 실행되고있는 스크립트의 절대경로를 ABSPATH에 저장
     스크립트가 저장된 디렉토리의 이름을 ABSDIR에 저장
     source는 import문과 비슷하다고 생각하면 된다. profile 스크립트를 import해준다

  ABSPATH=$(readlink -f $0)
  ABSDIR=$(dirname $ABSPATH)
  source ${ABSDIR}/profile.sh

  3. profile 스크립트의 finde_idle_port 함수를 실행시키면
     놀고있는 포트를 리턴받는다.

  IDLE_PORT=$(find_idle_port)

  4. lsof 명령어를 통해 시스템에서 열려있는 파일의 정보를 확인할 수 있다.
     - t 옵션 : 프로세스의 PID 출력 / i 옵션 : 호스트 이름대신 IP 정보로 출력
     - TCP:포트 : TCP:포트에서 동작중인 프로세스 출력 

  echo "> $IDLE_PORT 에서 구동중인 애플리케이션 PID 확인"
  IDLE_PID=$(lsof -ti tcp:${IDLE_PORT})

  5. -z 는 문자열의 길이가 0인 경우를 의미한다. PID 가 없다면 종료하지 않음.
     만약 문자열의 길이가 0이 아니라면 kill 에 15옵션을 줘서 안전하게 프로세스를 종료.

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

profile.sh

#!/usr/bin/env bash

1. 쉬고있는 profile 찾기
    curl : client url 다양한 프로토콜 데이터를 전송해볼 수 있다.
    -s : slient 모드. 진행내역이나 메세지를 출력하지 않는다
    -o /dev/null : remote에서 받아온 데이터를 /dev/null 파일로 저장한다.
    -w httpcode URL : URL 호출한 후 -w을 이용하여 http 응답코드를 반환받는다.

function find_idle_profile()
{
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)

    # 응답 코드가 400 보다 크면 (즉, 40x/50x 에러 모두 포함)
      현재 profile 설정을 real2로 한다.
    if [ ${RESPONSE_CODE} -ge 400 ]
    then
        CURRENT_PROFILE=real2
    else
        # 정상적으로 응답을 받았다면 localhost/profile 요청 후 리턴 값을
        현재 profile로 저장한다
        CURRENT_PROFILE=$(curl -s http://localhost/profile)
    fi

    # 현재 profile이 real1이라면 nginx와 연결되지 않은 설정파일은 real2고
    if [ ${CURRENT_PROFILE} == real1 ]
    then
      IDLE_PROFILE=real2

    # 현재 profile이 real2라면 nginx와 연결되지 않은 설정파일은 real1이다.
    else
      IDLE_PROFILE=real1
    fi

    # 현재 연결되지 않은 설정파일을 리턴하여 무중단 배포 준비를 한다. 
    echo "${IDLE_PROFILE}"
}

2. 쉬고 있는 profile의 port 찾기
function find_idle_port()
{
    IDLE_PROFILE=$(find_idle_profile)

    # 연결되지 않은 설정파일이 real1이라면 8082번 포트를 준비시킨다.
    if [ ${IDLE_PROFILE} == real1 ]
    then
      echo "8082"

    # 연결되지 않은 설정파일이 real2라면 8083번 포트를 준비시킨다.
    else
      echo "8083"
    fi
}

start.sh

  빌드된 jar을 실행시켜주는 스크립트.

#!/usr/bin/env bash

1, 현재 실행되고있는 스크립트의 절대경로를 ABSPATH에 저장
   스크립트가 저장된 디렉토리의 이름을 ABSDIR에 저장
   source는 import문과 비슷하다고 생각하면 된다. profile 스크립트를 import해준다

ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh

REPOSITORY=/home/ubuntu/app/myticket
PROJECT_NAME=portfolio

2. REPOSITORY/zip 에 있는 jar 파일을 REPOSITORY 하위에 복사

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

3. REPOSITORY에 있는 jar 형식의 파일목록을 가장 최근수정한 순, 역알파벳 순으로 출력한다.
   그리고 마지막 1라인을 출력한다.

echo "> 새 어플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)
echo "> JAR Name: $JAR_NAME"

4. jar파일에 실행권한을 준다

echo "> $JAR_NAME 에 실행권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"

5. 활성화 상태가 아닌 설정 파일 선택

IDLE_PROFILE=$(find_idle_profile)

6. 선택한 설정파일을 이용하여 웹 애플리케이션 jar을 실행한다.

echo "> $JAR_NAME 를 profile=$IDLE_PROFILE 로 실행합니다."
nohup java -jar \
    -Dspring.config.location=classpath:/application.properties,classpath:/application-
    $IDLE_PROFILE.properties,/home/ubuntu/app/application-real-db.properties \
    -Dspring.profiles.active=$IDLE_PROFILE \
    $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

health.sh

  nginx와 연결되지 않은 포트로 스프링 부트가 잘 수행되었는지 체크하는 스크립트.

#!/usr/bin/env bash

ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh

IDLE_PORT=$(find_idle_port)

1. NGINX와 연결되지 않은 포트에서 스프링 부트 JAR이 잘 실행중인지 확인

echo "> Health Check Start!"
echo "> IDLE_PORT: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/profile "
sleep 10

2. nginx와 연결되지 않은 포트의 /profile 요청을 한다.
   response가 오면 해당 response에 real이 포함되어있고 해당 파일의 라인수  wc -l를 
   통해 UP_COUNT 변수에 저장한다.

for RETRY_COUNT in {1..10}
do
  RESPONSE=$(curl -s http://localhost:${IDLE_PORT}/profile)
  UP_COUNT=$(echo ${RESPONSE} | grep 'real' | wc -l)

3. UP_COUNT 가 1 이상이면 health check에 성공하고
   포트를 바꿔주기 위해 switch_proxy 함수를 실행시켜준다. 

  if [ ${UP_COUNT} -ge 1 ]
  then # $up_count >= 1 ("real" 문자열이 있는지 검증)
      echo "> Health check 성공"
      switch_proxy
      break
  else
      echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
      echo "> Health check: ${RESPONSE}"
  fi

 4. 만약 crul 요청을 10번을 했으나 응답이 안온다면
   배포를 실패한걸로 간주한다.

  if [ ${RETRY_COUNT} -eq 10 ]
  then
    echo "> Health check 실패. "
    echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
    exit 1
  fi

  echo "> Health check 연결 실패. 재시도..."
  sleep 10
done

switch.sh

  nginx 포트를 변경하는 스크립트.

#!/usr/bin/env bash

ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh

1. 포트 전환 함수

function switch_proxy() {
    IDLE_PORT=$(find_idle_port)

    echo "> 전환할 Port: $IDLE_PORT"
    echo "> Port 전환"
    # 현재 서비스 중인 url을 변경된 포트로 바꿔준다.
      파이프라인 앞에서 넘겨준 문장을 service-url.inc에 덮어쓴다.
    echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx
    /conf.d/service-url.inc

    echo "> 엔진엑스 Reload"
    sudo service nginx reload
}

참고

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함