优化大规模Ansible部署的最佳实践

通过forks、策略插件、事实缓存、SSH复用和更好的剧本设计来加速大型Ansible运行的实用方法。

优化大规模Ansible部署的最佳实践

大型Ansible部署通常会因为显而易见的原因变慢:过多的SSH握手、过多的事实收集、控制器CPU不足,或者剧本让每个主机等待最慢的那个。修复方法很少是单一设置。通过减少连接开销、调整并发性以及编写每个主机工作量更少的剧本来获得最佳效果。

我不会用严格的主机数量来定义“大规模”。如果每个任务都通过慢速链接安装软件包,300台主机的清单可能会感觉很大。如果控制器配置得当且剧本紧凑,3,000台主机的清单也是可以管理的。将下面的数字视为起点,然后根据您自己的清单和模块进行测量。


在归咎于Ansible之前先调整并行性

并行性通常是首先要测试的杠杆,因为Ansible花费大量时间等待远程主机。目标不是“forks越高越好”。目标是足够的并发性,使控制器保持忙碌,而不会压垮SSH、特权升级、软件包仓库或目标本身。

使用forks控制并发性

forks参数定义了Ansible控制器可以生成的最大并行工作进程数。找到最佳数量需要在控制器资源(CPU和内存)与目标环境的连接限制之间取得平衡。

ansible.cfg或通过命令行(-f--forks)设置forks

[defaults]
forks = 100

从比您认为需要的更低的数值开始。使用25、50、100和200个forks对同一主机组运行同一剧本,同时监控CPU、内存、SSH失败和运行时间。如果CPU大部分时间空闲且主机在等待,则增加forks。如果控制器开始交换内存、Python进程堆积或目标拒绝连接,则减少forks。

选择正确的策略插件

Ansible的默认执行策略是linear,这意味着任务必须在所有目标主机上完成,然后才能进入剧本中的下一个任务。对于数千个节点,一个慢速主机可能会成为整个运行的瓶颈。

对于某些大型部署,请使用free策略。

Free策略(strategy = free): free允许主机在完成任务后独立地通过剧本,而无需等待较慢的主机。当任务独立时,它可以提高吞吐量。不要盲目地将其用于滚动部署、共享迁移或跨集群任务顺序重要的剧本。

# 示例剧本定义
---
- hosts: all
  strategy: free
  tasks:
    - name: 确保服务正在运行
      ansible.builtin.service:
        name: httpd
        state: started

在重复使用时缓存事实

事实收集很有用,但很容易重复付出代价。如果您的剧本在多次运行中使用事实,请缓存它们。如果某个剧本根本不需要主机事实,请为该剧本禁用收集。

使用外部缓存(Redis或Memcached)

对于单个控制器,JSON文件缓存可能就足够了。对于多个控制器或自动化工作节点,请使用外部缓存(如Redis或Memcached),以便每个工作节点都能看到相同的事实缓存。

ansible.cfg中的可操作配置:

[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 7200 ; 缓存事实2小时(以秒为单位)
fact_caching_prefix = ansible_facts

; 如果使用Redis
fact_caching_connection = localhost:6379:0

当缓存事实是您工作流程的一部分时,设置gathering = smart。如果您只需要一小部分主机数据,请使用gather_subset而不是收集所有内容。

3. 优化连接和传输

在处理数千个并发SSH会话时,减少与建立连接相关的开销至关重要。

SSH Pipelining

Pipelining减少了Ansible在许多模块执行中使用的SSH往返次数。它通常值得启用,但请使用您的特权升级规则进行测试。

SSH连接复用(ControlPersist)

对于类Unix目标,ControlMasterControlPersist设置可防止Ansible为每个任务启动全新的SSH会话。它在指定时间内保持一个控制套接字打开,允许后续任务使用现有连接。

ansible.cfg中的可操作配置:

[ssh_connection]
pipelining = True

; 使用激进的连接复用(例如,30分钟)
ssh_args = -C -o ControlMaster=auto -o ControlPersist=30m -o ServerAliveInterval=15

Pipelining可能与需要TTY的sudo配置冲突。如果您在sudoers中仍然有Defaults requiretty,请为自动化用户删除它,或者为这些主机保持pipelining禁用。

Windows优化(WinRM)

当针对Windows节点时,请单独调整WinRM。Kerberos通常是比Basic身份验证更好的生产选择,如果许多作业同时连接,可能需要检查WinRM服务限制。

4. 用于规模的清单管理

当主机频繁创建和销毁时,静态清单文件会变得很痛苦。动态清单并非每个大型环境都必须,但对于云集群、自动扩展组和基于CMDB的基础设施来说,它是正确的默认选择。

动态清单源

利用云提供商(AWS EC2、Azure、Google Cloud)或CMDB系统的清单插件。动态清单确保Ansible仅针对具有最新数据的活动主机。

# 示例:针对动态过滤的AWS清单运行
ansible-playbook -i aws_ec2.yml site.yml --limit 'tag_Environment_production'

智能定位和过滤

除非绝对必要,否则避免对整个清单(hosts: all)运行剧本。使用细粒度组、限制(--limit)和标签(--tags)来确保最小化执行目标集。

5. 架构考虑和控制器大小调整

对于大规模部署,Ansible运行的环境必须适当配置。

控制器大小调整

Ansible高度依赖控制器的资源,主要是CPU和RAM,因为需要派生进程进行并行执行。

  • CPU: 更多的forks通常意味着控制器上更多的Python工作。在真实的剧本运行期间监控平均负载和每核饱和度。
  • RAM: 每个fork都消耗内存。大型模板、大变量和冗长的回调插件会迅速增加内存使用量。
  • 存储I/O: 当控制器写入许多临时文件、日志、工件或基于文件的事实缓存条目时,快速的本地存储会有所帮助。

利用自动化平台

对于需要调度、RBAC、审计跟踪和多个执行工作节点的团队,请使用Ansible Automation Platform或AWX,而不是在一个控制节点上使用单个长时间运行的shell会话。

AAP提供:

  • 作业调度和历史记录: 集中式日志记录和审计。
  • 执行环境: 一致、可重复的运行时环境。
  • 集群和扩展: 跨多个工作节点分发执行,以处理大规模并发需求,而不会使单个控制器过载。
  • 凭据管理: 大规模安全处理机密。

6. 高效的剧本设计

即使优化了基础设施,编写不良的剧本也可能抵消性能提升。

最小化事实收集

如果您使用缓存事实(第2节),请主动禁用冗余的事实收集(如果可能):

- hosts: web_servers
  gather_facts: no # 为此剧本禁用事实收集
  tasks:
    # ... 仅运行不依赖于收集的系统事实的任务

谨慎使用run_oncedelegate_to

必须顺序或集中运行的任务(例如,启动滚动部署、更新负载均衡器)应通过run_once: truedelegate_to: management_node处理。这避免了在只有一个主机应执行操作时进行浪费的并行处理。

首选批处理操作

只要可能,使用原生处理批处理操作的模块(例如,接受包列表的包管理器,如aptyum),而不是使用loopwith_items在单独的package任务中迭代大型列表。

# 更好:一个包含列表的包任务
- name: 安装必要的依赖项
  ansible.builtin.package:
    name:
      - nginx
      - python3-pip
      - firewall
    state: present

7. 测量剧本,而不仅仅是主机数量

当Ansible运行缓慢时,在更改更多旋钮之前添加计时。内置的profile_tasks回调是一个很好的第一步:

[defaults]
callbacks_enabled = profile_tasks, timer

针对一个代表性的主机组运行一次剧本,并查看最慢的任务。您可能会发现大部分时间花在一个包安装、一个模板渲染步骤或一个等待外部服务的命令上。在这种情况下,增加forks只会给同一个瓶颈带来更多压力。

对于可重复的测试,保持清单切片稳定:

ansible-playbook -i inventory site.yml --limit 'web:&production' -f 50
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 100
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 200

记录总运行时间、失败主机、控制器CPU、控制器内存以及任何目标端的SSH或sudo错误。同时在测试期间监控包仓库或工件服务器。当真正的问题是每个主机同时从一个过载的内部镜像下载同一个包时,一个剧本可能看起来像是Ansible的问题。

8. 在增加并发性之前减少工作量

大型Ansible运行通常通过减少工作量而不是加快速度来获得更多改进。一些反复出现的例子:

  • 一个模板任务在每次运行时都渲染一个大型配置文件,即使只更改了一个小的包含项。
  • 一个shell任务在每个主机上运行一个发现命令,即使该值已在清单中。
  • 一个角色在循环中逐个安装包。
  • 一个处理程序在几次不相关的模板更改后重启服务,而一次重新加载就足够了。

尽可能使用模块幂等性而不是shell。一个总是报告更改的shell命令可能会触发数百个主机的处理程序,并将无害的检查变成滚动重启。如果您必须使用commandshell,请仔细设置changed_whencreatesremoves

- name: 初始化应用程序目录一次
  ansible.builtin.command: /usr/local/bin/app-init /srv/app
  args:
    creates: /srv/app/.initialized

这个小防护措施可以防止重复工作并避免错误的更改报告。

9. 使用批次进行风险控制

在大规模环境中,性能不是唯一的问题。有时最快的剧本在操作上是危险的。对于服务集群,请使用serial来控制爆炸半径:

- hosts: app_servers
  serial: 10%
  max_fail_percentage: 5
  tasks:
    - name: 部署应用程序包
      ansible.builtin.package:
        name: myapp
        state: latest

serial会使运行时间比同时向所有主机发起攻击更长,但它给负载均衡器、监控和人员留出了反应时间。它还可以保护共享依赖项。包镜像、数据库迁移端点或机密管理器可能无法承受数千个并发请求。

大型Ansible部署会给容易被遗忘的系统带来压力:DNS解析器、包仓库、机密存储、日志管道和监控端点。如果剧本仅在更高的fork计数下变慢,请在归咎于Ansible之前检查这些共享服务。

同时控制回调输出。非常详细的日志在调试时很有用,但它们可能会减慢大型运行并掩盖真正的故障。对窄主机切片使用高详细级别,然后返回到全集群执行的正常输出。

10. 按故障域拆分剧本

一个被忽视的扩展技巧是停止将整个资产视为一个部署单元。如果数据库主机、Web主机、队列和缓存节点都位于同一个大型剧本中,那么一个缓慢或损坏的组可能会延迟不相关的工作。按故障域和依赖顺序分离剧本。

例如,广泛运行基础操作系统配置,但按服务层部署应用程序代码。在它们自己的剧本中更新缓存节点。分批排空并重启Web节点。使用额外的检查和较小的并发性应用数据库配置。这使得重试更安全,因为您可以重新运行失败的部分,而无需在每个主机上重复工作。

这也使所有权更清晰。负责服务的团队可以调整其批次大小、健康检查和回滚行为,而无需更改全局自动化默认值。当剧本结构与基础设施在实际事件和维护窗口期间实际失败的方式相匹配时,大规模Ansible保持可维护性。

最高影响力的Ansible性能工作通常很简单:复用SSH连接,避免不必要的事实收集,正确调整forks大小,并阻止剧本重复执行微小操作。之后,看看架构。如果一个控制器无法干净地跟上,则将工作拆分到执行节点,并使清单、凭据和日志记录可重复。