0
Help us understand the problem. What are the problem?

posted at

updated at

【boto3】API Gateway経由で一定時間以上起動しているEC2インスタンスを検知し、ユーザーに通知する

はじめに

ご覧いただきありがとうございます。

業務の中でAmazon SageMakerを利用しているお客様と話すことがあります。
使用するノートブックインスタンスのスペックが高いので、起動し続けるとお金がかかります。

連続稼働時間を検知して自動通知を行えないかと試行錯誤する中で、そもそも普段使用しているEC2インスタンスの連続稼働時間を検知してユーザー通知する方法についても検討を行いました。

Lambdaを使用して「EC2インスタンスが一定時間以上稼働しているか確認して、1時間以上稼働していたらユーザー通知を行う」という検証を行ってみました。

概要

(★)がついているセクションは、手を動かして頂く項目です。

  1. 今回の構成
  2. 下準備(★)
  3. EC2の連続稼働時間を検知するには?(★)
  4. EC2をSysmtes Managerで扱えるようにする(★)
  5. LambdaからEC2の連続稼働時間を取得する(★)
  6. 稼働時間が一定時間を超えた場合、SNS通知を管理者に送る(★)
  7. API Gatewayを経由した手動実行(★)
  8. (おまけ)EventBridgeでの自動実行(★)

各種公式ドキュメントを参照しながら進めていきます。
 

事前準備

  • AWSアカウント作成
  • AdministratorAccessを付与したIAMユーザーの作成

1.今回の構成

【構成】

  • EC2
  • Lambda
  • API Gateway
  • EventBridge

2.下準備

VPCまわりの下準備

  • VPC
  • Public Subnet(Internet Gatewayへのルート設定あり)

EC2の作成

  • AMI: Amazon Linux2
  • インスタンスタイプ: t2.micro
  • Public Subnetに配置
  • パブリックIP自動割り当て有効化
  • セキュリティグループ(SSH 22番ポートのインバウンド通信許可)

上記設定でEC2インスタンスを1台作成します。
作成完了したら、SSH接続ができることを確認します。

3. EC2の連続稼働時間を検知するには?

boto3のリファレンスページを確認する

「EC2が1時間以上稼働しているかどうか」をLambdaの処理で検知できるようにしたいと思います。

今回は言語としてPythonを使用します。

お手持ちのブラウザで「boto3」と検索。

image.png

AWS SDK for Python (Boto3)のページで、APIリファレンスを参照します。

EC2のページに飛び、使えそうなメソッドを探します。

image.png

指定されたインスタンスまたはすべてのインスタンスについての情報が出力されます。
こちらに使えそうなパラメータはないでしょうか。

launch-time - The time when the instance was launched

「インスタンスが生成された日時」を指すようです。

EC2のページでも情報が確認できます。

image.png

他に「連続稼働時間のみ」を調べられる手段はないか?

稼働時間を調べるで思い浮かんだのは、Linuxコマンドの「$uptime」です。
EC2サーバにSSHログインして、コマンドを入力してみます。

$ uptime
  07:49:30 up 31 min,  1 user,  load average: 0.00, 0.00, 0.00

Lambdaで扱うには余計な情報が多いように思えます。

$ uptime -p
up 31 minutes

pオプションをつけて、情報を絞り込みましたが、まだ情報が多いです。

$ cat /proc/uptime
1914.03 1887.20

1フィールドは、システムが起動してから経過した合計時間(秒)を示します。
2フィールドは、各コアがアイドル状態で経過した合計時間の秒数です。

1フィールドのみ表示できれば、目的は達成できそうです。

$cut -d "." -f 1 /proc/uptime
1930

Lambdaで扱いやすい形にできました。

Lambdaからコマンドを実行するには?

使えそうなのはSystems Managerです。

公式ドキュメントの「コマンドのドキュメント」の箇所を確認します。

AWS Systems Manager の一機能である Run Command は、コマンドドキュメントを使用してコマンドを実行するとあります。

Run Commandは、EC2インスタンスを含めたマネージドノードの設定を安全にリモートで管理できます。

Run Commandを実行するためには、最低条件があるようなので、次のセクションで設定を行っていきます。

4. EC2をSysmtes Managerで扱えるようにする

前提条件を満たす

EC2はPublic Subnetにあります。

公式ドキュメントには、AWS Systems Manager を使用して Amazon Elastic Compute Cloud (Amazon EC2) インスタンス、エッジデバイス、オンプレミスサーバー、仮想マシン (VM) を管理するための前提条件が書かれています。

抜粋すると
・必要な AWS Identity and Access Management (IAM) ロールを設定
・VPC エンドポイントを使用しない場合は、Systems Manager エンドポイントへのHTTPS (ポート443)アウトバウンドトラフィックを許可するようにマネージドインスタンスを設定
・マネージドノードごとに SSM Agent のインストールの実施またはインストールされたことを確認

上記の3つの条件を満たせばよさそうです。

Systems Manager がインスタンスとやり取りするためのアクセス許可を付与するために、AmazonSSMManagedInstanceCoreを割り当てます。

  • EC2のセキュリティグループはデフォルトであれば、すべてのアウトバウンドトラフィックが有効になっています。よって本要件も満たします。

  • SSM Agentのインストールすることで、Systems Manager でEC2インスタンスを更新、管理、設定できるようになります。今回Amazon Linux2を使用しており、標準でSSM Agentがインストールされているはずです。念のため入っているか確認してください。

$ sudo systemctl status amazon-ssm-agent

Fleet Managerでの確認

上記の条件を満たしていれば、Sysmtems Manager側でEC2を認識します。
今回作成したAmazon Linux2のEC2インスタンスを使用している場合は、「AmazonSSMManagedInstanceCore」を含んだIAMロールを付与することで、条件は満たせるはずです。

Systems Managerのページに飛び、Fleet Managerを確認します。
Fleet Managerの画面では、管理対象のEC2インスタンスがrunning or stoppedかどうか、ステータスの確認ができます。

この画面に作成したEC2インスタンスが表示されていれば、ひとまず設定は成功です。
IAMロールを変更したばかりだと反映に時間がかかるようです。

5. LambdaからEC2の連続稼働時間を取得する

ではLambda側の実装を行います。

Lambda用のIAMロールを作成

新規でIAMロールを作成します。

  • 信頼されたエンティティ
    AWSサービス⇒EC2

  • ポリシーの追加
    AmazonSSMFullAccess

上記の条件でIAMロールを作成します。

Lambda関数を作成

Lambdaのページに移動し、「関数の作成」をクリック。

  • 一から作成
  • ランタイム : Python3.8
  • アーキテクチャ: x86_64
  • デフォルト実行ロール: 先ほど作成したロールを設定

上記の設定で作成。

関数が出来上がったら、一部設定を変更。

  • タイムアウト

設定⇒一般設定⇒編集⇒※タイムアウトを30秒に変更

ではLambdaのコード入力フォームに、コードを書いていきます。

image.png

EC2の連続稼働時間を取得する

import boto3
import json
import time

ssm_client = boto3.client('ssm')

instance_id = 'xxxxxxxxxxxx'


def lambda_handler(event, context):

    
    response = ssm_client.send_command(
        InstanceIds = [
        instance_id
    ],
    DocumentName="AWS-RunShellScript",
    Parameters={"commands": [
                    'cut -d "." -f 1 /proc/uptime'
                ]
        }
    )
    
    command_id = response['Command']['CommandId']

    time.sleep(5)

    list_invocations = ssm_client.list_command_invocations(
        CommandId = command_id,
        Details = True
        )
        
    output = list_invocations['CommandInvocations'][0]['CommandPlugins'][0]['Output']
    return output.strip()

instance_idの「xxxxxxxxxxxx」、作成したEC2のインスタンスIDに置き換えます。

EC2の連続稼働時間を検出するために、SSMを使用します。
boto3(SSM)のリファレンスを確認します。

image.png

send_commandメソッドが使えそうです。
こちらに使用方法は載っています。

send_commandのパラメータには

  • InstanceIds
  • DocumentName (AWS-RunShellScriptを使用)
  • Parameters
    の3つを指定しています。

send_commandの結果はdict型で返ってくるようなので、結果からCommandIDを取り出します。

次にlist_command_invocationsを実行します。
image.png

メソッドの使い方はこちらを参照ください。

今回は

  • CommandId
  • Details
    の2つのパラメータを指定します。

戻り値はdict型で返ってきます。
CommandPluginsの中の「Output」を取り出します。

テスト実行

試しにLambdaのTestを実行します。

image.png

EC2の連続稼働時間が返ってきました。

6. 稼働時間が一定時間を超えた場合、SNS通知を管理者に送る

SNSの設定

  • SNSトピック作成
    名前の設定のみでOK

  • サブスクリプション
    トピックARN: トピックを登録
    プロトコル: Eメール
    エンドポイント: 送信先メールアドレス

Lambdaにロールを追加

Lambdaの設定を追加します。
現在Lambdaに適用されているロールに「AmazonSNSFullAccess」を追加。

コードを修正

import boto3
import json
import time

ssm_client = boto3.client('ssm')
sns_client = boto3.client('sns')

instance_id = 'xxxxxxxxxxxxxxxx'
TOPIC_ARN = 'xxxxxxxxxxxxxxxx'


def lambda_handler(event, context):

    
    response = ssm_client.send_command(
        InstanceIds = [
        instance_id
    ],
    DocumentName="AWS-RunShellScript",
    Parameters={"commands": [
                    'cut -d "." -f 1 /proc/uptime'
                ]
        }
    )
    
    command_id = response['Command']['CommandId']

    time.sleep(5)

    list_invocations = ssm_client.list_command_invocations(
        CommandId = command_id,
        Details = True
        )
        
    output = list_invocations['CommandInvocations'][0]['CommandPlugins'][0]['Output']
    i_output = int(output)
    
    if i_output > 3600:
        msg = output
        subject = 'test'
        
        mail_response = sns_client.publish(
            
            TopicArn = TOPIC_ARN,
            Message = msg,
            Subject = subject
            )
            
        return mail_response
        
    else:
        return False

Instace_idとTOPIC_ARNは、自分の環境のものに置き換えてください。
またテストのために「連続稼働時間が1時間(=3600秒)を超えたら」メールを飛ばすようにします。

SNSの実装方法は、boto3のAPIリファレンスを参照します。
image.png

publish()メソッドが使えそうです。

image.png

トピックにメッセージを送信すると、AmazonSNSはトピックにサブスクライブされている各エンドポイントにメッセージを配信します。

パラメータに

  • TopicArn
  • Message、
  • Subject
    の3つを指定しています。

テスト実行

Lambdaのページでテストを実行します。

SNSの設定でエンドポイントに指定したメールアドレスにメールが来ていないかを確認します。

image.png

EC2の連続稼働時間がSNSで飛んできました。

7. API Gatewayを経由した手動実行

API Gatewayを経由して、Lambdaを手動実行できるようにします。

APIの作成

API Gatewayのページに移動します。
「APIを作成」をクリック。

  • APIタイプ: REST API
  • 新しいAPI
  • エンドポイントタイプ: リージョン

「APIの作成」をクリックして、APIを作成します。

image.png

GETメソッドを作成

では設定を行っていきます。

アクションを押して、「メソッドの作成」を選択。
image.png

GETを選択し、右横にあるチェックマークを押します。

image.png

セットアップ画面が現れるので、上記のように設定ください。
リージョンとLambda関数は自身の環境のものに合わせてください。

保存を行うと、下記の画面が表示されます。

image.png

APIのデプロイ

image.png

アクションから「APIのデプロイ」を選択。

image.png

新しいステージを選択して、デプロイを実行します。

image.png

URLが表示されます。

Lambdaの手動実行

URLにアクセスしてみましょう。

image.png

SNSでメールが飛んできました。

(おまけ)8. EventBridgeでの自動実行

ついでにEventBridgeを利用して、Lambdaを定期的に自動実行できるようにします。

ルールの作成

EventBridgeのページに移動して、「ルールを作成」をクリック。

  • イベントバス: default
  • ルールタイプ: スケジュール

「次へ」を選択。

image.png

今回はテストのために、15分に一回Lambdaが実行されるように設定します。

「ターゲットを選択」の画面で

  • ターゲットタイプ: AWSのサービス
  • ターゲット: Lambda関数
  • 機能: 作成したLambda関数を選択

あとはデフォルトでルールを作成します。

Lambdaの自動実行確認

メールが届いているか確認します。

1回目
image.png

2回目(15分後)

image.png

15分周期でメールが届いています。

さいごに

今回はEC2(1台)に対する運用になっていますが、複数台対応できるようにするなど、まだまだ改良の余地がありそうです。

御覧いただきありがとうございました!

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?