Python

gunicorn과 nginx를 사용해서 Flask 앱 배포하기

검정비니 2023. 10. 9. 18:14
728x90
반응형

1. 들어가기 전에

1-1. WSGI란?

WSGI(Web Server Gateway Interface)란 CGI(Common Gateway Interface)의 일종으로, 프레임워크의 웹 서버이다. 비슷한 개념으로 ASGI(Asynchronous Server Gateway Interface)가 있는데, 대표적인 예제로는 FastAPI의 배포에 사용되는 uvicorn이 있다.

WSGI Middleware 는 Middleware 라는 이름처럼 Web Application 의 실행 전과 후에 이러한 기능들을 추가해주며, 그 자체로도 WSGI Application 이다. Design Pattern 의 Decorator Pattern 을 생각하면 이해가 쉽다. 양파 껍질 처럼 Web Application 을 감싸고 있는 구조이다.

1-2. 웹서버 구조

흔히 Flask나 django 등으로 파이썬 웹 어플리케이션 서버를 구축하고 배포를 할 때에는 WSGI로 gunicorn이나 uwsgi를 사용하고, 그 앞단에 웹서버로 Nginx를 사용한다. Nginx를 맨 앞단에 배치하는 주된 이유로는 Buffering, Reverse Proxying, Load Balancing 등의 기능을 Nginx가 잘 수행을 해주기 때문이다.

Gunicorn 은 Web Application에 HTTP 요청을 전달해주는 역할의 WSGI HTTP Server 로서 사용하는 것이 주된 사용 목적이며, 추가적인 이유로는 workers의 숫자 조절을 통한 손쉬운 확장 등이 있다.

2. Nginx

2-1. Nginx 설치

배포 환경으로는 AWS EC2 t3.micro (Ubuntu 20.04)를 선택하였다. 우분투 운영체제이기에 설치를 위한 패키지 관리 툴로 apt를 사용하였다.

sudo apt update 
sudo apt install nginx 

2-2. 방화벽 설정

일반적으로 우분투 환경에서 nginx의 접근을 허용하기 위해 방화벽 설정부터 해줘야 한다.
우선 sudo ufw app list 를 통해 설정 가능한 방화벽을 확인하면 다음과 같이 나온다.

Available applications:
  Nginx Full    // HTTP, HTTPS 모두 허용
  Nginx HTTP    // HTTP만 허용
  Nginx HTTPS   // HTTPS만 허용
  OpenSSH

설정은 sudo ufw allow 'Nginx HTTP' 를 통해 가능하고,
sudo ufw status 를 통해 확인할 수 있다.

2-3. 웹서버 확인하기

nginx가 설치되면 자동으로 실행된다.
상태를 확인하고 싶다면 systemctl status nginx 로 가능하다.

만약 여기서 Inactive 상태가 나온다면 ufw의 설정을 바꿔야 할 수가 있다.
이때, 만약 클라우드 환경에서 작업을 하고 있다면 조심해야 한다.

sudo ufw enable을 사용하면 우분투 방화벽이 활성화되면서 설정된 접근들 외에는 모두 차단을 시키게 되는데, 바로 여기서 문제가 발생할 수가 있다.

이 '설정된 접근들'에 ssh가 없게 된다면 방화벽에서 모든 ssh 연결을 차단하면서 접속 중이던 모든 ssh 연결이 끊기고 새로운 ssh 접속마저 모두 차단되게 된다.

로컬 머신에서 작업하고 있다면 sudo ufw enable을 통해 방화벽 활성화를 진행하도록 하고, 클라우드 환경이라면 설정 파일을 다시 확인하거나, 아니면 Nginx 로그를 분석하는 시간을 가져야 할 것이다.

2-4. Nginx 상태 관련 명령어들

우분투에 Nginx를 설치했다면, Nginx는 시스템 데몬에 추가되어서 데몬 프로세스의 형태로 실행되고 있을 것이다. 우분투에서는 이러한 시스템 데몬들의 상태를 관리하기 위한 도구로 systemctl이라는 명령어를 제공한다.

다음 명령어들을 사용하면 쉽게 Nginx 상태를 관리할 수 있게 된다.

sudo systemctl start nginx   // nginx 시작
sudo systemctl stop nginx   // nginx 멈추기
sudo systemctl restart nginx   // nginx 재시작(멈추고 다시 시작)
sudo systemctl reload nginx   // 설정 파일만 수정했을 경우 연결을 끊지 않고 수정사항 적용시키기
sudo systemctl disable nginx   // nginx는 서버가 부팅되면 자동으로 시작된다.
sudo systemctl enable nginx   // 위의 설정을 다시 enable 시키기

3. Gunicorn

3-1. 설치 전에 의존 패키지 설치하기

sudo apt update
sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

3-2. 가상환경 설치하고 접속하기

Virtualenv는 하나의 서버 인스턴스에서 여러개의 파이썬 버전별 혹은 여러개의 프로젝트 별 종속성 문제를 해결하기 위해 설치하는 일종의 python 가상환경이며, 주로 아래의 케이스에서 많이 유용하다.

  • 개발 서버에서 설치된 python verion과 별도의 project를 진행
  • 동시에 여러 Python 프로젝트를 진행하는 경우 각 버전의 차이가 있는 경우
  • 오래된 Python Project에서 Python 버전은 유지하면 기능을 업그레이드하는 경우
  • 상용 클라우드 (AWS, MS Azure, Google Cloud) 등에서 개발할 때 최신 Python 버전을 지원하지 못하는 경우

가상환경 파일들을 만들 디렉토리로 이동한 뒤, 아래의 명령어를 통해서 가상환경을 만들 수 있다.

sudo apt install python3-venv
python3 -m venv venv-name
source venv-name/bin/activate

이제 파이썬 가상환경 상에서 작업을 하게 된다.

3-3. Gunicorn 설치

pip install wheel
pip install gunicorn flask
which gunicorn // gunicorn 경로 확인하기

3-4. Gunicorn으로 플라스크 앱 실행시키기

Gunicorn으로 플라스크 앱을 실행시키기 위해서는 플라스크 어플리케이션 객체를 생성해서 전역변수로 가지는 entrypoint가 되는 파이썬 파일이 필요하다.

예를 들자면 main.py 파일을 만든 뒤, 아래와 같이 app이라는 이름으로 플라스크를 초기화시키도록 하자.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Web App with Python Flask!'

if __name__ == '__main__':
	app.run(host='0.0.0.0', port=5000)

그 후, gunicorn main:app을 실행시키면 gunicorn이 플라스크 앱과 함께 실행된다.

3-5. argparse 사용 시 에러 발생

흔히 하는 실수들 중 하나인데, gunicorn으로 실행시키는 파이썬 프로그램에서 argparse를 사용한 커멘드 라인 매개변수 파싱 기능을 사용하게 되면 다음과 같은 에러가 발생하게 된다.

gunicorn: error: unrecongnized argument error

gunicorn 역시 실행을 할 때 커멘드라인 매개변수를 사용해서 옵션을 선택하게 되는데, 이 기능이 파이썬 프로그램 내의 argparse와 충돌하면서 에러를 발생시키게 된다.

은근히 많은 개발자들이 하는 실수이면서, 은근히 에러 발생 이유를 모르는 사람들이 많은 쉽지만 까다로운 에러이다.

3-6. 서비스 파일 만들기

이제 Nginx처럼 Gunicorn도 시스템 데몬을 통해서 데몬 프로세스의 형태로 실행시킬 차례이다.
우분투에서 시스템 데몬을 만드는 방법은 생각보다 간단하다.
지정된 위치에다가 새로 만들 시스템 데몬을 위한 서비스 파일을 만들어 준 뒤, 시스템에게 해당 서비스에 대한 정보를 추가시켜 주면 된다.

1) /etc/systemd/system/ 디렉토리 안에 /etc/systemd/system/myflask.service 파일을 만들어준다.

sudo nano /etc/systemd/system/myflask.service

위의 커멘드에서 myproject.service 대신 다른 이름을 사용해도 되며, 가능하다면 실행시키는 플라스크 앱 서비스의 이름과 연관지어서 이름을 지어주는 것이 좋다. 기술적인 이유가 있다기 보다는, 그래야 나중에 헷갈리지 않는다.

여기서는 구분을 위해 파일 이름은 myflask, 프로젝트 디렉토리 이름은 myproject, 유저 이름은 ubuntu로 했다. (EC2 우분투 인스턴스에서는 사용자 이름이 ubuntu이기 때문에 이를 그대로 사용한 것이다.)

2) 위에서 실행시킨 텍스트 에디터를 사용해서 아래 내용을 적절히 변경해서 추가해준다.

[Unit]
Description=Gunicorn instance to serve myflask
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/myproject
Environment="PATH=/home/ubuntu/myproject/myproject-env/bin"
ExecStart=/home/ubuntu/myproject/myproject-env/bin/gunicorn --workers 1 main:app

[Install]
WantedBy=multi-user.target

ExecStart 부분에서 HTTP 프록시 대신 유닉스 소켓을 사용할 것이라면 --bind unix:myproject.sock 옵션을 추가해주면 된다.

3) service 파일 실행시키고 상태 확인하기

sudo systemctl start myflask
sudo systemctl enable myflask
sudo systemctl status myflask

위의 커멘드에서 myflask 부분은 위에서 만든 서비스 파일의 이름으로 대체하면 된다.
세 개의 커멘드들을 모두 실행시키고 나면 이제 서비스가 데몬 프로세스로 실행되고 있을 것이다.

4. Gunicorn을 Nginx와 연결시키기

4-1. 들어가기 전에

우선 nginx의 디렉토리 구조를 조금이나마 이해할 필요가 있다.

  • /etc/nginx/sites-available/ : 여기서 설정 파일을 만들어서 수정한다.
  • /etc/nginx/sites-enabled/ : sited-available 디렉토리의 설정 파일이 이 디렉토리의 파일과 link되어 nginx에 적용된다.

정리하자면, 설정파일을 우리가 생성하고 수정할 때는 sites-available에 있는 파일을 건드리고 sites-enabled의 파일은 직접 건드리지 않는다.
그런데 nginx는 sites-enabled를 찾아보지 sites-available을 찾아보지 않는다.
그래서 sites-available에서 수정한 파일을 sites-enable의 파일과 링크시켜서 수정 사항이 반영되도록 한다.

4-2. nginx의 설정 파일 생성하기

/etc/nginx/sites-available/에 새 설정 파일을 만들어준다.

a. 기존의 파일(default)을 삭제하고 내 것(myflask)으로 다시 만들어준다.

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
sudo touch /etc/nginx/sites-available/myflask

b. 에디터로 열어서 다음의 내용을 작성하고 저장한다.

sudo nano /etc/nginx/sites-available/myflask 로 파일을 열어준다.

server {
	listen 80 default_server;
	listen [::]:80 default_server;
	server_name 0.0.0.0;
	location / {
		include proxy_params;
        proxy_pass http://127.0.0.1:8000;
		#proxy_pass http://unix:/home/ubuntu/myproject/myflask.sock; // gunicorn service 파일에서 socket 파일의 경로
	}
}

여기서는 http 프록시 방식을 사용했으나, 만약 유닉스 소켓을 프록시 통신에 사용하고자 한다면 위에 주석 처리되어 있는 줄을 해제하고, 그 위의 줄을 주석 처리해준 뒤, proxy_pass 뒤의 유닉스 소켓 파일의 경로를 수정해주면 된다.

4-3. sites-available의 파일과 sites-enabled의 파일을 연결시키기

sudo ln -s /etc/nginx/sites-available/myflask /etc/nginx/sites-enabled/myflask

4-4. 마무리 작업

마지막으로 해당 설정 파일에 문법 오류가 없는지 체크하고, 아무 문제가 없으면 nginx를 재시작한다.

sudo nginx -t  // 문법 오류 체크
sudo systemctl restart nginx

4-5. 접속해서 확인하기

이제 원격 IP 주소로 접속해서 잘 뜨는지 확인하면 끝이다.

4-6. Nginx 로그 확인하기

/var/log/nginx 로 이동해서 보면 access.log와 error.long 이렇게 두개의 파일을 찾을 수 있을 것이다. 이 두개의 로그 파일들이 Nginx의 로그 파일들이다. cat 등을 사용해서 로그를 확인하면서 디버깅을 진행하면 된다.

cat /var/log/nginx/access.log
cat /var/log/nginx/error.log

4-7. 유닉스 소켓 Permission error

만약 프록시 통신을 위해 유닉스 소켓을 사용하였다면, 소켓 파일에 대해서 Permission denied error가 발생할 수도 있을 것이다. 만약 그렇다면, 통신에 사용한 유닉스 소켓 파일의 접근 권한을 확인해 보도록 하라. 높은 확률로 접근 권한이 루트로 되어 있는 등의 문제가 있을 것이다.

반응형