무중단 배포

디우·2022년 10월 29일
1

모아모아

목록 보기
2/17

현재 모아모아 프로젝트의 경우에는 배포를 진행하는 동안 5 ~ 10초 서비스가 먹통이 된다.
기존 배포 과정에서는 기존의 WAS 프로세스를 죽이고 나서 새롭게 시작하는 방식이기 때문에 기존 프로세스가 죽고 새로운 프로세스 즉 WAS가 띄워지는 동안 서비스가 먹통이 되는 것이다.

하지만 우리가 흔히 사용하는 서비스 라고 하면 서비스를 배포하는 중에도 클라이언트의 요구를 수용할 수 있어야 하므로 무중단 배포를 간단하게 구성해보게 되었다.


무중단 배포 방식

무중단 배포 방식이란 앞서 우리팀에서 발생했던 문제처럼 배포 중에도 애플리케이션이 중단없이 서비스가 가능한 것을 말한다.
무중단 배포 방식에는 흔히 블루&그린 방식, 롤링 방식, 카나리 방식과 같이 3가지 방식이 존재하며 각 방식마다의 장단점이 존재한다. 우리 모아모아팀에서 어떤 방식의 배포를 하고 있는지 살펴보기 이전에 각 개념들에 대해서 정리해보자.

블루/그린 방식

블루를 기존 버전, 그리고 새롭게 배포할 버전을 그린이라고 하였을 때, 기존 버전과 동일하게 신버전의 인스턴스를 구성한 후 로드밸런서를 통해서 신버전으로 모든 트래픽을 한번에 전환하는 배포 방식을 블루/그린 방식이다.
여기서 핵심은 한 번에 변경을 수행한다는 것이다. 따라서 기존 버전에 대한 인스턴스와 새로운 버전을 위한 인스턴스가 존재해야하므로 자원이 2배가 든다는 단점이 있으며 비용이 크다. 하지만 기존 버전에 대한 인스턴스가 존재하기 때문에 새로운 버전으로 로드밸런서를 통해서 변경하였을 때 문제가 발생하면 손쉽게 롤백이 가능하다는 장점이 있다.

예를 들어, 기존에 2대로 운영중이었고, 새로운 버전을 배포한다고 하면 기존 2대는 유지한 상태에서 동일한 환경의 새로운 버전을 배포할 인스턴스에 새로운 버전을 배포하고 로드 밸러서의 트래픽을 새롭게 배포된 2대로 변경하는 것이다. 그렇기 때문에 운영하는 서버는 2대이지만 실질적으로는 4대의 서버가 필요하므로 비용이나 자원이 2배로 소모된다.

롤링 방식

롤링 방식은 사용중인 인스턴스를 새 버전으로 점진적으로 교체해 나가는 방식을 말한다.
앞선 예시를 들고와 기존에 2대로 운영중이었다고 하자. 그럼 여기서 한 대의 서버를 새 버전으로 교체하고 그 동안은 하나의 기존 서버로 트래픽이 가도록 조정한다. 그리고 나서 새 버전이 배포가 끝나고 나면 사용자의 요청을 새 버전으로 가게끔 하고 나머지 한 대에 대해서도 배포를 진행해준다. 모든 서버에 대한 배포가 끝나면 로드 밸러서를 통해서 총 2대로 트래픽이 분산되게 할 수 있다.
해당 방식 또한 인스턴스를 차례로 점진적으로 배포하기 때문에 문제가 발생하면 손쉽게 롤백이 가능하다는 장점이 있으며 앞선 블루/그린 방식과 달리 추가적인 인스턴스가 필요없게 된다. 하지만 앞서 보았던 것 처럼 배포 중일 때에는 처리가 가능한 인스턴스가 줄어들게 되기 때문에 사용중인 인스턴스에 트래픽이 몰린다는 문제점이 있으며 배포가 진행중인 상태에서 보면 호환성 문제가 존재할 수 있다는 단점이 있다.

카나리 방식

카나리 방식은 조금씩 새로운 버전으로 트래픽을 전환하는 방식이라고 볼 수 있다. 신버전의 제공 범위를 늘려가면서 모니터링 및 피드백 과정을 거칠 수 있으며 이러한 특징 덕에 A/B 테스트도 진행할 수 있는 배포 방식이다. 조금씩 변경해 나가기 때문에 문제 상황을 빠르게 감지하여 부정적인 영향을 최소화하고 빠르게 롤백할 수 있지만, 앞선 롤링 방식과 마찬가지로 신버전과 구버전에 대한 버전 관리가 필요하다. 또한 네트워크 트래픽 제어에 부담이 된다는 것이 단점으로 다뤄질 수 있다.


포트 스위칭 방식

우리팀의 현재 인프라 구조는 아래와 같다.

특별하게 사용자 트래픽이 많지 않고, 아직은 EC2 한 대로도 운영이 충분히 가능한 상황이다. 따라서 우리는 블루/그린 방식이나 롤링 방식 혹은 카나리 방식과 같이 여러 대의 서버를 운영할 때 사용하는 배포 방식을 적용하기 위해서 서버를 증설하는 것은 우리의 목표와 일치하지 않다고 판단하였고, 한 대의 서버에서 무중단 배포를 구성하는 방법인 포트 스위칭 방식으로 배포를 진행할 수 있도록 하였다.

우리가 적용한 포트 스위칭 방식을 간단하게 설명해보자면 기존에 만약 8080 포트로 WAS 가 동작하고 있다고 하면 새로운 버전을 8081 포트로 띄운다. 그리고 8081 포트가 정상적으로 다 띄워지고 나면 앞단에 존재하는 Reverse Proxy 에서 기존에 8080 포트를 바라보던 것을 8081 포트를 바라보도록 수정해주는 방식이다. (이후 8080 포트를 죽인다.)

우선 우리팀의 배포 환경부터 살펴보자. 우리팀의 경우 Jenkins 와 같은 별도의 CD 툴을 이용하지 않고 있으며 배포 환경은 다음과 같다.

우리는 Github Actions 를 통해서 CI 를 진행한 이후 curl 요청을 별도로 구성한 배포 서버로 보내고, 해당 배포 서버는 코드를 받아와 jar 파일을 생성하고, 해당 파일을 scp 명령을 통해서 production WAS 로 보낸다. 이후 배포 서버에서 같은 VPC 내부에 있는 production WAS에 접속해 기존 프로세스를 죽이고 새롭게 전달 받은 jar 파일을 통해서 스프링을 띄우는 방식이다.

위의 설명을 쉘로 작성한 모습은 아래와 같다.

이렇게 해서 새롭게 배포될 jar 파일을 전송하고 WAS 인스턴스에 작성되어 있는 deploy.sh 을 실행시키도록 하면 배포 서버의 역할을 끝이난다.

이제는 Reverse Proxy 쪽과 WAS 서버에 작성되어 있는 deploy.sh 에 대한 작업을 진행해주면 된다.

먼저 WAS 서버 쪽에 대한 작업부터 보자.
8080 포트가 열려 있으면 해당 포트로 스프링이 띄워져 있다는 것이므로 ./deploy/deploy.sh 에서는 8081 포트로 새로운 스프링을 실행시킨 이후에 기존의 8080포트로 떠있는 스프링을 죽이도록 해야한다.

/deploy/deploy.sh 경로에 작성된 deploy.sh 을 보자.

해당 쉘 스크립트는 8080 포트가 열려 있는지 확인하고 만약 8080 포트가 실행중이라면 RUNNING_PORT 를 8081 로 변경하고 죽여야할 포트(KILL_PORT)는 8080 으로 설정해준다. (만약 반대라면 RUNNING_PORT는 8080, KILL_PORT 는 8081)

그리고 나서 RUNNING_PORT 로 server.port 를 설정해주면서 jar 파일을 실행시켜 준다.

그리고 나서 sleep 15 를 해주고 있는데, 이유는 스프링이 띄워지고 난 이후에 Reverse Proxy 에 해당하는 NGINX에 포트 전환 요청을 보내야 하므로 스프링이 띄워지는 시간을 기다리도록 설정해준 것이다.

sleep 15 를 수행한 이후에는 앞서 배포 서버에서 WAS 서버로 접속하였던 것처럼 pem 키를 이용해 같은 VPC에 있는 NGINX 서버로 접속을 하고 ~/script/switch-dev-port.sh 를 실행하며 인자로 RUNNINGPORT 값을 주도록 하였다. (해당 인자로 NGINX는 포트를 스위칭한다.)

이렇게 하면 Reverse Proxy 쪽과 WAS 서버에 작성되어 있는 deploy.sh 에 대한 작업중 deploy.sh 에 대한 작업은 끝이 난다. 이제 마지막으로 Reverse Proxy 역할을 하고 있는 NGINX 쪽 설정을 살펴보자.

앞선 WAS 쪽에서 ssh 로 접속해서 switch-dev-port.sh 을 실행시켜 주었었는데, 해당 파일은 다음과 같이 작성되어 있다.

위의 쉘 스크립트에서 $1라는 것이 있는데, $n 은 입력 값들이 순서로 저장되며 위치 매개변수라고 표현한다.
즉, $1 은 첫번째 인자를 의미하며 앞서 WAS에서 전달한 RUNNING_PORT 를 의미하게 된다. 위 스크립트는 표준 입력을 읽어서 표준 출력으로 쓰는 tee 명령을 이용해서 /etc/nginx/conf.d/service-rul.incset $service_url http://{ip}:$1, 즉 set $service_url http://{ip}:RUNNING_PORT 를 쓰도록 하며 이후 nginx를 reload 하는 것으로 이해할 수 있다.

만약 RUNNING_PORT가 8080이었다고하면 /etc/nginx/conf.d/service-url.inc 는 다음과 같이 된다.

이제 마지막으로 /etc/nginx/sites-available/default 에 다음과 같이 앞서 설정한 service-url.inc 를 포함하도록 include 해주고, proxy_passservice-url.inc 에 명시해준 IP 와 포트로 설정해주면 포트 스위칭을 이용한 무중단 배포가 구성이 종료되게 된다.

참고로 위의 스크린샷들은 모두 dev 서버에서 설정해준 것을 가져온 것이지만 실제로 prod 환경도 동일하게 무중단 배포 환경이 구성되어 있습니다.


고민 사항

위와 같이 구성을 끝마친 이후에 드는 고민이 생겼다.
가장 먼저 든 고민은 아무래도 현재 WAS가 단일 장애 지점이라는 것이다.
그래서 현재 바로 여러 대의 WAS를 적절하게 구성하고 DB 레플리카도 싱글 레플리카 구성이 아닌 멀티 레플리카 구성을 진행하면서 Reverse Proxy 가 로드밸런싱 하도록 구성하기에는 시간이나 팀원들 모두의 동의를 이끌어 내기도 어려우므로 고민을 통해서 어떻게 개선해 나갈지에 대해서만 생각해보기로 하였다.

SPOF

우선 현재 WAS가 단일 장애 지점이라는 문제점이 있다. 그런데 해당 문제점은 어떻게 보면 간단하게 해결이 가능한데 바로 WAS 를 여러대 두도록 구성하는 것이다. 그리고 현재 Reverse Proxy 역할을 하고 있는 NGINX가 로드 밸런싱도 할 수 있도록 구성하면 된다. 간단하게는 라운드 로빈 방식으로 로드밸런싱을 할 수 있을 것이다. 들어오는 요청을 WAS 로 한 번씩 돌아가면서 보내는 방식이다.
그리고 여기서 현재는 백업 용도로 두고 있는 레플리카 구성을 멀티 레플리카 구성으로 개선하고, 여기서 읽기와 쓰기 쿼리를 분산시켜볼 수 있을 것이다.
그럼 여기서 배포 방식은 그대로 포트 스위칭 방식을 사용할 것인가? 하는 의문이 들 수 있다.
우선 개인적으로는 롤링 방식으로 전환하고 싶다. 블루/그린 방식은 실제 사용하는 서버보다 2배의 비용이나 자원이 들어간다는 점이 치명적인 단점으로 다가온다. (만약 내가 서버 비용을 직접 지불해야 한다고 하면 배포 하는 순간 이외에는 사용하지도 않는 인스턴스 서버에 대해서 비용을 지불하는 것은 심지어 학생인 나에게 큰 부담으로 다가온다.)
물론 롤링 방식도 앞서 정리한 것과 같이 배포가 끝나기 전까지는 특정 인스턴스로 트래픽이 물린다는 단점과 호환성에 대한 문제가 발생할 수 있는데, 우선 트래픽과 같은 문제는 트래픽이 가장 적게 몰리는 시간대에 배포하는 구현이 아닌 방식으로 어느정도 충분히 해결이 가능하다고 보며 호환성의 문제는 짧은 시간동안 배포되기 때문에 큰 문제가 발생할지 아직은 잘 모르겠다. (더 고민이 필요한 것 같다.)

배포가 제대로 되었는지 확인

그럼 이제 배포가 제대로 되었는지 어떻게 확인할 것이냐하는 문제가 있다. 또한 배포가 제대로 이루어지지 않으면 어떻게 되돌릴 것인가 하는 문제도 있다.
우선 우리 상황에서는 다음과 같이 슬렉 알림을 통해서 확인하는 방법이 최선인 것 같다.

앞서 배포 서버에 작성하였던 스크립트를 다음과 같이 수정하는 것이다.
쉘 스크립트의 $? 를 통해서 배포가 제대로 이루어졌는지 그렇지 않은지를 확인해서 슬렉으로 알림을 보내도록 하는 것이다.
($? : 최근에 실행된 명령어, 함수, 스크립트 자식의 종료 상태)

마지막 부분에서 배포가 제대로 이루어졌다면 "데브 배포 성공" 이라는 문구가 우리가 등록한 슬렉 훅을 통해서 전달 받을 수 있게 될 것이며 그렇지 않으면 데브 배포 실패 라는 문구를 알림으로 받을 수 있게 될 것이다.
(Production에 대한 배포이면 메인 배포 성공, 메인 배포 실패 라는 문구를 받게 된다.)

하지만 여전히 롤백에 대해서는 대비가 없다. 수동으로 배포 실패를 확인하고 이전 버전에 대한 배포를 수동으로 진행해주어 급하게 문제를 해결해야한다.

그런데 만약 현재 상황이 아닌 여러 WAS를 사용하는 상황으로 개선되었다고 하면 카나리 방식을 사용해볼 수 있을 것이다. 카나리 방식을 통해서 새로운 버전에 대한 배포가 안전하게 문제없이 잘 되었는지를 확인하고 트래픽을 그쪽으로 점진적으로 변경해 나가는 것이다. 그리고 추가적으로 A/B 테스트도 수행 가능하게 될 것이다.

즉 최종적인 우리팀의 배포 방식을 상상해본다면 카나리 방식을 통해 새로운 버전에 대한 배포를 안정적으로 수행하면서 동시에 A/B 테스트를 통해 사용자 만족도가 어느쪽에서 더 높은지 등도 체크해볼 수 있을 것 같다.

profile
꾸준함에서 의미를 찾자!

0개의 댓글