開發與維運

CDN – API 類型問題排查

作者:張醫博

淺談

在調用阿里雲 CDN openAPI,經常會出現各種各樣的問題排查起來沒有好的思路不好分析,今天說下基本的排查思路。

分析

不管調什麼 CDN openAPI,無論是控制檯還是 SDK 或者用戶的腳本,出現問題都可以按照如下思路排查。

蒐集信息

1) 調用 CDN openAPI 一定會返回一個阿里雲的 requestID ,這個值是用來排查用戶完整的請求參數和返回結果的憑證,如果客戶端調用 API 返回結果不是預期或者請求失敗,都會返回,務必保留。

2)如果沒有 requestID 返回的情況下,需要用戶提供

  • 調用 openAPI 的名稱;
  • 返回的異常結果全部截圖;
  • 調用的方式是 API 還是 SDK;
  • 什麼語言版本的。

問題分析

獲取 requestID 的情況。

1)先查 雲臺,requestID 的請求過程記錄;
一站式查詢-》 ECS 全鏈路日誌 -〉 輸入 requestID -》調整日誌 查詢。
關注下列的信息,查看詳細的返回錯誤內容,以及錯誤信息、API 名稱;

image.png

2)搜索 API.文檔
搜索對應的 API ,然後查看 API 請求是否有特殊說明,舉例說明
類似如下是 API 調用文檔,我們先要明白 API 是做什麼的?然後瞭解使用上的限制?以及調用傳參的限制?

image.png

對比用戶的參數使用是否都是正確的,如果確保都是正確再向下排查。

3)調用測試

當文檔和 requestID 都查不出來後,我們只能調用 openAPI 親自測試一遍,這裡提供了下載的測試腳本,可以參考。測試是請使用客戶端的 AccesskeyID 和 SecretKey 測試,這樣貼近用戶環境測試結果更有效。

  • 兩個腳本放在一個目錄下運行

aliyun.ini 腳本

[Credentials]
accesskeyid = <ak>
accesskeysecret = <sk>

can.py 生成鑑權 URL 腳本

#!/usr/bin/python
# -*- coding:utf-8 -*-

import sys,os
import urllib, urllib2
import base64
import hmac
import hashlib
from hashlib import sha1
import time
import uuid
import json
from optparse import OptionParser
import ConfigParser
import traceback

access_key_id = '';
access_key_secret = '';
cdn_server_address = 'https://cdn.aliyuncs.com'
CONFIGFILE = os.getcwd() + '/aliyun.ini'
CONFIGSECTION = 'Credentials'
cmdlist = '''
接口說明請參照pdf文檔
'''

def percent_encode(str):
    res = urllib.quote(str.decode(sys.stdin.encoding).encode('utf8'), '')
    res = res.replace('+', '%20')
    res = res.replace('*', '%2A')
    res = res.replace('%7E', '~')
    return res

def compute_signature(parameters, access_key_secret):
    sortedParameters = sorted(parameters.items(), key=lambda parameters: parameters[0])

    canonicalizedQueryString = ''
    for (k,v) in sortedParameters:
        canonicalizedQueryString += '&' + percent_encode(k) + '=' + percent_encode(v)

    stringToSign = 'GET&%2F&' + percent_encode(canonicalizedQueryString[1:])

    h = hmac.new(access_key_secret + "&", stringToSign, sha1)
    signature = base64.encodestring(h.digest()).strip()
    return signature

def compose_url(user_params):
    timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())

    parameters = { \
            'Format'        : 'JSON', \
            'Version'       : '2014-11-11', \
            'AccessKeyId'   : access_key_id, \
            'SignatureVersion'  : '1.0', \
            'SignatureMethod'   : 'HMAC-SHA1', \
            'SignatureNonce'    : str(uuid.uuid1()), \
            'TimeStamp'         : timestamp, \
    }

    for key in user_params.keys():
        parameters[key] = user_params[key]

    signature = compute_signature(parameters, access_key_secret)
    parameters['Signature'] = signature
    url = cdn_server_address + "/?" + urllib.urlencode(parameters)
    return url

def make_request(user_params, quiet=False):
    url = compose_url(user_params)
    print url
def configure_accesskeypair(args, options):
    if options.accesskeyid is None or options.accesskeysecret is None:
        print("config miss parameters, use --id=[accesskeyid] --secret=[accesskeysecret]")
        sys.exit(1)
    config = ConfigParser.RawConfigParser()
    config.add_section(CONFIGSECTION)
    config.set(CONFIGSECTION, 'accesskeyid', options.accesskeyid)
    config.set(CONFIGSECTION, 'accesskeysecret', options.accesskeysecret)
    cfgfile = open(CONFIGFILE, 'w+')
    config.write(cfgfile)
    cfgfile.close()

def setup_credentials():
    config = ConfigParser.ConfigParser()
    try:
        config.read(CONFIGFILE)
        global access_key_id
        global access_key_secret
        access_key_id = config.get(CONFIGSECTION, 'accesskeyid')
        access_key_secret = config.get(CONFIGSECTION, 'accesskeysecret')
    except Exception, e:
        print traceback.format_exc()
        print("can't get access key pair, use config --id=[accesskeyid] --secret=[accesskeysecret] to setup")
        sys.exit(1)



if __name__ == '__main__':
    parser = OptionParser("%s Action=action Param1=Value1 Param2=Value2\n" % sys.argv[0])
    parser.add_option("-i", "--id", dest="accesskeyid", help="specify access key id")
    parser.add_option("-s", "--secret", dest="accesskeysecret", help="specify access key secret")
    
    (options, args) = parser.parse_args()
    if len(args) < 1:
        parser.print_help()
        sys.exit(0)

    if args[0] == 'help':
        print cmdlist
        sys.exit(0)
    if args[0] != 'config':
        setup_credentials()
    else: #it's a configure id/secret command
        configure_accesskeypair(args, options)
        sys.exit(0)

    user_params = {}
    idx = 1
    if not sys.argv[1].lower().startswith('action='):
        user_params['action'] = sys.argv[1]
        idx = 2

    for arg in sys.argv[idx:]:
        try:
            key, value = arg.split('=')
            user_params[key.strip()] = value
        except ValueError, e:
            print(e.read().strip())
            raise SystemExit(e)
    make_request(user_params)

4) 測試分析
如果發現按照 API 文檔,生成的 API 調用 URL 沒有問題,說明用戶使用的 API 代碼可能存在問題,或者傳的參數和我們不一樣。可以用生成的正常 URL 和異常的 URL 進行比對。

案例:

案例

有用戶問控制檯的各項指標什麼含義,峰值 QPS 是哪個,總的 QPS 是哪個? HTTPS 的帶寬和 HTTP 帶寬怎麼區分,等等。
或者控制體臺報錯的問題,訪問控制異常問題,所有此類問題都可以按照如下排查;

image.png

分析

1) 首先瀏覽器打開 F12。開發者模式。

2) 找到你要瀏覽的頁面或者你點擊的功能模塊,觸發調用。

3)然後分析你觸發調用請求的 URL 是什麼,大致都可看出來的,並不複雜。比如我是查域名的流量,那就是 DescribeDomainBpsData.json 接口,也就是 CDN 的 openAPI ,因為即便是控制檯也是調用後端 API 操作的。

4)然後看瀏覽器對應的響應頭信息,將這些頭和 API 對應的文檔 返回參數說明對比就知道什麼含義了。

image.png

案例

某用戶反饋調用直播導播臺的 DescribeCasters 功能,並沒有返回 CasterTemplate 字段。

分析

首先找到這個 API 的解釋說明
https://help.aliyun.com/document_detail/60261.html?spm=5176.10695662.1996646101.searchclickresult.1f352a00rEBEV7

image.png

通過這個 API 告訴了用戶兩個條件:

1)導播臺分辨率配置,付費類型為預付費時必輸。那也就是非預付費的不一定會輸出。

2)用戶是否在直播的播流域名上配置了,轉碼如果沒有配置轉碼的話,一樣不會輸出 CasterTemplate 字段;

Leave a Reply

Your email address will not be published. Required fields are marked *