1.1 背景介绍
每个季度的安全补丁更新是运维团队的噩梦。上个季度我们还在用 SSH 跳板机 + xshell 批量执行脚本的方式给服务器打补丁,500 台机器分 10 批次,整整熬了三个通宵。中间还出了两次事故:一次是补丁依赖冲突导致某台数据库服务器重启失败,另一次是并发太高把内网带宽打满了。
痛定思痛,我们决定用 Ansible 重构整个补丁管理流程。这套方案跑了两个季度,现在 500 台服务器的补丁更新,从晚上 10 点开始,第二天早上 6 点收工,全程无人值守。
1.2 技术特点
- 幂等性保障:Ansible 的 yum/apt 模块天然支持幂等操作,重复执行不会造成问题,中断后可以安全重试
- 滚动更新:通过 serial 参数控制并发数,避免同时重启太多机器影响业务
- 自动回滚:结合健康检查,发现异常自动停止后续批次,保留现场等待人工介入
- 详细日志:每台机器的执行结果都有记录,事后审计和故障定位都很方便
1.3 适用场景
- 场景一:季度/月度安全补丁批量更新,需要在维护窗口内完成大量服务器的系统升级
- 场景二:紧急漏洞修复,比如 log4j 这种 0day,需要快速在全网推送修复补丁
- 场景三:内核升级,需要有序重启服务器并验证业务恢复状态
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| Ansible 控制节点 |
CentOS 7+ / Ubuntu 18.04+ |
建议使用专用的运维跳板机 |
| Ansible |
2.9+ (推荐 2.12+) |
2.12 版本的性能优化明显 |
| Python |
3.6+ |
目标机器需要安装 Python |
| 目标服务器 |
CentOS 7/8、Ubuntu 18/20/22 |
混合环境需要区分 playbook |
| 网络 |
控制节点到目标机器 SSH 可达 |
建议配置 SSH 密钥认证 |
二、详细步骤
2.1 准备工作
◆ 2.1.1 系统检查
# 检查 Ansible 版本
ansible --version
# 输出示例:
# ansible [core 2.14.3]
# python version = 3.9.16
# 检查控制节点到目标机器的连通性(抽样测试几台)
ansible -i inventory/prod.ini webservers -m ping --limit 'web-001,web-002,web-003'
# 检查目标机器的磁盘空间(补丁包需要空间)
ansible -i inventory/prod.ini all -m shell -a "df -h / | tail -1 | awk '{print \$5}'" --limit 'web-001'
◆ 2.1.2 安装依赖
# CentOS/RHEL 安装 Ansible
sudo yum install -y epel-release
sudo yum install -y ansible
# Ubuntu/Debian 安装 Ansible
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# 安装额外的 collection(用于更丰富的模块支持)
ansible-galaxy collection install ansible.posix
ansible-galaxy collection install community.general
◆ 2.1.3 配置 Ansible
# 创建项目目录结构
mkdir -p ~/ansible-patching/{inventory,group_vars,roles,logs}
cd ~/ansible-patching
# 生成 ansible.cfg
cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory/hosts.ini
remote_user = ops
private_key_file = ~/.ssh/ops_key
host_key_checking = False
timeout = 30
forks = 20
log_path = ./logs/ansible.log
callback_whitelist = profile_tasks
[privilege_escalation]
become = True
become_method = sudo
become_user = root
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-%%h-%%p-%%r
EOF
2.2 核心配置
◆ 2.2.1 主机清单配置
# inventory/hosts.ini
# 按照业务分组,方便灰度发布
[webservers]
web-[001:100].prod.internal
[appservers]
app-[001:150].prod.internal
[dbservers]
db-[001:050].prod.internal
[cacheservers]
redis-[001:030].prod.internal
memcache-[001:020].prod.internal
# 按照机房/可用区分组
[dc1]
web-[001:050].prod.internal
app-[001:075].prod.internal
[dc2]
web-[051:100].prod.internal
app-[076:150].prod.internal
# 定义更新批次(第一批是金丝雀,数量少)
[canary]
web-001.prod.internal
app-001.prod.internal
redis-001.prod.internal
[batch1]
web-[002:020].prod.internal
app-[002:030].prod.internal
[batch2]
web-[021:050].prod.internal
app-[031:075].prod.internal
# ... 后续批次类似
说明:主机清单的设计很关键。我们按照三个维度分组:业务类型、机房位置、更新批次。这样在执行时可以灵活控制范围,比如只更新某个机房,或者只更新某类服务器。
◆ 2.2.2 变量配置
# group_vars/all.yml
---
# 补丁更新相关配置
patching:
# 是否允许重启
allow_reboot: true
# 重启前等待时间(秒)
reboot_delay: 30
# 重启超时时间(秒)
reboot_timeout: 600
# 更新后健康检查等待时间
health_check_delay: 60
# 排除的包(某些包不想自动更新)
exclude_packages:
- kernel*
- docker*
# 仅安全更新
security_only: true
# 通知配置
notification:
webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
enabled: true
# group_vars/dbservers.yml
---
# 数据库服务器特殊配置
patching:
allow_reboot: false # 数据库服务器默认不自动重启
exclude_packages:
- kernel*
- mysql*
- mariadb*
参数说明:
- allow_reboot:是否允许自动重启。数据库这类有状态服务建议设为 false,手动处理
- reboot_timeout:重启超时时间,老机器启动慢的话需要调大
- exclude_packages:排除列表,避免误更新关键组件
- security_only:只安装安全补丁,不做全量更新
◆ 2.2.3 核心 Playbook
# playbooks/patching.yml
---
- name: 系统补丁更新 - 预检查
hosts: "{{ target_hosts | default('all') }}"
gather_facts: yes
serial: "{{ batch_size | default(50) }}"
tasks:
- name: 检查磁盘空间
assert:
that:
- ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first | int > 1073741824
fail_msg: "根分区剩余空间不足 1GB,跳过此主机"
tags: precheck
- name: 检查系统负载
assert:
that:
- ansible_processor_vcpus > 0
- (ansible_load_average.1 / ansible_processor_vcpus) < 2.0
fail_msg: "系统负载过高,跳过此主机"
tags: precheck
- name: 记录更新前的包版本
shell: |
rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}\n' | sort > /tmp/packages_before_{{ ansible_date_time.date }}.txt
when: ansible_os_family == "RedHat"
tags: precheck
- name: 系统补丁更新 - 执行更新
hosts: "{{ target_hosts | default('all') }}"
gather_facts: yes
serial: "{{ batch_size | default(50) }}"
max_fail_percentage: 10
pre_tasks:
- name: 发送开始通知
uri:
url: "{{ notification.webhook_url }}"
method: POST
body_format: json
body:
msgtype: "text"
text:
content: "补丁更新开始: {{ inventory_hostname }} (批次 {{ ansible_play_batch }})"
delegate_to: localhost
when: notification.enabled | default(false)
ignore_errors: yes
run_once: yes
tasks:
- name: 更新 YUM 缓存
yum:
update_cache: yes
when: ansible_os_family == "RedHat"
- name: 安装安全补丁 (RHEL/CentOS)
yum:
name: '*'
state: latest
security: "{{ patching.security_only | default(true) }}"
exclude: "{{ patching.exclude_packages | default([]) }}"
register: yum_result
when: ansible_os_family == "RedHat"
- name: 更新 APT 缓存
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: 安装安全补丁 (Ubuntu/Debian)
apt:
upgrade: dist
update_cache: yes
register: apt_result
when: ansible_os_family == "Debian"
- name: 检查是否需要重启
stat:
path: /var/run/reboot-required
register: reboot_required_file
when: ansible_os_family == "Debian"
- name: 检查是否需要重启 (RHEL)
command: needs-restarting -r
register: needs_restarting
failed_when: false
changed_when: false
when: ansible_os_family == "RedHat"
- name: 设置重启标志
set_fact:
needs_reboot: >-
{{ (ansible_os_family == "Debian" and reboot_required_file.stat.exists | default(false)) or
(ansible_os_family == "RedHat" and needs_restarting.rc == 1) }}
- name: 重启服务器
reboot:
reboot_timeout: "{{ patching.reboot_timeout | default(600) }}"
pre_reboot_delay: "{{ patching.reboot_delay | default(30) }}"
post_reboot_delay: 30
msg: "Ansible 补丁更新后重启"
when:
- needs_reboot | default(false)
- patching.allow_reboot | default(true)
- name: 等待服务恢复
wait_for:
port: "{{ item }}"
timeout: 120
loop: "{{ service_ports | default([22]) }}"
when: needs_reboot | default(false)
post_tasks:
- name: 记录更新后的包版本
shell: |
rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}\n' | sort > /tmp/packages_after_{{ ansible_date_time.date }}.txt
diff /tmp/packages_before_{{ ansible_date_time.date }}.txt /tmp/packages_after_{{ ansible_date_time.date }}.txt > /tmp/packages_diff_{{ ansible_date_time.date }}.txt || true
when: ansible_os_family == "RedHat"
- name: 健康检查
uri:
url: "http://localhost:{{ health_check_port | default(8080) }}/health"
status_code: 200
timeout: 30
register: health_check
retries: 3
delay: 10
until: health_check.status == 200
when: health_check_port is defined
ignore_errors: yes
- name: 发送完成通知
uri:
url: "{{ notification.webhook_url }}"
method: POST
body_format: json
body:
msgtype: "text"
text:
content: "补丁更新完成: {{ inventory_hostname }} - 状态: {{ 'SUCCESS' if not (health_check.failed | default(false)) else 'FAILED' }}"
delegate_to: localhost
when: notification.enabled | default(false)
ignore_errors: yes
2.3 启动和验证
◆ 2.3.1 执行补丁更新
# 先在金丝雀环境测试
ansible-playbook playbooks/patching.yml -e "target_hosts=canary" -e "batch_size=1" --check
# 确认无误后正式执行金丝雀批次
ansible-playbook playbooks/patching.yml -e "target_hosts=canary" -e "batch_size=1"
# 金丝雀通过后,分批次执行
ansible-playbook playbooks/patching.yml -e "target_hosts=batch1" -e "batch_size=10"
ansible-playbook playbooks/patching.yml -e "target_hosts=batch2" -e "batch_size=20"
# 或者一次性执行全部,由 serial 控制并发
ansible-playbook playbooks/patching.yml -e "target_hosts=all" -e "batch_size=30"
◆ 2.3.2 功能验证
# 查看执行日志
tail -f logs/ansible.log
# 检查更新结果汇总
ansible -i inventory/hosts.ini all -m shell -a "cat /tmp/packages_diff_*.txt 2>/dev/null | head -20" --limit 'web-001'
# 检查服务状态
ansible -i inventory/hosts.ini webservers -m shell -a "systemctl is-active nginx"
# 预期输出:每台机器都返回 active
三、示例代码和配置
3.1 完整配置示例
◆ 3.1.1 无人值守调度脚本
#!/bin/bash
# 文件路径:/opt/ansible-patching/run_patching.sh
# 功能:无人值守补丁更新调度脚本
set -e
WORK_DIR="/opt/ansible-patching"
LOG_DIR="${WORK_DIR}/logs"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="${LOG_DIR}/patching_${DATE}.log"
cd ${WORK_DIR}
# 函数:发送通知
send_notification() {
local message=$1
local webhook_url="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
curl -s -X POST ${webhook_url} \
-H 'Content-Type: application/json' \
-d "{\"msgtype\": \"text\", \"text\": {\"content\": \"${message}\"}}" \
> /dev/null 2>&1 || true
}
# 函数:执行单个批次
run_batch() {
local batch_name=$1
local batch_size=$2
echo "[$(date)] 开始执行批次: ${batch_name}" | tee -a ${LOG_FILE}
send_notification "[补丁更新] 开始执行批次: ${batch_name}"
if ansible-playbook playbooks/patching.yml \
-e "target_hosts=${batch_name}" \
-e "batch_size=${batch_size}" \
>> ${LOG_FILE} 2>&1; then
echo "[$(date)] 批次 ${batch_name} 执行成功" | tee -a ${LOG_FILE}
send_notification "[补丁更新] 批次 ${batch_name} 执行成功"
return 0
else
echo "[$(date)] 批次 ${batch_name} 执行失败" | tee -a ${LOG_FILE}
send_notification "[补丁更新] 批次 ${batch_name} 执行失败,请检查"
return 1
fi
}
# 主流程
main() {
send_notification "[补丁更新] 开始执行,预计耗时 6-8 小时"
# 金丝雀批次
if ! run_batch "canary" 1; then
send_notification "[补丁更新] 金丝雀批次失败,终止后续更新"
exit 1
fi
# 等待 10 分钟观察金丝雀
echo "[$(date)] 等待 10 分钟观察金丝雀环境..." | tee -a ${LOG_FILE}
sleep 600
# 后续批次
local batches=("batch1:10" "batch2:20" "batch3:30" "batch4:50" "batch5:50")
for batch_config in "${batches[@]}"; do
batch_name="${batch_config%%:*}"
batch_size="${batch_config##*:}"
if ! run_batch "${batch_name}" "${batch_size}"; then
send_notification "[补丁更新] 批次 ${batch_name} 失败,终止后续更新"
exit 1
fi
# 批次间间隔 5 分钟
echo "[$(date)] 批次间隔等待 5 分钟..." | tee -a ${LOG_FILE}
sleep 300
done
send_notification "[补丁更新] 全部完成!请检查各服务状态"
}
main "$@"
◆ 3.1.2 定时任务配置
# /etc/cron.d/ansible-patching
# 每月第一个周六晚上 22:00 执行补丁更新
0 22 1-7 * 6 root /opt/ansible-patching/run_patching.sh
3.2 实际应用案例
◆ 案例一:紧急漏洞修复
场景描述:2024 年某天早上收到安全团队通知,某个 glibc 漏洞需要紧急修复,要求当天完成全网更新。
实现代码:
# playbooks/emergency_patch.yml
---
- name: 紧急补丁修复
hosts: "{{ target_hosts | default('all') }}"
gather_facts: yes
serial: 100 # 紧急情况提高并发
tasks:
- name: 仅更新指定包
yum:
name: "{{ emergency_packages }}"
state: latest
when: ansible_os_family == "RedHat"
- name: 验证补丁版本
shell: "rpm -q {{ item }}"
loop: "{{ emergency_packages }}"
register: version_check
when: ansible_os_family == "RedHat"
- name: 显示更新后版本
debug:
msg: "{{ version_check.results | map(attribute='stdout') | list }}"
运行命令:
ansible-playbook playbooks/emergency_patch.yml \
-e "target_hosts=all" \
-e '{"emergency_packages": ["glibc", "glibc-common"]}' \
--forks 100
运行结果:
PLAY RECAP *********************************************************************
web-001.prod.internal : ok=4 changed=1 unreachable=0 failed=0 skipped=0
web-002.prod.internal : ok=4 changed=1 unreachable=0 failed=0 skipped=0
...
500 台服务器全部更新完成,耗时 45 分钟
◆ 案例二:内核升级与滚动重启
场景描述:需要升级内核修复某个性能问题,但必须保证业务不中断,采用滚动重启方式。
实现步骤:
- 首先修改配置,允许内核更新和自动重启
- 将主机按照业务分组,确保每组重启时有其他组提供服务
- 使用 serial: 1 逐台执行,配合负载均衡摘除
# playbooks/kernel_upgrade.yml
---
- name: 内核升级 - 滚动更新
hosts: "{{ target_hosts }}"
gather_facts: yes
serial: 1 # 逐台执行
tasks:
- name: 从负载均衡摘除
uri:
url: "http://{{ lb_api }}/api/v1/upstream/{{ inventory_hostname }}/down"
method: POST
delegate_to: localhost
when: lb_api is defined
- name: 等待连接排空
wait_for:
timeout: 60
- name: 升级内核
yum:
name: kernel
state: latest
register: kernel_update
- name: 重启服务器
reboot:
reboot_timeout: 900
msg: "Ansible 内核升级重启"
when: kernel_update.changed
- name: 验证内核版本
shell: uname -r
register: kernel_version
- name: 重新加入负载均衡
uri:
url: "http://{{ lb_api }}/api/v1/upstream/{{ inventory_hostname }}/up"
method: POST
delegate_to: localhost
when: lb_api is defined
- name: 等待流量恢复
wait_for:
timeout: 30
四、最佳实践和注意事项
4.1 最佳实践
◆ 4.1.1 性能优化
- 开启 SSH Pipelining:减少 SSH 连接次数,大幅提升执行速度
# ansible.cfg
[ssh_connection]
pipelining = True
- 调整 forks 数量:根据控制节点性能和网络情况调整并发数
- 4 核 8G 的机器,forks 设置 50-100 比较合适
- 网络带宽充足时可以适当调高
- 更新包比较大时要降低,避免打满带宽
- 使用 Mitogen 加速:Mitogen 是一个 Ansible 加速插件,实测能提升 2-5 倍速度
pip install mitogen
# ansible.cfg 添加
[defaults]
strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy
strategy = mitogen_linear
◆ 4.1.2 安全加固
# 创建加密的变量文件
ansible-vault create group_vars/all/vault.yml
# 内容示例
vault_webhook_key: "your-secret-key"
vault_ssh_password: "your-password"
- 限制 sudo 权限:运维账号的 sudoers 配置只开放必要权限
# /etc/sudoers.d/ops
ops ALL=(root) NOPASSWD: /usr/bin/yum, /usr/bin/apt, /usr/sbin/reboot, /usr/bin/systemctl
◆ 4.1.3 高可用配置
- 多控制节点:部署多个 Ansible 控制节点,避免单点故障
- AWX/Tower:生产环境建议使用 AWX 或 Ansible Tower,提供 Web 界面、权限控制、执行历史等功能
- 备份 Playbook:将 Playbook 代码托管到 Git,做好版本管理
4.2 注意事项
◆ 4.2.1 配置注意事项
警告:补丁更新可能导致服务中断,务必在维护窗口执行,并提前做好回滚准备!
- 注意事项一:数据库、消息队列等有状态服务,建议单独处理,不要混在批量更新里
- 注意事项二:更新前一定要检查磁盘空间,空间不足会导致更新失败甚至系统损坏
- 注意事项三:生产环境务必先在金丝雀/预发布环境验证,确认无问题再推全量
◆ 4.2.2 常见错误
| 错误现象 |
原因分析 |
解决方案 |
| SSH 连接超时 |
目标机器 SSHD 服务异常或网络不通 |
检查网络连通性和 SSHD 服务状态 |
| sudo 密码错误 |
密码过期或权限配置问题 |
检查 sudoers 配置,考虑使用 NOPASSWD |
| yum lock 错误 |
其他进程占用 yum 锁 |
kill 掉占用进程或等待其完成 |
| 重启后连接失败 |
启动时间过长或启动失败 |
增加 reboot_timeout,检查启动日志 |
| 包依赖冲突 |
第三方源与官方源冲突 |
使用 exclude 排除问题包,手动处理 |
◆ 4.2.3 兼容性问题
- 版本兼容:CentOS 7 和 CentOS 8 的包管理命令有差异(yum vs dnf),Ansible 的 yum 模块会自动处理
- 平台兼容:混合环境(RHEL + Ubuntu)需要用 when 条件判断,或者拆分成多个 playbook
- 组件依赖:某些包更新后可能需要重启服务才能生效,需要在 playbook 中处理
五、故障排查和监控
5.1 故障排查
◆ 5.1.1 日志查看
# 查看 Ansible 执行日志
tail -f /opt/ansible-patching/logs/ansible.log
# 查看详细输出(调试用)
ansible-playbook playbooks/patching.yml -vvv
# 查看目标机器的 yum 日志
ssh web-001 "tail -100 /var/log/yum.log"
# 查看系统日志(排查重启问题)
ssh web-001 "journalctl -b -1 --no-pager | tail -100"
◆ 5.1.2 常见问题排查
问题一:部分主机执行失败但没有详细错误信息
# 单独对失败主机执行,增加详细输出
ansible-playbook playbooks/patching.yml -e "target_hosts=web-001" -vvv
解决方案:
- 查看 -vvv 输出找到具体错误
- SSH 到目标机器手动执行命令验证
- 检查目标机器的系统日志
问题二:重启后服务没有自动恢复
# 检查服务状态
ansible -i inventory/hosts.ini webservers -m shell -a "systemctl status nginx"
# 检查服务是否设置了开机自启
ansible -i inventory/hosts.ini webservers -m shell -a "systemctl is-enabled nginx"
解决方案:确保关键服务都设置了 enable,在 playbook 中增加服务状态检查
问题三:某些包被跳过没有更新
- 症状:yum 报告某些包没有更新,但实际上有新版本
- 排查:检查 exclude 配置和 yum 仓库配置
- 解决:确认包名没有被意外排除,检查仓库优先级
◆ 5.1.3 调试模式
# 开启 Ansible 调试模式
export ANSIBLE_DEBUG=1
ansible-playbook playbooks/patching.yml -vvv
# 使用 --step 逐步执行
ansible-playbook playbooks/patching.yml --step
# 从某个任务开始执行(之前任务跳过)
ansible-playbook playbooks/patching.yml --start-at-task="重启服务器"
5.2 性能监控
◆ 5.2.1 关键指标监控
# 监控 Ansible 控制节点资源
top -p $(pgrep -f ansible)
# 监控网络带宽使用
iftop -i eth0
# 监控执行进度
watch -n 5 'grep -c "ok=" /opt/ansible-patching/logs/ansible.log'
◆ 5.2.2 监控指标说明
| 指标名称 |
正常范围 |
告警阈值 |
说明 |
| 批次成功率 |
100% |
< 95% |
单批次失败率超过 5% 需要人工介入 |
| 单机执行时间 |
3-10 分钟 |
> 30 分钟 |
执行时间过长可能有问题 |
| 控制节点 CPU |
< 50% |
> 80% |
CPU 过高需要降低 forks |
| 网络带宽 |
< 70% |
> 90% |
带宽打满需要降低并发 |
◆ 5.2.3 监控告警配置
# Prometheus 告警规则示例
groups:
- name: ansible-patching
rules:
- alert: PatchingBatchFailed
expr: ansible_batch_failed_hosts > 0
for: 5m
labels:
severity: critical
annotations:
summary: "补丁更新批次失败"
description: "批次 {{ $labels.batch }} 有 {{ $value }} 台主机失败"
5.3 备份与恢复
◆ 5.3.1 备份策略
#!/bin/bash
# 补丁更新前的快照备份脚本
HOSTS_FILE=$1
DATE=$(date +%Y%m%d)
# 对虚拟机创建快照(需要 vCenter API 或 cloud provider API)
while read host; do
echo "Creating snapshot for ${host}..."
# VMware 示例
# govc vm.snapshot.create -vm="${host}" "pre-patching-${DATE}"
# AWS 示例
# aws ec2 create-snapshot --volume-id $(get_volume_id ${host}) --description "pre-patching-${DATE}"
done < ${HOSTS_FILE}
◆ 5.3.2 恢复流程
- 停止服务:
ansible -i inventory/hosts.ini target_host -m service -a "name=nginx state=stopped"
- 恢复快照:通过虚拟化平台或云平台恢复到补丁前的快照
- 验证完整性:启动服务器,检查服务状态和数据完整性
- 重启服务:
ansible -i inventory/hosts.ini target_host -m service -a "name=nginx state=started"
六、总结
6.1 技术要点回顾
- 要点一:合理设计主机清单分组,支持灵活的灰度发布策略
- 要点二:使用 serial 和 max_fail_percentage 控制更新节奏和容错
- 要点三:更新前后做好检查和记录,便于追溯和回滚
- 要点四:区分处理有状态和无状态服务,数据库等关键服务单独处理
6.2 进阶学习方向
- AWX/Ansible Tower:生产环境建议使用,提供更好的可视化和权限管理
- Ansible Collections:学习使用官方和社区的 Collection,避免重复造轮子
- 配合 CI/CD 流水线:将补丁更新集成到 GitOps 流程中
- 学习资源:Jenkins/GitLab CI 与 Ansible 集成
- 实践建议:补丁更新 playbook 提交到 Git,通过 MR 审批后自动执行
6.3 参考资料
- Ansible 官方文档 - 最权威的参考
- Red Hat 补丁管理最佳实践 - 企业级方案参考
- Ansible for DevOps - 推荐书籍
- r/ansible - 社区讨论