第一个Ansible剧本

时间:2020-01-09 10:34:03  来源:igfitidea点击:

什么是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 playbookbinary/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模式,并且越来越多的模块在每个版本中被添加。特别要注意的是,commandshell模块不支持它,因为模块无法判断哪些命令会导致更改,哪些不会。因此,当这些模块在check模式之外运行时,它们总是会返回changed,因为它们假定已进行了更改

我个人也没有成功的'文件'模块与干模式,但它是可能的,在未来我们会有更好的结果,但干模式可以帮助你与其他ansible模块,所以你一定要使用它之前,执行一个剧本在生产环境。