总的构想:
通过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.py2 import salt.config3 master_ops=salt.config.client_config('/etc/salt/master')4 print(master_ops)
内容:
如上是获取saltmaster的配置信息,同样可以获取minion的配置信息。需要运行在minon端。唯一改变只是配置文件。
1 [root@localhost python]# cat python.py2 import salt.config3 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 interfaceused by the :command:`salt` CLI tool on the Salt Master4
5 ``LocalClient`` isused to send a command to Salt minions to execute6 :ref:`execution modules ` and returnthe results to the7 Salt Master.8
9 Importing and using ``LocalClient`` must be done on the same machine asthe10 Salt Master and it must be done using the same user that the Salt Master is
11 running as. (Unless :conf_master:`external_auth` isconfigured and12 authentication credentials are included inthe execution).13 该类主要是saltmaster端向minion端发送命令,接口。
14 .. code-block:: python15 例子:
16 import salt.client17
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 ifmopts:25 self.opts =mopts26 else:27 ifos.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_path32 )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_errors38 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 minions61 异步向客户端发送命令,并返回我们想要的信息比如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 empty65 dictionary on failure. The ``pub_data`` contains the job ID and a66 list of all minions that are expected to returndata.67
68 .. code-block:: python69 返回数据的格式。
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 message87 raise SaltClientError(88 'The salt master could not be contacted. Is master running?'
89 )90 except Exception asgeneral_exception:91 # Convert to generic client error and pass along mesasge92 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 ifmopts:5 self.opts =mopts6 else:7 ifos.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_path12 )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_errors18 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.client2 >>> cli=salt.clinet.LocalClient()3 >>> ret=cli.cmd('*','test.ping')4 >>>print ret5 {'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 minions13
14 The function signature is the same as:py:meth:`cmd` with the15 following exceptions.16
17 :returns: A job ID or 0on failure.18
19 .. code-block:: python20
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 inthe 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 minions14
15 Prep the job directory and publish a command to any targeted minions.16
17 :return: A dictionary of (validated) ``pub_data`` or an empty18 dictionary on failure. The ``pub_data`` contains the job ID and a19 list of all minions that are expected to returndata.20 给master端返回一个字典,里面包含一个jid和其他一些信息。
21 .. code-block:: python22
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 message40 raise SaltClientError(41 'The salt master could not be contacted. Is master running?'
42 )43 except Exception asgeneral_exception:44 # Convert to generic client error and pass along mesasge45 raise SaltClientError(general_exception)46
47 return self._check_pub_data(pub_data)
我看下如果我现在一个minion端挂掉,看看返回的结果是什么?
如果执行结果没有返回的时候,返回如下结果:
1 >>>import salt.client2 >>> cli=salt.client.LocalClient()3 >>> ret=cli.run_job('*','test.ping')4 >>>print ret5 {'jid': '20161211062720119556', 'minions': ['salt_minion']}
同样我可以去master端缓存的目录查看下:
查看相应的目录的,并没有执行结果返回:
我们把minion端启动起来:
1 >>>import salt.client2 >>> cli=salt.client.LocalClient()3 >>>
4 >>> ret=cli.run_job('*','test.ping')5 >>>print ret6 {'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: None2 mysql.ssl_cert: None3 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 utf83 DEFAULT COLLATE utf8_general_ci;4
5 USE `salt`;
创建表结构:
1 --
2 -- Table structure fortable `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 fortable `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 fortable `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.client2 >>> cli=salt.client.LocalClient()3 >>> ret=cli.run_job('*','test.ping')4 >>> ret=cli.run_job('*','cmd.run',['w'])5 >>>print ret6 {'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!!有结果了!搞定!!