技术指南

VPS 初始调优:先盘点、再变更、用证据验收

把新 VPS 初始化改造成可审查的变更流程:先保存基线,再逐项验证软件包、资源、内核、日志与时间设置。

新 VPS 的第一天不适合执行一串“优化命令”。发行版、虚拟化边界、软件源、当前服务和业务目标还没有对齐时,批量升级、全局调高限制、固定启用 BBR 或替换 DNS 都可能制造新的故障。更可靠的初始化把现状快照、候选变更、受控激活、业务验收和回滚证据串成一条短闭环。

先保存一份可复查的主机基线

范围要回答的问题只读证据
身份这台实例、发行版和内核是否是目标对象`hostnamectl`、`os-release`、`uname`
虚拟化是完整虚拟机、共享内核容器还是嵌套环境`systemd-detect-virt`
容量CPU、内存、swap、文件系统和 inode 是否有余量`lscpu`、`free`、`findmnt`、`df`
服务谁在监听、谁失败、谁开机启动`ss`、`systemctl --failed`、unit 清单
时间与日志时钟是否同步,日志放在哪里、占用多少`timedatectl`、`journalctl --disk-usage`
在变更单中保存 UTC 时间、命令版本和输出位置;基线可能包含主机名、地址与进程信息,应按运维记录保护
stamp=$(date --utc +%Y%m%dT%H%M%SZ)
out="$HOME/vps-baseline-$stamp.txt"
{
  date --utc --iso-8601=seconds
  cat /etc/os-release
  uname -a
  systemd-detect-virt || true
  lscpu
  free --bytes
  findmnt --real --output TARGET,SOURCE,FSTYPE,OPTIONS
  df --block-size=1 --output=source,fstype,size,used,avail,pcent,target
  df --inodes --output=source,itotal,iused,iavail,ipcent,target
  ss --listening --tcp --udp --numeric --processes
  systemctl --failed --no-pager
  timedatectl
  journalctl --disk-usage
} | tee "$out"

`systemd-detect-virt` 能区分完整虚拟机和容器,并只报告最内层环境。共享内核容器通常不能加载模块或修改部分内核参数;完整虚拟机也不代表云平台允许所有能力。把检测结果当作能力线索,再用目标参数和真实业务探针确认,不要仅凭“KVM”或“OpenVZ”标签下结论。

每个变更先写清收益、指标与撤销条件

  1. 现状:哪条观测证明当前确有问题,而不是套用通用清单。
  2. 候选:只包含本次要改的文件、包或 unit,能够在不激活时检查。
  3. 成功:至少一条系统证据和一条用户可见证据,且观察窗口明确。
  4. 停止:出现连接、延迟、错误率、磁盘或内存退化时由谁终止。
  5. 回滚:旧配置、旧包策略和恢复入口已经存在,并知道哪些数据变化不可逆。

先模拟软件包计划,再安排维护窗口

`apt-get update` 刷新索引;`apt-get upgrade` 只升级不需要新增或删除包的已安装软件,而 `dist-upgrade` 可能为了依赖关系删除包。不要把 `apt update && apt upgrade -y` 当作无需审阅的初始化动作。先刷新索引、模拟目标命令并检查安装、移除、保留和配置文件影响,再决定维护窗口与重启顺序。

模拟模式不修改系统;计划仍依赖当前索引和配置,真正执行前要在 root 权限下重新生成并复核
sudo apt-get update
apt-get --simulate --verbose-versions upgrade | tee /tmp/apt-upgrade.plan
rg '^(Inst|Remv|Conf) ' /tmp/apt-upgrade.plan
apt-mark showhold
apt-get check

升级后区分进程重启与主机重启

needrestart 的 list 模式列出仍使用旧库的进程;是否重启主机还要结合内核、关键服务和维护策略判断
sudo needrestart -r l
test -e /run/reboot-required && cat /run/reboot-required || true
systemctl --failed --no-pager
ss --listening --tcp --udp --numeric --processes

`needrestart` 的职责是找出库升级后需要重启的 daemon,并能检查旧内核或旧库。不要自动重启所有进程后就宣布成功:连接排空、集群顺序、数据库一致性和外部健康检查仍属于应用运行手册。重启主机也必须验证实例可从控制台恢复、服务依赖按预期启动。

文件描述符按服务容量设置,不做全局常数

shell 的 `ulimit`、PAM limits 和 systemd unit 是不同生效路径。systemd 管理的服务优先在该 unit 的 drop-in 中设置 `LimitNOFILE=`,依据连接模型和监控数据确定软硬限制;修改后检查进程实际限制,而不是只看配置文件或交互 shell。

先用 systemctl cat example.service 了解已有层级,再用 systemd-analyze verify 检查候选并在维护窗口重启服务
# /etc/systemd/system/example.service.d/70-resources.conf
[Service]
LimitNOFILE=16384
只有目标进程的实际 limits、服务健康与连接指标都通过,变更才算生效
systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl restart example.service
pid=$(systemctl show --property=MainPID --value example.service)
cat "/proc/$pid/limits"
systemctl show example.service --property=LimitNOFILE

更高的 nofile 不会自动增加应用并发能力,也不能替代 worker、上游连接池、内存和内核容量规划。把所有用户同时改成 65535 或 1048576 会扩大不可见资源消耗,也可能与应用自身限制冲突。先解决真实的 `EMFILE`/`Too many open files` 证据,再只调目标服务。

内核参数先做兼容性与单变量门禁

20 只是演示候选格式,不是推荐值;实际参数、目标和阈值必须来自目标工作负载
candidate=/tmp/70-example-tuning.conf
printf '%s\n' 'vm.swappiness = 20' > "$candidate"
sysctl vm.swappiness
sudo sysctl --dry-run --load="$candidate"
# 记录旧值后,才在维护窗口安装到 /etc/sysctl.d/
sudo install -m 0644 "$candidate" /etc/sysctl.d/70-example-tuning.conf
sudo sysctl --load=/etc/sysctl.d/70-example-tuning.conf
sysctl vm.swappiness

`sysctl.d` 按目录优先级和文件名字典序合并,`/etc` 留给管理员覆盖。部分参数只有模块加载后才出现,启动早期写入可能不会生效;不存在或无权限的键还可能只被记录。`--dry-run` 能发现当前主机上的未知键而不写值,但上线后仍要读取实际值并观察业务指标。

BBR 与 DNS 都是实验,不是新机默认动作

拥塞控制收益取决于内核能力、RTT、丢包、队列、对端与套餐限速;DNS 还可能承载云内私有域名。先记录当前算法、可用算法、解析管理者和同目标业务指标。只有对照实验稳定改善且没有破坏 IPv4、IPv6、私有解析或包管理,才把单一候选安装为持久配置。

模块可用或 sysctl 显示某算法,只证明选择状态,不证明业务提速
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control
readlink -f /etc/resolv.conf
resolvectl status 2>/dev/null || true
getent ahosts example.com
# 记录同一目标、时段、并发和持续时间的应用层成功率与延迟

先判断内存压力,再准备 swap 候选

swap 能为突发压力提供缓冲,但不能保证避免 OOM,也不能修复持续内存泄漏。大小和 swappiness 应由工作集、磁盘介质、延迟预算与休眠需求决定。swap 文件还受文件系统约束:稀疏文件会被拒绝,Btrfs 等写时复制文件系统需要专门步骤。

mkswap 会写入 swap 签名;目标必须是已审批的新文件,本文不直接启用或追加生产 fstab
free --bytes
swapon --show --bytes --output=NAME,TYPE,SIZE,USED,PRIO
journalctl --dmesg --grep='oom|Out of memory' --since='7 days ago' --no-pager
# 候选文件优先使用发行版和文件系统文档认可的方法;格式化后先识别签名
sudo mkswap /path/to/approved-swapfile
sudo blkid -p /path/to/approved-swapfile

日志预算要同时考虑持久与运行时存储

`SystemMaxUse=` 只约束持久 journal,且要求 `/var/log/journal` 可写并存在;否则实际使用的是 `/run/log/journal` 和 `RuntimeMaxUse=`。上限也不保证立即把占用压到该数字,因为清理只删除归档文件。先确认当前 storage 模式、磁盘余量与保留需求,再用 drop-in 覆盖。

数字只是结构示例;按磁盘、审计保留和事故调查窗口计算,并用 systemd-analyze cat-config systemd/journald.conf 查看合并结果
# /etc/systemd/journald.conf.d/70-storage-budget.conf
[Journal]
SystemMaxUse=512M
SystemKeepFree=1G
RuntimeMaxUse=128M
重启后确认日志仍可写、应用日志未丢失且磁盘趋势符合预算;不要用 vacuum 代替长期保留策略
systemd-analyze cat-config systemd/journald.conf
journalctl --disk-usage
df --block-size=1 /var/log /run/log
sudo systemctl restart systemd-journald
journalctl --since='5 minutes ago' --no-pager | tail

时区显示与时钟同步分开验收

业务日志可统一使用 UTC;改变显示时区不会让未同步的时钟变准确
timedatectl
timedatectl show --property=Timezone --property=NTPSynchronized
timedatectl timesync-status 2>/dev/null || true
timedatectl show-timesync --all 2>/dev/null || true

`timedatectl set-ntp true` 会启用系统识别到的第一个网络时间同步服务,不保证一定是 systemd-timesyncd。先确认当前管理者和同步证据;容器里时钟通常由宿主机控制。证书、分布式锁和事件排序依赖的是实际时间偏差,而不是屏幕显示为某个时区。

初始化结束前的验收清单

  • 基线包含 UTC 时间、发行版、内核、虚拟化、容量、监听、失败 unit、时间与日志状态。
  • 每个变更有单独候选、收益指标、停止条件和已验证恢复入口。
  • 软件包计划经过模拟和再次复核,没有未解释的移除、hold 或配置文件决策。
  • 服务限制读取自实际进程;sysctl 读取自实际键,并有业务指标对照。
  • swap 目标和文件系统兼容性已证明;日志的持久/运行时预算与保留要求一致。
  • 外部连接、核心业务探针、错误率、资源趋势和重启后的启动状态全部通过。
  • 临时计划、测试 unit 和敏感基线已按保留策略处理,变更单记录了真实结果。

回滚配置,也撤销错误假设

  1. 一次只回滚一个变更:恢复旧文件或删除本次 drop-in,重新加载对应管理器。
  2. 读取实际生效值,重启或 reload 目标服务,再跑同一系统与用户侧探针。
  3. 软件包已执行的 schema、数据或配置迁移不能只靠降包;按应用恢复手册处理。
  4. 若回滚后指标没有恢复,停止继续套用参数,回到基线重新定位瓶颈。
  5. 保留失败候选、时间线和观测差异,避免下一次把同一猜测重新包装成“最佳实践”。

本次隔离演练验证了什么

VPScope 在 Ubuntu 24.04 上保存只读主机基线,运行 `apt-get --simulate upgrade` 并得到 291 个待升级、0 个新增、0 个移除和 6 个保留包;APT/dpkg 状态未变化。有效 sysctl 候选通过 dry run,故意不存在的键返回状态 1。有效 systemd service 通过 verify,缺少绝对可执行路径的候选返回状态 1。

一个自动回收的 transient service 以 `LimitNOFILE=4096` 启动,并在进程内同时读到 4096 的软硬限制。替代根目录中的 journald drop-in 被 `cat-config` 合并显示;8 MiB 临时文件写入 swap 签名但从未启用。时区与 NTP 同步状态只读检查通过,全部临时文件和 transient unit 清理完成。演练没有升级包、写入 `/etc`、修改内核值、启用 swap、重启服务或触碰 SSH/防火墙。

返回知识库