寫在前面的話:
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')