跳到主要内容

Podman 基本网络指南

PODMAN logo 一旦人们理解了容器的基础知识,网络往往是他们开始尝试的第一个方面。而说到网络,几乎不需要多少尝试,就会发现自己已经深陷其中。以下指南展示了 Podman 有根和无根容器的最常见网络设置。每个设置都附有一个示例。

有根和无根容器网络之间的差异

Podman 容器网络的一个指导因素是容器是否由 root 用户运行。这是因为没有特权的用户无法在主机上创建网络接口。因此,对于无根容器,默认的网络模式是 slirp4netns。由于权限有限,与有根 Podman 的网络相比,slirp4netns 在网络功能方面存在一些不足;例如,slirp4netns 无法为容器提供可路由的 IP 地址。另一方面,有根容器的默认网络模式是 netavark,它允许容器具有可路由的 IP 地址。

防火墙

防火墙的作用不会影响网络的设置和配置,但会影响这些网络上的流量。最明显的是流入容器主机的网络流量,这通常通过端口映射传递给容器。根据防火墙的实现方式,我们观察到,由于运行带有端口映射的容器(例如),防火墙端口会自动打开。如果容器流量似乎无法正常工作,请检查防火墙并允许在容器使用的端口上的流量。一个常见的问题是,重新加载防火墙会删除 netavark iptables 规则,从而导致有根容器失去网络连接。Podman v3 提供了 podman network reload 命令,无需重启容器即可恢复网络连接。

基本网络设置

大多数使用 Podman 运行的容器和 Pod 遵循一些简单的场景。默认情况下,有根用户的 Podman 会创建一个桥接网络。这是 Podman 最直接和首选的网络设置。桥接网络在内部桥接网络上为容器创建一个接口,然后通过网络地址转换(NAT)连接到互联网。我们也看到用户希望使用 macvlan 进行网络设置。macvlan 插件将主机的整个网络接口转发到容器中,允许容器访问主机连接的网络。最后,无根容器的默认网络配置是 slirp4netns。slirp4netns 网络模式功能有限,但可以在没有 root 权限的用户上运行。它创建一个从主机到容器的隧道以转发流量。

桥接

桥接网络被定义为创建一个内部网络,其中容器和主机都连接到该网络。然后,这个网络能够允许容器与主机外部进行通信。

bridge_network

考虑上述插图。它描绘了一个笔记本电脑用户运行两个容器的情况:一个 Web 实例和一个数据库实例。这两个容器与主机在同一个虚拟网络上。此外,默认情况下,这些容器可以启动与笔记本电脑外部(例如互联网)的通信。虚拟网络上的容器通常具有不可路由的 IP 地址,也称为私有 IP 地址。

当处理从主机外部发起的通信时,外部客户端通常必须通过笔记本电脑的外部网络接口和给定的端口号进行访问。假设主机允许传入流量,主机将知道将传入流量转发到该端口上的特定容器。为了实现这一点,当容器请求转发特定端口时,会添加防火墙规则来转发流量。

桥接网络是 Podman 容器作为有根用户创建时的默认网络。Podman 提供了一个默认的桥接网络,但您可以使用 podman network create 命令创建其他网络。在创建容器时,可以使用 --network 标志将容器加入网络,或者通过 podman network connectpodman network disconnect 命令在创建容器后加入或断开网络连接。

如前所述,slirp4netns 是无根用户的默认网络配置。但从 Podman 版本 4.0 开始,无根用户也可以使用 netavark。无根 netavark 的用户体验与有根 netavark 非常相似,只是不提供默认网络配置。您只需创建一个网络,该网络将作为桥接网络创建。如果您想从 CNI 网络切换到 netavark,必须执行 podman system reset --force 命令。这将删除您所有的镜像、容器和自定义网络。

 podman network create

当运行无根容器时,网络操作将在额外的网络命名空间中执行。要加入这个命名空间,请使用 podman unshare --rootless-netns 命令。

默认网络

使用 netavark 的默认网络 podman 仅为内存存储。由于与 Docker 的向后兼容性,它不支持 DNS 解析。要更改设置,请将内存中的网络导出并修改文件。

对于有根用户的默认网络,使用以下命令:

podman network inspect podman | jq .[] > /etc/containers/networks/podman.json

对于无根网络,使用以下命令:

podman network inspect podman | jq .[] > ~/.local/share/containers/storage/networks/podman.json

示例

默认情况下,如果您没有从 Podman v3 迁移,有根容器将使用 netavark 作为其默认网络。在这种情况下,无需向 Podman 传递网络名称。但是,您可以使用 podman create 命令创建额外的桥接网络。以下示例展示了如何设置 Web 服务器并将其暴露给主机外部的网络,包括有根和无根的情况。它还将展示外部客户端如何连接到容器。请注意,在上述运行命令中,容器的端口 80(Nginx 服务器正在运行的端口)被映射到了主机的端口 8080(对于有根用户)或 8081(对于无根用户)。选择这些端口是为了演示主机和容器端口如何映射以供外部访问。端口当然也可以根据需要选择其他值。要从外部客户端连接到 Web 服务器,只需将有根用户的 HTTP 客户端指向主机的 IP 地址的 8080 端口,或将无根用户的 HTTP 客户端指向主机的 IP 地址的 8081 端口。

(rootful) $ sudo podman run -dt --name webserver -p 8080:80 quay.io/libpod/banner
00f3440c7576aae2d5b193c40513c29c7964e96bf797cf0cc352c2b68ccbe66a

现在运行容器。

$ podman run -dt --name webserver --net podman1 -p 8081:80 quay.io/libpod/banner
269fd0d6b2c8ed60f2ca41d7beceec2471d72fb9a33aa8ca45b81dc9a0abbb12

请注意,在上述运行命令中,容器的端口 80(Nginx 服务器正在运行的端口)被映射到了主机的端口 8080。选择端口 8080 是为了演示主机和容器端口如何映射以供外部访问。端口当然也可以是 80(对于无根用户除外)。要从外部客户端连接到 Web 服务器,只需将有根用户的 HTTP 客户端指向主机的 IP 地址的 8080 端口,或将无根用户的 HTTP 客户端指向主机的 IP 地址的 8081 端口。

(outside_host): $ curl 192.168.99.109:8080
___ __
/ _ \___ ___/ /_ _ ___ ____
/ ___/ _ \/ _ / ' \/ _ `/ _ \
/_/ \___/\_,_/_/_/_/\_,_/_//_/

(outside_host): $ curl 192.168.99.109:8081
___ __
/ _ \___ ___/ /_ _ ___ ____
/ ___/ _ \/ _ / ' \/ _ `/ _ \
/_/ \___/\_,_/_/_/_/\_,_/_//_/

Macvlan

在 macvlan 的模式下,容器将获得对主机上物理网络接口的访问权限。该接口可以配置多个子接口,每个子接口都可以拥有自己的 MAC 地址和 IP 地址。对于 Podman 容器来说,容器将表现得好像它与主机处于同一网络中。

macvlan_network

在图示中,外部客户端能够直接通过其 IP 地址访问 web 容器。通常,网络信息(包括 IP 地址)是从 DHCP 服务器(如网络上的其他大多数网络客户端)租赁的。如果笔记本电脑运行着防火墙(如 firewalld),则需要为正确访问做出相应的调整。

请注意,要使用 macvlan,必须以 root 用户身份运行 Podman。

示例

以下示例演示了如何在 macvlan 上设置 web 容器,以及如何从主机外部访问该容器。首先,创建 macvlan 网络。您需要知道主机上连接到可路由网络的网络接口。在本例中,它是 eth0。

$ sudo podman network create -d macvlan -o parent=eth0 webnetwork
webnetwork

下一步是确保 DHCP 服务正在运行。这负责处理来自网络的 DHCP 租约。如果不需要 DHCP,则可以在上面的 network create 命令中使用 --subnet 选项分配静态子网。

CNI 和 netavark 都使用自己的 DHCP 服务;因此,您需要知道自己正在使用哪个后端。要查看您正在使用哪个后端,请运行以下命令:

$ sudo podman info --format {{.Host.NetworkBackend}}

如果此命令不起作用,则表示您正在使用 Podman 4.0 之前的旧版本,这意味着您正在使用 CNI。 如果使用 netavark 后端,则至少需要 Podman v4.5 和 netavark v1.6 才能使用 DHCP。

对于 netavark 的使用:

 sudo systemctl enable --now netavark-dhcp-proxy.socket

或者,如果系统不使用 systemd,则手动启动守护进程:

/usr/libexec/podman/netavark dhcp-proxy --activity-timeout 0

对于 CNI 的使用:

 sudo systemctl enable --now cni-dhcp.socket

或者,如果系统不使用 systemd,则手动启动守护进程:

 sudo /usr/libexec/cni/dhcp daemon

请注意,根据发行版的不同,二进制文件的位置可能有所不同。

现在运行容器,并确保将其附加到我们之前创建的网络上。

sudo podman run -dt --name webserver --network webnetwork quay.io/libpod/banner
03d82083c434d7e937fc0b87c25401f46ab5050007df403bf988e25e52c5cc40
sudo podman exec webserver ip address show eth0
2: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state
UP
link/ether 0a:3c:e2:eb:87:0f brd ff:ff:ff:ff:ff:ff
inet 192.168.99.186/24 brd 192.168.99.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::83c:e2ff:feeb:870f/64 scope link
valid_lft forever preferred_lft forever

因为容器具有可路由的 IP 地址(在此网络上),并且不由 firewalld 管理,所以无需更改防火墙设置。

(outside_host): $ curl http://192.168.99.186
___ __
/ _ \___ ___/ /_ _ ___ ____
/ ___/ _ \/ _ / ' \/ _ `/ _ \
/_/ \___/\_,_/_/_/_/\_,_/_//_/

Slirp4netns

Slirp4netns 是无根容器和 Pod 的默认网络设置。它的发明是因为无特权用户不允许在主机上创建网络接口。Slirp4netns 在容器的网络命名空间中创建一个 TAP 设备,并连接到用户模式的 TCP/IP 栈。请考虑以下图示。

slirp_network

这台笔记本电脑上的无特权用户创建了两个容器:一个 DB 容器和一个 web 容器。这两个容器都能够访问笔记本电脑外部网络上的内容。如果容器绑定到主机端口且笔记本电脑防火墙允许,外部客户端也可以访问这些容器。请记住,无特权用户必须使用 1024 到 65535 之间的端口,因为较低的端口需要 root 权限。(CAP_NET_BIND_SERVICE) 注意:这可以通过调整 sysctl net.ipv4.ip_unprivileged_port_start 来实现。

slirp4netns 的一个缺点是容器之间完全隔离。与桥接方法不同,它不存在虚拟网络。为了使容器之间能够相互通信,它们可以使用与主机系统的端口映射,或者可以将它们放入同一个 Pod 中,共享相同的网络命名空间。有关更多信息,请参阅容器与 Pod 之间的通信

示例

以下示例将展示两个无根容器如何相互通信,其中一个容器是 web 服务器。然后,它将展示主机网络上的客户端如何与无根 web 服务器通信。

首先,运行无根 web 服务器,并将容器中的端口 80 映射到非特权端口(如 8080)。

$ podman run -dt --name webserver -p 8080:80 quay.io/libpod/banner
17ea33ccd7f55ff45766b3ec596b990a5f2ba66eb9159cb89748a85dc3cebfe0

由于无根容器无法直接通过 TCP/IP 和 IP 地址相互通信,因此需要使用主机和端口映射。为此,必须知道主机(接口)的 IP 地址。

$ ip address show eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group
default qlen 1000
link/ether 3c:e1:a1:c1:7a:3f brd ff:ff:ff:ff:ff:ff
altname eth0
inet 192.168.99.109/24 brd 192.168.99.255 scope global dynamic noprefixroute eth0
valid_lft 78808sec preferred_lft 78808sec
inet6 fe80::5632:6f10:9e76:c33/64 scope link noprefixroute
valid_lft forever preferred_lft forever

从另一个无根容器中,使用主机的 IP 地址和端口在两个无根容器之间成功通信。

$ podman run -it quay.io/libpod/banner curl http://192.168.99.109:8080
___ __
/ _ \___ ___/ /_ _ ___ ____
/ ___/ _ \/ _ / ' \/ _ `/ _ \
/_/ \___/\_,_/_/_/_/\_,_/_//_/

从主机外部的客户端,也可以使用该 IP 地址和端口:

(outside_host): $ curl http://192.168.99.109:8080
___ __
/ _ \___ ___/ /_ _ ___ ____
/ ___/ _ \/ _ / ' \/ _ `/ _ \
/_/ \___/\_,_/_/_/_/\_,_/_//_/

容器与 Pod 之间的通信

大多数容器用户都清楚容器之间以及容器与外界如何通信。通常,每个容器都有自己的 IP 地址和网络信息。它们使用常规的 TCP/IP 方法(如 IP 地址)或在很多情况下使用基于容器名称的 DNS 名称相互通信。但是,Pod 是由一个或多个容器组成的集合,因此具有一些独特性。

根据定义,Podman Pod 中的所有容器共享相同的网络命名空间。这意味着它们将具有相同的 IP 地址、MAC 地址和端口映射。您可以通过使用 localhost 方便地在 Pod 中的容器之间进行通信。

slirp_network

上面的插图描述了一个桥接网络上的 Pod。如图所示,Pod 中有两个容器:“DB”和“Web”容器。因为它们共享相同的网络命名空间,所以 DB 和 Web 容器可以使用 localhost(127.0.0.1)相互通信。此外,它们都可以通过分配给 Pod 本身的 IP 地址(以及适用的 DNS 名称)进行寻址。

有关容器到容器的网络配置的更多信息,请参阅 使用 Podman 配置容器网络