Docker 容器网络浅析
作为开发者,我们使用 Docker 取得的第一个成果通常是运行单个容器并通过本地主机(localhost)访问它。虽然这是一个坚实的开端,但容器的真正优势只有在开始运行多容器应用程序时才会显现出来。而这也是大多数开发者面临挑战的地方。
如果你有一个 Web 应用程序在一个容器中,一个数据库在另一个容器中,它们该如何通信呢?
你可能会尝试使用 docker inspect 命令查找容器的 IP 地址,但这个 IP 是临时的——下次容器重启后就会改变。你可能在旧教程中见过使用 --link 标志的方法,但这种方法已经过时,且并非有效的解决方案。
事实上,Docker 拥有一套功能强大的网络系统,只要掌握一个关键概念,使用起来就会非常简单。本文将阐明 Docker 网络的相关知识,向你展示如何正确连接容器,并解释 Docker Compose 如何简化这一流程。
桥接网络的问题
安装 Docker 时,它会创建一个名为 bridge 的默认网络。所有未使用 --net 标志运行的容器都会连接到这个网络。你可以通过运行 docker network ls 命令查看它。这个默认的桥接网络允许容器访问互联网,以执行 apt-get 或 npm install 等操作,也支持你使用 -p 8080:80 命令将容器端口映射到主机。
但它在服务发现方面存在问题。在默认桥接网络上运行的容器虽然理论上可以相互通信,但必须使用它们的内部 IP 地址(例如 172.17.0.2)。正如我们所见,这些 IP 地址并不可靠。而且,Web 容器无法通过数据库容器的名称来定位它,Docker 没有提供这种内置机制。因此,默认桥接网络几乎不适合任何多容器应用程序。
解决方案:用户自定义桥接网络
这是你需要理解的核心概念。不要使用默认网络,对于每个应用程序,你都应该创建自己的自定义桥接网络。创建自定义网络后,Docker 会免费提供一项重要功能:基于 DNS 的自动服务发现。
简单来说,Docker 会为你的自定义网络分配一个内部 DNS 服务器。任何连接到这个网络的容器,都会以其容器名称为标识,自动在该 DNS 服务器上进行注册。
实际命令行(CLI)示例
步骤 1:创建应用程序所需的网络
docker network create my-app-net
步骤 2:运行数据库容器
接下来,我们将运行一个 PostgreSQL 数据库,并将其附加到新创建的网络中。这一步非常重要,而且我们必须为数据库容器指定一个名称。
docker run -d \
--name my-database \
--net my-app-net \
-e POSTGRES_PASSWORD=mysecret \
postgres
此时,数据库容器已启动,并且在 “my-app-net” 网络的 DNS 服务器上以主机名 “my-database” 完成注册。
步骤 3:运行应用程序容器
现在,我们需要运行 Web 应用程序(本文示例中为一个简单的 Node.js 应用)。同样要将它附加到同一个网络,并且需要告知它如何找到数据库。
docker run -d \
--name my-api \
--net my-app-net \
-e DB_HOST=my-database \
-p 3000:3000 \
my-api-image
注意环境变量 DB_HOST=my-database,仅此而已。在你的应用程序代码中,现在可以使用 my-database 作为数据库连接的主机名。Docker 的内部 DNS 会自动将这个名称解析为 my-database 容器当前的私有 IP 地址。无论你是否重启容器,也无论容器获得的 IP 地址是什么,my-database 这个名称始终会指向正确的容器。
“简便方法”:使用 Docker Compose
虽然命令行命令有助于我们理解原理,但在日常开发中使用起来可能比较繁琐。而 Docker Compose 的设计目的就是解决这个问题,它会自动处理所有网络管理工作。下面我们通过一个 docker-compose.yml 文件来定义上述相同的双容器配置:
version: '3.9' # 最新版本为 3.9
services:
# 我们的 API 服务层
my-api:
build: .
image: my-api-image
ports:
- "5000:5000"
environment:
# 通过服务名称进行连接
- DB_HOST=my-database
depends_on:
- my-database
# 我们的数据库服务
my-database:
image: postgres
environment:
- POSTGRES_PASSWORD=mysecret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
当你运行 docker-compose 命令时,背后会发生以下操作:
- Docker Compose 会自动为当前项目创建一个新的默认网络。
- 启动
my-database服务,将其连接到该网络,并使其可以通过主机名my-database被访问。 - 启动
my-api服务,同样将其连接到同一个网络,并添加所需的环境变量。
之后,my-api 容器就可以通过连接服务名 my-database 来访问数据库了。这是 Docker 本地开发的标准工作流程。
网络驱动概述
95% 的情况下,你都会使用用户自定义桥接网络。不过,了解其他驱动的用途也会很有帮助。
1. Host(主机模式)
- 定义:禁用网络隔离,容器共享主机的网络栈。
- 使用场景:需要高性能网络,且可以接受牺牲安全性和端口映射功能的情况。例如,一个需要捕获主机上所有网络流量的网络监控工具。如果容器绑定到 80 端口,那么它就会直接使用主机的 80 端口。
2. Overlay(覆盖网络模式)
- 定义:一种分布式网络,可连接多个 Docker 主机上的容器。
- 使用场景:这是 Docker Swarm(Docker 原生编排工具)的网络模型。它会创建一个覆盖集群中所有节点的“扁平化”虚拟网络。通过这种网络,主机 A 上的 Web 容器可以与主机 B 上的数据库容器通信,就像它们在同一台机器上一样。
3. None(无网络模式)
- 定义:完全的网络隔离。容器没有网络接口,只拥有一个回环设备(loopback device)。
- 使用场景:适用于对安全性要求极高的任务,或者不需要与外部世界进行任何通信的简单批处理任务。
给软件开发者的核心要点
- 不要在多容器应用程序中使用默认桥接网络。使用命令行时,务必创建用户自定义桥接网络(通过
docker network create...命令),这样可以实现基于 DNS 的自动服务发现。 - 本地开发时使用 Docker Compose。它会自动创建自定义网络,只需通过服务名称就能轻松实现服务发现。容器名称就是你的新主机名。在 Docker 网络环境中,你连接的是
my-database,而不是localhost或172.17.0.2。
原文链接:https://dzone.com/articles/docker-networking-guide-for-developers