🧐 개요
이번 포스트는 Dockerfile로 FastAPI 서비스를 배포하는 예제를 다루어보도록 하겠습니다. 사실 FastAPI는 스크립트 내용 변화를 업데이트하려면 앱을 종료 후 재실행해야 하기 때문에 컨테이너로 배포하는 것에 큰 메리트가 없습니다. 하지만 서비스 배포 차원에서 설치해야 할 모듈들이 많을 경우, Docker 가상 환경에 분리할 수 있다는 이점이 있어서 개인적으로는 프로젝트 단위에서 종종 사용하였습니다.
🌲 File Tree 구조
.
├── apt-requirements.txt
├── Dockerfile
├── fastapi
│ └── main.py
├── pip-requirements.txt
└── README.md
서비스 배포를 위한 File Tree의 구조입니다.
- Dockerfile : Docker 이미지를 빌드하기 위한 설정 파일
- apt-requirements.txt : 설치해야 할 apt 패키지 이름들이 기재된 파일
- pip-requirements.txt : 설치해야 할 pip 패키지 이름들이 기재된 파일
- fastapi : fastapi 서비스가 포함된 폴더 디렉토리
- README.md(선택 사항) : 빌드 안내사항 기재
참고로 fastapi는 간단히 배포 테스트만 진행하기 위해 main.py 스크립트만 추가하였습니다. 실제로는 router, middleware, util 및 lib 등 다양한 요소들을 추가로 구성해야 합니다.
🖍️ 스크립트 작성하기
apt-requirements.txt
vim
git
wget
컨테이너를 구성할 때 기본적으로 설치하는 apt 패키지입니다. 필요한 패키지가 있을 경우 추가할 수 있습니다.
일반적으로는 추가적인 구성 요소를 설치하고 초기 개발 작업을 수행하는 과정에서 wget, git, 그리고 vim 패키지가 사용됩니다.
requirements 부분을 이렇게 별도의 스크립트로 분리하면 Dockerfile 스크립트의 길이가 줄어들어 가독성 측면에서의 이점이 생기고 스크립트 및 모듈의 관리가 쉬워진다는 이점이 있습니다.
pip-requirements.txt
fastapi
uvicorn[standard]
setuptools
컨테이너를 구성할 때 기본적으로 설치하는 pip 패키지입니다. 필요한 패키지가 있을 경우 추가할 수 있습니다.
fastapi 서버를 실행하기 위해서는 fastapi 모듈과 uvicorn 패키지가 필요합니다.
지난 포스트에서 언급한 것처럼, python 3.12.0 버전을 넘어오면서 distutils 모듈이 사라졌기 때문에 오류를 방지하는 차원에서 setuptools 모듈을 설치하고 있습니다. 현재로서는 필요하지 않지만, 추가적인 모듈을 설치해 사용하실 경우 setuptools를 설치하시는 것을 권장드립니다.
fastapi/main.py
from fastapi import FastAPI
app = FastAPI()
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8000)
앞서 이야기한 것처럼, 단순 테스트를 위한 간단한 fastapi 스크립트를 작성하였습니다.
fastapi 앱은 컨테이너 내부에서 8000번 포트에 동작하도록 구성하였습니다.
Dockerfile
FROM python:3.12-slim
# COPY REQUIREMENTS TO CONTAINER
COPY apt-requirements.txt /apt-requirements.txt
COPY pip-requirements.txt /pip-requirements.txt
COPY fastapi /fastapi
# APT UPDATE
RUN ["apt", "update"]
# INSTALL REQUIREMENTS
RUN ["xargs", "-a", "/apt-requirements.txt", "apt", "install", "-y"]
RUN ["pip", "install", "-r", "/pip-requirements.txt"]
# SET WORKDIR
WORKDIR /fastapi
# RUN CMD
CMD ["python", "main.py"]
Dockerfile은 실질적인 컨테이너 빌드를 수행하는 스크립트입니다.
- FastAPI가 Python 환경에서 동작하므로 기본 이미지는 python:3.12-slim 이미지를 사용하고 있습니다.
- 사전에 준비한 소스를 컨테이너가 활용하려면, 해당 소스들을 컨테이너 내부 경로로 복사(COPY)해야 합니다.
- main.py 스크립트를 실행하기 전 Work 디렉토리를 설정하고 있습니다. (절대 경로를 일일히 지정해 사용할 경우 생략 가능)
- 컨테이너가 스크립트를 동작하기 위해서는 RUN 대신 CMD 명령어를 사용해야 합니다. (RUN 사용 시 빌드 도중에 앱을 실행)
🛠️ 컨테이너의 빌드 및 배포
# build
# docker build -t <fastapi-이미지-이름> <dockerfile-절대경로>
docker build -t <fastapi-이미지-이름> .
# run
docker run -d -p 4000:8000 --name <fastapi-컨테이너-이름> <fastapi-이미지-이름>
# stop
docker stop <fastapi-컨테이너-이름>
# remove
docker rm <fastapi-컨테이너-이름>
docker rmi <fastapi-이미지-이름>
Docker 컨테이너를 배포하기 위해서는 먼저 이미지를 빌드하고, 빌드한 이미지를 기반으로 컨테이너를 생성해야 합니다.
- docker build 명령어는 Dockerfile이 위치한 경로에서 . 을 지정하여 실행하거나, Dockerfile의 절대 경로를 직접 지정하여 실행할 수 있습니다.
- docker run 명령어를 사용할 때 로컬 포트와 Docker 서비스 포트를 포워딩해야 합니다. 현재 스크립트에서는 컨테이너 내부의 8000번 포트(FastAPI 앱이 실행되는 포트)를 로컬 호스트의 4000번 포트에 매핑하고 있습니다. 이렇게 하면 로컬 호스트의 4000번 포트에서 FastAPI 앱 및 docs를 확인할 수 있습니다.
배포 결과
위에서 설명한 명령어를 build -> run 순으로 실행해 주었습니다.
4000/docs 엔드포인트에 FastAPI 앱이 잘 실행되었습니다.
라우터 없이 앱을 켜 본건 처음인데.. No operations defined in spec! 라는 경고가 떴었군요.
📝 마치며
예제 소스는 다음의 링크에서 확인하실 수 있습니다 - https://github.com/hooniegit/docker-and-services/tree/main/fastapi
Dockerfile 및 docker-compose.yml 파일은 필요한 서비스를 컨테이너의 형태로 빠르고 간편하게 구축, 배포 및 오케스트레이션할 수 있도록 도와주는 역할을 수행합니다. Docker을 적재 적소에 적절히 활용하면 빠르고 쉽게 인프라를 구성하고 개발 속도를 향상시키는 부분에 기여할 수 있을 것으로 기대합니다.
발자취를 로그처럼 남기고자 하는 초보 개발자의 블로그