etckeeper 配置版本管理实战:用 Git 纳管 /etc,让每次改配置都留痕、可回滚、可审计

服务器上最危险的东西,往往不是硬盘坏了,而是某个配置文件被人改过、却没人知道改了什么、什么时候改的。Nginx 突然 502、PHP 连不上数据库、SSH 再也登不进去——追查到最后,十有八九是 /etc 下的某个文件被改动过。本文讲一个几乎零成本、却能救命的习惯:用 etckeeper 把 /etc 整个目录纳入 Git 版本管理。

为什么 /etc 需要版本管理

绝大多数站长对代码会做版本控制,对 /etc 却完全放任。原因很简单:/etc 是"系统状态",不是"项目代码"。但现实是,/etc 里的东西和代码一样会被反复修改:装个软件、改个 Nginx 参数、调一下 PHP 的 php.ini、动一次防火墙规则。时间一长,没人记得住三天前把什么改坏了。

传统备份解决不了这个问题。备份是"某个时间点的快照",而你需要的是"两个时间点之间的差异"——是"谁在什么时候把这一行从 A 改成了 B"。这正是 Git 擅长的事。

etckeeper 就是干这个的:它是一组脚本,把 /etc 变成一个 Git(或 Mercurial、Bazaar)仓库,并且跟包管理器(apt/dnf/pacman)集成,在每次安装、升级、卸载软件包前后自动提交一次。这样你的 /etc 就有了完整的变更历史。

etckeeper 相比手动 git init 的优势

有人会说:我自己 cd /etc && git init 不行吗?可以,但会踩一堆坑,etckeeper 帮你解决了:

  • 文件权限与属主:/etc 里有大量只有 root 能读的文件(比如 shadow、私钥、sudoers),还有各种特殊权限位(setuid)。Git 默认不记录这些。.git 目录如果权限设置不当,等于把服务器密码本公开。etckeeper 用 etckeeper 的元数据机制处理权限,并强制 .git 目录 700。
  • 忽略规则:/etc 里有很多不该进版本库的东西,比如临时文件、缓存、mtab、resolv.conf 这种由系统动态生成的、机器相关的文件。etckeeper 自带一套成熟的 .gitignore。
  • 与包管理器集成:手动 git 不会在 apt 升级后自动提交,你会漏掉一大堆变更。etckeeper 挂进 apt 的 hook,装包前后各提交一次,还带包名注释。
  • 变更前后自动快照:配合 etckeeper vcs 子命令,可以在任何操作前后由脚本触发提交。

安装与初始化

Debian/Ubuntu 上直接装:

sudo apt update
sudo apt install etckeeper

安装过程本身会触发一次初始化。如果没触发,或者你想手动控制,可以运行:

sudo etckeeper init

这一步会在 /etc 下创建 Git 仓库,写入 .gitignore,并提交第一版(通常注释是 "Initial commit" 或 "saved /etc")。

安装时会有交互提示,重要的一项是是否允许 etckeeper 在 apt 操作时自动运行。默认是"是",建议保留。这一步实际上是往 /etc/apt/apt.conf.d/ 里放了一个 05etckeeper 钩子文件。

核心配置:/etc/etckeeper/etckeeper.conf

etckeeper 的主配置文件是 /etc/etckeeper/etckeeper.conf。几个关键选项:

# 使用哪个版本控制系统,取消注释对应行
VCS="git"
#VCS="hg"
#VCS="bzr"

# 高层的元数据:记录权限、属主、空目录等
# 对 /etc 强烈建议开启
HIGHLEVEL_PACKAGE_MANAGER=1

# 使用 Git 时的提交选项
GIT_COMMIT_OPTIONS=""

# 是否在 dailysnapshot 中自动提交
AVOID_DAILY_AUTOCOMMITS=1

大多数情况下默认配置就能用。HIGHLEVEL_PACKAGE_MANAGER=1 是推荐值,它会调用高级包管理钩子,记录得更完整。

如果你用的是 Git,还想让 etckeeper 在每次提交前做一些自定义检查,可以编辑 /etc/etckeeper/pre-commit.d/ 下的钩子脚本。

日常用法:查看、对比、回滚

etckeeper 本质上就是 Git 的包装,你可以直接进 /etc 用 git 命令,也可以用 etckeeper 提供的便捷子命令。

查看历史变更:

cd /etc
sudo git log --oneline -20

你会看到类似这样的记录,装包产生的提交会自动带上包名:

a1b2c3d saving uncommitted changes in /etc prior to apt run
9f8e7d6 committing changes in /etc after apt run
    Package: nginx

看某个文件的历史:

sudo git log --oneline -- nginx/nginx.conf
sudo git diff HEAD~5 -- nginx/nginx.conf

回滚单个文件到某个版本(这是最有用的操作之一):

# 查看 nginx.conf 的历史版本
sudo git log --oneline -- nginx/nginx.conf

# 把它恢复到指定 commit 的状态
sudo git checkout <commit-hash> -- nginx/nginx.conf

# 或者只撤销到上一个提交
sudo git checkout HEAD -- nginx/nginx.conf

回滚后记得重载对应服务,比如 sudo nginx -t && sudo systemctl reload nginx。

手动触发一次提交(改完配置后):

sudo etckeeper commit "调整 php.ini 上传大小限制"

查看整体状态:

sudo etckeeper vcs status

用 etckeeper 做"配置审计"

版本化之后,一项额外收益是"配置审计"。很多站长担心服务器被入侵后配置被偷偷篡改,etckeeper 正好能发现这个。你可以写个简单脚本,每天检查 /etc 是否有未提交的变更:

#!/bin/bash
# /root/check_etc.sh
cd /etc
if ! git diff --quiet || ! git diff --cached --quiet; then
    echo "警告:/etc 存在未提交的变更"
    git status --short
fi

把这段挂进 crontab,每天跑一次,把输出发给自己的邮箱或告警通道。正常情况下 /etc 不该有未提交的变更——如果有,要么是你自己刚改没提交,要么就是别人动了。

再进一步,可以用 git log --since="24 hours ago" --stat 列出最近一天所有改动过的配置文件和推送,形成一份"每日配置变更清单"。

异地备份:把仓库推到远程

etckeeper 的仓库默认只在本地。如果整台机器丢了,历史也就没了。建议加一个远程仓库:

cd /etc
sudo git remote add origin git@your-git-server:/srv/git/etc-$(hostname).git
sudo etckeeper commit "添加远程仓库"
sudo git push -u origin master

注意:/etc 里包含 shadow、ssh_host_*_key 等敏感内容,远程仓库必须是私有且可信的。如果你介意,可以在 .gitignore 里排除这些文件——但那样就无法完整恢复,需要权衡。我的做法是自建一台内网 Git 服务器(Gitea),只在内网可达,物理上不暴露公网。

实战场景:一次 Nginx 配置灾难的复盘

讲一个很多站长都遇到过的真实场景。某天下午,你在 Nginx 里加了段 try_files 规则想优化伪静态,改完 nginx -t 通过,于是 systemctl reload nginx。上线后一切正常,就去忙别的了。三天后有用户反馈"文章页全变成 404 了",你打开浏览器一看,果然。这时你试图回想:三天前到底改了什么?是不是那次改 try_files 改错了?还是后来动了别的地方?

如果你有 etckeeper,这个过程会变得非常轻松。首先查看最近几天 /etc 的所有变更:

cd /etc
sudo git log --since="7 days ago" --oneline
sudo git log --since="7 days ago" --stat

--stat 会列出每次提交改动了哪些文件。nginx/sites-available/default 出现了好几次,说明它是"重点嫌疑"。接着看这个文件的完整变更历史:

sudo git log -p -- nginx/sites-available/default

-p 会直接打印每次提交的完整 diff。你会清晰地看到三天前那次改动把 try_files $uri $uri/ =404; 改成了 try_files $uri $uri/ /index.php?$args; 之类,而真正导致 404 的是更早某次把 location / 块挪了位置。所有疑问在一屏 diff 里全部解答。

确定问题后,回滚:

sudo git checkout <正确的commit> -- nginx/sites-available/default
sudo nginx -t && sudo systemctl reload nginx

前后不到两分钟。没有 etckeeper 的话,你可能要在各种博客里翻找"我当初是怎么配的",或者干脆凭记忆重写一遍,结果引入新问题。这就是版本管理的价值:把"回忆"变成"查询"。

更妙的是,etckeeper 还能帮你回答"到底是谁改的"。如果是多人运维,每次提交前设置好 git config user.name,历史里就能看到操作者。再配合 SSH 的 auth.log(谁在什么时间登录),就能拼出完整的操作溯源链。

让编辑自动留痕:钩住编辑器与 shell

手动 commit 最大的问题是"容易忘"。有两个办法让留痕自动化。第一个是给 Vim 加一个退出时的检查钩子,或者更简单:在 ~/.bashrc 里为 root 定义一个包装函数:

# root 专属,放进 /root/.bashrc
nano() {
    command nano "$@"
    case "$1" in
        /etc/*) (cd /etc && sudo etckeeper commit "edit $1") ;;
    esac
}

这样每次用 nano /etc/... 保存退出后,/etc 会自动提交一次,注释里带上被编辑的文件名。虽然粗糙,但能覆盖绝大多数手动改配置的场景。

第二个办法更彻底:很多运维团队会禁用直接编辑 /etc,所有变更走 Ansible/Puppet 这类配置管理工具。但那是另一个话题了——对小规模站长来说,"编辑器钩子 + 每日审计"已经足够。

五个必踩的坑

  1. 忘了提交,直接重启服务导致改动丢失。etckeeper 不会自动捕获你手动的改动,要靠 etckeeper commit 或包管理器 hook。养成"改完配置立刻 commit"的习惯。更狠一点:给 /etc 下的编辑动作挂个 editor 退出钩子。
  2. .git 目录权限过大。如果 /etc/.git 权限是 755,普通用户可能读到历史里的密钥。检查 ls -ld /etc/.git,应为 drwx------。etckeeper 默认会设置,但别手动破坏它。
  3. 把机器相关的动态文件提交进去。resolv.conf、mtab、adjtime 这些每台机器不同、还会自动变,提交进去会造成大量无意义 diff。etckeeper 的默认 .gitignore 已经处理了,但如果你用自定义的,要留意。
  4. 在容器里用 etckeeper。容器里 /etc 通常是镜像的一部分,用 etckeeper 意义不大,而且会平白增大镜像。etckeeper 适合物理机或长期运行的 VPS。
  5. Git 提交时锁冲突。如果你的监控或脚本频繁触发提交,可能撞上 index.lock。避免在高频任务里调用 etckeeper commit,或用 flock 串行化。

和备份的关系:不是替代品

最后强调一点:etckeeper 是"审计和回滚工具",不是备份工具。它记录文本差异,不能恢复被删除的二进制文件、数据库数据、网站内容。正确的组合是:

  • etckeeper 管配置的历史与审计;
  • rsync / borg / restic 管整体文件级备份;
  • mysqldump / mariabackup 管数据库;
  • 三者都定期同步到异地。

用 etckeeper 的成本极低——一条 apt install,之后几乎无感,但它能在"改坏了配置却想不起来改了什么"的时候,几秒钟就把你救回来。对每个认真做站的人来说,这都是一项值得立刻补上的基础设施。

Last modification:October 8th, 2026 at 12:25 pm

Leave a Comment