안녕하세요. 곰돌이 개발자입니다.

 

이번에 스타트업에서 새롭게 합류하면서 FastAPI과 Python을 사용해서 백엔드 개발을 하게 되었습니다. Spring과 Java로 웹 개발을 할 때와는 비교해서 새로운 내용들이 있어 공부할 내용들이 많은 것 같습니다. 그 중에서 오늘은 Gunicorn과 Uvicorn에 대해서 공부한 내용을 정리해보고자 합니다.

제가 작성한 내용에 대해 틀린 점이 있다면 비판적인 지적은 언제나 환영입니다.

 

 

FastAPI로 웹 API를 실행시키기 위해서는 Uvicorn이 필요하다


https://fastapi.tiangolo.com/ko/tutorial/first-steps/

위의 FastAPI 공식문서에 들어가보면 FastAPI 서버를 실행할 때 Uvicorn을 사용해 API 서버를 실행합니다

uvicorn main:app --reload

 

여기의 uvicorn은 뭐고 왜 uvicorn을 사용해서 실행을 해야할까요?

 

Uvicorn은 ASGI(Asynchronous Server Gateway Interface)라는 표준을 구현한 서버입니다. FastAPI로 짜여진 코드는 단독으로 실행되어서 웹 API로써 동작할 수 없습니다. 실제로 공식문서를 따라 아래와 같이 간단한 웹 API를 구현해 파이썬 프로그램을 실행하면 바로 종료되어 버려집니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
  return {"message":  "Hello World"}
python main.py  // 바로 종료됨

 

 

이는 애초에 작성된 main.py 코드가 서버를 실행시키는 코드가 아니기 때문입니다. 위의 코드는 FastAPI app 인스턴스를 만들고 라우터를 등록하는 코드이고 HTTP 요청을 받아 FastAPI 인스턴스에게 전달하고 인스턴스로부터 데이터를 받아 HTTP 응답을 제공하는 서버의 역할을 하는 것은 Uvicorn입니다. 정리하면 웹 요청과 응답의 flow는 아래와 같습니다.

                                http request를
                                asgi 프로토콜에 맞게
        http request                 변환
client ---------------> uvicorn ---------------> fastapi app instance

 

 

main.py에 구현되어 있는 fastapi app 인스턴스는 Uvicorn에게 ASGI 프로토콜에 맞는 요청을 받아 데이터를 처리한 후 Uvicorn에게 값을 전달해줍니다. 여기서 Uvicorn은 HTTP 요청을 ASGI 프로토콜에 맞춰 변환하며 또 인스턴스에서 반환한 데이터를 HTTP 응답으로 변환해 client에게 전달하는 웹 서버의 역할을 합니다.

 

 

그럼 Gunicorn은 뭐야?


FastAPI가 등장하기 전 Python을 사용하는 Django와 Flask 웹 프레임워크는 Uvicorn과 비슷한 Gunicorn을 사용했습니다. Uvicorn이 ASGI을 구현한 서버라면 Gunicorn은 WSGI(Web Server Gateway Interface)를 구현한 서버입니다. 사실 등장만으로 따지만 WSGI가 더 먼저 등장하고 사용되었기 때문에 WSGI를 더 먼저 설명하는 것이 맞았을 겁니다 (하지만 주제가 FastAPI이니만큼 Uvicorn을 먼저 설명했습니다). WSGI와 ASGI의 차이라고 한다면 ASGI는 요청의 비동기 처리가 가능하다는 것입니다. WSGI는 HTTP 요청을 받으면 Django나 Flask에 등록된 함수를 callback 하여 동기적으로 호출합니다. 하지만 ASGI는 callback 함수를 비동기적으로 호출할 수 있으며 때문에 비동기 처리가 가능해졌습니다.

 

 

그럼 Gunicorn과 Uvicorn을 같이 쓰는 경우는 뭐야?


위에서 FastAPI로 웹 서버를 실행시키기 위해서는 ASGI를 구현한 Uvicorn을 사용해야 한다고 했었는데 사실 Gunicorn을 사용하여 실행을 하는 경우도 있습니다. 이는 여러 요청을 처리하기 위해 멀티 프로세스 서버를 띄울 때 많이 사용하는 방법입니다. Uvicorn 만으로도 여러 워커를 실행하도록 아래와 같이 웹 API 서버를 실행시킬 수 있습니다.

uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4

 

위의 명령어는 4개의 워커 프로세스를 생성해서 웹 서버를 띄우는 명령어입니다. 하지만 실제 운영 환경에서 멀티 프로세스 서버를 사용할 때는 Gunicorn을 함께 사용하는 방식이 프로세스가 갑작스레 종료될 경우 등 비정상적인 상황이 발생했을 때 회복능력이 뛰어나기 때문에 Gunicorn을 사용해서 멀티 프로세스 서버를 많이 띄웁니다.

 

하지만 앞서 설명했듯이 Gunicorn은 WSGI를 구현한 서버이며 FastAPI는 ASGI 표준에 맞게 구현이 되어있기 때문에 바로 Gunicorn과 연결해서 사용할 수는 없습니다. 그 대신 Gunicorn은 어떤 워커 프로세스 클래스를 사용할 지 지정을 할 수 있기 때문에 Uvicorn의 워커 클래스를 사용하는 방식으로 간접적으로 연결해서 사용합니다. 이 방식으로 서버를 실행할 경우 Gunicorn은 단순히 Uvicorn worker class가 실행되는 프로세스의 매니져 역할만을 합니다. 아래의 명령어로 Gunicorn으로 FastAPI 서버를 실행할 수 있습니다.

// worker 4개로 실행, worker class로 uvicorn.workers.UvicornWorker 클래스 지정
gunicorn main:app --workers 4 --worker-class \ 
uvicorn.workers.UvicornWorker --bind 0.0.0.0:80

 

 

결론


지금까지 Uvicorn과 Gunicorn이 뭔지, 그리고 멀티 프로세스 서버를 실행시키기 위해 FastAPI와 Gunicorn, Uvicorn이 함께 사용된다는 사실을 알아봤습니다. 하지만 이는 어디까지나 하나의 서버 인스턴스를 띄워서 사용하는 경우에 대해서 다룬 내용입니다.

만약 서버를 배포할 때 docker와 kubernetes를 사용해 여러 프로세스를 replication 하는 방식을 사용하신다면 따로 여러 워커 프로세스를 만들 필요 없이 docker container를 replication 하면 되기 때문에 Gunicorn을 사용하는 방식이 아닌 또 다른 방식을 사용할 수도 있을 것입니다. 

 

감사합니다.

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기