컴퓨터 공학

👉 [Infra] Nginx 기반 무중단 배포를 직접 만들어 보자.

bitcodic 2022. 4. 25. 01:16

Why

서비스를 배포하다보니 계속해서 반복되는 작업이 눈에 띄었다.

Spring 경우, 배포를 위한 과정이 python 프레임워크들에 비해 복잡했기 때문.

 

반복적으로 배포 과정을 불필요하게 진행하는 것보다 반자동으로 편하게 작업할 수 있을 것 같았다.

기존에 Docker 활용할 때도 일일이 입력하는 명령어 집합을 만들어서 종종 썼었으니.

 

검색해보니까 다양하게 나왔으나, 우선 내가 필요한건 라이트하게 임시로 쓸 수 있는 배포였다.

CI/CD 전문화하여 구축하기에는 '시간'이라는 리소스가 한정적이므로 trade-off가 명확했다.

 

인상 깊었던 SW스타트업 수업 캡쳐 짤. 우리도 프로젝트도 마찬가지.

HOW

 그래서 여러가지 찾아보는데, 이상하게 내용이 오버 스펙처럼 보였다.

굳이 이렇게 까지...? 라는 생각이 계속 들었다.

 

대게 어떤 방식이냐면...

 

(0) Spring profiles + port setting

(1) CHECK : 해당 API Call > check now port

(2) BUILD : 해당 port 가진 JAR 빌드

(3) KILL : 이전 프로세스

(4) RUN :배포판 실행

 

더불어 대부분이 누군가의 첫 블로그 내용이 계속 복사된 느낌이었다.

그래서 해당 배포의 원리만 이해한 채, 2시간 만에 뚝딱 만들어보았다.

 


 

우선 Nginx 기반 무중단 배포는 구현이 무척이나 간결하다.

 

(0) A 포트(과거 버전), B 포트(현재 버전)을 각각 띄운다. 현재 유저들은 A 포트로 연결이 된다.

(1) Nginx의 내장 기능 중 Reverse Proxy 를 활용해 80(Nginx) -> A(Spring) 로 돌려놓던 것을

(2) nginx reload 명령어를 활용해 80(Nginx) -> B(Spring)으로 돌려 놓는 거다.

 

내 경우는 미리 8081에 새버전의 서버를 기동시키고, Nginx 설정에서 프록시 설정포트를 8081로 넘겨주면 끝.

 

다음 버전은 그러면 8081이 아니라 8080로, 즉 번갈아서 쓰면 된다.

 

구조 자체는 이렇다

 

다른 아티클들에서 앞서 언급한 것처럼 프로필(?)을 써서 하지말고

그냥 현재 동작중인 포트만 체크해서 다른 포트 열어주고,

거기로 Nginx가 바라보게 하면 되는거 아닌가..? 하면서 작업했다.

 

lsof 활용해서 내가 쓰기로한 포트 8080 혹은 8081 중에 뭐가 열렸는지 확인하며

포트가 열리지 않은 쪽으로 환경 변수를 바꿔주고 reload 동작 시켜준다.

 

PREV_PORT=8080
CURRENT_PORT=8080

ACTIVE_PORT=$(lsof -t -i:8080)

#8080인지 확인
if [ ${ACTIVE_PORT} -ge 1 ]
	#기존 돌아가는 서버가 8080에서 돔
	then
		echo "set \$MAIN_PORT 8081;" | sudo tee /etc/nginx/conf.d/service-url.inc
		CURRENT_PORT=8081
		cd ubuntu
		mvn clean package -P deploy-1
    #8081에서 돔
	else
		echo "set \$MAIN_PORT 8080;" | sudo tee /etc/nginx/conf.d/service-url.inc
		PREV_PORT=8081
		cd ubuntu
		mvn clean package -P deploy-0
fi

echo "New build completed.."

nohup java -jar target/{FILE_NAME}.jar &

#널널하게 잘 동작하기까지 30초면 된다.
sleep 30

HEALTH_CHECK=$(lsof -t -i:$CURRENT_PORT)

if [ ${HEALTH_CHECK} -ge 1 ]
	#새로운 포트에서 잘 동작하면 이전 서버 죽임
	then
    	echo "Good"
    	service nginx reload
    	kill $(lsof -t -i:$PREV_PORT)
    else
    	echo "Bad"
fi

 

maven에서 빌드할 때, 스프링의 property 에 서버포트가 8080과 8081로 하드 코딩 및 할당하여

deploy-0과 deploy-1 각각 따로 두어 포트 변경을 처리했다.

 

Result

기존 : Pull > build > Run > Kill (Prev) > Check 과정을

현재 : Pull > Shell-Run

 

단 2개의 단계로 줄였다. 누가 보면 뭐 대단한거라고 블로깅까지 하나 생각할 수 있겠으나...🥲

개발자라면 문제의 크기에 상관없이 문제를 정의하고,

해결하는 흐름을 반복적으로&태도적으로 학습해야 한다고 생각한다. (가설검증)

거기에 비즈니스적 가치(Cash, Time etc.)까지 더 하는 것이 궁극적인 목표고.

 

그런 관점에 있어서 반자동화 프로세스를 구축한 것은 Problem Solving 차원에서 유의미한 과정이라고 생각한다.

 

Limit

다만 해당 방식의 한계는 nginx reload 처리로 신규 서버로 요청을 돌려주는데...

이 과정에서 물려있던 유저가 있다면 에러가 발생할 수 있다.

큰 서비스들은 사용할 수 없는 한계가 있지 않을까 싶다.

 

예를 들어 쿠팡/아마존에서 이런 방식으로 한다면, 1초 동안 얼마나 많은 사람들이 결제를 하겠는가?

그게 다 돈이니 손실로 이어진다.

 

내 경우는 동접자가 없다시피 하기 때문에 가능했다.

나중에 큰 서비스들은 무중단 배포의 원리가 어떻게 되는지 찾아봐야겠다.

 

+추가

nginx reload는 기존의 연결이 끝날때까지 죽이지 않는다고 한다. 그러면 해당 한계점은 해결될 수 있는 부분이라고 생각한다.