阿里云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-corepip install aliyun-python-sdk-cms
安装完成之后,来看一下如何使用;
#!/usr/bin/env python#coding=utf-8from aliyunsdkcore import client #客户类,其实叫account感觉更加合理,因为它是维护accessKeyId和secretKeyId的对象from aliyunsdkcms.request.v20170301 import QueryMetricListRequest #QueryMetricList是阿里云方面规定的一个名字,意思是要获取多条监控数据的请求import jsonclt = 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:weiyzimport sysimport osimport uuidimport datetimeimport urllibimport hmactry: import requestsexcept ImportError,e: import urllib2try: import jsonexcept 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_paramif __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分钟前开始那个节点到当前时间的监控数据是没有的。问了下阿里云的工作人员,发现这是他们内部的问题,不知道以后是否会修复。总之是这样的。