티스토리 뷰

Nginx

소개

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

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/playground-logging/
    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
  • hooks
    • CodeDeploy의 배포에는 각 단계 별 수명 주기가 존재합니다. 수명 주기에 따라 원하는 스크립트를 수행할 수 있습니다.
    • AppSpec 'hooks' 섹션 레퍼런스

ApplicationStart라는 수명 주기에 세 가지 스크립트를 차례로 실행시키겠습니다.

프로젝트 최상단에 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/playground-logging/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/playground-logging/build/libs/* &

nginx가 바라보도록 설정한 service_url.inc 파일이 8081포트를 가리키고 있기 때문에, 8081로 서버를 띄운 후 EC2 인스턴스의 퍼블릭 DNS로 접속해 보시면 다음과 같이 우리가 만들었던 Controller의 리턴값이 잘 나오는 것을 볼 수 있습니다.

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

application.yml의 버전을 0.0.2로 올리고, 커밋 - 푸시 후 Github Actions에서 배포를 진행해봅시다.

Github Actions 의 작업이 끝나고나서, CodeDeploy가 배포를 시작할 때 브라우저에서 지속적으로 새로고침을 눌러보시면!

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

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를 종료하는 스크립트도 추가할 수 있을 것입니다.

이번 Github Actions + CodeDeploy + Nginx 를 이용해 최소 규모로 무중단 배포하기 시리즈는 여기까지입니다!
긴 글 읽어주셔서 감사합니다 :)