2024.03.26
11 分钟

如何公开 Docker 端口

厘清 EXPOSE、--expose 与 -p 的区别,理解 Docker 端口暴露。

Docker

docker

Docker 的网络模型会将容器与主机系统隔离。每个容器都有独立的网络命名空间和端口范围。

Docker 不会自动通过主机网络公开容器端口。要让外部流量进入容器,需要明确暴露容器使用的端口,并将目标端口绑定到主机网络接口。

“暴露”和“绑定(发布)”描述的是不同操作。本文将通过示例说明两者的区别,以及如何查看和管理容器端口。

主要内容包括:

  1. 什么是在 Docker 中公开端口?
  2. 如何在 Dockerfile 中公开端口
  3. 如何在创建 Docker 容器时公开端口
  4. 公开 Docker 端口 - 示例
  5. 如何公开多个 Docker 端口
  6. 如何绑定公开的端口

什么是在 Docker 中公开端口?

公开 Docker 端口表示容器化工作负载正在使用这个端口。Docker 会记录并显示这些信息,帮助你判断应该将哪些容器端口绑定到主机。

需要注意,公开端口并不会自动将其绑定到主机上。公开端口只会添加元数据,用于说明容器中的应用正在监听哪些端口。

例如,Web 服务器的 Dockerfile 可以包含以下指令:

EXPOSE 80

这条指令表示容器中的应用监听 80 端口。要从容器外部访问服务器,还需要将这个端口绑定到主机;容器启动时不会因为该指令自动建立映射。

即使可以直接绑定一个未公开的端口,公开端口仍然具有文档作用。它能明确说明容器需要使用哪些端口,让镜像更容易理解和管理。

通过公开端口列表,你可以快速判断应该绑定哪些端口,也能发现已经分配给容器但没有使用的主机端口。启动容器时,还可以自动绑定所有已公开端口,减少手动配置。

公开端口与绑定端口

公开端口是一种被动行为,只表示容器能够监听该端口。要让外部流量进入容器,需要将主机端口绑定到容器端口,建立主机与容器之间的映射。

日常表达中,绑定端口也经常被称为“公开”端口,因为绑定后可以通过主机网络访问容器。但两者并不等价:端口可以只公开而不绑定,也可以不先公开就直接绑定。

  • 公开端口:记录元数据,说明容器中的应用或服务会监听该端口。
  • 绑定/发布端口:将主机端口映射到容器端口,允许通过主机网络接口与容器通信。

如何在 Dockerfile 中公开端口

Docker 提供两种主要方式公开端口:在 Dockerfile 中添加 EXPOSE,或者在通过 docker run 启动容器时传入 --expose

在 Dockerfile 中,需要为每个端口添加一条 EXPOSE 指令:

EXPOSE 80

默认情况下,Docker 会按 TCP 公开端口。如果需要 UDP,可以明确指定协议:

EXPOSE 80/udp

如果同一个端口需要同时支持 TCP 和 UDP,应分别添加两条 EXPOSE 指令:

EXPOSE 80/tcp
EXPOSE 80/udp

使用包含 EXPOSE 指令的镜像启动容器时,Docker 会自动记录其中列出的全部公开端口。

如何在创建 Docker 容器时公开端口

如果需要在启动容器时手动公开端口,可以在 docker run 中添加 --expose

bash
docker run my-container:latest --expose 80

重复传入该标志可以公开多个端口。与 EXPOSE 类似,--expose 支持 <port>/tcp<port>/udp 格式;只提供端口号时,默认使用 TCP。

实际使用中,--expose 并不常见。容器需要监听的端口通常已经定义在镜像的 Dockerfile 中。如果应用没有监听额外端口,即使在启动时将它们公开,也不会产生实际作用。

公开 Docker 端口 - 示例

下面通过一个简单示例,演示如何公开端口并查看结果。

使用 Dockerfile EXPOSE 公开端口

首先,创建一个包含公开端口的 Dockerfile:

Dockerfile
FROM alpine:latest
EXPOSE 80
CMD ["sleep", "300"]

然后构建镜像,并使用它启动容器:

bash
docker build -t demo-image:latest .
docker run -d --name demo docker-image:latest

使用 Docker run expose

如果无法修改镜像的 Dockerfile,可以在启动容器时手动公开端口:

bash
docker run -d --name demo-run --expose 443 demo-image:latest

查看公开的端口

使用 docker ps 可以查看容器公开的端口:

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2fdfd038a368 demo-image:latest "sleep 300" 2 seconds ago Up Less than a second 80/tcp, 443/tcp demo-run
dd50cd05e515 demo-image:latest "sleep 300" About a minute ago Up About a minute 80/tcp demo

如何公开多个 Docker 端口

docker ps 的输出中,每个容器公开的端口都会显示在 PORTS 一栏。

要为一个容器公开多个端口,可以重复使用 Dockerfile 中的 EXPOSE,或者重复传入 docker run--expose

如果同时使用 EXPOSE--expose,Docker 会合并两处定义的端口。上面的输出显示,示例容器同时公开了 Dockerfile 中定义的 80 端口,以及启动容器时通过 --expose 添加的 443 端口。

如何绑定公开的端口

要让外部流量进入容器,需要将容器端口绑定到主机。使用 docker run-p 标志可以建立端口映射:

bash
docker run -d --name nginx -p 80:80 nginx:latest

这个示例将主机的 80 端口映射到容器的 80 端口。完成映射后,可以通过主机网络接口访问容器中的应用:

curl http://localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

绑定结果会显示在 docker ps 输出的 PORTS 一栏。只公开端口时,Docker 仅显示 80/tcp 这类容器端口;绑定后,输出会包含主机端口到容器端口的映射:

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cafc1f042132 nginx:latest "/docker-entrypoint.…" About an hour ago Up About an hour 0.0.0.0:80->80/tcp, :::80->80/tcp nginx

重映射已绑定的端口

主机端口不需要与容器监听的端口相同。下面将主机的 8080 端口映射到容器的 80 端口:

docker run -d --name nginx -p 8080:80 nginx:latest
cur http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

将端口绑定到特定主机接口(如:localhost)

前面的端口绑定会作用于主机的所有网络接口,因此网络中的其他设备也可能访问容器。

如果服务只应该提供给主机上的其他进程,可以使用 host_address:host_port:container_port 格式,将端口限制在指定的主机接口:

bash
docker run -d --name nginx -p 127.0.0.1:8080:80 nginx:latest

这里将 Docker 主机 127.0.0.1(localhost)的 8080 端口映射到容器的 80 端口。从主机其他 IP 地址进入该端口的流量不会到达容器。

自动绑定公开的端口

公开端口与绑定端口可以独立使用。你可以绑定未公开的端口,也可以只公开端口而不建立任何主机映射。

如果希望绑定容器公开的所有端口,可以在 docker run 中添加 -P (--publish-all)

bash
docker run -d -P --name demo demo-image:latest

使用这个标志后,不需要先检查镜像公开了哪些端口,再逐个通过 -p 手动绑定。Docker 会为每个公开的容器端口随机分配一个主机端口。

使用 docker ps 可以确认实际映射结果:

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d66f376e3860 demo-image:latest "sleep 300" 2 seconds ago Up 1 second 0.0.0.0:32768->80/tcp, :::32768->80/tcp demo

示例 Dockerfile 中通过 EXPOSE 定义的容器端口 80,被随机映射到主机端口 32768。这是启动容器时使用 -P 的结果。

要点

暴露端口只会记录容器应用使用的端口,是镜像或容器的元数据。绑定或发布端口则会建立主机端口到容器端口的映射,让外部流量能够进入容器。

Dockerfile 中的 EXPOSE 用于声明镜像使用的端口,运行容器时的 --expose 可以补充公开端口。需要建立明确映射时使用 -p;需要自动绑定全部公开端口时,则可以使用 -P