Web/Ops
무중단 배포 - 롤링 배포 (2/4)
jodong2
2023. 3. 5. 16:24


롤링 배포 - 개념
롤링 배포는 현재 사용 중인 인스턴스 내에서 새 버전으로 하나씩 교체해나가는 것이다.
롤링 배포를 이용하게 된다면 기존에 유지하고 있는 인스턴스로만 진행하기 때문에 추가적으로 비용이 발생하지않는다. 또한 점진적으로 업데이트하기때문에 롤백이 비교적 쉽다. 하지만 새로운 버전으로 교체하는 과정에서 일부 인스턴스가 연결되어 있지 않기 때문에 트래픽을 감당할 수 있는 선에서 진행해야하며 이전 버전과 새로운 버전의 호환성 문제도 생각해야 한다.
롤링 배포 - 실습
1. spirng actuator 설정

다음과 같이 build.gradle에 spring actuator 의존성을 추가한다.
spring actuator는 서버의 여러가지 상태를 확인할 수 있는 기능이다. 하지만 민감함 정보도 포함될 수 있으므로 우리가 필요한 health만 사용하도록 yml(properties)파일을 수정해주자.

설정 후 /actuator/health 를 확인하면 {"start":"UP"} 을 확인할 수 있다.
2. nginx 설정
nginx pro버전에서는 health-check 기능을 지원하지만, 내 지갑은 휑하므로 무료 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
Docker file을 작성했으니 이미지를 빌드하고 실행하자
docker build --tag nginx:test-nginx .
docker run -d --name webserver -p 80:80 [tag | image id]
3. Shell script 작성
- jenkins에서 배포 후에 실행할 shell script를 서버에 작성해두자
- rollingdeploy.sh의 전체적인 흐름은 다음과 같다.
- 현재 배포하려는 서버 외에 트래픽을 받을 수 있는 서버, health-check 없다면 배포하지 않음
- 현재 tomcat server 종료 gracefully shut down -> force shut down
- 배포 시작
- 배포 후 자가 health-check
더보기
BASE_PATH=/home/app/
JAR_NAME=xxx.jar
echo "> build 파일명: $JAR_NAME"
#배포중인 server ip
IP1=#
IP2=#
#배포 port
DEPLOYED_PORT=8080
#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 &
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에서 배포 시 deploy.sh 실행.