在 AIO 主机上使用 Proxmox VE 重建 Home Lab 记录

Posted by

on

前言

朋友送了一台畅网 X86-P5 N305 的设备(以下简称“本设备”)过来让我玩耍。

正好我有想要换掉 NanoPi R2S,并在家里托管一些服务(例如本博客)的想法,于是最近对内网进行了改造。

架构图 TLDR

N305 AIO Server Architecture

最后图中的 2.5G 交换机被拔掉以省电。改用 “MT7621 OpenWrt AP” 同时兼任 VLAN 交换机的使命。

本次需求

  • 仅使用这一台设备实现 “主路由” ,“隧道旁路由”,“容器/虚拟机服务器” 等所有目的,即所谓的 AIO(All In One) Server
  • 主路由系统为 OpenWrt,要求虚拟化带来的损失最小化
  • 原有 NanoPi R2S 方案中,iEdon-Net 内网路由用隧道等均集中在一个 OpenWrt 主路由当中,这造成了接口众多,管理混乱的局面,本次希望按照目标分离虚拟机,使其各司其职
  • 因为 GreenCloud VPS 屡次爆炸丢失数据,本次将 Acorle 及 本 WordPress 博客由一台专门的虚拟机托管,好久没有玩 Windows Server 了,正好用一用 IIS
  • 家里购置了 Sharp 的空气净化器。本设备提供了 IoT 功能,出于对日本 IT 的极度不信任,需要划分一个独立的 VLAN 及一个独立的 SSID 专门给访客及这类不受信任的设备提供互联网访问能力
  • 多线接入(IoT 网采用 open.ad.jp 提供的免费 PPPoE 出口,服务器网段采用 SoftBank IPoE 的准静态 IP 出口,自用网段采用 SoftBank 的 PPPoE 出口)

重要内容记录

主 OpenWrt LXC 的配置

本次的 OpenWrt 主路由采用了 PVE 提供的 LXC 功能。由于是容器,有必要暴露一些宿主机的设备文件给容器,以提供我所需的功能。这里我从  网络上  摘抄并使用了以下配置。

# /etc/pve/lxc/100.conf
arch: amd64
cores: 8
hostname: OpenWrt
memory: 512
nameserver: fd42:4242:2189:ac76::1
onboot: 1
ostype: unmanaged
rootfs: local-lvm:vm-100-disk-0,size=256M
swap: 0
lxc.include: /etc/lxc-openwrt.conf
lxc.net.0.type: phys
lxc.net.0.link: enp1s0
lxc.net.0.name: eth0
lxc.net.0.flags: up
lxc.net.1.type: phys
lxc.net.1.link: enp2s0
lxc.net.1.name: eth1
lxc.net.1.flags: up
lxc.net.2.type: veth
lxc.net.2.link: vmbr0
lxc.net.2.name: server0
lxc.net.2.flags: up

# /etc/lxc-openwrt.conf
# /path/to/openwrt.common.conf
# LXC OpenWrt config, derived from /usr/share/lxc/config/openwrt.common.conf
# Default console settings
lxc.tty.dir = lxc
lxc.tty.max = 4
lxc.pty.max = 1024

# Default capabilities
lxc.cap.drop = mac_admin
lxc.cap.drop = mac_override
lxc.cap.drop = sys_admin
lxc.cap.drop = sys_module
lxc.cap.drop = sys_nice
lxc.cap.drop = sys_pacct
lxc.cap.drop = sys_ptrace
lxc.cap.drop = sys_rawio
lxc.cap.drop = sys_resource
lxc.cap.drop = sys_time
lxc.cap.drop = sys_tty_config
lxc.cap.drop = syslog
lxc.cap.drop = wake_alarm

lxc.apparmor.profile = unconfined
lxc.apparmor.allow_nesting = 1

# Default cgroups - all denied except those whitelisted
lxc.cgroup2.devices.deny = a
## /dev/null and zero
lxc.cgroup2.devices.allow = c 1:3 rwm
lxc.cgroup2.devices.allow = c 1:5 rwm
## consoles
lxc.cgroup2.devices.allow = c 5:0 rwm
lxc.cgroup2.devices.allow = c 5:1 rwm
## /dev/{,u}random
lxc.cgroup2.devices.allow = c 1:8 rwm
lxc.cgroup2.devices.allow = c 1:9 rwm
## /dev/pts/*
lxc.cgroup2.devices.allow = c 5:2 rwm
lxc.cgroup2.devices.allow = c 136:* rwm
## rtc
lxc.cgroup2.devices.allow = c 254:0 rm
## tun
lxc.cgroup2.devices.allow = c 10:200 rwm
## dev/tty0
lxc.cgroup2.devices.allow = c 4:0 rwm
## dev/tty1
lxc.cgroup2.devices.allow = c 4:1 rwm
## /dev/ppp 
lxc.cgroup2.devices.allow= c 108:0 rwm

## To use loop devices, copy the following line to the container's
## configuration file (uncommented).
#lxc.cgroup2.devices.allow = b 7:* rwm

# Blacklist some syscalls which are not safe in privileged
# containers
lxc.seccomp.profile = /usr/share/lxc/config/common.seccomp


lxc.mount.entry = /dev/ppp dev/ppp none bind,create=file
lxc.mount.entry = tmp tmp tmpfs rw,nodev,relatime,mode=1777 0 0
lxc.mount.auto = cgroup:rw
lxc.mount.auto = proc:rw
lxc.mount.auto = sys:rw
lxc.autodev = 1

本方案暴露了 “enp1s0” 及 “enp2s0” 接口给 OpenWrt LXC,并使其做到类似 PCIe 直通的物理独占效果。后者的 “vmbr0” 则提供了 PVE 宿主机及其他 LXC / VM 之间的网络访问专用段。

多出口情况下的内网与出口配置

Linux 下的多出口一般采用 ip rules 实现。

以前,我高度依赖 “mwan3″ 软件包。但是,”mwan3” 自身的设计并不是很满足我的需求,也容易与其它使用场景产生冲突(如我遇到的,与 BIRD 一起使用,会造成 mwan3 扫描内核路由表的时候耗时大量时间)。

后来,我开始手写脚本来实现 ip rule 的添加。虽然能很好实现分流,但彼时的 OpenWrt 并没有提供类似的图形操作界面,每次版本升级特别麻烦,需要手动搬运脚本。好在本次,23.05 版本带来了 IPv6 NAT,ip rule 的图形化操作功能(“Networking” -> “Routing” -> “IPv4(6) Rules”)。

在 /etc/iproute2/rt_tables 中创建好三个出口的专用路由表,便可以在 OpenWrt 中操作。

本次参考了 这篇文章,让不同出口的网段的设备可以访问内网的特定路由——前缀抑制功能。

 这里需要使用一个 “前缀抑制” 功能,目标是使该规则不匹配指定路由表中的指定前缀。在刚刚新建的规则之前,再新建一个规则,让指定的主机先匹配 mian 路由表,然后设置前缀抑制为 0,那么其意思就是不匹配 main 表中默认路由,但是匹配其他路由条目,若这些路由条目都没有匹配中,那么则命中下一条规则,即匹配 4837 表。 —— 《OpenWrt 下的策略路由》

OpenWrt main routing table Prefix suppressor

如图所示,图中 “server” 区域的设备进站流量的路由匹配,首先查阅 “main” 表,看看是否命中内网路由条目。如果是,直接送出数据包。如果否,由于屏蔽了 “/0” 的默认路由,导致 “main” 表没有匹配,则继续向下执行第二条规则,查阅 “ipoe” 表,命中 “ipoe” 表中的默认路由,送出数据包。

这两条规则组合实现了 “server” 区域的主机,既可以访问内网网段,又从 “ipoe” 表中设定的专用出口的默认路由出口上网。

而 “iot” 区域绝对不能与内网有任何交集,一概送入 “untrustpub” 路由表 —— 顾名思义,不信任的公用出口。

主路由的软 VLAN

与 Linux 类似,直接使用 “if.<VLAN ID>” 即可。如:eth0.10。即代表 eth0 接口上的 VLAN 10 的虚拟接口。

防火墙的配置

需要阻止 IoT 专网与内网任何其他网段的通信。仅允许与 WAN 区域通信。

OpenWrt IoT VLAN Firewall Configuration

旁路由的配置

旁路由主要用于组网,它用于收纳一系列隧道接口。旁路由通过 BIRD 的 OSPF,将这些内网路由传递给主路由上的 BIRD。内网机器访问其他内网网段时,首先路由到主路由,主路由将其指引到旁路由上,通过隧道出口。

虽然这个设计显得性能略微低效,但有效地避免了将过多接口收纳在一个主路由上的尴尬情况。

LXC Debian 中 sshd 更改端口号后无法正常反映

这是配置旁路由时遇到的唯一一个比较头疼的问题。好在 这则讨论 提供了解决方案。

systemctl mask ssh.socket
systemctl mask sshd.socket

systemctl disable sshd
systemctl enable ssh

就可以了。

LXC 中 nano 等工具中非英文内容乱码

locales 需要重新配置。这则讨论 提供了解决方案。

localectl set-locale LANG=en_US.UTF-8
set LC_ALL=en_US.UTF-8

locale-gen “en_US.UTF-8”
reboot

就可以了。

旁路由 LXC 加载 WireGuard

本次,旁路由也是 LXC。由于容器共享宿主机内核,所以需要宿主机在启动时加载 wireguard 模块——在 “/etc/modules” 中追加 “wireguard” 即可。

由于 WireGuard 依赖 TUN/TAP 设备,所以也自然需要将宿主机的 /dev/tun 暴露给 LXC。在 LXC 的配置文件中追加 “lxc.cgroup2.devices.allow: c 10:200 rwm” 即可。

本 LXC 可以是 Unprivileged,开启 Nest 功能即可正常使用。

无线 AP 的配置

从 Mercari 收了一台二手 MT7621 设备。1400 日元。参照 这篇文章 刷好同样的 OpenWrt 23.05 系统。

本设备起 AP + Managed Switch 之功能。按照如下图配置了 VLAN。LAN4 连接主路由(类似 Trunk 口)。

OpenWrt 23.05 VLAN Configuration

同样利用 “接口名.VLAN ID” 的方法划分好区域,用来让无线接口附加。

VLAN on br-lan interface

OpenWrt Wireless FW Zone Attach

由于新版 OpenWrt (23.05) 此时的硬件交换机实现已经改用了 DSA。本次 VLAN 划分参考了官方论坛的 这则讨论,及官方教程的 这篇关于 DSA 的文章

服务器的配置

网络总算是构建好了,现在开始老本行——Windows Server 的虚拟机。终于不是 LXC 了。

用 IIS PHP FastCGI 跑 WordPress 是已经玩了十几年的老招。

兼容 WP Super Cache 插件的 IIS 用的 URL 重写规则

该插件能自动为 Apache 生成 URL 重写所需的 “.htaccess” 文件。但不太适用于 nginx 和 IIS。网传的适配版 IIS 规则不是最新就是有遗漏,本次自己整理了一份。整体的 “web.config” 看起来是这样:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
    <handlers>
        <add name="PHP FastCGI" path="*.php" verb="*" modules="FastCgiModule" scriptProcessor="C:\ServerFiles\PHP\php-cgi.exe" resourceType="File" />
    </handlers>
    <defaultDocument>
        <files>
            <clear />
            <add value="index.html" />
            <add value="index.htm" />
            <add value="index.php" />
        </files>
    </defaultDocument>
    <httpProtocol>
        <customHeaders>
            <remove name="Vary"></remove>
            <add name="Vary" value="Accept-Encoding"></add>
        </customHeaders>
    </httpProtocol>
    <staticContent>
        <clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
    </staticContent>
    <rewrite>
        <rules>
            <rule name="WP super cache Windows WordPress" enabled="true" stopProcessing="true"> 
                <match url="(.*)" /> 
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_METHOD}" pattern="^POST$" negate="true" />
                    <add input="{QUERY_STRING}" pattern=".*=.*" negate="true" />
                    <add input="{HTTP_COOKIE}" pattern="^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$" negate="true" />
                    <add input="{HTTPS}" pattern="on" />
                    <add input="{DOCUMENT_ROOT}/wp-content/cache/supercache/{HTTP_HOST}/{R:1}index-https.html" matchType="IsFile" /> 
                </conditions> 
                <action type="Rewrite" url="wp-content/cache/supercache/{HTTP_HOST}/{R:1}index-https.html" /> 
            </rule>
            <!-- End WP Super Cache -->
            <rule name="WordPress Basic" enabled="true" patternSyntax="Wildcard" stopProcessing="false">
                <match url="*" />
                <conditions>
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="index.php" />
            </rule>
        </rules>
    </rewrite>
    </system.webServer>
</configuration>

IIS 改用 CloudFlare 的 IP 及环境变量

这两条规则首先将把 Cloudflare 传递过来的真实客户端 IP 覆盖到服务器变量 “REMOTE_ADDR” 和 “REMOTE_HOST”。

其次,由于 IIS 作为后端,并不终结 HTTPS 流量,但后端 PHP 业务代码高度依赖 “HTTPS” 环境变量来判断通讯是否是安全的,此时我们将 “HTTPS” 环境变量覆盖为 Cloudflare 传递过来的 schema。

<rule name="Set CF IPs to local variables">
    <match url=".*" />
    <conditions>
        <add input="{HTTP_CF_CONNECTING_IP}" pattern="(.|\s)*\S(.|\s)*" />
    </conditions>
    <serverVariables>
        <set name="REMOTE_ADDR" value="{C:0}" />
        <set name="REMOTE_HOST" value="{C:0}" />
        <set name="REMOTE_PORT" value="0" />
    </serverVariables>
    <action type="None" />
</rule>
<rule name="Set HTTPs on">
    <match url=".*" />
    <conditions>
        <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" />
    </conditions>
    <serverVariables>
        <set name="HTTPS" value="on" />
    </serverVariables>
    <action type="None" />
</rule>

由于修改了重要的服务器变量,需要在根配置下的 URL Rewrite 配置中,添加允许。
IIS Override Server Variables

整体规则一览

IIS Cloudflare URL Rewrite Rules

上传文件时,图片文件的 “HTTP Error 500.50 – URL Rewrite Module Error” 错误

参考 这篇文章,我们可以得知,PHP 接收上传文件时,将文件首先放到 OS 的 Temp 目录下,待接收完成后,移动到 WordPress 目录中。

由于 Temp 目录没有 IIS 所需要的 “IIS_IUSR” 用户权限,导致上述整体规则一览中的最后一条命中时,无法判断 “REQUEST_FILENAME” 是文件还是目录(即文件访问被拒绝),导致了 500 错误。

解决方法可以将 OS 的 Temp 目录添加 “IIS_IUSR” 的读写权限,但这可能不太安全。可以修改 “php.ini” 中的 “upload_tmp_dir”,指定一个自己设置好权限的文件夹。这样可以解决此问题。

参考

  1. PVE里用LXC容器部署OpenWrt, https://zenofhacking.com/zh/posts/openwrt-from-kvm-to-lxc/
  2. OpenWrt 下的策略路由, https://blog.kuretru.com/posts/fc5a70d/
  3. SSH doesn’t work as expected in LXC, https://forum.proxmox.com/threads/ssh-doesnt-work-as-expected-in-lxc.54691/page-2#post-451843
  4. WHR-1166DHP2 を openwrt-18.06インストール&リカバーリー, https://f0573.blogspot.com/2018/08/whr-1166dhp2-openwrt-18.html
  5. Mini tutorial for DSA network config, https://forum.openwrt.org/t/mini-tutorial-for-dsa-network-config/96998
  6. DSA Mini-Tutorial, https://openwrt.org/docs/guide-user/network/dsa/dsa-mini-tutorial
  7. IIS WordPress images 500 error, https://www.amixa.com/blog/2011/04/29/iis-wordpress-images-500-error/

2 responses to “在 AIO 主机上使用 Proxmox VE 重建 Home Lab 记录”

  1. openwrt下无线性能和原厂固件相比会有损失吗
    坐等BPi R4的WiFi模块发布(x

    1. 我记得是看情况的,只要不要覆盖掉原厂调优后的那个分区,应该问题不大
      我这次升级应该是没有动到这个分区,信号很强
      不过话说回来,1K 的小屋子 5G 怎么都覆盖满了 😣

Leave a Reply

Your email address will not be published. Required fields are marked *