docker-compose部署下Fastapi中使用sqlalchemy和Alembic

本篇介绍使用Fastapi + sqlalchemy + alembic 来完成后端服务的数据库管理,并且通过docker-compose来部署后端服务和数据库Mysql。包括:

  1. 数据库创建,数据库用户创建
  2. 数据库服务发现
  3. Fastapi 连接数据库
  4. Alembic 连接数据库
  5. 服务健康检查

部署数据库

python 复制代码
version: '3'
services:
  db:
    image: mysql
    container_name: db
    environment:
      - MYSQL_ROOT_PASSWORD=tv_2024 # root用户密码
      - MYSQL_DATABASE=tileView
      - MYSQL_USER=tile_viewer
      - MYSQL_PASSWORD=tv_2024
      - TZ=Asia/Shanghai
    volumes:
      - ./mysql:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 3306:3306
    restart: always

部署数据库有三个注意点:

  1. 将数据库文件映射出来,避免丢失数据

数据库中存储的数据都容器里在/var/lib/mysql目录下,将该目录映射出来,避免重启容器丢失数据

二、自动创建数据库DB

很多情况下需要在启动数据库容器是自动创建数据库,在environment中设置 MYSQL_DATABASE=tileView即可在容器启动是创建数据库

三、创建可读写可远程用户

默认情况下非root用户不支持远程连接读写权限,在environment中设置

  1. MYSQL_USER=tile_viewer
  2. MYSQL_PASSWORD=tv_2024

将会获得一个可远程可读写MYSQL_DATABASE库的用户

Fastapi 连接数据库

非docker部署情况下使用IP和端口连接数据库,使用docker-compose部署时服务都是自动化启动,事先不知道数据库的IP,这是就可以使用docker-compose提供的能力:使用服务名来请求服务。

docker-compose中可以使用服务的名称来通信,在通信请求中将服务名替换成容器IP。

首先将数据库连接的URL映射到服务的容器中

python 复制代码
version: '3'
services:
  db:
    image: mysql
    container_name: db
    environment:
      - MYSQL_ROOT_PASSWORD=tv_2024 # root用户密码
      - MYSQL_DATABASE=tileView
      - MYSQL_USER=tile_viewer
      - MYSQL_PASSWORD=tv_2024
      - TZ=Asia/Shanghai
    volumes:
      - ./mysql:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 3306:3306
    restart: always
    healthcheck:
      test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
      interval: 10s
      timeout: 5s
      retries: 3
  server:
    image: tileview:1.0
    restart: always
    container_name: tileview_server
    ports:
      - "9100:9100"
    volumes:
      - ./tiles_store:/app/server/tiles_store
      - ./log:/app/server/log
      - ./upload:/app/server/upload
    depends_on:
      db:
        condition: service_healthy
    environment:
      - DATABASE_URI=mysql+pymysql://tile_viewer:tv_2024@db/tileView

depends_on: 表示该服务依赖db服务,db服务要先启动。condition: service_healthy

DATABASE_URI:表示将数据库连接信息注入到该容器中,其中@db表示数据库IP:端口号 使用数据库服务名称db来替换,在容器中请求该url时,docker-compose会自动将db转换成对应的数据库的IP和端口号。

python 复制代码
depends_on:
      - db
    environment:
      - DATABASE_URI=mysql+pymysql://tile_viewer:tv_2024@db/tileView

修改sqlalchemy中数据库的连接

sqlalchemy 连接数据库时,数据库的url配置从环境变量中获取

sqlalchemy/database.py

python 复制代码
import os

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URI")
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

使用环境变量获取数据库

python 复制代码
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URI")

在Fastapi 服务 调用数据库的方法。

python 复制代码
DATABASE_URI=mysql+pymysql://tile_viewer:tv_2024@db/tileView

使用数据库服务名db来找到服务地址,在docker-compose中会将服务名解析成服务的IP。在使用时会将db解析成172.10.0.2,找到服务的IP。

迁移工具alembic中数据库连接

在数据库迁移工具中需要配置数据库的连接信息,该信息是配置在alembic.ini中,如果要使用环境变量中动态获取的方法,需要改变两点:

  1. alembic.ini中不配置数据库连接信息
  2. 在alembic/env.py动态获取连接url,并设置到alembic.ini中

alembic.ini中不配置数据库连接信息

在alembic/env.py动态获取连接url,并设置到alembic.ini中

alembic/env.py

python 复制代码
from logging.config import fileConfig

from alembic import context
from sqlalchemy import engine_from_config, pool

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.

import os  # noqa
import sys  # noqa

from server.db.base import Base  # noqa

basedir = os.path.split(os.getcwd())[0]
sys.path.append(basedir)

target_metadata = Base.metadata


def get_url() -> str:
    return os.getenv("DATABASE_URI", "sqlite:///app.db")


def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    # url = config.get_main_option("sqlalchemy.url")
    url = get_url()
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    configuration = config.get_section(config.config_ini_section)
    configuration["sqlalchemy.url"] = get_url()
    connectable = engine_from_config(
        configuration,
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(connection=connection, target_metadata=target_metadata)

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

在 run_migrations_offline 中将url获取从配置文件中改成从环境变量中

python 复制代码
# url = config.get_main_option("sqlalchemy.url")
url = get_url()

在 run_migrations_online 中修改配置文件的数据库连接信息

python 复制代码
configuration = config.get_section(config.config_ini_section)
configuration["sqlalchemy.url"] = get_url()
connectable = engine_from_config(
    configuration,
    prefix="sqlalchemy.",
    poolclass=pool.NullPool,
)

以上操作之后就能通过服务发现的方式动态使用数据库

数据库健康检查

在前面的配置中虽然让服务依赖db,db会先启动然后服务后启动,但是这种情况还会出现数据库连不上的情况,因为db启动不代表服务就绪,未就绪的时候连接会导致connect refuse。解决这个问题的方法是给db增加一个健康检查 healthcheck。

python 复制代码
services:
  db:
    image: mysql
    container_name: db
    environment:
      - MYSQL_ROOT_PASSWORD=tv_2024 # root用户密码
      - MYSQL_DATABASE=tileView
      - MYSQL_USER=tile_viewer
      - MYSQL_PASSWORD=tv_2024
      - TZ=Asia/Shanghai
    volumes:
      - ./mysql:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 3306:3306
    restart: always
    healthcheck:
      test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
      interval: 10s
      timeout: 5s
      retries: 3

当健康检查完成才代表数据库启动成功,服务才会启动。服务中也需要依赖数据库健康检查完成,写法如下:

python 复制代码
depends_on:
      db:
        condition: service_healthy
相关推荐
SsummerC28 分钟前
【leetcode100】二叉树的直径
数据结构·python·算法·leetcode
RacheV+TNY2642781 小时前
未来趋势:电商平台API接口的智能化与自动化发展
网络·人工智能·python·自动化·api
写点什么呢1 小时前
Pytorch学习12_最大池化的使用
人工智能·pytorch·python·深度学习·学习·pycharm
郝开1 小时前
Python中的可变对象与不可变对象;Python中的六大标准数据类型哪些属于可变对象,哪些属于不可变对象
开发语言·python·可变对象·不可变对象
这里有鱼汤2 小时前
Python高手都在用的10个高级技巧,你会几个?
python
小鹏编程2 小时前
C++和Python中负数取余结果的区别
c++·python
SoulKuyan2 小时前
Android客制化------7.0设置壁纸存在的一些问题
android·开发语言·python
zhangfeng11332 小时前
selenium python 模拟网页登陆,如何快速知道chrome的html元素的xpath
chrome·python·selenium
新手小袁_J2 小时前
Python的Matplotlib库应用(超详细教程)
开发语言·python·numpy·matplotlib·pip·基础知识
黑客老陈4 小时前
SRC技巧篇 | 利用工具批量挖掘SRC
网络·python·sql·安全·web安全