總的構想:
通過saltstack的LocalClient的run_job異步執行任務方法,返回任務jid,並存於數據庫中,前端通過ajax異步請求數據庫,查看后台任務是否執行完成。
一:salts opts dictionary
有些客戶端需要訪問salts opts dictionary(一個字典包含master和minion的配置文件)
通常字典的信息匹配通過環境變量如果做了設置的話,如果沒有做設置,會讀取默認配置文件進行讀取。
語法結構:
1 salt.config.client_config(path,env_var='SALT_CLIENT_CONFIG';default=None)
1 # cat salt_pots.py 2 import salt.config 3 master_ops=salt.config.client_config('/etc/salt/master') 4 print(master_ops)
內容:
如上是獲取saltmaster的配置信息,同樣可以獲取minion的配置信息。需要運行在minon端。唯一改變只是配置文件。
1 [root@localhost python]# cat python.py 2 import salt.config 3 master_ops=salt.config.client_config('/etc/salt/minion') 4 print(master_ops)
以上獲取master或者minion端的配置信息。
二:Salt's Client Interfaces salt客戶端api。通過調用salt API 直接讓minion端執行命令。
首先我們看下類:LocalClient源碼:下面是主要方法,詳細的介紹請參考官網:https://docs.saltstack.com/en/latest/ref/clients/index.html
1 class LocalClient(object): 2 ''' 3 The interface used by the :command:`salt` CLI tool on the Salt Master 4 5 ``LocalClient`` is used to send a command to Salt minions to execute 6 :ref:`execution modules <all-salt.modules>` and return the results to the 7 Salt Master. 8 9 Importing and using ``LocalClient`` must be done on the same machine as the 10 Salt Master and it must be done using the same user that the Salt Master is 11 running as. (Unless :conf_master:`external_auth` is configured and 12 authentication credentials are included in the execution). 13 該類主要是saltmaster端向minion端發送命令,接口。 14 .. code-block:: python 15 例子: 16 import salt.client 17 18 local = salt.client.LocalClient() 19 local.cmd('*', 'test.fib', [10]) 20 ''' 21 def __init__(self, 22 c_path=os.path.join(syspaths.CONFIG_DIR, 'master'), 23 mopts=None, skip_perm_errors=False): 24 if mopts: 25 self.opts = mopts 26 else: 27 if os.path.isdir(c_path): 28 log.warning( 29 '{0} expects a file path not a directory path({1}) to ' 30 'it\'s \'c_path\' keyword argument'.format( 31 self.__class__.__name__, c_path 32 ) 33 ) 34 self.opts = salt.config.client_config(c_path) 35 self.serial = salt.payload.Serial(self.opts) 36 self.salt_user = salt.utils.get_specific_user() 37 self.skip_perm_errors = skip_perm_errors 38 self.key = self.__read_master_key() 39 self.event = salt.utils.event.get_event( 40 'master', 41 self.opts['sock_dir'], 42 self.opts['transport'], 43 opts=self.opts, 44 listen=not self.opts.get('__worker', False)) 45 self.functions = salt.loader.minion_mods(self.opts) 46 self.returners = salt.loader.returners(self.opts, self.functions) 47 48 def run_job( 49 self, 50 tgt, 51 fun, 52 arg=(), 53 expr_form='glob', 54 ret='', 55 timeout=None, 56 jid='', 57 kwarg=None, 58 **kwargs): 59 ''' 60 Asynchronously send a command to connected minions 61 異步向客戶端發送命令,並返回我們想要的信息比如jid。 62 Prep the job directory and publish a command to any targeted minions. 63 64 :return: A dictionary of (validated) ``pub_data`` or an empty 65 dictionary on failure. The ``pub_data`` contains the job ID and a 66 list of all minions that are expected to return data. 67 68 .. code-block:: python 69 返回數據的格式。 70 >>> local.run_job('*', 'test.sleep', [300]) 71 {'jid': '20131219215650131543', 'minions': ['jerry']} 72 ''' 73 arg = salt.utils.args.condition_input(arg, kwarg) 74 75 try: 76 pub_data = self.pub( 77 tgt, 78 fun, 79 arg, 80 expr_form, 81 ret, 82 jid=jid, 83 timeout=self._get_timeout(timeout), 84 **kwargs) 85 except SaltClientError: 86 # Re-raise error with specific message 87 raise SaltClientError( 88 'The salt master could not be contacted. Is master running?' 89 ) 90 except Exception as general_exception: 91 # Convert to generic client error and pass along mesasge 92 raise SaltClientError(general_exception) 93 94 return self._check_pub_data(pub_data)
salt源碼做的真心不錯,他在介紹類和方法的同事給咱們提供相應的例子,如上,我們可以調用相應的方法在salt-minion端進行執行。主要接口的執行是在master端(如果你提供了相應的master數據,可以不在master端執行,如上的一內容的master-ops的字典形式內容),因為我們在創建對象的時候,進行如下配置的讀取:
1 def __init__(self, 2 c_path=os.path.join(syspaths.CONFIG_DIR, 'master'), 3 mopts=None, skip_perm_errors=False): 4 if mopts: 5 self.opts = mopts 6 else: 7 if os.path.isdir(c_path): 8 log.warning( 9 '{0} expects a file path not a directory path({1}) to ' 10 'it\'s \'c_path\' keyword argument'.format( 11 self.__class__.__name__, c_path 12 ) 13 ) 14 self.opts = salt.config.client_config(c_path) 15 self.serial = salt.payload.Serial(self.opts) 16 self.salt_user = salt.utils.get_specific_user() 17 self.skip_perm_errors = skip_perm_errors 18 self.key = self.__read_master_key() 19 self.event = salt.utils.event.get_event( 20 'master', 21 self.opts['sock_dir'], 22 self.opts['transport'], 23 opts=self.opts, 24 listen=not self.opts.get('__worker', False)) 25 self.functions = salt.loader.minion_mods(self.opts) 26 self.returners = salt.loader.returners(self.opts, self.functions)
我們實現一個簡單的模塊命令執行:
1 >>> import salt.client 2 >>> cli=salt.clinet.LocalClient() 3 >>> ret=cli.cmd('*','test.ping') 4 >>> print ret 5 {'salt_minion': True}
返回命令的執行結果。注意:cmd執行不異步執行,他會等所有的minion端執行完之后,才把所有的結果返回回來。這個也可以設置超時時間。這個超時間不是執行任務超時時間,官方解釋如下:
:param timeout: Seconds to wait after the last minion returns but before all minions return. 等待最后一個minion端返回結果的超時時間在所有數據返回之前。
也可以直接給模塊傳入參數,
1 >>> cli.cmd('*','cmd.run',['whoami']) 2 {'salt_minion': 'root'}
我們也可以執行多個模塊命令,傳入多個參數,其中需要注意的是:如果有的模塊不需要參數,但是需要傳入空的列表,否則報錯。
1 >>> cli.cmd('*',['cmd.run','test.ping'],[['whoami'],[]]) 2 {'salt_minion': {'test.ping': True, 'cmd.run': 'root'}}
需要注意的是:參數是一個列表套列表。而模塊是一個列表中,以字符形式傳入函數種。
該類還提供一個cmd_async,命令以異步的方式進行執行。如下:
1 def cmd_async( 2 self, 3 tgt, 4 fun, 5 arg=(), 6 expr_form='glob', 7 ret='', 8 jid='', 9 kwarg=None, 10 **kwargs): 11 ''' 12 Asynchronously send a command to connected minions 13 14 The function signature is the same as :py:meth:`cmd` with the 15 following exceptions. 16 17 :returns: A job ID or 0 on failure. 18 19 .. code-block:: python 20 21 >>> local.cmd_async('*', 'test.sleep', [300]) 22 '20131219215921857715' 23 ''' 24 arg = salt.utils.args.condition_input(arg, kwarg) 25 pub_data = self.run_job(tgt, 26 fun, 27 arg, 28 expr_form, 29 ret, 30 jid=jid, 31 **kwargs) 32 try: 33 return pub_data['jid'] 34 except KeyError: 35 return 0
但是如果在函數返回的時候,如果pub_data中即返回的數據中不包含key值為'jid'的將不會返回jid值。直接返回0.jid這個值比較重要,一會我們會說為什么要這個值。
1 >>> cli.cmd_async('*',['cmd.run','test.ping'],[['whoami'],[]]) 2 '20161211061247439244'
然后我們根據master的配置文件中,找到:cachedir: /var/cache/salt/master從該配置文件讀取我們這個任務的執行情況:
可以查找對應的任務返回情況。/var/cache/salt/master是minion返回任務執行結果的目錄,一般緩存24小時:
1 # Set the number of hours to keep old job information in the job cache: 2 #keep_jobs: 24
這個是默認值,我們可以進行修改。
然后我接下來研究LocalClient()的run_job的方法:
1 def run_job( 2 self, 3 tgt, 4 fun, 5 arg=(), 6 expr_form='glob', 7 ret='', 8 timeout=None, 9 jid='', 10 kwarg=None, 11 **kwargs): 12 ''' 13 Asynchronously send a command to connected minions 14 15 Prep the job directory and publish a command to any targeted minions. 16 17 :return: A dictionary of (validated) ``pub_data`` or an empty 18 dictionary on failure. The ``pub_data`` contains the job ID and a 19 list of all minions that are expected to return data. 20 給master端返回一個字典,里面包含一個jid和其他一些信息。 21 .. code-block:: python 22 23 >>> local.run_job('*', 'test.sleep', [300]) 24 {'jid': '20131219215650131543', 'minions': ['jerry']} 25 ''' 26 arg = salt.utils.args.condition_input(arg, kwarg) 27 28 try: 29 pub_data = self.pub( 30 tgt, 31 fun, 32 arg, 33 expr_form, 34 ret, 35 jid=jid, 36 timeout=self._get_timeout(timeout), 37 **kwargs) 38 except SaltClientError: 39 # Re-raise error with specific message 40 raise SaltClientError( 41 'The salt master could not be contacted. Is master running?' 42 ) 43 except Exception as general_exception: 44 # Convert to generic client error and pass along mesasge 45 raise SaltClientError(general_exception) 46 47 return self._check_pub_data(pub_data)
我看下如果我現在一個minion端掛掉,看看返回的結果是什么?
如果執行結果沒有返回的時候,返回如下結果:
1 >>> import salt.client 2 >>> cli=salt.client.LocalClient() 3 >>> ret=cli.run_job('*','test.ping') 4 >>> print ret 5 {'jid': '20161211062720119556', 'minions': ['salt_minion']}
同樣我可以去master端緩存的目錄查看下:
查看相應的目錄的,並沒有執行結果返回:
我們把minion端啟動起來:
1 >>> import salt.client 2 >>> cli=salt.client.LocalClient() 3 >>> 4 >>> ret=cli.run_job('*','test.ping') 5 >>> print ret 6 {'jid': '20161211063245061096', 'minions': ['salt_minion']}
有結果的返回的:
重點:
為什么我們一直強調這個jid,在saltstack中這個jid做為一個任務的唯一標識,所以當我們給大量的minion端推送數據的時候,需要關注以下問題:
1、任務執行需要異步非阻塞執行。
2、如果異步之后,我們怎么去master設置的job緩存中去找我們想要的任務內容呢?
3、所以這個時候jid顯得格外重要,所以我們上面篇幅討論那么多,尤其原因就是在找異步、jid的方法。
既然方法找到了,那下一步是將我們minion端返回的執行結果捕獲,在saltstack默認情況下,是將job任務緩存到我們的本地:/var/cache/salt/master中jobs。
但是這個方案我們不想使用,那么接下來就是將執行結果返回給到數據庫。
但是返回數據方式有如下2種形式:
(一):Master Job Cache - Master-Side Returner
如上圖(一):表示master發送的命令給minion端,然后minion端將結果返回給db、redis、等。
這種方式,只需要在master端進行簡單配置即可,minion端不需要進行什么配置。
(二):External Job Cache - Minion-Side Returner
如圖(2),是master端,將命令發送給minion端,然后由minion端將數據分別寫入redis、mysql等。
這種方式,需要在minion端安裝相應的模塊(python-mysqldb),還需要配置相應的db數據庫信息(賬號、密碼等),配置較為麻煩,最主要是不安全。
綜上所述:我們采用一的模式:Master Job Cache - Master-Side Returner
master配置文件需要添加如下:
1 mysql.host: 'salt' 2 mysql.user: 'salt' 3 mysql.pass: 'salt' 4 mysql.db: 'salt' 5 mysql.port: 3306
需要注意的是:salt需要能被解析在master端
修改master端job緩存:修改配置文件:/etc/salt/master
1 master_job_cache: mysql
如果在傳輸過程不需要證書認證的話需要往:/etc/salt/master 加入如下配置:
1 mysql.ssl_ca: None 2 mysql.ssl_cert: None 3 mysql.ssl_key: None
修改完如上配置重啟salt-master:
1 /etc/init.d/salt-master restart
創建數據庫:
1 yum install -y mysql-server.x86_64
創建相應的數據庫:
1 CREATE DATABASE `salt` 2 DEFAULT CHARACTER SET utf8 3 DEFAULT COLLATE utf8_general_ci; 4 5 USE `salt`;
創建表結構:
1 -- 2 -- Table structure for table `jids` 3 -- 4 5 DROP TABLE IF EXISTS `jids`; 6 CREATE TABLE `jids` ( 7 `jid` varchar(255) NOT NULL, 8 `load` mediumtext NOT NULL, 9 UNIQUE KEY `jid` (`jid`) 10 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 11 CREATE INDEX jid ON jids(jid) USING BTREE; 12 13 -- 14 -- Table structure for table `salt_returns` 15 -- 16 17 DROP TABLE IF EXISTS `salt_returns`; 18 CREATE TABLE `salt_returns` ( 19 `fun` varchar(50) NOT NULL, 20 `jid` varchar(255) NOT NULL, 21 `return` mediumtext NOT NULL, 22 `id` varchar(255) NOT NULL, 23 `success` varchar(10) NOT NULL, 24 `full_ret` mediumtext NOT NULL, 25 `alter_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 26 KEY `id` (`id`), 27 KEY `jid` (`jid`), 28 KEY `fun` (`fun`) 29 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 30 31 -- 32 -- Table structure for table `salt_events` 33 -- 34 35 DROP TABLE IF EXISTS `salt_events`; 36 CREATE TABLE `salt_events` ( 37 `id` BIGINT NOT NULL AUTO_INCREMENT, 38 `tag` varchar(255) NOT NULL, 39 `data` mediumtext NOT NULL, 40 `alter_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 41 `master_id` varchar(255) NOT NULL, 42 PRIMARY KEY (`id`), 43 KEY `tag` (`tag`) 44 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
給salt用戶授權訪問:
1 grant all on salt.* to salt@'%' identified by 'salt';
注意:%不包括localhost。
由於master和mysql數據在同一台服務器:所以需要授權給localhost。
1 grant all on salt.* to salt@'localhost' identified by 'salt';
另外master端需要安裝連接mysql的API。注意是:MySQL-python.x86_64 。注意名字!!在python2版本中。
1 yum install -y MySQL-python.x86_64
然后我們做個測試:
1 >>> import salt.client 2 >>> cli=salt.client.LocalClient() 3 >>> ret=cli.run_job('*','test.ping') 4 >>> ret=cli.run_job('*','cmd.run',['w']) 5 >>> print ret 6 {'jid': '20161211175654153286', 'minions': ['salt_minion']}
然后我查看數據庫:
1 mysql> select full_ret from salt_returns where jid='20161211175654153286'; 2 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 3 | full_ret | 4 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 5 | {"fun_args": ["w"], "jid": "20161211175654153286", "return": " 17:56:54 up 14:24, 2 users, load average: 0.20, 0.13, 0.11\nUSER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT\nroot tty1 - Thu01 2:06m 0.03s 0.03s -bash\nroot pts/0 192.168.217.1 15:50 2:05m 0.01s 0.01s -bash", "retcode": 0, "success": true, "cmd": "_return", "_stamp": "2016-12-12T01:56:54.256049", "fun": "cmd.run", "id": "salt_minion"} | 6 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 7 1 row in set (0.00 sec)
cheers!!有結果了!搞定!!