在 FreeBSD 上组织你的虚拟网络
作者:Michael Gmelin
现代 FreeBSD 提供一系列虚拟化选项,从传统的 jail 环境(与宿主操作系统共享网络栈),到 vnet jail(每个 jail 拥有自己的网络栈),再到运行各自内核/操作系统的 bhyve 虚拟机。根据不同需求,配置虚拟网络有不同的方式。jail 和 VM 管理工具通过抽象(至少部分)底层复杂性,可以简化这一过程。
文档约定
本文基于 FreeBSD 12.1-RELEASE,撰写时为 FreeBSD 最新发行版。假设运行 jail 和 bhyve VM 的宿主机使用 ZFS。
除特别说明外,示例中使用的软件包来自 FreeBSD 2019Q4 季度分支(https://svnweb.freebsd.org/ports/branches/2019Q4/)。使用季度软件包仓库是全新安装的 FreeBSD 系统的默认配置。
为简化说明,所有示例仅涉及 IPv4。
本文介绍各种 jail 和 VM 管理工具,尽管所有展示的内容都可以直接修改 /etc/jail.conf 等系统配置文件手动配置,无需安装任何软件包。
代码和终端交互/输出格式如下。如果你想复制本文中的代码,可以访问 https://blog.grem.de/ayvn。
为清晰起见,示例中有时(并非总是)会展示命令输出。
手册页引用以加粗显示,括号中标注手册章节;如 security(7) 指安全手册页,可输入 man 7 security 或 man security 显示(因无其他同名手册页,可省略章节号)。
FreeBSD 软件包引用以反引号显示,使用 Port 的 origin,即 <分类>/<名称>,如 security/sudo,可作为二进制软件包安装(pkg install sudo),也可从 Ports 安装(cd /usr/ports/security/sudo && make install clean)。
许可证
本作品采用知识共享署名 4.0 国际许可协议(CC BY 4.0)(https://creativecommons.org/licenses/by/4.0/)。
普通 Jail
普通(即非 VNET)jail 与所运行的 jailhost 共享网络栈。因此网络配置和防火墙工作在 jailhost 上完成,不在 jail 内部。这是创建 jail 的传统方式,尽管有局限,但在容器化软件、文件系统和服务方面仍然非常有用。例如 ports-mgmt/poudriere(FreeBSD 的批量软件包构建器和 Port 测试工具)大量使用普通 jail。
使用继承 IP 配置的普通 Jail
最基础的选项是运行一个从 jailhost 继承网络配置(所有接口/IP 地址)的 jail。如果 jail 主要用作容器以保持 jailhost(运行 jail 的宿主机)“干净”无依赖,这是常见做法。这样宿主机只需最少数量的软件包(基础如 security/sudo、shells/bash、sysutils/tmux),而应用 jail 可单独快照、备份、管理/迁移。
使用 sysutils/iocage 配置、运行和销毁一个简单 jail 的示例:
注意:这会继承所有接口并从 jailhost 复制 resolver 配置。
使用 sysutils/pot 做同样示例,它是一个替代的 jail 管理器,旨在提供类容器功能和 sysutils/nomad 集成:
注意:
sysutils/pot是一个演进中的项目,所以你可能想通过修改 /etc/pkg/FreeBSD.conf 从 FreeBSD 的 latest 分支获取版本,或从 Ports 安装(/usr/ports/sysutils/pot)。如果网络接口名不是em0,请确保在 /usr/local/etc/pot/pot.cfg 中设置POT_EXTIF。
使用专用 IP 地址的普通 Jail
如果想要更多隔离,可以分配专用静态 IP 地址。这能防止 jail 监听 jailhost 使用的端口,例如允许通过 ssh(1) 直接连接到监听标准端口(22)的 jail。
下例中,jailhost 使用 192.168.0.2 作为主 IP 地址,192.168.0.1 作为默认网关,192.168.0.3 是新增的、由 jail 使用的附加 IP 地址。目标是在局域网内创建一个使用专用静态 IP 地址的 jail,并在其内运行 sshd(8)。
这假设 jailhost 的安全 shell 守护进程已配置为只监听相关 IP 地址,方法是在 /etc/ssh/sshd_config 中设置 ListenAddress 为 192.168.0.2,并通过 service sshd reload 重新加载。
使用 sysutils/pot 的静态局域网 IP jail 示例:
你可以在供应过程的不同阶段运行 ifconfig(8),了解 IP 别名(192.168.0.33/32)何时被加入/移除到接口。根据用例,将别名永久添加到 jailhost 的 /etc/rc.conf 也可能有意义。
注意:在这种静态单 IP 配置下,jail 的唯一 IP 地址(神奇地)也用作 localhost,这有时会令人困惑,也意味着通常监听 localhost 因此外部无法访问的服务(如上例中的
sendmail_submit)突然暴露在外。所以在这种配置下,对 jailhost 上的服务做正确的防火墙(遵循默认阻止所有流量的最佳实践)很重要。可以为 jail 添加独立的回环地址(如 127.0.0.2/8),但考虑到额外引入的复杂性,这种做法只在少数用例中值得。
使用 VLAN IP 地址的普通 Jail
前一配置的变体是配置 jail 监听专用(私有)网络上的 IP 地址,方法是使用专用接口,或在现有物理接口上添加 VLAN 接口。这提供更好的网络分段,并允许部署中央过滤、出站网络地址转换(NAT)和入站重定向(DNAT)。
下例中,在 jailhost 上(接口 em0)配置了一个 VLAN 接口(VLAN 标签 101,10.1.1.1/24),并使用 sysutils/iocage 在配置好的 jail 中使用:
这假设防火墙和 NAT 在网络中的另一台主机/设备上进行。由于宿主机已配置 IP 地址(10.1.1.1/24),将 jail 的 IP 地址作为别名宿主地址(掩码 /32)添加正是所需。这也有助于配置,允许在 jail 启动前配置静态路由,并基于接口的网络配置设置防火墙规则。
如果 jail 应使用该接口的主 IP 地址,必须准确按照 VLAN 接口上的配置设置 IP 地址和掩码。上例中即设置 ip4_addr="em0.101|10.1.1.1/24"。iocage(8) 足够聪明,如果 jail 的 IP 地址在启动 jail 前已配置,则不会从接口移除。
提示:像大多数网络接口一样,可以语义化命名 VLAN 接口;详见 rc.conf(5)(
man rc.conf)。
使用回环 IP 地址的普通 Jail
在某些情况下,比如可分配给局域网接口的 IP 地址数量有限,或你只维护单一 jailhost,让 jail 监听本地回环接口可能合理。这种情况通常使用本地防火墙做出站 NAT 和入站流量重定向(如有必要)。
下面 sysutils/iocage 示例中,创建了名为 lo1、持有 IP 地址 172.31.255.17/32 的专用本地接口来服务 jail。本例中 IP 地址由 jailhost 在引导时分配,而不是由 sysutils/iocage 启动 jail 时分配:
为公共流量添加出站 NAT
此时 jail 存在但无法与外部世界通信。可以通过向防火墙添加出站 NAT 规则解决。
警告:启用/配置防火墙是把自己锁在机器外的好办法。确保你已有方案,万一意外发生能重新获得访问权限。
本例使用 pf(4)(packet filter)防火墙。假设尚未配置防火墙。
创建以下 /etc/pf.conf:
接下来启用 pf(4) 并验证出站连接按预期工作:
运行服务并将流量重定向到它
现在 jail 内有出站连接,让我们安装 www/nginx 提供静态内容。所需防火墙规则已就位(检查之前配置的 rdr 和 pass 规则):
此时从外部访问 jail 内托管的 web 服务器应该可用,可通过浏览器指向服务器的主 IP 地址验证。
警告:虽然可以用这种方式创建干净易懂的配置,但这种设置扩展性不佳,维护起来麻烦。而且虽然能在各 jail 之间做一定程度的防火墙,但不太实用,因此 VNET jail 更受推荐。
VNET Jail 和 bhyve VM
VNET(9)——网络子系统虚拟化基础设施——是一种虚拟化网络栈的技术。
虽然 VNET 最早出现在 FreeBSD 8.0 中,但直到最近都被视为实验特性。随着 FreeBSD 12 的到来,终于人人可用,无需构建自定义内核。
应用到 jail 时,VNET 让每个 jail 拥有自己的网络栈。这解决了之前看到的一些不足,带来了更好的隔离和分割、“正常”的本地回环接口,以及在 jail 内运行独立防火墙的能力。
使用 sysutils/pot 的 VNET Jail
本节演示如何使用 sysutils/pot 创建多个 VNET jail。在演示之前,先撤销前面 pf(4) 的配置(如果你没运行这些示例且 pf(4) 未运行,请将 service pf reload 替换为 service pf start):
当创建网络类型为 public-bridge 的 jail 时,pot 自动使用 VNET。基于 /usr/local/etc/pot/pot.cfg 中的“内部虚拟网络配置”,它创建桥接接口并分配一个 IP 地址作为 jail 的默认网关。
创建 jail 时,pot 从配置的范围自动分配静态 IP 地址。jail 启动时,会创建一个 epair(4) 接口,并将其“a 端”加入桥接:
epair(4) 接口的“b 端”由 jail 使用,jail 按其 /etc/rc.conf 中的配置设置静态 IP 地址:
使用 /etc/pf.conf 中配置的 anchor,pot 向 pf(4) 添加 NAT 规则,可通过 pfctl(8) 查看:
像前面普通 jail 重定向示例那样,我们希望其中一个 VNET jail 运行 web 服务器,并通过入站重定向(DNAT)对外提供。在第一个 jail 内安装 www/nginx,并使用 pot 的“export-port”功能创建入站重定向:
pot 在 pf(4) 中创建包含导出端口重定向 pass 规则的 anchor,同样可通过 pfctl(8) 查看:
注意:此时 pf(4) 仅用于 NAT 和重定向流量,不阻止任何流量。
使用 sysutils/iocage 的 VNET Jail
相比 sysutils/pot,sysutils/iocage 在创建自定义配置时提供更大灵活性和细粒度控制。
管理桥接
iocage(8) 期望用户自行管理 VNET jail 连接的桥接,所以通常通过 ifconfig(8) 和修改 /etc/rc.conf 手动完成。
另一种管理桥接的方式是使用 sysutils/vm-bhyve,它将桥接抽象为“虚拟交换机”。由于 vm-bhyve(8) 是管理 bhyve VM 的出色工具,我们利用它管理 iocage(8) jail 的桥接,之后将 bhyve VM 连接到它。
注意:vm-bhyve(8) 支持不同交换机类型。我们暂时限于默认的“standard”。
运行 rcorder /usr/local/etc/rc.d/* 可看到 vm 在系统启动时先于 iocage 执行。这意味着 jail 连接时桥接已可用。
理清这点后,我们创建第一个交换机:
并将新的 iocage(8) jail 连接到它:
为让 jail 能与外界通信,我们添加最简配置为其做出站 NAT(假设 pf(4) 尚未运行):
加入 bhyve VM 和 DHCP
上例创建了名为“services”的虚拟交换机,由桥接接口“vm-services”支撑,连接了一个名为“vnetcage”的 VNET jail,它使用 NAT 与外界通信。
下一步将一台运行 FreeBSD 的 bhyve VM 加入同一虚拟交换机,在网络 10.1.1.0/24 上使用不同 IP 地址。
网络图:
为简化供应过程,我们让 bhyve VM 通过 DHCP 获取 IP 地址。使用 dns/dnsmasq 实现这一目的:
我们前面已安装 sysutils/vm-bhyve;剩下的工作是下载安装 ISO、创建 VM、连接到交换机并运行安装程序:
这使用 bootonly ISO(基于网络的安装),应该可行,因为 IP 地址由 dnsmasq(8)(同时提供 DNS 服务)分配给客户机,且 NAT 已在宿主防火墙上配置。
注意:这显示两个网络接口。
vtnet1是我们设置中要使用的接口。可通过修改 VM 配置文件 /zroot/vms/guest/guest.conf 手动移除vtnet0。
如预期,dnsmasq(8) 从 DHCP 池给新 VM 分配了一个 IP 地址(本例中为 10.1.1.11/24)。在需要稳定 IP 的环境中,基于虚拟网络接口的 MAC 地址在 /usr/local/etc/dnsmasq.conf 中分配固定 IP(不属动态池)更有利,例如:
在新 VM 中添加非特权用户并启用 sshd(8) 后,我们就能 ssh(1) 进去:
注意:通过将
/dev/bpf暴露给 VNET jail,可以像此处 VM 那样用 DHCP 配置 jail 的 IP 地址。在 iocage(8) 中启用dhcp属性即可轻松实现。
阻止 VNET Jail/VM 之间的流量
阻止 jail 和 VM 之间流量的简单方法是在桥接成员上设置 private 标志(ifconfig <bridgename> private <interfacename>)。
任何标记为 private 的接口都无法与同样标记为 private 的其他接口通信。
sysutils/vm-bhyve 在 VM 启动时,如果交换机已通过运行 vm switch private <switchname> on 配置为 private,会自动设置 private 标志。
遗憾的是,这只对 VM 有效,所以如果将 iocage jail 连接到交换机,必须在每次 jail 启动时手动设置 private 标志。
当前配置中,“vnetcage”可以 ssh 到“guest”:
将交换机切换为 private 模式后,VM 连接到桥接的 tap 接口被标记为 PRIVATE。由于 jail 的 epair 接口未标记 PRIVATE,流量仍可流通,ssh 仍可用:
让我们手动将 jail 的连接改为“private”,可以看到连接尝试 jail 时超时,而到外界的 NAT 仍然完好,jailhost 仍能通过 ssh(1) 连接到 VM:
理想情况下,iocage(8) 应支持一个配置选项,允许在桥接成员上自动设置 private 标志。在该功能出现之前,下面的脚本可配置为通过 jail 的 poststart 钩子执行,达到(几乎)同样的效果。
/usr/local/sbin/set_ioc_vnet_private.sh:
脚本以 jail 名称为必需参数,可选 vnet 接口(iocage 内部编号)。后者默认为“vnet0”。
现在重启“vnetcage”jail,检查桥接成员配置,然后修改其配置以使用新脚本,再次重启并比较结果桥接配置:
如你所见,桥接成员现在在 jail 启动时正确配置为 private。
注意:这个方案不完美,因为在 jail 启动到运行
exec_poststart命令之间的短时间内流量是可能的。
VNET Jail/VM 内的防火墙
bhyve VM 内的防火墙很直接——只需运行 VM 内操作系统自带的宿主防火墙。
jail 内运行防火墙稍复杂。ipfw(8) 是推荐选项,但 pf(4) 此时也应可用。为不在本文引入更多语法,我们这里介绍后者,尽管 iocage 文档另有推荐。
要在 VNET jail 内运行 pf(4),必须加载 pf(4) 内核模块,并暴露多个设备。方法是在 /etc/devfs.rules 中添加新规则集:
并应用到 jail:
注意:由于 iocage(8) 的一个 bug,每次 jail 停止时配置的 devfs(8) 规则会从 devfs(8) 中移除。未来版本可能会修复;目前有一个补丁可用(
https://github.com/iocage/iocage/pull/1106)可解决该问题。
使用最简防火墙配置测试——检查阻止特定 IP 地址是否有效,以及状态是否如预期创建:
注意:同一桥接上的 jail/VM 可设置/盗用 IP、更改 MAC 地址,所以这层保护只足以防止不必要/意外的流量发生。如需更严格的安全,可在 jailhost 上启用 ipfw(8) 的二层过滤,并在宿主桥接和 jail 的 epair 接口之间插入额外的桥接。这种配置的细节超出本文范围。
Jail 和 VM 是虚拟化技术,为资源供应增加了大量灵活性和敏捷性。
VXLAN
VXLAN(虚拟可扩展局域网接口)是一种隧道协议,旨在将虚拟网络与底层物理网络解耦,从而简化自动化和编排。它通过将二层以太网帧封装到三层 IP/UDP 包中实现。可以把 VXLAN 看作多租户数据中心版的 VLAN。就像 VLAN 使用唯一 VLAN 标签,VXLAN 使用 VXLAN 网络标识符(VNI)——VXLAN 头部中的 24 位值——区分网段。
VXLAN 示例概览
示例设置包含同一局域网上的三台主机:一台网关主机(局域网 IP 192.168.0.1),以及两台 jailhost(“jailhost-a”和“jailhost-b”),局域网 IP 地址分别为 192.168.0.10 和 192.168.0.20。jailhost 托管 VNET jail 和 VM;“jailhost-b”还托管一个普通 jail。
三台主机通过两个 VXLAN(VXLAN id 111 和 222)连接;网关主机通过专用上行链路使用 NAT 提供互联网访问。
Jail 和 VM 在两个 VXLAN 上获取 IP 地址(网络 10.0.111.0/24 和 10.0.222.0/24)。
本例中,许多配置通过重启相关机器来应用。虽然这主要是为简洁起见,但作为副作用也重启测试了设置——本就需要做这件事。所有描述的操作也可不重启完成。
网络图:
本例使用多播模式。VXLAN 也可配置为单播模式(详见 vxlan(4))。
网关配置
网关主机使用简单的 ipfw(8)/natd(8) 配置和两个 vxlan(4) 接口。假设上行链路已配置好。
防火墙、NAT 和 IP 转发:
VXLAN 接口:
到 VXLAN 多播地址的静态路由:
Jailhost-a
“jailhost-a”托管一台 VM 和一个 VNET jail,通过交换机(桥接接口)连接到 VXLAN 接口,VXLAN 接口再使用物理接口 igb0 在局域网传输封装流量。
网络配置(jailhost-a)
创建 VXLAN 接口:
为多播流量设置静态路由(如果默认路由经过同一接口,技术上不需要):
创建交换机(桥接接口)连接 jail/VM,并将相应 VXLAN 接口加入它们:
VM 配置(jailhost-a)
安装 sysutils/vm-bhyve 并创建一个“manual”类型(不由 vm-bhyve 管理)的交换机,引用前面创建的桥接接口(switch111):
下载 FreeBSD ISO 并安装名为“guest-a”的 VM;让新 VM 在引导时启动:
启动 VM 并测试连接(注意公共流量如何通过 VXLAN 111 发送并在网关主机 NAT,因为网关主机配置为默认网关):
Jail 配置(jailhost-a)
安装 sysutils/iocage:
注意:你可能想通过修改 /etc/fstab 永久挂载 fdescfs(5)。
在 VXLAN 222 上创建名为“vnetjail-a”的 jail(配置为引导时启动,因此创建后立即启动):
进入 jail 并测试连接:
最后测试重启后一切是否正常启动。
Jailhost-b
“jailhost-b”的设置与“jailhost-a”类似,但网络对调(VM 在 VXLAN 222,jail 在 VXLAN 111)。此外,它托管一个普通 jail,使用 VXLAN 接口(vxlan111)上的别名地址直接访问,但不以该网络作为默认网关。在生产环境中,这个 jail 可用于容纳底层 jailhost 提供的支持性服务。
网络配置(jailhost-b)
创建 VXLAN 接口:
为多播流量设置静态路由(如果默认路由经过同一接口,技术上不需要):
普通 Jail 配置(jailhost-b)
安装 sysutils/iocage:
注意:你可能想通过修改 /etc/fstab 永久挂载 fdescfs(5)。
在别名地址 10.0.111.21 上创建一个普通(非 VNET)jail。配置为引导时启动,因此创建后立即启动:
运行 jail 并测试连接:
注意:“plainjail-b”这种配置下,其公共流量不会通过 VXLAN 发送并由网关主机 NAT。此设置是有意的;否则会用 VNET jail。
网络交换机设置(jailhost-b)
创建交换机(桥接接口)连接 jail/VM,并将相应 VXLAN 接口加入它们:
VNET Jail 配置(jailhost-b)
在 VXLAN 111 上创建名为“vnetjail-b”的 jail(配置为引导时启动,因此创建后立即启动):
进入 jail 并测试连接:
VM 配置(jailhost-b)
安装 sysutils/vm-bhyve 并创建一个“manual”类型(不由 vm-bhyve 管理)的交换机,引用前面创建的桥接接口(switch222):
下载 FreeBSD ISO 并安装名为“guest-b”的 VM;让新 VM 在引导时启动:
启动 VM 并测试连接(注意公共流量如何通过 VXLAN 222 发送并在网关主机 NAT,因为网关主机配置为默认网关):
最后测试重启后一切是否正常启动:
VXLAN 多播故障排查
有多种工具可帮助排查 VXLAN 设置。
使用 ifconfig(8) 确认接口确实正确配置:
注意:MTU 设为 1450(因为 VXLAN 使用 50 字节头部信息)。如果所有组件都支持,建议在网络中配置 jumbo frames。
使用 sockstat(1) 确认主机确实监听标准 VXLAN 端口 4789:
使用 ifmcstat(8) 验证多播配置合理:
使用 tcpdump(1) 检查 VXLAN 流量:
使用 arp(8) 检查 ARP 表(在 VM 内也可):
使用 sysctl(8) 检查 VXLAN 转发表:
清空 ARP 和 VXLAN 转发表:
如果环境允许,故障排查期间临时禁用主机防火墙,确保它们不干扰流量。
结论与延伸阅读
本文意在通过示例展示 FreeBSD 虚拟化特性的各种网络配置方式。有意选择使用 Ports/软件包中的第三方工具,因为许多用户在实践中会尝试这些工具起步。即使特定工具不会进入生产环境,原型阶段使用它们更易实验、感知已配置的内容,再构建更精简的方案。
所示示例绝非穷举,但应有助于读者入门,找出哪种配置可能满足其具体需求。建议进一步阅读以充分理解可用选项及其影响。 •
Michael Gmelin 在巴伐利亚乡村长大,90 年代在一家乡村的 userfriendly.org 风格 ISP 开始使用 FreeBSD。本世纪头十年大部分时间在开发软件以自动化 ISP 和网络运营商流程,接近十年末开始为项目贡献。后来投身新兴金融科技行业构建支付系统,最终在 2014 年成为 FreeBSD committer。他用多种语言写过软件,最近喜欢上 Rust。业余时间他喜欢玩经典电子游戏和运行 demoscene 作品。离开键盘时,他享受(制作)音乐、骑行、木工和烹饪。Perl 在他心里永远有一席之地。
一些主题信息的优秀来源
Michael W Lucas —— FreeBSD Mastery: Jails(
https://mwl.io/nonfiction/os#fmjail)John Nielsen —— Using VXLAN to network virtual machines, jails, and other fun things on FreeBSD(
https://www.bsdcan.org/2016/schedule/events/715.en.html)幻灯片(
https://www.bsdcan.org/2016/schedule/attachments/341_VXLAN_BSDCan2016.pdf)视频(
https://www.youtube.com/watch?v=_1Ne_TgF3MQ)
iocage: A FreeBSD Jail Manager(
https://iocage.readthedocs.io)vm-bhyve: Shell based, minimal dependency bhyve manager(
https://github.com/churchers/vm-bhyve)pot: another container framework for FreeBSD, based on jails, ZFS and pf(
https://github.com/pizzamig/pot)FreeBSD as a Host with bhyve(
https://www.freebsd.org/doc/handbook/virtualization-host-bhyve.html),涵盖直接使用局域地址设置 bhyve。FreeBSD ifconfig(8) man page(
https://www.freebsd.org/cgi/man.cgi?query=ifconfig&sektion=8)FreeBSD bridge(4) man page(
https://www.freebsd.org/cgi/man.cgi?query=bridge&sektion=4)FreeBSD vxlan(4) man page(
https://www.freebsd.org/cgi/man.cgi?query=vxlan&sektion=4)
最后更新于