Homelab搭建实录(二)——网络、备份与服务配置设计

前言

岁末年初,匆匆忙忙,年终决算更是没了半条命。近期,用于Homelab中网络服务的小主机突然烧了主板,所幸硬盘未受波及,否则至少要丢掉一周的数据。将各种服务还原恢复后,想起自从Homelab 搭建实录(一)—— 需求分析及对应解决方案后,有段时间没写系列文章了。因此这里借一个相对闲暇的周末,向各位读者介绍下站长的现有设备、网络划分、应用存储以及如此设计对应的简单灾难恢复逻辑和缺点。

设备列表

基于存算分离的考虑,站长主要选用了两台小主机(Proxmox VE),一台NAS主机(Truenas Scale)和一台交换机来打造Homelab。本文所有网络设计、应用服务配置方案均围绕以上设备进行,具体来说:

  • 小主机A以稳定为主,负载相对较低,所有数据都存在本地硬盘,主要用于保障路由服务、堡垒机、内网穿透、VPN等基础性服务多数情况下可用,以便快速接入内网使用各服务,或在需要时进行远程排障。

  • 小主机B以性能为主,负载相对较高,主要用于托管各类Docker应用、Gitlab及配套的CI/CD执行虚拟机、音频转文字等AI模型等开销较大的服务,其中Docker应用和部分虚拟机的磁盘通过SMB挂载到Truenas Scale上(不采用块存储远程挂载虚拟机磁盘,主要是以文件进行存储便于备份和迁移)。

  • 交换机置于稳定型小主机与高效能型小主机中间,主要用于优化处理大量的同网段数据交换请求(例如:Docker应用挂载的远程卷读写、虚拟机磁盘读写等),减轻稳定型小主机的路由压力。

  • NAS主机安装Truenas Scale系统,仅启用了SMB共享功能、配置了文件复制任务,尽量减少可能导致服务不稳定的因素。这一主机除了为站长的零星文件提供存储外,就是为高效能型小主机中的Docker存储卷和虚拟机磁盘提供远程数据存储服务。

整体架构情况

网络设计

为保障内网的整体安全性和数据隐私性,站长选择通过RouterOS和交换机划分VLAN,以便隔离部分不受信任的服务或设备。总体而言,站长将整套内网划分为三个部分:普通网(VLAN 10)、核心网(VLAN 20)、机密网(VLAN30),详情如下:

  • 普通网(VLAN 10):该部分能为不受信任或未经授权的设备提供互联网连接,并允许其中设备访问少量通过反向代理映射的自部署服务。通过无线网络或有线连接均可加入对应子网,由DHCP自动分配IP,不设其他准入限制。适用于登录微信、QQ、百度网盘等国产软件的虚拟机,手机,未经授权的笔记本电脑,扫地机器人等IoT设备。

  • 核心网(VLAN 20):该部分能为白名单内的设备提供互联网连接,并允许其中设备访问除财务管理、扫描件管理外的大多数自部署服务和内网SMB共享。通过无线网络、有线连接或Wireguard VPN均可连接到对应子网,由DHCP或Wireguard自动分配IP,不设其他准入限制。适用于可信笔记本的远程连接,自部署内网服务。

  • 保密网(VLAN 30):该部分能为白名单内的设备通过DHCP分配对应IP(采用ARP reply-only模式),并允许其中设备访问财务管理、扫描件管理等少数自部署服务和内网SMB共享。仅能通过堡垒机、交换机有线连接两种方式访问该部分网络中的资源。适用于涉及敏感个人数据的服务托管(财务类、扫描类、通话录音类、应用录音类、私有大模型等)、内网数据加解密服务平台(Backrest等)。

整体网络划分

应用服务配置

基于前文普通网、核心网、机密网的划分方案,即可根据各个应用服务的性质将其置于不同的网络中。需在不同网络中通信时,则配置对应的反向代理服务。以下是一些站长常用的虚拟机/服务,以及相关配置:

  • Frp:内网穿透使用。该工具分为Frpc与Frps,需要有公网服务器作为中转,无需打洞,相对来说很稳定。站长在本地一台虚拟机中的OpenWrt里顺带配置了Frpc,并在腾讯云上购置了轻量应用服务器配置了Frps,用于映射搭载于RouterOS上的Wireguard VPN端口到公网。

  • Immich + Syncthing,家庭相册管理使用。在家庭成员的各个手机上安装Syncthing客户端,并设定在接入特定WIFI时启动文件推送(单向推送,且所有推送到内网服务器的文件将被纳入版本管理,同时忽略删除操作),而后所有的照片经过选择后定期以外部文件的形式纳入到Immich管理。

  • Miniflux,RSS订阅使用。支持自动获取文章全文,界面和操作逻辑也相对清爽(FreshRSS等软件站长个人感觉操作有些别扭,而且基本不能有效获取全文,除非对接类似RSSHub等前置转换服务)。

  • 一台专用于托管国产软件的虚拟机。站长在其中安装了QQ、微信、百度网盘等日常较难离开的国产软件。其中,由于腾讯系IM的底层逻辑与邮件相似,这里安装QQ、微信是为了进行100%的消息记录留存;而安装百度网盘等其他软件则是为了避开流氓侵扰。

  • Gitlab + Harbor,自动部署Docker应用使用。由于配置的Docker服务较多,且相应服务的存储往往使用CIFS远程挂载,因此站长利用Gitlab构建了一套简单的推送流程,更新仓库中对应的docker-compose.yml文件即能自动更新对应Docker应用。而Harbor是内网中所有Docker引擎拉取镜像时的第一关口:为了避免Docker镜像源失效导致需要更新多台虚拟机中的配置文件的情形,站长只在Harbor上配置了对应的镜像源,其余所有Docker引擎所设镜像均为内网Harbor服务,这样只需单次更新就能修复整个内网中的Docker拉取失败问题。

一份较为粗糙的Docker应用自动部署方案(使用Gemini优化;若网络良好,可删除关于镜像的部分)
 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
stages:
  - check
  - deploy

variables:
  SSH_PRIVATE_KEY: $SSH_PRIVATE_KEY
  REMOTE_HOST: $REMOTE_HOST
  REMOTE_USER: $REMOTE_USER
  YQ_VERSION: "v4.16.2"
  YQ_BINARY: "yq_linux_amd64"

.setup_yq: &setup_yq
  - |
    if ! command -v yq &> /dev/null; then
      echo "Updating Alpine repositories to USTC..."
      ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1-2)
      echo "https://mirrors.ustc.edu.cn/alpine/v$ALPINE_VERSION/main" > /etc/apk/repositories
      echo "https://mirrors.ustc.edu.cn/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories
      apk add --no-cache curl wget
      echo "Downloading yq $YQ_VERSION..."
      wget https://{{YOUR_GITHUB_DOWNLOAD_MIRROR_LINK}}/https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY} -q --show-progress
      chmod +x ${YQ_BINARY} && mv ./${YQ_BINARY} /usr/bin/yq
    fi

.setup_ssh: &setup_ssh
  - apk add --no-cache openssh-client rsync grep
  - mkdir -p ~/.ssh && chmod 700 ~/.ssh
  - eval $(ssh-agent -s)
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
  - echo "$REMOTE_HOST_FINGERPRINT" >> ~/.ssh/known_hosts

docker-compose-config-check:
  image: {{YOUR_HARBOR_MIRROR_LINK}}/library/docker:dind
  stage: check
  before_script:
    - *setup_yq
  script:
    - |
      services=$(yq eval 'keys | .[]' config.yaml)
      for service in $services; do
        if [ "$(yq eval ".${service}.enabled" config.yaml)" == "true" ]; then
          folder=$(yq eval ".${service}.folder" config.yaml)
          if [ -f "$folder/docker-compose.yml" ]; then
            echo "Validating docker-compose.yml in folder: $folder"
            docker compose -f "$folder/docker-compose.yml" config -q || { echo "Error: docker-compose.yml in $folder is invalid"; exit 1; }
          else
            echo "Error: $folder/docker-compose.yml not found"; exit 1
          fi
        fi
      done
  only:
    - main

docker-compose-ssh-deploy:
  image: {{YOUR_HARBOR_MIRROR_LINK}}/library/docker:dind
  stage: deploy
  before_script:
    - *setup_yq
    - *setup_ssh
  script:
    - |
      set -e
      services=$(yq eval 'keys | .[]' config.yaml)
      for service in $services; do
        if [ "$(yq eval ".${service}.enabled" config.yaml)" == "true" ]; then
          folder=$(yq eval ".${service}.folder" config.yaml)
          remote_path="~/Containers/$folder"
          
          echo "Deploying service: [$service]"
          ssh "$REMOTE_USER@$REMOTE_HOST" "mkdir -p $remote_path"
          
          rsync_output=$(rsync -avz --itemize-changes --chmod=D755,F644 --update --dry-run "$folder/" "$REMOTE_USER@$REMOTE_HOST:$remote_path/")
          
          if echo "$rsync_output" | grep -q '^<f\|^<d'; then
            echo "Changes detected for [$service]. Updating files..."
            rsync -avz --itemize-changes --chmod=D755,F644 --update "$folder/" "$REMOTE_USER@$REMOTE_HOST:$remote_path/"
            
            echo "Stopping old containers for [$service]..."
            ssh "$REMOTE_USER@$REMOTE_HOST" "cd $remote_path && [ -f docker-compose.yml ] && docker compose stop" || echo "Warning: No existing containers to stop"
            
            echo "Starting new containers for [$service]..."
            ssh "$REMOTE_USER@$REMOTE_HOST" "cd $remote_path && docker compose up -d"
            
            echo "Deployment status for [$service]:"
            ssh "$REMOTE_USER@$REMOTE_HOST" "cd $remote_path && docker compose ps"
          else
            echo "No changes detected for [$service], skipping deployment."
          fi
        fi
      done
  only:
    - main

常用应用服务与所在网络概览

备份设计

为了避免ALL IN BOOM,数据备份措施是必要的。同时,出于对云服务商的不信任,数据加密措施同样是必要的。

总体来说,站长现选用Truenas Scale中数据快照与复制任务两项功能来备份两周内的数据,同时选用Restic(方案考量请见3.5 Restic & Rclone)将数据进行加密,以便后续上传到云服务商或拷贝到硬盘中进行冷备份。

比较遗憾的是,以上方案至多会占用原始数据300%的空间。站长曾在TrueNAS Scale论坛中看过以挂载数据快照替代复制任务的方案,至多只会占200%的空间,但需要手工编写需执行的指令。考虑到数据备份过程极为重要,应尽量减少风险和排障难度,站长最终还是采用了更简单但更占空间的方案。

落实到具体实施,站长在整个备份过程中,围绕个人需求,有几点实践经验(不一定对):

  • 将虚拟机磁盘、镜像等较大的文件单独拷贝、加密、备份处理。由于备份Proxmox VE硬盘文件或备份文件时,Restic的压缩率很低,存储多个版本所需的空间需求较大,频繁上传最新版本到云端覆盖原文件不太现实,因此站长最终选用手动云备份+手动硬盘冷备份的方案进行操作。

  • 基于数据敏感程度分级进行拷贝、加密处理。针对核心网内的个人数据(主要为日常存取的零星文件和家庭相片整理)及应用数据,可视为普通小文件,使用普通密钥进行加密;而针对机密网内的个人数据(录音数据、财务数据、扫描数据、密码文件、打印记录)及应用数据,应使用加强密钥进行加密,以避免因意外泄漏普通密钥时导致敏感数据同时泄露。为此,可能存取明文敏感数据的机密网虚拟机应不作备份,而是采用特定启用了Bitlocker的U盘对敏感数据进行保存,尽可能减少落盘。

  • 一些比较基础且特别的配置文件(例如:TrueNAS Scale的配置文件、RouterOS的路由配置文件)最好单独定期手动备份。当因为系统意外崩溃或RouterOS许可证过期等情况发生时,可利用配置文件快速进行恢复,而不是对照记忆中的配置方案与密码库记录手工补全信息。

整体数据备份流程

灾难恢复?

基于站长的设计方案,触发意外需将服务恢复至可用状态大致可分为3种情形:小主机(稳定型)崩溃;TrueNAS Scale服务崩溃;小主机(高效能型)崩溃。

小主机(稳定型)的故障恢复相对比较复杂。因为核心路由服务崩溃后,各个依赖于IP的服务将陆续出现问题,且无法进行远程排障。很悲伤,12月站长的这台小主机烧了主板,导致机器无法启动,内网一度瘫痪。

针对该情形,站长快速下单了一台新小主机并替换上备用硬盘,而后新安装Proxmox VE系统到硬盘上。在系统安装完毕并能通过WebUI对系统进行访问后,站长通过数据备份以及原系统硬盘的数据拷贝,优先恢复了Proxmox VE系统的网络接口配置、RouterOS虚拟机和堡垒机虚拟机三项。以上完成后,即可将小主机安装回机柜,余下恢复工作可远程慢慢完成。

此外,针对对于Proxmox VE系统能启动、能通过WebUI进行管理,但虚拟机因故均未能启动的不完全故障情形,站长预留了一台长期部署于机密网的J1900小主机,并在交换机上预留了一个对应机密网的网口,便于将笔记本快速接入WebUI对相应数据进行抢救。

小主机(高效能型)的故障恢复是最简单的。根据前文的设计思路,虚拟机的磁盘文件及各个Docker应用持久化挂载的存储卷,多储存在远程的NAS主机上。同时,各个Docker应用还支持通过Gitlab的CI/CD自动部署。因此,任一台搭载Proxmox VE系统并接入对应网络的设备,能通过挂载远程磁盘基本恢复虚拟机状态,并通过Gitlab快速恢复Docker应用状态。

当TrueNAS Scale出现故障时,只要不涉及数据盘,直接通过IPMI重装系统并通过WebUI恢复配置文件即可。后续只需注意检查各数据集的用户权限(uid、gid是否匹配),以及在恢复的过程中关闭小主机(高能效型),避免数据恢复过程中多端读写出现不一致,即基本能保障服务顺利恢复。如果故障波及到数据盘并破坏了整个RAID阵列,由于涉及到数据集的权限重建,站长认为只能逐步从云服务商和本地硬盘中逐步调回数据,并重建SMB共享了。

针对一般的断电情形,经测试,在配置好来电自启与虚拟机自启顺序后,基本不会影响到服务的可用性。同时,只要小主机(稳定型)能够正常工作,即使存在来电自启失败的设备,往往也能通过网络唤醒将其启动。

改进计划

首要的改进计划就是减少工作时间和精力消耗,不然根本没心思去玩各个服务。

总体来说,目前各个服务与数据备份方案已相对稳定。后续若有资金,或会再投资个MacMini用于部署本地大模型,专用于对敏感数据的分析处理。同时,12月烧主板的事情也给站长敲响了警钟,可能后续会参考前期在论坛中看到的瘦客户端组Proxmox VE集群的帖子一样,为最难进行灾难恢复的小主机(稳定型)配置故障转移,实现除多数机器被同时下线外,都能保障基本服务稳定运行的效果。

本博客已稳定运行 小时 分钟
共水了 22 篇文章 · 总计 100.17 k 字
萌ICP备20253555号
使用 Hugo 构建, 主题 StackJimmy 设计