知识广场

按学科筛选:计算机科学 / 操作系统 / Linux
清除筛选

«计算机科学 / 操作系统 / Linux» 分类下共 16 篇帖子

用 syncoid 把 ZFS 数据集自动增量同步到异地(家用 NAS → VPS)

## 起因 家里 NAS 上有 4TB 照片 + 项目数据,本地做了 ZFS RAID1(同一台机器 两块盘镜像)。但火灾 / 水患 / 误删 ZFS 仓库 → 一晚上全没。 "异地备份"是真灾备。 需求:每天把 NAS 上某个 dataset 增量同步到一台远程 VPS(10GB 起价 便宜的 storage VPS)。要求:增量、加密传输、自动管理 snapshot 链、 失败告警。 手写 `zfs send | ssh ... | zfs receive` 能行,但要自己管"上一次成功 的 base snapshot 是哪个"非常烦。`syncoid`(sanoid 项目的一部分)自动 处理整套流程。 ## 解决方案 ### 装 NAS + VPS 都要装: ```bash # Debian / Ubuntu sudo apt install sanoid syncoid --version ``` ### 配 SSH 密钥(NAS → VPS 单向) NAS: ```bash sudo ssh-keygen -t ed25519 -f /root/.ssh/syncoid -C 'syncoid' sudo cat /root/.ssh/syncoid.pub ``` VPS: ```bash sudo useradd -m -s /bin/bash zfsbackup sudo mkdir -p /home/zfsbackup/.ssh sudo tee -a /home/zfsbackup/.ssh/authorized_keys <<'EOF' command="zfs receive *",no-pty,no-port-forwarding ssh-ed25519 AAAA... EOF sudo chown -R zfsbackup:zfsbackup /home/zfsbackup/.ssh sudo chmod 700 /home/zfsbackup/.ssh sudo chmod 600 /home/zfsbackup/.ssh/authorized_keys ``` `command="zfs receive *"` 限定这把 key 只能跑 zfs receive, 即使泄露也不能登 shell。 ### 远程仓库 VPS 建 ZFS pool(数据盘,比如 `/dev/sdb`): ```bash sudo zpool create -O compression=zstd backup-pool /dev/sdb sudo zfs create backup-pool/nas sudo zfs allow zfsbackup create,mount,receive,destroy,snapshot \ backup-pool/nas ``` `zfs allow` 让 zfsbackup 用户能 receive 到这个 dataset 下。 ### 测试一次同步 NAS: ```bash sudo syncoid -i /root/.ssh/syncoid \ tank/photos [email protected]:backup-pool/nas/photos ``` 第一次跑会全量传 4TB(取决带宽,几小时-几天)。后续增量只传差异。 `syncoid` 自动: - 在源端创建一个 `syncoid_*` 快照 - `zfs send -i <last-common> <new>` 增量发到远端 - 远端 `zfs receive` - 删除源端不再需要的老 syncoid 快照(保留最近两个用于下次增量) ### sanoid 管本地 snapshot retention NAS `/etc/sanoid/sanoid.conf`: ```ini [tank/photos] use_template = production recursive = yes [template_production] hourly = 36 daily = 30 monthly = 12 yearly = 3 autosnap = yes autoprune = yes ``` cron / timer 跑: ```bash sudo systemctl enable --now sanoid.timer # 默认每 15 分钟检查 ``` 每小时打 snapshot,保留 36 小时;每天保留 30 天;每月保留 12 个月。 本地 snapshot 给"刚才误删了文件,立刻回来"用;syncoid 异地同步给"机器 没了"用。 ### 自动化 syncoid `/etc/systemd/system/syncoid-nas.service`: ```ini [Unit] Description=ZFS replicate tank/photos to vps After=network-online.target [Service] Type=oneshot ExecStart=/usr/sbin/syncoid \ --quiet \ -i /root/.ssh/syncoid \ tank/photos \ [email protected]:backup-pool/nas/photos ExecStartPost=-/usr/local/sbin/notify-success.sh syncoid [email protected] ``` `/etc/systemd/system/syncoid-nas.timer`: ```ini [Timer] OnCalendar=*-*-* 02:30:00 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target ``` ```bash sudo systemctl enable --now syncoid-nas.timer ``` ### 校验异地数据 VPS: ```bash sudo zfs list -t snapshot backup-pool/nas/photos | tail -5 # 应当看到最近 syncoid_* snapshot sudo zpool scrub backup-pool # 每月一次校验 ``` ### 还原演练(重要) 最佳实践:每季度做一次"模拟 NAS 没了"演练。 从 VPS 拉回最近 snapshot: ```bash ssh nas sudo zfs send -R zfsbackup@vps:backup-pool/nas/photos@syncoid_2026_05_20 \ | sudo zfs receive -F tank/photos-restored ``` 确认能用 + 文件完整 + 权限对。**演练过的备份才是真备份**。 ## 效果 - 4TB 数据每天增量同步,平均增量 100-500 MB(取决于使用量) - 上传跑半小时-1 小时(看带宽),完全在低峰期 02:30 - 本地误删 → ZFS snapshot 秒级恢复 - NAS 全挂 → VPS 拉回完整数据,零数据丢失 - VPS 端 zstd 压缩让 4TB 实际占 ~2.4 TB(10 美元 / 月的 storage VPS 够用) ## 踩过的坑 1. **第一次全量同步卡 ssh 限速**:default OpenSSH 在某些 distro 上有 ChaCha20 加密的 CPU 瓶颈。改 `[email protected]` 加密算法 能快 2-3 倍:`syncoid --sshcipher [email protected]`。 2. **远端 dataset 没存在**:syncoid 不会自动建 parent dataset。 先手动 `zfs create backup-pool/nas` 建好。 3. **snapshot 过多**:sanoid 没配 retention 时 snapshot 几千个, `zfs list` 都慢。一定配 `autoprune = yes`。 4. **clock skew**:源端 / 目标端时间差大于 1 分钟,syncoid 偶尔报 "common ancestor" 找错。两端配 chrony 同 NTP。 5. **加密 dataset 同步**:源是 encrypted dataset,要加 `--sendoptions=w` 送 raw encrypted stream,远端不需要密码就能存。

rsync over SSH 做带校验的增量备份(含硬链接快照)

rsync 是最稳的小型备份方案:传输层 SSH 已加密,增量算法只传 delta, 配合 `--link-dest` 可以在目标侧做"硬链接快照"——每个快照看起来像 完整目录,磁盘占用却只算改动量。 适用场景:单机或少量机器的备份目标是另一台 Linux 主机 / NAS。 量大或对 dedup 要求高时换 Restic / Borg。 ## 备份脚本 ```bash #!/usr/bin/env bash # /usr/local/sbin/snap-backup.sh set -euo pipefail SRC="/srv/app /var/log /etc" DEST_HOST="[email protected]" DEST_BASE="/volume1/backups/$(hostname -s)" STAMP="$(date +%Y%m%d-%H%M%S)" LATEST="${DEST_BASE}/latest" NEW="${DEST_BASE}/${STAMP}" ssh "${DEST_HOST}" "mkdir -p '${DEST_BASE}'" rsync -aAXH --delete --numeric-ids \ --link-dest="${LATEST}" \ --rsync-path="ionice -c 3 rsync" \ --info=stats2,progress2 \ ${SRC} "${DEST_HOST}:${NEW}/" ssh "${DEST_HOST}" "rm -f '${LATEST}' && ln -sfn '${STAMP}' '${LATEST}'" # 保留最近 14 个快照 ssh "${DEST_HOST}" " cd '${DEST_BASE}' \ && ls -1 | grep -E '^[0-9]{8}-[0-9]{6}\$' | sort | head -n -14 \ | xargs -r -I{} rm -rf -- {} " ``` 关键参数: - `-aAXH`:等价于 `-rlptgoD` + 保留 ACL、xattr、硬链接关系 - `--numeric-ids`:用数字 UID/GID 而不是名字,跨机器一致性更好 - `--link-dest`:与上一次快照做硬链接,未改的文件几乎不占空间 - `--rsync-path="ionice -c 3 rsync"`:远端 rsync 进程降到 idle I/O, 备份时段不影响其它服务 ## 校验 ```bash # 远端校验 checksum 是否匹配(慢,每月一次足够) rsync -aAXH --dry-run --checksum --itemize-changes \ ${SRC} "${DEST_HOST}:${LATEST}/" | head -20 # 任何输出都意味着源 vs 备份不一致,需要排查 ``` ## SSH 配置 避免脚本里硬编码密钥路径,统一在 SSH config: ``` # ~/.ssh/config(或 /root/.ssh/config) Host nas.example.com User backup IdentityFile ~/.ssh/id_backup_ed25519 IdentitiesOnly yes ``` NAS 上限制这个 key 只能跑 rsync: ``` # 远端 ~backup/.ssh/authorized_keys command="rsync --server -vlogDtprAXe.iLsfxC --numeric-ids --delete --link-dest=* . /volume1/backups/*",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... ``` ## systemd timer ```ini # /etc/systemd/system/snap-backup.timer [Timer] OnCalendar=*-*-* 02:00:00 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target ``` ## 踩过的坑 - `--link-dest` 路径必须是 **目标侧** 的绝对路径,而且当时已存在 上一次快照目录;第一次跑没快照时 link-dest 自然失效,rsync 会回退到 完整拷贝,正常。 - 备份 `/etc` 不开 `-H` 时,sudoers / cron / 各种 symlink 容易脱钩, 恢复时一头雾水。 - `--delete` 是双刃剑:源被误删后下次备份会同步删掉目标。所以快照轮转 保留多个版本很重要。

borg vs restic vs kopia:三个加密增量备份工具实测对比

## 起因 之前文章分别介绍过 borg 和 restic。一个朋友问"我应该选哪个?" 干脆做一个对比实测:同样数据集 + 同样备份策略,跑出真实数字。 三个候选: - **borg**:Python 写的,2014 起,老牌 - **restic**:Go 写的,2015 起,现代 - **kopia**:Go 写的,2018 起,最年轻,UI 最强 三者都支持:客户端加密、去重、增量、压缩、多 backend。 ## 测试 setup - 数据:500 GB(混合:1M 个文件 + 几个大 VM disk) - 机器:8 core / 32 GB / NVMe SSD - 后端:本地磁盘(避免网络变量) - 跑 5 次取均值 ## 1. 首次全量备份 | | 时间 | 备份大小 | CPU 峰值 | RAM 峰值 | |---|---|---|---|---| | borg | 38 min | 195 GB | 800% (8 core) | 1.2 GB | | restic | 31 min | 198 GB | 700% | 1.8 GB | | kopia | 24 min | 180 GB | 850% | 2.4 GB | kopia 最快 + 最小(zstd 默认压缩级别更激进)。 borg / restic 接近。 ## 2. 增量备份(改动 5GB) | | 时间 | 新写入 | |---|---|---| | borg | 1m 50s | 1.8 GB | | restic | 1m 20s | 2.0 GB | | kopia | 0m 55s | 1.5 GB | kopia 增量也最快。 ## 3. 还原性能(取 10 个文件) | | 时间 | |---|---| | borg | 4s | | restic | 2s | | kopia | 2s | borg 单文件还原稍慢(Python 启动开销)。 ## 4. 还原全量 | | 时间 | |---|---| | borg | 32 min | | restic | 25 min | | kopia | 22 min | 跟备份时间类似的趋势。 ## 5. 仓库 check(校验完整性) | | 时间 | |---|---| | borg check | 8 min | | restic check --read-data | 18 min | | kopia maintenance | 6 min | borg / kopia 校验快。restic 默认 check 不读 data;带 `--read-data` 慢。 ## 6. 大量小文件场景(100 万个 1KB 文件) | | 备份时间 | |---|---| | borg | 14 min | | restic | 12 min | | kopia | 9 min | kopia 处理 small files 最好。 ## 7. UI / 易用性 ### borg:CLI only ```bash borg init -e repokey /backup/repo borg create /backup/repo::$(date +%F) /home /etc borg list /backup/repo borg extract /backup/repo::2024-05-24 home/me/important.txt ``` `borgmatic` 是它的 YAML wrapper。无原生 GUI。 ### restic:CLI + fuse mount ```bash restic init -r /backup/repo restic backup -r /backup/repo /home /etc restic snapshots restic mount /tmp/r # FUSE 挂载浏览 ``` 无 GUI 但 fuse mount 让"浏览历史快照" 很方便。 ### kopia:CLI + Web UI + 跨平台 GUI ```bash kopia repository create filesystem --path=/backup/repo kopia snapshot create /home /etc kopia snapshot list kopia server start --address=0.0.0.0:51515 --insecure # Web UI ``` Web UI 浏览快照 / 还原 / 管理 policy 都可视化。 还有 Windows / Mac 客户端 GUI。 ## 8. 多客户端共享仓库 多机器备份到同一仓库(共享 dedup 池): | | 支持 | 体验 | |---|---|---| | borg | ❌ 单写者(一次一客户端) | 复杂:需 borg serve + lock | | restic | ✅ 多客户端 | 简单 | | kopia | ✅ 多客户端 | 简单(甚至有 P2P 模式) | 家庭多设备 / 公司多服务器场景 restic / kopia 友好。 ## 9. 远程后端支持 | | 本地 | SSH | S3 | B2 | GCS | Azure | rclone | |---|---|---|---|---|---|---|---| | borg | ✅ | ✅ | rclone 代理 | rclone | rclone | rclone | ✅ | | restic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | kopia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | webdav | restic / kopia 原生支持云存储更全。 ## 10. 安全 / 加密 三者都 AES + 客户端加密 + 服务端只看到加密块。 | | 加密算法 | 密钥管理 | |---|---|---| | borg | AES-256-CTR + HMAC-SHA256 | keyfile / repokey | | restic | AES-256-CTR + Poly1305-AES | 内置 config | | kopia | AES-256-GCM | 多 key 支持 | 都安全。kopia 用 AEAD 更现代。 ## 11. 资源占用对比 长期运行的 inactive 仓库: | | 元数据大小 | 看快照速度 | |---|---|---| | borg | 中(chunks index) | 中 | | restic | 中 | 慢(大 repo 时) | | kopia | 小 | 快 | restic 在 TB 级仓库的"列出快照"操作会慢,是个已知问题。 ## 选择决策 | 场景 | 推荐 | |---|---| | 单机 / Linux 老手 / 已经在用 | borg(稳定 + 成熟) | | 多机器 + 云后端 + 团队 | restic(生态最广) | | 喜欢 GUI + 跨平台 + 多设备共享 | **kopia**(最现代) | | 不想折腾 | restic | | 极致性能 | kopia | 我个人现在新项目首选 kopia,遗留项目继续 restic / borg。 ## 共同 best practice 不管选哪个: 1. **测试还原**:每季度真的还原一次到不同机器,验证流程能通 2. **3-2-1 规则**:3 份数据,2 种媒介,1 份异地 3. **加密 key 单独备份**:repo 备份了但 key 丢了 = 数据死透了 4. **自动 prune 策略**:保留 7d / 4w / 12m 之类 5. **monitor 告警**:备份失败要立刻知道(systemd OnFailure / healthchecks.io) ## 配置示例:kopia + B2 ```bash # 第一次 kopia repository create b2 \ --bucket=my-backups \ --key-id=00abc... \ --key=K00... \ --password=very-strong-password # 定 policy kopia policy set --global \ --keep-latest 10 \ --keep-hourly 24 \ --keep-daily 30 \ --keep-weekly 12 \ --keep-monthly 24 \ --keep-annual 5 \ --compression=zstd # 备份 kopia snapshot create /etc /home/me/important /srv # 定时 # systemd timer 每小时 ``` ## 踩过的坑 1. **borg 单写锁卡死**:多机器同时备份 → 第二个等待 / 失败。 解决:每机一独立 repo,或者错峰备份。 2. **restic prune 慢**:TB 级仓库 prune 几小时。改 `forget --keep-* --prune-max-unused 5%` 增量 prune。 3. **kopia metadata 损坏**:仓库被 unsafe shutdown → 偶尔 corruption。 `kopia maintenance --safety=full` 修复。 4. **三者都 case-sensitive**:Windows / Mac 用户备份大小写不敏感 文件系统,恢复到 Linux 时可能冲突。 5. **加密密码丢了**:**没有 recovery**。所有这类工具都设计成"你忘 密码 = 数据死透了"。一定要写在 password manager + 离线纸质备份。

用 strace 排查应用启动卡死 / 慢

应用启动卡了 30 秒、跑命令显示半天没动静、systemd 报 timeout —— 不知道 卡在哪步时,strace 能直接告诉你进程在做什么 syscall。 90% 的"卡"是某个 syscall 在等待:DNS / 网络连接 / 文件锁 / 互斥锁 / fsync。 ## 基本用法 ```bash # 跑一个新进程,观察所有 syscall strace -f -tt -o /tmp/trace.log ./myapp # attach 到正在运行的进程 strace -f -tt -p <PID> -o /tmp/trace.log # Ctrl-C 停(被 attach 的进程继续跑) ``` 关键参数: - `-f`:跟踪 fork 出来的子进程(多线程 / 多进程应用必选) - `-tt`:每行加微秒时间戳(看延迟必备) - `-o file`:写文件而不是混进 stdout - `-T`:每个 syscall 的耗时(找慢调用神器) - `-e trace=...`:只看某类 syscall 常见过滤: ```bash # 只看网络相关 strace -f -e trace=network -p <PID> # 只看文件 I/O strace -f -e trace=file -p <PID> # 只看慢的(> 100ms) strace -f -T -p <PID> 2>&1 | awk '/<[0-9]+\.[0-9]+>/{ split($NF, a, "<|>"); if (a[2]+0 > 0.1) print }' ``` ## 案例 1:应用启动卡 30 秒 ```bash strace -f -tt -T -o /tmp/start.log ./myapp # ... 30 秒后启动完成 sort -k1 /tmp/start.log | grep -E '\<[0-9]\.[0-9]+\>' | sort -k2 -t'<' -nr | head ``` 或者直接看时间戳跳变: ```bash awk '{print $1, $0}' /tmp/start.log | awk ' NR==1{prev=$1; next} { diff=$1-prev if (diff > 1) print "*** gap " diff "s at " $0 prev=$1 }' /tmp/start.log ``` 常见根因: - `connect(... 192.168.x.x:53 ...)` 后 timeout 几秒 → DNS 配置错或者 resolv.conf 第一个 server 不可达 - `openat(... /etc/...locked) = -1 EAGAIN` 反复重试 → 文件锁等其它进程 - `futex(... FUTEX_WAIT ...)` 长时间不返回 → 库内部 mutex 死锁 - `read(socket, ...)` 长时间阻塞 → 下游响应慢 ## 案例 2:DNS 慢 ```bash strace -f -e trace=network -tt curl http://api.example.com/ 2>&1 | head -30 # 11:23:45.001 socket(AF_INET, SOCK_DGRAM, 0) = 5 # 11:23:45.002 connect(5, {sa_family=AF_INET, sin_addr=8.8.8.8, ...}, 16) = 0 # 11:23:45.003 sendto(5, ...) = 32 # DNS query # 11:23:50.003 sendto(5, ...) = 32 # 5 秒后重试,第一次超时了 ``` 5 秒间隙说明第一个 nameserver 不响应。改 `/etc/resolv.conf`: ``` nameserver 1.1.1.1 nameserver 8.8.8.8 options timeout:1 attempts:2 ``` `timeout:1` 把单次 DNS 超时从默认 5 秒降到 1 秒。 ## 案例 3:找文件被打开了多少次 ```bash strace -f -e trace=openat -o /tmp/o.log ./myapp grep -oE '"[^"]+"' /tmp/o.log | sort | uniq -c | sort -rn | head # 832 "/etc/ld.so.cache" # 412 "/usr/lib/x86_64-linux-gnu/libc.so.6" # 158 "/proc/self/maps" # ... ``` 发现某个文件被 open 几百上千次 → 可能是配置文件没缓存、或者插件加载死循环。 ## strace vs ltrace vs perf | 工具 | 看什么 | 典型场景 | |---|---|---| | strace | syscalls | I/O 慢、卡 syscall、文件路径错 | | ltrace | 动态库函数调用 | 怀疑某个 libc 函数 / glib 函数行为 | | perf | CPU 采样 | CPU 100% 找热点函数 | | bpftrace | 几乎一切 | 复杂的内核态行为 | ## 性能影响 strace 会 **大幅** 拖慢被跟踪进程(每个 syscall 加上 ptrace 上下文切换)。 高并发服务上用 `-e ...` 严格限制范围,或者用 `bpftrace` 这种基于 eBPF 的"无停顿"工具。 ## 高级:只在某行附近抓 ```bash # 进程已经卡 5 分钟了,attach 抓 30 秒 strace -f -tt -p <PID> -o /tmp/probe.log & sleep 30 kill %1 ``` ## 踩过的坑 - 容器内可能没装 strace;`apt install strace` 在精简镜像装不上时, 从宿主 `nsenter` 进容器跑也行: ```bash sudo nsenter -t <PID> -p -m -n strace -f -p <PID> ``` - strace 输出的字符串默认截断 32 字符,要看完整路径加 `-s 256`。 - 多线程进程 attach 时 `-f` 会顺带跟所有线程,量大可能淹掉日志, 缩小范围用 `-e trace=...`。 - 不要在生产数据库上 attach strace —— 可能把 IO 慢到触发副节点切换 / 超时回滚。先看 perf / eBPF。

rsync over ZFS:用 snapshot 做"事务性"备份(避免备份到一半客户端写改)

## 起因 我们的备份服务器收 rsync from 多台业务机器。问题: - rsync 跑到一半,业务机继续写新文件 → 备份目录里出现"part-A from 12:00, part-B from 12:15" 时间错乱的版本 - 备份过程中如果客户端机器挂了 → 备份目录是半新半旧 - 想"备份这一刻的整盘快照" 而不是"几小时内分散的快照" 如果备份目标是 ZFS 文件系统,可以利用 ZFS snapshot 做"事务性"备份: 1. rsync 完整跑完 2. 跑完后立即 zfs snapshot 3. 之后业务继续 rsync 上新数据 4. snapshot 是这一刻完整一致的副本 ## 完整流程 ### setup 备份服务器 NAS 上: ```bash # 创建一个 ZFS dataset 收备份 sudo zfs create backuptank/clients/server-foo sudo zfs set compression=zstd backuptank/clients/server-foo sudo zfs set atime=off backuptank/clients/server-foo # 给 rsync 用户写权限 sudo zfs allow rsync-user create,destroy,snapshot,mount backuptank/clients/server-foo ``` ### 业务机推送 + snapshot 脚本 ```bash #!/usr/bin/env bash # /usr/local/sbin/snap-rsync.sh set -euo pipefail REMOTE_USER=rsync-user REMOTE_HOST=nas.local REMOTE_DATASET=backuptank/clients/server-foo REMOTE_MOUNT=/backuptank/clients/server-foo SRC="/etc /home /srv" HOSTNAME=$(hostname -s) TS=$(date +%Y-%m-%dT%H-%M-%S) # 1. 同步过去(增量;--delete 保镜像) rsync -aAXH --numeric-ids --delete --info=stats2,progress2 \ -e ssh \ $SRC "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_MOUNT}/${HOSTNAME}/" # 2. 完成后远程 zfs snapshot 标记这一刻 ssh "${REMOTE_USER}@${REMOTE_HOST}" \ "sudo zfs snapshot ${REMOTE_DATASET}@${HOSTNAME}-${TS}" echo "snapshot: ${REMOTE_DATASET}@${HOSTNAME}-${TS}" # 3. retention:保留最近 30 个 snapshot ssh "${REMOTE_USER}@${REMOTE_HOST}" " sudo zfs list -H -o name -t snapshot ${REMOTE_DATASET} \ | grep '@${HOSTNAME}-' \ | sort -r \ | tail -n +31 \ | xargs -r -n 1 sudo zfs destroy " ``` 每天 cron / timer 跑这个。 ### 浏览 / 还原历史快照 NAS 上 ZFS snapshot 默认可访问: ```bash ls /backuptank/clients/server-foo/.zfs/snapshot/ # server-foo-2024-05-24T03-00-00 # server-foo-2024-05-23T03-00-00 # ... # 还原某个文件 cp /backuptank/clients/server-foo/.zfs/snapshot/server-foo-2024-05-24T03-00-00/etc/nginx/nginx.conf /tmp/ ``` 或者 `zfs clone` 把 snapshot 挂成可写副本: ```bash sudo zfs clone backuptank/clients/server-foo@server-foo-2024-05-24T03-00-00 \ backuptank/restore/server-foo-2024-05-24 mount | grep restore ``` 任意时间点的完整目录树都能挂载浏览。 ## ZFS dedup(可选) 如果多机器备份内容重叠多(OS 文件大体相同): ```bash sudo zfs set dedup=on backuptank/clients ``` 代价:每 1 TB dedup 数据约需 5 GB RAM 维护 dedup table。 所以只在高 dedup ratio + 充裕 RAM 时开。 测: ```bash sudo zdb -S backuptank # 输出 estimated dedup ratio:1.5x 以上才值得开 ``` ## ZFS send:异地同步备份 ```bash # 第一次全量 → 异地 NAS sudo zfs send backuptank/clients/server-foo@latest \ | ssh remote-nas "sudo zfs receive backuptank-mirror/server-foo" # 后续增量 sudo zfs send -i @prev-snap backuptank/clients/server-foo@new-snap \ | ssh remote-nas "sudo zfs receive backuptank-mirror/server-foo" ``` 或者 syncoid 自动: ```bash syncoid backuptank/clients/server-foo remote-nas:backuptank-mirror/server-foo ``` 每天本地 rsync + snapshot;每周 syncoid 异地。**两层保护**。 ## 与"直接备到 ZFS snapshot" 的对比 替代方案:客户端不 rsync,业务机自己 ZFS snapshot + zfs send 给 NAS。 ```bash # 业务机 sudo zfs snapshot tank/data@$(date +%F) sudo zfs send -i @prev tank/data@new \ | ssh nas "sudo zfs receive backuptank/clients/foo" ``` 优点: - 原生 ZFS 一致性快照(毫秒级 frozen) - 增量 send 只传变化的 block(比 rsync 比 file 快很多) - 文件系统级,不漏 metadata / xattr / hardlink 要求:业务机文件系统是 ZFS。 如果业务机是 ext4 / xfs → rsync + 备份目标 ZFS snapshot 是 fallback。 ## monitoring ```bash # 看 snapshot 数 + 大小 zfs list -t snapshot backuptank/clients/server-foo # Prometheus exporter (node_exporter zfs collector) # 暴露:node_zfs_zpool_state{pool="backuptank"} = 1 (ONLINE) # node_zfs_zfs_dataset_used_bytes # node_zfs_zfs_dataset_available_bytes ``` 仪表盘看: - snapshot 数量是否正常增长 - 各客户端的备份目录大小变化 - 最新 snapshot 时间戳(确认 cron 跑了) 告警:snapshot 超过 26 小时没新 = 备份链断了。 ## retention 策略:sanoid 风格 手写 `tail -n +31` 简单但只按计数。sanoid 风格按时间精细: ```bash # 保留: # - 最近 7 个 hourly # - 最近 30 个 daily # - 最近 12 个 monthly snapshots=$(ssh nas "sudo zfs list -H -o name -t snapshot backuptank/clients/foo") # 分组按时间删 # (实际写起来复杂;建议用 sanoid 现成工具) ``` sanoid 配 retention policy + 跨多 dataset 统一管。装好后写一个 `sanoid.conf` 完事。 ## 实战效果 我们 20 台业务机每天备份到一台 NAS: | | rsync only | rsync + ZFS snapshot | |---|---|---| | 单次备份耗时 | 30 min | 30 min | | 一致性 | 半天的飘移 | 时刻精确 | | 历史版本 | 没有 | 30 天日级 | | 占用空间 | 增量 | 增量 + dedup 后接近 | | 还原历史文件 | 困难 | `cp /.zfs/...` | ZFS snapshot 几乎免费给增量备份"加时间维度"。 ## 与商业方案对比 | | rsync + ZFS | Duplicati | restic to S3 | Veeam | |---|---|---|---|---| | 价格 | 免费 | 免费 | 免费 + 存储费 | 商业 | | 客户端加密 | 否(依靠传输 ssh) | ✅ | ✅ | ✅ | | 时间点恢复 | ✅(snapshot) | ✅ | ✅ | ✅ | | Web UI | 无 | 有 | 第三方 | ✅ | | 学习曲线 | 中 | 低 | 低 | 中 | 家用 / 小公司 + 有 ZFS NAS → 这套最便宜 + 最快。 ## 踩过的坑 1. **`sudo zfs snapshot` 没权限**:rsync 用户没 zfs allow。 `sudo zfs allow rsync-user create,destroy,snapshot ...`。 2. **snapshot 名字有冲突**:同一秒两次备份生成同名 snapshot 失败。 用毫秒级或随机 suffix。 3. **`--delete` 删了重要文件**:业务机误删 → rsync 同步删 → 但 ZFS snapshot 保住前一刻状态。**snapshot 是真正救命**。 4. **未控制保留 → snapshot 爆**:1 年 = 365 个 daily snapshot, 每 snapshot 元数据几 MB → 几 GB 元数据。 sanoid 策略限制数量。 5. **NAS 空间满 → 客户端 rsync 失败**:监控 ZFS pool 利用率, 超过 80% 告警。

用 Restic 加密备份到 S3 兼容存储(含还原演练)

Restic 是单文件 Go 二进制,做客户端加密 + 内容寻址 dedup + 增量备份。 对比 rsync 优势: - 加密在客户端完成,存储端只能看到加密块 - 强 dedup:跨快照 / 跨主机 / 跨文件位置都生效 - 任何 S3 兼容存储(AWS S3 / Backblaze B2 / Wasabi / minio / Cloudflare R2)都支持 - 单二进制零运行时依赖 ## 安装 ```bash # Debian / Ubuntu 自带的版本一般偏旧,建议直接装官方二进制 RESTIC_VER=0.17.3 ARCH=$(dpkg --print-architecture) # amd64 / arm64 curl -fsSL "https://github.com/restic/restic/releases/download/v${RESTIC_VER}/restic_${RESTIC_VER}_linux_${ARCH}.bz2" \ | bunzip2 > /usr/local/bin/restic sudo chmod +x /usr/local/bin/restic restic version ``` ## 初始化仓库(一次) 以 Backblaze B2 为例(最便宜的 S3-API 兼容方案之一): ```bash export B2_ACCOUNT_ID=... export B2_ACCOUNT_KEY=... export RESTIC_REPOSITORY=b2:my-bucket:/host-foo export RESTIC_PASSWORD=$(openssl rand -base64 32 | tr -d '\n') echo "$RESTIC_PASSWORD" | sudo tee /etc/restic.pw && sudo chmod 600 /etc/restic.pw restic init ``` **`RESTIC_PASSWORD` 丢了仓库就回不来了**,没有任何官方 recover —— 一定要 把它存到一个独立的 password manager / 离线纸质备份。 ## 备份脚本 ```bash #!/usr/bin/env bash # /usr/local/sbin/restic-backup.sh set -euo pipefail export B2_ACCOUNT_ID=... export B2_ACCOUNT_KEY=... export RESTIC_REPOSITORY=b2:my-bucket:/host-$(hostname -s) export RESTIC_PASSWORD_FILE=/etc/restic.pw restic backup \ --tag daily \ --exclude-file /etc/restic.exclude \ /etc /srv /home/yourname/projects # 保留策略:每日 7、周 4、月 12、年 5 restic forget --prune \ --keep-daily 7 --keep-weekly 4 \ --keep-monthly 12 --keep-yearly 5 # 完整性校验(采样 10%,全量太慢) restic check --read-data-subset=10% ``` `/etc/restic.exclude` 例: ``` node_modules __pycache__ *.pyc .cache .venv venv target build dist ``` ## systemd timer ```ini # /etc/systemd/system/restic-backup.timer [Timer] OnCalendar=*-*-* 02:30:00 RandomizedDelaySec=1h Persistent=true [Install] WantedBy=timers.target ``` ## 还原演练(重要——做一次) ```bash # 列出所有快照 restic snapshots # 列某个快照的文件 restic ls latest /etc # 还原某文件到临时目录 restic restore latest --include /etc/nginx --target /tmp/restore-test # 直接挂载为只读 FUSE mkdir /tmp/rmount restic mount /tmp/rmount & ls /tmp/rmount/snapshots/ fusermount -u /tmp/rmount ``` **至少每季度做一次完整还原演练**到一台不同的机器,确认凭据和流程都对。 未经演练的备份等于没有备份。 ## 监控 加在脚本末尾: ```bash # 通知 healthchecks.io 跑完了 curl -fsSL --retry 3 "https://hc-ping.com/<uuid>" # 或者推 Prometheus pushgateway cat <<EOF | curl --data-binary @- http://pushgw:9091/metrics/job/restic/host/$(hostname) restic_backup_last_success_time $(date +%s) restic_backup_last_size_bytes $(restic stats latest --mode raw-data --json | jq .total_size) EOF ``` ## 踩过的坑 - 第一次备份会很慢(全量上传),后续增量秒级;不要担心。 - `restic prune` 在大仓库(TB 级)上非常慢且 I/O 密集;改成 `restic forget --keep-* --prune-max-unused 5%` 限制每次 prune 范围。 - 仓库锁残留:客户端被 kill 后会留 `lock-*` 文件,下次报"repo is locked"。 确认没有其他进程后 `restic unlock`。 - B2 / R2 的 API 调用收费在小文件场景容易超出预期,配 `restic backup --pack-size 32` 增加 pack 大小(默认 16 MB)能减少调用次数。

ZFS ARC 内存吃光的诊断 + 调优(家庭 NAS 16GB 内存够用)

## 起因 家用 NAS 16GB RAM,跑 ZFS + 几个 Docker 容器(Plex / Photoview / Syncthing)。`free -h` 显示 `used 15.2G`,容器频繁因 OOM 被重启。 但 `htop` 加起来所有进程才用 5GB。剩下 10GB 是谁吃的? 答案:ZFS ARC(Adaptive Replacement Cache,磁盘 cache)。 ZFS 默认抢半数物理内存做 cache,且不像 page cache 那样有 application 请求时立刻让出来。 ## 诊断 ### 看 ARC 实际占用 ```bash arc_summary | head -30 # 或: cat /proc/spl/kstat/zfs/arcstats | grep -E '^(c|c_max|c_min|size|hits|misses)' ``` 输出例: ``` size = 9.8 GB c = 10.4 GB c_max = 10.4 GB # arc 上限(默认 RAM 一半) c_min = 660 MB hits = 24351234 misses = 234567 # hit rate ~ 99% ``` `size` 是当前 ARC 占用。`c_max` 是上限——ZFS 不会让 ARC 超过这个值, 但会贪婪地用到这个值。 ### 看分类 ```bash arc_summary | grep -A 20 'ARC size' # ARC size (current): # MFU: 6.2 GB # MRU: 3.5 GB # Anon: 50 MB ``` MFU = most frequently used;MRU = most recently used。 ### `free` 看 ARC 在哪一栏 ```bash free -h # total used free shared buff/cache available # Mem: 15Gi 5.0Gi 0.2Gi 320Mi 9.8Gi 4.5Gi ``` ARC 主要算在 `used` 而非 `buff/cache`(与一般 page cache 不同)。 `available` 列才反映"应用真正可拿到多少内存"——这个 4.5GB 还行, 但 ZFS 释放 ARC 给 application 不是瞬间的,高内存压力时仍可能 OOM。 ## 解决方案 ### 1. 调小 ARC 上限 临时(重启失效): ```bash # 限到 4GB echo 4294967296 | sudo tee /sys/module/zfs/parameters/zfs_arc_max ``` 持久(modprobe 配置 + 重新生成 initramfs): ```bash echo 'options zfs zfs_arc_max=4294967296' | sudo tee /etc/modprobe.d/zfs.conf echo 'options zfs zfs_arc_min=1073741824' | sudo tee -a /etc/modprobe.d/zfs.conf sudo update-initramfs -u sudo reboot ``` `zfs_arc_max` 单位 byte。4GB = `4 * 1024^3 = 4294967296`。 ### 2. 设 ARC 收缩门槛(让 ARC 更快让位) ```bash # 系统 free memory < 256MB 时立刻收缩 ARC echo 268435456 | sudo tee /sys/module/zfs/parameters/zfs_arc_sys_free ``` 默认 ZFS 收缩很慢,对突发负载不友好。这条让 ARC 在内存紧张时更积极 释放。 ### 3. 实际数据评估"够不够" ```bash arc_summary | grep -A 5 'Cache hits' # Cache hits: # Total hits: 24.3M # Cache miss ratio: 1.0% ``` > 99% hit rate 说明现有 ARC 大小 OK,缩小一点不会明显影响性能。 < 90% 说明 ARC 缺,缩小会让磁盘 IO 增加。 ### 4. 应用专门的 prefetch 调优 ```bash # 预读(顺序读多 sample) echo 0 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable # = 0 启用 prefetch;某些场景关掉省内存 # L2ARC:用 SSD 作二级缓存(家用通常没必要) sudo zpool add tank cache /dev/nvme0n1p3 ``` L2ARC 把"放不下 RAM 的 ARC 内容"扩展到 SSD。代价是 RAM 里要存 L2ARC 的元数据(约每 1GB L2ARC 占 25MB RAM)。 ### 5. 给容器 / 重要服务设 cgroup 内存保证 ```ini # /etc/systemd/system/docker.service.d/memory.conf [Service] MemoryHigh=10G MemoryLow=2G # 内存紧张时优先保住 docker 这 2GB ``` `MemoryLow` 是"低水位保护"——内核回收内存时不会减这个 cgroup 低于 2GB(除非全系统真没内存)。 ```bash sudo systemctl daemon-reload sudo systemctl restart docker ``` ## 效果 我家 NAS 调整后: - 设 `zfs_arc_max=4G`,ARC 从 10GB 缩到 4GB - 释放 6GB 给 Docker 容器,OOM 消失 - ZFS cache hit rate 从 99.2% → 96.5%(轻微下降) - 磁盘 IO 从 5 MB/s 平均 → 12 MB/s(更频繁回原盘读) - 但容器响应延迟稳定,体验明显改善 **结论**:家用 NAS 16GB / 32GB 内存,ARC 给 1/4 物理内存合适。 ZFS 默认 1/2 是为"纯 NAS"设计的,混合负载机器要调。 ## 何时不调 ARC 服务器是 dedicated ZFS file server(不跑别的)→ ARC 越大越好, hit rate 决定性能。 服务器跑 PostgreSQL / 任何强 I/O DB → DB 自己也想要 RAM 做 buffer pool。 两者抢内存。建议给 DB 60% RAM、ARC 30% RAM,剩 10% OS。 ## 看 cache 效果 ```bash # arcstat 实时 sudo apt install zfsutils-linux arcstat 5 # time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c # 10:00 1.5K 2 0 2 0 0 0 1 0 4.0G 4.0G # ... # 第一列 read 是每秒读次数;miss% 越低越好 ``` ## 踩过的坑 1. **改 zfs.conf 后没 update-initramfs**:ZFS 模块在 initramfs 里 被加载,外面 conf 不生效。`update-initramfs -u` + reboot。 2. **`free` 误判**:很多人看到 ARC 占内存以为是 leak,去 kill 进程 也没用。`arc_summary` 才说清楚。 3. **L2ARC 放 HDD 上**:完全没意义,L2 比主磁盘还慢。L2ARC 必须 SSD 或 NVMe。 4. **ZIL(SLOG)vs L2ARC 混淆**:ZIL 是同步写日志加速(SSD 推荐), L2ARC 是读缓存扩展。不一样。 5. **swap 不要放 zvol 上**:ZFS 的写时复制 + 内存压力时需要 swap → 死锁。如果用 ZFS root,swap 单独建普通分区。

ZFS vs btrfs:家用 NAS 选哪个文件系统

## 起因 新装 NAS 4 块 4TB 硬盘。选 file system 时纠结 ZFS vs btrfs。 都支持:snapshot / compression / dedup / RAID / checksum。 但底层设计 + 成熟度 + 稳定性差异不小。下面是我做完功课的对比。 ## 共同特性 - 文件系统级 checksum(侦测 bit rot) - transparent compression(zstd / lz4 / gzip) - snapshot + send/receive 增量 - pool / volume / dataset 抽象 - 多盘 RAID-like 配置 - copy-on-write 设计 ## 主要差异 ### 1. RAID **ZFS**: - mirror / raidz (raid5) / raidz2 (raid6) / raidz3 - pool 一旦创建后**不能扩容 vdev 内的盘**(v2024 改了,但有限制) - 加更多盘要新 vdev(stripe over vdev) **btrfs**: - single / dup / raid0 / raid1 / raid10 / raid5 / raid6 - 极灵活:随时加盘 / 移盘 / 改 RAID profile(balance 操作) - **raid5/6 仍然 NOT production ready**(这是 btrfs 最大坑) 家用 NAS 常见配置: - 4 块盘 + ZFS RAID-Z2:可挂 2 块(最佳容错) - 4 块盘 + btrfs RAID1:只挂 1 块;btrfs RAID1 是"每文件 2 副本" 而非"两组镜像",新颖但适配差 ### 2. 稳定性 / 数据安全 **ZFS**: - 2005 起 production,全球大企业用,**经受 20 年实战** - 数据完整性是设计第一原则 - bug 极少(且大多在 cutting-edge 功能) **btrfs**: - 2007 起,主线内核 2009+ - 单盘 / RAID0/1/10 稳,**RAID5/6 多年文档警告 not production** - 2024 ext4 maintainer 写过 "I still don't recommend btrfs raid5/6" - 多盘环境历史 bug 多于 ZFS 数据安全敏感 → ZFS。 ### 3. 内存需求 **ZFS**: - ARC (Adaptive Replacement Cache) 吃内存:1GB / 1TB rule of thumb - 16GB NAS + 16TB 数据:ARC 占 10GB + - 启用 dedup 更吃(每 1TB ~5GB RAM) - 可调整 `zfs_arc_max` **btrfs**: - 内存占用低很多 - 4GB RAM 也能稳定跑 8TB btrfs 老 NAS / 低 RAM 设备 → btrfs。 ### 4. 性能 随机读写:ZFS(with ARC)通常更快。 顺序读写:相近。 压缩:ZFS lz4 / zstd 性能略好。 但实际差距不大(除非 RAM 极充裕时 ZFS ARC 效果显著)。 ### 5. snapshot + send/receive 两者都有,用法相似: ```bash # ZFS zfs snapshot tank/data@daily-20240524 zfs send tank/data@yesterday tank/data@daily-20240524 | ssh remote 'zfs receive ...' # btrfs btrfs subvolume snapshot /mnt/data /mnt/data/.snapshots/daily-20240524 -r btrfs send -p /mnt/data/.snapshots/yesterday /mnt/data/.snapshots/daily-20240524 | ssh remote 'btrfs receive ...' ``` ZFS send 更成熟稳定。btrfs send/receive 有过历史 bug。 ### 6. 加密 **ZFS** 2.0+:内置 native encryption **btrfs**:依赖 LUKS 下层加密(不是 fs 级 native) ZFS 加密更灵活(per-dataset key)。 ### 7. 文件系统级 quota **ZFS**:quota 是基础特性,per-dataset / per-user **btrfs**:qgroup 复杂 + 历史 bug + 性能影响 需要 quota → ZFS。 ### 8. Linux kernel 集成 **ZFS**:不在 mainline kernel(CDDL vs GPL license 冲突)。 - 装 OpenZFS 模块(apt install zfsutils-linux) - 每次 kernel 升级需要 DKMS 重 build - Ubuntu 官方支持;其它 distro 看情况 **btrfs**:mainline kernel 内置,开箱即用 部署简单 → btrfs。 但 ZFS 装好后稳定,DKMS 自动重 build,不算大坑。 ### 9. 工具生态 | 任务 | ZFS | btrfs | |---|---|---| | GUI | Cockpit ZFS / TrueNAS | Cockpit btrfs / Snapper | | 备份 sync | syncoid + sanoid(极成熟) | btrbk | | 自动 snapshot | sanoid | snapper / btrbk | | pool 监控 | zpool status + zed | btrfs scrub status | ZFS 工具链更成熟(成熟得益于 Solaris 时代积累)。 ### 10. 修复 / 恢复 **ZFS**: - `zpool scrub` 自动修复有 redundancy 的损坏 - 严重时 `zpool import -F` 强制恢复 - 数据恢复工具:zdb(专家用) **btrfs**: - `btrfs scrub` 类似 - 严重时 `btrfs restore` 拉文件出来 - 但 RAID5/6 + 多盘损坏场景历史上有人 lose 数据 ZFS recover 工具更可靠。 ## 选择决策 | 场景 | 推荐 | |---|---| | 4+ 盘 + RAID5/6 + 关键数据 | **ZFS** (RAID-Z2) | | 2 盘 mirror | 都行(btrfs 略简单) | | 单盘大容量 | btrfs(CoW + snapshot) | | 内存紧张(< 4GB) | btrfs | | 内存充裕(> 16GB) | ZFS | | 笔记本 + Fedora | btrfs(mainline) | | 严格 quota / multi-tenant | ZFS | | 想随时改 RAID profile | btrfs | | 远程异地复制 | ZFS(syncoid 成熟) | ## 我的实际选择 家用 NAS(4 × 4TB):**ZFS RAID-Z2 + zstd 压缩** - 容忍 2 盘挂 - 16GB RAM 给 ARC 用 - 用 sanoid + syncoid 自动 snapshot + 远程异地 笔记本 Linux 根分区:**btrfs** - mainline,无 DKMS 折腾 - snapshot 频繁,Fedora 系自动 snapper ## 安装 ### ZFS on Ubuntu 22.04+ ```bash sudo apt install -y zfsutils-linux # 4 盘 RAID-Z2 + 4K aligned + lz4 默认 sudo zpool create -o ashift=12 \ -O compression=lz4 -O atime=off -O xattr=sa -O dnodesize=auto \ tank raidz2 \ /dev/disk/by-id/scsi-... \ /dev/disk/by-id/scsi-... \ /dev/disk/by-id/scsi-... \ /dev/disk/by-id/scsi-... ``` ### btrfs on Ubuntu / Fedora ```bash sudo apt install -y btrfs-progs # 4 盘 RAID10 sudo mkfs.btrfs -L data -m raid10 -d raid10 /dev/sdb /dev/sdc /dev/sdd /dev/sde sudo mkdir /mnt/data sudo mount -o compress=zstd:3,space_cache=v2 /dev/sdb /mnt/data ``` ## 常见操作对照 ```bash # 看状态 zpool status -v # ZFS btrfs filesystem usage /mnt/data # btrfs # 校验所有数据 zpool scrub tank btrfs scrub start /mnt/data # 加盘扩容 zpool add tank /dev/new-disk # 新 vdev (stripe) btrfs device add /dev/new-disk /mnt/data && btrfs balance start /mnt/data # snapshot zfs snapshot tank/data@snap btrfs subvolume snapshot /mnt/data /mnt/data/.snap/snap # 删除 snapshot zfs destroy tank/data@snap btrfs subvolume delete /mnt/data/.snap/snap # 看压缩比 zfs get compressratio tank compsize /mnt/data ``` ## 踩过的坑 ### ZFS 1. **ARC 吃光内存** → 容器 OOM。`zfs_arc_max` 调小。 2. **DKMS 升级 kernel 慢** → reboot 后 zpool 找不到。等 DKMS build 完 再 reboot。 3. **pool import 失败** → 用 disk by-id 不要 /dev/sdX(顺序会变)。 4. **dedup 慎用** → 5GB RAM / 1TB 数据,开了多数情况后悔。 ### btrfs 1. **RAID5/6 不要上生产**——文档明确警告。 2. **subvolume 嵌套乱** → snapshot 时易踩坑。清晰目录结构。 3. **balance 时机器抖** → 大量 IO 影响业务。低峰跑 + 限速。 4. **qgroup 启用后慢 5-10 倍** → 不需要 quota 就别开。 ## 多年使用感受 我个人 5+ 年家用 NAS 跑 ZFS: - 多次硬盘故障,RAID-Z2 透明处理 + 在线替换 - 月度 scrub 偶尔修复 bit rot(每次几 KB;硬盘老化的标志) - snapshot 救命过若干次:误删文件 / VM 改坏直接回滚 btrfs 笔记本 3 年: - snapshot 救过几次升级失败 - 偶尔 scrub 报错(无 redundancy 时无法 fix),重要数据靠云备份 - 性能 / 稳定够日常用 两个都好。选符合你 use case 的不犯错。

用 chrony 校准服务器时间(替代 ntpd / systemd-timesyncd)

时间不准的服务器会引发各种诡异 bug: - TLS 证书"还没生效"或"已过期" - Kerberos 认证失败 - 分布式日志时间错乱,没法 trace - 数据库主从复制时间戳混乱 `systemd-timesyncd` 是 Ubuntu 默认的 SNTP 客户端,能用但功能很基础 (只读 NTP,不会被别人查询,调整精度一般)。生产用 chrony, 小内存占用 + 快收敛 + 网络中断后的快速恢复。 ## 安装 + 切换 ```bash sudo systemctl disable --now systemd-timesyncd sudo apt install -y chrony sudo systemctl enable --now chrony # 校验当前用的是哪个 timedatectl # System clock synchronized: yes # NTP service: active ``` ## 配置 `/etc/chrony/chrony.conf` 主流 distro 自带的默认就能跑。 生产建议改: ```conf # 优先用 pool 而不是 server —— 自动负载均衡到多台 pool 2.debian.pool.ntp.org iburst maxsources 4 # 国内服务器换国内源 # pool cn.pool.ntp.org iburst maxsources 4 # server ntp.aliyun.com iburst # server ntp.tencent.com iburst # 启动后的前 10 个样本只要测量到偏差就立即跳调 makestep 1.0 10 # 系统时钟同步到 RTC 硬件时钟(关机后保留) rtcsync # 允许哪些客户端查询(如果本机也是 NTP server) # allow 192.168.0.0/16 # 默认拒绝,注释掉它就 client-only # 数据存储 driftfile /var/lib/chrony/chrony.drift makestep 1 3 keyfile /etc/chrony/chrony.keys ntsdumpdir /var/lib/chrony # 日志 logdir /var/log/chrony log measurements statistics tracking ``` `iburst` 是关键:启动时连发 8 个查询,秒级完成初始同步(默认每 64 秒一次 会慢得让人无奈)。 ## 校验 ```bash # 当前选用的服务器 + 偏差 chronyc tracking # Reference ID : C0A87B0F (ntp1.aliyun.com) # Stratum : 3 # Ref time (UTC) : Sat May 23 09:00:12 2026 # System time : 0.000001234 seconds slow of NTP time # Last offset : +0.000045678 seconds # RMS offset : 0.000123456 seconds # Frequency : 12.345 ppm slow # ... # 看所有 source 状态 chronyc sources # MS Name/IP address Stratum Poll Reach LastRx Last sample # =============================================================== # ^* 100.100.5.1 2 7 377 45 +12us[+15us] +/- 1567us # ^* 是当前在用;^+ 是候选;^? 是不可达;^x 不一致;^- 被算法排除 # 各 source 的详细测量 chronyc sourcestats ``` `System time` 在毫秒级即合格;几十微秒到几百微秒是 Internet NTP 的正常范围。 ## NTS(NTP over TLS) 新硬件 / 新版 chrony 支持 NTS,给 NTP 流量加密 + 鉴权(防中间人篡改时间): ```conf server time.cloudflare.com iburst nts server nts.netnod.se iburst nts ``` 需要 `nts` 关键字 + chrony >= 4.0 + 客户端能解析 nts 服务器的证书链。 ## 给本机当 NTP server ```conf # /etc/chrony/chrony.conf allow 192.168.0.0/16 # 内网客户端 allow 10.0.0.0/8 # 监听 IPv4 / IPv6(默认开) # bindaddress 0.0.0.0 ``` ```bash sudo systemctl restart chrony sudo ufw allow 123/udp comment 'NTP' ``` 客户端 chrony.conf 写 `server <你这台>.example.com iburst`。 ## 强制立即同步 ```bash sudo chronyc -a 'burst 4/4' sudo chronyc -a makestep # 之前都不行的话直接: sudo chronyc -a 'manual on' && sudo chronyc settime ... ``` ## 监控 ```bash # Prometheus node_exporter 自带 chrony collector # node_chrony_system_time_offset_seconds 是要报警的指标 # 阈值 > 0.01 秒就告警 # 或直接定时脚本检查偏移 chronyc tracking | awk '/System time/ {print $4}' | xargs -I {} \ python3 -c "import sys; v=abs(float('{}')); sys.exit(0 if v<0.01 else 1)" ``` ## 踩过的坑 - 在云上(AWS / GCP / Azure)建议用 **云供应商提供的内网 NTP** 而不是公网 pool:低延迟 + 不出 VPC + 通常更稳定(AWS: `169.254.169.123`)。 - 容器里运行的程序看到的时间是宿主的;容器里跑 chrony 多此一举,反而可能 和宿主冲突。 - 虚拟机长时间挂起后 wakeup,时间漂移可能很大,触发 `makestep` 跳变。 如果业务对时间不能跳(金融 / log 单调),考虑把 makestep 关掉接受 slewing(缓慢调整)。 - 同步上游用 IP 而不用域名时,AWS / 云上 169.254.169.123 这类 link-local 地址不要经过 DNS(永远查不到)。

用 ZFS 快照做即时回滚 + zfs send 增量异地备份

ZFS 的两个杀手特性: 1. **快照即时 + 几乎零成本**:copy-on-write,秒级、不占额外空间 2. **zfs send / receive**:增量传输到另一台机器,做异地备份 下面在 Ubuntu 上装 ZFS、建池、配快照轮转、做远程增量备份。 ## 1. 装 ZFS(Ubuntu 已带) ```bash sudo apt install -y zfsutils-linux zfs version ``` CentOS / RHEL:用 `zfs-fuse` 不行,要装 native(zfsonlinux 仓库)。 ## 2. 建池 假设有两块裸盘 `/dev/sdb` `/dev/sdc`: ```bash # 镜像(mirror = RAID1) sudo zpool create tank mirror /dev/sdb /dev/sdc # 单盘 # sudo zpool create tank /dev/sdb # RAIDZ1 (RAID5 类似,需 ≥ 3 盘) # sudo zpool create tank raidz /dev/sdb /dev/sdc /dev/sdd sudo zpool status tank sudo zpool list sudo zfs list ``` `tank` 是池名。建议用块设备的 `/dev/disk/by-id/...` 而不是 `/dev/sdb`(重启后顺序变)。 ## 3. 创建文件系统(dataset) ```bash sudo zfs create tank/data sudo zfs create tank/data/users sudo zfs create tank/data/projects sudo zfs list # tank/data 96K 3.5T ... # tank/data/users 96K 3.5T ... # tank/data/projects 96K 3.5T ... ``` dataset 像目录但每个都是独立挂载 + 独立 properties。 ## 4. 重要 properties ```bash sudo zfs set compression=lz4 tank # 透明压缩,几乎免费 sudo zfs set atime=off tank # 关 atime,性能 + sudo zfs set xattr=sa tank # 高效 xattr 存储 sudo zfs set recordsize=1M tank/data # 大文件场景调大 sudo zfs set quota=500G tank/data/users # 限制使用空间 sudo zfs set reservation=100G tank/data/projects # 保留 100G ``` `compression=lz4` 是 ZFS 最值得开的:CPU 开销几乎为零,能压缩出 30-50% 的额外空间。 ## 5. 快照 ```bash sudo zfs snapshot tank/data@$(date +%F-%H%M%S) sudo zfs list -t snapshot # tank/data@2026-05-23-090000 0B ... ``` `@` 后面是快照名。 恢复某个文件: ```bash ls /tank/data/.zfs/snapshot/2026-05-23-090000/ # 像普通目录,cp 出来即可 ``` 整个 dataset 回滚到某快照: ```bash sudo zfs rollback tank/data@2026-05-23-090000 # 注意:会丢掉快照之后的所有改动! ``` 删快照: ```bash sudo zfs destroy tank/data@2026-05-23-090000 ``` ## 6. 自动快照轮转:zfs-auto-snapshot ```bash sudo apt install -y zfs-auto-snapshot ``` 自动每 15 分钟 / 小时 / 天 / 周 / 月做快照并轮转: ```bash ls /etc/cron.*/zfs-auto-snapshot # /etc/cron.d/zfs-auto-snapshot (15min) # /etc/cron.hourly/zfs-auto-snapshot # /etc/cron.daily/... # ... # 给特定 dataset 关掉某频率 sudo zfs set com.sun:auto-snapshot:frequent=false tank/data/projects sudo zfs set com.sun:auto-snapshot:hourly=true tank/data/projects ``` 默认保留:96 frequent / 24 hourly / 31 daily / 8 weekly / 12 monthly。 ## 7. zfs send:远程增量备份 第一次全量: ```bash # 源机 sudo zfs snapshot tank/data@base sudo zfs send tank/data@base | ssh backup@remote 'zfs receive -F tank-backup/data' # 远端 zfs list # tank-backup/data ``` 之后增量: ```bash # 源机:在前一个快照基础上做新快照 sudo zfs snapshot tank/data@2026-05-23 # 增量 send:只传 base → 2026-05-23 的差异 sudo zfs send -i tank/data@base tank/data@2026-05-23 \ | ssh backup@remote 'zfs receive tank-backup/data' # 完成后 base 可以删掉(远端也跟着删),用新快照做下一次的 base ``` 实际生产用 [syncoid](https://github.com/jimsalterjrs/sanoid) 或 [zrepl](https://zrepl.github.io/) 包装,自动管 base 和 retention: ```bash sudo apt install -y sanoid # /etc/sanoid/sanoid.conf 配置 dataset + retention # /etc/sanoid/syncoid.conf 配置 replication syncoid tank/data backup@remote:tank-backup/data ``` ## 8. 加密 ```bash # 建 dataset 时启用加密 sudo zfs create -o encryption=on -o keyformat=passphrase \ tank/data/sensitive # 之后挂载要解锁 sudo zfs load-key tank/data/sensitive # 输入密码 sudo zfs mount tank/data/sensitive ``` zfs send / receive 加 `-w` 直接发加密 stream(远端不需要密码, 压缩 + dedup 在加密状态下做)。 ## 9. 校验 + scrub ```bash sudo zpool scrub tank sudo zpool status tank # scan: scrub in progress since ... # 100M scanned out of 50G at 200M/s ``` scrub 读全部数据 + 校验 checksum,发现损坏自动从镜像 / parity 修复。 推荐每月跑一次: ```cron 0 3 1 * * /sbin/zpool scrub tank ``` ## 10. 容量监控 ```bash sudo zpool list -v tank # NAME SIZE ALLOC FREE ... # tank 4T 1.2T 2.8T ... # mirror 4T 1.2T 2.8T # sdb - - - # sdc - - - sudo zfs list -o name,used,avail,refer,mountpoint ``` ZFS 超过 80% 使用率性能急剧下降;保持在 80% 以下。 ## 踩过的坑 - 用 `/dev/sdb` 直接建池,机器重启 sdb 变成 sdc → 池找不到。永远用 `/dev/disk/by-id/` 路径。 - ZFS 内存吃得多:1GB / 1TB 数据是经验值。8GB 机器跑 32TB 池可能要 调 `zfs_arc_max` 限制 ARC 缓存。 - `zfs destroy` 没确认就执行,整个 dataset + 所有快照消失。养成 `-n` 模拟先看的习惯。 - snapshot 是 read-only,但占空间 = 这个快照后所有被修改的数据。 长期保留快照 + 高频改动 = 池快速胀满。retention 不要太长。

用 perf 找出 CPU 100% 进程的热点函数(含火焰图)

应用 CPU 跑满,不知道卡在哪个函数?`perf` 是 Linux 性能分析的"瑞士军刀": 低开销采样,看到 CPU 时间花在每个函数 / 每行指令上。 配合 FlameGraph 出火焰图,一眼看清谁吃 CPU。 ## 1. 装 perf ```bash sudo apt install -y linux-tools-common linux-tools-$(uname -r) sudo perf --version ``` CentOS / RHEL:`sudo yum install perf`。 ## 2. 系统级采样 ```bash # 采样全系统 10 秒 sudo perf record -F 99 -a -g -- sleep 10 # -F 99: 每秒 99 次采样(避免和定时任务整数倍重合) # -a: 全系统 # -g: 抓 call graph ls perf.data # 几 MB sudo perf report ``` `perf report` TUI 里: - `+/-` 展开 / 折叠调用栈 - `a` 显示汇编 - `/` 搜函数名 按 CPU 百分比从高到低排: ``` 17.32% nginx libc-2.31.so [.] __memcpy_avx_unaligned 12.45% nginx nginx [.] ngx_http_parse_header_line 8.21% nginx libssl.so.3 [.] ssl3_read_bytes ... ``` ## 3. 单进程采样 ```bash sudo perf record -F 99 -p <PID> -g -- sleep 30 sudo perf report ``` `sleep 30` 是采样时长。`-p <PID>` 限定单进程。 ## 4. 火焰图(一图胜千言) ```bash # 装 FlameGraph 脚本 git clone https://github.com/brendangregg/FlameGraph ~/FlameGraph # 采样 sudo perf record -F 99 -p <PID> -g -- sleep 30 sudo perf script > /tmp/out.perf # 生成 SVG ~/FlameGraph/stackcollapse-perf.pl /tmp/out.perf > /tmp/out.folded ~/FlameGraph/flamegraph.pl /tmp/out.folded > /tmp/flame.svg # 浏览器打开 /tmp/flame.svg xdg-open /tmp/flame.svg ``` 火焰图怎么读: - **X 轴**:CPU 占用(宽度 = 占用比例,与时间无关) - **Y 轴**:调用栈深度(栈顶在上) - **宽块**:耗 CPU 的函数(从顶往下看,找最宽的就是热点) ## 5. 看具体函数的汇编 / 源码 ```bash sudo perf report --stdio # 文本输出 sudo perf annotate function_name # 看汇编 + 哪一行最热 ``` 需要程序带 debug symbols(`-g` 编译,或装 `*-dbg` 包): ```bash sudo apt install -y nginx-dbg postgresql-16-dbgsym ``` ## 6. 上下文切换 / 系统调用 / 缓存命中 ```bash # 看进程的上下文切换 / 缺页 / cache miss sudo perf stat -p <PID> -- sleep 10 # Performance counter stats for process id 12345: # 3,532.18 msec task-clock (10 cpus utilized) # 48,927 context-switches # 13,200 cpu-migrations # 1,234 page-faults # 9,876,543,210 cycles # 4,321,098,765 instructions (0.44 insn per cycle) # 789,012,345 branches # 34,567,890 branch-misses (4.38% of all branches) # 看具体的硬件事件 sudo perf stat -e cache-misses,cache-references,L1-dcache-load-misses -p <PID> -- sleep 10 ``` `branch-misses` 高(> 5%)通常意味着分支预测不友好的代码。 `instructions per cycle` < 1 是性能差的信号。 ## 7. live 模式(top 风格) ```bash sudo perf top -p <PID> # 实时看哪些函数当前最热 ``` 排查"刚才一瞬间 CPU 高了"很有用。 ## 8. trace syscall(替代 strace 的高性能版) ```bash sudo perf trace -p <PID> # 实时显示进程的所有 syscall(比 strace 开销小 10x) sudo perf trace -s -p <PID> -- sleep 10 # 统计 10 秒内的 syscall 次数 + 耗时 ``` ## 9. Python / Node / Java 特殊处理 perf 默认看不到解释器 / VM 里的函数名,需要特殊 hook: ```bash # Python: 用 py-spy 替代 perf pip install py-spy sudo py-spy record -o profile.svg --pid <PID> # Node.js: 用 0x(npm install -g 0x)或 node --perf-basic-prof node --perf-basic-prof app.js # 然后 perf record 会读 /tmp/perf-<PID>.map 解析 JS 函数名 # Java: 用 async-profiler java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ... ``` ## 10. 远程 / 容器内 ```bash # 容器内的进程:在 host 上跑 perf,看到的是 host 视角的 PID sudo perf record -F 99 -p $(pgrep -f my-container-process) -g -- sleep 10 # 如果容器内 / 进程内没 debug symbols,host 上的 perf 看到的也是 [unknown] # 解决:把容器内的 /usr/lib/debug 也挂到 host,或在容器里跑 perf ``` ## 11. 实战案例:Python web 应用慢 ```bash # 看哪个进程在烧 CPU top -H -p $(pgrep gunicorn | head -1) # 多个线程?PID 列里的 TID sudo py-spy top --pid <TID> # 实时看每个 Python 函数 CPU 占比 # 或者出火焰图 sudo py-spy record -o /tmp/flame.svg --pid <PID> --duration 30 ``` ## 12. 注意采样精度 `-F 99` 是每秒 99 次。意味着: - 持续 100ms 的函数:约 10 个样本,足够看见 - 持续 1ms 的函数:约 0.1 样本,看不到 短函数 + 高频调用看 `-F 999` 或 `-F 4999`(CPU 开销变大但更精细)。 ## 踩过的坑 - 没装 debug symbols:火焰图全是 `[unknown]` 一片黑。装 `*-dbg` / 重编译加 `-g`。 - 容器 + minimal image:image 里没 debug symbols,分析复合 image 把 dbg 包打进去。 - `perf record` 写盘飞快(GB 级 perf.data),磁盘满:缩短采样时间, 或者 perf script 后立刻删 perf.data。 - root 才能 perf:调 `sysctl kernel.perf_event_paranoid=-1` 让普通用户 可以采样(生产环境慎用,有信息泄露风险)。

用 cgroup v2 / systemd slice 限制服务的 CPU / 内存

## 起因 一台服务器同时跑应用 + cron 任务 + 数据库。某个 cron 任务(数据 处理脚本)偶尔吃 90% CPU + 8GB 内存,把主应用挤到 OOM kill。 "给 cron 任务限定最大资源"是基础隔离。 cgroup(control group)是 Linux 内核功能,systemd 在 v2 之后用得很直接。 ## 解决方案 ### 1. 系统当前 cgroup 状态 ```bash # 看 cgroup 树 systemd-cgls # 看每个 unit 的资源占用 systemd-cgtop # 当前 cgroup 版本(v2 推荐,现代 distro 默认) cat /sys/fs/cgroup/cgroup.controllers # memory cpu io pids ... ``` ### 2. 给单个 service 限资源 ```ini # /etc/systemd/system/data-pipeline.service [Service] ExecStart=/usr/local/bin/process-data.sh # CPU:最多用 2 个核(200% = 2 * 100%) CPUQuota=200% CPUWeight=50 # 相对优先级(默认 100,调低让出 CPU) # 内存:硬上限 4GB,超过 OOM kill 这个 service(不影响别的) MemoryMax=4G MemoryHigh=3.5G # 软上限,超过后系统 throttle 这个进程的分配 # IO:限速 50MB/s 读 + 50MB/s 写(针对某块磁盘) IOReadBandwidthMax=/dev/sda 50M IOWriteBandwidthMax=/dev/sda 50M # tasks(线程 / 进程数) TasksMax=64 ``` `sudo systemctl daemon-reload && sudo systemctl restart data-pipeline`。 之后这个 service 超过 4GB 内存就被 OOM killed(service 重启), 不会影响系统其它进程。 ### 3. 实时调整(不重启 service) ```bash sudo systemctl set-property data-pipeline.service \ MemoryMax=2G CPUQuota=150% # 校验 systemctl show data-pipeline.service -p MemoryMax,CPUQuota ``` `set-property` 立刻生效 + 写到 `/etc/systemd/system.control/`,重启 保留。 ### 4. 把多个 service 归一组:slice ```ini # /etc/systemd/system/background.slice [Unit] Description=Background batch jobs [Slice] CPUWeight=10 # 后台任务低优先级 CPUQuota=400% # 整组合计最多 4 核 MemoryMax=8G # 整组合计最多 8GB IOWeight=10 ``` ```ini # data-pipeline.service [Service] Slice=background.slice # 不需要再单独设 CPUQuota / MemoryMax,由 slice 限制 # log-cleaner.service [Service] Slice=background.slice # image-resizer.service [Service] Slice=background.slice ``` `background.slice` 整体限到 4 核 + 8GB,里面 3 个 service 自动竞争分配。 主应用(不在这个 slice 里)不受影响。 ### 5. 给用户 / 会话限资源 `/etc/systemd/system/user-1001.slice`(用户 ID 1001 的所有进程): ```ini [Slice] CPUQuota=200% MemoryMax=4G ``` 或编辑用户的: ```bash sudo systemctl edit user-1001.slice ``` 防"某个用户跑了一个内存炸弹把整机器 OOM"。 ### 6. 临时跑一个命令带限制 ```bash systemd-run --scope --slice=background.slice -p MemoryMax=1G -p CPUQuota=50% \ ./bigjob.sh ``` 不需要建 service unit,临时挂在 background.slice 下跑。 ### 7. 看 cgroup 实际占用 ```bash # 某个 service 当前 RAM cat /sys/fs/cgroup/system.slice/data-pipeline.service/memory.current # 上限 cat /sys/fs/cgroup/system.slice/data-pipeline.service/memory.max # CPU 时间累计 cat /sys/fs/cgroup/system.slice/data-pipeline.service/cpu.stat ``` `systemctl status data-pipeline` 也会显示 Tasks / Memory / CPU。 或更直观: ```bash systemd-cgtop -d 1 # 实时刷新每 cgroup 资源占用 ``` ### 8. 监控 OOM kill ```bash # 查 dmesg 里的 oom_kill sudo dmesg | grep -i 'killed process' # journal 里看是哪个 cgroup 触发 OOM sudo journalctl -k --since '1 day ago' | grep oom ``` `MemoryMax` 触发的 OOM 只 kill 那个 cgroup 里的进程,不影响其它。 进程死了 systemd 按 `Restart=on-failure` 重启它(或留死状态等告警)。 ## 几个常用 hardening 选项 ```ini # 文件系统 ProtectSystem=strict # / 全只读 ProtectHome=true # /home 不可见 ReadWritePaths=/var/log/myapp # 例外可写 # 内核能力 NoNewPrivileges=true # exec 后不能升 cap PrivateTmp=true # /tmp 私有 PrivateDevices=true # /dev 极简 PrivateNetwork=true # 网络命名空间隔离(注意:不能访问外网) # user 隔离 User=trio Group=trio DynamicUser=true # 动态分配 UID(最安全,但要求 service 真不需要持久 user) # 资源 LimitNOFILE=65536 # 文件描述符 TasksMax=1024 ``` `systemd-analyze security <unit>` 给每个 service 评一个 0-10 分的 安全分,按提示加 hardening。 ## 效果 - 后台 batch job 不再挤死主应用 - 一个 cron 任务跑飞了只 OOM 自己 cgroup,systemd 重启它 - 多个低优先级服务自动让 CPU 给业务 - 资源审计:每个 service / slice 用了多少一目了然 ## 与 Docker / K8s 对比 容器(docker / k8s)其实就是 cgroup + namespace + 镜像分发。 单机服务用 systemd cgroup 已经够,不需要为了"资源隔离"就上 Docker。 ```bash # 等价物 docker run --cpus 2 --memory 4g ... # vs systemctl set-property myservice.service CPUQuota=200% MemoryMax=4G ``` ## 踩过的坑 1. **cgroup v1 vs v2**:老 distro / docker 默认 v1,systemd unified hierarchy 用 v2。混用导致某些 unit option 不生效。 `systemd.unified_cgroup_hierarchy=1` 内核参数强制 v2。 2. **MemoryMax 太严格**:服务正常工作时偶尔 spike → 频繁 OOM 重启 雪崩。给 50% buffer。 3. **PrivateNetwork=true 但服务要访问 DB**:隔离过头,service 起不来 连不上 DB。这个选项只给完全离线的工具用。 4. **CPUQuota=100%(一个核)但服务是多线程**:会被严格限速到 1 核 throughput,多线程优势没了。看实际负载决定。 5. **DynamicUser + 文件持久化**:DynamicUser 每次启动 UID 可能不同, 服务写到 `/var/lib/myservice/` 的文件下次 UID 变了读不了。 `StateDirectory=myservice` 让 systemd 管这个目录的权限。

Borg + Borgmatic 做本地 + 异地双备份(一份配置两个仓库)

Borg 是 Python 写的去重备份工具,自带客户端加密、压缩、增量、仓库 deduplication。功能上和 Restic 相近,但生态早成熟两年,老牌系统管理 员居多。 Borgmatic 是 Borg 的 YAML wrapper,把 "备份 → 检查 → 清理 → 通知" 四件事写在一个配置文件里。 ## 安装 ```bash sudo apt install -y borgbackup borgmatic # 也可以 pip install --user borgmatic 拿更新版本 ``` ## 仓库初始化 我们做两份:本地 NAS + 异地(rsync.net / hetzner storage box / 自建机)。 两次 init: ```bash # 本地 NAS(NFS / SMB 挂载到 /mnt/nas) borg init -e repokey /mnt/nas/borg/host-foo # 异地,通过 ssh+borg serve borg init -e repokey \ ssh://[email protected]:23/./borg/host-foo ``` 每次 init 会要求设置 passphrase;同样这两个密码 **丢了就完了**。 ## Borgmatic 配置 `/etc/borgmatic/config.yaml`: ```yaml location: source_directories: - /etc - /srv - /home/yourname/projects exclude_patterns: - '*/node_modules' - '*/__pycache__' - '*/.venv' - '*/target' - '*/.cache' exclude_caches: true exclude_if_present: - .nobackup repositories: - path: /mnt/nas/borg/host-foo label: nas - path: ssh://[email protected]:23/./borg/host-foo label: offsite storage: encryption_passcommand: 'cat /etc/borgmatic/passphrase' compression: zstd,3 archive_name_format: '{hostname}-{now:%Y%m%d-%H%M%S}' borg_keep_exclude_tags: true retention: keep_daily: 7 keep_weekly: 4 keep_monthly: 12 keep_yearly: 5 consistency: checks: - name: repository frequency: 2 weeks - name: archives frequency: 1 month hooks: before_backup: - echo "starting backup at $(date)" after_backup: - curl -fsSL --retry 3 'https://hc-ping.com/<uuid>' || true on_error: - curl -fsSL --retry 3 'https://hc-ping.com/<uuid>/fail' || true ``` `/etc/borgmatic/passphrase`: ``` my-very-long-random-passphrase ``` 权限:`sudo chmod 600 /etc/borgmatic/passphrase`。 ## 跑 ```bash sudo borgmatic --verbosity 1 # 等价于:create + prune + 周期性 check ``` 只跑某一步: ```bash sudo borgmatic create # 只备份 sudo borgmatic check # 只校验 sudo borgmatic prune # 只清理 sudo borgmatic list # 看快照列表 ``` ## systemd timer Borgmatic 包自带 unit: ```bash sudo systemctl enable --now borgmatic.timer systemctl list-timers borgmatic.timer ``` 或者自己写 timer 覆盖时间。 ## 还原 ```bash # 列快照 sudo borgmatic list --repository /mnt/nas/borg/host-foo # 或 sudo borg list /mnt/nas/borg/host-foo # 看快照内容 sudo borg list /mnt/nas/borg/host-foo::host-foo-20260520-024300 | head # 还原一个文件到当前目录 cd /tmp sudo borg extract /mnt/nas/borg/host-foo::host-foo-20260520-024300 etc/nginx/nginx.conf # 挂载快照为只读文件系统(很方便) sudo mkdir /mnt/snap sudo borg mount /mnt/nas/borg/host-foo::host-foo-20260520-024300 /mnt/snap ls /mnt/snap sudo borg umount /mnt/snap ``` ## 双仓库的取舍 - 本地仓库快但 fate-shared:火灾水患时一起没。 - 异地仓库慢但是真灾备;如果带宽紧张就只把 `/etc` `/srv/critical` 推异地。 可以拆配置: ```yaml repositories: - path: /mnt/nas/borg/host-foo label: nas # 仅在某些 source_directory 同步到异地,靠 borg patterns 文件 ``` 更简单的做法:写两份 borgmatic 配置 `local.yaml` / `offsite.yaml`, 各自有 source_directories,timer 在不同时间触发。 ## 踩过的坑 - Borg 仓库 **不能** 让两个 client 同时写。两台机器要往同一个仓库 备份必须串行或用 `--lock-wait`。 - 加密类型 `repokey` 把 key 放仓库里(密码加密),方便但密码弱时不安全; `keyfile` 把 key 放客户端 `~/.config/borg/keys/`,要单独备份。 - 大文件改动(虚拟机磁盘)每次 dedup 都要扫整个文件,慢。建议这种文件 排除,单独做内置工具的快照备份。 - 远端 ssh 用专门 key 加 `command="borg serve --restrict-to-repository ..."` 限制;别用你的常规 key 跑 borg。

bpftrace 一行 eBPF 脚本排查"看不见"的内核 / 用户态问题

eBPF 允许在内核里跑沙箱化的小程序,无侵入地观察系统行为。 bpftrace 是 awk 风格的高级语言,让你不用 C / libbcc 也能写 eBPF 脚本。一行就能解决很多用 strace / perf 都麻烦的问题。 ## 装 ```bash sudo apt install -y bpftrace bpftrace --version # 需要 >= 0.16,老版本功能少很多 # 内核 5.5+ / Debian 11+ / Ubuntu 20.04+ ``` ## 1. 一行解决的常见问题 ### 谁在打开 /etc/passwd ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_openat /str(args->filename) == "/etc/passwd"/ { printf("%s (%d) opened /etc/passwd\n", comm, pid); } ' ``` 任何进程 open /etc/passwd 时立即打印进程名 + PID。 ### 哪个进程在创建 socket ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_socket { printf("%s (%d) opened socket family=%d type=%d\n", comm, pid, args->family, args->type); } ' ``` ### 哪个 exec 系列 syscall 在被频繁调用 ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_execve { printf("%s -> %s\n", comm, str(args->filename)); } ' ``` 实时显示所有 exec —— 找出 shell 死循环 / 短生命周期进程喷。 ### 系统调用直方图 ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_read { @[comm] = count(); } interval:s:5 { print(@); clear(@); exit(); } ' # 5 秒内每个进程的 read 次数 ``` ### 哪个进程在 page fault ```bash sudo bpftrace -e ' software:major-faults:1 { @[comm] = count(); } ' # Ctrl-C 后看汇总 ``` major-fault = 必须从磁盘加载 page,对应 swap / mmap miss 等慢操作。 ## 2. 用户态 dynamic instrumentation (uprobe) 观察 nginx 调用某函数: ```bash # 列 nginx 二进制的可探测函数 sudo bpftrace -l 'uprobe:/usr/sbin/nginx:*' | head # 探测某个函数 sudo bpftrace -e ' uprobe:/usr/sbin/nginx:ngx_http_process_request { @[comm] = count(); } ' ``` 不需要重启 nginx、不需要改代码,实时统计某函数被调多少次。 ## 3. 时延直方图 ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; } tracepoint:syscalls:sys_exit_read /@start[tid]/ { @us = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); } interval:s:5 { print(@us); clear(@us); } ' ``` 输出每 5 秒一次 read syscall 的时延直方图(微秒): ``` @us: [0] 8 |@ | [1] 256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 4) 384 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [4, 8) 128 |@@@@@@@@@@@@@@@@ | [8, 16) 16 |@@ | [16, 32) 4 | | [1K, 2K) 1 | | ``` 立刻看出 read 大多 1-4 µs,偶尔有 1-2 ms 的慢——可能是磁盘 / 网络 IO 慢。 ## 4. 内置变量速查 - `pid` / `tid`:进程 / 线程 ID - `comm`:进程名(前 16 字符) - `nsecs`:当前时间戳(纳秒) - `cpu`:当前 CPU - `args`:tracepoint 参数(结构体) - `kstack` / `ustack`:内核 / 用户调用栈 ## 5. 输出 + 聚合 ``` @hist_name = hist(value) # 2 的幂直方图 @lhist_name = lhist(value, 0, 100, 10) # 线性直方图 @count[key] = count() # 计数 @sum[key] = sum(value) # 求和 @avg[key] = avg(value) # 平均 @stats[key] = stats(value) # min/max/avg/count/sum @top10 = bottom(10, count()) # Top-N ``` Map 自动按 key 分组。 ## 6. 实战:找出"哪个文件被读最多" ```bash sudo bpftrace -e ' tracepoint:syscalls:sys_enter_openat { @opens[str(args->filename)] = count(); } interval:s:30 { print(@opens, 10); clear(@opens); } ' # 每 30 秒输出 Top 10 被 open 的文件 ``` ## 7. 现成脚本库:bpftrace tools ```bash git clone https://github.com/iovisor/bpftrace ls bpftrace/tools # biolatency.bt biosnoop.bt execsnoop.bt funcslower.bt # opensnoop.bt runqlat.bt tcpaccept.bt tcpconnect.bt # ... ``` 例:磁盘 IO 时延直方图 ```bash sudo bpftrace bpftrace/tools/biolatency.bt ``` `execsnoop.bt` 实时显示新建进程,`tcpconnect.bt` 显示所有新建 TCP 连接, 都是诊断生产问题的利器。 ## 8. 跟 bcc 比 bcc 是用 C 写 eBPF + Python 包装,更强大但学习成本高。 bpftrace 是高级语言,限制更多但写起来快 10x。生产经验: - 简单监控 / 调试一次性脚本:bpftrace - 持续运行的复杂 agent / 工具:bcc ## 9. 性能影响 bpftrace 比 strace / perf 通常开销小 5-10x。但还是有开销: - tracepoint:几乎零(编译时插入的探测点) - kprobe / uprobe:每次触发都中断到 BPF 解释器,有一点开销 - 高频事件(每秒百万级)+ map 写:可能加 5-15% CPU 生产环境 attach 前先估算事件频率,从低开销 tracepoint 开始。 ## 10. CO-RE / libbpf-tools 最新的 BPF 工具用 CO-RE(Compile Once, Run Everywhere): ```bash sudo apt install -y libbpf-tools sudo execsnoop-bpfcc # bcc 版(需要 kernel-devel) sudo execsnoop # libbpf-tools 版(任意内核 5.4+) ``` CO-RE 工具不需要内核头文件,便携性最好。生产首选 libbpf-tools。 ## 踩过的坑 - 老内核(< 5.0)功能阉割严重;如果没法升内核,回退到 strace / perf。 - BTF(BPF Type Format)没启用的内核:很多 bpftrace 脚本直接报错。 `ls /sys/kernel/btf/vmlinux` 看是否有,没有就只能用 kprobe 而非 fentry。 - 一个 bpftrace 脚本里写 5+ 个 probe + 大 map → BPF verifier 拒绝 ("too many instructions" / "stack overflow")。拆成多个脚本。 - 容器内跑 bpftrace 没权限:需要 `--privileged` + `--cap-add SYS_ADMIN`, 或者直接在 host 上跑。

LVM 上扩 / 缩 ext4 分区(不停机)

LVM 比直接分区灵活得多:磁盘空间可以在线扩、跨多块物理盘组卷、做快照。 日常运维最常用的就是"分区满了,加块盘扩进去"。下面按真实流程走一遍。 前置:分区是 `ext4` on `LVM`,要扩或缩的是某个 LV。XFS 类似但不能缩。 ## 现场摸底 ```bash df -h /data # Filesystem Size Used Avail Use% Mounted on # /dev/mapper/vg0-data 100G 95G 5G 95% /data sudo lvs # LV VG Attr LSize # data vg0 -wi-ao 100.00g # root vg0 -wi-ao 50.00g sudo vgs # VG #PV #LV #SN Attr VSize VFree # vg0 1 2 0 wz--n- 200.00g 50.00g sudo pvs # PV VG Fmt Attr PSize PFree # /dev/sda2 vg0 lvm2 a-- 200.00g 50.00g ``` ## 场景 A:VG 有剩余空间,扩 LV + ext4 最简单情况。直接扩: ```bash # 加 30G 到 data LV,顺手扩 ext4 sudo lvextend -L +30G -r /dev/vg0/data # -r 等价于扩完后自动跑 resize2fs(ext4)或 xfs_growfs ``` 不带 `-r` 时分两步: ```bash sudo lvextend -L +30G /dev/vg0/data sudo resize2fs /dev/vg0/data ``` ext4 / XFS 都支持在线扩容,不需要 umount,业务无感知。 校验: ```bash df -h /data # /dev/mapper/vg0-data 130G 95G 31G 76% /data ``` ## 场景 B:VG 空间不够,先加物理盘 ```bash # 新磁盘 /dev/sdb 装上,整盘做 PV(生产建议先 fdisk 建分区给它) sudo pvcreate /dev/sdb sudo vgextend vg0 /dev/sdb sudo vgs # VG #PV #LV #SN Attr VSize VFree # vg0 2 2 0 wz--n- 700.00g 550.00g # 然后回到场景 A 扩 LV sudo lvextend -L +100G -r /dev/vg0/data ``` ## 场景 C:缩 LV(危险,要停机 + 备份) ext4 缩容必须先 umount,且 **没有 100% 安全的在线缩容方案**。 执行前 ===先做备份===。 ```bash # 1. umount sudo umount /data # 2. fsck(强制检查) sudo e2fsck -fy /dev/vg0/data # 3. 文件系统先缩到目标大小 sudo resize2fs /dev/vg0/data 60G # 4. LV 缩到同样或稍大的尺寸(建议 +1G 余量) sudo lvreduce -L 61G /dev/vg0/data # 5. 文件系统再扩满 LV sudo resize2fs /dev/vg0/data # 6. 重新 mount sudo mount /data ``` 步骤 3 < 步骤 4 的顺序不能颠倒!文件系统比块设备大会立即损坏。 ## 场景 D:从一块物理盘迁数据到另一块(替换坏盘) ```bash # 新盘加入 VG sudo pvcreate /dev/sdc sudo vgextend vg0 /dev/sdc # 把 sdb 上的所有 LV 迁到其它 PV(pvmove 是在线的) sudo pvmove /dev/sdb # 漫长过程,可以加 -b 后台跑,sudo pvmove -b /dev/sdb # 完成后从 VG 移除 sudo vgreduce vg0 /dev/sdb sudo pvremove /dev/sdb # 现在 sdb 可以物理移除 ``` ## 快照(紧急回滚) ```bash # 给 data 拍个快照,预留 10G 写时变化 sudo lvcreate -L 10G -s -n data-snap /dev/vg0/data # 操作系统看到的是冻结时刻的 data,挂载它能恢复文件 sudo mkdir /mnt/snap sudo mount /dev/vg0/data-snap /mnt/snap # 没问题就删掉快照(不删会占空间且影响写性能) sudo umount /mnt/snap sudo lvremove /dev/vg0/data-snap ``` 升级 / 大改动前给 root LV 拍快照,是 LVM 系统的"undo"能力。 ## 踩过的坑 - `lvextend -L 200G` 是 **设置** 总大小到 200G,`-L +100G` 是 **追加** 100G。 搞反了能把分区缩没掉。 - ext4 在 64-bit 模式(新版本默认)才支持超 16T;老分区扩到 16T+ 会 fail。 `tune2fs -O 64bit /dev/...` 升级,操作需要 fsck 时间。 - pvmove 时如果系统 reboot,会卡在半迁状态。重新启动后用 `pvmove --abort` 或继续 `pvmove`。 - XFS 不能缩 —— 想缩只能 dump + mkfs + restore。所以新分区如果有可能缩, 用 ext4。