本次的程式碼與目錄結構可以參考 FastAPI Tutorial : Day23 branch
透過 Docker Compose 一鍵部署 FastAPI + PostgreSQL + MySQL 專案
可以任意在 backend Container 中任意切換 DB 進行測試
在 Day21 和 Day23 我們完成 pytest
的 Unit Test
接下來我們要透過 Docker Compose 來部署我們的 FastAPI 專案
如果單純使用 Docker 部署 FastAPI 專案,我們需要先建立一個給 FastAPI 的 Docker Image
再分別建立 PostgreSQL 和 MySQL 的 Docker Image
最後跑 3 次 docker run
來啟動所有 Container
透過 Docker Compose 我們可以透過一個 docker-compose.yml
檔案來定義我們的專案
並且能一次啟動所有 Container
首先我們先來建立 FastAPI 的 Docker Image
在 backend
目錄下建立 Dockerfile
touch backend/Dockerfile
由於我們的專案結構是透過 Poetry 來管理的
所以我們可以先 export Poetry 的 dependencies 到 backend/requirements.txt
poetry export -f requirements.txt -o backend/requirements.txt
接著就可以開始編寫 Dockerfile
backend/Dockerfile
FROM python:3.11.1-slim
ENV PYTHONDONTWRITEBYTECODE 1
WORKDIR /usr/backend
COPY ./requirements.txt /usr/backend/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /usr/backend/
我們把 workdir
設定到 /usr/backend
特別設定 PYTHONDONTWRITEBYTECODE
讓 Python 不會產生 .pyc
檔
可以讓產生的 Docker Image 更小
.dockerignore
設定在建立 Docker Image 時,我們可以透過 .dockerignore
來避免不必要的檔案被加入
再次縮小 Docker Image 的大小
touch .dockerignore
backend/.dockerignore
__pycache__
.pytest_cache
Dockerfile
.dockignore
這樣在 COPY . /usr/backend/
時就不會把這些檔案加入 Docker Image 中
與不設定 .dockerignore
和 ENV PYTHONDONTWRITEBYTECODE 1
時的 Docker Image 大小比較
大概小 10MB 左右
影響 Docker Image 大小的因素有很多,像是使用的 Base Image、安裝的套件等等
如果一開始的 Poetry 是開在 backend
目錄下
可以在 Build Image 時再產生 requirements.txt
並透過 Multi Stage Build 來減少 Image 的大小
FROM python:3.11.1-slim as builder
WORKDIR /tmp
RUN pip install poetry
COPY ./pyproject.toml ./poetry.lock* /tmp/
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
FROM python:3.11.1-slim
ENV PYTHONDONTWRITEBYTECODE 1
WORKDIR /usr/backend
COPY --from=builder /tmp/requirements.txt /usr/backend/requirements.txt
# ... 剩下與原本的相同
可以參考 fastapi dockerize : docker image with poetry
在根目錄下建立 docker-compose.yml
touch docker-compose.yml
最基本的設定如下
而 services
中的 backend
會使用 backend/Dockerfile
來建立 Docker Image
PostgreSQL 和 MySQL 則是直接使用 Docker Hub 上的 Image
docker-compose.yml
version: '1.0'
services:
postgresql_db:
image: postgres:15.1
restart: always
volumes:
- ./db_volumes/postgresql:/var/lib/postgresql/data/
mysql_db:
image: mysql:8.1.0
restart: always
volumes:
- ./db_volumes/mysql:/var/lib/mysql/
backend:
build: ./backend
volumes:
- ./backend/:/usr/backend/
command: python3 run.py --prod
restart: always
networks:
default:
name: fastapi_tutorial_network
networks
: network
來讓所有 Container 透過 Service Name 來連線 volumes
:
db_volumes
目錄下 backend
目錄掛載到 Container 中 接著我們設定 Database 要 Expose 的 Port
這邊我們設定 PostgreSQL 的 Port 為 5432
MySQL 的 Port 為 3306
並將 backend
Container 的 8003
Port Forward 到本機的 8000
Port
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
expose:
- 5432
mysql_db:
# ...
expose:
- 3306
backend:
# ...
ports:
- 8000:8003
我們 backend
Container 會依賴於 PostgreSQL 和 MySQL
應該要先等 Database 啟動完成後才啟動 backend
所以需要特別設定 depends_on
docker-compose.yml
# ...
services:
# ...
backend:
# ...
depends_on:
- postgresql_db
- mysql_db
environment
設定對於 PostgreSQL 和 MySQL 我們都需要設定 environment
需要將 POSTGRES_USER
、POSTGRES_PASSWORD
、POSTGRES_DB
這些環境變數在 docker-compose.yml
中設定好
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
environment:
- POSTGRES_USER=fastapi_tutorial
- POSTGRES_PASSWORD=fastapi_tutorial_password
- POSTGRES_DB=fastapi_tutorial
mysql_db:
# ...
environment:
- MYSQL_ROOT_PASSWORD=fastapi_tutorial_password
- MYSQL_DATABASE=fastapi_tutorial
這樣的缺點是我們的密碼都會直接寫在 docker-compose.yml
中
env_file
設定所以我們可以透過 env_file
來設定
我們先建立要載入的 .env
檔案
touch db.{postgresql,mysql}.env
db.postgresql.env
POSTGRES_PASSWORD=fastapi_tutorial_password
POSTGRES_USER=fastapi_tutorial
POSTGRES_DB=fastapi_tutorial
db.mysql.env
MYSQL_ROOT_PASSWORD=fastapi_tutorial_password
MYSQL_DATABASE=fastapi_tutorial
要注意這邊 .env
的設定要與 backend/setting/.env.prod
中的 DATABASE_URL
中 User、Password、DB Name 一致
setting/.env.prod
中的 DATABASE_URL
因為我們要透過 Docker Compose 來部署
有設定 network
的關係,所以我們必須要使用 Service Name 來連線
有點像 Docker Compose 幫我們設定內網 DNS 的感覺
要將原本是 localhost:5432
的 DATABASE_URL
改成 postgresql_db:5432
localhost:3306
改成 mysql_db:3306
setting/.env.prod
# ...
SYNC_POSTGRESQL_DATABASE_URL='postgresql+psycopg2://fastapi_tutorial:fastapi_tutorial_password@postgresql_db:5432/fastapi_tutorial'
ASYNC_POSTGRESQL_DATABASE_URL='postgresql+asyncpg://fastapi_tutorial:fastapi_tutorial_password@postgresql_db:5432/fastapi_tutorial'
SYNC_MYSQL_DATABASE_URL='mysql+pymysql://root:fastapi_tutorial_password@mysql_db:3306/fastapi_tutorial'
ASYNC_MYSQL_DATABASE_URL='mysql+aiomysql://root:fastapi_tutorial_password@mysql_db:3306/fastapi_tutorial'
# ...
如果這時候直接使用 docker-compose up
來啟動
有時候會發現我們 FastAPI 在連接 Database 時會出現錯誤
說還無法 connect 到 Database
這是因為雖然我們有設定 depends_on
但是我們的 Database 還沒有啟動完成
所以我們可以額外透過 condition
和 healthcheck
來等待 Database 完全啟動完成
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
healthcheck:
test: ["CMD", "pg_isready", "-U", "fastapi_tutorial", "-d", "fastapi_tutorial"]
interval: 5s
timeout: 5s
retries: 5
# ...
mysql_db:
# ...
healthcheck:
test: ["CMD", "echo" , ">/dev/tcp/localhost/3306"]
interval: 5s
timeout: 5s
retries: 5
# ...
PostgreSQL 的 healthcheck
可以透過 pg_isready
來檢查
MySQL 的 healthcheck
則是透過 echo >/dev/tcp/localhost/3306
來檢查
這時候再直接使用 docker-compose up
來啟動
或是使用 docker-compose up -d
來背景執行
就可以看到我們的 FastAPI 專案已經成功部署
可以在 localhost:8000/docs
來看到 Swagger UI
進入 Backend Container 後
以 PostgreSQL 測試
docker exec -it ithome2023-fastapi-tutorial-backend-1 bash
cd tests && pytest --prod --db postgresql
以 MySQL 測試
pytest --prod --db mysql
都可以正常運作 !
今天我們透過 Docker Compose 來部署我們的 FastAPI 專案
使用 env_file
來載入 db.env
檔案
而不用將密碼直接寫在 docker-compose.yml
中
並且透過 depends_on
和 healthcheck
來等待 Database 完整啟動完成後
再啟動 backend
Container
最後我們進入 backend
Container 中跑不同 Database 測試
也可以正常運作 !