Ansible清单(Inventory)文件(静态与动态)及示例

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

列表包含主机名或者IP地址的列表,并遵循INI格式。在Ansible中,我们有"静态"和"动态"清单(Inventory)。即使是在localhost上执行的特别操作也需要一个列表,尽管该列表可能只是由localhost组成的。

清单(Inventory)是Ansible架构最基本的构建块。执行ansible或者ansible playbook时,必须引用清单(Inventory)。列表是存在于运行ansible或者"ansible playbook"的同一系统上的文件或者目录。列表的位置可以在运行时用"-inventory file(-i)"参数引用,也可以通过在Ansible配置文件中定义路径来引用。

动态清单(Inventory)

从github列表脚本复制"ec2.py"和"ec2.ini"的内容,并使用相同的内容在"controller"上创建新文件。

[ansible@controller ~]$ ls -l
total 740
-rwxrwxrwx. 1 root root 148801 Sep 20 16:40 ec2.ini
-rwxrwxrwx. 1 root root 605527 Sep 20 16:40 ec2.py

提示:

修改环境#!/usr/bin/env pythonec2.py中使用python3当我下载脚本时,它使用python3作为环境变量

接下来,我们尝试执行脚本,但它抱怨"boto"模块。

[ansible@controller ~]$ ./ec2.py
Traceback (most recent call last):
  File "./ec2.py", line 164, in <module>
    import boto
ModuleNotFoundError: No module named 'boto'

让我们使用"pip"安装缺少的"boto"模块`

[ansible@controller ~]$ sudo pip3 install boto
WARNING: Running pip install with root privileges is generally not a good idea. Try `pip3 install --user` instead.
Collecting boto
  Downloading https://files.pythonhosted.org/packages/23/10/c0b78c27298029e4454a472a1919bde20cb182dab1662cec7f2ca1dcc523/boto-2.49.0-py2.py3-none-any.whl (1.4MB)
    100% |████████████████████████████████| 1.4MB 915kB/s
Installing collected packages: boto
Successfully installed boto-2.49.0

将IAM角色分配给Ansible引擎服务器

接下来,如果我尝试重新执行脚本,我们会得到以下错误

boto.exception.NoAuthHandlerFound: No handler was ready to authenticate. 1 handlers were checked. ['HmacAuthV4Handler'] Check your credentials

这意味着我们必须为Ansible引擎分配一个IAM角色。

选择ansible引擎实例,单击"操作",然后从下拉菜单中选择"实例设置"→"修改IAM角色"

因为我其中没有角色,所以我将创建一个新角色。单击"创建新IAM角色",将打开一个新的终端窗口

单击"创建角色"`

选择"AWS service"作为实体,然后选择"EC2"作为用例。单击"下一步:权限"

在搜索栏中搜索"ec2full",然后选择"AmazonEC2FullAccess"。单击"下一步:标记"`

我们稍后将使用"tags"字段进行动态清单(Inventory)。现在让我们将此字段留空,然后单击"下一步:回顾"

分配一个角色名,我们给了"EC2FullAccessForAnsible"。单击"创建角色"

一旦角色创建成功。使用"Modify IAM role"返回终端,单击刷新按钮刷新刚才所做的更改。现在从下拉列表中可以看到新创建的角色。

选择角色并单击"保存"

如果一切正常,那么我们将看到一条消息"IAM role successfully attached"

接下来,我们尝试运行脚本,但再次出现一些错误

[ansible@controller ~]$ ./ec2.py
ERROR: "Forbidden", while: getting ElastiCache clusters

我在网上查了一下,发现有一个github页面报告了这个错误,但在编写本教程时没有可用的修复程序,所以我继续在ec2中禁用了elasticache。

反正我不需要它来做演示。

[ansible@controller ~]$ grep ^elasticache ec2.ini
elasticache = False

现在让我们重新运行脚本(手指交叉)

[ansible@controller ~]$ ./ec2.py
{
  "_meta": {
    "hostvars": {
      "18.216.206.252": {
        "ansible_host": "18.216.206.252",
        "ec2__in_monitoring_element": false,
        "ec2_account_id": "311590943723",
        "ec2_ami_launch_index": "2",
        "ec2_architecture": "x86_64",
        "ec2_block_devices": {
          "sda1": "vol-04186fa5137af2ce7"
        },
<output trimmed>
  "us-east-2": [
    "18.216.206.252",
    "3.17.80.41",
    "3.137.172.55"
  ],
  "us-east-2a": [
    "18.216.206.252",
    "3.17.80.41",
    "3.137.172.55"
  ],
  "vpc_id_vpc_232f8148": [
    "18.216.206.252",
    "3.17.80.41",
    "3.137.172.55"
  ]
}

答对了,现在我们用"动态目录"得到了主机列表。现其中创建了不同的区域,如"us-east-2a"、"us-east-2"等

我们可以在此列表中的"ec2"组下找到我们的实例列表:

"ec2": [
    "18.216.206.252",
    "3.17.80.41",
    "3.137.172.55"
  ],

现在我们将使用ansible检查'ec2'组的连通性

[ansible@controller ~]$ ansible -i ec2.py ec2 -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
18.216.206.252 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: Warning: Permanently added '18.216.206.252' (ECDSA) to the list of known hosts.\r\[email protected]: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
    "unreachable": true
}
3.137.172.55 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
3.17.80.41 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

ping对2台服务器成功,但对其中一台服务器失败,这是预期的,因为我没有将公钥复制到"controller"上的"localhost"。

让我们将公钥复制到localhost并重新验证输出

[ansible@controller ~]$ ssh-copy-id controller

让我们为"ec2"组重新运行"ping"命令:

[ansible@controller ~]$ ansible -i ec2.py ec2 -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
3.137.172.55 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
3.17.80.41 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
18.216.206.252 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

这次我们从"ec2"组的所有服务器得到一个"pong"响应。

创建新实例以验证动态列表脚本

现在我们有了一个动态列表脚本,它将获取我们在AWS上创建的任何新实例。但是如果没有部署SSH密钥,ansible将"无法连接"到新实例,因此我们有一个依赖关系。

重要提示:

这一点很重要,我们必须记住,使用动态资源清册,ansible将能够动态获取IP地址,但要连接到实例,它仍然依赖于SSH密钥,而SSH密钥必须手动复制。

现在为了避免这种依赖,我们必须始终使用任何现有托管节点的镜像创建新实例。因此,新实例将提供所有配置,这些配置已经是托管节点的一部分,包括SSH公钥、ansible user和python工具。

要创建任何manage节点的镜像,请选择实例(我们将选择server1),然后单击"Actions"→"image"→"create image"

给出一个图像名称,在本教程中,我给出了"ansiblemagednodes"并单击"Create image"。

这个过程需要一些时间。我们可以通过从左侧选项卡菜单中选择"Images"并单击"AMIs",即AmazonMachineimages来检查状态

一旦AMI的状态为"available",选择相应的AMI并单击"Launch"以启动使用此镜像创建新实例的过程。

在下一个终端中,根据需求选择实例类型,然后单击"下一步:配置实例详细信息"

我将此部分保留为默认值,然后单击"下一步:添加存储"。

在本节中,我们可以选择存储大小。我也将保留默认设置,然后单击"下一步:添加标记"

我不想添加任何标记详细信息,请单击"下一步:配置安全组"。在本节中,我将选择我先前创建的安全组以允许所有通信,然后单击"查看并启动"

在下一个会话中,我们将获得为实例选择的配置。如果一切正常,请单击"启动"。接下来选择"SSH密钥对",或者创建一个新的密钥对。我将使用现有的密钥对。

接受条款和条件并单击"启动实例"。接下来,我们可以单击"查看实例"来检查状态。如我们所见,新服务器处于"初始化"状态,可能需要一些时间才能启动。

我们将为这个实例添加一个名称"server3"。我们可以单击"刷新"按钮来检查新实例的状态。一旦它处于"running"状态,我们将连接到控制器,然后重新执行"ec2.py"脚本,使用动态资源清册获取主机列表

[ansible@controller ~]$ ./ec2.py

现在从输出中查找"ec2"组:

"ec2": [
    "3.17.178.77",
    "18.216.206.252",
    "3.17.80.41",
    "3.137.172.55"
  ],

如我们所见,现在"ec2"组有4台服务器,其中"3.137.172.55"是我们刚刚创建的第4台服务器的IP。接下来,我们将使用ansible ad-hoc命令ping此服务器,以检查ansible是否能够与此服务器通信

[ansible@controller ~]$ ansible -i ec2.py 3.137.172.55 -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
3.137.172.55 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

我给出了新服务器的IP,而不是整个"ec2"组,以避免长时间的输出。但是正如我们所看到的,我们已经从这个服务器收到了一个"pong"响应,所以"动态资源清册正在按预期工作"。

创建自定义动态列表脚本

我们已经有了一个来自Ansible的动态列表脚本,但是如果我们需要创建自己的脚本呢?因此,"让我们为我们的环境创建一个动态列表脚本"。

提示:

我在AWS中的托管节点实例中添加了"db"和"app"标记,以便我们可以在列表中相应地对它们进行分组。要添加/修改标记,可以选择实例并单击"操作"→"管理标记",然后修改标记值。

我的一个实例中的"Manage tags"的示例屏幕截图。同样,可以将"Key"添加为"Env",将"Value"添加为"db"

目前,我已将"db"标记添加到两个节点,并将"app"标记添加到一个节点。如果执行"ec2.py"脚本,我们应该得到以下信息:

[ansible@controller ~]$ ./ec2.py
  <output trimmed>
  "tag_Name_app": [
    "18.220.88.40"
  ],
  "tag_Name_controller": [
    "3.137.186.147"
  ],
  "tag_Name_db": [
    "52.15.35.213",
    "3.133.110.138"
  ],
  <output trimmed>

因此,我捕获了只包含EC2实例细节的输出。这里有3个可用的标签,appdbcontroller

我们将使用一个脚本,它将根据标记值收集所有实例的公共IP地址值。

说明:

该脚本将再次需要访问EC2实例,我们已经在前面创建了一个新的IAM角色。

#!/usr/bin/env python3
import sys
import json
try:
    import boto3
except Exception as e:
    print(e)
    print("Please rectify above exception and then try again")
    sys.exit(1)
def get_hosts(ec2_ob,fv):
    f={"Name":"tag:Env" , "Values": [fv]}
    hosts=[]
    for each_in in ec2_ob.instances.filter(Filters=[f]):
        hosts.append(each_in.public_ip_address)
    return hosts
def main():
    ec2_ob=boto3.resource("ec2","us-east-2")
    db_group=get_hosts(ec2_ob,'db')
    app_group=get_hosts(ec2_ob,'app')
    all_groups={'db': db_group, 'app': app_group }
    print(json.dumps(all_groups))
    return None
if __name__=="__main__":
    main()

下面是脚本:

  • 需要在"controller"上安装"boto3"模块。我们可以使用pip3 install boto3安装它

  • 使用"ec2"组搜索"us-east-2"区域下的所有实例。

  • 我们创建了两个组,即"db_group"和"app_group",它们将使用"get_hosts"函数获取主机

  • 在"get_hosts"中,我们为"Name"和"Values"创建了一个过滤器`

  • 名称将包含"Key",即"Env",而"Value"是"db"或者"app"`

  • 对于ec2中的每个实例_ob.实例根据我们的过滤器,我们打印实例的公共IP地址

  • 我们将服务器列表存储在"hosts"列表中,并将每个实例添加到此列表中

  • 所有\_groups将存储'db _group'和'app _group'输出,并将其映射到相应的标记

  • 使用json.dumps文件我们将把输出转换成JSON格式

为脚本分配可执行权限

[ansible@controller custom_scripts]$ chmod u+x aws_ec2_custom.py

现在我们将执行我们的脚本并检查它是否能够从ec2组获取公共地址列表:

[ansible@controller custom_scripts]$ ansible -i aws_ec2_custom.py all --list-hosts
  hosts (3):
    52.15.35.213
    3.133.110.138
    18.220.88.40

因此,我们得到了"app"和"db"服务器的列表,我们还可以打印各个组的服务器:

[ansible@controller custom_scripts]$ ansible -i aws_ec2_custom.py db --list-hosts
  hosts (2):
    52.15.35.213
    3.133.110.138
[ansible@controller custom_scripts]$ ansible -i aws_ec2_custom.py app --list-hosts
  hosts (1):
    18.220.88.40

我们可以使用ad-hoc命令使用我们的动态列表脚本ping所有服务器:

[ansible@controller custom_scripts]$ ansible -i aws_ec2_custom.py all -m ping
52.15.35.213 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
3.133.110.138 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
18.220.88.40 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

静态清单(Inventory)

在详细介绍之前,让我们先看看基本的清单(Inventory)文件:

[ansible@controller ~]$ cat /etc/ansible/hosts
server1
server2
server3
localhost
  • 在本例中,我定义了3个托管主机和localhost,这意味着"controller"节点也将充当客户机节点。

  • 如果要针对所有这些主机运行Ansible任务,则可以在运行Ansible playbook时将all传递给hosts参数或者Ansible命令;这将使

  • Ansible针对列表文件中列出的所有主机运行其任务。

  • 这种类型的简单列表文件的一个缺点是,我们不能对主机的子集运行Ansible任务,也就是说,如果我们想对其中两个主机运行Ansible,那么我们就不能用这个列表文件来运行Ansible任务。

提供主机作为输入参数

如果不希望在列表文件中的所有主机上执行ansible,则可以手动提供主机名或者IP地址作为ansible命令或者playbook的输入参数

[ansible@controller ~]$ ansible server1:server2 -m ping

这里ansible命令将仅在"server1"和"server2"上执行。现在,这绝对是隔离主机列表的一个选项,但是这种方法的缺点是,如果我们有100个主机,那么提供这么多主机作为输入不是一个好主意。

列表文件中的组

在下面的示例中,我们将列表文件分组到不同的部分:

[ansible@controller ~]$ cat /etc/ansible/hosts
[devops]
server1
server2
[db]
server3
server4
[app]
server5
server6

现在,我们可以通过将组名传递给"Ansible playbook"命令来对一组主机运行Ansible,而不是对所有主机运行Ansible。当Ansible对一个组运行其任务时,它将获取该组下的所有主机。

要对"devops"组的所有主机运行Ansible,我们需要运行如下所示的命令行:

~]# ansible devops all -m ping

如果还要指定列表文件的路径,则可以使用

~]# ansible devops all -i /home/hynman/ansible/hosts -m ping

分组

  • 分组是在多个主机上同时运行Ansible的好方法。

  • Ansible提供了一种将多个组进一步分组到一起的方法。

  • 我们可以在清单(Inventory)文件中有多个组,甚至可以将类似的组合并到一个组中

  • 例如,假设我们在东区有多个应用程序和数据库服务器,这些服务器被分组为"devops"和"db"。

然后可以创建一个名为"eastzone"的主组,如下所示。

[devops]
server1
server2
[db]
server3
server4
[app]
server5
server6
[eastzone:children]
devops
db

使用此命令,我们可以在整个"eastzone"数据中心上运行Ansible,而不是逐个在所有组上运行它。

[ansible@controller ~]$ ansible eastzone -m ping

带有列表文件的正则表达式

  • 如果我们有许多服务器,列表文件将非常有用。

  • 假设有大量web服务器遵循相同的命名约定,例如,server001server002、…、server00N,等等。

  • 单独列出所有这些服务器将导致一个脏的列表文件,这将很难用成百上千行来管理。

  • 为了处理这种情况,Ansible允许我们在其列表文件中使用regex。

[devops]
server[1:9]
[db]
server[10:19]
[app]
server[20:29]
[eastzone:children]
devops
db
  • 服务器[1:9]将匹配'server1'、'server2'、'server3'、。。。服务器9

  • 服务器[10:19]将匹配'server10'、'server11'、'server12'、。。。服务器19

  • 服务器[20:29]将匹配'server20'、'server21'、'server22'、。。。服务器29

清单(Inventory)变量

我们还可以在列表文件中定义不同的变量。我已将一个新的"server3"实例添加到我的托管节点列表中,其中我没有配置无密码身份验证,并在"server3"上创建了一个新用户"hynman"。我将使用"server3"中的用户"hynman"从controller节点使用"ansible"进行通信

我在"controller"节点上的ansible用户的主文件夹下装箱了一个自定义列表文件

[ansible@controller ~]$ cat myinventory
[passwordless]
server1
server2
[password]
server3

其中:我将我的列表分为两组,其中"passwordless"组由两台主机组成,其中我实际配置了无密码身份验证,而"server3"配置为使用密码。

如果我尝试从所有这些主机获取"whoami"命令信息:

[ansible@controller ~]$ ansible -i myinventory all -m command -a "whoami"
server3 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ansible@server3: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
    "unreachable": true
}
server1 | CHANGED | rc=0 >>
ansible
server2 | CHANGED | rc=0 >>
ansible

命令已在"server1"和"server2"上成功执行,但在"server3"上由于缺少无密码通信而失败。为了克服此问题,我们可以使用"username"和"password"在列表文件中"定义变量",ansible应使用这些变量连接"server3"`

让我用以下信息更新"myinventory"文件:

[passwordless]
server1
server2
[password]
server3 ansible_ssh_user=hynman ansible_ssh_pass=redhat

这里我提供了登录"server3"的用户名和密码,因此现在ansible应该使用这些凭据与"server3"通信

让我们执行相同的命令:

[ansible@controller ~]$ ansible -i myinventory all -m command -a "whoami"
server2 | CHANGED | rc=0 >>
ansible
server1 | CHANGED | rc=0 >>
ansible
server3 | CHANGED | rc=0 >>
hynman

因此,这次命令成功了,我们可以看到对于"server3",whoami返回了"hynman",而不是"ansible"user。

我们还可以在列表中使用"vars"来定义这些变量:

[passwordless]
server1
server2
[password]
server3
[password:vars]
ansible_ssh_user=hynman
ansible_ssh_pass=redhat

ansible仍然可以使用提供的凭据访问"[password]"组的服务器部分

然而,考虑这些值有一个"顺序顺序"。分配给服务器本身的变量具有最高优先级,后跟"vars"变量:

例如,我将"ansible_ssh_user"定义为服务器旁的ansible,而使用"vars"则将"hynman"定义为"ssh_user"。

[passwordless]
server1
server2
[password]
server3 ansible_ssh_user=ansible ansible_ssh_pass=redhat
[password:vars]
ansible_ssh_user=hynman
ansible_ssh_pass=redhat

让我们执行ansible ad-hoc命令:

[ansible@controller ~]$ ansible -i myinventory all -m command -a "whoami"
server1 | CHANGED | rc=0 >>
ansible
server2 | CHANGED | rc=0 >>
ansible
server3 | CHANGED | rc=0 >>
ansible

这里的"server3"是使用"ansible"用户访问的,因此我们在服务器上给出的值具有"更高的优先级"。我们将在后面的不同章节中学习有关ansible变量的更多信息,同时使用playbooks。