阿里雲API
為了監控我們使用的一些阿里雲產品,需要些一些腳本,定時調用這些腳本來獲得相關阿里雲產品的信息。
■ 概述
調用阿里雲API大約分成兩類方法,一個是直接從HTTP協議開始,自己根據阿里雲的規則進行URL的編寫然后發起請求,獲得回應。這種方法比較繁瑣(阿里雲API的校驗挺復雜的,昨天寫了一晚上才成功寫出對的URL)。另一種方法是利用阿里雲提供的SDK(有多種語言的SDK,比如java,python,php等等),比如之前就有用過SDK來編寫腳本監控RDS。下面從這兩種方法來分別說明阿里雲API的使用方法。
貼出阿里雲官方API說明資料:
總體調用方式說明:【https://help.aliyun.com/document_detail/28616.html?spm=5176.doc51936.6.635.7Gl897】
資源監控接口說明:【https://help.aliyun.com/document_detail/51936.html?spm=5176.doc28616.6.637.6sm7b1】
預設監控項說明:【https://help.aliyun.com/document_detail/28619.html?spm=5176.doc51936.6.650.rpFTYc】
■ 用SDK調用API
其實對SDK的研究還沒有很深入,基本上是對之前的人留下來的腳本的一個觀摩分析,然后依葫蘆畫瓢。這里主要說明python的SDK。pythonSDK的使用手冊【https://help.aliyun.com/document_detail/28622.html?spm=5176.doc28619.6.653.EIfwUD】
其實官網上說得也很清楚了。。
安裝:
pip install aliyun-python-sdk-core pip install aliyun-python-sdk-cms
安裝完成之后,來看一下如何使用;
#!/usr/bin/env python #coding=utf-8 from aliyunsdkcore import client #客戶類,其實叫account感覺更加合理,因為它是維護accessKeyId和secretKeyId的對象 from aliyunsdkcms.request.v20170301 import QueryMetricListRequest #QueryMetricList是阿里雲方面規定的一個名字,意思是要獲取多條監控數據的請求 import json clt = client.AcsClient('access_key_id','secret_key_id','cn-hangzhou')
#構建客戶對象時需要一明一暗兩個KEY,然后第三個參數可以參見“調用方式”那個網頁上上面的各個物理區域的代碼,比如cn-hangzhou,cn-hongkong,us-west-1等等 request = QueryMetricListRequest.QueryMetricListRequest()
#===========================================================
#下面就是設置request中的各種參數,有可能沒有涵蓋所有的方法,不過也都大同小異,值得一提的是,可以看到所有參數都是字符串,即便是dimension本來是一個字典,
#period本來是一個數字,兩個time本來是時間對象,都要轉化成相關的字符串
#=========================================================== request.set_accept_format('JSON') request.set_Project('acs_slb_dashboard') request.set_Metric('ActiveConnection') request.set_StartTime('2017-08-09 11:00:00') request.set_EndTime('2017-08-09 11:10:00') request.set_Dimensions(str({'instanceId':'instance_id','port':'port'})) request.set_Period('60') res = json.loads(clt.do_action_with_exception(request)) #選擇with_exception的話出錯是以raise Exception的形式報出的,否則就是返回的res是錯誤碼和錯誤信息這樣子。 print json.dumps(res,indent=4)
如果你是第一次接觸阿里雲API,很可能會覺得一臉懵逼。主要是針對各個參數的意義還不明確,對各個參數意義及格式規定的解說放在下面,直接用HTTP請求來調用API的章節里面。
■ 通過HTTP請求調用API
其實用SDK當然也是通過HTTP請求來調用的API,這里的通過HTTP請求是指沒有任何包裝的,純粹用我們自己的代碼從0開始建立起一個調用API的URL然后發出請求。
首先,阿里雲似乎支持POST和GET兩種HTTP方法的調用方式,但是我沒有試過POST,現在官網上的說明文檔好像也已經放棄POST了,所以之后的說明都是基於GET方法的。說到GET方法,我們的印象就是域名后面一長串帶着各種百分號數字字母的URI(注意URL和URI的區別,URL是整串,URI是問號后面的一長串),那么阿里雲API調用時的那串URI包含了哪些參數就是關鍵。
這些參數基本可以分成三個部分:
● 公共參數 每次請求都要包括的部分
公共參數在“調用方式”那張網頁上有說明,包括
Format 要求返回的格式,默認是'xml',我們可以設置為'JSON'
Version 指當前API的版本,看官網要求,目前最新的是'2017-03-01'
AccessId 用戶賬號的明鑰ID
SignatureMethod 簽名使用的算法,目前是只有'HMAC-SHA1'一種選擇,至於什么是簽名下面會講的
Timestamp 時間戳,使用的格式是'%Y-%m-%dT%XZ'(帶T和Z的這個是ISO8601的標准時間格式),而且一定要用倫敦時區的時間,也就是說對於中國的使用者我們可以(( datatime.datetime.now - datetime.timedelta(hours=8)).strftime('%Y-%m-%dT%XZ'))
SignatureVersion 算法版本,目前寫'1.0'即可
SignatureNonce 為了防止重放攻擊設置的隨機數,在python中可以用uuid來生成這個隨機數: str(uuid.uuid1())
● 請求具體參數 跟這次請求想要獲得的信息相關,每次請求之間都需要不同的參數
就監控而言,請求具體參數的說明在“查詢監控數據”那張網頁上。
Action 請求動作,請求監控數據就寫QueryMetricList
Project 指明是監控什么產品,比如ECS就寫'acs_ecs_dashboard',如果是SLB就寫'acs_slb_dashboard',每個產品的這個字段信息可以在“預設監控項”的網頁中看到
Metric 監控項名稱,也是在“預設監控項”中查詢
Period 時間間隔,以秒數計,說明返回的監控數據,兩個時間點之間跨度多大。一般默認是60。
EndTime 指出查詢的監控數據截止至什么時間
StartTime 指出查詢的監控數據開始於什么時間,這兩個時間參數可以寫毫秒時間戳,也可以寫成'%Y-%m-%d %X'的形式,推薦后者,因為python自帶的時間戳生成time.time()是以秒為單位而非毫秒
Dimensions 用於過濾數據的k-v對對象,在python里的話可以str化一個字典來實現。Dimensions里面可以寫哪些字段可以在“預設監控項”中查詢,一般必然帶有一個'instanceId'字段
Length 用於查詢結果的分頁,設置了Length之后最多只返回一定條數的結果
Cursor 游標
● 簽名
Signature 簽名,其實嚴格來說,簽名是屬於公共參數的一部分,只不過簽名每次請求也是不同的,而且簽名的生成要依靠上面兩部分參數的信息。下面重點講一下如何生成簽名,感覺官網上講得不是很清楚。。
● 簽名生成方法
首先,簽名是一串字符串,用於提供給阿里雲后台校驗我們的信息是否正確,有沒有調出后台信息的權限。要生成這個簽名,首先要把上面兩個參數集合(在python中就是兩個字典了)除了整合到一起,然后按照key的順序對其排序,對排完序的每一對k-v對,分別將k和v都轉化成url形式(python的話可以調用urllib.quote方法)的字符串,然后用等號連接這兩個字符串得到一對k-v對的一個整體字符串。然后用'&'符號將各個k-v對的整體字符串按照之前排序(也就是k的順序)連接起來,獲得一個長的,包含了上面兩個字典中所有鍵值對的字符串。
拿到這個長字符串之后,再用urllib.quote方法對其轉義,此時可以看到比如冒號在結果的字符串中變成了%253A,這就是兩次轉義(: -> %3A -> %253A)的結果。根據官網上的說明我們對結果字符串還可以做一些轉義上出現誤差后的替換,比如replace('+','%20').replace('*', '%2A').replace('%7E', '~'),最終得到了一個更長的字符串。。
再在這個更長字符串的開頭加上'GET&%2F&',這里注意兩點,1.因為是GET方法所以這里寫GET 2.這里的&和%不用再轉義的,不要問為什么。。阿里雲就是這么規定的。至此,我們得到了所謂的StringToSign,即用於簽名的一個原料字符串。
接下來,要運用hmac-sha1的算法來對這個原料字符串進行散列取值,sha1算法還需要一個秘鑰,這時候就是用戶秘鑰(secret_key_id)派上用場的時候,把secret_key_id的末尾加上一個'&',然后把其作為秘鑰來進行計算,按照sha1的算法得到結果后再把它用base64的標准進行編碼,編碼完成之后去掉行尾的\n,就得到了最后的簽名了。
下面給出python的實現代碼:
string_to_sign = '' for k, v in sorted(unsign_param.iteritems(), key=lambda x: x[0]): string_to_sign += urllib.quote(k) + '=' + urllib.quote(v) + '&' string_to_sign = 'GET&%2F&' + urllib.quote(string_to_sign[:-1]).replace('+', '%20').replace('*', '%2A').replace('%7E', '~') # 要[:-1]的原因是因為生成string_to_sign的時候,最后必然會多出一個&,這個是不必要的。如果用的是'&'.join(xxx)這樣的方法就沒有這個煩惱了 import hashlib,hmac coder = hmac.new(key=secret, msg=string_to_sign, digestmod=hashlib.sha1) signature = coder.digest().encode('base64').strip() #記得encode出來行尾有換行符,要去掉
● 發出請求,接受回復
在做完簽名之后,可以往之前沒有Signature這個字段的公共參數字典里加上{'Signature':signature},然后整合公共參數字典和請求參數字典,得到了一個大字典,這個大字典就是我們在請求阿里雲API時用到的所有參數了。把這個參數按照GET方法要求的樣子整合到基本請求地址'http://metric.cn-hangzhou.aliyuncs.com/'(根據物理機房所在區域不同,基本地址也會有不同,參見“基本調用”那張網頁)上面去,然后發出請求就可以了。
回復的數據如果成功,則是像下面這樣的:
{ "Code": "200", "Success": true, "Period": "60", "RequestId": "8AE2C5AD-7CB3-4B5D-BE21-72AD4F762D8E", "Datapoints": [ { "instanceId": "instance_id", "timestamp": 1502263380000, "Average": 280, "userId": "xxxxxxxxxxxx", "Maximum": 280, "vip": "x.x.x.x", "Minimum": 280, "port": "443" }, { "instanceId": "instance_id", "timestamp": 1502263440000, "Average": 2024, "userId": "xxxxxxxxxxxx", "Maximum": 2024, "vip": "x.x.x.x", "Minimum": 2024, "port": "443" } ] }
//數據已做脫敏處理
可以看到Datapoints字段下面的列表,其中每一項都是一個時間點上的監控數值的信息。
如果回復失敗了,那么得到的可能會是下面這樣的:
{ "Code": "SignatureDoesNotMatch", "Message": "Specified signature is not matched with our calculation.", "HostId": "metrics.cn-hangzhou.aliyuncs.com", "RequestId": "B4086476-2299-45F5-AC99-00AC1769E4D5" }
■ 完整的python請求實現
下面是一個較為完整的實現,用python寫成,有些校驗什么的主要是考慮到我們自己的業務場景, 可以不用管
#!/usr/bin/env python # coding=utf-8 # @author:weiyz import sys import os import uuid import datetime import urllib import hmac try: import requests except ImportError,e: import urllib2 try: import json except ImportError,e: import simplejson as json # 配置文件指定 ACCOUNT_CONF_FILE = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'xxxx.ini') if not os.path.isfile(ACCOUNT_CONF_FILE): raise Exception('Configuration file {0} is not found'.format(ACCOUNT_CONF_FILE)) try: instance_id = sys.argv[1] search_key = sys.argv[2] account = sys.argv[3] port = sys.argv[4] search_type = sys.argv[5] except IndexError, e: raise Exception('Invalid arguments. Check there are four arguments for the script.') def get_key_from_account(): ''' 通過讀文件獲得相關賬號的AccessKeyId :return: ''' import ConfigParser cf = ConfigParser.ConfigParser() cf.read(ACCOUNT_CONF_FILE) try: return cf.get(account, 'id') , cf.get(account,'secret') except ConfigParser.NoSectionError: raise Exception('Account {0} is not configured in configuration.'.format(account)) def get_sign_param(): ''' 進行公共參數字典、請求參數字典的匯總,並計算簽名,最終把簽名也加入字典當中 最終形成的就是完整的請求參數字典,只要整合成URI就可以發起請求了 :return: ''' time_format = '%Y-%m-%dT%XZ' time_stamp = (datetime.datetime.now() - datetime.timedelta(hours=8)).strftime(time_format) access_key_id,secret = get_key_from_account() unsign_public_param = { 'Format': 'JSON', 'Version': '2017-03-01', 'AccessKeyId': access_key_id, 'SignatureMethod': 'HMAC-SHA1', 'Timestamp': time_stamp, # 時間戳格式一定是帶T和Z的那個 'SignatureVersion': '1.0', 'SignatureNonce': str(uuid.uuid1()) } secret += '&' req_param = { 'Action': 'QueryMetricList', 'Project': 'acs_slb_dashboard', 'Metric': search_key, 'Period': '60', 'EndTime': datetime.datetime.now().strftime('%Y-%m-%d %X'), 'StartTime': (datetime.datetime.now() - datetime.timedelta(minutes=8)).strftime('%Y-%m-%d %X'), 'Dimensions': str({'instanceId': instance_id, 'port': port}) # 另外注意Dimensions這個字典里面的key也是要按照順序排列的 } unsign_param = {} unsign_param.update(unsign_public_param) del (unsign_public_param) unsign_param.update(req_param) del (req_param) string_to_sign = '' for k, v in sorted(unsign_param.iteritems(), key=lambda x: x[0]): string_to_sign += urllib.quote(k) + '=' + urllib.quote(v) + '&' string_to_sign = 'GET&%2F&' + urllib.quote(string_to_sign[:-1]).replace('+', '%20').replace('*', '%2A').replace('%7E', '~') import hashlib coder = hmac.new(key=secret, msg=string_to_sign, digestmod=hashlib.sha1) signature = coder.digest().encode('base64').strip() unsign_param.update({'Signature': signature}) return unsign_param if __name__ == '__main__': sign_param = get_sign_param() base_url = 'http://metrics.cn-hangzhou.aliyuncs.com/' if 'requests' in locals(): res = requests.get(base_url, params=sign_param) result = json.loads(res.content) elif 'urllib2' in locals(): req = urllib2.Request(base_url+'?'+urllib.urlencode(sign_param)) res = urllib2.urlopen(req) result = res.read() json.loads(result) else: result = {'Message':'Sending Request Failed.'} if result.get('Code') != '200' or result.get('Success') != True: raise Exception('API Call Failed:[Error Message]{0}'.format(result.get('Message'))) def filter_result(result): for datapoint in result.get('Datapoints'): yield datapoint.get('Maximum'), datapoint.get('Average') maxlist, avglist = zip(*list(filter_result(result))) max_, avg = max(maxlist), max(avglist) if search_type == 'max': print max_ elif search_type == 'avg': print avg
■ 其他一些注意點
● 調用監控數據是有時滯的
不知道其他產品的情況怎么樣,今天我調用SLB的監控數據的話始終有7-8分鍾的時滯。也就是說保持調取當前時間前8分鍾之內的監控數據只能看到一條或兩條數據,6分鍾前開始那個節點到當前時間的監控數據是沒有的。問了下阿里雲的工作人員,發現這是他們內部的問題,不知道以后是否會修復。總之是這樣的。