背景

我有一个非常简单的需求:在一个容器里起一个 wireguard client,并将其直接接入上游网络(不要 NAT)。

不想看过程可以直接看总结

折腾

在容器里起 wireguard

首先搜索 prior art,发现 https://hub.docker.com/r/linuxserver/wireguard。试了一下发现不好:

  • 这货是基于 ghcr.io/linuxserver/baseimage-ubuntu:bionic 的,巨大。
  • 已经巨大了,它还安装 build-essential 等一堆东西。用完还不删。
  • 它需要 SYS_MODULE 以及 -v /lib/modules:/lib/modules 较为恐怖。我已配备新款内核自带有关模块。
  • 它需要就地编译 wireguard,需要内核头文件。我用的正规系统它却找不到相关包裹。
  • wg-quick 走到 net.ipv4.conf.all.src_valid_mark=1 那步的时候由于没有权限会失败(尽管有关 sysctl 已经满足)。
  • 自带了一个我不要的 coredns

于是研究了一下,发现这事在配备新款内核的 host 上其实很简单:

1
2
3
4
5
FROM alpine:3

RUN apk update
RUN apk add --no-cache wireguard-tools
RUN sed -E -i 's/^(\s*)(.*sysctl -q net.ipv4.conf.all.src_valid_mark=1)/\1# \2/' /usr/bin/wg-quick

build 完 image 总共 16 MB,而 linuxserver/wireguard 有 400 MB+。

然后带上 --cap-add=NET_ADMIN --sysctl=net.ipv4.conf.all.src_valid_mark=1 起起来,发现好用。ping 不好使,可以加 --cap-add=NET_RAW 解决。

podman-compose:cap_add 不好使

发现好用后很快写了一个 docker-compose.yamlpodman-compose 起,发现不好。wg-quick 在创建 link 的时候会坏:RTNETLINK answers: **Operation not permitted**

怎么会这样呢???

搜索了一番发现 podman-compose #239,表示 cap_add / cap_drop 根本不管用。作者给出了相关代码的链接(由于没有锚定到相关 commit 现已刻舟求剑),原来 podman-compose 其实是个单文件 python 脚本,把 docker-compose.yaml 翻译成命令行。

于是打算去打 log 看看发生了什么,一看发现相关代码不存在。再一看发现 podman-compose 在 2019-09-03 和 2021-11-14 之间都没往 PyPI 上 release 过。pip3 install --upgrade podman-compose 解决。

podman-compose:podman network exists 不存在

升级完以后 podman-compose 开始调用 podman network exists。然而好像没有这个命令。apt upgrade 一下发现已经是最新版本 3.0.1。于是 blame 发现 podman-compose 从 1.0.2 开始才用上这个东西,遂回滚至 0.1.10。然而回滚后 podman-compose 并没有用上什么替代品,而是根本就不创建网络了。原来创建网络上个月才被 podman-compose 支持。

于是尝试往前滚 podmandebian/stable 里最新的是 3.0.1,于是上 debian/unstable,升级到 3.4.3,解决。

podman-compose 不支持 macvlan

升级完 podman 后 podman-compose 不报错了,但是起来的容器获得的还是一个 bridge network。从标准输出看根本没有 macvlan。

怎么办呢?

据了解,podman 对 docker-compose 的支持分两支:

  • podman-compose:将 docker-compose.yaml 翻译为 podman 命令。
  • podman system service:提供与 docker 兼容的 RESTful service,可配合 docker-compose 使用。

于是安排后者。

network create only supports the bridge driver

用上 podman system service 后 docker-composeERROR: network create only supports the bridge driver。至少比 podman-compose 不报错偷偷偷懒强了。

搜一下, 发现 rootless 不支持 macvlan。于是加 sudo,然而还是不管用。

在 GitHub 上搜索未发现该报错信息。于是试图了解一下 podman system service 是个啥。

首先代码都在 containers/podman 这个仓库里。观察根目录,猜测 cli 相关代码应当在 /cmd 中,于是可以依次发现:

  • /cmd/podman/system/service.go
  • /cmd/podman/system/service_abi.go
  • /pkg/api/server/server.go
  • /pkg/api/server/register_networks.go
  • /pkg/api/handlers/compat/networks.go

诶好像到这该干的都干了,没人打印这个错误信息啊。不过有了先前的经验,容易猜测可能又是版本问题。确实,这个报错信息在 2021-09-15 的 85e8fbf 中被移除了,而这个版本至今未被包含在任何 release 中。

于是从 HEAD 编译一个,解决。

cannot set network aliases for network “…” because dns is disabled: invalid argument

用上来自 HEAD 的 podman,再来。这次报错:

1
container create: cannot set network aliases for network "..." because dns is disabled: invalid argument

好在现在我们站在他头上,可以轻松搜索到它来自 /libpod/runtime_ctr.go/lib/networking_linux.go。可以发现 dns enablement 是 network 本身的属性,而 alias 是 network 在一个 service 里的别名。

尝试在 docker-compose.yaml 里放置 aliases: [],无果。似乎是 docker-compose 会主动加一个 alias。那我们把 dns 打开吧。观察 compose 的 spec 然而根本没有这个选项。

那我们暂时不用 compose 配网,手动 create network(带 dns)然后用 compose 起 service,期待后续版本能添加相关支持。这次报:

1
compose.cli.main.main: Network "..." needs to be recreated - option "parent" has changed

诶明明我已经设置了 parent 了呀。

podman network inspect 可以观察到 "master": "eth0",显然 podman 是理解相关配置的。再看 curl -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/v1.40/networks/... | jq,网卡不见了……似乎是 /pkg/api/handlers/compat/networks.go 里没有做相关转换。

这下需要改代码了,累了,算了。既然这事没法 rootless 就先用 docker 好了。

总结

  • 在配备新版内核的宿主机里起 wireguard 容器十分简单,alpine 里直接 apk add wireguard-tools 即可。
  • podman-compose 19-21 之间有两年半时间没发布 PyPI 更新。遇到问题先更新一下。
  • podman-compose 目前还不支持很多东西。
  • podman-compose 可以用 podman system service 和 docker-compose 替代。由于这个 service 无需长期运行,我们可以写一个脚本来启动 service 并运行 docker-compose。
  • 无论是 podman-compose 还是 podman system service,目前的 HEAD 版本都不能很好的支持 macvlan / ipvlan。
  • GitHub 搜索不覆盖历史版本。搜不到可能是代码被改了。
  • 纯的 rootless 容器不支持 macvlan / ipvlan。lxc-user-nic 也许可以解决这个问题但那就不全是 rootless 了。