静态清单与动态清单:为规模化选择正确的Ansible策略

比较静态和动态Ansible清单,为云环境、本地环境和混合环境提供实用指导。

静态清单与动态清单:为规模化选择正确的Ansible策略

Ansible清单是Ansible允许接触的机器列表,以及说明如何访问这些机器的组和变量。如果该列表有误,即使剧本完美,执行仍可能带来风险。你可能会遗漏新主机,持续管理已删除的主机,或者因为组名偏差而对Web节点执行数据库任务。

静态与动态Ansible清单的选择并非成熟度标志。对于许多小型、稳定的环境,静态文件仍然是最清晰的答案。当基础设施由云API、自动伸缩组、Kubernetes、Terraform或其他事实来源创建和销毁时,动态清单通常更优。有用的问题不是“哪个更高级?”,而是“这些主机的真实信息已经存在于哪里?”

理解Ansible清单

Ansible清单的核心是Ansible将管理的主机列表。这些主机可以是服务器、网络设备或任何其他受管节点。清单可以通过多种方式结构化,包括按组划分,这允许你将配置应用于基础设施的子集。

清单文件(或源)可以是INI或YAML格式。例如,一个简单的INI格式清单可能如下所示:

[webservers]
web1.example.com
web2.example.com

[databases]
db1.example.com

此结构定义了两个组:webserversdatabases,每个组分配了特定的主机。Ansible可以在其剧本中针对这些组,例如,将Web服务器配置部署到webservers组中的所有主机。

静态清单:简单性与控制力

静态清单是指主机列表被显式定义并手动维护的清单源。这通常通过纯文本文件(INI或YAML)完成,每当基础设施发生变化时手动更新。

静态清单的特点

  • 手动定义:主机及其组成员关系直接列在文件中。
  • 固定结构:清单保持不变,直到手动编辑。
  • 易于上手:对于小型、稳定的环境易于设置。
  • 可预测性:你始终确切知道Ansible将针对哪些主机。

静态清单的优点

  • 简单性:对于小型、可预测的环境,静态清单管理直观。
  • 控制力:提供对包含哪些主机以及如何分组的完全控制。
  • 易于理解:结构易于阅读和理解。

静态清单的缺点

  • 可扩展性问题:手动管理大量主机变得耗时且容易出错。
  • 维护开销:基础设施的每次添加、删除或更改都需要手动更新清单文件。
  • 不适用于动态环境:在实例频繁启动和终止的云环境中,静态清单很快就会过时。

何时使用静态清单

静态清单是以下情况的绝佳选择:

  • 小型、变化不频繁的本地基础设施。
  • 具有固定机器集的开发或测试环境。
  • 对受管节点需要精确控制且变化很少的情况。

动态清单:自动化与弹性

另一方面,动态清单允许Ansible自动发现和管理主机。Ansible不是手动列出主机,而是查询外部数据源(如云提供商API、CMDB或脚本)以检索基础设施的当前状态。

动态清单的工作原理

动态清单源通常作为脚本或插件实现,遵循Ansible的动态清单API。当Ansible需要清单数据时,它会执行此脚本或插件,然后查询相关系统并以JSON格式返回主机信息。此JSON输出包括主机、其组以及任何关联变量。

Ansible为许多云提供商和服务提供内置支持,使集成动态清单变得容易。例如,要将AWS EC2用作动态清单源,你可以安装aws_ec2清单插件。

动态清单的特点

  • 自动发现:从外部源自动发现主机。
  • 实时更新:清单反映基础设施的当前状态。
  • 与云提供商集成:无缝适用于AWS、Azure、GCP等云平台。
  • 标签和元数据:利用外部源的标签和元数据进行分组和变量分配。

动态清单的优点

  • 可扩展性:轻松处理拥有数百或数千台主机的环境。
  • 自动化:消除手动清单维护,减少错误并节省时间。
  • 弹性:自动考虑新配置或终止的资源。
  • 灵活性:适应云计算的动态特性。

动态清单的缺点

  • 复杂性:初始设置和配置可能比静态清单更复杂。
  • 依赖外部系统:依赖于外部数据源的可用性和准确性。
  • 过度管理的可能性:如果没有仔细配置,Ansible可能会尝试管理不打算管理的资源。

流行的动态清单源

  • 云提供商插件aws_ec2azure_rmgcp_compute
  • 容器编排器kubernetes.core.k8s
  • CMDB和资产系统:ServiceNow、NetBox或内部服务目录。
  • 自定义脚本:任何输出有效JSON的脚本。

示例:使用AWS EC2动态清单

要将AWS EC2实例用作动态清单,你通常需要配置aws_ec2插件。这可能涉及创建一个Ansible清单配置文件(例如aws_ec2.yml),指定AWS区域、凭证和过滤器。

# aws_ec2.yml
plugin: aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: tags.Project
    prefix: project
compose:
  ansible_host: private_ip_address

使用此配置,Ansible将查询us-east-1中正在运行的EC2实例。它将根据EnvironmentProject标签自动创建组,分别添加env_project_前缀。它还将每个实例的ansible_host设置为其私有IP地址。

然后,你可以使用此动态清单源运行Ansible命令或剧本:

ansible-inventory --graph -i aws_ec2.yml
ansible-playbook -i aws_ec2.yml site.yml

何时过渡到动态清单

从静态清单转向动态清单的决定通常由基础设施的特征和运营成熟度驱动。

应考虑动态清单的迹象

  • 基础设施增长:当手动清单编辑导致遗漏主机、主机过时或审查缓慢时。
  • 采用云服务:如果你大量使用AWS、Azure或GCP等云平台,其中资源是短暂的且自动伸缩的。
  • 频繁变更:当你的基础设施频繁更新、扩展或缩减,或经历频繁部署时。
  • 自动化目标:为了实现更高水平的自动化并减少基础设施管理中的手动干预。
  • 编排集成:如果你使用Kubernetes等容器编排器,动态清单对于管理Pod和服务至关重要。

过渡过程

  1. 评估你的基础设施:了解你的主机在哪里管理(云、本地、容器)以及它们是如何配置的。
  2. 识别数据源:确定持有基础设施权威列表的外部系统(例如,云提供商API、CMDB)。
  3. 选择合适的插件/脚本:为你的数据源选择或开发适当的动态清单插件或脚本。
  4. 配置分组和变量:定义如何对主机进行分组(例如,按标签、实例类型)以及如何分配变量。
  5. 彻底测试:在部署到生产环境之前,在暂存环境中针对动态清单运行Ansible命令。
  6. 更新剧本(如有必要):确保你的剧本与新的分组和变量结构兼容。

清单管理最佳实践

无论你选择静态还是动态清单,遵循最佳实践将确保Ansible操作高效可靠:

  • 保持组织性:使用有意义的组名和一致的主机命名约定。
  • 利用变量:使用Ansible变量(host_vars、group_vars)管理配置差异,避免在剧本中重复。
  • 使用别名和事实:对于静态清单,考虑使用别名。对于动态清单,尽可能利用云提供商标签和元数据进行动态变量分配。
  • 定期审查和审计:定期检查清单的准确性和完整性,特别是使用静态清单时。
  • 保护凭证:当使用需要API访问的动态清单插件时,确保凭证安全地管理(例如,使用Ansible Vault、IAM角色)。

实际应用场景

对于小型静态环境,一个简单的清单文件可能比巧妙的集成更好。想象一下,在一个小型办公室或小型生产部署中有三台Web服务器、一台数据库服务器和一台堡垒机:

[webservers]
web01 ansible_host=10.20.1.11
web02 ansible_host=10.20.1.12
web03 ansible_host=10.20.1.13

[databases]
db01 ansible_host=10.20.2.20

[all:vars]
ansible_user=deploy

每个人都可以在Git中审查该文件。一个将db01移入错误组的拉取请求很容易被发现。无需管理云凭证,无需调试插件缓存,也不会因API中断而意外。如果服务器列表每季度更改一次,静态清单并非弱点。

当文件不再反映现实时,麻烦就开始了。一个团队通过Terraform添加实例,另一个团队在事件期间终止节点,而Ansible清单稍后“当有人记得时”才更新。这种差距就是陈旧自动化的来源。你会看到诸如无法访问主机的错误,或者更糟的是,由于新主机从未被添加,你只修补了部分集群。

当外部系统已被视为权威时,动态清单效果最佳。在AWS中,这可能是EC2标签。在数据中心,可能是NetBox。在平台团队中,可能是由配置管道填充的服务目录。清单插件应该是该事实的读取者,而不是操作员发明新组逻辑的第二个地方。

标签质量比插件更重要。这个AWS示例按EnvironmentProject标签分组:

plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: tags.Project
    prefix: project
compose:
  ansible_host: private_ip_address

只有当每个实例都有这些标签,并且标签值一致时,这才是干净的。prodproductionProduction会创建不同的组,除非你进行规范化。在将重要的剧本迁移到动态清单之前,运行:

ansible-inventory -i aws_ec2.yml --graph
ansible-inventory -i aws_ec2.yml --list --yaml

查找空组、意外的组名、应使用私有IP时出现的公有IP,以及出现在太多地方的主机。图形输出通常比失败的剧本更快地发现错误。

混合方法很常见,也完全合理。你可以为网络设备、传统数据库服务器和堡垒机保留静态清单,同时为自动伸缩的应用节点使用动态清单。Ansible可以同时加载多个清单源:

ansible-playbook -i inventory/static.ini -i inventory/aws_ec2.yml site.yml

当你组合源时,要仔细命名组。如果静态文件和动态插件都创建了webservers,Ansible将合并组成员关系。这可能有用,但也可能隐藏错误。我更喜欢能揭示来源或意图的组名,例如aws_webdc_webprod_weblegacy_db,然后有意识地创建父组。

还要在迁移前决定如何处理变量。动态清单擅长发现主机和元数据;它并不总是存储应用程序配置的最佳位置。当设置属于Ansible时,将长期设置保留在group_vars/host_vars/中,并使用标签或元数据对已存在于Ansible外部的分组事实进行分组。像Environment=prod这样的云标签是一个很好的分组信号。数据库密码则不是。

缓存值得快速提一下。许多动态清单插件可以缓存结果,这样每个命令就不会都命中提供商API。缓存可以使运行更快并减少速率限制问题,但它引入了另一个问题:清单可以有多陈旧?对于部署自动化,短缓存可能没问题。对于规模事件后的应急响应,你可能希望显式刷新清单。

过渡不需要在一次有风险的切换中完成。首先,生成动态清单并与静态文件进行比较。然后通过动态源运行只读命令:

ansible -i aws_ec2.yml env_prod -m ping
ansible -i aws_ec2.yml env_prod -m setup -a "filter=ansible_hostname"

之后,移动一个低风险的剧本。保留旧清单,直到团队信任分组和变量行为。最好的清单策略是操作员在中断期间能够解释的策略,而不是纸面上自动化程度最高的策略。