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!!有结果了!搞定!!

 

posted @ 2016-12-06 13:56  evil_liu  阅读(6073)  评论(11编辑  收藏  举报