読者です 読者をやめる 読者になる 読者になる

AWS Lambdaを使ってmackerelにEC2のSpotPriceを投げ込む

こんにちは。g0eです。

昨日、弊社のコーポレートサイトをリニューアルしました。 なかなかかっこよく仕上がったんじゃないかと満足している一方で、随分と更新をサボっていたこのブログにも、きちんとしたリンクが貼られてしまったので、とりあえず何か書いておこうと思います。

参考までにコーポレートサイト corp.intimatemerger.com

背景

弊社ではAWS上にスポットインスタンスをうまく活用して、elasticsearchのクラスタを構築しています。スポットインスタンスは価格をかなり抑えられる一方で、時々価格が高騰してインスタンスが強制ターミネートされてちょっとドキドキします。(昨年の11月下旬〜12月上旬は特に頻度が高かったような)

冗長化して片方のAZが全滅してもデータ欠損は発生しない構成にしたり、スポットインスタンスで十分な数が確保できない場合は、オンデマンドインスタンスが自動的に起動するようにしたり、いくつか対応策はとっているので実害はほぼなかったりするのですが(ここの話はまた機会があれば改めて書きたいなとは思っています)、まずはスポットインスタンスの価格をきちんとモニタリングして傾向をつかんで行こうというのが今回の投稿の背景だったりします。

目的

前置きが少し長くなってしまいましたが、本題としては、弊社で監視に使っているmackerelにEC2のスポットインスタンスの価格推移を投げ込んで、アラートをあげたり、中長期のトレンドを可視化していきたいという話になります。(mackerel→slackにアラートを投げてPC/スマホから監視していますが、チャートも一緒に表示されるのでなかなか便利です。)

実装

早速ですが、lambdaのコード貼っておきます。言語はpythonです。

コード書いたのが1ヶ月以上前なので若干記憶が曖昧なのですが、スポット価格を取得するAPIの仕様がちょっと特殊で、

  • 指定した期間で価格が動いた時のデータ
  • 価格変動がなければ最後に価格が動いた時のデータ

が返ってきていたと記憶しています。

コードではちょっとトリッキーですが、未来の時間を開始時間=終了時間で指定することで、APIをコールした時点で最新の価格だけを取得しています。(気持ち悪いですが、試行錯誤した中ではこれが一番良かった)

import boto3
import datetime, time, json, urllib2
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

MACKEREL_APIKEY = "XXXXXXXXXXXXXXXXXXXX" # mackerelのAPIキー
MACKEREL_SERVICE = "xxxxxxxxx" # mackerelのサービスを指定
EC2_INSTANCE_TYPES = ["m4.4xlarge","c3.2xlarge"] # 価格を調べたいインスタンスを指定
EC2_PRODUCT_DESCRIPTIONS = ["Linux/UNIX (Amazon VPC)"]

def post_mackerel (apikey, service, payload):
    headers = { 'Content-type': 'application/json', 'X-Api-Key': apikey }
    url = "https://mackerel.io/api/v0/services/{0}/tsdb".format(service)
    data = json.dumps(payload)
    req = urllib2.Request(url, data, headers)
    res = urllib2.urlopen(req)
    return res

def get_payload():
    cl = boto3.client("ec2",region_name="ap-northeast-1")
    t = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    res = cl.describe_spot_price_history( \
        StartTime=t,
        EndTime=t,
        ProductDescriptions=EC2_PRODUCT_DESCRIPTIONS,
        InstanceTypes=EC2_INSTANCE_TYPES,
    )
    payload = []
    now = int(time.time())
    for r in res["SpotPriceHistory"]:
        if r["AvailabilityZone"] == "ap-northeast-1a":
            az = "a"
        elif r["AvailabilityZone"] == "ap-northeast-1c":
            az = "c"
        else:
            raise Exception("Invalid AvailabilityZone")
        payload.append({ \
            "time": now,
            "name": "SpotPrice."+r["InstanceType"].replace(".","-")+"_"+az,
            "value": float(r["SpotPrice"]),
        })
    logger.info("payload length ... "+str(len(payload)))
    return payload

def lambda_handler (event, context):
    payload = get_payload()
    post_mackerel(MACKEREL_APIKEY, MACKEREL_SERVICE, payload)
    logger.info("done!!")

lambdaは、最近追加されたスケジュール実行の機能を使って、↓みたいな感じで5分毎に実行しています。 f:id:intimatemerger:20160116021742p:plain

結果mackerel上のサービスメトリックスに溜まったデータが↓みたいな感じになります。一回、スパイクしていますね…。 f:id:intimatemerger:20160116021901p:plain

今回の投稿は以上になります。ほぼ既存のコード貼っただけですね…。

はやく、LambdaがVPC対応しないかなぁ…。