前沿:
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,如需轉載請自行聯系原作者