saltstack python API(二)


總的構想:

        通過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!!有結果了!搞定!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM