[SPOT] 무중단 배포 도입

김민석·2025년 1월 6일
1

SPOT

목록 보기
6/11
post-thumbnail

SPOT의 출시가 가까워지고 있다! 서버 기능 개발은 마무리된 상태고, 서비스 테스트 코드 작성을 통해 비즈니스 로직들을 다시 한 번 점검하고 있다.

출시를 위해서 더욱 안정적인 서버 관리와 운영이 필요한 시점이라는 생각이 들었다. 그래서 그냥 토이 프로젝트라는 관점에서 벗어나 실제 운영되는 서비스라는 관점에서 이 “SPOT”을 바라보았을 때, 그저 대학생 개발자인 내가 보기에도 눈에 띄는 불편한 부분들이 몇 존재했다.

그 중 하나가 새로운 버전의 서버를 배포할 때, 서버가 아예 다운되어 서비스를 이용할 수 없다는 문제점이었다. 현재 배포 파이프라인은 기존의 서버를 종료한 뒤 새로운 버전의 서버를 실행시키기 때문에 두 과정 사이 발생하는 다운타임 동안은 서비스를 이용할 수 없다.

이런 문제점이 이전에도 종종 발생했다.

Downtime 발생

Demo Day에서 서비스를 시연하고 있을 때였다. 시연 중, 스터디 검색 기능이 제대로 작동하지 않는다는 문제점을 발견하여 이를 급하게 수정한 후 서버에 배포를 하려고 했다. 하지만 우리 서비스를 한 번 사용해보고 싶은 사람들은 계속 우리 부스에 방문했지만, 이 서버가 배포되는 다운타임 동안에는 API 호출이 안 되었기 때문에 서비스 시연을 정상적으로 할 수 없었다. 어쩔 수 없이 관람객들을 다른 부스로 보내는 수밖에 없었다.

위와 같은 문제가 실 서비스 환경에서도 발생한다면 정말 큰 문제다. 약 3분 간의 다운타임은 굉장히 치명적이다.

추가적으로 현재 배포 로직은 기존의 서버를 종료한 뒤, 새로운 서버를 가동하는 것이다. 만약 새로운 서버 실행 중 문제가 발생한다면 이를 즉시 롤백 할 수 없다는 문제점 또한 존재한다.

그래서, 위 문제 들을 해결하기 위해 무중단 배포를 도입할 것을 팀에 제안했다.

무중단 배포 방식

크게 Blue-Green 배포, Rolling 배포, Canary 배포 3가지로 나뉜다.
이를 간단히 정리하자면, 다음과 같다.

종류설명
Rolling 배포기존 버전의 인스턴스를 순차적으로 새 버전으로 대체하는 방식.
Canary 배포트래픽의 일부를 새 버전으로 보내면서 점진적으로 배포를 진행하는 방식.
Blue-Green 배포로드 밸런서를 통해 트래픽을 한 번에 기존 버전에서 새 버전으로 옮기는 방식.
새로운 서버의 배포가 성공적으로 완료된 경우, 트래픽을 옮기고 기존 서버는 종료.

현재 SPOT 팀은 단일 인스턴스를 사용하고 있고, 이후에도 우선은 하나의 인스턴스로 서버를 관리할 예정이기 때문에, Rolling과 Canary 같은 여러 인스턴스가 필요한 배포 방식은 적합하지 않다고 생각했다.

Blue-Green 배포

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분의 다운타임을 거의 없다시피 감소시킬 수 있었다.

또한 배포 후 문제가 발생하더라도 즉시 롤백할 수 있었다. 기존 배포 과정에서는 기존의 서버를 종료 하고, 새로운 서버를 실행하기 때문에 롤백 과정이 복잡했지만, 이제는 기존 버전으로 트래픽을 되돌리는 것만으로 빠르게 롤백할 수 있기 때문에 위험을 최소화할 수 있다.

profile
경험하며 성장하는 개발자 지망생

0개의 댓글