SPOT의 출시
가 가까워지고 있다! 서버 기능 개발은 마무리된 상태고, 서비스 테스트 코드 작성을 통해 비즈니스 로직들을 다시 한 번 점검하고 있다.
출시를 위해서 더욱 안정적인 서버 관리와 운영이 필요한 시점이라는 생각이 들었다. 그래서 그냥 토이 프로젝트라는 관점에서 벗어나 실제 운영되는 서비스라는 관점에서 이 “SPOT”을 바라보았을 때, 그저 대학생 개발자인 내가 보기에도 눈에 띄는 불편한 부분들이 몇 존재했다.
그 중 하나가 새로운 버전의 서버를 배포할 때, 서버가 아예 다운되어 서비스를 이용할 수 없다는 문제점이었다. 현재 배포 파이프라인은 기존의 서버를 종료한 뒤 새로운 버전의 서버를 실행시키기 때문에 두 과정 사이 발생하는 다운타임 동안은 서비스를 이용할 수 없다.
이런 문제점이 이전에도 종종 발생했다.
Demo Day에서 서비스를 시연하고 있을 때였다. 시연 중, 스터디 검색 기능이 제대로 작동하지 않는다는 문제점을 발견하여 이를 급하게 수정한 후 서버에 배포를 하려고 했다. 하지만 우리 서비스를 한 번 사용해보고 싶은 사람들은 계속 우리 부스에 방문했지만, 이 서버가 배포되는 다운타임 동안에는 API 호출이 안 되었기 때문에 서비스 시연을 정상적으로 할 수 없었다. 어쩔 수 없이 관람객들을 다른 부스로 보내는 수밖에 없었다.
위와 같은 문제가 실 서비스 환경에서도 발생한다면 정말 큰 문제다. 약 3분 간의 다운타임은 굉장히 치명적이다.
추가적으로 현재 배포 로직은 기존의 서버를 종료한 뒤, 새로운 서버를 가동하는 것이다. 만약 새로운 서버 실행 중 문제가 발생한다면 이를 즉시 롤백 할 수 없다는 문제점 또한 존재한다.
그래서, 위 문제 들을 해결하기 위해 무중단 배포를 도입할 것을 팀에 제안했다.
크게 Blue-Green 배포, Rolling 배포, Canary 배포 3가지로 나뉜다.
이를 간단히 정리하자면, 다음과 같다.
종류 | 설명 |
---|---|
Rolling 배포 | 기존 버전의 인스턴스를 순차적으로 새 버전으로 대체하는 방식. |
Canary 배포 | 트래픽의 일부를 새 버전으로 보내면서 점진적으로 배포를 진행하는 방식. |
Blue-Green 배포 | 로드 밸런서를 통해 트래픽을 한 번에 기존 버전에서 새 버전으로 옮기는 방식. 새로운 서버의 배포가 성공적으로 완료된 경우, 트래픽을 옮기고 기존 서버는 종료. |
현재 SPOT 팀은 단일 인스턴스를 사용하고 있고, 이후에도 우선은 하나의 인스턴스로 서버를 관리할 예정이기 때문에, Rolling과 Canary 같은 여러 인스턴스가 필요한 배포 방식은 적합하지 않다고 생각했다.
Blue-Green 배포 방식은 두 개의 환경(Blue와 Green)을 병행하여 운영하며, 트래픽을 한 번에 이전 버전에서 새로운 버전으로 전환하는 방식이다. 이 방식은 일반적으로 여러 인스턴스를 사용하는 환경에서 효과적으로 작동하는데, SPOT에서는 단일 인스턴스를 사용하면서도 Blue-Green 배포 방식을 도입할 수 있었다.
리소스가 제한된 환경에서 무중단 배포를 구현하기 위해, 단일 서버 인스턴스에서 두 포트(8080, 8081)를 활용하는 방식으로 Blue-Green 배포를 적용했다.
예를 들어, 8080 포트는 기존 서버, 8081 포트는 새로운 버전의 서버를 처리 한다고 가정하자. 이때, Nginx 리버스 프록시를 사용해 트래픽을 포트 8080에서 8081로 빠르게 전환할 수 있다. 새로운 버전이 안정적으로 작동하는 것을 확인한 후, 트래픽을 8081 포트로 전환하는 방식으로 새 서버의 배포를 완료한다면 제한된 리소스 내에서 두 포트를 활용하여 무중단 배포를 성공적으로 구현할 수 있다고 판단 했다.
기존 CI/CD 파이프라인을 적극 활용하여 무중단 배포를 구현했다. GitHub Action Workflow에 무중단 배포를 위한 스크립트를 추가하였다. 변경된 내용은 다음과 같다.
- name: Deploy to server
uses: appleboy/ssh-action@master
id: deploy
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
# Docker 이미지를 최신 상태로 가져오기
sudo docker pull ${{ secrets.DOCKER_REPO }}:latest
# 서버의 현재 환경 확인 (블루 / 그린)
ENV_COLOR=$(curl --silent --fail https://www.teamspot.site/spot/current-env || echo "none")
if [[ "$ENV_COLOR" == "blue" || "$ENV_COLOR" == "none" ]]; then
# 블루 서버가 실행 중이거나 아무 서버도 돌아가고 있지 않다면 그린 서버를 실행
sudo docker-compose -f docker-compose.yml up -d --no-deps web-green
sleep 45
# 그린 서버가 정상적으로 작동하는지 확인 (예: HTTP 상태 코드 200 확인)
if curl --silent --fail http://localhost:8081; then
echo "Green server is working correctly."
# Nginx를 Green 서버로 전환
sudo sed -i 's/8080/8081/' /etc/nginx/sites-available/teamspot.site
sudo systemctl restart nginx
# 기존 서버 종료
sudo docker-compose -f docker-compose.yml stop web-blue
else
echo "Green server is not responding correctly. Exiting."
exit 1
fi
elif [[ "$ENV_COLOR" == "green" ]]; then
# 그린 서버가 실행 중이면 블루 서버를 실행
sudo docker-compose -f docker-compose.yml up -d --no-deps web-blue
sleep 45
# 블루 서버가 정상적으로 작동하는지 확인
if curl --silent --fail http://localhost:8080; then
echo "Blue server is working correctly."
# Nginx를 Blue 서버로 전환
sudo sed -i 's/8081/8080/' /etc/nginx/sites-available/teamspot.site
sudo systemctl restart nginx
# 기존 서버 종료
sudo docker-compose -f docker-compose.yml stop web-green
else
echo "Blue server is not responding correctly. Exiting."
exit 1
fi
else
echo "Unable to determine current environment, exiting."
exit 1
fi
현재 리버스 프록시와 HTTPS 지원을 위해 Nginx를 사용하고 있다. 이를 Blue-Green 배포에도 적극 활용했다.
먼저, 현재 가동되고 있는 서버의 Color를 확인한다. 이후 서버의 Color에 맞게 아래 로직을 수행한다.
만약, 현재 가동되고 있는 서버가 Blue라면, 새로운 서버인 Green을 실행한다. 이후 서버가 가동될 때까지 일정 시간 대기한 뒤, Health-Check를 진행하여 새로 가동된 Green 서버가 문제 없이 작동된 경우, Green 서버로 트래픽을 보내고 기존 Blue 서버를 종료한다.
만약 정상적으로 작동되지 않는다면, Blue 서버를 종료하지 않고, 모든 트래픽을 Blue 서버에게 가도록 유지한다.
기존 배포 과정에 비해 배포 소요 시간 자체는 약 30~40초(1m 57s → 2m 27s)
정도 증가했다. 지금 이용하고 있는 인스턴스가 프리티어에서 제공되는 인스턴스라 성능이 떨어지기도 하고 동시에 Spring 서버 2대를 가동하려고 하니 생각보다 서버가 가동되기까지 긴 시간이 필요했다.
하지만, 사용자 입장에서는 Nginx의 재실행 시간 외에는 문제 없이 서비스를 이용할 수 있기 때문에 기존 약 3분의 다운타임을 거의 없다시피 감소시킬 수 있었다.
또한 배포 후 문제가 발생하더라도 즉시 롤백할 수 있었다. 기존 배포 과정에서는 기존의 서버를 종료 하고, 새로운 서버를 실행하기 때문에 롤백 과정이 복잡했지만, 이제는 기존 버전으로 트래픽을 되돌리는 것만으로 빠르게 롤백할 수 있기 때문에 위험을 최소화할 수 있다.