Ansible清单(Inventory)文件(静态与动态)及示例
列表包含主机名或者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 python
在ec2.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",即A
mazonM
achinei
mages来检查状态
一旦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个可用的标签,app
,db
和controller
。
我们将使用一个脚本,它将根据标记值收集所有实例的公共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服务器遵循相同的命名约定,例如,
server001
、server002
、…、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。