在Ansible中,将可管理的服务器集合成为Inventory。因此,Inventory管理便是服务器管理。
hosts文件位置
我们知道,Ansible在执行操作时,首先需要确定对哪些服务器执行操作。默认情况下,Ansible读取/etc/ansible/hosts文件中的服务器配置,获取需要操作的服务器列表。Ansible定义与获取服务器列表的方式比这个要灵活的多。
在Ansible中,有三种方式指定hosts文件:
1)默认读取/etc/ansible/hosts文件
2)通过命令行参数的 -i 指定hosts文件
3)通过ansible.cfg文件的inventory选项
ansible命令的--list-hosts选项用来显示匹配的服务器列表,我们可以通过该参数验证服务器的匹配情况
[heboan@c1 ~]$ ansible test --list-hosts hosts (2): 192.168.88.2 192.168.88.3
我们在家目录下创建个hosts,然后用-i指定这个hosts
[heboan@c1 ~]$ cat hosts [test] 192.168.20.1 192.168.20.2 [heboan@c1 ~]$ [heboan@c1 ~]$ ansible test -i hosts --list-hosts hosts (2): 192.168.20.1 192.168.20.2 [heboan@c1 ~]$ [heboan@c1 ~]$ ansible test --list-hosts #默认情况 hosts (2): 192.168.88.2 192.168.88.3
我们也可以在ansible.cfg中通过inventory选项指定hosts文件路径
[heboan@c1 ~]$ cat /etc/ansible/ansible.cfg [defaults] remote_port = 2202 remote_user = heboan inventory = /home/heboan/hosts [heboan@c1 ~]$ [heboan@c1 ~]$ ansible test --list-hosts hosts (2): 192.168.20.1 192.168.20.2
灵活定义hosts文件内容
Ansible支持更加灵活的方式定义hosts文件,例如将服务器进行分组,以便对不同的服务器类型进行不同的操作, 如下
mail.heboan.com #不属于任何一个组 [webservers] foo.heboan.com bar.heboan.com [dbserver] one.heboan.com two.heboan.com three.heboan.com
在服务器匹配时,all或星号是一个特殊的名称,用于匹配所有的服务器
[heboan@c1 ~]$ ansible '*' --list-hosts hosts (6): mail.heboan.com foo.heboan.com bar.heboan.com one.heboan.com two.heboan.com three.heboan.com
使用组名,匹配该组所有服务器
[heboan@c1 ~]$ ansible dbserver --list-hosts hosts (3): one.heboan.com two.heboan.com three.heboan.com
Ansible也可以定义一个组,这个组下面定义的不是服务器列表,而是包含其他组的名称, 通过":children"的方式来声明
[common:children]
webserver
dbserver
如果我有一批服务器,并且这些服务器有相同的模式,我们可以向下面这样定义服务器
[webservers] web[1:3].heboan.com bar.heboan.com [dbservers] db[a:d].heboan.com
灵活匹配hosts文件内容
规则 | 含义 |
192.168.88.2或c2.heboan.com | 匹配模板IP地址或服务器名,如果有多个ip或服务器,使用":"分隔 |
webservers | 匹配目标组为webservers,多个组使用":"分隔 |
all或'*' | 匹配所有服务器 |
webservers:!dbservers | 匹配在webservers中,不在dbservers组中的服务器 |
webservers:&dbservers | 匹配同时在webservers组以及在dbservers组中的服务器 |
*.example.com或192.168.* | 使用通配符匹配 |
webservers[0],webservers[1:],webservers[-1] | 使用索引或切片操作的方式匹配组中的服务器 |
~(web|db).*example.com | 以~开头的匹配,表示使用正则表达式匹配 |
动态Inventory获取
从前面的介绍可以看到,Ansible提供给你了非常灵活的方式来编写hosts文件,可以节省不必要的时间浪费、提高工作效率。此外,Ansible还可以通过调用云计算服务的API,编写自定义脚本的方式获取服务器列表。如果公司有使用CMDB系统来管理服务器,那么,我们可以通过读取CMDB数据库中的记录得到服务器列表。
既然 我们可以通过调用云计算系统的API,或者读取CMDB系统的数据库获取服务器列表,那么,再将服务器地址写入hosts文件就显得比较冗余,且没有任何必要。
一个动态获取服务器列表的脚本必须支持如下两个命令行参数:
1)--host=<hostname>: 用于列出某台服务器的详细信息
2)--list:用于列出群组以及群组中的服务器
例如,我们才CMDB系统库中包含了一些服务器的信息, 如果将这些服务器信息从数据库中拷贝到hosts文件中,效果如下:
[root@c1 ~]# cat /etc/ansible/hosts [test] 192.168.88.2 ansible_user=heboan ansible_port=22 192.168.88.3 ansible_user=heboan ansible_port=22 [uat] 192.168.88.4 ansible_user=heboan ansible_port=22
按照Ansible的约定,我们需要编写一个动态脚本来获取服务器的列表,这个脚本必须支持--list选项和--host选项。其中,--list选项以json的格式返回以组名为key,服务器列表为value的数据。--host返回一个字典,该字典中包含了这个服务器的详细信息,如下:
[heboan@c1 ~]$ python hosts.py --list { "test": [ "192.168.88.2", "192.168.88.3" ], "uat": [ "192.168.88.4" ] } [heboan@c1 ~]$ [heboan@c1 ~]$ python hosts.py --host='192.168.88.2' { "ansible_port": "22", "asnible_user": "heboan" }
接下来我们来看下如何编写这样一个动态获取服务器的脚本。假设。我们的CMDB数据库中,存在一张名为hosts的表:
CREATE TABLE hosts( id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT, host varchar(15) DEFAULT NULL, groupname varchar(15) DEFAULT NULL, username varchar(15) DEFAULT NULL, port int(11) DEFAULT NULL ); INSERT INTO hosts values(null, '192.168.88.2', 'test', 'heboan', 22); INSERT INTO hosts values(null, '192.168.88.3', 'test', 'heboan', 22); INSERT INTO hosts values(null, '192.168.88.3', 'uat', 'heboan', 22);
下面是动态获取服务器列表的程序
#!/usr/bin/python #_*_ coding: UTF-8 _*_ import argparse import json from collections import defaultdict from contextlib import contextmanager import pymysql def to_json(in_dict): return json.dumps(in_dict, sort_keys=True, indent=4) @contextmanager def get_conn(**kwargs): conn = pymysql.connect(**kwargs) try: yield conn finally: conn.close() def parse_args(): parser = argparse.ArgumentParser(description='Heboan Inventory Module') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true', help='list active servers') group.add_argument('--host', help='List details about the specific host') return parser.parse_args() def list_all_hosts(conn): hosts = defaultdict(list) with conn as cur: cur.execute('select * from hosts') rows = cur.fetchall() for row in rows: no, host, group, user, port = row hosts[group].append(host) return hosts def get_host_detail(conn, host): details = {} with conn as cur: cur.execute("select * from hosts where host='{}'".format(host)) rows = cur.fetchall() if rows: no, host, group, user, port = rows[0] details.update(asnible_user=user, ansible_port=port) return details def main(): parser = parse_args() with get_conn(host='127.0.0.1', user='root', password='root', db='cmdb' ) as conn: if parser.list: hosts = list_all_hosts(conn) print(to_json(hosts)) else: details = get_host_detail(conn, parser.host) print(to_json(details)) if __name__ == '__main__': main()
为了让我们动态获取服务器列表程序能够应用在Ansible中,需要为hosts.py加上可执行权限。其他使用方式和普通的hosts文件一模一样。
定义服务器变量
...