如何使用Ansible管理多阶段环境
介绍
Ansible 是一个强大的配置管理系统,用于在各种环境中设置和管理基础架构和应用程序。 虽然 Ansible 提供了易于阅读的语法、灵活的工作流程和强大的工具,但当它们因部署环境和功能而异时,管理大量主机可能具有挑战性。
在本指南中,我们将讨论使用 Ansible 处理多阶段部署环境的一些策略。 通常,不同阶段的要求会导致组件的数量和配置不同。 例如,开发服务器的内存需求可能与暂存和生产服务器的内存需求不同,重要的是明确控制代表这些需求的变量的优先级。 在本文中,我们将讨论一些可以抽象出这些差异的方法,以及 Ansible 提供的一些结构来鼓励配置重用。
使用 Ansible 管理多阶段环境的不完整策略
虽然您可以通过多种方式在 Ansible 中管理环境,但 Ansible 本身并不提供固执己见的解决方案。 相反,它提供了许多可用于管理环境并允许用户选择的结构。
我们将在本指南中演示的方法依赖于 Ansible 组变量 和 多个清单 。 但是,还有其他一些策略值得考虑。 我们将在下面探讨其中一些想法,以及为什么它们在复杂环境中实施时可能会出现问题。
如果您想开始使用 Ansible 推荐的策略,请跳到 使用 Ansible 组和多个清单 部分。
仅依赖组变量
乍一看,组变量似乎提供了 Ansible 所需的环境之间的所有分隔。 您可以将某些服务器指定为属于您的开发环境,而其他服务器可以分配到暂存和生产区域。 Ansible 可以轻松创建组并为其分配变量。
然而,组交叉给这个系统带来了严重的问题。 组通常用于对多个维度进行分类。 例如:
- 部署环境(本地、开发、阶段、产品等)
- 主机功能(Web 服务器、数据库服务器等)
- 数据中心区域(NYC、SFO 等)
在这些情况下,主机通常属于每个类别的一组。 例如,主机可能是 NYC(数据中心区域)舞台(部署环境)上的 Web 服务器(功能性)。
如果同一变量由多个组为主机设置,Ansible 无法明确指定优先级。 您可能更喜欢与部署环境关联的变量来覆盖其他值,但 Ansible 没有提供定义它的方法。
相反,Ansible 使用最后加载的值 。 由于 Ansible 按字母顺序评估组,因此与字典排序中最后一个的组名关联的变量将获胜。 这是可预测的行为,但从管理的角度来看,明确管理组名字母顺序并不理想。
使用组子来建立层次结构
Ansible 允许您使用清单中的 [groupname:children]
语法将组分配给其他组。 这使您能够命名其他组的某些组成员。 子组可以覆盖父组设置的变量。
通常,这用于自然分类。 例如,我们可以有一个名为 environments
的组,其中包括组 dev
、stage
、prod
。 这意味着我们可以在 environment
组中设置变量并在 dev
组中覆盖它们。 您可以类似地拥有一个名为 functions
的父组,其中包含组 web
、database
和 loadbalancer
。
这种用法并不能解决组交集的问题,因为子组只会覆盖其父组。 子组可以覆盖父组中的变量,但上述组织尚未建立组类别之间的任何关系,如environments
和functions
。 两个类别之间的变量优先级仍未定义。
通过设置非自然组成员身份, 是 可能利用此系统。 例如,如果要建立以下优先级,从最高优先级到最低优先级:
- 开发环境
- 地区
- 功能
您可以分配如下所示的组成员身份:
示例库存
. . . [function:children] web database loadbalancer region [region:children] nyc sfo environments [environments:children] dev stage prod
我们在这里建立了一个层次结构,允许区域变量覆盖功能变量,因为 region
组是 function
组的子组。 同样,在 environments
组中设置的变量可以覆盖任何其他变量。 这意味着如果我们在 dev
、nyc
和 web
组中将相同的变量设置为不同的值,则属于每个组的主机将使用来自dev
。
这达到了预期的结果,也是可预测的。 然而,它是不直观的,它混淆了真正的孩子和建立层次结构所需的孩子之间的区别。 Ansible 的设计使其配置清晰易懂,即使对于新用户也是如此。 这种类型的解决方法会损害该目标。
使用允许显式加载顺序的 Ansible 构造
Ansible 中有一些允许显式可变负载排序的结构,即 vars_files
和 include_vars
。 这些可以在 Ansible 播放 中使用,以按照文件中定义的顺序显式加载其他变量。 vars_files
指令在播放上下文中有效,而 include_vars
模块可用于任务。
一般的想法是在 group_vars
中只设置基本的识别变量,然后利用这些变量加载正确的变量文件和其余所需的变量。
例如,一些 group_vars
文件可能如下所示:
group_vars/dev
--- env: dev
group_vars/阶段
--- env: stage
group_vars/网络
--- function: web
group_vars/数据库
--- function: database
然后,我们将有一个单独的 vars 文件来定义每个组的重要变量。 为了清楚起见,这些通常保存在单独的 vars
目录中。 与 group_vars
文件不同,在处理 include_vars
时,文件必须包含 .yml
文件扩展名。
假设我们需要在每个 vars
文件中将 server_memory_size
变量设置为不同的值。 您的开发服务器可能会小于生产服务器。 此外,您的 Web 服务器和数据库服务器可能有不同的内存要求:
变量/dev.yml
--- server_memory_size: 512mb
变量/prod.yml
--- server_memory_size: 4gb
变量/web.yml
--- server_memory_size: 1gb
变量/数据库.yml
--- server_memory_size: 2gb
然后,我们可以创建一个剧本,根据 group_vars
文件中分配给主机的值,显式加载正确的 vars
文件。 加载文件的顺序将决定优先级,最后一个值获胜。
使用 vars_files
,示例播放如下所示:
example_play.yml
--- - name: variable precedence test hosts: all vars_files: - "vars/{{ env }}.yml" - "vars/{{ function }}.yml" tasks: - debug: var=server_memory_size
由于最后加载功能组,因此 server_memory_size
值将从 var/web.yml
和 var/database.yml
文件中获取:
ansible-playbook -i inventory example_play.yml
Output. . . TASK [debug] ******************************************************************* ok: [host1] => { "server_memory_size": "1gb" # value from vars/web.yml } ok: [host2] => { "server_memory_size": "1gb" # value from vars/web.yml } ok: [host3] => { "server_memory_size": "2gb" # value from vars/database.yml } ok: [host4] => { "server_memory_size": "2gb" # value from vars/database.yml } . . .
如果我们切换要加载的文件的顺序,我们可以让部署环境变量的优先级更高:
example_play.yml
--- - name: variable precedence test hosts: all vars_files: - "vars/{{ function }}.yml" - "vars/{{ env }}.yml" tasks: - debug: var=server_memory_size
再次运行 playbook 会显示从部署环境文件中应用的值:
ansible-playbook -i inventory example_play.yml
Output. . . TASK [debug] ******************************************************************* ok: [host1] => { "server_memory_size": "512mb" # value from vars/dev.yml } ok: [host2] => { "server_memory_size": "4gb" # value from vars/prod.yml } ok: [host3] => { "server_memory_size": "512mb" # value from vars/dev.yml } ok: [host4] => { "server_memory_size": "4gb" # value from vars/prod.yml } . . .
使用 include_vars
作为任务运行的等效剧本如下所示:
--- - name: variable precedence test hosts: localhost tasks: - include_vars: file: "{{ item }}" with_items: - "vars/{{ function }}.yml" - "vars/{{ env }}.yml" - debug: var=server_memory_size
这是 Ansible 允许显式排序的一个领域,这非常有用。 然而,与前面的例子一样,也有一些明显的缺点。
首先,使用 vars_files
和 include_vars
需要您将与组紧密相关的变量放在不同的位置。 group_vars
位置成为位于 vars
目录中的实际变量的存根。 这再次增加了复杂性并降低了清晰度。 用户必须将正确的变量文件与主机匹配,这是 Ansible 在使用 group_vars
时自动执行的操作。
更重要的是,依赖这些技术使它们成为强制性的。 每个剧本都需要一个部分,以正确的顺序显式加载正确的变量文件。 没有这个的剧本将无法使用相关的变量。 此外,对于任何依赖变量的任务来说,为临时任务运行 ansible
命令几乎是完全不可能的。
Ansible 推荐策略:使用组和多个库存
到目前为止,我们已经研究了一些管理多阶段环境的策略,并讨论了它们可能不是完整解决方案的原因。 然而,Ansible 项目确实提供了一些关于如何最好地跨环境抽象基础架构的建议。
推荐的方法是通过完全分离每个操作环境来处理多级环境。 不是在单个清单文件中维护所有主机,而是为每个单独的环境维护一个清单。 还维护了单独的 group_vars
目录。
基本目录结构如下所示:
. ├── ansible.cfg ├── environments/ # Parent directory for our environment-specific directories │ │ │ ├── dev/ # Contains all files specific to the dev environment │ │ ├── group_vars/ # dev specific group_vars files │ │ │ ├── all │ │ │ ├── db │ │ │ └── web │ │ └── hosts # Contains only the hosts in the dev environment │ │ │ ├── prod/ # Contains all files specific to the prod environment │ │ ├── group_vars/ # prod specific group_vars files │ │ │ ├── all │ │ │ ├── db │ │ │ └── web │ │ └── hosts # Contains only the hosts in the prod environment │ │ │ └── stage/ # Contains all files specific to the stage environment │ ├── group_vars/ # stage specific group_vars files │ │ ├── all │ │ ├── db │ │ └── web │ └── hosts # Contains only the hosts in the stage environment │ ├── playbook.yml │ └── . . .
正如您所看到的,每个环境都是不同的和划分的。 环境目录包含一个清单文件(任意命名为 hosts
)和一个单独的 group_vars
目录。
目录树中有一些明显的重复。 每个单独的环境都有 web
和 db
文件。 在这种情况下,复制是可取的。 通过首先修改一个环境中的变量并在测试后将它们移动到下一个环境,可以跨环境推出变量更改,就像您对代码或配置更改所做的那样。 group_vars
变量跟踪每个环境的当前默认值。
一个限制是无法跨环境按功能选择所有主机。 幸运的是,这与上面的变量重复问题属于同一类别。 虽然为一项任务选择所有 Web 服务器有时很有用,但您几乎总是希望一次在您的环境中推出一个更改。 这有助于防止错误影响您的生产环境。
设置跨环境变量
在推荐的设置中不可能的一件事是跨环境共享变量。 我们可以通过多种方式实现跨环境变量共享。 最简单的方法之一是利用 Ansible 使用目录代替文件的能力。 我们可以将每个 group_vars
目录中的 all
文件替换为 all
目录。
在目录中,我们可以再次在一个文件中设置所有特定于环境的变量。 然后,我们可以创建指向包含跨环境变量的文件位置的符号链接。 这两个都将应用于环境中的所有主机。
首先在层次结构中的某处创建一个跨环境变量文件。 在本例中,我们将其放置在 environments
目录中。 将所有跨环境变量放在该文件中:
cd environments touch 000_cross_env_vars
接下来,移动到 group_vars
目录之一,重命名 all
文件,并创建 all
目录。 将重命名的文件移动到新目录中:
cd dev/group_vars mv all env_specific mkdir all mv env_specific all/
接下来,您可以创建指向跨环境变量文件的符号链接:
cd all/ ln -s ../../../000_cross_env_vars .
当您为每个环境完成上述步骤后,您的目录结构将如下所示:
. ├── ansible.cfg ├── environments/ │ │ │ ├── 000_cross_env_vars │ │ │ ├── dev/ │ │ ├── group_vars/ │ │ │ ├── all/ │ │ │ ├── 000_cross_env_vars -> ../../../000_cross_env_vars │ │ │ │ └── env_specific │ │ │ ├── db │ │ │ └── web │ │ └── hosts │ │ │ ├── prod/ │ │ ├── group_vars/ │ │ │ ├── all/ │ │ │ │ ├── 000_cross_env_vars -> ../../../000_cross_env_vars │ │ │ │ └── env_specific │ │ │ ├── db │ │ │ └── web │ │ └── hosts │ │ │ └── stage/ │ ├── group_vars/ │ │ ├── all/ │ │ │ ├── 000_cross_env_vars -> ../../../000_cross_env_vars │ │ │ └── env_specific │ │ ├── db │ │ └── web │ └── hosts │ ├── playbook.yml │ └── . . .
000_cross_env_vars
文件中设置的变量将可用于每个低优先级的环境。
设置默认环境清单
可以在 ansible.cfg
文件中设置默认库存文件。 这是一个好主意,有几个原因。
首先,它允许您将明确的库存标志留给 ansible
和 ansible-playbook
。 所以不要输入:
ansible -i environments/dev -m ping
您可以通过键入以下内容访问默认清单:
ansible -m ping
其次,设置默认清单有助于防止不必要的更改意外影响暂存或生产环境。 通过默认为您的开发环境,最不重要的基础设施会受到更改的影响。 促进对新环境的更改是需要 -i
标志的显式操作。
要设置默认库存,请打开 ansible.cfg
文件。 这可能位于项目的根目录或 /etc/ansible/ansible.cfg
中,具体取决于您的配置。
注意: 下面的例子演示了在项目目录中编辑一个ansible.cfg
文件。 如果您使用 /etc/ansibile/ansible.cfg
文件进行更改,请修改下面的编辑路径。 使用 /etc/ansible/ansible.cfg
时,如果您的库存维护在 /etc/ansible
目录之外,请务必在设置 inventory
值时使用绝对路径而不是相对路径。
nano ansible.cfg
如上所述,建议将您的开发环境设置为默认清单。 注意我们如何选择整个环境目录而不是它包含的主机文件:
[defaults] inventory = ./environments/dev
您现在应该可以在没有 -i
选项的情况下使用默认库存。 非默认清单仍然需要使用 -i
,这有助于保护它们免受意外更改。
结论
在本文中,我们探讨了 Ansible 为跨多个环境管理主机提供的灵活性。 当主机是多个组的成员时,这允许用户采用许多不同的策略来处理可变优先级,但是模棱两可和缺乏官方方向可能具有挑战性。 与任何技术一样,最适合您的组织将取决于您的用例和需求的复杂性。 找到适合您需求的策略的最佳方法是进行实验。 在下面的评论中分享您的用例和方法。