第一个Ansible剧本
什么是Ansible剧本
Ansible playbooks是以称为YAML的特定格式编写的文本文件或者配置文件。
它们以YAML格式表示,并且具有最少的语法,这不是编程语言或者脚本,而是配置的模型。
每个剧本都在一个列表中包含一个或者多个"剧本"。
戏剧的目标是把一组"主人"映射到一些定义明确的角色和"任务"。
"task"只不过是一个调用或者操作,它适用于一组"hosts"。
示例1:安装单个软件包的第一个playbook
让我们编写一个简单的剧本,在所有托管节点上安装"wget"rpm。所以我们将在这两台主机上安装wget。下面是我们的ansible剧本。
[ansible@controller ~]$ cat install_wget.yml # --- represents this file as a playbook -- # Use [space]play[space] where play is combination of hosts+tasks - hosts: all # Become root user become: yes # List of tasks to be executed tasks: - yum: name=wget state=absent
我们的剧本里有一个剧本。每个剧本都由以下两个重要部分组成:
配置内容
:我们需要配置一个主机或者一组主机来运行游戏。此外,我们还需要包括有用的连接信息,例如连接哪个用户,是否使用sudo命令,等等。运行内容
:这包括要运行的任务的规范,包括要修改的系统组件以及它们应该处于的状态,例如,已安装、已启动或者最新状态。这可以用任务来表示,然后用角色来表示。
创建主机资源清册
在开始使用Ansible编写剧本之前,我们需要定义一个需要配置的所有主机的列表,并使其可供Ansible使用。我们也可以选择使用动态清单(Inventory),我们已经在前面的章节中学习过了。其中:我将保持简单,并坚持静态清单(Inventory)。
我们将使用默认的列表文件/etc/ansible/hosts
来定义下面的托管节点
server1 server2 server3
早些时候,我部署了不带密码短语的"server3",以了解如何使用带有密码的托管节点。现在,我将把公钥重新部署到"server3",以便在"controller"和"server3"之间进行无密码通信
[ansible@controller ~]$ ssh server3 Activate the web console with: systemctl enable --now cockpit.socket Last login: Mon Sep 21 09:49:33 2017 from 172.31.7.253 [ansible@server3 ~]$ logout Connection to server3 closed.
我们的无密码通信正在使用服务器3
使用主机模式
我们在playbook中使用了"hosts:all",这意味着playbook将对列表中找到的所有节点执行。我们还可以定义模式而不是"all",以匹配列表文件中的一组有选择的主机或者组。
模式 | 示例 |
---|---|
组名 | 应用程序 |
全部匹配 | 全部 |
范围 | 服务器[000:999] |
主机名/主机名全局 | *.example.com,host01. example.com |
例外情况 | app:!server3 |
正则表达式 | ~(nn |
我们已经在"使用清单(Inventory)文件"中讨论了这些单独的模式
任务
将主机映射到"任务"。任务是对一组主机执行的一系列动作,这些主机与游戏中指定的模式相匹配。每个播放通常包含多个任务,这些任务在与模式匹配的每台机器上连续运行。
例如,下面的片段来自我们的剧本
tasks: - yum: name=wget state=present
在"tasks"下,我们定义了用于执行任务的模块,即"yum"。此模块需要某些参数来完成任务,例如包的名称、"state",它定义了操作"present/latest/absent"。为了更好的可读性,我们在新行中定义了这些值。
另外,由于安装新的rpm需要root权限,因此我们在playbook中也使用了"become:yes"
运行剧本
Ansible附带了"Ansible playbook"命令来启动一个playbook。让我们执行这个剧本
[ansible@controller ~]$ ansible-playbook install_wget.yml
以下是运行上述命令时发生的情况:
"ansible playbook"参数是将playbook作为参数的命令(
install_wget.yml
)和主持人对决"install_wget"参数包含我们创建的单个播放,即安装"wget"rpm
"hosts:all"参数是我们主机的列表,它让Ansible知道要对哪些主机或者主机组进行调用
启动前面的命令将开始调用plays,按照我们在playbook中描述的顺序编排。以下是前面命令的输出:
Ansible读取指定为Ansible playbook命令参数的playbooks,并开始按序列顺序执行播放。
既然我们已经宣布单打,它就和"所有"主机相抗衡。"all"关键字是一种特殊的模式,它将匹配所有主机。因此,此播放中的"tasks"将在作为参数传递的列表中的所有主机上执行。
在运行任何任务之前,Ansible将收集有关要配置的系统的信息。这些信息是以"事实"的形式收集的。如果你还记得我在"可靠的事实"一章中提到过这一点。在playbook中,我们没有提到任何关于收集事实的内容,但是在执行任务之前调用了'setup'模块来收集事实
下一节是使用"yum"模块安装包的任务的输出。对于"server1"和"server2",输出显示"OK",但对于"server3",则显示"changed"。这意味着在"server1"和"server2"上执行成功,但没有任何更改,即没有安装任何内容。"wget"可能已在这些服务器上处于安装状态,因此ansible跳过了任何更改。在"server3"上,输出为"changed",这意味着"wget"已成功安装
最后,Ansible在"PLAY RECAP"部分打印playbook运行的"summary"。如果任何主机无法访问,或者在任何系统上执行失败,则它指示进行了多少修改。
让我们在playbook中使用"state=abserve"而不是"present"和"yum"模块,从所有托管节点中删除"wget"rpm:
现在我们使用'state=absent'重新运行playbook,我们看到'changed=1',这意味着执行成功地更改了一个数据,其中'wget'已从托管节点中删除。
示例2:在不同的托管节点上安装多个包
现在让我们向前看,在这个例子中,我们将使用一个playbook文件安装多个包。我修改了我的清单(Inventory)文件
[ansible@controller ~]$ cat /etc/ansible/hosts [web] server1 server2 [app] server3
如我们所见,我将托管节点分为两组,其中"server1"和"server2"是"web"组的一部分,"server3"是"app"组的一部分。我们将在"web"组中安装"httpd"rpm,而在"app"组中安装"wget"和"vim"rpm。
下面是我们的第二张照片_剧本.yml`
-- - hosts: app become: yes tasks: - yum: name=wget state=latest - yum: name=vim state=latest - hosts: web become: yes tasks: - yum: name=httpd state=latest
我已经从这个YAML文件中删除了注释,因为现在我们应该已经熟悉了流程。我们在一个剧本中创造了两个剧本。在第一个重头戏中,我们将在"app"组的主机上安装"wget"和"vim"。在第二个重头戏中,我们将在"web"组的主机部分安装"httpd"。
让我们执行剧本:
[ansible@controller ~]$ ansible-playbook second_playbook.yml PLAY [app] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server3] TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** changed: [server3] TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** changed: [server3] PLAY [web] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server2] ok: [server1] TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** changed: [server2] changed: [server1] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** server1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 server2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 server3 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
现在输出应该很清楚了:
第一个重头戏即"play[app]"被执行,其中第一步是在"app"组的所有服务器上收集事实。然后在"app"组上预期任务,即安装两个RPM
在第二个重头戏即"play[web]"中,再次调用了"setup"模块来收集"web"组中所有主机的所有事实。接下来执行任务,即在相应的受管节点上安装"wget"和"vim"rpm
PLAY_RECAP
指最终结果。在"server3"上,我们更改了=2,这意味着在"server1"和"server2"上安装了2个RPM,我们更改了=1,因为使用此剧本安装了单个"httpd"包。
示例3:禁用收集事实模块
我们知道,默认情况下,ansible playbook将执行setup模块,从各个托管节点收集事实。如果我们觉得不需要使用'gather_facts:false,我们还可以选择禁用此功能
让我们在行动手册中使用它,删除_事实.yml包括以下内容:
-- - hosts: server1 become: yes gather_facts: false tasks: - yum: name=wget state=absent
在这个剧本中,我们将删除wget rpm作为根用户,此外,我们还禁用了"ansible facts"。
执行此剧本的输出。这一次输出必须更短,因为ansible没有从'server3'收集任何事实
示例4:为游戏和任务指定自定义名称
在上面的所有示例中,如果观察ansible playbook的输出,则剧本的名称基于"hosts"的值,类似地,"TASK"的名称基于使用的"module"。现在,当我们有一个包含多个重头戏的剧本时,这可能会让人感到困惑,因此我们希望通过使用'name=<name>'为单个剧本和任务指定一个自定义名称。
例如,这里我们有一个剧本,其中我有一个剧本,将执行两个任务。
复制
/tmp/演示.txt
从ansible引擎到我的列表中"web"组的主机部分在"我的资源清册"中"web"组的主机部分再次创建一个空文件
示例assign_custom_姓名.yml
-- - name: Hello World hosts: web gather_facts: false tasks: - name: Copy file to web group copy: src=/tmp/demo.txt dest=~/ - name: Create an empty file on web group file: path=/tmp/src_file.txt state=touch
其中:因为我们有两个任务,所以我们已经使用了两次"hyphen"。为剧本和两个任务定义了一个"name="。现在让我们来执行这个剧本:
我们可以检查输出,现在可以看到"PLAY"和"TASK"的正确名称。
示例5:将playbook作为shell脚本执行
我们还可以选择调用playbook,就像调用shell脚本一样。为了实现这一点,我们必须用三个破折号替换第一行-
到ansible playbook
binary/usr/bin/ansible playbook
的路径,用she bang
字符,就像我们对shell脚本所做的那样。
可以使用"which"命令获取"ansible playbook"二进制文件的路径:
~]$ which ansible-playbook /usr/bin/ansible-playbook
我们将使用最后一个示例脚本"assign_custom"_姓名.yml`在playbook中用这个二进制路径替换第一行,如下所示
#!/usr/bin/ansible-playbook - name: Hello World hosts: web gather_facts: false tasks: - name: Copy file to web group copy: src=/tmp/demo.txt dest=~/ - name: Create an empty file on web group file: path=/tmp/src_file.txt state=touch
为剧本提供可执行权限:
[ansible@controller ~]$ chmod u+x assign_custom_name.yml
让我们像执行shell脚本一样运行剧本:
[ansible@controller ~]$ ./assign_custom_name.yml PLAY [Hello World] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Copy file to web group] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [server1] ok: [server2] TASK [Create an empty file on web group] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** changed: [server1] changed: [server2] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** server1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 server2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例6:使用playbooks打印调试消息
到目前为止,在所有的例子中,我们都在执行任务,但在控制台上什么也不打印。我们确实更改了游戏和任务的名称,但这些是不同的用例。在所有其他脚本和代码中,我们可以选择在控制台上打印文本,同时执行任何任务,例如使用"echo"、"print"、"printf"等。同样,在ansible中,我们有"debug"模块,它只是ansible版本的"print"语句。
我们可以使用"任务"部分中的"调试"模块打印有助于"调试"剧本的消息。
我写了一本小剧本-消息.yml它只是在"任务"部分打印一条消息"Hello World":
-- - hosts: server2 tasks: - debug: msg="Hello World"
让我们来执行这个剧本:
所以我们在控制台上得到了预期的输出。如果必须打印多行文本,则必须将"msg"放在下一行:
-- - hosts: server2 tasks: - debug: msg: - "This is first line" - "This is second line" - "This is third line"
观察压痕。我在下一行加了"msg"并加了一些空格。所有的文本消息都会再次添加到以"-"(破折号)开头的单独行中,并带有一些额外的空格。
示例7:增加剧本的详细程度
我们可以添加调试消息,这很容易调试单个任务,但要调试整个playbook,我们还可以选择打印详细消息并增加详细级别,以获得在后端执行的活动的更详细输出。
在本示例中,ymlincrease-详细.yml
文件我创建了两个任务,其中第一个任务我将打印一条没有任何详细信息的消息,而第二个任务我将打印一条更加详细的消息。
-- - hosts: server2 tasks: - name: default verbose debug: msg: - This is a test message without verbosity - name: verbosity level 2 debug: msg: - This is a test message with verbosity level 2 verbosity: 2
让我们来执行这个剧本:
[ansible@controller ~]$ ansible-playbook increase-verbosity.yml PLAY [server2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** ok: [server2] TASK [default verbose] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** ok: [server2] => { "msg": [ "This is a test message without verbosity" ] } TASK [verbosity level 2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** skipping: [server2] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** server2 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
从输出中,我们看到没有详细信息的消息被打印出来,但是详细级别为2的消息被"跳过"。
具有详细信息的消息被"跳过",因为在这种情况下,ansible希望使用详细输入执行playbook,因此我们将使用"-vv"参数重新运行playbook:
[ansible@controller ~]$ ansible-playbook increase-verbosity.yml -vv ansible-playbook 2.9.13 config file = /etc/ansible/ansible.cfg configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.6/site-packages/ansible executable location = /usr/bin/ansible-playbook python version = 3.6.8 (default, Apr 16 2017, 01:36:27) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] Using /etc/ansible/ansible.cfg as config file PLAYBOOK: increase-verbosity.yml ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** 1 plays in increase-verbosity.yml PLAY [server2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** task path: /home/ansible/increase-verbosity.yml:2 ok: [server2] META: ran handlers TASK [default verbose] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** task path: /home/ansible/increase-verbosity.yml:4 ok: [server2] => { "msg": [ "This is a test message without verbosity" ] } TASK [verbosity level 2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** task path: /home/ansible/increase-verbosity.yml:8 ok: [server2] => { "msg": [ "This is a test message with verbosity level 2" ] } META: ran handlers META: ran handlers PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** server2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
现在我们在控制台上看到了更详细的输出。我们可以增加"-v"的计数,以获得更详细的信息,从而填满控制台。
示例8:执行语法检查
定义一个文件是否具有正确的语法对于机器来说相当容易,但是对于人类来说可能更复杂。这并不意味着机器能够为我们修复代码,但它们可以快速识别是否存在问题。
当我们执行剧本时,ansible将执行任何语法检查,但是他们可能会通过部分执行任务来破坏功能。所以在实际执行剧本之前,我们可以进行语法检查
我已经创建了一个新的yaml文件syntax_check.yml
我还特意在"debug"处添加了额外的空白。因为"msg"和"debug"都是从同一行开始的。我还应该为带有"msg:`
-- - hosts: all tasks: - debug: msg: "Hello World"
让我们使用"--syntax check"来执行脚本,以检查剧本的语法:
[ansible@controller ~]$ ansible-playbook syntax_check.yml --syntax-check ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each: JSON: Expecting value: line 1 column 1 (char 0) Syntax Error while loading YAML. did not find expected '-' indicator The error appears to be in '/home/ansible/syntax_check.yml': line 5, column 8, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: - debug: msg: "Hello World" ^ here
现在我将修复缩进并重新执行--syntax check
:
[ansible@controller ~]$ ansible-playbook syntax_check.yml --syntax-check playbook: syntax_check.yml
当语法检查没有发现任何错误时,输出将与前一个类似,它列出了分析过的文件,而没有列出任何错误。
由于Ansible知道所有支持的模块中所有支持的选项,因此它可以快速读取代码,并验证我们提供的YAML是否包含所有必需的字段,以及是否不包含任何不支持的字段。
示例9:执行剧本的试运行
尽管我们可能对所编写的代码很有信心,但在生产环境中真正运行代码之前对其进行测试仍然是值得的。在这种情况下,运行代码是一个好主意,但是要有一个安全网。这就是检查模式的用途:
让我们创建一个名为"check"的剧本-模式.yml包含以下内容:
-- - hosts: server2 tasks: - name: Install nano yum: name=nano state=latest
此playbook应在"server2"上安装"nano"rpm。你可能已经意识到这里出了什么问题?安装rpm需要根级别的权限,这在本手册中是不存在的。
但是,让我们使用"-check"在干模式下执行这个剧本,并验证输出:
[ansible@controller ~]$ ansible-playbook --check check-mode.yml PLAY [server2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** ok: [server2] TASK [Install nano] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** fatal: [server2]: FAILED! => {"changed": false, "msg": "This command has to be run under the root user.", "results": []} PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** server2 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
正如预期的那样,试运行告诉了我们这个问题,即缺少根级别权限。这个想法是运行不会改变机器的状态,只会突出显示当前状态和playbook中声明的状态之间的差异。
并不是所有的模块都支持check模式,但是所有的主要模块都支持check模式,并且越来越多的模块在每个版本中被添加。特别要注意的是,command
和shell
模块不支持它,因为模块无法判断哪些命令会导致更改,哪些不会。因此,当这些模块在check模式之外运行时,它们总是会返回changed,因为它们假定已进行了更改
我个人也没有成功的'文件'模块与干模式,但它是可能的,在未来我们会有更好的结果,但干模式可以帮助你与其他ansible模块,所以你一定要使用它之前,执行一个剧本在生产环境。