利用 cgroup 在一台服务器方便地使用多公网 IP

创建于 2991 / 约需 14 分钟

本文距离上次更新已经超过 1000 天。因此,其中的信息可能已经过时。


在有些案例下(多数是类似爬虫的需求),用户会需要使用一台被分配多个公网 IP 的设备。本文提供一种利用 systemd 和 cgroups v2 方便地同时使用多个公网 IP 作为出口的方法。

本文使用 AWS 的 EC2 和 Ubuntu 18.04 作为样例。

原理

EC2 中,每个公网 IP 对应一个内网 IP。从不同内网 IP 发出的数据包将会通过不同的公网 IP 发出。系统的默认路由通常会使所有数据包的源 IP 都是首先获得的内网 IP,这带来的结果就是第二个 IP 不会被使用。为了使用不同的 IP 地址,我们只需要修改出站数据包的源 IP,这可以用 iptables 方便地实现。至于如何让 iptables 判断需要修改哪些出站数据包,就看 iptables 的筛选规则配置如何了。这里我们使用用起来比较方便的 cgroups 模块进行筛选。

方法

在 AWS 给一台 EC2 服务器分配第二个公网 IP

我们知道,新配置的 EC2 服务器默认有一个内网 IP 和一个公网 IP:

Newly created EC2 server has only one internal/external IP

Newly created EC2 server has only one internal/external IP

为了获取并配置新的公网 IP,我们需要:

  • 给这台服务器配置第二个内网 IP (对于 IPv4,即上图中的 "Secondary private IPv4 addresses")
  • 获取一个弹性公网 IP
  • 将第二个内网 IP 和这个弹性公网 IP 绑定
  • 在服务器配置新的内网 IP

那么我们开始:

配置第二个内网 IP

我们先需要知道这台服务器的网卡 ID(不过,如果只开个一台服务器就不必特意记下来了):

Interface ID of the EC2 server

Interface ID of the EC2 server

在左侧的 "Network & Security" > "Network Interfaces" 找到这个 ID 的网卡,点击右上方 "Actions" > "Manage IP addresses",展开 eth0,点击 "Assign new IP address" 来分配一个新内网 IP,点击右下角的 "Save" 保存。保存后,新的内网 IP 会出现在之前写着 "Auto-assign" 的空位。记下这个内网 IP 地址。

Interface page

Interface page

The newe internal IP

The newe internal IP

获取一个弹性公网 IP

在左侧的 "Network & Security" > "Elastic IPs",点击右上方的 "Allocate Elastic IP address",获取一个新的弹性 IP。

注意,弹性 IP 通常会产生费用。

绑定内网 IP 和公网 IP

选择新拿到的弹性 IP,点击 "Actions" > "Associate Elastic IP address",在新界面选择你要分配 IP 地址的实例和新获取的内网 IP,点击下方的 "Associate" 即可。

Associating elastic IP

Associating elastic IP

为了方便演示起见,我们创建两个 slice,使在它们之中运行的进程分别使用两个公网 IP 地址。我们假设:

  • 第一组:
    • AWS 分配的第一个内网 IP:172.20.0.1
    • AWS 分配的第一个公网 IP:3.4.5.6
    • 设置的 cgroup 名字:a1.slice
  • 第二组:
    • 我们获取的第二个内网 IP:172.21.0.1
    • 我们获取的弹性公网 IP:223.4.5.6
    • 设置的 cgroup 名字:a2.slice

在服务器配置新的内网 IP

我们还需要让服务器知道新的内网 IP。给它的 eth0 接口(其它服务商可能不同)添加 IP:

sudo ip addr add 172.21.0.1/20 dev eth0
sh

这里填你的新内网 IP (经常以 172 开头),后面的 /x 建议按照服务器已有 IP 的相应配置设定。

如何知道 /x 该填多少?

在服务器运行 ip addr,可以看到类似这样的内容:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.x.x.x/20 brd 172.x.x.x scope global dynamic eth0
(...)
text

这里就可以看到服务器自动获取的 IP 在这里的设置是 /20。新的 IP 一般也按这个设置就好。

创建 cgroup

临时使用的话,用 systemd-run 通过在 cgroup 中运行程序,动态创建 cgroup 即可:

# 以 uid=1000 启动 shell,顺便创建 slice:
sudo systemd-run --slice a1.slice --scope --uid 1000 -S
# 如果是直接运行程序的话:
# sudo systemd-run --slice a1.slice --scope --uid 1000 /bin/bash

# 还有 a2.slice!
sudo systemd-run --slice a2.slice --scope --uid 1000 -S
sh

我们有两个公网 IP 可用。在实际应用中,可以为它们各创建一个 cgroup,也可以只为第二个创建一个(因为第一个默认就是使用的)。

在 cgroup 创建之后,通过 systemd-cgtopsystemd-cgls 可以看到新创建的 cgroup。

如果希望通过系统配置来在开机时自动创建 cgroup 的话,可以在 /etc/systemd/system 下创建 <name>.slice 文件。具体格式可以参考 system.slicesystemd.resource-control 的文档。

在下一步开始之前,记得确认 cgroup 已经创建,否则 iptables 会报错。

配置 iptables

我们要做的事情是修改从不同 slice 发出的数据包的源地址。看过上次透明代理那篇文的读者大概对下面的 iptables 命令感到有一点熟悉:

# 发往本地地址的不修改源地址
sudo iptables -t nat -A POSTROUTING -m addrtype --dst-type LOCAL -j RETURN

sudo iptables -t nat -A POSTROUTING -m cgroup --path "a1.slice" -j SNAT --to-source 172.20.0.1
sudo iptables -t nat -A POSTROUTING -m cgroup --path "a2.slice" -j SNAT --to-source 172.21.0.1
sh

完成!之后在两个 slice 中运行的程序就会使用各自设置的 IP 作为出口了。

结语


LIKE 本文

Webmention 回应

本文暂无回应。