Docker Compose初始化数据库致服务启动失败问题求助
问题原因
depends_on: service_started仅能确保MySQL容器完成启动,但不代表数据库服务已就绪——首次启动时,MySQL需要初始化数据目录、执行初始化脚本,这个过程比容器启动慢得多,导致API服务启动时无法连接数据库而失败。
解决方案
方案1:给API服务添加重启策略
最简单的处理方式是让API容器在连接失败后自动重启,直到成功连接数据库。在app_user_api服务中添加重启配置:
app_user_api: depends_on: mysql_test_db: condition: service_started image: fast-api-beta container_name: test_user_api_1 restart: on-failure # 新增:启动失败时自动重试 volumes: - /etc/certificates/site:/certs:ro # 其余环境变量、端口等配置保持不变
restart: on-failure会在容器启动失败时自动重试,直到成功连接数据库。如果希望容器始终保持运行(除非手动停止),也可以替换为restart: unless-stopped。
方案2:配置MySQL健康检查,让API等待数据库就绪
通过Docker的健康检查功能,检测MySQL服务是否真正可以接受连接,再启动API服务,从根源避免连接失败。
步骤1:给MySQL服务添加健康检查
在mysql_test_db服务中添加healthcheck配置:
mysql_test_db: image: mysql container_name: mysql_test_db command: --init-file /data/application/init.sql volumes: - ./init.sql:/data/application/init.sql - ./db_data/mysql:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_db_password MYSQL_DATABASE: TEST_SS_REST_API_1 ports: - "3306" networks: - net_db secrets: - mysql_db_password restart: unless-stopped # 新增健康检查配置 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$(cat /run/secrets/mysql_db_password)"] interval: 5s # 每5秒检查一次 timeout: 5s # 检查超时时间 retries: 10 # 最多重试10次 start_period: 10s # 启动后10秒再开始检查(给初始化留缓冲时间)
健康检查命令通过mysqladmin ping验证MySQL服务可用性,使用secret中的密码进行认证($$是Docker Compose中对$的转义)。
步骤2:修改API服务的依赖条件
将app_user_api的depends_on条件改为service_healthy:
app_user_api: depends_on: mysql_test_db: condition: service_healthy # 改为等待MySQL健康就绪 # 其余配置保持不变
这样API服务只会在MySQL通过健康检查后才启动,确保数据库已完全初始化完成。
方案3:在API代码中添加数据库连接重试逻辑
从代码层面保障启动时能等待数据库就绪,适合需要更精确控制的场景。以FastAPI为例,在初始化数据库连接时添加重试逻辑:
import os import time from sqlalchemy import create_engine def init_db_connection(): max_retries = 20 retry_delay = 2 # 每次重试间隔2秒 engine = None for attempt in range(max_retries): try: # 从环境变量读取数据库配置 username = os.getenv("APP_KEY_MYSQL_USERNAME") with open(os.getenv("APP_KEY_MYSQL_PASSWORD_FILE"), 'r') as f: password = f.read().strip() db_name = os.getenv("APP_KEY_MYSQL_DATABASE") host = os.getenv("APP_KEY_MYSQL_DOMAIN") port = os.getenv("APP_KEY_MYSQL_PORT") # 创建数据库引擎并测试连接 engine = create_engine(f"mysql+pymysql://{username}:{password}@{host}:{port}/{db_name}") with engine.connect(): print("数据库连接成功") break except Exception as e: print(f"连接失败,重试({attempt+1}/{max_retries}): {str(e)}") time.sleep(retry_delay) if not engine: raise RuntimeError("多次尝试后仍无法连接数据库,启动失败") return engine # 在FastAPI启动前初始化数据库连接 db_engine = init_db_connection()
这段代码会在启动时循环尝试连接数据库,直到成功或达到重试上限。
额外优化:移除冗余的初始化脚本
你已经在MySQL环境变量中设置了MYSQL_DATABASE: TEST_SS_REST_API_1,MySQL容器启动时会自动创建该数据库,因此init.sql中的CREATE DATABASE IF NOT EXISTS TEST_SS_REST_API_1可以移除,减少不必要的初始化步骤。
内容的提问来源于stack exchange,提问作者Decaf Sux

