第5章:Docker 网络 - 容器的沟通桥梁
到目前为止,我们已经学会了如何运行单个容器,并为它保存数据。但一个真正的应用,往往是由多个服务(容器)组成的,比如一个 Web 应用容器需要连接一个数据库容器。
这就引出了一个新问题:容器之间如何进行通信?
默认情况下,Docker 会为每个容器分配一个独立的 IP 地址,但这些 IP 地址是动态的,每次容器重启都可能改变。我们总不能每次都手动去查找 IP,然后在代码里写死吧?
本章,我们将深入 Docker 的网络模型,学习如何为我们的多个容器创建一个稳定、可靠的“局域网”,让它们可以通过服务名轻松地互相发现和通信。
5.1 Docker 的几种网络模式
Docker 内置了多种网络驱动(drivers),来满足不同的通信需求。我们重点关注最常见的三种:
-
bridge (桥接网络)
- 这是默认模式。 当你启动一个容器而没有指定任何网络时,它就会被连接到一个名为
bridge的默认桥接网络上。 - 工作原理:Docker 会创建一个虚拟的网桥(可以想象成一个虚拟路由器),所有连接到这个网络的容器,都会获得一个内部 IP 地址。它们之间可以通过 IP 地址互相访问,但 Docker 不推荐这样做,因为默认桥接网络在服务发现方面有很大局限性。
- 与外界通信:容器可以通过这个网桥访问外部世界(比如
ping baidu.com)。但外界无法直接访问容器,除非做了端口映射 (-p参数)。
- 这是默认模式。 当你启动一个容器而没有指定任何网络时,它就会被连接到一个名为
-
host (主机网络)
- 是什么:容器将不再拥有自己独立的网络空间,而是直接共享主机的网络。
- 优缺点:优点是网络性能极高,因为没有了虚拟化层的开销。缺点是破坏了容器的网络隔离性,容器内监听的端口会直接占用主机的端口,容易引起冲突。使用场景较少。
-
none (无网络)
- 是什么:容器将拥有自己的网络空间,但不进行任何网络配置。它就像一个被拔掉网线的电脑,完全与世隔绝。适合那些完全不需要网络连接的、纯计算型任务。
用户自定义桥接网络 (User-defined Bridge Network)
虽然 Docker 有一个默认的 bridge 网络,但官方强烈推荐我们为自己的应用创建自定义的桥接网络。
为什么?因为自定义桥接网络提供了两个杀手级特性:
- 自动服务发现 (Automatic Service Discovery):在同一个自定义网络中,容器之间可以直接通过容器名作为主机名 (hostname) 来互相访问!Docker 内置了一个 DNS 服务器来负责解析。
- 更好的隔离性:自定义网络能提供更好的隔离。只有连接在同一个自定义网络上的容器才能互相通信,连接在不同网络上的容器则默认是隔离的。
5.2 容器间如何“对话”?
现在,我们知道了最佳实践是使用用户自定义桥接网络。让我们通过一个实战,来体验一下它神奇的“自动服务发现”功能。
第一步:创建一个新的网络
我们可以使用 docker network create 命令来创建一个新的桥接网络。我们给它起个名字叫 my-app-net。
docker network create my-app-net
你可以用 docker network ls 来查看你主机上所有的 Docker 网络,应该能看到我们刚创建的 my-app-net。
第二步:将容器连接到这个网络
现在,我们启动两个容器,并在启动时使用 --network 参数,将它们都连接到 my-app-net。
我们启动一个 postgres 数据库容器,并给它起个名字叫 db。
docker run -d --name db \
--network my-app-net \
-e POSTGRES_PASSWORD=mysecretpassword \
-v pg-data:/var/lib/postgresql/data \
postgres:15-alpine
接着,我们启动一个普通的 alpine 容器(一个极简的 Linux 镜像),并进入它的 shell,用来模拟我们的应用容器。我们给它起个名字叫 client。
docker run -it --rm --name client \
--network my-app-net \
docker.m.daocloud.io/alpine sh
--rm: 这个参数表示容器退出后自动删除它,很适合做一次性测试。
第三步:见证奇迹的时刻
现在你已经进入了 client 容器的 shell。在这个 shell 里,我们来尝试 ping 一下名为 db 的容器。
# 在 client 容器的 shell 中执行
ping db
你会惊奇地发现,ping 命令成功了!它会自动解析 db 这个名字,并找到 postgres 容器的内部 IP 地址。
这就是 Docker 的内置 DNS 发挥的作用。在同一个自定义网络中,容器名就是它们互相访问的地址。
5.3 [实战] 让应用容器连接到数据库容器
理论验证完毕,我们来动真格的。修改我们的 Node.js 应用代码,让它去连接 postgres 数据库。
注意:为了演示,我们这里只做连接测试,实际的项目代码会更复杂。你需要先在
my-app目录下安装pg驱动:npm install pg,然后重新构建my-app:1.1镜像。
修改 my-app/app.js 文件:
const express = require('express');
const { Pool } = require('pg');
// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
// App
const app = express();
// Postgres Client Setup
const pgClient = new Pool({
// 关键在这里!我们直接使用容器名 'db' 作为主机地址
host: 'db',
port: 5432,
user: 'postgres', // 默认用户
password: 'mysecretpassword', // 我们启动db容器时设置的环境变量
database: 'postgres' // 默认数据库
});
app.get('/', (req, res) => {
res.send('Hello, Docker World!');
});
app.get('/db', async (req, res) => {
try {
const result = await pgClient.query('SELECT NOW()');
res.send(`Database time is: ${result.rows[0].now}`);
} catch (err) {
res.status(500).send(`Error connecting to database: ${err}`);
}
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
重新构建镜像:
# 在 my-app 目录下
docker build -t my-app:1.1 .
启动所有服务:
确保你已经创建了 my-app-net 网络,并启动了 db 容器。
# 如果之前的 db 还在,先停掉并删除
docker rm -f db
# 启动数据库
docker run -d --name db \
--network my-app-net \
-e POSTGRES_PASSWORD=mysecretpassword \
-v pg-data:/var/lib/postgresql/data \
postgres:15-alpine
# 启动我们的应用
docker run -d --name my-web-app -p 4000:8080 \
--network my-app-net \
my-app:1.1
现在,打开浏览器:
- 访问
http://localhost:4000,应该能看到 "Hello, Docker World!"。 - 访问
http://localhost:4000/db,你应该能看到页面成功地显示出了数据库的当前时间!
这证明我们的 my-web-app 容器,已经通过 db 这个名字,成功地连接上了 db 容器。
5.4 本章小结 & 避坑指南
你已经打通了容器之间任督二脉,构建多服务应用的基础已经牢固。
-
本章回顾:
- 我们了解了 Docker 的
bridge,host,none三种网络模式。 - 我们掌握了最佳实践:使用用户自定义桥接网络。
- 我们利用了自定义网络的核心优势——自动服务发现,实现了通过容器名进行通信。
- 我们通过实战,成功让应用容器连接上了数据库容器。
- 我们了解了 Docker 的
-
避坑指南:容器无法访问外部网络怎么办?
- 问题:在容器里
ping baidu.com或npm install时,提示Temporary failure in name resolution或网络超时。 - 原因:这通常是 Docker 的 DNS 配置问题,或者主机防火墙的限制。
- 排查与解决:
- 检查 Docker DNS:Docker 默认会使用主机的 DNS 设置。但有时可能会出问题。你可以尝试在 Docker 的配置文件(Docker Desktop 在 Settings -> Docker Engine 的 JSON 里,Linux 在
/etc/docker/daemon.json)中,强制指定一个公共 DNS 服务器,比如 Google 的8.8.8.8或 Cloudflare 的1.1.1.1。修改后需要重启 Docker 服务。{ "dns": ["8.8.8.8", "1.1.1.1"] } - 检查防火墙:确保你的主机防火墙没有阻止 Docker 的网络流量。特别是要允许 IP 转发。
- 重启大法:有时候,简单地重启 Docker 服务或重启电脑就能解决一些奇怪的网络问题。
- 检查 Docker DNS:Docker 默认会使用主机的 DNS 设置。但有时可能会出问题。你可以尝试在 Docker 的配置文件(Docker Desktop 在 Settings -> Docker Engine 的 JSON 里,Linux 在
- 问题:在容器里
虽然我们已经能让多个容器协同工作了,但每次都要手动敲那么多 docker run 命令,还要注意启动顺序,实在太麻烦了。在下一章,我们将学习一个编排神器——Docker Compose,让你用一个文件,一键启停整个应用!