一.简述:
当公司业务达到一定规模后,不同的业务,部署不同的服务/组件,很有必要用一些方法(静态文件/主机名?)标识出相应的主机角色(所属项目),有的还会同公司的CMDB平台做数据同步便于维护。 在以上描述的多role的复杂环境下,在使用ansible的过程中, 难免会通过facts采集一些数据的信息,然而ansible默认搜集的信息基本上是根据机器属性相关;而无法搜集业务相关的信息,好消息是,尽管ansible不能默认搜集相关信息,但是却支持自定义facts信息采集的插件。
二. facts使用简述:
一般情况下ansible可以通过setup模块采集facts信息。setup的源码如下:
def main():
module = AnsibleModule(
argument_spec = dict(
gather_subset=dict(default=["all"], required=False, type='list'),
filter=dict(default="*", required=False),
fact_path=dict(default='/etc/ansible/facts.d', required=False),
),
supports_check_mode = True,
)
data = get_all_facts(module)
module.exit_json(**data)
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.facts import *
if __name__ == '__main__':
main()
可发现,setup定义了一个main函数,然后加载了module_utils.basic和facts数据,打开facts.py可看一下代码片段:
def __init__(self, module, load_on_init=True):
self.module = module
self.facts = {}
### TODO: Eventually, these should all get moved to populate(). But
# some of the values are currently being used by other subclasses (for
# instance, os_family and distribution). Have to sort out what to do
# about those first.
if load_on_init: #采集信息的函数如下,如不需要可注释相关的方法(platform_facts有依赖,不能注释)
self.get_platform_facts()
self.get_distribution_facts()
self.get_cmdline()
self.get_public_ssh_host_keys()
self.get_selinux_facts()
self.get_caps_facts()
self.get_fips_facts()
self.get_pkg_mgr_facts()
self.get_service_mgr_facts()
self.get_lsb_facts()
self.get_date_time_facts()
self.get_user_facts()
self.get_local_facts()
self.get_env_facts()
self.get_dns_facts()
self.get_python_facts()
可发现Facts类初始化会执行一下相关的函数采集相关的facts信息,其中get_local_facts()可从被控制机上读取文件,作为facts信息进行搜集,这种情况下,我们就可以通过将被控制机上的一些信息写入相关文件,即可通过ansible的采集。get_local_facts内容如下:
def get_local_facts(self):
fact_path = self.module.params.get('fact_path', None) #获取facts文件路径(main中定义)
if not fact_path or not os.path.exists(fact_path):
return
local = {}
for fn in sorted(glob.glob(fact_path + '/*.fact')): #for facts文件路径下所以*.fact结尾的文件
print fn
# where it will sit under local facts
fact_base = os.path.basename(fn).replace('.fact','')
if stat.S_IXUSR & os.stat(fn)[stat.ST_MODE]:
# run it
# try to read it as json first
# if that fails read it with ConfigParser
# if that fails, skip it
rc, out, err = self.module.run_command(fn)
else:
out = get_file_content(fn, default='')
fact = 'loading %s' % fact_base
try: #尝试用json解析,失败用configparser(fact文件内容只支持json或INI).
fact = json.loads(out)
except ValueError:
# load raw ini
cp = configparser.ConfigParser()
try:
cp.readfp(StringIO(out))
except configparser.Error:
fact = "error loading fact - please check content"
else:
fact = {}
#print cp.sections()
for sect in cp.sections():
if sect not in fact:
fact[sect] = {}
for opt in cp.options(sect):
val = cp.get(sect, opt)
fact[sect][opt]=val
local[fact_base] = fact
if not local:
return
self.facts['local'] = local #赋值
注意ansible默认只处理/etc/ansible/facts.d下以.fact结尾的文件,并且文件内容只能是json格式或者INI格式。知道了ansible facts的处理过程后,就好解决了, 可以在被控制机上/etc/ansible/facts.d(也可以通过指定或修改默认路径)创建一个fact结尾的文件:
# mkdir -p /etc/ansible/facts.d/
# vim /etc/ansible/facts.d/test.fact
{"name":"xiang.xiao3", "dicttest":{"AA":"BB"}}
执行setup测试如下:
# ansible test -m setup -a "filter=ansible_local"
BJCER11-18.opi.com | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"ansible": {
"dicttest": {
"AA": "BB"
},
"name": "xiang.xiao3"
}
}
},
"changed": false
}
采集OK 。。。。。。。
上面一种情况用于在默认facts的基础上做相应的扩展,但其实很多时候我们并不会使用所采集的信息,比如,hosts定义的是主机名,我只想采集主机上的ip,其他的都不需要,或者想采集默认facts信息中没有的信息, 这种情况下,就不太适合使用facts.d的方式(并且,执行默认的facts采集,本人测试大概会需要3s钟的时间,比较长了),解决方式是:禁用默认的facts,采用自己写的模块!
三. 自定义facts模块
如果自定义facts模块的话,需要注意下路径问题,配置文件中可通过library参数指定或者使用ANSIBLE_LIBRARY环境变量指定,或者ansible目录下的library目录下(后两种方式这里就不说了),本人的library配置如下:
library = /usr/share/my_modules/
在library目录下创建一个ipinfo的模块,内容如下:
#!/usr/bin/python
DOCUMENTATION = """ #简介
test
test
"""
EXAMPLES=""" #案例
- info: test
"""
import sys
import shlex
import json
import socket
hosts = socket.gethostname()
ips = socket.gethostbyname(hosts) #通过主机名获取ip
args_file = sys.argv[1]
args_data = file(args_file).read()
argu = shlex.split(args_data)
for i in argu:
if "=" in i:
(key,value) = i.split("=")
if key == "enable" and value == "yes":
print json.dumps({"ansible_facts":{"ipp":ips}})
注: 采集的数据必须json格式,且放在key名为'ansible_facts' 的key下,否则。。。。等着纠结吧!!!!
然后配置playbook:
---
- hosts: test
#gather_facts: False
tasks:
- name: req value
ipinfo: enable=yes
- name: copy nginx.conf
debug: msg="value:{{ ipp }}"
执行结果如下:
TASK [copy nginx.conf] *********************************************************
ok: [BJCER11-18.opi.com] => {
"msg": "value:10.5.11.18"
}
ad-hoc执行方式:
# ansible test -m ipinfo -a "enable=yes"
BJCER11-18.opi.com | SUCCESS => {
"ansible_facts": {
"ipp": "10.5.11.18"
},
"changed": false
}
如有需求,细节上的可自行修改!
----------------------------------------------------------------------------------------------
深耕运维行业多年,擅长linux、容器云原生、运维自动化等方面。
承接各类运维环境部署、方案设计/实施、服务代运维工作,欢迎沟通交流 !