写在前面的话:
salt-api是一个基于Cherrypy(python的一个web框架)的Rest API程序。
注意:CherryPy版本3.2.5到3.7.x有一个已知的SSL追溯。请使用3.2.3版本或最新的10.x版本。
一、salt认证
依赖:
salt-api依赖的模块是Cherrypy,用于支持websockets的ws4py python模块(可选)
安装及配置:
salt-api 运行在Salt Master程序的机器上。
1. 安装salt-api,需要确保salt-api 与salt版本一致。
2. 安装Cherrypy,ws4py(可选)。
3. 生成自签名证书(可选)。建议使用安全的HTTPS连接,因为salt eauth 身份验证凭证将通过线路发送。
①.安装 pyOpenSSL 包。
②.使用create_self_signed_cert()
执行功能生成自签名证书。
salt-call --local tls.create_self_signed_cert
4. 编辑配置文件添加至少一个外部认证用户或组。详情这里。
5. salt-master配置文件添加如下配置来启用rest_cherrypy模块。
rest_cherrypy: port: 8000 ssl_crt: /etc/pki/tls/certs/localhost.crt ssl_key: /etc/pki/tls/certs/localhost.key
6. 重启salt-master 进程。
7. 重启salt-api 进程。
二、使用
开始使用之路吧。
首先是服务端认证,通过每个请求传递会话令牌来执行身份验证,token通过Login URL生成。
token认证采用两种方法发送:一种是hearder头添加认证token,另一种作为会话cookie。
用法:
请求主体必须是一组命令。使用此工作流程来构建命令:
1. 选择一个客户端界面。
2. 选择一个功能。
3.填写所选客户端所需的其余参数。
client字段是对Salt的python api中使用的主要python类的引用。
local:向本地发送命令的“本地”使用。等同于salt 命令。
runner:调用master的runner 模块。等同于salt-run命令。
wheel:调用master的wheel模块。wheel没有知己额的CLI命令,它通常广利Master-side资源,例如状态文件,支柱文件,salt配置文件,以及salt-key类似的功能。
在执行LocalClient,它需要将命令转发给Minions,所以需要tgt参数来指定minionid.
也需要arg(数组)和kwarg(之前)参数,这些值被发送到minions并用作请求函数的参数。
RunnerClient和WheelClient直接在Master上执行,因此不需要接受这些参数。
header头设置:
REST接口在接受什么样的数据格式以及它将返回什么格式(例如,JSON,YAML,urlencoded)方面是灵活的
通过包含Content-type头来指定请求正文中的数据格式。
使用Accept头指定相应主体所需的数据格式。
关于CherryPy的并发:
CherryPy服务器是一个生成就绪的线程HTTP服务器,用Python编写。它使用线程池来处理HTTP请求,所以不适合维护大量的并发同步连接,在配置默认设置的中等硬件上,他最高大约30到50个并发连接。
注意:每个salt的命令运行都会启动一个实例化的进程(LocalClient),它将自己的监听器实例化为salt事件总线,并发出自己的周期性salturil.find_job查询来确定Minion是否仍在运行该命令,不完全是一个轻量级操作。
超时:
CherryPy还可以设置HTTP超时时间。LocalClient和RunnerClient都可以在顶级关键字(timeout)中设置自己的超时参数。
异步操作:
由于性能开销和HTTP超时,长时间运行上述操作,可以使用local_asyn,runner_asyn,wheel_asyn进行异步方式运行更能节省开销。执行结果可以通过 /jobs/<jid> URL 从缓存中获取,也可以使用salt的Rerutner 系统收集到数据存储中。
/events URL专门用户处理长时间运行的HTTP请求,并包含了作业返回的salt事件总线,但该操作具有不同步性。
性能调整:
设置thread_pool和socket_queue_size 可以用来增加处理传入请求的rest_cherrypy的能力。设置这些配置时需要留意RAM的使用情况以及可用文件句柄。由于salt-api是基于salt使用,同时还需要考虑salt的性能。
下面福利时间:
下面的代码大概整合了一些经常使用的api,送给大家。
未完待续。。。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = '40kuai' 4 __version__ = 'v0.0.1' 5 """ 6 1. 整合 salt-api 功能 7 2. 获取token多次使用,发现token过期后重新获取token从当前失败任务重新继续执行 8 3. 可选:出现接口或服务异常(501),本次操作尝试几次重新执行 9 arg 为模块需要传入的参数,kwargs为pillar或grains参数。 10 11 分为以下几个类: 12 1. salt-api 方法类 13 2. 发送请求的类 14 """ 15 16 from urlparse import urljoin 17 import requests 18 19 20 class Salt_Api(): 21 def __init__(self, url, username, password): 22 self.url = url 23 self._username = username 24 self._password = password 25 self.get_token() 26 27 def get_token(self, eauth='pam', ): 28 """获取salt-api使用的token""" 29 get_token_url = urljoin(self.url, 'login') 30 json_data = {'username': self._username, 'password': self._password, 'eauth': eauth} 31 token_obj = requests.post(get_token_url, json=json_data, verify=False) 32 if token_obj.status_code != 200: 33 raise Exception(token_obj.status_code) 34 self.token = token_obj.json()['return'][0]['token'] 35 36 def post(self, prefix='/', json_data=None, headers=None): 37 post_url = urljoin(self.url, prefix) 38 if headers is None: 39 headers = {'X-Auth-Token': self.token, 'Accept': 'application/json'} 40 else: 41 headers = {'X-Auth-Token': self.token, }.update(headers) 42 post_requests = requests.post(post_url, json=json_data, headers=headers, verify=False) 43 return post_requests.json() 44 45 def get(self, prefix='/', json_data=None, headers=None): 46 post_url = urljoin(self.url, prefix) 47 if headers is None: 48 headers = {'X-Auth-Token': self.token, 'Accept': 'application/json'} 49 else: 50 headers = {'X-Auth-Token': self.token, }.update(headers) 51 get_requests = requests.get(post_url, json=json_data, headers=headers, verify=False) 52 return get_requests.json() 53 54 def get_all_key(self): 55 """获取所有minion的key""" 56 json_data = {'client': 'wheel', 'fun': 'key.list_all'} 57 content = self.post(json_data=json_data) 58 minions = content['return'][0]['data']['return']['minions'] 59 minions_pre = content['return'][0]['data']['return']['minions_pre'] 60 return minions, minions_pre 61 62 def accept_key(self, minion_id): 63 """认证minion_id,返回Ture or False""" 64 json_data = {'client': 'wheel', 'fun': 'key.accept', 'match': minion_id} 65 content = self.post(json_data=json_data) 66 return content['return'][0]['data']['success'] 67 68 def delete_key(self, node_name): 69 """删除minion_id,返回Ture or False""" 70 json_data = {'client': 'wheel', 'fun': 'key.delete', 'match': node_name} 71 content = self.post(json_data=json_data) 72 return content['return'][0]['data']['success'] 73 74 def host_remote_module(self, tgt, fun, arg=None): 75 """根据主机执行函数或模块,模块的参数为arg""" 76 json_data = {'client': 'local', 'tgt': tgt, 'fun': fun, } 77 if arg: 78 json_data.update({'arg': arg}) 79 content = self.post(json_data=json_data) 80 return content['return'] 81 82 def group_remote_module(self, tgt, fun, arg=None): 83 """根据分组执行函数或模块,模块的参数为arg""" 84 json_data = {'client': 'local', 'tgt': tgt, 'fun': fun, 'expr_form': 'nodegroup'} 85 if arg: 86 json_data.update({'arg': arg}) 87 content = self.post(json_data=json_data) 88 return content['return'] 89 90 def host_sls_async(self, tgt, arg): 91 '''主机异步sls ''' 92 json_data = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg} 93 content = self.post(json_data=json_data) 94 return content['return'] 95 96 def group_sls_async(self, tgt, arg): 97 '''分组异步sls ''' 98 json_data = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup'} 99 content = self.post(json_data=json_data) 100 return content['return'] 101 102 def server_hosts_pillar(self, tgt, arg, **kwargs): 103 '''针对主机执行sls and pillar ''' 104 print kwargs 105 kwargs = {'pillar': kwargs['kwargs']} 106 json_data = {"client": "local", "tgt": tgt, "fun": "state.sls", "arg": arg, "kwarg": kwargs} 107 content = self.post(json_data=json_data) 108 return content['return'] 109 110 def server_group_pillar(self, tgt, arg, **kwargs): 111 '''分组进行sls and pillar''' 112 kwargs = {'pillar': kwargs['kwargs']} 113 json_data = {'client': 'local', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup', 114 'kwarg': kwargs} 115 content = self.post(json_data=json_data) 116 return content['return'] 117 118 def jobs_all_list(self): 119 '''打印所有jid缓存''' 120 json_data = {"client": "runner", "fun": "jobs.list_jobs"} 121 content = self.post(json_data=json_data) 122 return content['return'] 123 124 def jobs_jid_status(self, jid): 125 '''查看jid运行状态''' 126 json_data = {"client": "runner", "fun": "jobs.lookup_jid", "jid": jid} 127 content = self.post(json_data=json_data) 128 return content['return'] 129 130 def keys_minion(self, hostname): 131 """Show the list of minion keys or detail on a specific key""" 132 content = self.get('keys/%s' % hostname) 133 return content 134 135 136 if __name__ == '__main__': 137 url = 'https://local:8000/' 138 obj = Salt_Api(url, 'username', 'password') 139 print obj.keys_minion('minionid')