[스프링] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - 코드가 푸시되면 자동으로 배포해 보자 - Travis CI 배포 자동화

June·2021년 10월 17일
0

24시간 365일 운영되는 서비스에서 배포 환경 구축은 필수다. 여러 개발자의 코드가 실시간으로 병합되고, 테스트가 수행되는 환경, master 브랜치가 푸시되면 배포가 자동으로 이루어지는 환경을 구축하지 않으면 실수할 여지가 너무 많다.

CI & CD 소개

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

마틴 파울러는 CI의 4가지 규칙을 이야기 한다.

  • 모든 소스 코드가 살아 있고 (현재 실행되고) 누구든 현재의 소스에 접근할 수 있는 단일 지점을 유지할 것

  • 빌드 프로세스를 자동화해서 누구든 소스로부터 시스템을 빌드하는 단일 명령어를 사용할 수 있게 할 것

  • 테스팅을 자동화해서 단일 명령어로 언제든지 시스템에 대한 건전한 테스트 수트를 실행할 수 있게 할 것

  • 누구나 현재 실행 파일을 얻으면 지금까지 가장 완전한 실행 파일을 얻었다는 확신을 하게 할 것

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

테스트 코드 작성, TDD에 대해 좀 더 알고 싶으면 클린 코더스 - TDD편을 보자.

Travis CI 연동하기

Travis CI는 깃허브에서 제공하는 무료 CI서비스다. 젠킨스와 같은 CI 도구도 있지만, 젠킨스는 설치형이기 때문에 이를 위한 EC2 인스턴스가 하나 더 필요하다.

Travis CI 웹 서비스 설정

https://www.travis-ci.com/ 에서 깃허브 계정 로그인 -> 계정명 -> setting

상세한 설정은 프로젝트의 yml 파일로 진행한다.

프로젝트 설정

Travis CI의 상세한 설정은 프로젝트에 존재하는 .travis.yml 파일로 할 수 있다. YAML은 쉽게 말해 JSON에서 괄호를 제거한 것이다. YAML의 이념이 "기계에서 하싱하기 쉽게, 사람이 다루기 쉽게"다 보니 다루기 쉽다.

build.gradle과 같은 위치에 .travis.yml을 만든다.

.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:
      - injoon2019@gamil.com
  1. branches
    • Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정한다.
    • 현재 옵션은 오직 master 브랜치에 push 될 때만 수행한다.
  2. cache
    • 그레이들을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 떄부터 다시 받지 않도록 설정한다.
  3. script
    • master 브랜치에 푸시되었을 때 수행되는 명령어다
    • 여기서는 프로젝트 내부에 둔 gradlew를 통해 clean & build를 수행한다.
  4. notifications
    • Travis CI 실행 완료 시 자동으로 알람이 가도록 설정한다.

안된다. Travis ci 말고 GithubAction을 쓰자

https://jojoldu.tistory.com/543

name: freelec-springboot2-webservice

on:
  push:
    branches:
      - master # (1) 실습하시는분들은 master로 하시면 됩니다. (저는 별도 브랜치로 지정)
  workflow_dispatch: # (2) 수동 실행

jobs:
  build:
    runs-on: ubuntu-latest # (3)

    steps:
      - name: Checkout
        uses: actions/checkout@v2 # (4)

      - name: Set up JDK 1.8
        uses: actions/setup-java@v1 # (5)
        with:
          java-version: 1.8

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew # (6)
        shell: bash

      - name: Build with Gradle
        run: ./gradlew clean build # (7)
        shell: bash

(1) on: push: branches: - version/2020-12-11

  • 현재 만드는 Github Action의 트리거 브랜치를 지정합니다.
  • 즉, 여기서는 version/2020-12-11브랜치가 push되면 현재 만들고 있는 Github Action이 실행됩니다.
  • master 브랜치가 트리거가 되길 원하시면 master를 등록하시면 됩니다.

(2) on: workflow_dispatch:

  • 브랜치 push 이벤트외에, 수동으로 실행하는 것도 가능하게 만드는 옵션입니다.

(3) jobs: build: runs-on: ubuntu-latest

  • 해당 Github Action 스크립트가 작동될 OS 환경을 지정합니다.
  • 일반적으로 웹 서비스의 OS는 Ubuntu 보다는 Centos를 많이들 쓰기 때문에 Centos는 없을까 생각해보실텐데요.
  • 아쉽게도 Github Action에서 공식지원하는 OS 목록에는 Centos가 없으니 Ubuntu를 사용합니다.

(4) uses: actions/checkout@v2

  • 프로젝트 코드를 checkout 합니다.

(5) actions/setup-java@v1

  • Github Action이 실행될 OS에 Java를 설치합니다.
  • with: java-version: 1.8 로 메이저 버전을 설치할 수 있으며 11, 13 등 버전들도 설치 가능합니다.
  • 자세한건 마켓플레이스 Action을 참고하시면 좋습니다.

(6) run: chmod +x ./gradlew

  • gradle wrapper를 실행할 수 있도록 실행 권한 (+x)을 줍니다.
  • 해당 실행 권한이 있어야 아래 (7) 를 실행할 수 있습니다.

(7) run: ./gradlew clean build

  • gradle wrapper를 통해 해당 프로젝트를 build 합니다.

파일 생성이 끝나셨으면, Github으로 Push를 실행해봅니다.

여기에서 Build를 클릭하면 각 단계의 로그를 볼 수 있다.

Github Action에 Time Action 추가하기

다음 단계를 위해서 한가지 job을 추가해볼텐데요.
build 시점의 현재 시간을 확인하는 기능입니다.

다음 포스팅에서 build 파일명에 현재시간을 추가하기 위함입니다

deploy.yml

...
- name: Get current time
  uses: 1466587594/get-current-time@v2 
  id: current-time
  with:
    format: YYYY-MM-DDTHH-mm-ss # (1)
    utcOffset: "+09:00"

- name: Show Current Time
  run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}" # (2)
  shell: bash

(1) with: format: YYYY-MM-DDTHH-mm-ss

  • 1466587594/get-current-time action의 경우 기존의 Momentjs을 지원하기 때문에 동일한 포맷을 사용하면 됩니다.
  • utcOffset: "+09:00": 해당 action의 기준이 UTC이기 때문에 한국시간이 KST를 맞추기 위해서는 +9시간이 필요하여 offset을 추가합니다.

(2) ${{steps.current-time.outputs.formattedTime}}

  • (1)의 get-current-time 에서 지정한 포맷대로 현재 시간을 노출하게 됩니다.

Travis CI와 AWS S3 연동하기

원래 책에는 이렇게 되어있으나 저자의 블로그에는 AWS Beanstalk을 이용한게 있다. 어느정도 책을 따라가다가 Beanstalk을 이용해보자.

이하 블로그

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

이하는 블로그 글이다.
https://jojoldu.tistory.com/549?category=777282

Github Action & AWS Beanstalk 배포하기 - profile=local로 배포하기

profile=local, 즉, 운영 DB와 구글&네이버 OAuth 를 사용하지 않는 간단한 테스트 용도로만 배포할 예정입니다.

실제 운영 배포는 다음 시간에 진행할 예정입니다.
Github Action과 Beanstalk 연동된 환경 (즉, 이번 시간에 설정된 환경)를 구성하고 이를 기반으로 개선하는 과정으로 진행할 예정입니다.

AWS Beanstalk 생성하기

저번 시간에도 간략하게 소개 드렸지만, AWS Beanstalk은 AWS에 지원하는 PaaS (Platform as a Service) 입니다.

기존에 EC2를 직접 구성하고, Code Deploy를 통해 배포환경을 구성하고, 로드밸런서를 연결하고, 오토스케일링 그룹을 직접 생성해서 연결하는 등의 모든 행위가 이 Beanstalk에서는 자동으로 이루어지는데요.

이로인해서 개발자는 코드를 업로드하기만 하면 Elastic Beanstalk가 프로비저닝, 로드 밸런싱, Auto Scaling부터 시작하여 애플리케이션 상태 모니터링에 이르기까지 배포를 자동으로 처리합니다.

다만, 이렇게 자동으로 해주는 형태로 인해서 요금 걱정을 하실 수가 있는데요.
Beanstalk는 추가 비용 없이 구성한 AWS 리소스에 대해서만 요금을 지불하면 됩니다

이번에 저희가 사용할 AWS Beanstalk의 서비스는 2가지입니다.

  • EC2

  • LoadBalancer

    1-1. AWS Beanstalk 환경 생성하기

    대시보드에 보이는 새 환경 생성 버튼을 클릭합니다.

    환경에서는 웹 서버 환경을 선택합니다.

    추가 옵션 구성
    먼저 최상단에 사전 설정으로 되어있는 부분을 사용자 지정 구성으로 변경합니다.

단일 인스턴스 옵션으로 설정할 경우 로드 밸런서를 설정할 수 없어 Beanstalk으로 무중단 배포를 할 수가 없습니다.
그러니 꼭 사용자 지정 구성으로 진행합니다.

SSH 접속과 HTTP 접근이 가능하도록 보안그룹을 선택합니다.

용량
용량은 오토스케일링 그룹을 이야기합니다.
즉, 이 Beanstalk으로 운영될 인스턴스의 수를 몇대로 할 것인지 정한다고 보시면 되는데요.

프리티어로 사용할 것이기 때문에 1대로 유지합니다.

최대값을 1개 초과해서 적으시면 많은 트래픽이 들어올 경우 서버가 자동으로 증설되어 프리티어를 초과한 비용이 부담됩니다.

현재 설정으로 하시면 많은 트래픽이 오면 서버가 죽는 한이 있어도, 증설되진 않습니다.

롤링 업데이트와 배포

보안

만약 pem키를 한번도 생성해보신적 없다면 (즉, EC2 생성을 한번도 못해봤거나, key가 분실된 경우) 이 글을 참고하여 수동으로 pem키를 생성하시면 됩니다.

로드 밸런서
마지막으로 로드밸런서까지 선택합니다.

기존에 많이 사용한 로드밸런서는 Elastic Load Balancer (이하 ELB) 로 불리기도 했던 Class Load Balancer인데요.

ELB의 경우 먼저 나온 로드밸런서다보니 이후에 나온 Application Load Balancer (이하 ALB) 에 비해 성능과 기능면에서 부족한점이 많습니다.

로드밸런서 라우팅 기능이 ELB는 거의 없다시피하며, ALB는 다양한 라우팅 기능을 지원합니다.

다만, 먼저 나왔다보니 그만큼 국내 자료가 많긴하지만, 최근 대부분의 서비스는 ALB를 선택하다보니 저희 역시 ALB를 선택하겠습니다.

대규모 서비스를 운영하다보면 서버의 하드웨어 지표는 무리가 없는데 로드밸런서가 못버텨서 장애가 나기도 합니다.
이를 대응하기 위해 ELB에서는 미리 충분한 대역폭을 늘려놓을 수 있도록 pre-warming 이라는 작업을 AWS측으로 요청할 수 있는데요.
예정된 이벤트의 경우에는 이렇게 pre-warming이 가능한데, 갑작스런 트래픽에 관해서는 대응이 어려웠습니다.
반면 ALB의 경우 이런 갑작스런 트래픽에 관해서도 대응이 되기 때문에 선택하지 않을 이유가 없습니다.

생성
1-2의 모든 설정들이 끝나면 환경 생성 버튼을 클릭합니다.

애플리케이션 코드는 샘플 애플리케이션을 선택합니다.

그럼 아래와 같이 생성 로그가 출력 됩니다.

생성이 다 끝나시면 아래와 같이 애플리케이션과 환경을 볼 수 있습니다.

애플리케이션 하위에 환경이 있습니다.
이는 Elastic Benastalk은 컨셉상 하나의 애플리케이션을 호스팅하는데요.
그러다보니 개발환경/스테이지환경/프로덕션환경 등등 여러 환경으로 나뉠수 있도록 환경이라는 단위가 있는 것입니다.

자 그럼 Beanstalk 환경이 생성되는 동안 저희는 Github Action 작업을 진행하겠습니다.

2. IAM 인증키 Github Action에서 사용하기

같은 AWS 서비스가 아닌 외부 서비스인 Github Action에서는 TravisCI와 마찬가지로 AWS 서비스에 명령을 줄 수 있는 권한을 받아야 합니다.

Travis CI와 AWS ElasticBeanstalk 연동하기

외부 서비스가 AWS 서비스에 대한 명령 권한을 받는 방법으로는 IAM 사용자를 이용한 인증키 (accessKey, secretKey)가 있습니다.

그래서 해당 인증키를 먼저 발급 받겠습니다.

2-1. IAM 인증키 발급받기

AWS Beanstalk 생성 페이지를 두고, 새 페이지를 열어서 iam을 검색해봅니다.
그럼 아래와 같이 IAM 서비스가 나오는데요.
해당 페이지로 이동합니다.

accessKey, secretKey를 발급받을 수 있는 사용자 정보를 생성합니다.

기존 정책 연결에서는 AWS Beanstalk의 Access를 할당 받습니다.

Code Deploy때와 다르게 별도로 S3에 대한 권한이 필요하지 않습니다.
배포 파일을 그대로 Beanstalk으로 전달하기 때문에 S3를 통할 필요가 없습니다.

AdministratorAccess-AWSElasticBeanstalk

여러 사용자들 사이에서 식별 가능하도록 태그에는 Name을 지정합니다.

생성 되시면 아래와 같이 accessKey와 secretKey가 생성됩니다.

해당 내용을 복사를 하신 뒤 해당 프로젝트의 Github 페이지로 이동합니다.

Github 에서 상단 탭을 보시면 Settings가 보입니다.
클릭하신뒤, 좌측 사이드바의 Secrets -> New Repository secret 버튼을 차례로 클릭합니다.

그럼 아래와 같이 생성된 IAM 인증키 항목을 등록하시면 되는데요.

  • AWS_ACCESS_KEY_ID: IAM 엑세스키 ID
  • AWS_SECRET_ACCESS_KEY: IAM 비밀 엑세스 키
    로 채워주시면 됩니다.

AWS_ACCESS_KEY_ID

AWS_SECRET_ACCESS_KEY

다 생성 되시면 아래와 같이 secrets 항목에 2개의 키가 추가된 것을 확인할 수 있습니다.

자 그럼 위에서 등록한 key들을 Github Action에서 사용할 수 있도록 스크립트에 코드를 심어보겠습니다.

2-2. Github Action 스크립트 수정하기

이번에 사용할 Github Action 플러그인은 Beanstalk Deploy 입니다.

AWS CLI (커맨드라인)으로도 할 수 있지만, 해당 플러그인을 사용할 경우 아래와 같이 설정값만 채워주면 편하게 배포 코드를 작성할 수 있으니 이를 사용합니다.

- name: Deploy to EB
    uses: einaregilsson/beanstalk-deploy@v14
    with:
    aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    application_name: MyApplicationName
    environment_name: MyApplication-Environment
    version_label: 12345
    region: ap-northeast-2
    deployment_package: deploy.zip

14버전이 최신이니 einaregilsson/beanstalk-deploy@v14 를 선언만 하면됩니다.

몇번 언급드렸지만, 이 코드는 모두 2021년 1월을 기준으로 합니다.
이후에 플러그인/AWS 콘솔등의 변경이 있을 수 있음을 미리 말씀드립니다.

name: freelec-springboot2-webservice

on:
  push:
    branches:
      - master 
  workflow_dispatch: 

jobs:
  build:
    runs-on: ubuntu-latest 

    steps:
      - name: Checkout
        uses: actions/checkout@v2 

      - name: Set up JDK 1.8
        uses: actions/setup-java@v1 
        with:
          java-version: 1.8

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew 
        shell: bash

      - name: Build with Gradle
        run: ./gradlew clean build 
        shell: bash

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss 
          utcOffset: "+09:00"

      - name: Show Current Time
        run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}" 
        shell: bash

      - name: Generate deployment package # (1)
        run: |
          mkdir -p deploy
          cp build/libs/*.jar deploy/application.jar
          cp Procfile deploy/Procfile
          cp -r .ebextensions deploy/.ebextensions
          cp -r .platform deploy/.platform
          cd deploy && zip -r deploy.zip .

      - name: Deploy to EB # (2)
        uses: einaregilsson/beanstalk-deploy@v14
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: freelec-springboot2-webervice
          environment_name: freelec-springboot2-webervice
          version_label: github-action-${{steps.current-time.outputs.formattedTime}}
          region: ap-northeast-2
          deployment_package: deploy/deploy.zip

(1)

  • Gradle Build를 통해 만들어진 jar 파일을 Beanstalk에 배포하기 위한 zip 파일로 만들어줄 스크립트 입니다.

  • 빌드가 끝나면 해당 배포 Jar의 파일명을 application.jar로 교체합니다.

    • 매 빌드때마다 jar의 파일명이 버전과 타임스탬프로 파일명이 교체됩니다.
    • 그래서 Beanstalk 배포시에 매번 달라질 파일명을 찾아내기 보다는 하나로 통일해서 사용하도록 변경하였습니다.
  • application.jar 외에 3개의 파일/디렉토리 Procfile, .ebextensions, .platform 도 함께 zip에 포함시킵니다.

  • 3개 파일/디렉토리에 대해서는 아래 2-3에서 좀 더 상세하게 설명 드리겠습니다.

(2)

  • Beanstalk 플러그인을 사용하는 코드입니다.

  • 미리 생성해둔 IAM 인증키를 사용합니다.

  • 이전 시간 에 만들어준 현재 시간 플러그인을 통해 Beanstalk이 배포될때마다 유니크한 버저닝이 될 수 있도록 github-action-${{steps.current-time.outputs.formattedTime}} 코드를 추가하였습니다.

여기까지 하셨다면 배포 인프라 환경 구성은 끝이납니다.
남은 작업은 배포에 필요한 설정 파일들을 만들어보겠습니다.

위에서 언급한대로 Procfile, .ebextensions, .platform 3개에 대한 설정들입니다.

2-3. Beanstalk 애플리케이션 구성

3개 파일/디렉토리의 구조는 다음과 같이 됩니다.
(즉 셋 모두 프로젝트 루트에서 생성해주시면 됩니다.)

(deploy.yml은 저희가 기존에 만들어두었던 Github Action 스크립트 파일입니다.)

위에서부터 차례로 생성해보겠습니다.

첫번째는 .ebextensions 입니다.
Beanstalk은 시스템의 대부분을 AWS에서 자동으로 구성해주기 때문에 기존 EC2에 직접 설치할때처럼 사용할 순 없는데요.
그래서 직접 Custom 하게 사용할 수 있도록 설정할 수 있는 방법이 바로 .ebextensions 디렉토리입니다.

해당 디렉토리에 .config 파일 확장명을 가진 YAML이나 JSON 형태의 설정 코드를 두면 그에 맞춰 Beanstalk 배포시/환경 재구성시 사용하게 됩니다.

이번에 저희가 사용할 Custom 기능은 애플리케이션 실행 스크립트 생성입니다.
Beanstalk이 Github Action으로 전달 받은 zip파일 (배포 파일)이 압축이 풀리고 나서 어느 파일을 어떤 파라미터로 실행할지를 설정하는 스크립트라고 보시면 됩니다.

java -jar application.jar 하는 코드를 스크립트로 만든다고 보시면 됩니다.

자 그래서 실제로 코드를 보시면 아래와 같습니다.

.ebextensions/00-makeFiles.config

files:
    "/sbin/appstart" :
        mode: "000755"
        owner: webapp
        group: webapp
        content: |
            #!/usr/bin/env bash
            JAR_PATH=/var/app/current/application.jar

            # run app
            killall java
            java -Dfile.encoding=UTF-8 -jar $JAR_PATH
  • 아시다시피 /sbin 아래에 스크립트 파일을 두면 전역에서 실행 가능합니다.
  • 그래서 /sbin 아래에 appstart란 이름의 스크립트 파일을 만들고,
  • 권한은 755, 사용자는 webapp으로 하여 content 내용을 가진 스크립트 파일이 생성된다고 보시면 됩니다.
  • 여기서 만들어진 /sbin/appstart 스크립트 파일이 Procfile에서 실행됩니다.

.ebextensions 의 config에 대한 상세한 설명은 우아한형제들의 기술 블로그를 참고하시면 좋습니다.

다음으로는 Procfile입니다.
Beanstalk은 배포 파일을 전달 받고나면 .ebextensions를 비롯한 각종 설정파일들을 실행한 뒤에, 애플리케이션 실행 단계를 거치는데요.
이때 애플리케이션 실행 단계때 하는 행위는 이 Procfile을 실행하는 것 뿐입니다.

즉, Beanstalk 입장에서 배포 애플리케이션 실행이라는 것은 Procfile파일을 실행하는 것을 의미합니다.

그래서 위에서 .ebextensions/00-makeFiles.config 으로 만들어진 /sbin/appstart 스크립트를 실행하도록 코드를 구성합니다.

Procfile

web: appstart

마지막으로 리버스 프록시를 담당할 Nginx 설정을 합니다.

여기서 Nginx는 무중단 배포를 위한 것이 아닙니다.

제 저서에서는 Nginx를 이용해서 무중단 배포를 하는 방법을 소개 드렸는데요.

이 시리즈에서는 로드밸런서 (ALB) 가 그 역할을 대신 하기 때문에 Nginx에서는 단순히 임베디드 톰캣으로 요청을 보내는 역할만 할 예정입니다.

그래서 아래와 같이 config 파일을 생성합니다.

.platform/nginx/nginx.conf

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    33282;

events {
    use epoll;
    worker_connections  1024;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

  include       conf.d/*.conf;

  map $http_upgrade $connection_upgrade {
      default     "upgrade";
  }

  upstream springboot {
    server 127.0.0.1:8080;
    keepalive 1024;
  }

  server {
      listen        80 default_server;

      location / {
          proxy_pass          http://springboot;
          proxy_http_version  1.1;
          proxy_set_header    Connection          $connection_upgrade;
          proxy_set_header    Upgrade             $http_upgrade;

          proxy_set_header    Host                $host;
          proxy_set_header    X-Real-IP           $remote_addr;
          proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
      }

      access_log    /var/log/nginx/access.log main;

      client_header_timeout 60;
      client_body_timeout   60;
      keepalive_timeout     60;
      gzip                  off;
      gzip_comp_level       4;

      # Include the Elastic Beanstalk generated locations
      include conf.d/elasticbeanstalk/healthd.conf;
  }
}

Beanstalk에서의 Nginx 설정이 왜 .platform가 되었는지 궁금하신 분들은 이전 포스팅을 참고하시면 좋습니다.

Beanstalk 배포를 위한 설정파일들도 모두 생성되었습니다.

자 그럼 마지막으로 RDS 접속정보와 OAuth 인증 정보 없이 실행될 수 있도록 프로젝트 코드를 변경해보겠습니다.

3. Github Action으로 Beanstalk 배포하기

제 저서를 그대로 하셨다면, profile=local로 실행할 경우에도 OAuth 정보가 필요할텐데요.

이번 시간에는 OAuth 정보 없이도 애플리케이션이 실행 가능해야하기 때문에 profile을 상세하게 분리해볼 예정입니다.

  • profile=local

    • 이번 시간에 사용될 배포환경
    • RDS나 구글/네이버 등의 OAuth정보 없이 단순히 실행만 가능한 상태
    • 테스트용 OAuth 토큰 정보와 H2 DB만 사용하는 상태
  • profile=local-real

    • 로컬 PC에서 개발용으로 사용할 환경
    • H2 DB를 사용하나, OAuth 정보는 실제 토큰 값을 사용하는 상태
  • profile=real

    • 서비스 배포 환경
    • RDS와 OAuth 정보를 모두 사용하는 상태

이렇게 나누는 이유는 RDS (DB) 접속 정보나 구글/네이버 등의 OAuth Key를 이번 챕터에서 다루기에는 범위가 너무 크기 때문입니다.

해당 정보들은 외부에 공개되면 안되기 때문에 별도의 우회하는 방법을 선택해야하는데요.

이것까지 포함하기에는 이번 챕터의 범위가 너무 크기 때문에 테스트용 정보만 갖고 있는 profile을 만들어서 사용하겠습니다.

기존의 local profile은 로컬 PC에서 개발한다는 의미로 구글/네이버 등의 OAuth Key를 사용하는 환경이였으나, 이를 제거하고 local-real에서 구글/네이버 등의 OAuth Key를 가진다고 보시면 됩니다.

3-1. application.properties 정리

application.properties

spring.profiles.active=local
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.session.store-type=jdbc

spring.profiles.group.local-real=local-real, oauth

기존의 profiles.include 옵션이 아닌 profiles.group.그룹명 이라는 생소한 옵션이 보이실텐데요.

이는 Spring Boot 2.4 부터 profile 사용 방식이 변경되었기 때문입니다.

자세한 내용은 아래 2개글을 참고해주세요.

간단하게 정리하면 spring.profiles.group.local-real=local-real, oauth 로 선언되면 앞으로 local-real로 실행할 경우 local-realoauth profile이 묶음으로 포함되어 실행된다는 것입니다.

위 설정으로 이제 공통 설정들은 끝이나, 이번 Beanstalk에 테스트 용도로 배포할 profile 설정을 해보겠습니다.

application.properties

spring.profiles.active=local
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.session.store-type=jdbc

spring.profiles.group.local-real=local-real, oauth

application-local.properties

spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.datasource.hikari.jdbc-url=jdbc:h2:mem:testdb;MODE=MYSQL
spring.datasource.hikari.username=sa

spring.h2.console.enabled=true
spring.session.store-type=jdbc

# Test OAuth

spring.security.oauth2.client.registration.google.client-id=test
spring.security.oauth2.client.registration.google.client-secret=test
spring.security.oauth2.client.registration.google.scope=profile,email

마지막으로 로컬 PC에서 실제 개발에 사용될 (OAuth정보를 사용하되, H2 DB를 사용하는) profile을 설정하겠습니다.

application-local-real.properties

spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.datasource.hikari.jdbc-url=jdbc:h2:mem:testdb;MODE=MYSQL
spring.datasource.hikari.username=sa

spring.h2.console.enabled=true
spring.session.store-type=jdbc

Beanstalk 배포

Push를 하시면 아래와 같이 Github Action 빌드로그를 확인할 수 있습니다.

Github Action 에서 성공 메세지를 확인하셨다면 AWS Beanstalk 페이지를 보시면 다음과 같이 배포가 성공된 것을 확인할 수 있습니다.

거의 다왔는데 마지막 오류 발생

다른 블로그: https://wbluke.tistory.com/39
https://wbluke.tistory.com/41?category=418851

이상 블로그

Travis CI와 AWS S3 연동하기

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

실제 패보는 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에 접근할 수 있도록 해야 한다.

S3 버킷 생성

S3 (Simple Storage Service)에 관해 설정을 진행하겠다. AWS의 S3 서비스는 일종의 파일 서버다. 순수하게 파일들을 저장하고 접근 권한을 관리, 검색 등을 지원하는 파일 서버의 역할을 한다.

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

https://wbluke.tistory.com/39

여기서 env 에잇는 프로젝트 네임과 S3 네임을 맞췄어야했는데 맞추지 않아 실패헀다. 맞추고 나니 성공!

Github Actions + CodeDeploy + Nginx 로 무중단 배포하기 (2)

먼저 CodeDeploy에 대해 간단하게 소개하자면, 애플리케이션 배포를 자동화하는 AWS 의 배포 서비스입니다.
EC2, AWS Lambda 와 같은 서비스에 배포를 할 수 있고, 현재위치 배포나 블루/그린 배포와 같은 무중단 배포를 지원합니다.
한 번 구축해 놓으면 이후로는 배포가 매우 간편하고 AWS 콘솔을 통해 제어하면서 배포 과정을 확인할 수 있기 때문에 많은 분들이 이 서비스를 이용하여 배포 플로우를 구축합니다.

CodeDeploy 의 배포 과정은 다음과 같습니다.

  • 개발한 애플리케이션 최상단 경로에 AppSpec.yml 이라는 파일을 추가합니다.
    • AppSpec.yml 은 배포에 필요한 모든 절차를 적어둔 명세서라고 생각하시면 됩니다.
  • CodeDeploy에 프로젝트의 특정 버전을 배포해 달라고 요청하면, CodeDeploy는 배포를 진행할 EC2 인스턴스에 설치돼 있는 CodeDeploy Agent들과 통신하며 Agent들에게 요청받은 버전을 배포해 달라고 요청합니다.
  • 요청 받은 Agent들은 코드 저장소에서 프로젝트 전체를 서버에 내려받고, AppSpec.yml 파일을 읽어 해당 파일에 적힌 절차대로 배포를 진행합니다.
  • Agent는 배포를 진행한 후 CodeDeploy에게 성공/실패 등의 결과를 알려줍니다.

배포할 EC2 세팅하기

먼저 우리가 애플리케이션을 배포할 EC2 를 하나 생성하겠습니다.
EC2 인스턴스를 띄우는 방법은 이미 많은 자료가 있기 때문에 생략하겠습니다.

ssh 로 새롭게 생성한 인스턴스에 접속해주세요!

다음으로는 CodeDeploy Agent 를 설치하겠습니다.

# 패키지 매니저 업데이트, ruby 설치 
sudo yum update 
sudo yum install ruby 
sudo yum install wget 

# 서울 리전에 있는 CodeDeploy 리소스 키트 파일 다운로드 
cd /home/ec2-user 
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install 

# 설치 파일에 실행 권한 부여 
chmod +x ./install 

# 설치 진행 및 Agent 상태 확인 
sudo ./install auto
sudo service codedeploy-agent status

CodeDeploy Agent 가 실행되었음을 확인할 수 있습니다!

만약 status 확인 시 Agent 가 실행중이지 않다면 다음 커맨드를 수행합니다.

sudo service codedeploy-agent start

EC2 에 IAM 역할 부여하기

이전 글에서 IAM 권한 사용자를 생성하여 Github Actions 가 해당 사용자의 권한을 사용할 수 있도록 설정한 것을 기억하실텐데요.
이번에는 EC2 에 S3, CodeDeploy 권한 정책을 부여해 보겠습니다.
IAM 사용자는 아이디/비밀번호 기반으로 외부 프로그램이 사용자처럼 접근하는 것이라면, IAM 역할은 다른 계정의 IAM 사용자, 혹은 다른 AWS 서비스가 수행하는 역할 권한을 부여하는 것이라고 생각하시면 됩니다.

appspec.yml

# appspec.yml

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/freelec-springboot2-webservice/ # 프로젝트 이름
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

deploy.yml

name: freelec-springboot2-webservice

on:
  push:
    branches:
      - master # (1) 실습하시는분들은 master로 하시면 됩니다. (저는 별도 브랜치로 지정)
  workflow_dispatch: # (2) 수동 실행

env: # 새로 추가한 부분
  S3_BUCKET_NAME: logging-system-deploy2
  PROJECT_NAME: freelec-springboot2-webservice


jobs:
  build:
    runs-on: ubuntu-latest # (3)

    steps:
      - name: Checkout
        uses: actions/checkout@v2 # (4)

      - name: Set up JDK 1.8
        uses: actions/setup-java@v1 # (5)
        with:
          java-version: 1.8

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew # (6)
        shell: bash

      - name: Build with Gradle
        run: ./gradlew clean build # (7)
        shell: bash

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss # (1)
          utcOffset: "+09:00"

      - name: Show Current Time
        run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}" # (2)
        shell: bash

      - name: Generate deployment package # (1)
        run: |
          mkdir -p deploy
          cp build/libs/*.jar deploy/application.jar
          cp Procfile deploy/Procfile
          cp -r .ebextensions deploy/.ebextensions
          cp -r .platform deploy/.platform
          cd deploy && zip -r deploy.zip .
      ### 새로 추가한 부분 ###
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA.zip .
        shell: bash

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip

      ### 새로 추가한 부분 ###
      - name: Code Deploy
        run: aws deploy create-deployment --application-name logging-system-deploy --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name develop --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

여기까지가 이번 단계의 끝입니다!

Github Actions에서 배포를 실행하기 전에 한 가지 진행해야 하는 부분이 있는데요.
지금까지의 과정 순서를 똑같이 따라하셨다면, CodeDeploy Agent를 시작한 후에 IAM Role을 EC2에 부여했기 때문에 아마 Agent가 IAM Role을 가지고 있지 않아 배포가 실패할 것입니다.

EC2에 접속해서 다음 커맨드로 Agent를 재시작해주세요!

sudo service codedeploy-agent restart

ec2-user home 디렉토리에 S3에서 받아온 프로젝트가 있는 것을 확인할 수 있습니다!
물론 아직 진짜 배포를 하도록 스크립트를 짜지는 않았지만, 적어도 각 서비스 간 통신은 잘 이루어졌다고 생각할 수 있습니다.

여기까지 완료했다면 이제 마지막 단계인 Nginx 무중단 배포를 진행하러 가보겠습니다.

Github Actions + CodeDeploy + Nginx 로 무중단 배포하기 (3)

Nginx는 널리 쓰이는 웹 서버 중 하나입니다.
동적 처리를 주로 담당하는 WAS(Web Application Server)와는 다르게 웹 서버(Web Server)는 정적 자원에 대한 응답을 내려주는 역할을 가지고 있는데요.
Nginx는 정적 자원의 처리 외에도 proxy 서버의 역할이나, reverse proxy 서버의 역할 등 여러방면에서 높은 활용도를 보여줍니다.

여기서는 Nginx가 CodeDeploy Agent에 의해 두 WAS간의 스위칭 역할을 담당하도록 구성해 보겠습니다.

Nginx 설치와 설정

먼저 Nginx를 설치하겠습니다.

EC2에 ssh로 접속하여 다음 커맨드를 수행합니다

sudo yum install nginx

그럼 설치가 되는 듯 하였으나, 다음과 같이 Amazon Linux 에서의 설치법을 따로 안내해줍니다.

sudo amazon-linux-extras install nginx1
sudo nginx -v # 설치 버전 확인

설치 후 /etc/nginx/ 로 이동해 보시면 다양한 nginx 설치파일들을 보실 수 있는데요.
우리가 관심있게 보아야 할 것은 설정파일인 nginx.conf 파일입니다.

파일 수정이 필요하니 sudo 권한으로 nginx.conf 파일을 엽니다.

sudo vim /etc/nginx/nginx.conf

    ...
        include /home/ec2-user/service_url.inc;

        location / {
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header Host $http_Host;
            proxy_pass $service_url;
        }
   ...
  • include
    • 다른 곳에 존재하는 설정 파일 등을 불러올 수 있습니다.
  • proxy_pass
    - 우리가 지정할 $service_url로 요청을 보낼 수 있도록 하는 프록시 설정입니다.
    include 로 불러올 파일 경로에 다음과 같이 파일을 생성하고 $service_url 변수를 설정하겠습니다.
vim /home/ec2-user/service_url.inc
# service_url.inc

set $service_url http://127.0.0.1:8081;

이러면 nginx 설정은 끝입니다!

다음 명령어로 nginx를 시작하고 nginx의 status를 확인할 수 있습니다.

sudo service nginx start 
sudo service nginx status

배포 스크립트 추가

마지막으로 프로젝트에 CodeDeploy Agent가 참고하여 배포를 진행하기 위한 스크립트들을 추가해보도록 하겠습니다.

appspec.yml에 다음과 같이 스크립트를 추가합니다.

appspec.yml

# appspec.yml

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/freelec-springboot2-webservice/ # 프로젝트 이름
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user
    
### 새로 추가한 부분 ### 
hooks: 
  ApplicationStart: 
    - location: scripts/run_new_was.sh 
      timeout: 180 
      runas: ec2-user 
    - location: scripts/health_check.sh 
      timeout: 180 
      runas: ec2-user 
    - location: scripts/switch.sh 
      timeout: 180 
      runas: ec2-user

프로젝트 최상단에 scripts 라는 디렉토리를 만들고 다음과 같이 세 개의 파일을 만들겠습니다!

# run_new_was.sh 

# !/bin/bash 
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1) 
TARGET_PORT=0 

echo "> Current port of running WAS is ${CURRENT_PORT}." 

if [ ${CURRENT_PORT} -eq 8081 ]; then 
  TARGET_PORT=8082 
elif [ ${CURRENT_PORT} -eq 8082 ]; then 
  TARGET_PORT=8081 else echo "> No WAS is connected to nginx" 
fi 

TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+') 

if [ ! -z ${TARGET_PID} ]; then 
  echo "> Kill WAS running at ${TARGET_PORT}." 
  sudo kill ${TARGET_PID} 
fi 

nohup java -jar -Dserver.port=${TARGET_PORT} /home/ec2-user/freelec-springboot2-webservice/build/libs/* > /home/ec2-user/nohup.out 2>&1 & 
echo "> Now new WAS runs at ${TARGET_PORT}." 
exit 0
  • 새로운 WAS를 띄우는 스크립트입니다.
    • service_url.inc 에서 현재 서비스를 하고 있는 WAS의 포트 번호를 읽어옵니다.
    • 현재 포트 번호가 8081이면 새로 WAS를 띄울 타겟 포트는 8082, 혹은 그 반대 상황이라면 8081을 지정합니다.
    • 만약 타겟포트에도 WAS가 떠 있다면 kill하고 새롭게 WAS를 띄웁니다.
  • nohup
    • 터미널 엑세스가 끊겨도 실행한 프로세스가 계속 동작하게 합니다.
    • 마지막의 &는 프로세스가 백그라운드로 실행되도록 해줍니다.
# health_check.sh

# !/bin/bash

# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

# Toggle port Number
if [ ${CURRENT_PORT} -eq 8081 ]; then
  TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
  TARGET_PORT=8081
else
  echo "> No WAS is connected to nginx"
  exit 1
fi

echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}' ..."

for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10
do
  echo "> #${RETRY_COUNT} trying..."
  RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${TARGET_PORT}/health)

  if [ ${RESPONSE_CODE} -eq 200 ]; then
    echo "> New WAS successfully running"
    exit 0
  elif [ ${RETRY_COUNT} -eq 10 ]; then
    echo "> Health check failed."
    exit 1
  fi
  sleep 10
done

새로 띄운 WAS가 완전히 실행되기까지 health check 하는 스크립트입니다.

# switch.sh

# !/bin/bash
# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

echo "> Nginx currently proxies to ${CURRENT_PORT}."

# Toggle port number
if [ ${CURRENT_PORT} -eq 8081 ]; then
  TARGET_PORT=8082 
elif [ ${CURRENT_PORT} -eq 8082 ]; then 
  TARGET_PORT=8081
else 
  echo "> No WAS is connected to nginx" 
  exit 1 
fi 

# Change proxying port into target port 
echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ec2-user/service_url.inc 

echo "> Now Nginx proxies to ${TARGET_PORT}." 

# Reload nginx 
sudo service nginx reload 

echo "> Nginx reloaded."
  • nginx 리로드를 통해 서비스하는 포트를 스위칭하는 스크립트입니다.
    • sudo service nginx reload 는 nginx 서버의 재시작 없이 바로 새로운 설정값으로 서비스를 이어나갈 수 있도록 합니다.
    • sudo service nginx restart 는 말그대로 서버의 shutdown 이후 재시작하는 명령이므로 의도하지 않았다면 주의해야 합니다.
  • tee
    • 출력 내용을 파일로 만들어주는 커맨드입니다.
    • 새로 띄운 WAS의 포트를 nginx가 읽을 수 있도록 service_url.inc에 내용을 덮어씁니다.

이제 모든 준비가 끝났습니다!
무중단 배포를 진행하기 전에, 현재 서버에 아무런 WAS가 떠 있지 않기 때문에 8081 포트에 WAS를 새로 한번 띄워보겠습니다.

EC2에 접속하여 프로젝트 내에 있는 jar 파일을 실행하겠습니다.

nohup java -jar -Dserver.port=8081 /home/ec2-user/freelec-springboot2-webservice//build/libs/* &

이제 드디어 무중단 배포를 진행해볼 차례입니다.

내용을 바꾸고 , 커밋 - 푸시 후 Github Actions에서 배포를 진행해봅시다.

서버의 중단 없이 한순간에 버전이 바뀌는 것을 볼 수 있습니다! (드디어!)

ps -ef | grep java 커맨드를 통해 배포 전과 배포 후를 비교해보면 8082 포트로 새로운 서버가 실행된 것을 볼 수 있습니다.

그리고 tail service_url.inc로 내용을 확인해보시면 우리가 작성한 스크립트가 Nginx가 바라보는 포트를 8082로 변경한 것도 보실 수 있습니다!

이제 그 다음 배포를 한번 더 진행한다고 하면, 새로운 서버는 기존 8081 포트의 애플리케이션을 종료하고 새로 뜰 것이고, Nginx는 8081 포트를 서비스할 것입니다.

현재 이 배포 구성은 V2 버전의 WAS가 서비스를 하고 있으면서 이전 버전인 V1의 WAS가 서비스를 하지 않는데도 백그라운드에 그대로 떠 있는 상황인데요.
배포 직후 모니터링 과정에서 롤백해야 하는 상황이 온다면 롤백용 스크립트를 추가해서 V1 버전의 WAS를 다시 서비스하도록 Nginx를 reload하는 방식을 적용할 수도 있습니다.
또는 서비스 하는 내내 두 개의 WAS를 불필요하게 띄워놓을 필요가 없겠다는 생각이 드신다면, 배포 직후 V1 버전의 WAS가 롤백 대비용으로 떠 있다가, 모니터링을 진행하고 일정 시간이 지나면 해당 WAS를 종료하는 스크립트도 추가할 수 있을 것입니다.

0개의 댓글