배포하는 동안 애플리케이션이 종료된다는 문제가 있었습니다. 긴 기간은 아니지만, 새로운 Jar가 실행되기 전까진 기존 Jar를 종료시켜 놓기때문에 서비스가 중단됩니다.

반면 24시간 서비스하는 네이버나 카카오톡 같은 경우 배포하는 동안 서비스가 정지되지는 않습니다. 그럼, 어떻게 서비스 중단 없이 배포를 계속할 수 있는지 배워봅시다.

 

 

 

무중단 배포 소개

예전에는 배포는 팀의 아주 큰 이벤트이기 때문에 다 같이 코드를 합치는 날과 배포를 하는 날을 정하고 진행했습니다. 특히 배포일에는 사용자가 적은 새벽 시간에 개발자들이 모두 남아 배포 준비를 해야만 했고 배포가 잦아질 때는 새벽마다 남아야만 했습니다.

더군다나 배포를 하고 나서 치명적인 문제가 발견되면 어떻게 해야 할까요? 새벽 시간에 부랴부랴 문제를 해결하다가, 사용자 유입이 많아지는 아침이 되면 긴급점검 공지를 올리고 수정해야만 했습니다. 실제로 게임을 할 때 이런공지를 많이 보곤했었습니다.

이런 수고스러움을 덜기 위해 기존 서비스를 정지하지 않고, 배포할 수 있는 방법들을 찾기 시작 했고 이를 무중단 배포라고 합니다.

 

무중단 배포 방식에는 몇 가지가 있습니다.

  • AWS에서 블루 그린(Blue-Green) 무중단 배포
  • 도커를 이용한 웹서비스 무중단 배포

이외에도 L4 스위치를 이용한 무중단 배포 방법도 있지만, L4가 워낙 고가의 장비이다 보니 대형 인터넷 기업 외에는 쓸 일이 거의 없다고합니다.

 

이 책에서는 NGINX를 이용한 무중단 배포를 진행합니다. NGINX는 웹 서버, 리버스 프록시, 캐싱, 로드 밸런싱, 미디어 스트리밍 등을 위한 오픈소스 소프트웨어입니다. 이전에 아파치가 대세였던 자리를 완전히 빼앗은 가장 유명한 웹 서버이자 오픈소스입니다. 고성능 웹 서버이기때문에 대부분 서비스들이 현재는 NGINX를 사용하고 있습니다.

 

NGINX가 가지고 있는 여러 기능 중 리버스 프록시가 있습니다. 리버스 프록시란 NGINX가 외부의 요청을 받아 백엔드 서버로 요청을 전달하는 행위를 이야기합니다. 리버스 프록시 서버(NGINX)는 요청을 전달하고, 실제 요청에 대한 처리는 뒷단의 웹 애플리케이션 서버들이 처리합니다. 

 

우리는 이 리버스 프록시를 통해 무중단 배포 환경을 구축할 예정입니다. NGINX를 이용한 무중단 배포를 하는 이유는 가장 저렴하고 쉽기 때문입니다. 기존에 쓰던 EC2에 그대로 적용하면 되므로 배포를 위해 AWS EC2 인스턴스가 하나 더 필요하지 않습니다. 추가로 이 방식은 꼭 AWS와 같은 클라우드 인프라가 구축되어 있지 않아도 사용할 수 있는 범용ㅇ적인 방법입니다. 즉, 개인 서버 혹은 사내 서버에서도 동일한 방식으로 구축할 수 있으므로 사용처가 많습니다.

 

구조는 간단합니다. 하나의 EC2 혹은 리눅스 서버에 NGINX 1대와 스프링 부트 Jar를 2대 사용하는 것입니다.

  • NGINX는 80(http), 443(https) 포트를 할당합니다.
  • 스프링 부트1은 8081포트로 실행합니다.
  • 스프링 부트2는 8082포트로 실행합니다.

NGINX 무중단 배포 1은 아래와 같은 구조가 됩니다.

NGINX 무중단 배포 1

운영 과정을 아래와 같습니다.

  1. 사용자는 서비스 주소로 접속합니다. (80 or 443 port)
  2. NGINX는 사용자의 요청을 받아 현재 연결된 스프링 부트로 요청을 전달합니다. 
  3. 스프링 부트2는 NGINX와 연결된 상태가 아니니 요청받지 못합니다.

1.1 버전으로 신규 배포가 필요하면, NGINX와 연결되지 않은 스프링 부트2(8082 port)로 배포합니다.

 

NGINX 무중단 배포 2

  1. 배포하는 동안에도 서비스는 중단되지 않습니다. (NGINX는 스프링 부트1을 바라보기 때문)
  2. 배포가 끝나고 정상적으로 스프링 부트2가 구동 중인지 확인합니다.
  3. 스프링 부트2가 정상 구동 중이면 nginx reload 명령어를 통해 8081대신에 8082를 바라보도록합니다.
  4. nginx reload는 0.1초 이내에 완료됩니다.

이후 1.2 버전 배포가 필요하면 이번에는 스프링 부트1로 배포합니다.

 

이렇게 구성하게 되면 전체 시스템 구조는 아래와 같습니다.

 

무중단 배포 전체 구조

기존 구조에서 EC2 내부의 구조만 변경된 것입니다.

 

 

 

NGINX 설치와 스프링 부트 연동하기

먼저 EC2에 NGINX를 설치합시다.

 

  • NGINX 설치

EC2에 접속해서 아래 명령어로 NGINX를 설치합니다.

sudo amazon-linux-extras install nginx1

설치가 완료되었으면 아래 명령어로 nginx를 실행합니다.

sudo service nginx start

아래 로그가 나오면 정상 실행하는 것 입니다.

Redirecting to /bin/systemctl start nginx.service

 

  • 보안 그룹 추가

먼저 nginx의 포트번호를 보안 그룹에 추가하겠습니다. nginx의 포트번호는 기본적으로 80입니다. 해당 포트 번호가 보안 그룹에 없으니 ec2->보안 그룹-> ec2 보안 그룹 선택 -> 인바운트 편집으로 차례로 이동해서 변경해줍니다.

 

  • 리다이렉션 주소 추가

8080이 아닌 80포트로 주소가 변경되니 구글과 네이버 로그인에도 변경된 주소를 등록해야만 합니다. 기존에 등록된 리디렉션 주소에서 8080 부분을 제거하여 추가로 등록합니다.

 

 

추가한 후에는 EC2의 도메인으로 접근하되, 8080 포트를 제거하고 접근해 봅니다. 그럼 NGINX 웹 페이지를 볼 수 있습니다. 이제 스프링 부트와 연동해봅시다.

 

 

  • NGINX와 스프링 부트 연동

NGINX가 현재 실행 중인 스프링 부트 프로젝트를 바라볼 수 있도록 프록시 설정을 하겠습니다. NGINX 설정파일을 아래 코드를 통해 열어봅시다.

sudo vim /etc/nginx/nginx.conf

설정 내용 중 server 아래의 location / 부분을 찾아서 아래와 같이 추가합니다.

   location / {
        proxy_pass http://localhost:8080;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
    }
  • proxy_pass
    • nginx로 요청이오면 http://locahost:8080으로 전달합니다.
  • proxy_set_header XXX
    • 실제 요청 데이터를 header의 각 항목에 할당합니다.
    • 예) proxy_set_header X-Real-IP $remote_addr: Request Header의 X-Real-IP에 요청자의 IP를 저장합니다.

수정이 끝났으면 :wq 명령어로 저장하고 종료한 뒤, 아래 코드로 nginx를 재시작 하겠습니다.

sudo service nginx restart

다시 브라우저로 포트번호 없이 접속하면 스프링 부트 프로젝트가 나오는 것을 볼 수 있습니다.

 

 

 

무중단 배포 스크립트 만들기

무중단 배포 스크립트 작업 전에 API를 하나 추가하겠습니다. 이 API는 이후 배포 시에 8081을 쓸지, 8082를 쓸지 판단하는 기준이 됩니다.

 

profile API 추가

profileController를 만들어 아래와 같이 간단한 API 코드를 추가합니다. web 디렉토리에 생성합니다.

ProfileController 클래스 생성

@RequiredArgsConstructor
@RestController
public class ProfileController {
    private final Environment env;

    @GetMapping("/profile")
    public String profile() {
        List<String> profiles = Arrays.asList(env.getActiveProfiles());
        List<String> realProfiles = Arrays.asList("real", "real1", "real2");
        String defaultProfile = profiles.isEmpty()? "default" : profiles.get(0);

        return profiles.stream()
                .filter(realProfiles::contains)
                .findAny()
                .orElse(defaultProfile);
    }
}
  • evn.getActiveProfiles()
    • 현재 실행 중인 ActiveProfile을 모두 가져옵니다.
    • 즉, real, oauth, real-db 등이 활성화되어 있다면 3개가 모두 담겨있습니다.
    • 여기서 real, real1, real2는 모두 배포에 사용될 profile이라 이 중 하나라도 있으면 그 값을 반환하도록합니다.
    • 실제 이번 무중단 배포에서는 real1과 real2만 사용되지만, step2를 다시 사용해볼 수도있으니 real도 남겨둡니다.

해당 코드가 잘 작동하는지 테스트 코드를 작성해보겠습니다. 해당 컨트롤러는 특별히 스프링 환경이 필요하지는 않습니다. 그래서 @SpringBootTest없이 테스트 코드를 작성합니다. test에 web 디렉토리에 테스트 클래스를 생성해줍시다.

ProfileControllerUnitTest 클래스 생성

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.security.core.parameters.P;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class ProfileControllerUnitTest {

    @Test
    public void real_profile_조회() {
        //given
        String expectedProfile = "real";
        MockEnvironment env = new MockEnvironment();
        env.addActiveProfile(expectedProfile);
        env.addActiveProfile("oauth");
        env.addActiveProfile("real-db");

        ProfileController controller = new ProfileController(env);

        //when
        String profile = controller.profile();

        //then
        assertThat(profile).isEqualTo(expectedProfile);
    }

    @Test
    public void real_profile_없으면_첫번째_조회() {
        //given
        String expectedProfile = "oauth";
        MockEnvironment env = new MockEnvironment();

        env.addActiveProfile(expectedProfile);
        env.addActiveProfile("real-db");

        ProfileController controller = new ProfileController(env);

        //when
        String profile = controller.profile();

        //then
        assertThat(profile).isEqualTo(expectedProfile);
    }

    @Test
    public void active_profile_없으면_default_조회() {
        //given
        String expectedProfile = "default";
        MockEnvironment env = new MockEnvironment();
        ProfileController controller = new ProfileController(env);

        //when
        String profile = controller.profile();

        //then
        assertThat(profile).isEqualTo(expectedProfile);
    }
}

 

Security.config에 인증없이 호출될 수 있게 andMatcher에 /profile 추가

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOauth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().headers().frameOptions().disable()
                .and()
                    .authorizeRequests()
                    .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout()
                    .logoutSuccessUrl("/")
                .and()
                    .oauth2Login()
                    .userInfoEndpoint()
                    .userService(customOauth2UserService);
    }
}

그리고 SecurityConfig 설정이 잘 되었는지도 테스트 코드로 검증합니다. 이 검증은 스프링 시큐리티 설정을 불러와야 하니 @SpringBootTest를 사용하는 테스트 클래스를 하나 더 추가합니다.

ProfileControllerTest 클래스 생성

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ProfileControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void profile은_인증없이_호출된다() throws Exception {
        String expected = "default";

        ResponseEntity<String> response = restTemplate.getForEntity("/profile", String.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo(expected);
    }
}

여기까지 모든 테스트가 성공했다면 깃허브로 푸시하여 배포합니다. url/profile로 접속해서 profile이 잘 나오는지 확인합니다.

 

 

real1, real2 profile 생성

현재 EC2 환경에서 실행되는 profile은 real밖에 없습니다. 해당 profile은 Travis CI 배포 자동화를 위한 profile이니 무중단 배포를 위한 profile 2개를 src/main/resources 아래에 추가합시다.

application-real1.properties 생성

server.port=8081
spring.profiles.include=oauth,real-db
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.session.store-type=jdbc

application-real2.properties 생성

server.port=8082
spring.profiles.include=oauth,real-db
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.session.store-type=jdbc

2개의 profile은 real profile과 크게 다른 점은 없지만 한 가지가 다릅니다. server.port가 8080가 아닌 8081/8082로 되어있습니다. 이제 깃에 푸시해줍시다.

 

 

NGINX 설정 수정

무중단 배포의 핵심은 NGINX 설정입니다. 배포 때마다 nginx의 프록시 설정(스프링 부트로 요청을 흘려보내는)이 순식간에 교체됩니다. 여기서 프록시 설정이 교체될 수 있도록 설정을 추가하겠습니다.

 

nginx 설정이 모여있는 /etc/nginx/conf.d/에 service-url.inc라는 파일을 아래 명령어로 하나생성합니다.

sudo vim /etc/nginx/conf.d/service-url.inc

그리고 다음 코드를 입력합니다.

set $service_url http://127.0.0.1:8080;

저장하고 종료한 뒤, 해당 파일은 nginx가 사용할 수 있게 설정합니다. 아래와 같이 nginx.conf 파일을 열어줍니다.

sudo vim /etc/nginx/nginx.conf

location / 부분을 찾아 아래와 같이 변경해줍니다.

저장하고 종료한 뒤 재시작합니다.

sudo service nginx restart

다시 브라우저에서 정상적으로 호출되는지 확인합니다. 확인되었다면 nginx 설정까지 잘 된 것 입니다.

 

 

배포 스크립트들 작성

먼저 step2와 중복되지 않기 위해 EC2에 step3 디렉토리를 생성합니다.

mkdir ~/app/step3 && mkdir ~/app/step3/zip

무중단 배포는 앞으로 step3를 사용하겠습니다. 그래서 appspec.yml 역시 step3로 배포되도록 수정합니다.

version: 0.0 # CodeDeploy 버전을 명시한다.
os: linux
files:
  - source:  / # 전체 파일을 나타낸다.
    destination: /home/ec2-user/app/step3/zip/ # source로 지정된 전체 파일이 받을 위치이다.
    overwrite: yes # 기존 파일들이 있으면 덮어 쓸지를 결정한다.

무중단 배포를 진행할 스크립트들은 총 5개 입니다.

  • stop.sh : 기존 nginx에 연결되어 있진 않지만, 실행 중이던 스프링 부트 종료
  • start.sh : 배포할 신규 버전 스프링 부트 프로젝트를 stop.sh로 종료한 'profile'로 실행
  • health.sh : 'start.sh'로 실행시킨 프로젝트가 정상적으로 실행됐는지 체크
  • switch.sh : nginx가 바라보는 스프링 부트를 최신 버전으로 변경
  • profile.sh : 앞선 4개 스크립트 파일에서 공용으로 사용할 'profile'와 포트 체크 로직

appspec.yml에 앞선 스크립트를 사용하도록 설정합니다.

hooks:
  AfterInstall:
    - location: stop.sh # 엔진엑스와 연결되어 있지 않은 스프링 부트를 종료합니다.
      timeout: 60
      runas: ec2-user
  ApplicationStart:
    - location: start.sh # 엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작합니다.
      timeout: 60
      runas: ec2-user
  ValidateService:
    - location: health.sh # 새 스프링 부트가 정상적으로 실행됐는지 확인 합니다.
      timeout: 60
      runas: ec2-user

Jar 파일이 복사된 이후부터 차례로 앞선 스크립트들이 실행된다고 보면 됩니다. 다음은 각 스크립트 입니다. 이 스크립트들 역시 scripts 디렉토리에 추가합니다.

health.sh 생성

#!/usr/bin/env bash

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

IDLE_PORT=$(find_idle_port)

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

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

  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

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

  echo "> Health check 연결 실패. 재시도..."
  sleep 10
done
  1. 엔진엑스와 연결되지 않은 포트로 스프링 부트가 잘 수행되었는지 체크
  2. 정상 확인 후 엔진엑스 프록시 설정 변경(switch_proxy)
  3. 엔진엑스 프록시 설정 변경은 switch.sh에서 수행

 

profile.sh 생성

#!/usr/bin/env bash

# bash는 return value가 안되니 *제일 마지막줄에 echo로 해서 결과 출력*후, 클라이언트에서 값을 사용한다

# 쉬고 있는 profile 찾기: real1이 사용중이면 real2가 쉬고 있고, 반대면 real1이 쉬고 있음
function find_idle_profile()
{
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)

    if [ ${RESPONSE_CODE} -ge 400 ] # 400 보다 크면 (즉, 40x/50x 에러 모두 포함)
    then
        CURRENT_PROFILE=real2
    else
        CURRENT_PROFILE=$(curl -s http://localhost/profile)
    fi

    if [ ${CURRENT_PROFILE} == real1 ]
    then
      IDLE_PROFILE=real2
    else
      IDLE_PROFILE=real1
    fi

    echo "${IDLE_PROFILE}"
}

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

    if [ ${IDLE_PROFILE} == real1 ]
    then
      echo "8081"
    else
      echo "8082"
    fi
}
  • $(curl -s -o /dev/null -w "%{http_code}"http://localhost/profile)
    • 현재 엔진엑스가 바라보고 있는 스프링 부트가 정상적으로 수행 중인지 확인
    • 응답값을 HttpStatus로 받습니다.
    • 정상이면 200, 오류가 발생한다면 400~503 사이로 발생하니 400 이상은 모두 예외로 보고 real2를 현재 profile로 사용
  • IDLE_PROFILE
    • 엔진엑스와 연결되지 않은 profile
    • 스프링 부트 프로젝트를 이 profile로 연결하기 위해 반환
  • echo "${IDLE_PROFILE}"
    • bash라는 스크립트는 값을 반환하는 기능이 없습니다.
    • 그래서 제일 마지막 줄에 echo로 결과를 출력 후, 클라이언트에서 그값을 잡아서 $(find_idle_profile) 사용합니다.
    • 중간에 echo를 사용해선 안됩니다.

 

start.sh 생성

#!/usr/bin/env bash

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

REPOSITORY=/home/ec2-user/app/step3
PROJECT_NAME=SpringBootWebService

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

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

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 실행"

IDLE_PROFILE=$(find_idle_profile)

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

prfile.sh를 통해 IDLE_PROFILE를 가져온다.

 

stop.sh 생성

#!/usr/bin/env bash

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

IDLE_PORT=$(find_idle_port)

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

if [ -z ${IDLE_PID} ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $IDLE_PID"
  kill -15 ${IDLE_PID}
  sleep 5
fi
  • ABSDIR=$(dirname $ABSPATH)
    • 현재 stop.sh가 속해 있는 경로 찾기
  • source ${ABSDIR}/profile.sh
    • 자바로 보면 일종의 import 구문
    • 해당 코드로 인해 stop.sh에서도 profile.sh의 여러 function을 사용할 수 있게 됩니다.

 

switch.sh 생성

#!/usr/bin/env bash

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

function switch_proxy() {
    IDLE_PORT=$(find_idle_port)

    echo "> 전환할 Port: $IDLE_PORT"
    echo "> Port 전환"
    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
}
  • echo "set $service_url http://127.0.0.1:${IDLE_PORT};"
    • 하나의 문장을 만들어 파이프라인(|)으로 넘겨주기 위해 echo를 사용
    • 엔진엑스가 변경할 프록시 주소를 생성
  • sudo tee /etc/nginx/conf.d/service-url.inc
    • 앞에서 넘겨준 문장을 service-url.inc에 덮어 씁니다.
  • sudo service nginx reload
    • 엔진엑스 설정을 다시 불러온다.
    • restart는 잠시 끊기는 현상이 있지만, reload는 끊김 없이 다시 불러온다.

이제 실제로 무중단 배포를 진행해 봅시다.

 

 

 

무중단 배포 테스트

build.gradle

version '1.0.1-SNAPSHOT-'+new Date().format("yyyyMMddHHmmss")
  1. build.gradle은 Groovy 기반의 빌드 툴입니다.
  2. 당연히 Groovy 언어의 여러 문법을 사용할 수 있는데, 여기서는 new Date()로 빌드할 때마다 그 시간이 버전에 추가되도록 구성하였습니다.

여기까지 구성한 뒤 최종 코드를 깃허브로 푸시합니다. 배포가 자동으로 진행되면 CodeDeploy 로그로 잘 진행되는지 확인해 봅시다.

tail -f /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

위와 같은 메시지가 차례대로 출력되면 성공입니다.

한 번 더 배포하면 그때는 real2로 배포됩니다. 이 과정에서 브라우저 새로고침을 해보면 전혀 중단 없는 것을 확인할 수 있습니다. 2번 배포를 진행한 뒤에 아래와 같이 자바 애플리케이션 실행 여부를 확인해봅시다.

ps -ef | grep java

아래와 같이 2개의 애플리케이션이 실행되고 있음을 알 수 있습니다.

이제 해당 시스템은 마스터 브랜치에 푸시가 발생하면 자동으로 서버 배포가 진행되고 서버 중단 역시 전혀 없는 시스템이 되었습니다. 

 

 

 

 

 

 

출처

 

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - YES24

가장 빠르고 쉽게 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!이 책은 제목 그대로 스프링 부트와 AWS로 웹 서비스를 구현한다. JPA와 JUnit 테스트, 그레이들, 머스테치, 스프링

www.yes24.com

 

 

+ Recent posts