前沿:
ansible的文档说的不清不楚,文档一点也不实在,有些范例都做不通,走不通。今天中午吃饭的时候,和同事 祖天彪(这名够霸道吧),聊了很长时间ansible在实际项目中碰到的问题,尤其是运维平台页面上。 下午的时候,找他去玩耍,这哥们正好在看ansible的api源码,也就是 ansible runner ~ 就这样一块交流一两个小时,把runner 这部分的核心代码给过了一遍。 中文的文档真的很少, 我这边就简单的阐述下,看完后的一些总结。 可能有些地方不对,或者是没有说明白,还请大家见谅。
1
2
3
4
5
6
7
8
9
10
11
12
<code>import ansible.runner</code>
<code>runner = ansible.runner.Runner(</code>
<code> </code><code>host_list=</code><code>"/etc/ansible/xiaorui.py"</code>
<code> </code><code>module_name=</code><code>'command'</code><code>,</code>
<code> </code><code>module_args=</code><code>'ip a'</code><code>,</code>
<code> </code><code>pattern=</code><code>'web'</code><code>, </code>
<code>)</code>
<code>datastructure = runner.run()</code>
<code>print</code> <code>'\n'</code>
<code>print</code> <code>datastructure</code>
上面是一个简单的ansible api的执行的例子。 我们可以看到他调用的runner模块。
runner目录下面有个__init__.py文件,__init__.py的作用, 相当于class中的def __init__(self):函数,用来初始化模块。 把所在目录当作一个package处理。不懂的去看,python包相关的基础。
下面讲解下 Runner这个类。
class Runner(object):
''' core API interface to ansible '''
# see bin/ansible for how this is used...
def __init__(self,
host_list=C.DEFAULT_HOST_LIST, 这里不仅可以放 静态的hosts文件,也可以放 inventory的脚本,脚本要777权限。
module_path=None, 这个是ansible的路径,一般不用写
module_name=C.DEFAULT_MODULE_NAME, 模块的名字,模块的位置要选定在/usr/share/absible下,不然会识别不到。 要注意下~
module_args=C.DEFAULT_MODULE_ARGS, # ex: "src=/tmp/a dest=/tmp/b" 模块的参数
forks=C.DEFAULT_FORKS, # parallelism level 进程的数目,他的逻辑是这样,你如果填入了20个进程,他会判断你的list_hosts是否有20个,没有的话,他就会根据主机的数目来派生进程,如果超过20个,那就用multiprocess进程池pool来调度。mulitiprocess本身有个isalive的东西,来判断分离出去进程的存活状态。
timeout=C.DEFAULT_TIMEOUT, 这个就是超时的时间
pattern=C.DEFAULT_PATTERN, # which hosts? ex: 'all', 'acme.example.org' 这个是做相关的匹配,是关于inventory的匹配
remote_user=C.DEFAULT_REMOTE_USER, # ex: 'username' 远端用户的选择
remote_pass=C.DEFAULT_REMOTE_PASS, # ex: 'password123' or None if using key 远端密码的选择
remote_port=None, # if SSH on different ports 远端端口的选择
private_key_file=C.DEFAULT_PRIVATE_KEY_FILE, # if not using keys/passwords 还可以用指定key
sudo_pass=C.DEFAULT_SUDO_PASS, # ex: 'password123' or None sudo之后的密码的推送
background=0, # async poll every X seconds, else 0 for non-async 看字眼就知道他是做什么的了,他非常的像 saltstack的 event, 当派生出了一个任务后,
产生一个ansible_job_id,然后时不时的去拿数据
transport=C.DEFAULT_TRANSPORT, # 'ssh', 'paramiko', 'local' 这里是选择你的链接得到方式,默认是用的 paramiko
conditional='True', # run only if this fact expression evals to true 这个是什么呢? 相当与 puppet saltstack 里面的require,状态的判断。
callbacks=None, # used for output 回调的输出
sudo=False, # whether to run sudo or not 是否是sudo
sudo_user=C.DEFAULT_SUDO_USER, # ex: 'root' sudo的时候,用到的用户名
):
再来关注下这个,我和同事的关注点,不太一样,他以前经常用salt,他不想写hosts文件,或者是inventory文件,想直接 ansible ip地址 -m shell "ip a" 类似这样的使用,而我的想法是,想给inventory传递参数, inventory在被ansbile调用的时候,是不能传递参数的, 他后面的那个参数,只是获取 json的那个key。
简单的说, ansible -i nima.py "bj_nginx" -m shell "dir" 这里的bj_nginx 是不能传递给nima里面的,而是获取nima返回的那片json的。
<code>self.inventory = utils.</code><code>default</code><code>(inventory, lambda: ansible.inventory.Inventory(host_list))</code>
咱们进到,inventory的目录
[root@devops-ruifengyun inventory ]$ pwd
/usr/lib/python2.7/dist-packages/ansible/inventory
试图找下,官网支持不支持针对inventory的传递参数,host_list传递给了 Inventory类,他的逻辑也很干练,如果 host_list 如果没有传入的话,默认走的是 /etc/ansible/hosts文件,如果你指定了,他会从你的文件读入的, 首先如果是str,根据逗号来区分并且去除空格,放到list里面。接着是list了,。。。 霹雳拉ye,oh。。
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code> </code><code>if</code> <code>type</code><code>(host_list) </code><code>in</code> <code>[ </code><code>str</code><code>, </code><code>unicode</code> <code>]:</code>
<code> </code><code>if</code> <code>host_list.find(</code><code>","</code><code>) !</code><code>=</code> <code>-</code><code>1</code><code>:</code>
<code> </code><code>host_list </code><code>=</code> <code>host_list.split(</code><code>","</code><code>)</code>
<code> </code><code>host_list </code><code>=</code> <code>[ h </code><code>for</code> <code>h </code><code>in</code> <code>host_list </code><code>if</code> <code>h </code><code>and</code> <code>h.strip() ]</code>
<code> </code><code>if</code> <code>type</code><code>(host_list) </code><code>=</code><code>=</code> <code>list</code><code>:</code>
<code> </code><code>self</code><code>.parser </code><code>=</code> <code>None</code>
<code> </code><code>all</code> <code>=</code> <code>Group(</code><code>'all'</code><code>)</code>
<code> </code><code>self</code><code>.groups </code><code>=</code> <code>[ </code><code>all</code> <code>]</code>
<code> </code><code>for</code> <code>x </code><code>in</code> <code>host_list:</code>
<code> </code><code>if</code> <code>x.find(</code><code>":"</code><code>) !</code><code>=</code> <code>-</code><code>1</code><code>:</code>
<code> </code><code>tokens </code><code>=</code> <code>x.split(</code><code>":"</code><code>,</code><code>1</code><code>)</code>
<code> </code><code>all</code><code>.add_host(Host(tokens[</code><code>0</code><code>], tokens[</code><code>1</code><code>]))</code>
<code> </code><code>else</code><code>:</code>
<code> </code><code>all</code><code>.add_host(Host(x))</code>
<code> </code><code>elif</code> <code>os.path.exists(host_list):</code>
<code> </code><code>if</code> <code>os.path.isdir(host_list):</code>
<code> </code><code># Ensure basedir is inside the directory</code>
<code> </code><code>self</code><code>.host_list </code><code>=</code> <code>os.path.join(</code><code>self</code><code>.host_list, "")</code>
<code> </code><code>self</code><code>.parser </code><code>=</code> <code>InventoryDirectory(filename</code><code>=</code><code>host_list)</code>
<code> </code><code>self</code><code>.groups </code><code>=</code> <code>self</code><code>.parser.groups.values()</code>
<code> </code><code>elif</code> <code>utils.is_executable(host_list):</code>
<code> </code><code>self</code><code>.parser </code><code>=</code> <code>InventoryScript(filename</code><code>=</code><code>host_list)</code>
<code> </code><code>else</code><code>:</code>
<code> </code><code>data </code><code>=</code> <code>file</code><code>(host_list).read()</code>
<code> </code><code>if</code> <code>not</code> <code>data.startswith(</code><code>"---"</code><code>):</code>
<code> </code><code>self</code><code>.parser </code><code>=</code> <code>InventoryParser(filename</code><code>=</code><code>host_list)</code>
<code> </code><code>self</code><code>.groups </code><code>=</code> <code>self</code><code>.parser.groups.values()</code>
<code> </code><code>raise</code> <code>errors.AnsibleError(</code><code>"YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout"</code><code>)</code>
<code> </code><code>utils.plugins.vars_loader.add_directory(</code><code>self</code><code>.basedir(), with_subdir</code><code>=</code><code>True</code><code>)</code>
<code> </code><code>else</code><code>:</code>
<code> </code><code>raise</code> <code>errors.AnsibleError(</code><code>"Unable to find an inventory file, specify one with -i ?"</code><code>)</code>
<code> </code><code>def</code> <code>_match(</code><code>self</code><code>, </code><code>str</code><code>, pattern_str):</code>
<code> </code><code>原文:http:</code><code>/</code><code>/</code><code>rfyiamcool.blog.</code><code>51cto</code><code>.com</code><code>/</code><code>1030776</code><code>/</code><code>1420147</code>
最后,咱们的流程跑到这里。。。
elif utils.is_executable(host_list):
self.parser = InventoryScript(filename=host_list)
self.groups = self.parser.groups.values()
咱们再来看看InventoryScript做了什么东西。。。。
找到了, 这就是我给 host_list传递字符串的时候,会提示没有该文件,因为他的subprocess的shell状态是 False,这样的话,不能识别 分号和空格。 只要在subprocess.Popen追加一个shell=True就可以了。
既然ansible runner代码,基本过了一遍,那么 inventory 模块的参数传递,很明了了。。。
<code> </code><code>30</code> <code>def</code> <code>__init__(</code><code>self</code><code>, filename</code><code>=</code><code>C.DEFAULT_HOST_LIST):</code>
<code> </code><code>31</code>
<code> </code><code>32</code> <code>self</code><code>.filename </code><code>=</code> <code>filename</code>
<code> </code><code>33</code> <code>cmd </code><code>=</code> <code>[ </code><code>self</code><code>.filename, </code><code>"--list"</code> <code>]</code>
<code> </code><code>34</code> <code>try</code><code>:</code>
<code> </code><code>35</code> <code>sp </code><code>=</code> <code>subprocess.Popen(cmd, stdout</code><code>=</code><code>subprocess.PIPE, stderr</code><code>=</code><code>subprocess.PIPE)</code>
<code> </code><code>36</code> <code>except</code> <code>OSError, e:</code>
<code> </code><code>37</code> <code>raise</code> <code>errors.AnsibleError(</code><code>"problem running %s (%s)"</code> <code>%</code> <code>(</code><code>' '</code><code>.join(cmd), e))</code>
<code> </code><code>38</code> <code>(stdout, stderr) </code><code>=</code> <code>sp.communicate()</code>
<code> </code><code>39</code> <code>self</code><code>.data </code><code>=</code> <code>stdout</code>
<code> </code><code>40</code> <code>self</code><code>.groups </code><code>=</code> <code>self</code><code>._parse()</code>
接着,他用调用了他自己类下的 _parse() 函数 ~
39
<code>def</code> <code>_parse(</code><code>self</code><code>):</code>
<code> </code><code>all_hosts </code><code>=</code> <code>{}</code>
<code> </code><code>self</code><code>.raw </code><code>=</code> <code>utils.parse_json(</code><code>self</code><code>.data)</code>
<code> </code><code>all</code> <code>=</code> <code>Group(</code><code>'all'</code><code>)</code>
<code> </code><code>groups </code><code>=</code> <code>dict</code><code>(</code><code>all</code><code>=</code><code>all</code><code>)</code>
<code> </code><code>group </code><code>=</code> <code>None</code>
<code> </code><code>if</code> <code>'failed'</code> <code>in</code> <code>self</code><code>.raw:</code>
<code> </code><code>raise</code> <code>errors.AnsibleError(</code><code>"failed to parse executable inventory script results"</code><code>)</code>
<code> </code><code>for</code> <code>(group_name, data) </code><code>in</code> <code>self</code><code>.raw.items():</code>
<code> </code><code>group </code><code>=</code> <code>groups[group_name] </code><code>=</code> <code>Group(group_name)</code>
<code> </code><code>host </code><code>=</code> <code>None</code>
<code> </code><code>if</code> <code>not</code> <code>isinstance</code><code>(data, </code><code>dict</code><code>):</code>
<code> </code><code>data </code><code>=</code> <code>{</code><code>'hosts'</code><code>: data}</code>
<code> </code><code>if</code> <code>'hosts'</code> <code>in</code> <code>data:</code>
<code> </code><code>for</code> <code>hostname </code><code>in</code> <code>data[</code><code>'hosts'</code><code>]:</code>
<code> </code><code>if</code> <code>not</code> <code>hostname </code><code>in</code> <code>all_hosts:</code>
<code> </code><code>all_hosts[hostname] </code><code>=</code> <code>Host(hostname)</code>
<code> </code><code>host </code><code>=</code> <code>all_hosts[hostname]</code>
<code> </code><code>group.add_host(host)</code>
<code> </code><code>if</code> <code>'vars'</code> <code>in</code> <code>data:</code>
<code> </code><code>for</code> <code>k, v </code><code>in</code> <code>data[</code><code>'vars'</code><code>].iteritems():</code>
<code> </code><code>group.set_variable(k, v)</code>
<code> </code><code>all</code><code>.add_child_group(group)</code>
<code> </code><code># Separate loop to ensure all groups are defined</code>
<code> </code><code>if</code> <code>isinstance</code><code>(data, </code><code>dict</code><code>) </code><code>and</code> <code>'children'</code> <code>in</code> <code>data:</code>
<code> </code><code>for</code> <code>child_name </code><code>in</code> <code>data[</code><code>'children'</code><code>]:</code>
<code> </code><code>if</code> <code>child_name </code><code>in</code> <code>groups:</code>
<code> </code><code>groups[group_name].add_child_group(groups[child_name])</code>
<code> </code><code>return</code> <code>groups</code>
上面的代码已经很好理解了,就是把刚才通过subprocess获取的数据,用json模块,json.loads一下。这里会取出两个大key,一个是主机类型标签的hosts 这个key,和vars ,这个vars是给模板或者其他功能的数据。
有很多人会说,这也太搓了,太scha了,居然是通过外部调用获取的数据,但是我觉得这也是很多人着迷ansible的地方,不管你会不会python,你只要会任何一个语言,shell、php、java、nodejs、ruby只要能标准的stdout输出,那就行了。
剩下的不需要你再度的介入,因为对于ansible来说,我拿到的数据都是标准的输出,所以大家不管用怎么语言,一定要做好异常的处理,让他print干干净净的数据,这样ansible 这个小孩才能完美的加载数据。
希望大家通过这篇文章,对于ansible api更有深度的了解,一些的帮助,如果文章描述的有问题,请即使的通知我,我会即使的更正修改。
本文转自 rfyiamcool 51CTO博客,原文链接:http://blog.51cto.com/rfyiamcool/1420147,如需转载请自行联系原作者