0%

记一次使用macvlan给容器分配独立IP

起因

最近用上了iKuai的分流后,一直没什么问题,直到有一天查看了OpenClash的连接信息发现PT站的请求还是被先分到了旁路由,反正都是直连,我当然希望直接从主路由就直接出去而不是先到我的旁路由里面绕一圈。于是开始了尝试。

排查

iKuai分流里面是有域名分流的策略的,而我也确确实实把PT站点的域名写了进去并配置了直接从WAN1口出去,不要走WAN2口(旁路由)。但是ikuai的域名分流并不可靠,尽管我按照官方要求的填写了对应的多线路DNS之后,仍然是偶尔成功偶尔失败,明明上午traceroute的时候没有走旁路由,结果下午再试发现又绕道旁路由了。在官方论坛发了帖也没人回应,Google了半天也没答案,似乎用这种分流方式的人比较少。

解决

没办法,iKuai域名分流支持得确实比较烂,预期指望官方后续更新能解决域名分流不正常的问题还不如另辟蹊径。PT比较忌讳的主要是做种汇报的时候是走了梯子,浏览网页理论上不影响。那么就好办了,只要把PT的做种进程分离开来就行了。因为我是用的Docker部署Qbitorrent和Transmission,所以我接下来只讨论Docker下的解决方案。

映射端口

因为宿主机还是需要按需分流的,所以我们不能将整个IP都归到直连去,我们这里需要缩小范围。首先我们可以将容器的端口映射出来,我用的是docker-compose编排,所以这里写一个docker-compose.yaml的示例:

1
2
3
4
5
6
7
8
services:
qb:
image: qbitorrent
container_name: qb
restart: always
ports:
- 8080:8080
# ...

将端口映射到宿主机后,再到iKuai的端口分流里面设置好IP和端口,绑定到主路由出口网口即可:

ikuai端口分流截图

可以到qbitorrent容器中traceroute检查流量是否流向正确:

端口映射traceroute检查流向是否正确

macvlan给容器分配独立IP

端口映射的话还要担心端口冲突,那么有没有不用端口映射的方法呢?当然有,答案就是macvlan。由于篇幅限制,macvlan的原理这里不进行讨论,只给出配置方式。

创建macvlan虚拟网络

首先我们需要在docker中创建macvlan:

1
2
3
4
5
6
7
8
9
10
11
12
docker network create -d macvlan \
# 你的网段
--subnet=10.0.0.0/24 \
# 你的网关
--gateway=10.0.0.254 \
# IPv6网段,如不需要可以去掉
--subnet=fd00::/80 \
# IPv6网关,如不需要可以去掉
--gateway=fd00::1 \
--ipv6 \
# parent是你的实体网卡,可以ip addr查看,最后的my-macvlan就是macvlan的网络名,后面会用到
-o parent=enp6s18 my-macvlan

创建好macvlan之后,需要在docker-compose.yaml中使用:

1
2
3
4
5
6
7
8
9
10
11
services:
qb:
image: qbitorrent
container_name: qb
restart: always
networks:
my-macvlan:
# 给容器分配IP地址,注意不要跟已有地址冲突
ipv4_address: 10.0.0.88
#ports:
# - 8080:8080

这样简单两步我们就已经创建好macvlan并用上啦。

分配IP的容器与宿主机互通

因为我nginx也是docker的容器,但是我nginx不想分配IP地址,所以用的是宿主机的网络,然后qbitorrent改了macvlan后我发现nginx的反向代理失效了,nginx连不上qbittorrent容器了,也就是说宿主机连不上使用了macvlan的容器了。这是因为在 macvlan 模式下,出于安全考虑,默认禁止宿主机与容器的通信,也就是宿主机无法访问以及反代容器。但是局域网中的其他设备可以访问到容器。但 macvlan 之间可以通信,因此可以通过新建一个macvlan,然后修改路由,使得主机 <-> 容器变为主机 <-> 虚拟接口 <-> 容器,就可以打通容器与宿主机之间的通信。

新建macvlan

首先需要创建名为my-macvlan2的虚拟网卡,连接到你的物理网卡上:(我这里是enp6s18,请按实际情况修改网卡名)

1
ip link add my-macvlan2 link enp6s18 type macvlan mode bridge

然后设置该虚拟网卡的IP,需要在上面创建的macvlan网段内,否则无法互通:

1
ip addr add 10.0.0.99 dev my-macvlan2

然后启动网卡:

1
ip link set my-macvlan2 up

最后把容器IP加进来做路由即可:

1
2
ip route add 10.0.0.88 dev my-macvlan2
ip route add 10.0.0.89 dev my-macvlan2

创建systemd开机自启

网卡和路由规则会在重启之后消失,所以需要进行持久化配置,我这里选择了使用systemd。

首先把上面的命令写到脚本中,我这里放在/usr/local/bin/docker-macvlan.sh中(不要忘记chmod +x):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash

# macvlan 接口名称[可修改]
MACVLAN_INTERFACE="my-macvlan2"
# 桥接网口[需修改]
PARENT_INTERFACE="enp6s18"
# macvlan 接口 IP [需修改]
IP_ADDRESS="10.0.0.99/24"
# 所有路由列表 [需修改]
ROUTES=("10.0.0.88" "10.0.0.89")

start_macvlan() {
ip link add $MACVLAN_INTERFACE link $PARENT_INTERFACE type macvlan mode bridge
ip addr add $IP_ADDRESS dev $MACVLAN_INTERFACE
ip link set $MACVLAN_INTERFACE up
for route in "${ROUTES[@]}"; do
ip route add $route dev $MACVLAN_INTERFACE
done
}

stop_macvlan() {
for route in "${ROUTES[@]}"; do
ip route del $route dev $MACVLAN_INTERFACE || true
done
ip link set $MACVLAN_INTERFACE down || true
ip addr del $IP_ADDRESS dev $MACVLAN_INTERFACE || true
ip link del $MACVLAN_INTERFACE || true
}

case "$1" in
start)
start_macvlan
;;
stop)
stop_macvlan
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac

然后创建/etc/systemd/system/docker-macvlan.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Setup macvlan shim for docker to allow to route to host
BindsTo=docker.service
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-macvlan.sh start
ExecStop=/usr/local/bin/docker-macvlan.sh stop
RemainAfterExit=yes
Restart=no

[Install]
WantedBy=multi-user.target

然后开机自启动:

1
2
sudo systemctl daemon-reload
sudo systemctl enable docker-macvlan.service

创建端口分流规则

IP分配好之后,我们只需要到端口分流中新建对应的规则即可(如果有多个IP则这里填入多个IP即可):

macvlan模式ikuai端口分流规则

到qbitorrent容器中traceroute检查流量是否流向正确:

端口映射traceroute检查流向是否正确

结束

ikuai免费版没有wireguard,分流功能似乎也蛮坑的,然后还天天弹窗让我登录,接下来就把你换掉!

ikuai首次登录弹窗