vault backup: 2026-04-22 10:10:19
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
# Docker 网络数据库连接指南
|
||||
|
||||
## 问题描述
|
||||
|
||||
在后端服务连接运行在 Docker 网络中的数据库时,**不能使用 `localhost`** 作为连接地址。
|
||||
|
||||
## 原因分析
|
||||
|
||||
### 为什么使用 `localhost` 是错误的?
|
||||
|
||||
在 Docker 网络中,每个容器都有**独立的网络栈**:
|
||||
|
||||
- **`localhost` 在容器内部指向容器自身**(127.0.0.1 指向容器内部)
|
||||
- **不是指向同一网络中的其他容器**
|
||||
|
||||
当你的后端容器尝试连接 `localhost:3306` 时,它实际上是在寻找**自己的 3306 端口**,而不是数据库容器的端口。
|
||||
|
||||
#### 直观理解
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Docker 网络(我的网络) │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Backend 容器 │ │ Database 容器 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ localhost ───────┼──→ │ 自己的 8000 端口 │ │
|
||||
│ │ (127.0.0.1) │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ database ────────┼──→ │ 正确!指向 DB │ │
|
||||
│ │ (服务名) │ │ │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ ↑ ↑ │
|
||||
│ └──────────┬───────────────┘ │
|
||||
│ │ │
|
||||
│ 只能通过服务名通信 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 实际会发生什么
|
||||
|
||||
```bash
|
||||
# 在 backend 容器内执行
|
||||
# 尝试连接 localhost:3306
|
||||
# 结果:Connection refused
|
||||
# 原因:backend 自己没有在 3306 端口监听服务
|
||||
```
|
||||
|
||||
#### 类比说明
|
||||
|
||||
这就好比:
|
||||
- 你和朋友住在同一个大楼(Docker 网络)
|
||||
- 你想找朋友
|
||||
- 你大喊"我在这里"(使用 `localhost`)
|
||||
- 但实际上你需要喊的是"小明"(使用服务的名字)
|
||||
- 因为 `"我在这里"` 指的是你自己,不是小明
|
||||
|
||||
### Docker 的网络隔离机制
|
||||
|
||||
Docker 容器默认使用以下网络模式:
|
||||
|
||||
1. **Bridge 网络模式**(默认):
|
||||
- 每个容器有独立的 IP 地址
|
||||
- 容器间通过服务名互相发现
|
||||
- `localhost` 仅指向容器自身
|
||||
|
||||
2. **容器间的 DNS 解析**:
|
||||
```
|
||||
# 在 backend 容器内
|
||||
$ ping database
|
||||
PING database (172.19.0.2) // 解析到数据库容器的 IP
|
||||
|
||||
$ ping localhost
|
||||
PING localhost (127.0.0.1) // 指向自己
|
||||
```
|
||||
|
||||
3. **端口监听位置**:
|
||||
```
|
||||
Database 容器内部:MySQL 监听 0.0.0.0:3306
|
||||
Database 容器 IP:172.19.0.2
|
||||
Backend 容器不能访问:127.0.0.1:3306(自己的端口)
|
||||
Backend 容器应该访问:172.19.0.2:3306(通过服务名解析)
|
||||
```
|
||||
|
||||
### 结论
|
||||
|
||||
**使用 `localhost` 连接数据库必然失败**,因为它:
|
||||
- 永远指向调用方容器自己
|
||||
- 无法跨容器通信
|
||||
- 违反了 Docker 的网络隔离设计
|
||||
|
||||
**正确做法:使用服务名或容器名**,这样 Docker 的内置 DNS 才能正确解析到目标容器。
|
||||
|
||||
## 正确做法
|
||||
|
||||
### 1. 使用服务名(Service Name)
|
||||
|
||||
在 `docker-compose.yml` 中定义的服务名即可用作主机名:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
# 后端连接配置
|
||||
DATABASE_URL = "mysql://user:password@database:3306/dbname"
|
||||
# ^^^^^^^^ 使用服务名 "database"
|
||||
```
|
||||
|
||||
### 2. 使用容器名
|
||||
|
||||
在 docker-compose 中为容器指定名称:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
container_name: my-mysql-db # 显式指定容器名
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
DATABASE_URL = "mysql://user:password@my-mysql-db:3306/dbname"
|
||||
```
|
||||
|
||||
### 3. 网络别名(Network Alias)
|
||||
|
||||
如果有多个服务需要连接,可以设置网络别名:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
networks:
|
||||
appnet:
|
||||
aliases:
|
||||
- db
|
||||
- mysql
|
||||
|
||||
networks:
|
||||
appnet:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
# 可以使用任一别名
|
||||
DATABASE_URL = "mysql://user:password@db:3306/dbname"
|
||||
# 或
|
||||
DATABASE_URL = "mysql://user:password@mysql:3306/dbname"
|
||||
```
|
||||
|
||||
## 常见错误
|
||||
|
||||
### ❌ 错误写法
|
||||
```python
|
||||
# 这会导致连接失败
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"
|
||||
# ^^^^^^^^^ 指向 backend 自己
|
||||
```
|
||||
|
||||
### ✅ 正确写法
|
||||
```python
|
||||
# 使用 docker-compose 中的服务名
|
||||
DATABASE_URL = "mysql://user:password@database:3306/dbname"
|
||||
# ^^^^^^^^ 数据库服务名
|
||||
```
|
||||
|
||||
## 实际示例
|
||||
|
||||
### 示例 1:Spring Boot + MySQL
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
springboot-app:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql-db:3306/mydb
|
||||
SPRING_DATASOURCE_USERNAME: user
|
||||
SPRING_DATASOURCE_PASSWORD: password
|
||||
depends_on:
|
||||
- mysql-db
|
||||
|
||||
mysql-db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_DATABASE: mydb
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
ports:
|
||||
- "3306:3306"
|
||||
```
|
||||
|
||||
### 示例 2:Node.js + PostgreSQL
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
api:
|
||||
build: ./api
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
DATABASE_HOST: postgres
|
||||
DATABASE_PORT: 5432
|
||||
DATABASE_NAME: myapp
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_DB: myapp
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
ports:
|
||||
- "5432:5432"
|
||||
```
|
||||
|
||||
```javascript
|
||||
// .env
|
||||
DATABASE_URL=postgres://user:password@postgres:5432/myapp
|
||||
// ^^^^^^^^ 服务名
|
||||
```
|
||||
|
||||
### 示例 3:Python/Django + MongoDB
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
MONGO_HOST: mongodb
|
||||
MONGO_PORT: 27017
|
||||
depends_on:
|
||||
- mongodb
|
||||
|
||||
mongodb:
|
||||
image: mongo:6.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
```
|
||||
|
||||
```python
|
||||
# settings.py
|
||||
MONGO_URI = "mongodb://mongodb:27017/mydb"
|
||||
# ^^^^^^^^ 使用服务名
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 进入容器测试连接
|
||||
```bash
|
||||
# 进入后端容器
|
||||
docker exec -it <backend-container> bash
|
||||
|
||||
# 测试数据库连接
|
||||
ping database
|
||||
telnet database 3306
|
||||
nc -zv database 3306
|
||||
```
|
||||
|
||||
### 2. 查看网络信息
|
||||
```bash
|
||||
# 查看容器网络
|
||||
docker network inspect <network-name>
|
||||
|
||||
# 查看容器 IP 和别名
|
||||
docker inspect <database-container> --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
|
||||
```
|
||||
|
||||
### 3. 使用 docker network 连接
|
||||
```bash
|
||||
# 从宿主机通过容器 IP 连接(不推荐)
|
||||
docker inspect <database-container> | grep IPAddress
|
||||
|
||||
# 正确方式:从另一个容器连接
|
||||
docker run --rm --network <network-name> mysql:8.0 \
|
||||
mysql -h database -u user -ppassword dbname
|
||||
```
|
||||
|
||||
## 特殊情况
|
||||
|
||||
### 从宿主机连接数据库
|
||||
|
||||
如果需要从**宿主机**(非 Docker 容器)连接数据库,使用:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- "3306:3306" # 映射到宿主机
|
||||
```
|
||||
|
||||
**连接方式:**
|
||||
```python
|
||||
# 从宿主机运行的应用
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"
|
||||
```
|
||||
|
||||
**注意:** 这是唯一可以使用 `localhost` 的情况——从宿主机连接暴露端口的容器。
|
||||
|
||||
### 开发环境 vs 生产环境
|
||||
|
||||
```python
|
||||
# 开发环境:可能直接使用本地数据库
|
||||
if os.getenv("ENVIRONMENT") == "development":
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/devdb"
|
||||
else:
|
||||
# 生产环境:使用 Docker 网络中的数据库
|
||||
DATABASE_URL = "mysql://user:password@database:3306/proddb"
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用环境变量**:将数据库连接配置为环境变量,便于在不同环境间切换
|
||||
2. **使用服务名**:优先使用 docker-compose 中的服务名,保持配置一致性
|
||||
3. **健康检查**:使用 `depends_on` + 健康检查确保数据库就绪后再启动应用
|
||||
4. **网络隔离**:为不同环境使用不同的网络
|
||||
5. **安全性**:不要在代码中硬编码密码,使用 secrets 或环境变量
|
||||
|
||||
## 故障排查清单
|
||||
|
||||
- [ ] 检查 docker-compose.yml 中数据库服务的名称
|
||||
- [ ] 确认后端使用的是服务名而非 `localhost`
|
||||
- [ ] 确认数据库服务在同一个网桥网络中
|
||||
- [ ] 检查端口映射配置(如果从宿主机连接)
|
||||
- [ ] 验证容器间的网络连通性(使用 `ping` 或 `telnet`)
|
||||
- [ ] 检查防火墙规则
|
||||
- [ ] 查看容器日志确认数据库是否正常启动
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [Docker Networking](https://docs.docker.com/network/)
|
||||
- [Docker Compose Service Discovery](https://docs.docker.com/compose/networking/)
|
||||
- [Connect to MySQL from another container](https://hub.docker.com/_/mysql)
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2026-04-21
|
||||
Reference in New Issue
Block a user