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