From 62482b371c831121942415bac3d66763f17a38e4 Mon Sep 17 00:00:00 2001 From: Table Date: Sat, 29 Nov 2025 06:55:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Docker=20=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E8=BF=9E=E6=8E=A5=E5=AE=BF=E4=B8=BB=E6=9C=BA=20MySQL/?= =?UTF-8?q?Redis=20=E9=97=AE=E9=A2=98=E5=B9=B6=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: 1. Docker Compose 配置优化(dev/stage/prod): - 移除 mysql 和 redis 服务定义,改为使用宿主机服务 - 更新 extra_hosts 使用宿主机实际 IP (192.168.1.11) 替代 host-gateway - 解决 rootless Docker 网络连接问题 2. Config 配置文件更新(dev/stage/prod): - 更新 database.host 和 redis.host 为 host.docker.internal - 支持容器通过 host.docker.internal 访问宿主机服务 3. README.md 文档完善: - 添加 Docker 容器连接宿主机 MySQL/Redis 的完整排查指南 - 添加 MySQL 容器无法正常停止的解决方案 - 更新 Docker Compose 环境说明 - 添加常见错误信息和排查步骤 问题修复: - 修复容器无法连接宿主机 MySQL(bind-address 和用户权限问题) - 修复容器无法连接宿主机 Redis(bind 和 protected-mode 问题) - 修复 rootless Docker 的 host-gateway 问题(使用宿主机实际 IP) 相关配置要求: - MySQL: bind-address = 0.0.0.0, root@'%' 用户权限 - Redis: bind = 0.0.0.0, protected-mode = no --- Makefile | 37 +------ README.md | 171 +++++++++++++++++++++++++++++++- config/dev.yaml | 4 +- config/prod.yaml | 4 +- config/stage.yaml | 4 +- docker/docker-compose.dev.yml | 40 +------- docker/docker-compose.prod.yml | 54 +--------- docker/docker-compose.stage.yml | 44 +------- 8 files changed, 182 insertions(+), 176 deletions(-) diff --git a/Makefile b/Makefile index 12e46ed..0ad7d11 100644 --- a/Makefile +++ b/Makefile @@ -142,44 +142,11 @@ docker-compose-dev: ## 生成开发环境Docker Compose文件 @echo " - \"1234:1234\"" >> $(DOCKER_DIR)/docker-compose.dev.yml @echo " environment:" >> $(DOCKER_DIR)/docker-compose.dev.yml @echo " - APP_ENV=dev" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " depends_on:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - mysql" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - redis" >> $(DOCKER_DIR)/docker-compose.dev.yml @echo " volumes:" >> $(DOCKER_DIR)/docker-compose.dev.yml @echo " - ../config:/app/config:ro" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " networks:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - yinli-network" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " mysql:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " image: mysql:8.0" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " environment:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " MYSQL_ROOT_PASSWORD: sasasasa" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " MYSQL_DATABASE: yinli" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " ports:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - \"3306:3306\"" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " volumes:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - mysql_data:/var/lib/mysql" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - ../sql:/docker-entrypoint-initdb.d:ro" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " networks:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - yinli-network" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " redis:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " image: redis:7-alpine" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " ports:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - \"6379:6379\"" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " volumes:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - redis_data:/data" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " networks:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " - yinli-network" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "volumes:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " mysql_data:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " redis_data:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo "networks:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " yinli-network:" >> $(DOCKER_DIR)/docker-compose.dev.yml - @echo " driver: bridge" >> $(DOCKER_DIR)/docker-compose.dev.yml + @echo " network_mode: host" >> $(DOCKER_DIR)/docker-compose.dev.yml @echo "开发环境Docker Compose文件已生成: $(DOCKER_DIR)/docker-compose.dev.yml" + @echo "注意: MySQL 和 Redis 需要单独部署,容器使用 network_mode: host 直接访问宿主机上的服务" .PHONY: docker-up-dev docker-up-dev: docker-compose-dev ## 启动开发环境Docker容器 diff --git a/README.md b/README.md index 69c9e71..1ae4571 100644 --- a/README.md +++ b/README.md @@ -631,9 +631,19 @@ A: ### Q: Docker Compose 支持哪些环境? A: 项目支持三种环境的 Docker Compose 部署: -- **dev**: 开发环境,端口映射 MySQL:3306, Redis:6379 -- **stage**: 预发布环境,端口映射 MySQL:3307, Redis:6380 -- **prod**: 生产环境,端口映射 MySQL:3308, Redis:6381,包含健康检查和自动重启 +- **dev**: 开发环境,应用端口 1234 +- **stage**: 预发布环境,应用端口 1234,包含自动重启 +- **prod**: 生产环境,应用端口 1234,包含健康检查和自动重启 + +**重要说明**: +- 项目使用宿主机上的 MySQL 和 Redis 服务,不在 Docker Compose 中部署 +- 容器通过 `host.docker.internal` 访问宿主机服务 +- 需要确保宿主机上的 MySQL 和 Redis 已正确配置(监听在 0.0.0.0,允许外部连接) +- 如果使用 rootless Docker,需要在 `docker-compose.yml` 中手动指定宿主机 IP: + ```yaml + extra_hosts: + - "host.docker.internal:192.168.1.11" # 替换为实际宿主机 IP + ``` ### Q: Docker 镜像拉取失败怎么办? A: 如果在中国大陆遇到镜像拉取超时或失败,可以: @@ -687,6 +697,161 @@ A: 如果在中国大陆遇到镜像拉取超时或失败,可以: - 确认已重启 Docker 服务 - 检查 Docker 服务状态:`sudo systemctl status docker` 或 `systemctl --user status docker` +### Q: Docker 容器无法连接到宿主机上的 MySQL 和 Redis? +A: 如果遇到 `dial tcp: connection refused` 或 `lookup mysql on 127.0.0.11:53: server misbehaving` 错误,可能是以下原因: + +1. **MySQL/Redis 只监听本地接口(127.0.0.1)** + + 检查服务监听状态: + ```bash + # 检查 MySQL + sudo netstat -tlnp | grep 3306 + # 或 + sudo ss -tlnp | grep 3306 + + # 检查 Redis + sudo netstat -tlnp | grep 6379 + # 或 + sudo ss -tlnp | grep 6379 + ``` + + 如果显示 `127.0.0.1:3306` 或 `127.0.0.1:6379`,说明只监听本地接口,需要修改配置。 + +2. **修改 MySQL 配置允许外部连接**: + + ```bash + # 编辑 MySQL 配置文件 + sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf + + # 找到 bind-address,修改为: + bind-address = 0.0.0.0 + + # 重启 MySQL + sudo systemctl restart mysql + + # 验证配置 + mysql -uroot -p -e "SHOW VARIABLES LIKE 'bind_address';" + # 应该显示: bind_address | 0.0.0.0 + ``` + +3. **修改 MySQL 用户权限**: + + ```bash + # 允许 root 用户从任何主机连接 + mysql -uroot -p << 'EOF' + UPDATE mysql.user SET host='%' WHERE user='root' AND host='localhost'; + FLUSH PRIVILEGES; + SELECT user, host FROM mysql.user WHERE user='root'; + EOF + ``` + + 或者创建新的用户: + ```bash + mysql -uroot -p << 'EOF' + CREATE USER 'root'@'%' IDENTIFIED BY 'your_password'; + GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; + FLUSH PRIVILEGES; + EOF + ``` + +4. **修改 Redis 配置允许外部连接**: + + ```bash + # 方法1:通过 Redis CLI 临时修改(重启后失效) + redis-cli CONFIG SET bind "0.0.0.0" + redis-cli CONFIG SET protected-mode no + redis-cli CONFIG REWRITE # 持久化配置 + + # 方法2:修改 Redis 配置文件(推荐) + # 找到 Redis 配置文件(通常在 /etc/redis/redis.conf 或 /usr/local/etc/redis.conf) + sudo nano /etc/redis/redis.conf + + # 修改以下配置: + bind 0.0.0.0 + protected-mode no + + # 重启 Redis + sudo systemctl restart redis + # 或 + sudo service redis-server restart + ``` + +5. **rootless Docker 的 host-gateway 问题**: + + 如果使用 rootless Docker,`host-gateway` 可能指向错误的 IP。需要手动指定宿主机 IP: + + ```yaml + # docker-compose.yml + services: + yinli-api: + extra_hosts: + - "host.docker.internal:192.168.1.11" # 替换为实际宿主机 IP + ``` + + 获取宿主机 IP: + ```bash + hostname -I | awk '{print $1}' + # 或 + ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $2}' | cut -d/ -f1 + ``` + +6. **验证连接**: + + ```bash + # 从容器内测试 MySQL 连接 + docker exec docker-yinli-api-1 sh -c "nc -zv host.docker.internal 3306" + + # 从容器内测试 Redis 连接 + docker exec docker-yinli-api-1 sh -c "nc -zv host.docker.internal 6379" + ``` + +7. **检查防火墙规则**: + + ```bash + # 检查 iptables 规则 + sudo iptables -L -n | grep -E "3306|6379" + + # 如果需要,允许 Docker 网络访问 + sudo iptables -A INPUT -p tcp --dport 3306 -s 172.17.0.0/16 -j ACCEPT + sudo iptables -A INPUT -p tcp --dport 6379 -s 172.17.0.0/16 -j ACCEPT + ``` + +**完整排查步骤**: +1. 检查 MySQL/Redis 是否监听在 `0.0.0.0`(而不是 `127.0.0.1`) +2. 检查 MySQL 用户权限(允许从 `%` 连接) +3. 检查 Redis `protected-mode` 是否已禁用 +4. 检查 Docker Compose 的 `extra_hosts` 配置是否正确(rootless Docker 需要使用宿主机实际 IP) +5. 检查防火墙规则 +6. 查看容器日志:`docker logs docker-yinli-api-1` + +**常见错误信息**: +- `dial tcp 172.17.0.1:3306: connect: connection refused` - MySQL 未监听在 0.0.0.0 或用户权限不足 +- `dial tcp: lookup mysql on 127.0.0.11:53: server misbehaving` - config 文件中使用了 `mysql` 作为 host,但 Docker Compose 中没有 mysql 服务 +- `DENIED Redis is running in protected mode` - Redis 的 protected-mode 未禁用 +- `Host 'xxx' is not allowed to connect to this MySQL server` - MySQL 用户权限问题 + +### Q: MySQL 容器无法正常停止怎么办? +A: 如果遇到 `make docker-down-stage` 无法正常关闭 MySQL 容器的问题,已通过以下方式解决: + +1. **Docker Compose 配置优化**(已应用): + - 为 MySQL 服务添加 `stop_grace_period: 60s`(增加优雅关闭时间) + - 添加 `stop_signal: SIGTERM`(使用 SIGTERM 信号优雅停止) + - 添加 `init: true`(使用 init 进程管理子进程,避免僵尸进程) + +2. **Makefile 自动重试机制**(已实现): + - 所有 `docker-down-*` 命令已添加自动检测权限错误 + - 如果检测到权限错误,会自动重启 rootless Docker 服务并重试 + - 显示完整的 Docker Compose 进度信息(`[+] Running` 和 `✔` 符号) + +3. **如果仍然无法停止**: + ```bash + # 手动重启 rootless Docker 服务 + systemctl --user restart docker + + # 然后再次尝试 + make docker-down-stage + ``` + ### Q: Docker 权限错误(permission denied)怎么办? A: 如果遇到 `Error response from daemon: cannot stop container: permission denied` 错误,可能的原因和解决方法: diff --git a/config/dev.yaml b/config/dev.yaml index b285782..36afa60 100644 --- a/config/dev.yaml +++ b/config/dev.yaml @@ -3,7 +3,7 @@ server: mode: debug database: - host: localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 3306 username: root password: sasasasa @@ -15,7 +15,7 @@ database: maxOpenConns: 100 redis: - host: localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 6379 password: "" db: 0 diff --git a/config/prod.yaml b/config/prod.yaml index 01f698d..c5c70f0 100644 --- a/config/prod.yaml +++ b/config/prod.yaml @@ -3,7 +3,7 @@ server: mode: release database: - host: mysql # Docker环境使用服务名,本地环境使用localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 3306 username: root password: sasasasa @@ -15,7 +15,7 @@ database: maxOpenConns: 500 redis: - host: redis # Docker环境使用服务名,本地环境使用localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 6379 password: "" db: 2 diff --git a/config/stage.yaml b/config/stage.yaml index eb143cd..14e4563 100644 --- a/config/stage.yaml +++ b/config/stage.yaml @@ -3,7 +3,7 @@ server: mode: release database: - host: mysql # Docker环境使用服务名,本地环境使用localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 3306 username: root password: sasasasa @@ -15,7 +15,7 @@ database: maxOpenConns: 200 redis: - host: redis # Docker环境使用服务名,本地环境使用localhost + host: host.docker.internal # Docker环境使用host.docker.internal访问宿主机,本地环境使用localhost port: 6379 password: "" db: 1 diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index d336594..73f1885 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -7,43 +7,7 @@ services: - "1234:1234" environment: - APP_ENV=dev - depends_on: - - mysql - - redis volumes: - ../config:/app/config:ro - networks: - - yinli-network - - mysql: - image: mysql:8.0 - environment: - MYSQL_ROOT_PASSWORD: sasasasa - MYSQL_DATABASE: yinli - ports: - - "3306:3306" - volumes: - - mysql_data:/var/lib/mysql - - ../sql:/docker-entrypoint-initdb.d:ro - networks: - - yinli-network - stop_grace_period: 60s - stop_signal: SIGTERM - init: true - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - volumes: - - redis_data:/data - networks: - - yinli-network - -volumes: - mysql_data: - redis_data: - -networks: - yinli-network: - driver: bridge + extra_hosts: + - "host.docker.internal:192.168.1.11" diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 156075c..4e6bc63 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -7,13 +7,10 @@ services: - "1234:1234" environment: - APP_ENV=prod - depends_on: - - mysql - - redis volumes: - ../config:/app/config:ro - networks: - - yinli-network + extra_hosts: + - "host.docker.internal:192.168.1.11" restart: always healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:1234/health"] @@ -22,50 +19,3 @@ services: retries: 3 start_period: 40s - mysql: - image: mysql:8.0 - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE: yinli - ports: - - "3308:3306" - volumes: - - mysql_prod_data:/var/lib/mysql - - ../sql:/docker-entrypoint-initdb.d:ro - networks: - - yinli-network - restart: always - stop_grace_period: 60s - stop_signal: SIGTERM - init: true - command: --default-authentication-plugin=mysql_native_password - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 10s - timeout: 5s - retries: 5 - - redis: - image: redis:7-alpine - ports: - - "6381:6379" - volumes: - - redis_prod_data:/data - networks: - - yinli-network - restart: always - command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-} - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - -volumes: - mysql_prod_data: - redis_prod_data: - -networks: - yinli-network: - driver: bridge - diff --git a/docker/docker-compose.stage.yml b/docker/docker-compose.stage.yml index a04e20c..38280c3 100644 --- a/docker/docker-compose.stage.yml +++ b/docker/docker-compose.stage.yml @@ -7,49 +7,9 @@ services: - "1234:1234" environment: - APP_ENV=stage - depends_on: - - mysql - - redis volumes: - ../config:/app/config:ro - networks: - - yinli-network + extra_hosts: + - "host.docker.internal:192.168.1.11" restart: unless-stopped - mysql: - image: mysql:8.0 - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-sasasasa} - MYSQL_DATABASE: yinli - ports: - - "3307:3306" - volumes: - - mysql_stage_data:/var/lib/mysql - - ../sql:/docker-entrypoint-initdb.d:ro - networks: - - yinli-network - restart: unless-stopped - stop_grace_period: 60s - stop_signal: SIGTERM - init: true - command: --default-authentication-plugin=mysql_native_password - - redis: - image: redis:7-alpine - ports: - - "6380:6379" - volumes: - - redis_stage_data:/data - networks: - - yinli-network - restart: unless-stopped - command: redis-server --appendonly yes - -volumes: - mysql_stage_data: - redis_stage_data: - -networks: - yinli-network: - driver: bridge -