배포 개념¶
FastAPI 애플리케이션(사실 어떤 종류의 웹 API든)을 배포할 때는, 여러분이 신경 써야 할 여러 개념이 있습니다. 그리고 이 개념들을 활용하면 애플리케이션을 배포하기 위한 가장 적절한 방법을 찾을 수 있습니다.
중요한 개념 몇 가지는 다음과 같습니다:
- 보안 - HTTPS
- 시작 시 실행
- 재시작
- 복제(실행 중인 프로세스 수)
- 메모리
- 시작 전 사전 단계
이것들이 배포에 어떤 영향을 주는지 살펴보겠습니다.
결국 최종 목표는 API 클라이언트에 서비스를 제공할 때 보안을 보장하고, 중단을 피하며, 컴퓨팅 리소스(예: 원격 서버/가상 머신)를 가능한 한 효율적으로 사용하는 것입니다. 🚀
여기서 이 개념들을 조금 더 설명하겠습니다. 그러면 서로 매우 다른 환경, 심지어 아직 존재하지 않는 미래의 환경에서도 API를 어떻게 배포할지 결정하는 데 필요한 직관을 얻을 수 있을 것입니다.
이 개념들을 고려하면, 여러분은 자신의 API를 배포하기 위한 최선의 방법을 평가하고 설계할 수 있습니다.
다음 장들에서는 FastAPI 애플리케이션을 배포하기 위한 더 구체적인 레시피를 제공하겠습니다.
하지만 지금은, 이 중요한 개념적 아이디어들을 확인해 봅시다. 이 개념들은 다른 어떤 종류의 웹 API에도 동일하게 적용됩니다. 💡
보안 - HTTPS¶
이전 HTTPS 장에서 HTTPS가 API에 암호화를 제공하는 방식에 대해 배웠습니다.
또한 HTTPS는 일반적으로 애플리케이션 서버 바깥의 외부 컴포넌트인 TLS Termination Proxy가 제공한다는 것도 확인했습니다.
그리고 HTTPS 인증서 갱신을 담당하는 무언가가 필요합니다. 같은 컴포넌트가 그 역할을 할 수도 있고, 다른 무언가가 담당할 수도 있습니다.
HTTPS를 위한 도구 예시¶
TLS Termination Proxy로 사용할 수 있는 도구는 예를 들어 다음과 같습니다:
- Traefik
- 인증서 갱신을 자동으로 처리 ✨
- Caddy
- 인증서 갱신을 자동으로 처리 ✨
- Nginx
- 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
- HAProxy
- 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
- Nginx 같은 Ingress Controller를 사용하는 Kubernetes
- 인증서 갱신을 위해 cert-manager 같은 외부 컴포넌트 사용
- 클라우드 제공자가 서비스 일부로 내부적으로 처리(아래를 읽어보세요 👇)
또 다른 선택지는 HTTPS 설정을 포함해 더 많은 일을 대신해주는 클라우드 서비스를 사용하는 것입니다. 제약이 있거나 비용이 더 들 수도 있습니다. 하지만 그 경우에는 TLS Termination Proxy를 직접 설정할 필요가 없습니다.
다음 장에서 구체적인 예시를 보여드리겠습니다.
다음으로 고려할 개념들은 실제로 여러분의 API를 실행하는 프로그램(예: Uvicorn)과 관련된 내용입니다.
프로그램과 프로세스¶
실행 중인 "프로세스"에 대해 많이 이야기하게 될 텐데, 이 말이 무엇을 의미하는지, 그리고 "프로그램"이라는 단어와 무엇이 다른지 명확히 해두는 것이 유용합니다.
프로그램이란¶
프로그램이라는 단어는 보통 여러 가지를 가리키는 데 사용됩니다:
- 여러분이 작성하는 코드, 즉 Python 파일들
- 운영체제에서 실행할 수 있는 파일, 예:
python,python.exe,uvicorn - 운영체제에서 실행 중인 특정 프로그램으로, CPU를 사용하고 메모리에 내용을 저장합니다. 이것을 프로세스라고도 합니다.
프로세스란¶
프로세스라는 단어는 보통 더 구체적으로, 운영체제에서 실행 중인 것(위 마지막 항목처럼)만을 가리키는 데 사용됩니다:
- 운영체제에서 실행 중인 특정 프로그램
- 파일이나 코드를 의미하는 것이 아니라, 운영체제가 실제로 실행하고 관리하는 대상을 구체적으로 의미합니다.
- 어떤 프로그램이든 어떤 코드든, 실행될 때만 무언가를 할 수 있습니다. 즉, 프로세스가 실행 중일 때입니다.
- 프로세스는 여러분이, 혹은 운영체제가 종료(또는 “kill”)할 수 있습니다. 그러면 실행이 멈추고, 더 이상 아무것도 할 수 없습니다.
- 컴퓨터에서 실행 중인 각 애플리케이션 뒤에는 프로세스가 있습니다. 실행 중인 프로그램, 각 창 등도 마찬가지입니다. 그리고 컴퓨터가 켜져 있는 동안 보통 많은 프로세스가 동시에 실행됩니다.
- 같은 프로그램의 여러 프로세스가 동시에 실행될 수도 있습니다.
운영체제의 “작업 관리자(task manager)”나 “시스템 모니터(system monitor)”(또는 비슷한 도구)를 확인해 보면, 이런 프로세스가 많이 실행 중인 것을 볼 수 있습니다.
또 예를 들어, 같은 브라우저 프로그램(Firefox, Chrome, Edge 등)을 실행하는 프로세스가 여러 개 있는 것도 보일 가능성이 큽니다. 보통 탭마다 하나의 프로세스를 실행하고, 그 외에도 추가 프로세스 몇 개가 더 있습니다.

이제 프로세스와 프로그램의 차이를 알았으니, 배포에 대한 이야기를 계속해 보겠습니다.
시작 시 실행¶
대부분의 경우 웹 API를 만들면, 클라이언트가 언제나 접근할 수 있도록 항상 실행되고 중단되지 않기를 원합니다. 물론 특정 상황에서만 실행하고 싶은 특별한 이유가 있을 수는 있지만, 대부분은 지속적으로 실행되며 사용 가능한 상태이기를 원합니다.
원격 서버에서¶
원격 서버(클라우드 서버, 가상 머신 등)를 설정할 때, 가장 단순한 방법은 로컬 개발 때처럼 수동으로 fastapi run(Uvicorn을 사용합니다)이나 비슷한 명령을 실행하는 것입니다.
이 방식은 동작하고, 개발 중에는 유용합니다.
하지만 서버에 대한 연결이 끊기면, 실행 중인 프로세스도 아마 종료될 것입니다.
또 서버가 재시작되면(예: 업데이트 이후, 혹은 클라우드 제공자의 마이그레이션 이후) 여러분은 아마 알아차리지 못할 겁니다. 그 결과, 프로세스를 수동으로 다시 시작해야 한다는 사실도 모르게 됩니다. 그러면 API는 그냥 죽은 상태로 남습니다. 😱
시작 시 자동 실행¶
일반적으로 서버 프로그램(예: Uvicorn)은 서버가 시작될 때 자동으로 시작되고, 사람의 개입 없이도 FastAPI 앱을 실행하는 프로세스가 항상 실행 중이도록(예: FastAPI 앱을 실행하는 Uvicorn) 구성하고 싶을 것입니다.
별도의 프로그램¶
이를 위해 보통 애플리케이션이 시작 시 실행되도록 보장하는 별도의 프로그램을 둡니다. 그리고 많은 경우, 데이터베이스 같은 다른 컴포넌트나 애플리케이션도 함께 실행되도록 보장합니다.
시작 시 실행을 위한 도구 예시¶
이 역할을 할 수 있는 도구 예시는 다음과 같습니다:
- Docker
- Kubernetes
- Docker Compose
- Swarm Mode의 Docker
- Systemd
- Supervisor
- 클라우드 제공자가 서비스 일부로 내부적으로 처리
- 기타...
다음 장에서 더 구체적인 예시를 제공하겠습니다.
재시작¶
애플리케이션이 시작 시 실행되도록 보장하는 것과 비슷하게, 장애가 발생했을 때 재시작되도록 보장하고 싶을 것입니다.
우리는 실수합니다¶
사람은 언제나 실수합니다. 소프트웨어에는 거의 항상 여기저기에 숨은 버그가 있습니다. 🐛
그리고 개발자는 버그를 발견하고 새로운 기능을 구현하면서 코드를 계속 개선합니다(새로운 버그도 추가할 수 있겠죠 😅).
작은 오류는 자동으로 처리됨¶
FastAPI로 웹 API를 만들 때 코드에 오류가 있으면, FastAPI는 보통 그 오류를 발생시킨 단일 요청 안에만 문제를 가둡니다. 🛡
클라이언트는 해당 요청에 대해 500 Internal Server Error를 받지만, 애플리케이션은 완전히 크래시하지 않고 다음 요청부터는 계속 동작합니다.
더 큰 오류 - 크래시¶
그럼에도 불구하고, 우리가 작성한 코드가 전체 애플리케이션을 크래시시켜 Uvicorn과 Python 자체가 종료되는 경우가 있을 수 있습니다. 💥
그래도 한 군데 오류 때문에 애플리케이션이 죽은 채로 남아 있기를 바라지는 않을 것입니다. 망가진 경로 처리를 제외한 나머지 경로 처리라도 계속 실행되기를 원할 가능성이 큽니다.
크래시 후 재시작¶
하지만 실행 중인 프로세스가 크래시하는 정말 심각한 오류의 경우에는, 적어도 몇 번은 프로세스를 재시작하도록 담당하는 외부 컴포넌트가 필요합니다...
팁
...다만 애플리케이션 전체가 즉시 계속 크래시한다면, 무한히 재시작하는 것은 아마 의미가 없을 것입니다. 그런 경우에는 개발 중에, 또는 최소한 배포 직후에 알아차릴 가능성이 큽니다.
그러니 여기서는, 특정한 경우에만 전체가 크래시할 수 있고 미래에도 그럴 수 있으며, 그래도 재시작하는 것이 의미 있는 주요 사례에 집중해 봅시다.
애플리케이션을 재시작하는 역할은 외부 컴포넌트가 맡는 편이 보통 좋습니다. 그 시점에는 Uvicorn과 Python을 포함한 애플리케이션이 이미 크래시했기 때문에, 같은 앱의 같은 코드 안에서 이를 해결할 방법이 없기 때문입니다.
자동 재시작을 위한 도구 예시¶
대부분의 경우 시작 시 실행에 사용한 도구가 자동 재시작도 함께 처리합니다.
예를 들어 다음이 가능합니다:
- Docker
- Kubernetes
- Docker Compose
- Swarm Mode의 Docker
- Systemd
- Supervisor
- 클라우드 제공자가 서비스 일부로 내부적으로 처리
- 기타...
복제 - 프로세스와 메모리¶
FastAPI 애플리케이션은 Uvicorn을 실행하는 fastapi 명령 같은 서버 프로그램을 사용하면, 하나의 프로세스로 실행하더라도 여러 클라이언트를 동시에 처리할 수 있습니다.
하지만 많은 경우, 여러 워커 프로세스를 동시에 실행하고 싶을 것입니다.
여러 프로세스 - 워커¶
단일 프로세스가 처리할 수 있는 것보다 클라이언트가 더 많고(예: 가상 머신이 그리 크지 않을 때), 서버 CPU에 여러 코어가 있다면, 같은 애플리케이션을 실행하는 여러 프로세스를 동시에 띄우고 요청을 분산시킬 수 있습니다.
같은 API 프로그램을 여러 프로세스로 실행할 때, 이 프로세스들을 보통 workers라고 부릅니다.
워커 프로세스와 포트¶
HTTPS에 대한 문서에서, 서버에서 하나의 포트와 IP 주소 조합에는 하나의 프로세스만 리스닝할 수 있다는 것을 기억하시나요?
이것은 여전히 사실입니다.
따라서 여러 프로세스를 동시에 실행하려면, 먼저 포트에서 리스닝하는 단일 프로세스가 있어야 하고, 그 프로세스가 어떤 방식으로든 각 워커 프로세스로 통신을 전달해야 합니다.
프로세스당 메모리¶
이제 프로그램이 메모리에 무언가를 로드한다고 해봅시다. 예를 들어 머신러닝 모델을 변수에 올리거나 큰 파일 내용을 변수에 올리는 경우입니다. 이런 것들은 서버의 메모리(RAM)를 어느 정도 사용합니다.
그리고 여러 프로세스는 보통 메모리를 공유하지 않습니다. 즉, 각 실행 중인 프로세스는 자체 변수와 메모리를 갖습니다. 코드에서 메모리를 많이 사용한다면, 각 프로세스가 그만큼의 메모리를 사용하게 됩니다.
서버 메모리¶
예를 들어 코드가 크기 1 GB의 머신러닝 모델을 로드한다고 해봅시다. API를 프로세스 하나로 실행하면 RAM을 최소 1GB 사용합니다. 그리고 4개 프로세스(워커 4개)를 시작하면 각각 1GB RAM을 사용합니다. 즉 총 4 GB RAM을 사용합니다.
그런데 원격 서버나 가상 머신의 RAM이 3GB뿐이라면, 4GB를 넘게 로드하려고 할 때 문제가 생깁니다. 🚨
여러 프로세스 - 예시¶
이 예시에서는 Manager Process가 두 개의 Worker Processes를 시작하고 제어합니다.
이 Manager Process는 아마 IP의 포트에서 리스닝하는 역할을 합니다. 그리고 모든 통신을 워커 프로세스로 전달합니다.
워커 프로세스들이 실제로 애플리케이션을 실행하며, 요청을 받아 응답을 반환하는 주요 연산을 수행하고, RAM에 변수로 로드한 모든 내용을 담습니다.
그리고 물론 같은 머신에는 애플리케이션 외에도 다른 프로세스들이 실행 중일 가능성이 큽니다.
흥미로운 점은 각 프로세스의 CPU 사용률은 시간에 따라 크게 변동할 수 있지만, 메모리(RAM)는 보통 대체로 안정적으로 유지된다는 것입니다.
매번 비슷한 양의 연산을 수행하는 API이고 클라이언트가 많다면, CPU 사용률도 (급격히 오르내리기보다는) 안정적일 가능성이 큽니다.
복제 도구와 전략 예시¶
이를 달성하는 접근 방식은 여러 가지가 있을 수 있으며, 다음 장들에서 Docker와 컨테이너를 설명할 때 구체적인 전략을 더 알려드리겠습니다.
고려해야 할 주요 제약은 공개 IP의 포트를 처리하는 단일 컴포넌트가 있어야 한다는 점입니다. 그리고 그 컴포넌트는 복제된 프로세스/워커로 통신을 전달할 방법이 있어야 합니다.
가능한 조합과 전략 몇 가지는 다음과 같습니다:
--workers옵션을 사용한 Uvicorn- 하나의 Uvicorn 프로세스 매니저가 IP와 포트에서 리스닝하고, 여러 Uvicorn 워커 프로세스를 시작합니다.
- Kubernetes 및 기타 분산 컨테이너 시스템
- Kubernetes 레이어의 무언가가 IP와 포트에서 리스닝합니다. 그리고 여러 컨테이너를 두어 복제하며, 각 컨테이너에는 하나의 Uvicorn 프로세스가 실행됩니다.
- 이를 대신 처리해주는 클라우드 서비스
- 클라우드 서비스가 복제를 대신 처리해줄 가능성이 큽니다. 실행할 프로세스나 사용할 컨테이너 이미지를 정의하게 해줄 수도 있지만, 어떤 경우든 대개 단일 Uvicorn 프로세스를 기준으로 하고, 클라우드 서비스가 이를 복제하는 역할을 맡습니다.
팁
컨테이너, Docker, Kubernetes에 대한 일부 내용이 아직은 잘 이해되지 않아도 괜찮습니다.
다음 장에서 컨테이너 이미지, Docker, Kubernetes 등을 더 설명하겠습니다: 컨테이너에서 FastAPI - Docker.
시작 전 사전 단계¶
애플리케이션을 시작하기 전에 어떤 단계를 수행하고 싶은 경우가 많습니다.
예를 들어 데이터베이스 마이그레이션을 실행하고 싶을 수 있습니다.
하지만 대부분의 경우, 이런 단계는 한 번만 수행하고 싶을 것입니다.
그래서 애플리케이션을 시작하기 전에 그 사전 단계를 수행할 단일 프로세스를 두고 싶을 것입니다.
또한 이후에 애플리케이션 자체를 여러 프로세스(여러 워커)로 시작하더라도, 사전 단계를 수행하는 프로세스는 반드시 하나만 실행되도록 해야 합니다. 만약 사전 단계를 여러 프로세스가 수행하면, 병렬로 실행하면서 작업이 중복될 수 있습니다. 그리고 데이터베이스 마이그레이션처럼 민감한 작업이라면 서로 충돌을 일으킬 수 있습니다.
물론 사전 단계를 여러 번 실행해도 문제가 없는 경우도 있습니다. 그런 경우에는 처리하기가 훨씬 쉽습니다.
팁
또한 설정에 따라, 어떤 경우에는 애플리케이션을 시작하기 전에 사전 단계가 전혀 필요 없을 수도 있다는 점을 기억하세요.
그런 경우에는 이런 것들을 전혀 걱정할 필요가 없습니다. 🤷
사전 단계 전략 예시¶
이는 여러분이 시스템을 배포하는 방식에 크게 좌우되며, 프로그램을 시작하는 방식, 재시작 처리 방식 등과도 연결되어 있을 가능성이 큽니다.
가능한 아이디어는 다음과 같습니다:
- 앱 컨테이너보다 먼저 실행되는 Kubernetes의 “Init Container”
- 사전 단계를 실행한 다음 애플리케이션을 시작하는 bash 스크립트
- 이 bash 스크립트를 시작/재시작하고, 오류를 감지하는 등의 방법도 여전히 필요합니다.
팁
컨테이너로 이를 처리하는 더 구체적인 예시는 다음 장에서 제공하겠습니다: 컨테이너에서 FastAPI - Docker.
리소스 활용¶
서버는 여러분이 프로그램으로 소비하거나 활용(utilize)할 수 있는 리소스입니다. CPU의 계산 시간과 사용 가능한 RAM 메모리가 대표적입니다.
시스템 리소스를 얼마나 소비/활용하고 싶으신가요? “많지 않게”라고 생각하기 쉽지만, 실제로는 크래시하지 않는 선에서 가능한 한 많이 사용하고 싶을 가능성이 큽니다.
서버 3대를 비용을 내고 쓰고 있는데 RAM과 CPU를 조금만 사용한다면, 아마 돈을 낭비하고 💸, 서버 전력도 낭비하고 🌎, 기타 등등이 될 수 있습니다.
그 경우에는 서버를 2대만 두고, 각 서버의 리소스(CPU, 메모리, 디스크, 네트워크 대역폭 등)를 더 높은 비율로 사용하는 것이 더 나을 수 있습니다.
반대로 서버 2대를 두고 CPU와 RAM을 100% 사용하고 있다면, 어느 시점에 프로세스 하나가 더 많은 메모리를 요청하게 되고, 서버는 디스크를 “메모리”처럼 사용해야 할 수도 있습니다(수천 배 느릴 수 있습니다). 또는 심지어 크래시할 수도 있습니다. 혹은 어떤 프로세스가 계산을 해야 하는데 CPU가 다시 비워질 때까지 기다려야 할 수도 있습니다.
이 경우에는 서버 한 대를 추가로 확보하고 일부 프로세스를 그쪽에서 실행해, 모두가 충분한 RAM과 CPU 시간을 갖도록 하는 편이 더 낫습니다.
또 어떤 이유로 API 사용량이 급증(spike)할 가능성도 있습니다. 바이럴이 되었거나, 다른 서비스나 봇이 사용하기 시작했을 수도 있습니다. 그런 경우를 대비해 추가 리소스를 확보해두고 싶을 수 있습니다.
리소스 활용률 목표로 임의의 수치를 정할 수 있습니다. 예를 들어 50%에서 90% 사이처럼요. 요점은, 이런 것들이 배포를 조정할 때 측정하고 튜닝하는 주요 지표가 될 가능성이 크다는 것입니다.
htop 같은 간단한 도구로 서버의 CPU와 RAM 사용량, 또는 각 프로세스별 사용량을 볼 수 있습니다. 혹은 서버 여러 대에 분산될 수도 있는 더 복잡한 모니터링 도구를 사용할 수도 있습니다.
요약¶
여기까지 애플리케이션 배포 방식을 결정할 때 염두에 두어야 할 주요 개념들을 읽었습니다:
- 보안 - HTTPS
- 시작 시 실행
- 재시작
- 복제(실행 중인 프로세스 수)
- 메모리
- 시작 전 사전 단계
이 아이디어들을 이해하고 적용하는 방법을 알면, 배포를 구성하고 조정할 때 필요한 직관을 얻는 데 도움이 될 것입니다. 🤓
다음 섹션에서는 따라 할 수 있는 가능한 전략의 더 구체적인 예시를 제공하겠습니다. 🚀