4. 自动化更新策略:补丁管理工具与灰度发布

补丁管理这事儿,说起来简单,做起来全是坑。我见过太多团队,要么是「补丁恐惧症」—— 服务器几年不更新,漏洞一堆;要么是「补丁狂热症」—— 补丁一下来就全量推,结果把自己搞宕机。

这一章,咱们就聊聊怎么用工具把补丁管理自动化,再配上灰度发布和回滚机制,做到既安全又稳妥。

4.1 补丁管理工具选型

工具选对了,事半功倍。我这些年用过不少,挑三个有代表性的说说。

4.1.1 WSUS:Windows 环境的老大哥

WSUS(Windows Server Update Services)是微软自家的补丁管理工具。说白了,它就是个「补丁中转站」—— 微软把补丁发到你的 WSUS 服务器,你再审批、分发到内网机器。

核心价值:内网环境不需要每台机器都去微软官网下载补丁,节省带宽,也方便统一管控。

我习惯这么配置 WSUS:

  1. 分组管理:按业务线、环境(开发/测试/生产)建组
  2. 自动审批规则:安全补丁自动审批,功能更新手动审批
  3. 同步频率:每天凌晨同步一次,避开业务高峰

举个例子,一个典型的 WSUS 分组策略:

组名 包含机器 审批策略 安装截止时间
DEV-ALL 所有开发服务器 自动审批 24小时内
TEST-SQL 测试数据库 手动审批 48小时内
PROD-WEB 生产 Web 集群 手动审批 + 灰度 72小时内

注意:WSUS 只管理 Windows 更新,不能管第三方软件。而且 WSUS 本身也要打补丁,我见过有人忘了更新 WSUS 服务器,结果它自己先被攻破了。

4.1.2 Ansible:无代理的灵活之选

Ansible 是我个人最喜欢的自动化工具。它不需要在目标机器上装 agent,通过 SSH 就能干活。对于补丁管理,Ansible 的 yum、apt、win_updates 模块特别好用。

一个典型的 Linux 补丁更新 playbook 长这样:

---
- name: 安全补丁更新
  hosts: prod-web
  become: yes
  tasks:
    - name: 更新所有安全补丁
      yum:
        name: '*'
        security: yes
        state: latest
      register: patch_result

    - name: 需要重启吗?
      debug:
        msg: "系统需要重启"
      when: patch_result.restart_needed

    - name: 记录补丁日志
      copy:
        content: "{{ patch_result.results | to_nice_json }}"
        dest: "/var/log/patch_{{ ansible_date_time.date }}.log"

嗯,这里要注意:Ansible 的幂等性做得很好,同一个 playbook 跑多次不会出问题。但如果你在 playbook 里写了 state: latest,它会把所有能更新的包都更新了 —— 这有时候不是你想要的效果。

我的习惯:生产环境用 state: latest 配合 security: yes,只更新安全补丁。功能更新走单独的 playbook,手动触发。

4.1.3 SaltStack:大规模并发的利器

SaltStack 跟 Ansible 的思路不太一样。它用 ZeroMQ 做消息队列,支持 master-minion 架构,并发能力极强。我在管理 5000+ 台服务器的时候,SaltStack 的表现让我印象深刻。

SaltStack 的补丁管理用 state 文件描述:

# /srv/salt/patch/security.sls
security-patches:
  pkg.uptodate:
    - refresh: True
    - only_upgrade: True
    - kwargs:
        security: True

reboot-if-needed:
  module.run:
    - name: system.reboot
    - onchanges:
      - pkg: security-patches
    - kwargs:
        timeout: 60
        in_seconds: True

执行命令:

salt 'prod-*' state.apply patch.security

为什么会选 SaltStack?因为它支持 targeting,可以用 Grains(机器属性)或 Pillar(自定义数据)来精确控制哪些机器打哪些补丁。比如「只给 CentOS 7.9 的 Web 服务器打补丁」:

salt -G 'osrelease:7.9 and role:web' state.apply patch.security

选型建议:

  • Windows 为主 → WSUS
  • Linux 为主,机器 < 500 → Ansible
  • Linux 为主,机器 > 1000 → SaltStack
  • 混合环境 → Ansible + WSUS 组合

4.2 灰度发布策略

补丁全量推送?那是给自己找麻烦。我经历过一次,一个补丁导致某业务模块崩溃,200 多台机器同时挂掉。从那以后,灰度发布就成了我的铁律。

4.2.1 灰度发布模型

下面这张图是我常用的灰度发布流程:

灰度发布补丁流程 补丁发布 灰度 1% 机器 灰度 5% 机器 灰度 20% 机器 全量发布 回滚? 回滚路径 观察期: 每阶段观察 30-60 分钟 监控 CPU、内存、错误率 确认无异常再继续

4.2.2 灰度发布的关键指标

灰度不是随便选几台机器就完事了。你得盯着几个关键指标:

  • 错误率:5xx 错误是否上升?比基线高 1% 就停
  • 响应时间:P99 延迟是否增加?超过 20% 就回滚
  • 系统资源:CPU、内存、磁盘 I/O 是否异常
  • 业务指标:订单量、登录成功率等业务数据是否正常

我的做法:灰度期间,我会把监控告警阈值调低一半。比如正常是 5% 错误率告警,灰度时 2.5% 就告警。宁可误报,不能漏报。

4.3 回滚机制

补丁出了问题怎么办?回滚。但回滚不是「把补丁卸载」那么简单。我见过有人回滚到一半,系统直接挂了,因为补丁修改了数据库 schema。

4.3.1 回滚策略对比

回滚方式 适用场景 优点 缺点
补丁卸载 Windows 更新 操作简单,系统自带 卸载可能不干净,依赖链复杂
快照回滚 虚拟机环境 秒级恢复,状态完全一致 需要提前打快照,存储开销大
蓝绿部署 容器/微服务 零停机,切换快 资源翻倍,成本高
版本控制回滚 配置文件/代码 精确到文件级别 依赖完善的版本管理

4.3.2 回滚脚本示例

用 Ansible 做回滚,我习惯这么写:

---
- name: 回滚补丁
  hosts: "{{ target_hosts }}"
  become: yes
  tasks:
    - name: 检查是否有快照
      stat:
        path: "/snapshot/pre-patch-{{ patch_date }}.img"
      register: snapshot_file

    - name: 如果有快照,用快照回滚
      command: "qemu-img snapshot -a pre-patch-{{ patch_date }} /dev/sda"
      when: snapshot_file.stat.exists

    - name: 没有快照,尝试卸载补丁
      yum:
        name: "{{ rollback_package }}"
        state: absent
      when: not snapshot_file.stat.exists

    - name: 验证回滚结果
      command: "rpm -q {{ rollback_package }}"
      register: verify_result
      failed_when: verify_result.rc == 0

我曾经踩过的坑:有一次回滚数据库补丁,卸载后数据库版本降级了,但数据文件格式不兼容,直接起不来。从那以后,我定了个规矩:数据库补丁只做快照回滚,不做卸载回滚。

4.4 自动化更新策略的最佳实践

说了这么多,总结几条我这些年沉淀下来的经验:

  1. 补丁分级:安全补丁、功能补丁、驱动补丁分开管理,优先级不同
  2. 环境隔离:开发 → 测试 → 灰度 → 生产,逐级推进
  3. 自动化测试:补丁上线前,跑一遍自动化测试用例
  4. 回滚预案:每次更新前,写好回滚脚本,测试过再上线
  5. 审计日志:谁、什么时候、打了什么补丁、结果如何,全记下来

一句话总结:补丁管理不是「打了就行」,而是「打了不出事」。灰度发布是刹车,回滚机制是气囊,两者缺一不可。