Jenkins/Nginx로 무중단 배포 하기 2편

HYK·2022년 12월 31일
2

project

목록 보기
3/8
post-thumbnail

2편 무중단 배포 구현하기

1편에 이어서 이번에는 서비스를 중단하지 않고 배포할 수 있는 환경을 만들어 보자.

무중단 배포 방식

  • 무중단 배포 방식은
    • 롤링(Rolling) 배포
    • Blue/Green 배포
    • 카나리(Canary) 배포

크게 3종류의 배포 방식이 있다. 각각 어떤 식으로 배포되고 장단점은 무엇이 있는지 간단하게 살펴보자

롤링(Rolling) 배포

롤링 배포는 각 서버를 한 개씩 새로운 버전으로 (점진적) 배포하는 방법을 말한다.

장점

  • 추가적인 자원 없이 무중단 배포가 가능하다.
  • 점진적 배포이기 때문에 손쉽게 롤백이 가능하다.

단점

  • 점진적으로 배포를 하기 때문에 서버 간의 버전 호환성 문제가 발생할 수 있다.
  • 원래 4개로 트래픽을 나누던 서버 중에 배포할 서버 한 개를 로드밸런서에서 떼어낸 뒤에 새로운 버전을 배포해야 하기 때문에 기존에 4개로 나누어 받던 트래픽이 3개의 서버로 나눠 받아야 해서 각각의 서버의 트래픽 부담이 증가하는 단점이 있다.
    (새로운 서버를 하나 생성후 배포할 때 동안에도 일정한 트래픽을 받을 수 있도록 하는 방법도 있다.)

Blue/Green 배포

장점

  • 한 번에 새로운 버전으로 배포하고 트래픽을 옮기기 때문에 호환성 문제가 발생하지 않는다.

단점

  • 새로운 버전을 배포할 서버를 미리 생성해두어야 하기 때문에 비용/리소스 측면에서 불리하다.

카나리(Canary) 배포

장점

  • 트래픽을 조금씩 새로운 서버로 옮기면서 문제가 없는 경우 트래픽을 증가시키기 때문에 새로운 버전에 문제가 생기더라도 쉽게 롤백 하는 등의 대처를 할 수 있다.(신버전 서버에 트래픽을 조금씩 늘리면서 문제가 없는지 테스트한다고 생각하면 된다.)

단점

  • 두 버전이 동시에 존재하기 때문에 호환성 문제가 발생할 수 있다.

선택

이번에는 간단하고 비교적 구현이 쉬운 롤링 배포 방식으로 배포를 해볼 예정이다.

구축하기

구축 순서

서버는 이미 가동 중인 것을 전제로 한다.

    1. spirng actuator 설정
    1. nginx 설치하기
    1. shell script 작성하기
    1. jenkins 설정하기
    1. 테스트하기

1. spirng actuator 설정

  • actuator는 spring 서버의 상태를 실시간으로 확인할 수 있는 기능이다.

  • 다음과 같이 의존성을 추가하자

implementation 'org.springframework.boot:spring-boot-starter-actuator'
  • spring actuator는 서버의 여러 가지 상태를 확인할 수 있는 강력한 기능이지만 민감한 정보도 포함되어 있을 수 있다. 따라서 다음과 같이 health만 사용하도록 yml 파일로 설정해 주어야 한다.

  • 설정후에 /actuator/health로 확인하면 다음과 같은 결과를 받을 수 있다.

nginx

  • nginx를 reverse proxy로 이용해서 서버의 트래픽을 분산시킬 것이다.

  • 배포 시에 한쪽의 서버가 배포 중이더라도 health-check를 통해서 다른 살아있는 서버들에게 트래픽을 로드밸런싱 해준다. 따라서 멈추지 않고 서비스를 지속할 수 있다.

2. nginx 설치

  • nginx의 health-check는 NGINX Plus 버전(유료)에서 지원하기 때문에 우리는 무료 health-check 모듈이 포함된 nginx를 사용해서 진행할 것이다.

  • 먼저 nginx 설정 파일을 만들자

vi nginx.conf

events {}

http {
  upstream market {
	# 배포 중인 서버들
	server ip:port;
	server ip:port;

	#health-check
	# interval - 3초씩에 한 번씩 확인
	# rise - 2번 이상 응답에 성공하면 서버가 살은 것으로 판단
	# fall - 5번 이상 응답에 실패할 경우 서버가 죽은 것으로 판단
	# timeout - 응답 시간 초과 1초
	check interval =3000 rise=2 fall=5 timeout=1000 type=http;
	# GET /actuator/health으로 healthcheck 요청
	check_http_send "GET /actuator/health HTTP/1.0\r\n\r\n";
	check_http_expect_alive http_2xx http_3xx;
  }

  server {
    listen 80;
    location / {
       proxy_pass http://market;
    }
    # 서버 상태 확인 url
     location /status {
     check_status;
   }
  }
}
  • nginx image를 빌드 하기 위해 필요한 Dockerfile을 만들어보자
vi Dockerfile

# mrlioncub/nginx_upstream_check_module - health-check 모듈이 포함된 nginx image
FROM mrlioncub/nginx_upstream_check_module
COPY nginx.conf /etc/nginx/nginx.conf
  • Dockerfile을 이용해 이미지 build 하기
docker build --tag nginx:test-nginx .
  • build 확인

  • nginx 실행시키기

docker run -d --name webserver -p 80:80 [tag | image id]
  • nginx 확인하기 /status

/status로 확인해 보면 서버가 죽은 경우는 다음과 같이 down으로 표시된다.

서버가 살아있는 경우는 다음과 같이 up으로 표시된다.

3. shell script 작성하기

  • jenkins에서 배포 후에 실행할 shell script를 서버에 작성해두자
  • deploy.sh의 전체적인 흐름은 다음과 같다.
    • 현재 배포하려는 서버 외에 트래픽을 받을 수 있는 서버 health-check 없다면 배포하지 않음
    • 현재 tomcat server 종료 gracefully shut down -> force shut down
    • 배포 시작
    • 배포 후 자가 health-check

deploy.sh

BASE_PATH=/home/app/
JAR_NAME=xxx.jar
echo "> build 파일명: $JAR_NAME"
#환경변수 받기 암호화 및 서버ip

#암호화 키
KEY=$1
#배포중인 server ip
IP1=$2
IP2=$3
#배포 port
DEPLOYED_PORT=8080

echo ">환경변수 확인"
echo ">KEY="$1
echo ">IP1="$2
echo ">IP2="$3
echo "> 현재 구동중인 Set 확인"

#private ip
MY_IP=$(hostname -i)

loop=1
limitLoop=30
flag='false'


if [ $MY_IP == $IP1 ]; then
  OTHER_IP=$IP2
elif [ $MY_IP == $IP2 ]; then
  OTHER_IP=$IP1
else
  echo "> 일치하는 IP가 없습니다. "
fi

#==========================살아있는 서버가 존재하는지 확인==============================
echo "> 서버 체크 시작"
for retry_count in {1..10};
do
  response=$(sudo curl -s http://$OTHER_IP:$DEPLOYED_PORT/actuator/health)
  up_count=$(echo $response | grep 'UP' | wc -l)
  echo "> $retry_count : $response  : $up_count"
  if [ $up_count -ge 1 ]; then
    echo "> 서버 health 체크 성공"
    break
  fi
  if [ $retry_count -eq 10 ]; then
    echo "> 서버 health 체크 실패"
    exit 1
  fi
  echo "> 실패 10초후 재시도"
  sleep 10
done

#===================================프로세스 종료======================================
# tomcat gracefully shutdown
echo "> 구동중인 애플리케이션 pid 확인"
IDLE_PID=(`ps -ef | grep  $JAR_NAME | grep -v 'grep' | awk '{ print $2 }'`)
 if [ ${#IDLE_PID[@]} = 0 ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
  flag='true'
else
  for pid in "${IDLE_PID[@]}"
  do
      echo "> [$pid] gracefully shutdown"
      kill -15 $pid
  done
  while [ $loop -le $limitLoop ]
  do
      PID_LIST=(`ps -ef | grep  $JAR_NAME | grep -v 'grep' | awk '{ print $2 }'`)
      if [ ${#PID_LIST[@]} = 0 ]
      then
          echo "> gracefully shutdown success "
          flag='true'
          break
      else
          for pid in "${PID_LIST[@]}"
          do
              echo "> [$loop/$limitLoop] $pid 프로세스 종료를 기다리는중입니다."
          done
          loop=$(( $loop + 1 ))
          sleep 1
          continue
      fi
  done
fi
if [ $flag == 'false' ];
then
    echo "> 프로세스 강제종료 시도"
    sudo ps -ef | grep $JAR_NAME | grep -v 'grep' |  awk '{ print $2 }' | \
    while read PID
    do
        echo "> [$PID] forced shutdown"
        kill -9 $PID
    done
fi

#===================================배포======================================

echo "> 배포"
echo "> 파일명" $BASE_PATH$JAR_NAME
sudo nohup java -jar -Dspring.profiles.active=prod $BASE_PATH$JAR_NAME --jasypt.encryptor.password=$KEY & 
sudo sleep 10

echo "> 10초 후 Health check 시작"
echo "> curl -s http://$MY_IP:$DEPLOYED_PORT/actuator/health"

#==========================현재 서버 Health check============================
for retry_count in {1..10}; do
  response=$(sudo curl -s http://$MY_IP:$DEPLOYED_PORT/actuator/health)
  up_count=$(echo $response | grep 'UP' | wc -l)
  if [ $up_count -ge 1 ]; then
    echo "> Health check 성공"
    break
  else
    echo "> Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다."
    echo "> Health check: ${response}"
  fi

  if [ $retry_count -eq 10 ]; then
    echo "> Health check 실패. "
    echo "> Nginx에 연결하지 않고 배포를 종료합니다."
    exit 1
  fi

  echo "> Health check 연결 실패. 재시도..."
  sudo sleep 10
done

sleep 60 # 다음 배포 서버를 위한 지연

4. jenkins 설정하기

  • Jenkins 관리 > Configure System > Global properties

  • shell script에서 사용할 민감 정보인 ip와 yml을 암호화한 key를 환경 변수로 저장해두고 쓰자

  • 빌드 후 조치에서 실행시킬 스크립트를 다음과 같이 변경해 주자

  • deploy.sh에 jenkins 환경 변수 값을 넘겨주는 것이다.

테스트

클릭시 영상 재생

무중단 배포가 잘 동작하는 것을 확인할 수 있다.

profile
Test로 학습 하기

0개의 댓글