Spring/Spring ++

Github Actions + Code Deploy + Ngxin + ubuntu 20.04로 무중단 배포하기 (3) 마지막!

backend dev 2022. 10. 23.

마지막 Nginx로 무중단 배포구현

 

자동 빌드/테스트 + 무중단배포의 전체 흐름중

마지막인 빨간 체크박스 부분을 구현할 시간이다.

 

스프링부트 프로젝트를 실행시 어느 포트를 사용할지 지정할 수 있다.

 

현재 프로젝트가 실행된 포트가 8081라고 하자. (현재 실행중)

 

개발자가 개발을 하던중 수정사항이 생겼고 커밋을해서 원격저장소에 올린후 workflow를 실행했다고 해보자(게시글에서는 수정으로 workflow를 실행하게끔 구현했으므로)

 

workflow로 인해 빌드/테스트가 진행된후 s3에 빌드파일이 올라가고 

code deploy agent가 배포를 할텐데 

 

배포할때 8082 포트로 방금 배포된(=수정된,최신버전) 프로젝트를 실행하고 잘 켜졌는지 확인한후 잘켜졌다면

 

nginx가 8082포트의 프로젝트를 가르키게한다. ( 클라이언트에서 8082포트로 실행된 프로젝트를 이용하게끔)

 

그러면 클라이언트 입장에서는 8081포트의 프로젝트를 사용하다가

 

중단된 시간도 없이

 

최신업데이트 내용이 들어간 8082 프로젝트를 사용하게 되는것이다.

 

[중단된 시간이 없는 이유는 배포과정중에 8082 프로젝트를 실행해서 잘 돌아가는지 체크하고 nginx설정상에서 8082포트의 프로젝트를 가리키게 하기때문이다.]

 

최신버전 배포시 중단시간이 있으려면?

실행중인 8081 포트의 프로젝트를 중지 시키고 (중지시킨 시간부터 정지)

최신버전을 배포받아서 다시 프로젝트를 8081 포트로 실행한다. ( 실행될때까지 서비스가 중단됨)

 

자세한 내용은 설정하면서 알아보자!

 

Nginx설정

nginx의 설치는 스킵한다!

 

 

설정방법이 다다르다.

첫번째로 localhost(= ec2의 ip,기본도메인)를 이용할 사람이면 default ( nginx 기본서버 설정파일)에서 설정해주면되고

 

서브도메인을 사용하는 사람은 서브도메인의 conf 파일에서 설정해주면된다.(또는 서브도메인의 서버블록)

 

나같은 경우는 dev.wogus4048.shop이라는 서브도메인.conf 파일을 생성해줬으므로 거기서 설정해준다.

 

즉!, 내가 원하는 서버블록에 가서 설정해야한다.

 

 

나는 해당위치에 default라는 기본 서버설정 파일이 있었다. (이 부분은 구글링)

그 안에 내가 만든 도메인.conf 파일을 수정해줄것임! (나는 dev.wogus4048.shop을 사용)

 

빨간 네모칸 쳐놓은 부분을 추가해줬다!

include /home/자신의 host이름/service_url.inc; #밑에서 설명할부분

location / {
    proxy_set_header    X-Forwarded-For $remote_addr;
    proxy_set_header    Host $http_Host;
    proxy_pass          $service_url; # 리버스 프록시를 설정하는부분
}

include
다른 곳에 존재하는 설정 파일 등을 불러올 수 있습니다. ( 호스트이름 수정해줘야한다!)

( 우분투에서는  /home/ubuntu/service_url.inc)

proxy_pass
우리가 지정할 $service_url로 요청을 보낼 수 있도록 하는 프록시 설정입니다.

윗부분에 대한 설정은

server_name 에 대한 접속이 생겼을때 (도메인이라던지 ,ip라던지 해당 서버블록에 설정된 server_name)

service_url의 파일내용을 참고해서 접속시켜준다는것이다 (service_url 파일 내용을 수정하면

접근시 어떻게 연결시켜줄지 설정해줄수있다!)

 

 

include로 불러올 파일을 생성해주자! 

vi /home/host이름입력/service_url.inc

ex) vi /home/ubuntu/service_url.inc  -> 해당명령어로 파일생성!

 

내용은

set $service_url http://127.0.0.1:원하는포트넘버;

ex) set $service_url http://127.0.0.1:8081;

이런식으로 채워서 파일을 만들어주면된다!

 

위에 예시를 이용해 설명하자면)

 

아까 서버블록에서 설정한 마지막줄  proxy_pass  $service_url; 에서

 

해당 파일내용을 가져오고 

 

set $service_url http://127.0.0.1:8081;의 파일 내용을 실행하게되서

server_name(ec2아이피나 , 기본도메인)에 접근한 사람들은 -> http://127.0.0.1:8081로 접근되는것이다.

파일의 내용에 따라 접속되는 포트를 바꿔줄수있게끔하는 부분! -> nginx의 스위칭에 이용하는 파일!

 

 

설정이 끝난후 nginx를 재시작해주자!

sudo service nginx restart

sudo service nginx -t 로 nginx에 문제가 없는지도 체크해주자.

 

여기까지 하면 nginx 설정끝!

 

배포 스크립트 추가

nginx 설정까지 했으니

 

code deploy agent가 어떤 순서대로 배포를 진행할지를 정하기 위해

 

배포 스크립트를 작성해보자

 

그전에 appspec.yml에 추가 할 부분이 있다.

 

appspec.yml

version: 0.0
os: linux

files:
  - source:  /
    destination: /var/www/dev_planet/cicd_template #ubuntu에서 배포될 프로젝트가 저장될 위치
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu #ec2 host이름.
    group: ubuntu

#추가된 부분
hooks:
  ApplicationStart:
    - location: scripts/run_new_was.sh
      timeout: 180
      runas: ubuntu # host이름
    - location: scripts/health_check.sh
      timeout: 180
      runas: ubuntu  # host이름
    - location: scripts/switch.sh
      timeout: 180
      runas: ubuntu  # host이름

hooks 부분에 배포 진행방식을 설정해줄 수 있다.

application start, beforestart 등 code deploy의 배포에는 단계별 수명주기가 존재하는데 수명주기마다 원하는 스크립트를 실행시킬수있음! 

 

어떤 수명주기가 있는지 확인하고 싶다면

 

AppSpec 'hooks' 섹션 - AWS CodeDeploy

배포의 Start, DownloadBundle, Install, BlockTraffic, AllowTraffic 및 End 이벤트는 스크립팅할 수 없기 때문에 이 다이어그램에서 회색으로 표시됩니다. 그러나 AppSpec 파일의 'files' 섹션을 편집하여 Install 이벤

docs.aws.amazon.com

 

이 게시글에서는 ApplicationStart라는 수명주기에

3가지 스크립트를 실행시킬것이다! 

 

스크립트는 

프로젝트 최상단에 scripts라는 디렉토리를 만들고 그곳에 저장할것이다!

 

배포 순서는 appspec에 적힌대로 run_new_was -> health_check -> switch 순이다.

배포 순서대로 각 스크립트의 내용을 설명해보자! ( 스크립트는 쉘스크립트이고, 추가하고 싶은 원하는문법이 필요시 구글링)

 

run_new_was.sh

# run_new_was.sh

#!/bin/bash

CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1)
#현재 실행되고 있는 포트는 service_url에서 포트번호를 가져와서 확인가능하다.

TARGET_PORT=0

echo "> Current port of running WAS is ${CURRENT_PORT}."
#나는 9001,9003 포트를 서로 스왑하는 형식으로 구현하였다 
if [ ${CURRENT_PORT} -eq 9001 ]; then  # 현재 실행되는 포트가 9001이라면 9003으로 타겟포트설정
  TARGET_PORT=9003  # 9001 -> 9003
elif [ ${CURRENT_PORT} -eq 9003 ]; then# 현재 실행되는 포트가 9003이라면 9001으로 타겟포트설정
  TARGET_PORT=9001 # 9003-> 9001
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을 이용하여 프로젝트를 무중단실행시켜준다  (자세한건 구글링)
-Dserver.port를 이용하여 원하는포트로 프로젝트 실행가능

nohup java -jar -Dserver.port=${TARGET_PORT} /var/www/dev_planet/cicd_template/build/libs/demo-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."

sleep 10s # 10초 대기
#프로젝트마다 다르겠지만 내 프로젝트는 켜지는시간이 길기때문에 10초정도 쉰다.  
$바로 nginx가 다른포트를 가리키면 아직 켜지지않았기떄문에 중단되어있는시간이 존재하기 때문이다!
exit 0

새로운 WAS를 띄우는 스크립트이다!


service_url.inc 에서 현재 서비스를 하고 있는 WAS의 포트 번호를 읽어옵니다.
현재 포트 번호가 8081이면 새로 WAS를 띄울 타겟 포트는 8082, 혹은 그 반대 상황이라면 8081을 지정합니다.
만약 타겟포트에도 WAS가 떠 있다면 kill하고 새롭게 WAS를 띄웁니다.(타켓포트에 떠있는 WAS는 이전버전 WAS가 될것이다) ( 위에 코드상으로는 9001,9003 포트를 이용한다)

nohup
터미널 엑세스가 끊겨도 실행한 프로세스가 계속 동작하게 합니다.
마지막의 &는 프로세스가 백그라운드로 실행되도록 해줍니다.

 

자세한 설명은 주석으로 적어놓았음!

 

 

health_check.sh

# health_check.sh

#!/bin/bash

# Crawl current connected port of WAS
#현재 실행중인 포트를 가져온다!
CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

# Toggle port Number , 타켓포트를 설정하는부분!
if [ ${CURRENT_PORT} -eq 9001 ]; then
    TARGET_PORT=9003
elif [ ${CURRENT_PORT} -eq 9003 ]; then
    TARGET_PORT=9001
else
    echo "> No WAS is connected to nginx"
    exit 1
fi

#run_new_was.sh에서 새로운 포트로 실행한 프로젝트가 잘실행되고있는지 체크하는부분
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..."
    #뒤에 사용되는 임의로 만든부분이다. /test/log부분은 밑에서 설명!
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}"  http://127.0.0.1:${TARGET_PORT}/test/log)

    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가 잘실행됬는지 체크하는부분!

 

test/log는 TestController에 작성한 테스트용 api이다!

package com.example.demo.src.test;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    public TestController() {
    }

    /**
     * 로그 테스트 API
     * [GET] /test/log
     *
     * @return String
     */
    @ResponseBody
    @GetMapping("/log")
    public String getAll() {

        return "Success Test version -> 0.0.4";
    }

}

 

나중에 무중단배포가 잘됬는지 체크할때도 사용할 예정

 

switch.sh

# switch.sh

#!/bin/bash

# Crawl current connected port of WAS
#현재 실행중인 포트 체크
CURRENT_PORT=$(cat /home/ubuntu/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 9001 ]; then
    TARGET_PORT=9003
elif [ ${CURRENT_PORT} -eq 9003 ]; then
    TARGET_PORT=9001
else
    echo "> No WAS is connected to nginx"
    exit 1
fi

# Change proxying port into target port
#service_url 내용을 수정함으로서 포트를 변경한다!
echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc

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

# Reload nginx
#nginx를 reload함으로서 수정사항이 업데이트 되도록한다
sudo service nginx reload

echo "> Nginx reloaded."

nginx 리로드를 통해 서비스하는 포트를 스위칭하는 스크립트입니다.


sudo service nginx reload 는 nginx 서버의 재시작 없이 바로 새로운 설정값으로 서비스를 이어나갈 수 있도록 합니다.


모든 스크립트에서

현재 실행중인 포트를 가져오고 

타켓 포트를 설정하는 부분이있는데

 

swich 스크립트에서 

service_url 내용을 타켓포트로 수정하고 nginx를 reload함으로서 

nginx가 타켓포트로 실행된 프로젝트를 연결시켜주게끔 수정해주는 부분이다.

 

결론 

run_new_was -> 새로운 포트(타겟포트,배포받은 버전)의 프로젝트를 실행해준다.

 

health_check -> 새로운 포트(타겟포트,배포받은 버전)의 프로젝트가 잘 실행됬는지 체크 ( + sleep을 이용하여 실행될때까지 대기)

 

switch -> service_url파일내용을 타켓포트로 수정함으로서

nginx가 가르키는 프로젝트를 run_new_was에서 실행시킨 타켓포트의 프로젝트(배포버전)을 가리키게된다.

+ nginx reload하여 중단없이 수정사항이 업데이트된다.

 

[nginx 서버블록설정에서 service_url에 대한 내용을 참조하여 프록시설정을 했으니까]

 

여기까지가 무중단배포 설정끝

 

자동 빌드/테스트 + 무중단배포 테스트! 

잘되는지 테스트하기위해

EC2에 접속해서 프로젝트에 있는 jar파일을 실행해보자. (일단 서비스가 진행되고있어야하니까!,켜져있다면 안켜도됨)

 

nohup java -jar -Dserver.port=포트번호 프로젝트위치/jar파일이름 & > dev/null

내 예시

nohup java -jar -Dserver.port=9001 /var/www/dev_planet/cicd_template/build/libs/demo-0.0.1-SNAPSHOT.jar & > dev/null

 

 

잘 켜진걸 확인할 수 있고  

아까 테스트용 api를 이용해서 현재어떤 버전값인지 체크해보자! (0.0.4)

 

 

 

0.0.5버전으로 수정후 커밋하고 원격저장소로 push해보자

수정 -> commit -> pull -> push

 

 

원격저장소로 잘 push 됬다면

 

원격저장소 -> Actions -> run workflow 실행

진행중일때 모습

( 지금은 수동으로 workflow를 실행하지만

workflow 스크립트를 수정해서 원격저장소 특정 branch에 push하면 자동으로 workflow가 실행하게끔 수정가능)

 

workflow가 성공해서 초록체크가 됬다면

 

aws code deploy의 배포부분에 진행중인 배포가 보일것이다.

배포가 진행중일때

이 화면에서 계속 새로고침을 눌러보면 ( 배포가 끝나고 확인하면 이미 버전업이 되어있기때문에)

서버의 중지가 없이 순식간에

 

0.0.5버전으로 업데이트가 된것을 확인할 수 있다.

 

그리고 code deploy 배포가보면 배포도 성공한걸 확인할 수 있음!

 

 

ps -ef | grep java

커맨드를 이용하여 9001 포트말고 9003 포트로도 서버가 실행된걸 확인할 수 있고

 

service_url이 있던 폴더로 이동해서

tail service_url.inc 명령어를 치면 

service_url 파일 내부 내용이 9003포트로 바뀐걸 확인할 수 있고

그로인해 9003 포트로 실행중인 프로젝트가 보이는것이구나 라고 판단할 수 있습니다!

 

이렇게 된후 

다음 또 배포를 하면

기존 9001포트의 프로젝트를 종료하고, 9001포트에  배포받은 최신버전의 프로젝트를 실행시킬것이며

nginx가 9001포트에 실행된 프로젝트(서버)를 가리키게 될것이다!

 

 

---

ex)

현재 이 배포구성은

9001의 최신버전(v2) 의 서버가 실행된 상황에서 9003의 이전버전(v1)의 서버도 실행되고있는데

 

v1의 서버는 v2를 실행시키고 롤백해야하는 상황이 있을때를 대비하여 롤백용 스크립트를 추가해서

롤백이 필요한 상황에 다시 nginx가 v1서버를 가리키게 하는 롤백용 서버로 써도되고

 

아니면 롤백용으로 냅둬서 지켜보다가 일정 시간동안 모니터링 한 결과 v2가 잘돌아간다 싶으면

v1버전의 서버를 종료시키는 스크립트도 추가가능하다

 

(즉 , 배포 스크립트를 추가, 수정함으로서 입맛대로 설정가능)

 

 

 

출처,참고,더 자세한 정보

 

 

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

Nginx 소개 Nginx는 널리 쓰이는 웹 서버 중 하나입니다. 동적 처리를 주로 담당하는 WAS(Web Application Server)와는 다르게 웹 서버(Web Server)는 정적 자원에 대한 응답을 내려주는 역할을 가지고 있는데

wbluke.tistory.com

 

댓글