This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/金山办公作业/Week05/db in docker.md
T

363 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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