Amazon-CloudWatch

CloudWatchダッシュボードにAutoScalingのカスタムメトリクスを自動更新する

はじめに

CloudWatchダッシュボードでは登録したメトリクスのグラフ等を監視することができます。
しかしながらAuto Scalingのインスタンスのカスタムメトリクスの場合、スケールイン、スケールアウトの度にメトリクスが更新されてしまい、Auto Scalingの名前空間もCWAgentに割り振られているのでAutoScaling Groupで登録することができません。

この記事ではAuto Scalingで動作しているインスタンスのカスタムメトリクスを自動でCloudWatchダッシュボードに更新するLambdaを紹介したいと思います。

今回用いる構成

構成図

AutoScaling Groupのインスタンスの増減をトリガーにLambdaを起動します。

CloudWatch AgentをインストールしたAMIの作成

Auto Scalingに使用しているAMIはAmazon LinuxにCloudWatch Agentを導入して設定等も済ませているものを使用しています。
以下手順で作成しており、自分用に備忘録としてまとめておきます。

sudo yum install amazon-cloudwatch-agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y collectd
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -s -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a status

Lambdaで実行するコードの紹介

今回使用するコードです。

import json
import os
import urllib.request
from collections import OrderedDict
import boto3

#各種クライアント
autoscaling = boto3.client('autoscaling', os.environ.get('REGION_NAME'))
cloudwatch = boto3.client('cloudwatch', os.environ.get('REGION_NAME'))
ec2 = boto3.client('ec2', os.environ.get('REGION_NAME'))

def get_instances_data_dict(instance_id,autoscaling_group_name):
    '''
    渡されたインスタンスの情報をまとめてdictで返す。
    '''
    res = ec2.describe_instances(InstanceIds=[instance_id])

    instance_data = {
        'AutoScalingGroupName': autoscaling_group_name,
        'InstanceId': res['Reservations'][0]['Instances'][0]['InstanceId'],
        'AmiId': res['Reservations'][0]['Instances'][0]['ImageId'],
        'InstanceType': res['Reservations'][0]['Instances'][0]['InstanceType']
    }

    return instance_data

def get_edit_widget_index(dashboard_json,target_title):
    '''
    jsonの変更を加える箇所の要素数を取得する
    '''

    for index, widget in enumerate(dashboard_json['widgets']):
        #title要素が含まれており、titleが求めているものの場合
        if 'title' in widget['properties'] and widget['properties']['title'] == target_title:
            return index

def apply_instance_data_to_json(target_widget, instance_data_list):
    '''
    target_widgetをinstance_data_listの情報を用いて書き換える
    '''

    #各オートスケールグループに含まれるインスタンスごとに
    for index, _ in enumerate(instance_data_list):
        #1つめの要素はすべて入力する必要がある
        if index == 0:
            #空にする処理
            target_widget['properties']['metrics'] = []
            target_widget['properties']['metrics'].append( [
                'CWAgent', 
                os.environ.get('METRICS_NAME'), 
                'InstanceId', instance_data_list[index]['InstanceId'], 
                'AutoScalingGroupName', instance_data_list[index]['AutoScalingGroupName'], 
                'ImageId', instance_data_list[index]['AmiId'], 
                'InstanceType', instance_data_list[index]['InstanceType']
                ])
        #それ以降は1つめと異なる要素のみ入力する
        else:
            target_widget['properties']['metrics'].append([
                '...', instance_data_list[index]['InstanceId'], 
                'AutoScalingGroupName', instance_data_list[index]['AutoScalingGroupName'], 
                'ImageId', instance_data_list[index]['AmiId'], 
                'InstanceType', instance_data_list[index]['InstanceType']
                ])
    return target_widget

def lambda_handler(event, context):

    #ダッシュボードのデータを取得
    dashboard_res = cloudwatch.get_dashboard(DashboardName=os.environ.get('DASHBOARD_NAME'))
    #↑で取得したデータをstring->jsonに変換
    dashboard_json = json.loads(dashboard_res['DashboardBody'])
    #オートスケーリンググループのデータを取得
    autoscaling_res = autoscaling.describe_auto_scaling_groups(AutoScalingGroupNames=[os.environ.get('AUTOSCALING_GROUP_NAME')])

    #オートスケールグループのリストを回してjsonを更新する。
    for autoscaling_group in autoscaling_res['AutoScalingGroups']:

        #instance_data(辞書型)のリスト
        instance_data_list = []

        #オートスケールグループ内のインスタンスを回す
        for instance in autoscaling_group['Instances']:
            instance_data = get_instances_data_dict(instance['InstanceId'],autoscaling_group['AutoScalingGroupName'])
            instance_data_list.append(instance_data)

        index = get_edit_widget_index(dashboard_json, 'blog test')
        target_widget = dashboard_json['widgets'][index]
        #dashboard_jsonに変更を適用する。
        target_widget = apply_instance_data_to_json(target_widget, instance_data_list)

    #jsonダッシュボードに適用する。
    cloudwatch.put_dashboard(
        DashboardName=os.environ.get('DASHBOARD_NAME'),
        DashboardBody=json.dumps(dashboard_json)
        )

少しわかりづらいかもしれないですが、大まかな流れとしましては以下のようになります。

  1. インスタンスの増減でLambdaが起動。
  2. ダッシュボードのデータを取得し辞書型に変換。 (72行目)
  3. AutoScaling Group内のインスタンスのAMI等の情報を格納。 (get_instances_data_dict)
  4. ダッシュボードのデータの変更する箇所を特定。 (get_edit_widget_index)
  5. 3のデータを元に4の箇所を書き換え。 (apply_instance_data_to_json)
  6. 変更したデータをCloudWatchダッシュボードに適用。 (95行目)

またos.environ.getの箇所はあらかじめ設定した環境変数が入ります。

CloudWatchダッシュボードを確認する

Lambda実行前後でダッシュボードを見比べてみます。
インスタンス台数は1から4に変更しました。

Lambda実行前のダッシュボード

Lambda実行前のダッシュボード

Lambda実行後のダッシュボード

Lambda実行後のダッシュボード

ちゃんと4台とも表示してもらえました。

おわりに

一応カスタムメトリクスも料金が発生するのでエージェントかインスタンスを停止しておかないと場合によっては大変なことになるので気を付けましょう。
この記事が何かしら参考になれば幸いです。

参考にさせていただいた記事

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudwatch.html
https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CloudWatch-Dashboard-Body-Structure.html
https://dev.classmethod.jp/articles/amazon-linux-2-cloudwatch-agent-error-solution/

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA