ansbile实战应用系列教程5:管理playbook
编写并运行playbook
Ansible Playbooks and Ad Hoc Commands
Ad hoc命令可以作为一次性命令对一组目标主机运行单个、简单的任务。然而,Ansible的真正力量在于学习如何使用playbook对一组目标主机以一种容易重复的方式运行多个复杂的任务。
play是一组有序的task,应该针对从inventory中选择的主机运行。playbook是一个文本文件,其中包含一个或多个要按顺序运行的plays列表。
play允许您将一组冗长、复杂的手动管理任务更改为易于重复的例行程序,并具有可预测和成功的结果。在playbook中,你可以将playbook中的task序列保存为人类可读且可立即运行的形式。由于编写task的方式不同,task本身记录了部署应用程序或基础设施所需的步骤。
playbook剧本中可以包含多个play,每个play中包含多个任务task,每个任务对应一个模块module。
Format of an Ansible Playbook
为了帮助您理解ansible playbook的格式,我们将回顾您在前一章中看到的一个临时命令:
[student@workstation ~]$ ansible -m user -a "name=newbie uid=4000 state=present" servera.lab.example.com
编写为playbook(可多次执行),通常保存在以.yml或者.yaml结尾的文件名中。
---
- name: Configure important user consistently
hosts: servera.lab.example.com
tasks:
- name: newbie exists with UID 4000
user:
name: newbie
uid: 4000
state: present
[student@workstation ~]$ ansible -a 'id newbie' node1.example.com
playbook是用YAML格式编写的文本文件,通常使用扩展名yml保存。playbook主要使用带有空格字符的缩进来表示其数据的结构。YAML对缩进使用多少空格没有严格的要求,但是有两个基本规则。
层次结构中相同级别的数据元素(例如相同列表中的项)必须具有相同的缩进。
作为另一项的子项的项必须比其父项缩进更多。还可以添加空行以增强可读性
缩进只能使用空格字符;不允许使用制表符。
如果您使用Vim文本编辑器,您可以应用一些设置,这可能使编辑您的剧本更容易。例如,在$HOME/.vimrc中添加以下行,当vim检测到您正在编辑一个YAML文件时,它将在按Tab键时执行两个空格的缩进,将自动缩进后续行,并将制表符展开为空格。
/etc/vimrc【全局】或者~/.vimrc【针对某个用户】
autocmd FileType yaml setlocal ai ts=2 sw=2 et
ai = auto indent,自动退格对齐
set tabstop=2(ts=tabstop,即一个tab的宽度被设为2个空格辣么宽)
set shiftwidth=2(sw=shiftwidth,即退格对齐以2个空格为准)
set expandtab(et=expandtab,将tab变成空格)
playbook以一行由三个破折号(—)组成的作为文档标记的开始。它也可能以三个点(…)作为文档结束标记,尽管在实践中这很少用于playbook。
在这些标记之间,playbook被定义为play列表。YAML列表中的项目以一个破折号后跟一个空格开始。例如,YAML列表可能如下所示:
- apple
- orange
play本身是key:value对的集合(一个关联数组或散列/字典)。键在同一play应该有相同的缩进。下面的示例描述了具有三个键的YAML散列/字典。前两个键有简单的值。第三个值是一个包含三个项目的列表。
name: just an example
hosts: webservers
tasks:
- first
- second
- third
最初的示例有三个键:name、hosts和tasks。这些键都有相同的缩进,因为它们属于play。
示例play的第一行以破折号和空格开始(表示play是列表的第一项),然后是第一个键,即name属性。将一个任意字符串作为标签与play关联。name键是可选的,但推荐使用,因为它有助于记录play。当playbook包含多个play时,这一点特别有用。
- name: Configure importantuser consistently
playbook中的第二个键是一个hosts属性,它指定playbook的任务应该在哪些主机上运行。与ansible命令的参数一样,hosts属性接受一个主机模式作为值,例如inventory中托管主机或组的名称。
hosts: node1.example.com
最后,play中的最后一个键是tasks属性,它的值指定为该play运行的任务列表。这个示例有一个任务,它使用特定的参数运行user模块(以确保user newbie存在并拥有UID4000)。
tasks:
- name: newbie exists with UID 4000
user:
name: newbie
uid: 4000
state: present
tasks属性是play的一部分,它实际上按顺序列出了要在托管主机上运行的任务。列表中的每个任务本身就是键-值对的集合。
在我们的例子中,play中的唯一任务有两个键:
•name是记录任务目的的可选标签。为所有任务命名是一个好主意,以帮助记录自动化过程中每一步的目的。
•user是为此任务运行的模块。它的参数作为键值对的集合传递,键值对是模块的子元素(name、uid和state)。
下面是带有多个任务的任务属性的另一个例子,使用service模块确保在启动时启用多个网络服务:
tasks:
- name : web server is enabled
service :
name : httpd
enabled: true
- name: NTP server is enabled
service :
name : chronyd
enabled: true
- name : Postfix is enabled
service :
name : postfix
enabled: true
举例:
--- #代表文件开始,固定格式
- name: Configure importantuser consistently # - 表示该playbook中的第一个play,
hosts: node1.example.com
remote_user:devops
become:true
tasks:
- name: newbie exists with UID 4000 # - 表示该play中的第一个task
user:
name: newbie
uid: 4000
state: present
… #文件结尾,可省略
运行playbook
ansible-playbook命令用于运行playbooks。该命令在控制节点上执行,将运行的playbook的名称作为参数传递
[student@workstation dep-dynamic]$ ansible-playbook play.yml
当playbook被执行时,会产生输出来显示play和正在执行的任务。输出还报告执行的每个任务的结果。
下面的示例展示了一个简单剧本的内容,以及运行它的结果。
[student@workstation playbook]$ cat createuser.yaml
- name: Configure important user consistently
hosts: node1.example.com
remote_user: root
become: true
tasks:
- name: newbie exists with UID 4000
user:
name: newbie
uid: 4000
state: present
[student@workstation playbook]$ ansible-playbook createuser.yaml
PLAY [Configure important user consistently] ******************************
TASK [Gathering Facts] ****************************************
ok: [servera.lab.example.com]
TASK [newbie exists with UID 4000] ************************************
ok: [servera.lab.example.com]
PLAY RECAP ********************************************
servera.lab.example.com : ok=2 changed=0 unreachable=0 failed=0
一般来说,可执行playbook中的task是幂等的,多次运行playbook是安全的。如果目标托管主机已经处于正确状态,则不应进行任何更改。
[student@workstation playbook]$ ansible-playbook createuser.yaml
PLAY [configuration user] ******************************************************
TASK [Gathering Facts] *********************************************************
ok: [node1.example.com]
TASK [newbie exists with uid 4000] *****************************************************
ok: [node1.example.com]
PLAY RECAP *************************************************************
node1.example.com : ok=2 changed=0 unreachable=0 failed=0
这一次,所有任务都以ok状态传递,没有报告任何更改。
选项options
a.语法检查–syntax-check,可以用来验证playbook文件的语法。
在执行剧本之前,最好执行一次验证以确保其内容的语法是正确的。ansible-playbook命令提供了一个–syntax-check选项,可用于验证playbook文件的语法。下面的示例显示了剧本的成功语法验证。
[student@workstation dep-dynamic]$ ansible-playbook play.yml --syntax-check
playbook: play.yml
当语法验证失败时,将报告语法错误。输出还包括剧本中语法问题的大致位置。以下示例显示剧本的语法验证失败,其中剧本的name属性后面缺少空格分隔符。
b.空运行-C
另一个有用的选项是-C选项。这导致Ansible报告如果执行剧本会发生什么变化,但不会对托管主机进行任何实际更改。
[student@workstation dep-dynamic]$ ansible-playbook play.yaml -C
模拟实际运行情况。
playbook中编写多个plays
Writing Multiple Plays
playbook是一个包含一个或多个play列表的YAML文件。请记住,单个play是要对从inventory中选择的主机执行的任务的有序列表。因此,如果playbook包含多个play,每个play都可以将它的任务应用到一组独立的主机上。
这在编排可能涉及不同主机上的不同任务的复杂部署时非常有用。可以编写一个playbook,对一组主机运行一个play,完成后对另一组主机运行另一个play。
编写包含多个plays的playbook非常简单。playbook中的每个play都被编写为playbook中的顶级列表项。每个play都是一个列表项,包含通常的play指令。
下面的示例展示了一个包含了两个play的playbook,第一个play对node1.example.com运行,第二个play对node2.example.com运行。
- name: first play
hosts: node1.example.com
tasks:
- name: first task
yum:
name: httpd
state: present
- name: second task
service:
name: httpd
enabled: true
- name: second play
hosts: node2.example.com
tasks:
- name: first task
yum:
name: mariadb-server
state: present
- name: second task
service:
name: mariadb
enabled: true
Remote Users and Privilege Escalation in Plays
remote_user和提权privilege escalation设置默认使用ansible.cfg配置,也可以在play中单独设定。与hosts和tasks是指令是同一级别。
playbook提权
playbook中的任务通常通过与托管主机的网络连接来执行。与ad hoc一样,用于任务执行的用户帐户取决于可执行配置文件/etc/ansibl/ansible.cfg中的各种参数。运行任务的用户可以由remote_user参数定义。但是,如果启用了特权升级Privilege Escalation,其他参数(如become_user)也会产生影响。
如果在任务执行的ansible configuration中定义的远程用户不合适,可以在剧本中使用remote_user属性覆盖它。
remote_user: devops
become: true
become_method: sudo
becom_user: root
配置文件提权
还可以使用其他属性从playbook中定义特权升级参数。可以使用become布尔参数来启用或禁用特权升级,而不管它是如何在ansible的配置文件中定义的。通常,可以使用yes或true来启用权限升级,使用no或false来禁用权限升级。
如果启用了特权升级,则可以使用become_method属性来定义在特定play期间要使用的特权升级方法。
此外,启用了特权升级后,become_user属性可以定义用户帐户,用于在特定play的上下文中进行特权升级。
[student@workstation basic-playbook]$ cat ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
示例:
---
- name: /etc/hosts is up to date
hosts: servera.lab.example.com
remote_user: devops
become: yes
tasks:
- name: update hosts
lineinfile:
path: /etc/hosts
line: '192.168.1.1 server.lab.example.com server'
state: present
Finding Modules for Tasks
Module Documentation
Ansible提供了大量的模块,为管理员提供了很多工具来完成常见的管理任务。在本课程的早些时候,我们在http://docs.ansible.com上讨论了Ansible文档网站。网站上的模块索引是一个简单的方式浏览与Ansible模块列表。例如,用于user和service管理的模块可以在系统模块下找到,用于数据库管理的模块可以在数据库模块下找到。
对于每个模块,Ansible documentation网站提供了其功能的摘要,以及如何通过模块选项调用每个特定功能的说明。文档还提供了一些有用的示例,向您展示如何使用每个模块以及如何在任务中设置它们的参数。
您已经使用了ansible-doc命令来查找本地系统上安装的模块的信息。作为回顾,要查看控制节点上可用模块的列表,请运行ansible-doc -l命令。这将显示模块名称的列表及其函数的概要。
[student@workstation basic-playbook]$ ansible-doc -l
可以通过将模块名称传递给ansible-doc来显示有关特定模块的详细文档。与Ansible documentation网站一样,该命令提供了模块功能的概要、各种选项的详细信息和示例。下面的示例显示了为yum模块显示的文档。
[student@workstation ~]$ ansible-doc yum
> YUM (/usr/lib/python2.7/site-packages/ansible/modules/packaging/os/yum.py)
Installs, upgrade, removes, and lists packages and groups with the `yum' package manager.
Options (= is mandatory):
- conf_file
The remote yum configuration file to use for the transaction.
[Default: None]
- disable_gpg_check
Whether to disable the GPG checking of signatures of packages being installed. Has an effect only if state is `present' or `latest'.
(Choices: yes, no)[Default: no]
- disablerepo
`Repoid' of repositories to disable for the install/update operation. These repos will not persist beyond the transaction. When specifying
multiple repos, separate them with a ",".
[Default: None]
- enablerepo
`Repoid' of repositories to enable for the install/update operation. These repos will not persist beyond the transaction. When specifying
multiple repos, separate them with a ",".
[Default: None]
- exclude
Package name(s) to exclude when state=present, or latest
[Default: None]
- installroot
Specifies an alternative installroot, relative to which all packages will be installed.
-
模块的维护
Ansibie配备了大量可以用于许多任务的模块。上游社区非常活跃,这些模块可能处于不同的开发阶段。该模块的ansible-doc文档预计将指定在Ansibie上游社区中谁为该模块提供维护,以及该模块的开发状况。该模块的ansible-doc输出末尾的METADATA部分指出了这一点。
status字段记录模块的开发状态:
stableinterface:模块参数稳定,尽量不删除参数,不改变参数含义。
preview:模块处于技术预览状态,可能不稳定,它的参数可能会改变,或者它可能需要库或web服务,而这些库或web服务本身也会发生不兼容的变化。
deprecated:该模块已弃用,在未来的某个版本中将不再可用。
removed:模块已经从版本中删除,但是存在一个存根用于文档目的,以帮助以前的用户迁移到新的模块。
stableinterface状态只表明模块的接口是稳定的,它并不评价模块的代码质量。
在上游Ansibie社区维护该模块的supported_by字段记录。可能的值是:
CORE:由“CORE”Ansibie开发者上游维护,并始终包含在Ansibie中。
curated:由社区合作伙伴或公司提交和维护的模块。这些模块的维护者必须注意报告的问题或对模块提出的pull请求。在社区维护人员批准变更后,上游“核心”开发人员会审查对管理模块的建议变更。核心提交者还确保这些模块由于Ansibie引擎的变化而产生的任何问题都得到了纠正。这些模块目前包含在Ansibie中,但将来可能会单独打包。
community:核心上游开发者或合作伙伴/公司不支持的模块,但完全由一般的开源社区维护。这个类别中的模块仍然是完全可用的,但是问题的响应率完全取决于社区。这些模块目前也包含在Ansible中,但可能会在将来的某个时候单独打包。
有时候,你想做的事情模块并不存在。作为最终用户,您还可以编写自己的私有模块,或者从第三方获取模块。Ansible在ANSIBLE_LIBRARY环境变量指定的位置搜索定制模块,如果没有指定,在当前Ansible配置文件中的库指令中搜索定制模块。Ansible也搜索定制模块。/library目录相对于当前运行的剧本。
library = /usr/share/my_modules实战:
在taijitao-server上已经为Ansible项目创建了工作目录“/home/student/playbook”。目录中已经编写了ansible.cfg配置文件和一个inventory清单文件。托管主机server.example.com已经在这个inventory文件中定义。
在这个路径中,创建一个名为internet.yml的剧本,其中将包含两个plays。第一次play需要特权升级,并且必须按照指定的顺序执行以下任务:
-
使用yum模块确保安装了最新版本的软件包:firewall、httpd。
-
确保防火墙服务已启用并启动。
-
确保防火墙配置为允许连接到httpd服务使用的端口。
-
确保httpd服务已启用并启动。
-
确保托管主机的/var/www/html/index.html文件包含“Welcome to the ansible test !”。
第二种方法不需要特权升级,应该使用uri模块运行一个任务来确认URL http://server.example.com返回的HTTP状态码为200。
操作步骤如下:
- 切换到工作目录,/ home/student/playbook。
[ student@taijitao-server ~] cd /home/student/playbook
- 创建一个新的剧本,/home/student/playbook/intranet.yml,并添加开始第一个play所需的内容。它应该以托管主机taijitao-server.example.com为目标,并启用特权升级。
2.1. 创建并打开一个新的playbook,/home/student/playbook/intranet.yml,并在文件的开头添加一行由三个破折号组成的行,以指示YAML文件的开始。
---
2.2. 将以下行添加到/home/student/imp -playbook/intranet.yml文件表示play的开始,其名称为Enable intranet services。
- name: Enable intranet services
2.3. 在/home/student/imp-playbook/intranet.yml文件中添加以下一行,以指示该plays应用于servera托管主机。确保用两个空格缩进一行(与上面的name指令对齐),以表明它是第一个剧本的一部分。
hosts: servera.lab.example.com
2.4. 将以下行添加到/home/student/playbook/intranet.yml文件来启用权限升级。确保用两个空格缩进一行(与上面的指示对齐),以表明它是第一个剧本的一部分。
become: yes
- 在/home/student/playbook/intranet.yml文件中添加以下一行,定义任务列表的开头,将该行缩进两个空格(与上面的指令对齐),以表明它是第一个play的一部分。
tasks:
- 作为第一个play中的第一个任务,定义一个任务,以确保httpd和firewalld包是最新的。
4.1. 在第一个play中的tasks指令下,将以下行添加到/home/student/playbook/intranet.yml文件。这将创建确保安装最新版本httpd和firewalld包的任务。
一定要用四个空格、一个破折号和一个空格缩进任务的第一行。这表明该任务是第一次play tasks列表中的一个项目。
第一行提供了任务的描述性名称。第二行用六个空格缩进并调用yum模块。下一行缩进了8个空格,是一个name指令。它告诉yum模块应该确保哪些包是最新的。yum模块的name指令(与任务的名称不同)可以接受一个包列表,在接下来的两行中缩进10个空格。在列表之后,八个空格的缩进状态指令告诉yum模块应该安装软件包的最新版本。- name: latest version of httpd and firewalld installed yum: name: - httpd - firewalld state: latest
- 在play中定义另外两个任务,以确保firewalld正在运行,并将在引导时启动,并允许连接到http服务。
5.1. 在“/home/student/playbook/intranet.yml”文件中添加如下代码行,创建防火墙服务启用并运行的任务。请确保用四个空格、一个破折号和一个空格缩进该行。这表明该tasks包含在palys中,并且它是tasks列表中的一个项目。
第一个条目为任务提供了一个描述性名称。第二个条目用8个空格缩进并调用服务模块。其余条目缩进十个空格,并传递必要的参数,以确保启用并启动防火墙服务。- name: firewalld enabled and running service: name: firewalld state: started enabled: true
5.2. 在/home/student/playbook/intranet.yml文件中添加以下代码行,以创建任务,确保防火墙向远程系统打开HTTP服务。请确保用四个空格、一个破折号和一个空格缩进该行。这表明该任务包含在播放中,并且它是任务列表中的一个项目。
第一个条目为任务提供了一个描述性名称。第二个条目用六个空格缩进并调用防火墙模块。其余条目用8个空格缩进,并传递必要的参数,以确保永久允许对HTTP服务的访问。- name: firewalld permits httpd service firewalld: service: http permanent: true state: enabled immediate: true
- 向第一个play的列表中添加另一个任务,以确保httpd服务正在运行并将在引导时启动。
6.1. 在/home/student/playbook/intranet.yml文件中添加以下代码行,创建任务以确保httpd服务已启用并正在运行。请确保用四个空格、一个破折号和一个空格缩进该行。这表明该任务包含在播放中,并且它是任务列表中的一个项目。
第一个条目为任务提供了一个描述性名称。第二个条目用六个空格缩进并调用服务模块。其余条目用8个空格缩进并传递必要的参数,以确保httpd服务已启用并正在运行。- name: httpd enabled and running service: name: httpd state: started enabled: true
- 在第一个play列表中添加最后一个任务,确保正确的内容在/var/www/html/index.html中。
7.1. 在/home/student/playbook/intranet.yml文件中添加以下行,以创建确认/var/www/html/index.html文件填充了正确内容的任务。请确保用四个空格、一个破折号和一个空格缩进该行。这表明该任务包含在播放中,并且它是任务列表中的一个项目。
第一个条目为任务提供了一个描述性名称。第二个条目用六个空格缩进并调用copy模块。其余条目用8个空格缩进,并传递必要的参数,以确保web页面中有正确的内容。- name: test html page is installed copy: content: "welcome to the ansible test !\n" dest: /var/www/html/index.html
- 在/home/student/playbook/intranet.yml中定义针对localhost的第二个play,该play将测试intranet web服务器。它不需要特权升级。
8.1. 在/home/student/playbook/intranet.yml文件中添加以下一行,表示第二次play的开始。
- name: test intranet web server
8.2. 在/home/student/playbook/intranet.yml文件中添加以下一行,以指示play应用于localhost管理的主机。请确保用两个空格缩进该行,以表明它包含在第二个play中。
hosts: localhost
8.3. 在“/ home/student/playbook/intranet.yml”文件中添加如下代码,禁用权限升级。确保将成为指令的缩进与它上面的hosts指令对齐。
become: no
- 在/home/student/playbook/intranet.yml文件中添加以下一行,以定义任务列表的开头。请确保用两个空格缩进该行,以表明它包含在第二个play中。
tasks:
- 在第二个play中添加一个任务,该任务使用uri模块联系http://server.lab.example.com,如果HTTP状态代码为200则成功返回。
10.1. 在/home/student/playbook /intranet.yml文件中添加以下行,以创建从控制节点验证web服务的任务。请确保用四个空格、一个破折号和一个空格缩进第一行。这表明该任务是第二个剧本任务列表中的一个项目。
第一行提供了任务的描述性名称。第二行用六个空格缩进并调用uri模块。其余行缩进八个空格,并传递必要的参数以执行从控制节点到托管主机的web内容查询,并验证接收到的状态代码。- name: connect to intranet web server uri: url: http://server.example.com status_code: 200
- 看看最终的/ home / student /playbook /intranet.yml
---
- name: enable intranet services
hosts: server.example.com
become: yes
tasks:
- name: latest version of httpd and firewalld installed
yum:
name:
- httpd
- firewalld
state: latest
- name: firewalld enabled and running
service:
name: firewalld
state: started
enabled: true
- name: firewalld permits httpd service
firewalld:
service: http
permanent: true
state: enabled
immediate: true
- name: httpd enabled and running
service:
name: httpd
state: started
enabled: true
- name: test html page is installed
copy:
content: "Welcome to the ansible test!\n"
dest: /var/www/html/index.html
- name: test intranet web server
hosts: localhost
become: no
tasks:
- name: connect to intranet web server
uri:
url: http://server.example.com
status_code: 200
- 通过使用–syntax-check选项执行ansible-playbook命令来验证intranet.yml剧本的语法。
[student@taijitao-server playbook]$ ansible-playbook --syntax-check intranet.yml
playbook: intranet.yml
- 执行playbook。以确保所有任务成功完成。
[student@taijitao-server playbook]$ ansible-playbook intranet.yml