☰
从零搭建个人服务器:硬件、Docker、安全与备份
2026/11/4 12:47:55 网站建设 项目流程

上个月帮朋友搬家,从他柜子深处翻出五块移动硬盘,接口从老式 USB 2.0 到 Type-C 一应俱全。他想找一张三年前孩子刚学走路的照片,插盘、等识别、翻目录,前后折腾了快四十分钟,最后在一个叫“新建文件夹(3)”的目录里找到了。那天晚上他问了我一句话:这些东西能不能都塞进一台自己的机器里,手机点开就能看?

这句话基本就是个人服务器最朴素的起点。所谓个人服务器,说穿了就是把一台常年开机的电脑放在你家里或者机房里,由你自己决定它跑什么、存什么、谁能访问,而不是把数据托管给某个会随时改规则、调价格、关停服务的平台。它可以是家里那台淘汰的笔记本,可以是一台巴掌大的迷你主机,也可以是一块开发板。它能干的事情远比想象中多:集中存照片和文档、做家庭影音库、跑自动备份、挂定时任务、当代码仓库、做智能家居的中枢、给自己搭一个随时能连的远程开发环境。

这篇内容面向的是想动手但还没动手的人,以及动手过一次、被各种报错劝退过的人。我会从“你到底需要它干什么”开始,一路讲到硬件怎么挑、系统怎么装、服务怎么用容器跑起来、安全怎么加固、备份怎么做才不会在真出事的时候才发现备份是坏的。中间会插入我自己踩过的几个坑,包括磁盘被日志撑爆、容器时区错乱、内存不够被系统直接杀进程,这些坑在教程里基本不会写,但实际会花掉你一整个周末。全篇不假设你有运维背景,命令都给到可以直接复制粘贴的程度,但每一个关键选择我都会解释为什么这么选,因为照着抄只能成功一次,理解逻辑才能在你换硬件、换系统的时候不慌。

1. 先把需求钉死:这台机器到底替你干哪几件活

很多人搭服务器的第一步是打开购物网站看配置单,这是最容易走弯路的顺序。硬件是结果的函数,你得先知道自己要跑什么,再决定花多少钱。我见过有人一上来配了 32GB 内存加四块企业盘,结果一年下来只用来存了几部电影和一份简历,风扇噪音倒是天天在客厅响;也见过有人拿一块老开发板想跑媒体转码,播放器一转圈就是十分钟。

我自己习惯用一张纸把需求分成三列:必须有的、想要但可以等的、纯属好奇想试试的。这个分法能帮你砍掉至少一半的预算浪费,因为“纯属好奇”那一列的东西,往往在你搭完之后三天就再也不会打开。

1.1 三类典型诉求与对应的硬件档位

绝大多数人的需求会落在下面三类里,你可以对号入座。

第一类是数据集中型。核心诉求是文件共享、照片归档、手机自动备份、跨设备同步。这类负载的特点是 IO 频繁但 CPU 几乎闲着,一台四核低功耗小主机加一块 SSD 做系统盘、两块大容量机械盘做数据盘,就完全够用。内存 8GB 是甜点,16GB 属于舒服但不必需。

第二类是服务托管型。除了存文件,你还想跑一堆常驻服务:媒体库、下载器、笔记系统、代码仓库、监控面板、自动化脚本。这类场景真正的瓶颈不是 CPU 而是内存和磁盘的随机读写,因为每个服务都要占一份常驻内存,容器编排本身也要开销。我的经验值是:Debian 基础系统约占 300MB,容器运行时约 100MB,一个文件共享服务约 100MB,媒体服务空闲时 300MB、转码时能吃掉 1.5GB 以上,再加数据库、反向代理、监控,8GB 内存在跑五六个服务之后就会开始紧张。想长期舒服,16GB 起步。

第三类是折腾学习型。你想玩虚拟化、跑几个互相隔离的环境、做网络实验、编译点东西。这类需求对核心数和内存的要求会陡然上升,而且你大概率会想要一块独立的系统盘来放虚拟机镜像,因为虚拟机镜像的随机写会明显拖慢机械盘。这种场景我更推荐直接上虚拟化平台,把物理机当成资源池,需要什么环境就开一个,用完删掉,比在一台裸系统上反复装卸载干净得多。

下面这张表是我按实际装机经验整理的档位参考,价格区间取的是二手和全新混合的常见水平,具体以你当地市场为准:

档位典型配置适合场景待机功耗大致预算
入门四核低功耗小主机 / 旧笔记本,8GB 内存,256GB SSD + 1×4TB HDD文件共享、手机备份、轻量服务8–15W低
主力六到八核小主机,16–32GB 内存,512GB SSD + 2×8TB HDD媒体库、多服务容器、虚拟机15–25W中
折腾二手工作站 / 自组装,32GB 以上内存,多盘位机箱虚拟化、编译、实验环境40–80W高

1.2 一台机器能扛多少服务:把功耗和内存算清楚

功耗这件事必须算,因为它决定了这台机器能不能长期开着,而“长期开着”是服务器的前提。算法很简单:待机功率乘以 24 小时再乘以 30 天,得到月耗电度数。一台 15W 的小主机,月耗电约 10.8 度;按每度 0.6 元算,一个月六块多,一年八十块左右。同样场景换成一台待机 60W 的旧台式机,月耗电约 43 度,一年电费接近三百块,噪音和发热还是额外的代价。三年下来这个差额足够你直接买一台新的低功耗小主机了,所以“用旧电脑凑合”在长期视角下并不总是省钱。

内存的算法更粗糙一些,但足够指导决策。我的做法是把每个要跑的服务标一个内存上限,然后加总再乘以 1.3 的系数留余量。比如:系统 0.3GB + 容器运行时 0.1GB + 文件共享 0.1GB + 媒体服务 1.5GB(按转码峰值算)+ 数据库 0.4GB + 反向代理 0.1GB + 监控 0.3GB ≈ 2.8GB,乘 1.3 约 3.6GB。看起来 8GB 绰绰有余,但实际运行中文件缓存会吃掉大量空闲内存,这是好事不是坏事,Linux 会主动把空闲内存拿来做页缓存提升读性能,只是你在看监控面板的时候别被“内存使用率 85%”吓到,要看的是 available 那一栏。

还有一个容易被忽略的维度是并发转码。如果你的媒体库服务需要实时转码,一路 1080p 转码大约占用 1 到 2 个核心,四路并发就能把一台四核机器压满。解决办法有两个:一是尽量让客户端直通播放原始文件,把转码当成兜底而不是常态;二是选带硬件编解码能力的平台,把转码负载卸载到集成显卡上,CPU 占用能降到个位数。这一点在选硬件的时候就要想清楚,事后没法补。

2. 硬件与系统的选型逻辑:省心比性能更值钱

服务器和日常用的电脑,评价标准完全不一样。你给自己买笔记本会看屏幕、看重量、看外观,给服务器买硬件只看四件事:能不能常年稳定运行、功耗高不高、噪音大不大、扩展方不方便。性能排在这四项后面,因为家用场景的负载峰值其实很低,真正的痛点是某天半夜它自己重启了,而你第二天早上才发现所有服务全挂。

2.1 迷你主机、旧笔记本、开发板三条路的真实差距

迷你主机是这两年最省心的选择。低功耗平台待机功耗普遍在 10W 上下,无风扇或者低转速风扇,放在电视柜里几乎听不见。接口通常有一个到两个 M.2 插槽加一个 2.5 寸盘位,做系统和一块数据盘够用。缺点是盘位少,想上多块大容量机械盘就得外接硬盘柜,而外接硬盘柜的供电稳定性是个隐患,我用过一个便宜的四盘位柜子,夏天连续运行三个月之后开始偶发掉盘,换电源适配器才解决。所以如果你打算走多盘路线,一开始就选带独立盘位的机箱或者带可靠供电的柜子。

旧笔记本的优势被严重低估了。它自带屏幕和键盘,装机阶段不用接显示器;自带电池,相当于一个免费的 UPS,短时停电不会直接断电;功耗通常也不高。缺点是硬盘位少,多数只有一个 M.2 加一个 2.5 寸,而且长期高温运行对散热是个考验,尤其是那种轻薄本,风扇进灰之后温度会一路飙到九十度然后降频。用旧笔记本的正确姿势是:拆后盖清灰、换硅脂、把电池健康度查一下,如果电池已经鼓包就直接拆掉只用适配器供电,别留着过年。

开发板是最有诱惑力也最容易失望的一条路。便宜、安静、功耗极低,跑点轻量服务完全没问题。但它的硬伤在于存储和供电:多数板子的 USB 供电能力有限,接一块机械盘需要额外带电源的硬盘盒,否则大文件写入时电压一掉,盘就掉了;另外通过 USB 转 SATA 的桥接方案在长时间高负载下容易出现超时错误,日志里会冒出一堆重置记录。我的建议很明确:开发板适合做旁路任务,比如网络监控、定时脚本、传感器采集,不要把它当成唯一的存储中心。

2.2 系统镜像怎么挑:裸系统、虚拟化平台与容器路线的取舍

系统选择上,我给新手的默认答案是 Debian 稳定版或者 Ubuntu 的长期支持版。理由不是它们功能最强,而是资料最多、坑被踩得最透、出问题的时候你能搜到答案。装系统的时候选最小化安装,不要勾选任何桌面环境,图形界面在服务器上只会白占内存和攻击面。

如果你确定以后要跑多个互相隔离的环境,那就在第一步直接装虚拟化平台,把物理机当资源池。这样做的好处是隔离彻底:某个环境把内存跑爆了,影响范围仅限那一台,不会把整机拖死。代价是多一层抽象,硬盘 IO 会有轻微损耗,而且你需要额外学一套管理界面。我的判断标准是:如果你打算长期跑超过五个服务,或者需要频繁做“装一个环境试试,不行就删”的操作,就直接上虚拟化;否则裸系统加容器已经足够。

容器和虚拟机的区别值得用生活化的方式说清楚。虚拟机像是一栋独立的小房子,有自己的地基、水电和门牌号,隔离性好但笨重;容器像是同一栋楼里的不同房间,共用一套水电管线,启动快、占用小,但如果某个人把总水阀关了,所有房间都受影响。绝大多数自建服务用容器就够了,因为开源社区早就把镜像打包好了,你只需要写几行编排文件。

3. 从裸机到能远程登录:安装与首轮加固的完整链路

装机这一步看着简单,但新手翻车大多发生在这里,而且翻车方式很隐蔽:系统装完了、能开机了,你以为成功了,结果一个月后发现磁盘分区方案不合理,想扩容量得重装。所以这一节我会把“为什么这么选”讲透。

3.1 启动盘制作与安装阶段的几个致命选项

启动盘制作工具有很多,命令行用 dd 最直接,图形界面用 balenaEtcher 或 Rufus 都行。这里有个细节:写盘会清空目标 U 盘的全部数据,而且写完之后 U 盘在 Windows 里会显示成未格式化状态,这是正常的,别手贱去点格式化,格式化完启动盘就废了。

进 BIOS 之后要改的选项不多,但每一个都影响后续体验:

  1. 关闭安全启动(Secure Boot)。部分发行版的安装镜像没有签名,开着安全启动会直接引导失败。
  2. 开启来电自启。这个选项在电源管理菜单里,名字通常是 “AC Power Loss” 或 “Restore on AC Power Loss”,值设成 Power On。作用是一旦意外断电恢复供电,机器会自动开机,不用你亲自按电源键。这个选项是家用服务器必开的,我见过太多人出差在外,家里跳闸之后服务器就再也没起来。
  3. 确认启动模式为 UEFI。新系统一律用 UEFI 加 GPT 分区,传统模式留着只会带来麻烦。
  4. 开启虚拟化支持。即使你现在不用虚拟机,也把 VT-x / AMD-V 打开,省得以后折腾。

分区方案上,我最推荐的是“系统盘和数据盘分离”。系统装在 SSD 上,/给 40 到 60GB,其余留给数据目录;数据盘单独一块,格式化成 ext4 或者 XFS,挂载到/data之类的路径。这么做的核心理由是:系统盘出问题的时候你可以直接重装,数据盘拔下来插到别的机器上照样能读。如果只有一块盘,那至少把/var和/home单独分区,避免日志写爆把根分区撑满——这个坑我在第 6 节会详细讲。

安装过程中还会让你设置用户名和密码,这里给一个建议:不要用 root 直接登录,创建一个普通用户,后续通过 sudo 提权。这不是形式主义,而是为了让所有操作都有审计痕迹,出问题的时候你能查到是哪次操作导致的。

3.2 第一次 SSH 登录后必须做的六件事

系统装完、网线插上、用ssh 用户名@IP连上去之后,先别急着装服务软件。下面这六件事做完,你的机器才算“能用”,顺序也不建议打乱。

第一件,更新系统并装基础工具。

sudo apt update && sudo apt full-upgrade -y sudo apt install -y curl wget vim htop tmux git ca-certificates gnupg lsb-release

tmux特别重要,它是你的保险绳。远程执行长时间任务(比如大规模拷贝、编译、系统升级)之前先开一个 tmux 会话,这样即使 SSH 断线,任务还在后台跑。我早期不知道这个,一次跨盘拷贝跑了两个小时,网络抖动断线,任务直接中断,白等两小时还得重来。

第二件,配置 SSH 密钥登录并禁用密码。

本地机器上生成密钥对:

ssh-keygen -t ed25519 -C "home-server" ssh-copy-id -i ~/.ssh/id_ed25519.pub 用户名@服务器IP

然后在服务器上编辑/etc/ssh/sshd_config,改这几项:

PasswordAuthentication no PermitRootLogin no PubkeyAuthentication yes

改完先别重启服务,用新开一个终端测试密钥能不能登录,确认能登上之后再sudo systemctl restart ssh。这一步顺序错了会把自己锁在门外,虽然还能接显示器救回来,但很折腾。

第三件,改 SSH 端口。很多人说改端口没用,因为扫描器照样能扫到。这话对一半:改端口挡不住有针对性的攻击,但能挡掉 99% 的无差别扫描,日志会干净很多,fail2ban的压力也小。改的时候记得同步更新防火墙规则,否则改完自己也连不上。

第四件,启用防火墙并只放行必要端口。

sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw enable sudo ufw status verbose

default deny incoming是核心,意思是除你明确放行的,其他入站一律拒绝。这个默认策略能让很多“服务默认监听所有网卡”的意外暴露问题自动消失。

第五件,开自动安全更新。

sudo apt install -y unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades

长期跑的服务器的最大风险不是被针对性攻击,而是某个组件爆出漏洞之后你忘了打补丁。自动更新能把这个风险按下去,代价是偶尔会在半夜重启某个服务。生产环境要谨慎,家用服务器我认为值得开。

第六件,设置时区。

sudo timedatectl set-timezone Asia/Shanghai timedatectl

看着不起眼,但它会直接影响所有日志的时间戳和定时任务的执行时刻。我在第 6 节会讲一个因为时区导致的连环问题。

3.3 静态地址规划:让服务器永远只有一个门牌号

服务器的 IP 必须固定,否则每次重启地址一变,你所有的书签、挂载配置、容器映射全要改。两种做法:

一种是在路由器上做 DHCP 静态绑定,把服务器的网卡 MAC 和某个 IP 绑死。优点是配置集中在一处,服务器本身不用改;缺点是换了路由器就得重配。

另一种是在服务器系统内配静态地址。用systemd-networkd的话,配置文件大概长这样:

# /etc/systemd/network/10-static.network [Match] Name=enp1s0 [Network] Address=192.168.1.50/24 Gateway=192.168.1.1 DNS=192.168.1.1 DNS=223.5.5.5

改完sudo systemctl restart systemd-networkd生效。注意网卡名字要用ip link查实际值,别照抄。

这里有个经验:地址段规划要留余量。我一般把192.168.1.2到192.168.1.99留给基础设施(路由器、交换机、服务器、打印机),100到200留给 DHCP 动态分配,200以上留给临时调试。这么规划之后,你永远知道哪个地址是干什么的,排查问题的时候能省不少时间。

关于远程访问,我的立场是先想清楚边界。绝大多数人的实际需求是“在家里任何设备上访问”,这只需要局域网内地址固定就够了。如果确实需要在外面访问,优先考虑在公网侧放一台云主机做入口,通过它反向代理回内网,所有流量走加密通道,边界清晰、可控性强。不要为了图省事把家庭网络直接暴露出去,也不要随手装一些来路不明的穿透工具,那些东西的权限比你想象的大得多。

4. 把服务跑起来:容器化部署的落地顺序

到这一步,机器已经能稳定登录了,接下来才是真正有意思的部分。我强烈建议所有服务都走容器,理由很简单:干净、可回滚、迁移成本低。你在一台机器上折腾一年,裸装的服务会留下一堆依赖和配置文件,想搬到新机器上基本等于重来;而容器化之后,你只需要带走一个编排目录和数据目录。

4.1 容器运行时的安装、目录规划与日志治理

安装走官方源,不要用系统仓库里那个版本,后者往往落后好几个大版本:

# 添加源(具体发行版代号按实际情况替换) sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

装完之后有两件事必须马上做,否则一个月内必定出事。

第一件,把数据目录挪到数据盘。默认所有镜像和容器数据都在/var/lib/docker,也就是系统盘上。跑几个服务之后,镜像缓存能轻松吃掉二三十 GB。改法是在/etc/docker/daemon.json里指定:

{ "data-root": "/data/docker", "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }

改完sudo systemctl restart docker,然后用docker info | grep "Docker Root Dir"确认生效。注意重启会导致所有容器停止,别在有任务跑的时候操作。

第二件,限制日志大小。上面那段配置里的max-size和max-file就是干这个的。容器日志默认是无限增长的,一个跑得比较欢的服务,日志能在两周内写到几十 GB,把系统盘撑满,然后整台机器上的所有服务开始报“no space left on device”。这个坑我踩过两次,第二次之后我把所有新机器的日志限制都写进了装机脚本。

目录规划上,我的习惯是这样:

/data ├── docker/ # 容器运行时数据 ├── appdata/ # 各服务的配置与元数据 │ ├── service-a/ │ └── service-b/ ├── media/ # 媒体文件 ├── backup/ # 本地备份落点 └── downloads/ # 临时下载区

好处是备份的时候目标明确:appdata和backup必须备份,media看容量决定,downloads完全不用备份。分清楚“哪些数据丢了会心痛”,备份策略才有意义。

4.2 一个组合实例:文件共享、媒体库与定时备份的编排

下面是一个精简的编排示例,三个服务:文件共享、媒体库、定时备份。镜像名我用占位符表示,实际部署时按你选用的开源项目替换即可。

services: fileserver: image: <你的文件共享服务镜像>:latest container_name: fileserver restart: unless-stopped environment: - TZ=Asia/Shanghai - PUID=1000 - PGID=1000 ports: - "445:445" - "139:139" volumes: - /data/appdata/fileserver:/config - /data/media:/share healthcheck: test: ["CMD", "nc", "-z", "127.0.0.1", "445"] interval: 60s timeout: 5s retries: 3 medialib: image: <你的媒体库服务镜像>:latest container_name: medialib restart: unless-stopped environment: - TZ=Asia/Shanghai - PUID=1000 - PGID=1000 ports: - "8096:8096" volumes: - /data/appdata/medialib:/config - /data/media:/media:ro devices: - /dev/dri:/dev/dri mem_limit: 2g backup: image: <你的备份工具镜像>:latest container_name: backup restart: "no" environment: - TZ=Asia/Shanghai volumes: - /data/appdata:/src:ro - /data/backup:/dst

几个配置项的意图值得说明一下。

restart: unless-stopped让容器在异常退出时自动拉起,但你自己手动停掉的不会自动重启。TZ环境变量必须显式传,因为容器默认是 UTC,日志时间会差八小时,这个问题在第 6 节展开。

PUID和PGID决定容器内进程以哪个用户身份写文件。宿主机上的普通用户通常是 1000,把这两个值设成 1000,容器写出来的文件你在宿主机上就能直接读写,不用sudo也不用改属主。这一项配错的表现是“文件能建但打不开”或者“权限被拒绝”,非常典型。

medialib的mem_limit: 2g是给自己上一道保险。媒体服务在转码时内存会往上冲,没有上限的话极端情况下会把整机内存吃满,触发系统的 OOM killer,而被杀掉的往往是内存占用最大的那个进程,于是你的数据库可能莫名其妙就没了。给每个可能膨胀的服务设上限,是长期稳定运行的关键习惯。

medialib挂的/dev/dri是集成显卡设备节点,用于硬件转码。挂载之前先在宿主机上确认设备存在,并且当前用户在该设备的用户组里。

/media:ro里的ro是只读挂载。媒体库只需要读文件,不需要写,给它只读权限能防止某个程序的 bug 把源文件改坏。这个习惯值得推广到所有“只需要读”的挂载点上。

4.3 反向代理与证书:给内网服务一个统一的入口

随着服务变多,你会面临一个实际问题:每个服务一个端口,8096、8080、3000记不住。解决办法是上一层反向代理,用不同的子域名或者路径指向不同服务,比如media.内网域名指向媒体库,files.内网域名指向文件共享。

代理服务我倾向于用配置化的方案,把每个站点写成一个配置文件,行为可预测、可版本管理:

server { listen 80; server_name media.example.lan; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name media.example.lan; ssl_certificate /etc/ssl/private/media.crt; ssl_certificate_key /etc/ssl/private/media.key; client_max_body_size 0; location / { proxy_pass http://127.0.0.1:8096; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } }

proxy_buffering off这行对流媒体和实时性要求高的服务很关键。开着缓冲的话,代理会先把响应攒在内存里再转发,表现为播放器点开之后转圈很久才开始播,或者进度条拖动反应迟钝。我第一次遇到这个问题的时候以为是磁盘慢,查了半天 IO 才发现是代理缓冲。

client_max_body_size 0表示不限制上传大小,文件共享类的服务必须设成 0 或者一个很大的值,否则传大文件会直接返回 413。

证书方面,内网域名可以用自签证书,也可以用内部签发工具生成自己的根证书再分发到各设备。自签证书的浏览器会报不安全,但对你自己的设备来说,把根证书装进信任列表之后就一切正常。如果服务需要从公网访问,那就在公网入口那一层用 ACME 协议自动签发和续期,注意 90 天有效期的自动续期一定要配好并且验证过,我见过证书过期导致整站不可访问、排查了两个小时才发现是证书问题的情况。

5. 安全与运维:把“能跑”变成“敢长期跑”

服务跑起来只是开始,能不能让它安静地跑上一年不出事,靠的是运维习惯。这一节讲三件事:访问控制、备份、监控。这三件事的共同点是——平时看不出价值,出事的时候决定你是损失一小时还是损失全部数据。

5.1 防火墙、密钥登录与异常登录封禁

前面已经配了ufw的默认拒绝策略,这里补充几条细化规则:

# 只允许局域网访问管理端口 sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp # 允许局域网访问 Web 服务 sudo ufw allow from 192.168.1.0/24 to any port 443 proto tcp # 查看规则编号 sudo ufw status numbered

按来源网段放行比按端口放行更安全:即使端口号被人知道,来自外网的连接也会被直接拒绝。这一条能把绝大部分自动化扫描挡在外面。

再上一层是登录封禁。fail2ban会监控日志里连续失败的登录尝试,超过阈值就把来源地址临时封掉:

# /etc/fail2ban/jail.local [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 [sshd] enabled = true port = ssh logpath = %(sshd_log)s backend = systemd

配好之后sudo systemctl enable --now fail2ban,用sudo fail2ban-client status sshd查看当前封禁情况。这个工具的收益很直接:装上之后我的日志里每天几百条失败尝试会迅速降到个位数,因为同一批来源被封了就不再重试。

还有一条容易被忽略:别把管理界面暴露到不需要它的地方。很多服务的 Web 管理面板默认没有强认证,如果你把一个管理后台放在所有人都能访问的位置,等于把家门钥匙挂在门把手上。我的做法是管理类服务只监听127.0.0.1,通过本机的代理转发,需要的时候再单独开一条受限的访问路径。

5.2 备份的 3-2-1 落地与恢复演练

备份这件事,最大的误区是“我做了备份”和“我的备份能用”之间的差距。我自己就吃过亏:早期用脚本每天把目录打包到另一块盘上,觉得万无一失,直到有一次误删了一个配置文件想从备份里找,打开压缩包才发现里面是空的——脚本里源路径写错了一个字符,一年多来每天“成功”备份了一个不存在的目录,日志里全是正常退出。

3-2-1 原则说的是:至少 3 份数据,存在 2 种不同介质上,其中 1 份在异地。落到家用场景,我把它翻译成三句话:

  • 本地一份:数据盘上的原始文件。
  • 本地另一份:另一块独立硬盘上的备份,用增量快照工具做,保留多个历史版本,能回到任意一天。
  • 异地一份:定期同步到一台不同物理位置的机器上,或者用对象存储做冷备份。

增量备份工具的核心是“去重 + 快照”。它的原理是把文件切成小块计算哈希,只上传没见过的块,所以第二天的增量备份可能只有几十 MB,而不是几十 GB。这带来的一个关键能力是:你可以保留 30 天甚至 90 天的历史版本而不撑爆存储。相比之下,每天全量打包的方式在数据量上到几百 GB 之后就完全不可持续了。

备份脚本我一般这么写:

#!/usr/bin/env bash set -euo pipefail export RESTIC_PASSWORD_FILE=/root/.restic-pass REPO=/data/backup/restic # 初始化(只需执行一次,已存在会报错,用 || true 忽略) restic -r "$REPO" init || true # 备份关键目录,排除缓存和临时文件 restic -r "$REPO" backup /data/appdata \ --exclude='*/cache' \ --exclude='*/tmp' \ --exclude='*.log' \ --tag daily # 保留策略:最近 7 天每天一份,最近 8 周每周一份,最近 12 个月每月一份 restic -r "$REPO" forget --keep-daily 7 --keep-weekly 8 --keep-monthly 12 --prune

挂到定时任务里每天凌晨跑:

# crontab -e 30 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

保留策略那行是关键,很多人的备份盘是被无限增长的旧备份撑爆的,撑爆之后新备份静默失败,直到需要恢复的时候才发现最新的一份是三个月前的。

但真正的重点在恢复演练。我的规矩是每季度随机挑一个目录,从备份里恢复到临时位置,然后逐个文件比对。恢复命令大概是:

restic -r /data/backup/restic restore latest --target /tmp/restore-test --include /data/appdata/fileserver diff -r /data/appdata/fileserver /tmp/restore-test/data/appdata/fileserver

没做过恢复演练的备份,只能算“可能存在的备份”。这句话有点扎心,但确实是经验之谈。

5.3 监控、温度与断电保护

监控的目的不是看漂亮的曲线,而是在用户投诉之前发现异常。我关注四个指标:磁盘剩余空间、内存可用量、CPU 温度、磁盘健康状态。

磁盘空间最危险,因为它是渐变式的,从 60% 到 95% 可能只用了两周,而你对这台机器的关注度早就下降了。做法很简单,写个脚本每天检查,超过阈值就发通知:

#!/usr/bin/env bash THRESHOLD=85 USAGE=$(df -h /data | awk 'NR==2 {gsub("%","",$5); print $5}') if [ "$USAGE" -gt "$THRESHOLD" ]; then echo "警告:/data 使用率 ${USAGE}%" | mail -s "服务器磁盘告警" you@example.com fi

磁盘健康用 SMART 数据看:

sudo apt install -y smartmontools sudo smartctl -H /dev/sda sudo smartctl -A /dev/sda | egrep 'Reallocated|Pending|Uncorrectable|Temperature'

重点关注Reallocated_Sector_Ct和Current_Pending_Sector这两个值,只要不是 0 并且还在增长,就说明盘在退化,该准备换盘了。硬盘不会突然坏,它通常会给几周甚至几个月的预警信号,前提是你在看。

温度方面,机械盘长期高于 50 度会明显缩短寿命,CPU 长期高于 85 度会降频。用小机箱的时候特别要注意风道,我见过把机器塞进封闭电视柜里的,夏天盘温直接干到 60 度,运行半年就开始出现读写错误。

最后是断电保护。前面提到的旧笔记本自带电池是天然优势;如果是迷你主机,建议配一个小容量 UPS,不用多贵,能撑十分钟让你把机器正常关机就行。断电本身不可怕,可怕的是写入过程中断电,那有可能损坏文件系统甚至丢数据。配了 UPS 之后再装个管理工具,检测到市电断开就自动执行关机,这套组合能避免绝大多数突然断电带来的问题。

6. 我踩过的几个坑与对应的排查链

下面这几个问题都是我实际遇到过的,写出来不是为了让你避开(有些坑避不开),而是让你在遇到类似症状的时候能顺着同一条思路找到原因。

6.1 磁盘满导致服务集体假死:从现象到根因的完整排查

症状是这样的:某天早上所有服务都连不上,SSH 也登不进去,但机器风扇在转、网口灯在闪。接上显示器一看,满屏的No space left on device。

排查顺序我是这么走的:

# 第一步,看整体使用率 df -h # 第二步,找最大的目录,逐层下钻 sudo du -h --max-depth=1 / | sort -rh | head -20 # 第三步,如果 /var 异常大,继续下钻 sudo du -h --max-depth=1 /var/lib | sort -rh | head -20

那次最后定位到/var/lib/docker/containers下一个日志文件有 47GB。根因是某个服务在反复重试一个失败的外部请求,每次失败都打一条完整的错误堆栈,一天能写几百 MB,而容器的默认日志驱动不做轮转,就这么一路涨。

修复动作分三步:先清掉日志腾出空间(truncate -s 0那个文件,别直接删,删了容器跑着的时候空间不会释放),再在daemon.json里加上日志上限,最后重启运行时让配置生效,此后所有容器的日志都受限制。

这件事之后我养成了一个习惯:新机器装完系统,第一件事就是配日志轮转和磁盘告警,而不是等出事。另外系统层面的日志也要限制,/var/log/journal在默认配置下可能占用几个 GB,用一句命令就能设上限:

sudo journalctl --vacuum-size=500M # 永久生效 sudo sed -i 's/^#SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf sudo systemctl restart systemd-journald

6.2 容器时区差八小时:一个看着小但会连锁的问题

这个问题的表现很有意思:日志里的事件时间是凌晨三点,但你明明是晚上七点操作的;更麻烦的是定时任务,你设的“每天凌晨三点备份”,实际执行时间是上午十一点,因为容器里的 cron 认为现在是 UTC 三点。

根因是容器默认继承 UTC 时区。宿主机的系统时区你设过了,但容器是独立的运行环境,时区来自镜像自己的配置。

修复方式是在编排文件里给每个容器加环境变量TZ=Asia/Shanghai,或者在宿主机上把/etc/localtime和/etc/timezone挂进容器:

volumes: - /etc/localtime:/etc/localtime:ro

我现在的做法是两条都上:环境变量简单直接,挂载文件更全面,能覆盖那些不读TZ变量的程序。加完之后记得docker compose up -d重建容器,改配置不重启是不生效的,这一点我一开始经常忘。

顺带说一个相关的坑:如果你用数据库,时区问题会影响时间字段的写入和查询,表现出来是“按日期分组统计,数据总是少一天”。这类问题排查起来很花时间,因为数据本身没错,只是偏移了。

6.3 内存被吃满后的连锁反应:OOM killer 挑谁下手

有一次媒体库服务在处理一批高清文件,第二天发现数据库连不上,日志里查不到任何数据库自己的错误,只有一个容器以退出码 137 结束。137 就是 128 加 9,9 是 SIGKILL,说明进程是被强制杀掉的。

看系统日志就清楚了:

sudo journalctl -k | grep -i "out of memory" dmesg -T | grep -i "killed process"

内核的 OOM killer 在内存耗尽时会挑一个“分数最高”的进程杀掉。这个分数主要看内存占用量,所以被杀掉的往往是最大的那个——可能是数据库,也可能是媒体服务,跟你当下最需要哪个服务完全无关。

解决办法是给容器设内存上限,同时给自己留出系统余量。我现在的做法是宿主机预留 2GB 应急,剩下按服务重要性分配。另外可以减小 OOM killer 杀掉关键服务的概率,通过调整oom_score_adj让关键进程“更难被杀”。但更根本的还是那句话:限制每个服务的内存上限,让它自己在超限时被重启,而不是把整机拖下水。

还有一个容易被误判的情况:内存不够的表现不一定是被杀进程,也可能是系统整体变得极慢,因为频繁换页。这时候htop里看起来内存还有剩余,但可用内存其实在 swap 上挣扎。判断方法是看vmstat 1的si和so两列,如果持续不为 0,说明已经在换页了,加内存或者减服务是唯一出路。

6.4 文件共享能看见但打不开:权限与协议的两个方向

这个问题的表现形式是:在别的设备上能看到共享目录,点进去提示“无法访问”或者“权限不足”。排查要分两个方向。

方向一是UID/GID 映射。容器内进程以某个 UID 写文件,宿主机上你用的是另一个 UID 访问,两者对不上就会出现“文件是我的但我没有权限”的诡异现象。检查方法:

id ls -ln /data/media | head

ls -ln显示的是数字 UID,直接和你的id输出对比。解决方案有两个:把容器的PUID/PGID设成宿主机的实际值,或者把宿主机用户加入文件所属的组。

方向二是协议版本。老的一些客户端可能还在用早已被淘汰的 SMB 版本,而新版本的服务端默认禁用了它。这时候现象是能列目录但打不开文件,或者干脆连不上。检查服务端日志能看到协商失败的信息。正确的做法不是把服务端降级去迁就老客户端,而是升级客户端或者换访问方式,因为老协议本身存在已知的弱点。

我还遇到过一个更隐蔽的:文件系统挂载参数里的权限设置。某些文件系统在挂载时可以统一指定文件属主,如果你没设,所有文件都会显示成 root 所有,普通用户自然访问不了。这类问题在挂载外接硬盘的时候特别常见,记得在/etc/fstab里补上uid、gid和umask参数。

7. 关于长期运行的一点个人体会

这台机器从第一次点亮到现在,中间换过两次硬件、重装过一次系统、迁移过一次全部数据。回头看,真正让我省心的不是某个具体的软件或者配置,而是几个很朴素的习惯:所有服务都走容器,配置和数据全部落在/data下面,任何改动之前先想清楚“回滚怎么做”,以及定期真的去点开一次备份看看里面有什么。

我现在的做法是给每台机器留一个README放在/root下,记录这台机器的地址、端口分配、服务清单、磁盘布局和上次恢复演练的日期。这东西看起来像形式主义,但当你在半年后的某个深夜需要快速判断“这个目录能不能删”的时候,它会救你一命。

还有一个建议给刚上手的人:不要一次把所有想装的服务全装上。一次装一个,装完跑一周,确认稳定了再加下一个。这样出问题的时候变量只有一个,排查成本是完全不同的量级。我见过太多人一个周末装了十个服务,然后花两个周末找哪个在互相打架。

最后分享一个很小的技巧:给所有容器名和目录名都用短横线分隔的小写命名,比如media-lib、file-share。不是为了好看,而是因为在终端里输路径的时候不用切大小写,写脚本的时候少踩一个坑。这类细节单个看微不足道,积累起来就是你的机器“顺手”和“别扭”的区别。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询