msawady’s engineering-note

なにも分からないエンジニアです。

【AWS】【Python】EC2インスタンスをCPU使用率の高い順に表示するLambdaスクリプト

EC2インスタンスをCPU使用率が高い順に表示する

  • 性能テストなどを行っていて、ボトルネックとなっているインスタンスを探したい
  • cloudwatchの結果をクエリして「当該時間帯にCPU使用率が高かったインスタンス」を探す
  • tabulateを利用して良い感じに表示する

IAMロール、ポリシー

以下の2つのbuilt-inポリシーをアタッチしたロールを作成し、Lambdaにアタッチします。

  • AWSLambdaExecute
  • AmazonEC2ReadOnlyAccess

tabulate を使えるようにする

表示にはtabulateを使いたいのですが、Lambdaではpipを利用することが出来ません。以下の記事に従って、tabulateを利用できるようにします。

【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita

テストイベントの設定

引数には調査対象の環境(env)と、時間帯(timebox)を渡します。timeboxにはISOフォーマットを利用し、timezoneを設定することも可能です。以下のサンプルではJST(+09:00)でtimeboxを指定しています。

{
  "env": "test1",
  "timebox":{
      "start": "2018-02-23T12:30:00+09:00",
      "end":"2018-02-23T13:00:00+09:00"
  }
}

コード

Python 3.6 で書きます。

import datetime

import boto3
import dateutil.parser as parser
from tabulate import tabulate


def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:env',
                'Values': [event.get('env')]
            }
        ]
    )
    instances = []
    for r in full_info['Reservations']:
        for i in r['Instances']:
            instances.append(i)

    cw_client = boto3.client('cloudwatch')
    timebox = event.get('timebox', {})
    if not timebox:
        now = datetime.datetime.utcnow()
        timebox['start'] = now - datetime.timedelta(seconds=600)
        timebox['end'] = now
    else:
        timebox['start'] = parser.parse(timebox['start'])
        timebox['end'] = parser.parse(timebox['end'])

    ret = []
    for ins in instances:
        metrics = cw_client.get_metric_statistics(
            Namespace='AWS/EC2',
            MetricName='CPUUtilization',
            Dimensions=[{'Name': 'InstanceId', 'Value': ins['InstanceId']}],
            StartTime=timebox['start'],
            EndTime=timebox['end'],
            Period=60,
            Statistics=['Average']
        )
        # get latest data
        latest = None
        for d in metrics['Datapoints']:
            if not latest:
                latest = d
            else:
                if latest['Timestamp'] < d['Timestamp']:
                    latest = d
        if not latest:
            continue
        ret.append({
            'name': [t['Value'] for t in ins['Tags'] if t['Key'] == 'Name'][0],
            'instance_id': ins.get('InstanceId', ''),
            'private_ip': ins.get('PrivateIpAddress', ''),
            'CPU_usage': latest['Average'],
            'timestamp': latest['Timestamp'].isoformat()
        })
    ret.sort(key=lambda d: d['CPU_usage'], reverse=True)

    print(tabulate(ret, headers="keys"))
    return ret

補足と解説

dateutil.parser を利用してISOフォーマットの時刻文字列をdatetimeに変換できます。

    timebox = event.get('timebox', {})
    if not timebox:
        now = datetime.datetime.utcnow()
        timebox['start'] = now - datetime.timedelta(seconds=600)
        timebox['end'] = now
    else:
        timebox['start'] = parser.parse(timebox['start'])
        timebox['end'] = parser.parse(timebox['end'])

Dimensions で InstanceId 、StartTimeEndTimeで時間帯を指定してcloudwatch のデータをクエリします。

    for ins in instances:
        metrics = cw_client.get_metric_statistics(
            Namespace='AWS/EC2',
            MetricName='CPUUtilization',
            Dimensions=[{'Name': 'InstanceId', 'Value': ins['InstanceId']}],
            StartTime=timebox['start'],
            EndTime=timebox['end'],
            Period=60,
            Statistics=['Average']
        )

CPU使用率が高い順にデータをソートして、tabulate で綺麗にprintします。headerにはデータのkeyを指定します。

    ret.sort(key=lambda d: d['CPU_usage'], reverse=True)

    print(tabulate(ret, headers="keys"))

出力結果

tabulateを利用することで、綺麗に表示されます。(サーバー名、instance_idなどはマスクしました)

name       instance_id          private_ip      CPU_usage  timestamp
---------  -------------------  ------------  -----------  -------------------------
server1    i-1a1a1a1a1a1a1a1a1  111.1.1.11      13.234     2018-02-23T04:28:00+00:00
server2    i-2b2b2b2b2b2b2b2b2  111.1.2.22       7.854     2018-02-23T04:27:00+00:00
server3    i-3c3c3c3c3c3c3c3c3  111.1.3.33       5.342     2018-02-23T04:28:00+00:00
server4    i-4d4d4d4d4d4d4d4d4  111.1.4.44       3.47458   2018-02-23T04:29:00+00:00
server5    i-5e5e5e5e5e5e5e5e5  111.1.5.55       1.208     2018-02-23T04:28:00+00:00
server6    i-6f6f6f6f6f6f6f6f6  111.1.6.66       0.95      2018-02-23T04:28:00+00:00
server7    i-7g7g7g7g7g7g7g7g7  111.1.7.77       0.758     2018-02-23T04:27:00+00:00
server8    i-8h8h8h8h8h8h8h8h8  111.1.8.88       0.738     2018-02-23T04:27:00+00:00
server9    i-9i9i9i9i9i9i9i9i9  111.1.9.99       0.730958  2018-02-23T04:29:00+00:00
server10   i-10j10j10j10j10j10  111.1.10.110     0.6       2018-02-23T04:29:00+00:00
server11   i-11k11k11k11k11k11  111.1.11.121     0.4       2018-02-23T04:29:00+00:00
server12   i-12l12l12l12l12l12  111.1.12.132     0.38337   2018-02-23T04:26:00+00:00
server13   i-13m13m13m13m13m13  111.1.13.143     0.36428   2018-02-23T04:29:00+00:00
server14   i-14n14n14n14n14n14  111.1.14.154     0.35      2018-02-23T04:26:00+00:00
server15   i-15o15o15o15o15o15  111.1.15.165     0.324     2018-02-23T04:29:00+00:00

おわりに

  • 性能テストや障害時に「ボトルネックとなっているインスタンスを特定 -> private ip を見てssh」といった流れをスムーズに出来るようになりました。

  • 使用するメトリクスを変えることで、ネットワーク使用率やディスクI/Oなどを見ることも出来るので、中々便利なものだと思います。

  • 3rd Partyのライブラリの利用方法や、データ操作/表示のやり方を学べたので個人的にも良い勉強になりました。