摘要:寫在前面是一個非常棒的運維工具,可以遠程批量執行命令上傳文件等自動化運維操作,由于要搞配置管理,初始化等批量操作,而自己對相對熟悉因此選擇了。
寫在前面:
ansible是一個非常棒的運維工具,可以遠程批量執行命令、上傳文件等自動化運維操作,由于要搞配置管理,初始化等批量操作,而自己對ansible相對熟悉,因此選擇了ansible playbook。
不過在調用playbook api的過程中,發現原始api并不能滿足我的需求,網絡上多數文檔還是1.0版本,因此下載了2.0源碼查看,重寫了部分類。因此這里總結學習,也給有相同需求的朋友參考
playbook字面意思看就是劇本,其實也很形象,ansible-playbook就是事先定義好很多task(任務)放在.yml文件中,執行的時候就像劇本一樣,依次往下演(執行)
#/tmp/xx.yml --- - hosts: all #hosts中指定 tasks: - name: "ls /tmp" #- name:任務名稱,下面是任務過程 shell: "ls /tmp/"
ansible-playbook執行:
二、ansible-playbook api介紹ansible-playbook api由ansible官方提供接口,用于在python代碼中更靈活的使用,但官方只提供了ansible-api的參考文檔,且2.0以后變化較大,api編寫更復雜,但更為靈活
官方參考: http://docs.ansible.com/ansib...
一個基本的ansible-playbook api調用
# -*- coding:utf-8 -*- # 描述: # V1 WZJ 2016-12-20 from collections import namedtuple from ansible.parsing.dataloader import DataLoader from ansible.vars import VariableManager from ansible.inventory import Inventory from ansible.executor.playbook_executor import PlaybookExecutor # 用來加載解析yaml文件或JSON內容,并且支持vault的解密 loader = DataLoader() # 管理變量的類,包括主機,組,擴展等變量,之前版本是在 inventory中的 variable_manager = VariableManager() # 根據inventory加載對應變量,此處host_list參數可以有兩種格式: # 1: hosts文件(需要), # 2: 可以是IP列表,此處使用IP列表 inventory = Inventory(loader=loader, variable_manager=variable_manager,host_list=["172.16.1.121"]) variable_manager.set_inventory(inventory) # 設置密碼,需要是dict類型 passwords=dict(conn_pass="your password") # 初始化需要的對象 Options = namedtuple("Options", ["connection", "remote_user", "ask_sudo_pass", "verbosity", "ack_pass", "module_path", "forks", "become", "become_method", "become_user", "check", "listhosts", "listtasks", "listtags", "syntax", "sudo_user", "sudo"]) # 初始化需要的對象 options = Options(connection="smart", remote_user="root", ack_pass=None, sudo_user="root", forks=5, sudo="yes", ask_sudo_pass=False, verbosity=5, module_path=None, become=True, become_method="sudo", become_user="root", check=None, listhosts=None, listtasks=None, listtags=None, syntax=None) # playbooks就填寫yml文件即可,可以有多個,以列表形式 playbook = PlaybookExecutor(playbooks=["/tmp/xx.yml"],inventory=inventory, variable_manager=variable_manager, loader=loader,options=options,passwords=passwords) # 開始執行 playbook.run()
執行上面腳本:
可以發現,這個api調用執行雖然成功了,但是并沒有什么詳細輸出(這個跟一個名叫CallbackBase的類有關)
重寫palybook api1 為什么重寫?
在實際應用中,有許多地方是不太滿足需求的
1.比如inventory有host group(主機組)的概念,但是默認都放在all組里面去了,導致在編寫yml的時候,無法對不同IP組進行不同的操作
2.console輸出結果有時候需要自定義
3.調用api需要更為靈活
當然除了以上需求外,還有一些其它意義,比如閱讀源碼有助于熟悉ansible的工作方式,也提升自己的腳本能力
注意:所謂重寫只是在源碼的基礎上繼承并修改了部分代碼,有可能導致ansible不穩定的情況,另外只有在有中文注釋的地方才是重寫內容
2 ansible源碼文件簡單介紹
源碼下載地址:https://github.com/ansible/an...
其實ansible我們所用到的源碼都在lib里面,而需要關注的也主要是三個文件夾和一些類:
用于解析和聚合host,group等環境變量: lib/ansible/inventory/__init__.py 中 Inventory 類
用于console輸出: lib/ansible/plugins/callback/default.py 中 CallbackModule 類
用于playbook的解析工作: lib/executor/playbook_executor.py 中 PlaybookExecutor 類
playbook底層用到的任務隊列:lib/executor/task_queue_manager.py 中 TaskQueueManager 類
幾個重寫的類的樹狀圖:
1.修改可以為IP傳入group name
2.額外的自定義參數,如:{"test":1000}
# -*- coding:utf-8 -*- # 描述: # V1 WZJ 2016-12-20 Inventory重寫封裝api基本功能 import fnmatch from ansible.compat.six import string_types, iteritems from ansible import constants as C from ansible.errors import AnsibleError from ansible.inventory.dir import InventoryDirectory, get_file_parser from ansible.inventory.group import Group from ansible.inventory.host import Host from ansible.module_utils._text import to_bytes, to_text from ansible.parsing.utils.addresses import parse_address from ansible.plugins import vars_loader from ansible.utils.vars import combine_vars from ansible.utils.path import unfrackpath try: from __main__ import display except ImportError: from ansible.utils.display import Display display = Display() HOSTS_PATTERNS_CACHE = {} from ansible.inventory import Inventory class YunweiInventory(Inventory): """重寫Inventory""" def __init__(self, loader, variable_manager, group_name, ext_vars=None,host_list=C.DEFAULT_HOST_LIST): # the host file file, or script path, or list of hosts # if a list, inventory data will NOT be loaded # self.host_list = unfrackpath(host_list, follow=False) # 傳入的hosts self.host_list = host_list # 傳入的項目名也是組名 self.group_name = group_name # 傳入的外部變量,格式為字典 self.ext_vars = ext_vars # caching to avoid repeated calculations, particularly with # external inventory scripts. self._vars_per_host = {} self._vars_per_group = {} self._hosts_cache = {} self._pattern_cache = {} self._group_dict_cache = {} self._vars_plugins = [] self._basedir = self.basedir() # Contains set of filenames under group_vars directories self._group_vars_files = self._find_group_vars_files(self._basedir) self._host_vars_files = self._find_host_vars_files(self._basedir) # to be set by calling set_playbook_basedir by playbook code self._playbook_basedir = None # the inventory object holds a list of groups self.groups = {} # a list of host(names) to contain current inquiries to self._restriction = None self._subset = None # clear the cache here, which is only useful if more than # one Inventory objects are created when using the API directly self.clear_pattern_cache() self.clear_group_dict_cache() self.parse_inventory(host_list) def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] self.parser = None # Always create the "all" and "ungrouped" groups, even if host_list is # empty: in this case we will subsequently an the implicit "localhost" to it. ungrouped = Group("ungrouped") all = Group("all") all.add_child_group(ungrouped) # 加一個本地IP local_group = Group("local") # 自定義組名 zdy_group_name = Group(self.group_name) self.groups = {self.group_name:zdy_group_name,"all":all,"ungrouped":ungrouped,"local":local_group} #self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): # 默認添加一個本地IP (lhost, lport) = parse_address("127.0.0.1", allow_ranges=False) new_host = Host(lhost, lport) local_group.add_host(new_host) for h in host_list: try: (host, port) = parse_address(h, allow_ranges=False) except AnsibleError as e: display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_text(e)) host = h port = None new_host = Host(host, port) if h in C.LOCALHOST: # set default localhost from inventory to avoid creating an implicit one. Last localhost defined "wins". if self.localhost is not None: display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name)) display.vvvv("Set default localhost to %s" % h) self.localhost = new_host # 為組添加host zdy_group_name.add_host(new_host) # 為主機組添加額外參數 # 添加外部變量 if self.ext_vars and isinstance(self.ext_vars,dict): for k,v in self.ext_vars.items(): zdy_group_name.set_variable(k,v) local_group.set_variable(k,v) elif self._loader.path_exists(host_list): # TODO: switch this to a plugin loader and a "condition" per plugin on which it should be tried, restoring "inventory pllugins" if self.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self._basedir, with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError("Unable to parse %s as an inventory source" % host_list) else: display.warning("Host file not found: %s" % to_text(host_list)) self._vars_plugins = [ x for x in vars_loader.all(self) ] # set group vars from group_vars/ files and vars plugins for g in self.groups: group = self.groups[g] group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) self.get_group_vars(group) # get host vars from host_vars/ files and vars plugins for host in self.get_hosts(ignore_limits=True, ignore_restrictions=True): host.vars = combine_vars(host.vars, self.get_host_variables(host.name)) self.get_host_vars(host)二、collback重寫,用于自定義輸出
1.以callback.default.CallbackModule為基類繼承
2.自定義了輸出格式,正確輸出情況,不需要的輸出就忽略了,只留下幾個有用的stdout
3.最后的輸出結果:
#-*- coding:utf-8 -*- # 描述: # V1 WZJ 2016-12-19 CallBack重寫封裝api基本功能,用于console輸出 import json from ansible import constants as C from ansible.plugins.callback.default import CallbackModule try: from __main__ import display except ImportError: from ansible.utils.display import Display display = Display() class YunweiCallback(CallbackModule): """重寫console輸出日志""" # 重寫2.0版本正確stdout def v2_runner_on_ok(self, result): if self._play.strategy == "free" and self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) self._clean_results(result._result, result._task.action) #delegated_vars = result._result.get("_ansible_delegated_vars", None) delegated_vars = self._dump_results(result._result) #delegated_vars = result._result #n_delegated_vars = self._dump_results(result) #print n_delegated_vars self._clean_results(result._result, result._task.action) if result._task.action in ("include", "include_role"): return elif result._result.get("changed", False): if delegated_vars: # 自定義輸出 zdy_msg = self.zdy_stdout(json.loads(delegated_vars)) if zdy_msg: msg = "changed: [%s]%s" % (result._host.get_name(), zdy_msg) else: msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars) else: msg = "changed: [%s]" % result._host.get_name() color = C.COLOR_CHANGED # 判斷是否是第一步 setup elif result._result.get("ansible_facts",False): msg = "ok: [ %s | %s ]" % (str(result._host),str(result._host.get_groups())) color = C.COLOR_OK else: if delegated_vars: # 自定義輸出 zdy_msg = self.zdy_stdout(json.loads(delegated_vars)) if zdy_msg: msg = "ok: [%s]%s" % (result._host.get_name(), zdy_msg) else: msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars) else: msg = "ok: [%s]" % result._host.get_name() color = C.COLOR_OK if result._task.loop and "results" in result._result: self._process_items(result) else: self._display.display(msg, color=color) self._handle_warnings(result._result) # 自定義輸出,格式清晰一些 def zdy_stdout(self,result): msg = "" if result.get("delta",False): msg += u" 執行時間:%s"%result["delta"] if result.get("cmd", False): msg += u" 執行命令:%s"%result["cmd"] if result.get("stderr",False): msg += u" 錯誤輸出: %s"%result["stderr"] if result.get("stdout",False): msg += u" 正確輸出: %s"%result["stdout"] if result.get("warnings",False): msg += u" 警告:%s"%result["warnings"] return msg三、封裝PlaybookExecutor
1.封裝為更容易調用的playbook api調用
2.定制化一部分參數
# -*- coding:utf-8 -*- # 描述: # V1 WZJ 2016-12-19 PlayBook重寫封裝api基本功能 import json from ansible import constants as C from collections import namedtuple from ansible.parsing.dataloader import DataLoader from ansible.vars import VariableManager from ansible.playbook.play import Play from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.playbook_executor import PlaybookExecutor from callback import YunweiCallback from ansible.utils.ssh_functions import check_for_controlpersist # 調用自定義Inventory from inventory import YunweiInventory as Inventory try: from __main__ import display except ImportError: from ansible.utils.display import Display display = Display() class YunweiPlaybookExecutor(PlaybookExecutor): """重寫PlayBookExecutor""" def __init__(self, playbooks, inventory, variable_manager, loader, options, passwords, stdout_callback=None): self._playbooks = playbooks self._inventory = inventory self._variable_manager = variable_manager self._loader = loader self._options = options self.passwords = passwords self._unreachable_hosts = dict() if options.listhosts or options.listtasks or options.listtags or options.syntax: self._tqm = None else: self._tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=self.passwords, stdout_callback=stdout_callback) # Note: We run this here to cache whether the default ansible ssh # executable supports control persist. Sometime in the future we may # need to enhance this to check that ansible_ssh_executable specified # in inventory is also cached. We can"t do this caching at the point # where it is used (in task_executor) because that is post-fork and # therefore would be discarded after every task. check_for_controlpersist(C.ANSIBLE_SSH_EXECUTABLE) class PlayBookJob(object): """封裝一個playbook接口,提供給外部使用""" def __init__(self,playbooks,host_list,ssh_user="bbs",passwords="null",project_name="all",ack_pass=False,forks=5,ext_vars=None): self.playbooks = playbooks self.host_list = host_list self.ssh_user = ssh_user self.passwords = dict(vault_pass=passwords) self.project_name = project_name self.ack_pass = ack_pass self.forks = forks self.connection="smart" self.ext_vars = ext_vars ## 用來加載解析yaml文件或JSON內容,并且支持vault的解密 self.loader = DataLoader() # 管理變量的類,包括主機,組,擴展等變量,之前版本是在 inventory中的 self.variable_manager = VariableManager() # 根據inventory加載對應變量 self.inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, group_name=self.project_name, # 項目名對應組名,區分當前執行的內容 ext_vars=self.ext_vars, host_list=self.host_list) self.variable_manager.set_inventory(self.inventory) # 初始化需要的對象1 self.Options = namedtuple("Options", ["connection", "remote_user", "ask_sudo_pass", "verbosity", "ack_pass", "module_path", "forks", "become", "become_method", "become_user", "check", "listhosts", "listtasks", "listtags", "syntax", "sudo_user", "sudo" ]) # 初始化需要的對象2 self.options = self.Options(connection=self.connection, remote_user=self.ssh_user, ack_pass=self.ack_pass, sudo_user=self.ssh_user, forks=self.forks, sudo="yes", ask_sudo_pass=False, verbosity=5, module_path=None, become=True, become_method="sudo", become_user="root", check=None, listhosts=None, listtasks=None, listtags=None, syntax=None ) # 初始化console輸出 self.callback = YunweiCallback() # 直接開始 self.run() def run(self): pb = None pb = YunweiPlaybookExecutor( playbooks = self.playbooks, inventory = self.inventory, variable_manager = self.variable_manager, loader = self.loader, options = self.options, passwords = self.passwords, stdout_callback = self.callback ) result = pb.run() # daemo if __name__ == "__main__": PlayBookJob(playbooks=["xx.yml"], host_list=["10.45.176.2"], ssh_user="root", project_name="test", forks=20, ext_vars=None )參考:
ansible-api官方文檔:http://docs.ansible.com/ansib...
一個非常好的ansible2.0 api調用文檔,相見恨晚:https://serversforhackers.com...
ansible源碼:https://github.com/ansible/an...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7989.html
摘要:寫在前面是一個非常棒的運維工具,可以遠程批量執行命令上傳文件等自動化運維操作,由于要搞配置管理,初始化等批量操作,而自己對相對熟悉因此選擇了。 寫在前面: ansible是一個非常棒的運維工具,可以遠程批量執行命令、上傳文件等自動化運維操作,由于要搞配置管理,初始化等批量操作,而自己對ansible相對熟悉,因此選擇了ansible playbook。不過在調用playbook api...
摘要:與最大的區別是無需在被控主機部署任何客戶端代理,默認直接通過通道進行遠程命令執行或下發配置相同點是都具備功能強大靈活的系統管理狀態配置,兩者都提供豐富的模板及,對云計算平臺大數據都有很好的支持。臨時禁止使用庫。強制更新的緩存。 概述 運維工具按需不需要有代理程序來劃分的話分兩類: agent(需要有代理工具):基于專用的agent程序完成管理功能,puppet, func, zabb...
摘要:與最大的區別是無需在被控主機部署任何客戶端代理,默認直接通過通道進行遠程命令執行或下發配置相同點是都具備功能強大靈活的系統管理狀態配置,兩者都提供豐富的模板及,對云計算平臺大數據都有很好的支持。臨時禁止使用庫。強制更新的緩存。 概述 運維工具按需不需要有代理程序來劃分的話分兩類: agent(需要有代理工具):基于專用的agent程序完成管理功能,puppet, func, zabb...
閱讀 2065·2021-10-11 10:59
閱讀 924·2021-09-23 11:21
閱讀 3541·2021-09-06 15:02
閱讀 1610·2021-08-19 10:25
閱讀 3364·2021-07-30 11:59
閱讀 2362·2019-08-30 11:27
閱讀 2574·2019-08-30 11:20
閱讀 2964·2019-08-29 13:15