1편에 이어서 이번에는 서비스를 중단하지 않고 배포할 수 있는 환경을 만들어 보자.
크게 3종류의 배포 방식이 있다. 각각 어떤 식으로 배포되고 장단점은 무엇이 있는지 간단하게 살펴보자
롤링 배포는 각 서버를 한 개씩 새로운 버전으로 (점진적) 배포하는 방법을 말한다.
이번에는 간단하고 비교적 구현이 쉬운 롤링 배포 방식으로 배포를 해볼 예정이다.
actuator는 spring 서버의 상태를 실시간으로 확인할 수 있는 기능이다.
다음과 같이 의존성을 추가하자
implementation 'org.springframework.boot:spring-boot-starter-actuator'
spring actuator는 서버의 여러 가지 상태를 확인할 수 있는 강력한 기능이지만 민감한 정보도 포함되어 있을 수 있다. 따라서 다음과 같이 health만 사용하도록 yml 파일로 설정해 주어야 한다.
설정후에 /actuator/health로 확인하면 다음과 같은 결과를 받을 수 있다.
nginx를 reverse proxy로 이용해서 서버의 트래픽을 분산시킬 것이다.
배포 시에 한쪽의 서버가 배포 중이더라도 health-check를 통해서 다른 살아있는 서버들에게 트래픽을 로드밸런싱 해준다. 따라서 멈추지 않고 서비스를 지속할 수 있다.
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;
}
}
}
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 build --tag nginx:test-nginx .
build 확인
nginx 실행시키기
docker run -d --name webserver -p 80:80 [tag | image id]
/status로 확인해 보면 서버가 죽은 경우는 다음과 같이 down으로 표시된다.
서버가 살아있는 경우는 다음과 같이 up으로 표시된다.
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 # 다음 배포 서버를 위한 지연
Jenkins 관리 > Configure System > Global properties
shell script에서 사용할 민감 정보인 ip와 yml을 암호화한 key를 환경 변수로 저장해두고 쓰자
빌드 후 조치에서 실행시킬 스크립트를 다음과 같이 변경해 주자
deploy.sh에 jenkins 환경 변수 값을 넘겨주는 것이다.
클릭시 영상 재생
무중단 배포가 잘 동작하는 것을 확인할 수 있다.