DevOps/AWS

[AWS] Github Actions, CodeDeploy, Nginx 로 무중단 배포하기 - 4

샤아이인 2022. 6. 2.

총 4개의 시리즈 글로 진행될 것입니다.

 

1) Github Actions과 AWS S3 연동

2) EC2 설정과 CodeDeploy 적용

3) EC2와 RDS

4) Nginx 설치와 배포 스크립트 (이번 글)

 

▶ 전체 흐름도

우선 전반적인 흐름은 다음과 같습니다!

과정을 진행하면서 막힐 때, 다음 그림을 보면서 생각해보시면 어떤 부분에서 막히고 있는지 이해하기 쉬우실 거예요!

이번 시간에는 마지막으로 실질적인 배포를 하는 과정입니다!

 

4. Nginx 설치와 배포 스크립트

동적인 자원의 처리를 주로 WAS(Web Application Server)가 담당하고, 정적인 자원은 웹 서버(Web Server)가 처리해주게 됩니다.

 

여기서 웹 서버의 역할을 Nginx가 해줍니다! WAS가 하는 역할이 줄었으니 부담이 줄어들게 됩니다!


Nginx는 정적 자원의 처리 외에도 proxy 서버의 역할이나, reverse proxy 서버의 역할 등 여러방면에서 높은 활용도를 보여줍니다.

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

 

1. Nginx 설치

EC2 에서 다음 커멘드를 통해 Nginx를 설치합니다!

sudo amazon-linux-extras install nginx1

 

이후 설치가 정상적으로 됬는지 다음 버전확인 커멘드를 통해 확인합니다.

sudo nginx -v # 설치 버전 확인

이후 /etc/nginx 에 가보면 여러가지 파일이 생성된것을 확인할 수 있습니다.

 

2. Nginx에서 symbolic link 연결하기

음 대부분의 다른 블로그들 에서는 /etc/nginx/nginx.conf에서 reverse proxy를 수정하는데,

최신 Nginx 에서는 직접적으로 파일을  수정해도 정상적으로 작동하지 않는것 같습니다.

 

따로 폴더를 만들어 설정 파일들을 만들어 Symbolic link 를 걸어주는 것이 중요합니다.

 

우선 /etc/nginx 안에 sites-available 과 sites-enabled 폴더가 없을것 입니다. (있다면 안만드셔도 되요!)

다음 명령어를 통해 만들어 봅시다.

sudo mkdir /etc/nginx/sites-available
sudo mkdir /etc/nginx/sites-enabled

이후 sites-avaliable 디렉토리로 이동하여 default 파일을 만들어봅시다.

cd /etc/nginx/sites-available
sudo vim default

이후 default 안에 다음과 같이 작성합니다.

server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

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

        location / {
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_Host;
                proxy_pass $service_url;
        }

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
            location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
}
  • 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;

 

이후  /etc/nginx/nginx.conf 폴더에 있는 파일에서 원래의 server에 대한 부분을 다음과 같이 주석으로 처리할것 입니다.

기존 부분을 전부 주석처리 한 것 입니다.

이후 위에서 언급한 심볼릭 링크를 만들어 줍니다.

sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

 

이후 Nginx가 정상적인지 테스트를 다음 커멘드로 해줍니다.

sudo nginx -t

마지막으로 Nginx를 재시작 해주시면 됩니다!

sudo service nginx restart

 

3. 배포 스크립트 만들기

프로젝트에 CodeDeploy Agent가 참고하여 배포를 진행하기 위한 스크립트를 만들어 봅시다!

 

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

# appspec.yml

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/airbnb-deploy/ # 프로젝트 이름
    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' 레퍼런스

예를 들어 CodeDeploy를 실행할 때를 보면 다음과 같은 과정으로 진행됩니다.

위 여러 수명주기 중에서 우리는 ApplicationStart라는 수명 주기에 세 가지 스크립트가 작동하도록 하겠습니다.

 

▶ 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/airbnb-deploy/build/libs/* > /home/ec2-user/nohup.out 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0

WAS를 새롭게 하나 띄우는 스크립트 입니다.

 

우선 service_url.inc 에서 현재 서비스를 하고 있는 WAS의 포트 번호를 읽어옵니다.

이후 DB를 위한 환경변수들을 리눅스의 env로 불러와서 변수에 저장합니다. 

 

현제 포트가 8081이라면 target으로 8082를, 8082라면 8081을 선택하게 됩니다.

target 포트로 이미 was가 띄워져 있다면 kill 명령어로 일단 다운시킨 후, 새롭게 띄웁니다.

 

참고로 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}/api/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에 내용을 덮어씁니다.

 

4. 환경변수 설정

마지막으로 Linux에 환경변수를 설정해 줍시다.

sudo vim /etc/profile

vim으로 파일을 열어 다음과 같이 문서 맨 끝에 추가해 줍니다.

DB의 username 과 password, 그리고 RDS의 엔드포인트를 적어줍니다.

 

5. 실행하기

무중단 배포를 진행하기 전에, 현재 서버에 아무런 WAS가 떠 있지 않기 때문에 8081 포트에 WAS를 새로 한번 띄워봅시다!

nohup java -jar -Dserver.port=8081 /home/ec2-user/airbnb-deploy/build/libs/* --username=$username --password=$password --datasource=$datasource &

해당 서버가 잘 동작하고 있는지 우리의 Health 체크용 API를 통해 확인한 후,

 

우리의 원래 코드를 일부 수정하여 commit 후 push 하면 자동으로 배포가 8082 포트로 진행되게 됩니다!

자동 배포 전에는 8081 하나만 떠있었는데, 배포 후 8082 까지 확인할 수 있게 되었습니다!

 

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

 

현재 이 배포 구성은 V2 버전의 WAS가 서비스를 하고 있으면서 이전 버전인 V1의 WAS가 서비스를 하지 않는데도 백그라운드에 그대로 떠 있는 상황인데요.

만약 이전꺼를 종료 하고 싶다면 직접 변경해주면 충분히 가능하다 생각합니다!!

 

긴 저의 글을 읽어주시느라 수고 많으셨습니다.

부족한 부분이 많고, 각자마다 조금씩 환경설정이 다르겠지만... 도움이 되길 기원합니다!

댓글