[Github Action + AWS CodeDeploy + NGINX] 서버 무중단 배포하기

Dev_ch·2022년 11월 27일
3

NGINX는 고성능, 확장성, 고가용성 웹서버, 역방향 프록시 서버 및 웹 가속기(HTTP 로드밸런서, 콘텐츠 캐시 등의 기능 결합)이다.

이 글은 이전 포스팅과 이어집니다.

[Github Action + AWS CodeDeploy] Spring Boot 서버 자동 배포 및 실행하기


이전 포스팅에서 깃헙 액션과 AWS의 CodeDeploy를 통해 CI/CD를 구축해 서버를 자동으로 배포 및 실행하는 서비스를 구현해보았다. 그런데 위 포스팅의 로직은 배포될때 서버가 중지되었다가 시작되는데 이러하다면 잠시 서버가 중지되어 있는 틈이 발생하게 된다. 실제로 서비스가 돌아간다고 가정할때 서버가 잠시라도 꺼지는 순간이 존재하면 안된다. 그렇기 때문에 서버를 무중단으로 배포해야하며 이를 위해 NGINX를 사용했다.

생각보다 오류가 빈번히 발생해서 여러번 거듭 수정하고 배포하고 테스트하는 과정을 거쳤다. 생각보다 쉽지는 않았지만 아래의 과정을 통해 구현을 성공했다.

1. NGINX 설치

sudo yum install nginx
sudo amazon-linux-extras install nginx1
sudo nginx -v

위의 커맨드를 토대로 설치하고 설치되어있는지 확인한다.

2. NGINX 설정 파일 수정

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;
}

위 코드에서 proxy_pass를 통해 $service_url로 요청을 보낼 수 있도록 할 것 이다. 그러기 위해서 $service_url을 설정해둔 파일을 하나 생성해줄 것 이다.

vim /home/ec2-user/service_url.inc

# service_url.inc 내부 작성 코드
set $service_url http://127.0.0.1:8081;

위와 같이 inc 파일을 생성해주면 된다. 추후 CI/CD 과정에서 쉘 스크립트를 통해 위 포트가 8081 또는 8082로 전환된다.

3. NGINX 실행 및 상태 확인

sudo service nginx start
sudo service nginx stop
sudo service nginx restart
sudo service nginx reload
sudo service nginx status

해당 커맨드들은 nginx가 시작/종료/재시작/종료되지않고 재시작/상태확인 커맨드들을 차례대로 나열했으며 필요한 상황에 따라 사용하자. 우리는 nginx의 가동을 위해 시작과 상태확인 커맨드를 입력하여 nginx가 제대로 실행되고 있는지 확인해주자.

sudo service nginx status를 입력 후 active 상태라면 nginx 웹서버가 실행되고 있는 중 이다.

위 과정을 통해 Nginx를 사용하기 위한 설치와 설정은 끝났다. 이제 배포하려는 프로젝트의 스크립트를 만들면 된다.

4. 프로젝트 내 배포 스크립트 작성

appspec.yml

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/{프로젝트 경로}

hooks:
  ApplicationStart:
    - location: scripts/run_new_was.sh
      timeout: 60
      runas: ec2-user
    - location: scripts/health_check.sh
      timeout: 60
      runas: ec2-user
    - location: scripts/switch.sh
      timeout: 60
      runas: ec2-user

스크립트를 추가하기 전 appspec에 위와 같이 작성한 이후 배포 스크립트를 하나씩 추가해주자. 참고로 배포 스크립트는 프로젝트 최상단 scripts 패키지에 작성하며 잘 모르겠다면 이전 포스팅을 참고하자. 이전 포스팅의 쉘 스크립트들은 전부 삭제해도 된다.

run_new_was.sh

# run_new_was.sh

#!/bin/bash

PROJECT_ROOT="/home/ec2-user/{프로젝트 폴더}" # 프로젝트 루트
JAR_FILE="$PROJECT_ROOT/build/libs/{배포용 jar 파일}" # JAR_FILE (어쩌구저쩌구.jar)

# service_url.inc 에서 현재 서비스를 하고 있는 WAS의 포트 번호 가져오기
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 # 현재포트가 8081이면 8082로 배포
elif [ ${CURRENT_PORT} -eq 8082 ]; then
  TARGET_PORT=8081 # 현재포트가 8082라면 8081로 배포
else
  echo "> Not connected to nginx" # nginx가 실행되고 있지 않다면 에러 코드
fi

# 현재 포트의 PID를 불러온다
TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')

# PID를 이용해 해당 포트 서버 Kill
if [ ! -z ${TARGET_PID} ]; then
  echo "> Kill ${TARGET_PORT}."
  sudo kill ${TARGET_PID}
fi

# 타켓 포트에 jar파일을 이용해 새로운 서버 실행
nohup java -jar -Dserver.port=${TARGET_PORT} ${JAR_FILE} > /home/ec2-user/nohup.out 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0

위 스크립트 파일은 우리가 위에서 만든 service_url.inc 파일을 통해 현재 서비스를 하고 있는 WAS의 포트 번호를 가져오고 이를 토대로 8081 또는 8082 포트로 서버가 배포되며 최신화된다.

health_check.sh

# health_check.sh

#!/bin/bash

# service_url.inc 에서 현재 서비스를 하고 있는 WAS의 포트 번호 가져오기
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

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}' ..."

# 아래 커맨드들을 새로 열린 서버가 정상적으로 작동하는지 확인

# 해당 커맨드들을 10번씩 반복
for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10
do
    echo "> #${RETRY_COUNT} trying..."
    # 테스트할 API 주소를 통해 http 상태 코드 가져오기
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}"  http://127.0.0.1:${TARGET_PORT}/테스트해볼 api 주소)

	# RESPONSE_CODE의 http 상태가 200번인 경우
    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
    sleep 15
done

위 스크립트 파일은 새로 열린 서버가 정상적으로 작동하는지 확인하는 코드이다. for 문을 통해 서버가 열리는 시간을 고려하여 healthcheck를 진행하며 200번인경우 정상적으로 돌아가는지 확인이 완료된다. 서버가 열리지 않은 상태라면 sleep 15 를 통해 잠시 기다리게 되며 다시한번 RESPONSE_CODE의 http 상태 코드를 검사하여 총 for문에 기재된대로 10번 반복한다.

switch.sh

# switch.sh

#!/bin/bash

# service_url.inc 에서 현재 서비스를 하고 있는 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}."

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

# 위 커맨드들을 통해 현재 타겟포트 가져오기

# $ service_url.inc 파일을 현재 바뀐 서버의 포트로 변경
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}."

# nginx를 reload 해준다.
sudo service nginx reload

echo "> Nginx reloaded."

위 스크립트는 healthcheck가 완료된 시점이며, WAS의 현재포트 번호를 통해 새로 실행할 타겟 포트를 가져오며 WAS의 포트번호를 타겟 포트로 변경해주고 nginx를 reload 해준다.

필자의 경우 healthcheck를 위한 테스트 api 주소는 /version 이였으며 해당 api는 간단하게 String으로 버전이름이 뜨게끔 만들어 주었다. 현재 포스트의 최하단 이미지를 통해 확인할 수 있다.

참고로 reload 커맨드를 입력해준 이유는 nginx 서버의 재시작 없이 바로 새로운 설정값으로 서비스를 이어나갈 수 있도록 하기위함이다. restart의 경우 서버가 재시작 되므로 잠시 중지되는 과정이 발생하기에 사용하지 않는다.

5. NGINX로 서버 배포하기

현재 서버에 프로젝트가 배포되어있지 않기에 프로젝트 내 build/libs로 이동하여서

nohup java -jar -Dserver.port=8081 {배포jar파일} &

위 커맨드를 입력해 nginx 웹서버의 8081포트로 프로젝트를 배포해준다.

그 후 프로젝트가 변경 될때마다 깃헙에 push를 해주면 쉘 스크립트를 통하여 8081 / 8082 를 번갈아가며 배포하여 서버가 무중단 배포되게된다.

테스트를 위해 버전을 올려가며 깃헙에 푸쉬를 해봤다. 퍼블릭 DNS 주소를 통해 접근하여 API를 확인해본 결과 정상적으로 서버가 배포되는 것을 확인하였다. 참고로 healthcheck에서 테스트하는 api주소도 위와 동일하다.


저번 포스팅에서 깃헙 액션과 AWS CodeDeploy를 통해 CI/CD를 구축하였는데 서버가 잠시 중단이 되는 틈이 발생한다는걸 알고있었기에 무중단 배포 서비스를 구현하기 위해 추가 포스팅을 진행하였다. NGINX라는 웹서버로 배포하는 것도 처음해보면서 해당 서비스를 구현하다보니 시행착오가 많았지만 결국 해냈다 👍

다음 개발 포스팅은 뭐가 될진 모르겠지만 새로운걸 해내는건 참 재밌다,, 아무튼 이번 포스팅은 여기서 마무리 하도록 하자 !

도움이 된 블로그

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

profile
내가 몰입하는 과정을 담은 곳

5개의 댓글

comment-user-thumbnail
2022년 12월 1일

안녕하세요! 혹시 stop.sh와 start.sh 파일은 어떻게 관리하셨나요?!
그냥 그대로 유지하셨을까요 아니면 삭제 후 위의 3개의 파일만 남겨두셨나요?
감사합니다!

1개의 답글
comment-user-thumbnail
2023년 6월 24일

안녕하세요! 적어주신대로 따라했는데 자꾸 오류가 생기네요..ㅠㅠ 8081 8082 포트로 작동되고 있긴한데 한쪽 포트로만 변경된 코드가 들어가고 무중단 배포가 안되네요..ㅠㅠ 왜그런걸까요 PID 번호도 한 포트의 PID 번호만 바껴있네요...

1개의 답글