如何使用 Ansible 管理多阶段环境
介绍
Ansible 是一个功能强大的配置管理系统,用于设置和管理各种环境中的基础设施和应用程序。虽然 Ansible 提供了易于阅读的语法、灵活的工作流程和强大的工具,但当部署环境和功能各不相同时,管理大量主机可能具有挑战性。
在本指南中,我们将讨论使用 Ansible 处理多阶段部署环境的一些策略。通常,不同阶段的要求会导致组件的数量和配置不同。例如,开发服务器的内存要求可能与暂存和生产服务器的内存要求不同,因此明确控制如何对代表这些要求的变量进行优先级排序非常重要。在本文中,我们将讨论一些可以抽象这些差异的方法以及 Ansible 提供的一些结构以鼓励配置重用。
使用 Ansible 管理多阶段环境的不完整策略
虽然 Ansible 中有许多管理环境的方法,但 Ansible 本身并不提供固定的解决方案。相反,它提供了许多可用于管理环境的结构,并允许用户选择。
本指南中演示的方法依赖于 Ansible组变量和多个清单。但是,还有其他几种策略值得考虑。我们将在下面探讨其中一些想法,以及为什么它们在复杂环境中实施时可能会出现问题。
如果您想开始使用 Ansible 推荐的策略,请跳到有关使用 Ansible 组和多个库存的部分。
仅依赖组变量
乍一看,组变量似乎提供了 Ansible 所需的所有环境分离。您可以将某些服务器指定为属于您的开发环境,而其他服务器则可以分配给暂存区和生产区。Ansible 可以轻松创建组并为其分配变量。
然而,群组交集给该系统带来了严重问题。群组通常用于对多个维度进行分类。例如:
- 部署环境(本地、开发、阶段、生产等)
- 主机功能(Web 服务器、数据库服务器等)
- 数据中心区域(纽约、旧金山国际机场等)
在这些情况下,主机通常每个类别属于一个组。例如,主机可能是位于纽约(数据中心区域)的阶段(部署环境)上的 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
可能如下所示:
---
env: dev
---
env: stage
---
function: web
---
function: database
然后,我们将有一个单独的 vars 文件,用于定义每个组的重要变量。vars
为了清晰起见,这些变量通常保存在单独的目录中。与group_vars
文件不同,在处理时include_vars
,文件必须包含.yml
文件扩展名。
假设我们需要server_memory_size
在每个文件中将变量设置为不同的值vars
。您的开发服务器可能比生产服务器小。此外,您的 Web 服务器和数据库服务器可能具有不同的内存要求:
---
server_memory_size: 512mb
---
server_memory_size: 4gb
---
server_memory_size: 1gb
---
server_memory_size: 2gb
vars
然后,我们可以创建一个剧本,根据从文件中分配给主机的值明确加载正确的文件group_vars
。加载文件的顺序将决定优先级,最后一个值获胜。
使用vars_files
,示例游戏将如下所示:
---
- 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
}
. . .
如果我们切换要加载的文件的顺序,我们可以使部署环境变量具有更高的优先级:
---
- name: variable precedence test
hosts: all
vars_files:
- "vars/{{ function }}.yml"
- "vars/{{ env }}.yml"
tasks:
- debug: var=server_memory_size
再次运行剧本显示从部署环境文件中应用的值:
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 使用目录代替文件的功能。我们可以用目录替换all
每个group_vars
目录中的文件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
如上所述,建议将开发环境设置为默认清单。请注意我们如何选择整个环境目录而不是其中包含的 hosts 文件:
[defaults]
inventory = ./environments/dev
您现在应该能够使用默认库存,而无需使用 选项-i
。非默认库存仍需要使用-i
,这有助于防止意外更改。
结论
在本文中,我们探讨了 Ansible 为跨多个环境管理主机提供的灵活性。当主机是多个组的成员时,这允许用户采用许多不同的策略来处理变量优先级,但模糊性和缺乏官方指导可能会带来挑战。与任何技术一样,最适合您组织的策略将取决于您的用例和需求的复杂性。找到适合您需求的策略的最佳方法是进行实验。在下面的评论中分享您的用例和方法。