一台家用多用途服务器的设计和配置 | Drown in Codes

一台家用多用途服务器的设计和配置 | Drown in Codes

需求

一台用于非IT专业人士家的小服务器需要运行诸如DNS分流和代理之类的软件供内网使用它处于Full Cone NAT之后并且需要能被远程管理和重新配置它应当不发出噪音并且尽量节约用电

设备选型

考虑到这台设备上会跑一些网络应用我需要一个比较能用的CPU加上多于一个的以太网端口在淘宝上购买了某家专门做无风扇small form factor机器厂的产品拆掉里面不靠谱的垃圾配件组成了以下配置

  • Intel Celeron J1900
  • 自己换的8G三星内存
  • HP拆机的Intel S3610 200G存储Hypervisor和关键应用数据
  • Samsung 860 EVO mSATA 256G存储非关键应用数据
  • 两个Intel I211网卡
  • 12V 3A外置电源

其实本来想选择一个支持ECC内存的CPU的但是这个价格上确实也不好找遂放弃J1900让我不满的另一点是不支持SR-IOV这样机子上自带的一块Intel 5100 Wi-Fi网卡就算是废了只能把它拆掉了不用的端口例如COM和音频输入输出口可以用相应的防尘塞堵上拆掉网卡以后露出来的天线口用黑色的电工胶布贴了一下防止进灰然后买了一个VGA的假负载用于绕过一个固件bug

固件配置

这台设备因为是深圳小作坊出品其UEFI固件基本上是Aptio提供给Bay Trail设备的参考固件设置非常全面一个都没隐藏固件版本为GBYT9 V1.02坑有几个

  • 使用UEFI GOP的时候HDMI输出不可用只能用VGA
  • 如果没有连接显示器固件会死机无法启动系统
  • 如果设备上连接了多个USB键盘只有第一个被枚举的能在固件中使用因此尽量不要在机子上插没用的键盘或者无线接收器之类的玩意

固件里面需要修改的设置不多

  • 关掉Legacy Option ROM然后需要重启一下
  • 关掉CSM
  • 打开Secure BootESXi 6.7支持Secure Boot和TPM2.0了虽然没有vCenter的话TPM用不上
  • 在南桥设置中设置掉电后自动开机
  • 在北桥设置中把显卡的睿频关掉显存分配全部设为最小

Hypervisor配置

考虑到设备后期扩展和维护的需要这台服务器上会运行一个Hypervisor所有应用软件放进虚拟机那么常见的免费Hypervisor有这么几个选项

  • Microsoft Hyper-V Server
  • Proxmox VE
  • VMWare ESXi

Proxmox VE的不稳定和设计糟糕是所有用过的人都明白的Microsoft Hyper-V Server在非Active Directory域环境中很不好使远程管理配置困难命令行体验也非常糟糕最后我决定用ESXi相对来讲比较轻量级对各种操作系统的支持都比较好远程管理处于能用水平并且免费版提供的功能对家用环境来说也够用

VMWare ESXi安装

很幸运Intel I211网卡是ESXi 6.7原生驱动支持的网卡如果系统上没有ESXi自带驱动的网卡的话你就得去第三方网站上找找有没有相应的驱动了

安装过程感觉没什么好讲的安装向导一步步走完就是了唯一需要注意的是ESXi默认会把管理网所在的vmk0挂到第一个枚举到的网卡下面对于多网卡设备可能需要试一下具体是哪个网卡为了方便区分我打印了一张贴纸贴在机器外壳上

在我这个配置的机子上全新安装的ESXi 6.7启动时间约2分钟

另外ESXi 6.7 Build 13006603及之前的版本好像和Chrome 73及以上版本有一些兼容性问题如果ESXi网页打不开可以试一下Firefox或者其它非Chrome内核的浏览器

内核参数

我这台设备比较诡异安装程序启动和安装完成第一次重启的时候需要在bootloader界面按Shift+O在内核参数的末尾添加ignoreHeadless=TRUE否则启动内核的时候会卡住

第一次启动成功后通过console shell或者SSH把这个参数存入配置文件

  1. esxcfg-advcfg –set-kernel “TRUE” ignoreHeadless

然后重启一下确认系统能在无用户干预情况下自动启动

主机名

前往Networking -> TCP/IP stacks -> Default TCP/IP stack -> Edit settings在Basic DNS configuration里面选择Manually configure the settings for this TCP/IP stack在Host name一项中输入你想要的主机名应用配置后再把设置改回Use DHCP DNS这样网络中的DHCP设置会继续生效但是主机名已经改成你自己设置的了

如果你考虑给ESXi加Active Directory域的话建议主机名长度不要超过15字符不然和NetBIOS协议会有兼容性问题

NTP

为了保证在中国大陆的可用性设置了以下NTP服务器

设置完以后需要手工启动一下NTP服务同步需要过几分钟才会完成不需着急

第二块硬盘

前往Storage -> Datastores新建一个datastore即可也没有什么可以说的

SFCBD

启用SFCBDSmall Footprint CIM Broker Daemon我们才能读到一些硬件的健康数据不过这个比较看机型我这台机子上就基本上读不到有效信息通过console shell或者SSH执行以下命令

  1. esxcli system wbem set –enable true

用户

如果有需要的话前往Host -> Manage -> Security & users -> Users创建用户需要注意的是ESXi默认会禁止最近一段时间登录失败次数过多的用户登录所以如果你只有默认的root用户并且机子还暴露在公网上的话很可能因为有人爆破密码而把你自己关在外面因此建议至少创建一个用户名不常见的用户

虚拟机配置

我们只有8GiB内存硬件会占用大概0.11GiBESXi本身会吃掉1.22GiB如果打开swap会稍微少一点那么满打满算剩下来的还有6.67GiB在这点内存里面要塞进大量功能那么每一点都不能浪费FlexVPN之类的方案虽然非常好用但是实在是太吃内存暂时不予考虑

远程管理方案1RouterOS

选择RouterOS的唯一原因是它能只用256MiB内存启动并且有各种路由功能非常省资源如果应急需要用它来当路由器它也能撑一阵子RouterOS的缺点是bug多而且修bug比较慢除去应急时可以用的路由功能不谈RouterOS的OpenVPN可以提供一个同地区不同站点之间稳定的站到站连接

因为这台虚拟机是作为远程管理用的它不应当依赖任何当前站点的服务

安装RouterOS

前往官网下载最新的Cloud Hosted Router的VMDK镜像传到ESXi上然后把VMDK转成ESXi支持的flat格式

  1. [root@localhost:/vmfs/volumes/system/RouterOS] vmkfstools -i chr-6.43.14.vmdk chr-6.43.14-new.vmdk
  2. Destination disk format: VMFS zeroedthick
  3. Cloning disk ‘chr-6.43.14.vmdk’
  4. Clone: 100% done.

这样会生成两个文件一个结尾有flat这个文件在网页管理面板上看不到一个没有这两个文件组成了整个flat格式的VMDK如果觉得在ESXi上操作麻烦qemu-img也可以做相应的格式转换文件创建出来以后就不能重命名否则会从系统中消失

接下来只要正常创建一个Other Linux 3.x (64bit)类型的虚拟机挂载现有硬盘启动就可以了

配置网络

启用ipv6包重启一下然后在ether1上配一个DHCP客户端就完事了

  1. /ip dhcp-client
  2. add disabled=no interface=ether1

DNS和NTP服务器分别配置成公共服务不要依赖本地服务

  1. /ip dns
  2. set query-total-timeout=20s servers=
  3. 114.114.114.114,114.114.115.115,223.5.5.5,223.6.6.6,1.2.4.8,210.2.4.8
  4. /system ntp client
  5. set enabled=yes server-dns-names=ntp.felixc.at,time.asia.apple.com

设置远程管理

创建一个OpenVPN客户端连接到我的服务器上

自动更新

虽然RouterOS的更新没那么靠谱但是如果你在long-term发行通道上的话鉴于它漏洞那么多还是自动更新比较好

  1. /system script
  2. add dont-require-permissions=no name=update owner=admin policy=
  3. ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=”/sy
  4. stem package updater
  5. ncheck-for-updates oncer
  6. n:delay 1s;r
  7. n:if ( [get status] = “New version is available”) do={ install }”
  8. /system scheduler
  9. add interval=1d name=autoupdate on-event=“/system script run update” policy=
  10. ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
  11. start-date=apr/28/2019 start-time=04:00:00

远程管理方案2Linux + ZeroTier One

从节约资源的角度考虑我选择了Debian 9分配了2C/512MiB/32GB的资源另外限制了30000IOPS经验表明在ESXi上一旦你的Guest OS在疯狂IO比如不小心开了swap然后不小心用完了内存很有可能因为IO速度而把ESXi自己也一起拖死因此一定要根据你的硬盘实际能力对IOPS做相应的限制

安装系统

需要安装standard system utilities + SSH server然后关掉swap

/etc/dhcp/dhclient.conf里写上

  1. prepend domain-name-servers 114.114.114.114,223.5.5.5,223.6.6.6,114.114.115.115,1.2.4.8,210.2.4.8;

/etc/systemd/timesyncd.conf里写上

设置一下sysctl

  1. kernel.sysrq=1
  2. kernel.panic=10

安装VMWare Tools

  1. sudo apt install open-vm-tools

配置ZeroTier One

  1. sudo apt install curl
  2. curl -s https://raw.githubusercontent.com/zerotier/download.zerotier.com/master/htdocs/contact%40zerotier.com.gpg | gpg –import &&
  3. if z=$(curl -s https://install.zerotier.com/ | gpg); then echo “$z” | sudo bash; fi
  4. sudo zerotier-cli join xxxx
  5. sudo zerotier-cli set xxxx allowGlobal=1

检查资源占用

  1. $ free -wh
  2. total used free shared buffers cache available
  3. Mem: 473M 107M 126M 2.4M 13M 225M 351M
  4. Swap: 0B 0B 0B

应用容器Linux + Docker

这一台虚拟机给了2C/2GiB/64GB安装系统和初始配置的方法和上一台Linux虚拟机类似不再赘述

代理网关

TCP部分可以在iptables的nat表上做REDIRECTUDP部分可以使用iptable/netfilter的TPROXY功能实现TPROXY需要Linux内核版本4.18以上所以我在这台机子上用了内核版本4.19的Debian Buster实现很简单

首先打开系统的转发功能注意持久化

  1. sysctl net.ipv4.ip_forward=1
  2. sysctl net.ipv6.conf.all.forwarding=1

TCP这边的话简单设置所有公网流量重定向到代理所在端口

  1. iptables -t nat -N PROXY
  2. iptables -t nat -A PROXY -d 0.0.0.0/8 -j RETURN
  3. iptables -t nat -A PROXY -d 10.0.0.0/8 -j RETURN
  4. iptables -t nat -A PROXY -d 100.64.0.0/10 -j RETURN
  5. iptables -t nat -A PROXY -d 127.0.0.0/8 -j RETURN
  6. iptables -t nat -A PROXY -d 169.254.0.0/16 -j RETURN
  7. iptables -t nat -A PROXY -d 172.16.0.0/12 -j RETURN
  8. iptables -t nat -A PROXY -d 192.168.0.0/16 -j RETURN
  9. iptables -t nat -A PROXY -d 224.0.0.0/4 -j RETURN
  10. iptables -t nat -A PROXY -d 240.0.0.0/4 -j RETURN
  11. # exclude docker
  12. iptables -t nat -A PROXYCORE -s 172.17.0.0/16 -j RETURN
  13. iptables -t nat -A POSTROUTING -o ens192 -j MASQUERADE
  14. iptables -t nat -A PROXY -p tcp -j REDIRECT –to-ports 1919
  15. iptables -t nat -A PREROUTING -p tcp -j PROXY
  16. ip rule add fwmark 0x01 lookup 100
  17. ip route add local 0.0.0.0/0 dev lo table 100
  18. ip6tables -t nat -N PROXY
  19. ip6tables -t nat -A PROXY -p tcp -d 2000::/3 -j REDIRECT –to-ports 1919
  20. ip6tables -t nat -A PREROUTING -p tcp -j PROXY
  21. ip –6 rule add fwmark 0x01 lookup 100
  22. ip –6 route add local default dev lo table 100

UDP则是使用TPROXY规则在mangle上拦截一下

  1. iptables -t mangle -N PROXY_UDP
  2. iptables -t mangle -A PROXY_UDP -d 0.0.0.0/8 -j RETURN
  3. iptables -t mangle -A PROXY_UDP -d 10.0.0.0/8 -j RETURN
  4. iptables -t mangle -A PROXY_UDP -d 100.64.0.0/10 -j RETURN
  5. iptables -t mangle -A PROXY_UDP -d 127.0.0.0/8 -j RETURN
  6. iptables -t mangle -A PROXY_UDP -d 169.254.0.0/16 -j RETURN
  7. iptables -t mangle -A PROXY_UDP -d 172.16.0.0/12 -j RETURN
  8. iptables -t mangle -A PROXY_UDP -d 192.168.0.0/16 -j RETURN
  9. iptables -t mangle -A PROXY_UDP -d 224.0.0.0/4 -j RETURN
  10. iptables -t mangle -A PROXY_UDP -d 240.0.0.0/4 -j RETURN
  11. iptables -t mangle -A PROXY_UDP -p udp -j TPROXY –on-port 1919 –tproxy-mark 0x01/0x01
  12. iptables -t mangle -A PROXY_UDP -p udp -j MARK –set-mark 1
  13. iptables -t mangle -A PREROUTING -j PROXY_UDP
  14. ip6tables -t mangle -N PROXY_UDP
  15. ip6tables -t mangle -A PROXY_UDP -p udp -d 2000::/3 -j TPROXY –on-port 1919 –tproxy-mark 0x01/0x01
  16. ip6tables -t mangle -A PREROUTING -p udp -j PROXY_UDP

然后分别在1919端口上启动相应代理程序的TCP和UDP监听即可需要注意的是代理程序自己的流量不能走这条规则那么就要自行想办法做区分调整iptables规则的时候要注意一下不要把Docker的默认NAT规则清掉了另外如果你的IPv6是SLAAC配置的话给用户下发网关可能比较麻烦需要自己想一下办法

DNS分流dnsdist-autoconf + Pi-Hole

我们首先需要安装DockerDocker有一个大坑就是它默认会把iptables的forwarding chain的默认规则改成DROP这样你的路由器莫名其妙就坏了我们需要配置Docker不要没事折腾iptables

  1. $ cat /etc/docker/daemon.json
  2. {
  3. “iptables”: false
  4. }

配置完以后重启一下Docker daemon这样配置有安全风险请自行考量

安装dnsdist-autoconf很简单写一个基础配置

  1. # global config
  2. # if an error is encountered, stop
  3. quit_on_error = false
  4. listen = [
  5. “0.0.0.0:53”,
  6. “[::]:53”,
  7. ]
  8. # default upstream
  9. upstreams = [
  10. “202.141.162.123:53”,
  11. “182.254.242.15:53”,
  12. “208.67.222.222:443”,
  13. “208.67.220.220:443”,
  14. ]
  15. allowed_client_subnets = [
  16. # everywhere – potentially unsafe, use with caution
  17. “0.0.0.0/0”,
  18. “::/0”,
  19. ]
  20. # set to true if you use Active Directory and allows DNS update
  21. allow_ddns_updates = false
  22. # EDNS0 Client Subnet
  23. [ecs]
  24. enabled = true
  25. default_prefix_v4 = 24
  26. default_prefix_v6 = 48
  27. # if DNS request source IP is not a public routable IP, still forward its ECS information to upstream
  28. keep_private_ip = false
  29. #[control_socket]
  30. #listen = “0.0.0.0”
  31. #key = “”
  32. #[web_server]
  33. #listen = “127.0.0.1:8083”
  34. #password = “supersecretpassword”
  35. #api_key = “supersecretAPIkey”
  36. # enable cache
  37. # note: it eats memory
  38. [cache]
  39. enabled = true
  40. max_entries = 16384
  41. # Rules
  42. [[match]]
  43. domains = [
  44. music.httpdns.c.163.com,
  45. zzhc.vnet.cn,
  46. ]
  47. action = “block”
  48. [[match]]
  49. provider = “dnsmasq-china-list”
  50. upstreams = [
  51. “223.5.5.5:53”,
  52. “223.6.6.6:53”,
  53. “114.114.114.114:53”,
  54. “114.114.115.115:53”,
  55. ]

然后写一个简单的systemd服务来启动它

  1. [Unit]
  2. Description=Auto configured DNS loadbalancer in Docker
  3. Requires=docker.service
  4. Conflicts=systemd-resolved.service,dnsdist.service
  5. [Service]
  6. ExecStartPre=-/usr/bin/docker kill dnsdist-autoconf_1
  7. ExecStartPre=-/usr/bin/docker rm dnsdist-autoconf_1
  8. ExecStart=/usr/bin/docker run –rm –name=dnsdist-autoconf_1 –memory=512m -p=127.0.0.1:5302:53/udp -p=127.0.0.1:5302:53/tcp -p=8083:80/tcp -v=/etc/dnsdist:/etc/dnsdist –dns=114.114.114.114 –dns=223.5.5.5 jamesits/dnsdist-autoconf:1.3.7
  9. ExecStop=/usr/bin/docker stop dnsdist-autoconf_1
  10. ExecReload=/usr/bin/docker restart dnsdist-autoconf_1
  11. TimeoutStartSec=infinity
  12. [Install]
  13. WantedBy=multi-user.target

注意这里监听了5302端口因为我们之后会用Pi-Hole来间接使用它)

安装Pi-Hole继续用Docker+systemd

  1. [Unit]
  2. Description=Pi-Hole
  3. Requires=docker.service
  4. [Service]
  5. ExecStartPre=-/usr/bin/docker kill pihole
  6. ExecStartPre=-/usr/bin/docker rm pihole
  7. ExecStart=/usr/bin/docker run –rm –name=pihole –memory=512m -v /etc/pihole:/etc/pihole -v /etc/dnsmasq.d:/etc/dnsmasq.d -e TZ=“Asia/Hongkong” -e WEBPASSWORD=“password” -e ServerIP=192.168.1.2 -e DNS1=“127.0.0.1#5302” -e DNS2=“127.0.0.1#5301” –dns=127.0.0.1 –dns=114.114.114.114 –dns=223.5.5.5 –dns 114.114.115.115 –dns 223.6.6.6 –net=host –cap-add=NET_ADMIN pihole/pihole:4.2.21
  8. ExecStop=/usr/bin/docker stop pihole
  9. ExecReload=/usr/bin/docker restart pihole
  10. TimeoutStartSec=infinity
  11. [Install]
  12. WantedBy=multi-user.target

注意这玩意设计很糟糕需要硬编码本机IP地址上游DNS必须写在环境变量里不能用网页设置我这里设了用dnsdist-autoconf开出来的localhost:5302和用另一个fallback程序开出来的localhost:5301

检查资源占用

  1. $ free -wh
  2. total used free shared buffers cache available
  3. Mem: 1.9G 440M 1.1G 5.8M 17M 382M 1.4G
  4. Swap: 0B 0B 0B

测试

  • Hypervisor能够在不插键盘和显示器的情况下正常启动
  • 虚拟机能在Hypervisor启动之后自动启动
  • 虚拟机中各个应用程序工作正常并且不依赖虚拟机的启动顺序
  • 所有远程访问方式能够正常工作

清理工作

  • 关掉不必要的Hypervisor服务console shellSSH
  • 给所有虚拟机做个snapshot

On site安装工作

  • 网关虚拟机需要固定IP
  • Pi-Hole需要设置网关IP

参考