uWSGI 무중단 배포 구현

Ihwan Shin·2024년 12월 22일

CI/CD & devOps

목록 보기
5/10
post-thumbnail

Nginx + uWSGI + Django를 사용한 서버에서 배포 시 매번 sudo systemctl restart emperor.uwsgi.service 명령을 사용하지 않고, touch-reload 옵션을 활용하여 무중단 배포를 구현하는 방법에 대해 다뤄보겠습니다.

기존 배포 방식

기존에는 sudo systemctl restart emperor.uwsgi.service 명령을 사용하여 uWSGI Emperor 서비스를 재시작하고, 그로 인해 서버 전체가 다운되거나, 서비스 요청이 잠시 중단되는 상황이 발생했습니다.

배포 시마다 서버가 재시작되기 때문에, 사용자에게 제공되는 서비스의 가용성을 유지하는 데 어려움이 있었습니다. 특히, 트래픽이 많은 서비스에서는 이러한 다운타임이 큰 문제가 될 수 있습니다.

touch-reload 옵션을 통한 무중단 배포

touch-reload 옵션을 사용하면, 애플리케이션의 파일 수정을 통해 서버를 graceful reload 할 수 있습니다. 이 방법을 사용하면 배포 시 서버를 재시작할 필요 없이, 기존 요청을 처리하면서 새로운 요청은 새로운 애플리케이션 코드로 처리할 수 있게 됩니다.

1. uWSGI 설정 파일에 touch-reload 옵션 추가

먼저, uWSGI Emperor 모드로 실행되는 uWSGI 설정 파일touch-reload 옵션을 추가합니다. 이 옵션은 특정 파일의 수정을 감지하여 uWSGI 애플리케이션을 graceful reload 시킵니다.

emperor를 사용하고 있다면 /etc/uwsgi/emperor.ini 말고 하위의 uWSGI 애플리케이션의 설정 파일에 다음과 같이 touch-reload 옵션을 설정합니다.

[uwsgi]
# 기존 설정들...
emperor = /etc/uwsgi/vassals

master = true
lazy-apps = true
reload-on-asynchronous-exit = true
touch-reload = /path/to/your/touch/file.py

2. 배포 후 파일 변경 감지 트리거

이제 배포가 완료되면, 새로운 코드가 반영되도록 하기 위해 touch-reload 옵션에 설정된 파일을 수정합니다. 예를 들어, touch.py 파일을 수정하는 방법은 다음과 같습니다.

touch /path/to/your/touch/file.py

이 명령은 단순히 file.py 파일의 최종 수정 시간을 변경합니다. 그러면 uWSGI는 이 파일의 변경을 감지하고, 해당 애플리케이션을 graceful reload합니다. 기존 요청은 처리 중인 워커에서 계속 처리되고, 새로운 요청은 새로운 코드로 처리되기 시작합니다.

3. 무중단 배포 동작

touch-reload 옵션을 설정한 후, 배포 시에는 아래와 같은 방식으로 무중단 배포를 할 수 있습니다.

  1. 애플리케이션의 새로운 코드를 배포합니다.
  2. 배포가 끝난 후, /path/to/your/touch/file.py 파일을 수정하여 uWSGI에 graceful reload 신호를 보냅니다.
  3. 기존 워커는 현재 처리 중인 요청을 모두 처리하고 종료되며, 새로운 워커가 새로운 코드로 요청을 처리합니다.
  4. 이 과정에서 서비스 중단 없이 애플리케이션 코드가 새로운 버전으로 교체됩니다.

4. 자동화 예시

배포 자동화 스크립트를 작성하여, 배포 과정에서 자동으로 touch-reload 파일을 수정하도록 할 수 있습니다. 예를 들어, 배포 스크립트에서 다음과 같이 추가할 수 있습니다.

#!/bin/bash

# 1. 코드 배포
git pull origin main

# ~~~ 대충 배포 준비하는 동작 ~~~

# 2. touch-reload 파일 수정
touch /path/to/your/touch/file.py

echo "배포 완료, uWSGI 애플리케이션 리로드됨."

5. uWSGI 서비스 설정

touch-reload 옵션을 설정한 후에는 systemctl restart emperor.uwsgi.service 명령 없이도, 배포할 때마다 graceful reload가 자동으로 이루어집니다. 기존의 systemctl restart 명령을 더 이상 사용할 필요가 없고, 애플리케이션은 다운타임 없이 자동으로 리로드됩니다.

6. uWSGI 메모리 관리 및 최적화

마스터 프로세스와 워커 프로세스의 메모리 사용

uWSGI의 Emperor 모드에서 마스터 프로세스는 여러 워커 프로세스를 관리하는 역할을 하며, 실제로 요청을 처리하는 것은 워커 프로세스입니다. 따라서, 마스터 프로세스는 메모리를 크게 소모하지 않으며, 점진적인 메모리 증가가 발생하지 않습니다. 주로 메모리 관리 문제는 워커 프로세스에서 발생합니다.

워커 프로세스의 메모리 관리

워커 프로세스는 실제 애플리케이션 로직을 처리하면서 메모리를 할당하고, 때때로 메모리 누수나 과도한 메모리 사용이 발생할 수 있습니다. 이를 해결하기 위해 uWSGI는 reload-on-rss, reload-on-as, vacuum 등의 메모리 관리 옵션을 제공합니다.

Graceful reload를 사용할 경우, 워커 프로세스만 재시작되고 마스터 프로세스는 종료되지 않습니다. 이때, vacuum, reload-on-rss, reload-on-as 등의 설정을 통해 메모리 관리가 자동으로 이루어집니다. 이러한 설정은 워커가 과도한 메모리 사용으로 인한 성능 저하를 일으키지 않도록 하며, 마스터 프로세스는 재시작하지 않아도 안정적인 메모리 관리를 할 수 있습니다.

1. vacuum = true: 메모리 및 리소스 자동 정리

  • 설명: 워커가 종료될 때 메모리와 리소스를 자동으로 정리하는 기능입니다. 이를 통해 메모리 누수를 방지할 수 있습니다.
  • 장점: 워커가 종료될 때마다 메모리를 정리하므로, 메모리 누수가 발생할 가능성이 줄어듭니다.
    vacuum = true # 워커 종료 시 메모리 및 리소스를 자동으로 정리

2. reload-on-rss: 물리적 메모리 초과 시 워커 재시작

  • 설명: 워커가 사용하는 물리적 메모리(RSS, Resident Set Size)가 설정된 임계값을 초과하면 워커를 재시작하는 기능입니다.
  • 장점: 메모리 사용량이 과도해질 경우 워커를 자동으로 재시작하여, 서버 성능 저하를 방지할 수 있습니다.
    reload-on-rss = 200 # 워커가 200MB 이상의 물리적 메모리를 사용하면 재시작

3. reload-on-as: 가상 메모리 초과 시 워커 재시작

  • 설명: 워커가 사용하는 가상 메모리(AS, Address Space)가 설정된 임계값을 초과하면 워커를 재시작하는 기능입니다.
  • 장점: 가상 메모리 사용량을 모니터링하여, 물리적 메모리뿐만 아니라 가상 메모리가 과도하게 사용되는 경우에도 워커를 재시작할 수 있습니다.
    reload-on-as = 400 # 워커가 400MB 이상의 가상 메모리를 사용하면 재시작

그 외에도 추가적으로 이용할 수 있는 설정들

[uwsgi]
# 기존 설정들...

# master 모드로 uWSGI를 실행하여 여러 worker 프로세스를 관리하도록 합니다.
master = true  # uWSGI가 master-worker 모델로 실행되어야 graceful reload가 가능

# lazy-apps = true로 설정하면, 기존대로 maste가 애플리케이션 초기화를 하지않고 worker 프로세스 각각이 시작될 때 애플리케이션을 초기화하도록 합니다. 이 설정은 초기화 과정에서 발생하는 지연을 줄이는 데 유용합니다.
lazy-apps = true  # worker 프로세스에서 애플리케이션 초기화 

# touch-reload 옵션을 설정하여 특정 파일을 수정하면 uWSGI가 graceful reload를 합니다.
touch-reload = /path/to/your/touch/file.py  # 배포 시 수정할 파일

# 기본적으로 uWSGI는 재시작 시에 기존 요청을 끝까지 처리하고, 이후 새로운 워커를 시작하는데, 이때 reload-mercy 값에 따라 대기 시간을 설정할 수 있습니다.
reload-mercy = 10  # 기존 요청이 최대 10초 동안 처리되도록 대기

# 서버가 종료될 때 불필요한 임시 파일들을 자동으로 삭제합니다.
vacuum = true  # 서버 종료 시 임시 파일 정리

# worker 프로세스의 수를 설정합니다. 이 값은 서버의 트래픽과 CPU 코어 수에 맞게 조정해야 합니다.
workers = 8  # 8개의 worker 프로세스를 생성


buffering = true  # 요청을 처리한 후 응답을 버퍼링하여 한 번에 전송
post-buffering = true  # POST 요청 본문이 모두 전송된 후 응답을 시작

# 각 워커가 처리할 최대 요청 수를 설정하여 메모리 누수 및 리소스 낭비를 방지합니다.
max-requests = 1000  # 각 워커가 1000개의 요청을 처리 후 종료하고 새 워커로 교체

# 워커의 메모리 사용량이 특정 값 이상으로 증가하면 워커를 리로드하도록 설정합니다.
#reload-on-rss = 512  # 워커 프로세스의 메모리 사용량이 512MB를 초과하면 리로드
reload-on-as = 1024  # 워커 프로세스의 주소 공간 사용량이 1GB를 초과하면 리로드

# 멀티스레딩을 활성화하여 워커 내에서 여러 요청을 동시에 처리할 수 있게 합니다.
enable-threads = true  # 멀티스레딩 활성화

# uWSGI 로그를 지정된 파일에 기록하여 배포 과정에서 발생한 이벤트를 추적합니다.
logto = /path/to/your/uwsgi.log  # 로그 파일 경로 설정

# uWSGI에서 로그 출력을 비활성화하여 배포 시 로그를 기록하지 않도록 설정합니다.
# disable-logging = true  # 로그 비활성화 (필요시 주석 해제)

# 유휴 워커의 메모리 누수 및 리소스를 방지하기 위해 워커 종료 후 자동으로 새 워커가 시작되도록 합니다.
reload-on-exit = true  # 워커 종료 후 자동 리로드

결론

touch-reload 옵션을 활용한 무중단 배포는 Nginx + uWSGI + Django 기반의 서버에서 배포 시 서비스 중단을 최소화하고 애플리케이션 코드의 신속한 반영을 가능하게 해줍니다. 이 방법은 특히 트래픽이 많은 서비스에서 유용하며, 서버 다운타임을 피할 수 있는 매우 효과적인 방법입니다.

이번에 소개한 방법을 적용하면, 배포 과정에서 서버를 재시작하거나, 서비스 중단을 걱정할 필요 없이 자동으로 graceful reload가 이루어져, 사용자에게 원활한 서비스를 제공할 수 있습니다.

요약:

  • 기존 배포 방식: 기존에 sudo systemctl restart emperor.uwsgi.service를 사용한 방식은 서버가 재시작되므로 잠시 다운타임이 발생할 수 있습니다.
  • touch-reload 옵션: 이 옵션을 설정하여, 특정 파일의 수정만으로 uWSGI 서버를 재시작하지 않고 graceful reload가 가능하게 됩니다. 이를 통해 다운타임 없이 배포를 할 수 있습니다.
  • 배포 자동화 스크립트: 배포 후 자동으로 touch-reload 파일을 수정하는 스크립트를 작성하여 배포 과정을 자동화할 수 있습니다.
profile
Backend Engineer 💻 (since. 21/07/01)

0개의 댓글