Lambda + EC2 Run Commandで擬似Cron環境を作る【東京上陸記念】

AWS

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

EC2 Run Commandとは

本日のAWSアップデートで、EC2 Run Commandがついに東京リージョンでも利用可能になりました。

EC2 Run CommandはEC2インスタンスの外からOS内部のコマンドを実行したり、設定を変更したりすることを可能にしたサービスです。詳しい紹介や導入手順などは以下のブログを参照下さい。

今回、東京リージョンでもRun Commandが利用できるようになったということで、試してみたかったことをやってみました。

Pseudo Cron

AWS Lambdaを利用すると、タスクスケジューラのように一定の決まった時刻にプログラムを実行することができます。 *1

これとSSMを組み合わせることで、決まった時刻になったらEC2上でプログラムが動作するという、擬似Cronの仕組みができないか?と思ったので実装してみました。以下のような仕組みになります。

Untitled

それでは、試してみましょう。

セットアップ

今回の環境は、EC2はAmazon Linuxにて検証しています。

EC2の起動・エージェントインストール

まずはコマンドを実行するEC2を起動し、SSM Agentをインストールしておきましょう。 SSMのインストール方法などは以下のブログを参考にして下さい。

注意点としては、東京リージョンで起動する際には、インストール時のバケットがamazon-ssm-ap-northeast-1となりますのでそこを変更して下さい。また、EC2に対してIAM Roleを忘れずに設定しておきましょう。

IAM Role権限

次に、Lambdaに紐付けるIAM Roleを作成しておきます。以下のManaged Policyをアタッチしておきましょう。実際はもう少し細かい権限設定が可能です。

  • AmazonEC2FullAccess
  • CloudWatchLogsFullAccess
  • AmazonSSMFullAccess

IAM_Management_Console

Lambdaコード

それではLambda Functionを作成しましょう。今回は以下のようなPythonコードを作成しました。

import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ec2 = boto3.client('ec2')
ssm = boto3.client('ssm')

def lambda_handler(event, context):
    try:
        # all running EC2 instances
        ec2_resp = ec2.describe_instances(Filters=[{'Name':'instance-state-name','Values':['running']}] )

        ec2_count = len(ec2_resp['Reservations'])
        if ec2_count == 0:
            logger.info('No EC2 is running')

        # Get All InstanceID
        instances = [i["InstanceId"] for r in ec2_resp["Reservations"] for i in r["Instances"]]
        ssm.send_command(
            InstanceIds = instances,
            DocumentName = "AWS-RunShellScript",
            Parameters = {
                "commands": [
                    "echo %s > /tmp/lambda_event" % event["id"]
                ],
                "executionTimeout": ["3600"]
            },
        )

    except Exception as e:
        logger.error(e)
        raise e

対象となるインスタンスは、起動中の全インスタンスとしました。ここはインスタンスIDを固定にしたり、タグなどを利用して絞り込むことでカスタマイズ可能です。

OS上で実行されるコマンドは26行目の部分に記載しています。ここに実行したいコマンドをカンマ区切りで記載していきます。今回はLambdaのイベントIDをファイルに書き込んでみるだけの単純なコマンドを実行してみます。

Function作成の際には、上の手順で作ったIAM Roleを忘れずにアタッチしておきましょう。

実行

以上で準備は完了です。それでは試しにManagement Consoleからテスト実行してみましょう。

AWS_Lambda

エラーが表示されていなければ成功です。OSにログインし、実行結果が残っているかどうか確認してみます。

[ec2-user@ip-172-31-10-242 ~]$ ls -l /tmp/
合計 4
-rw-r--r-- 1 root root 37  1月 20 23:56 lambda_event
[ec2-user@ip-172-31-10-242 ~]$ cat /tmp/lambda_event
cdc73f9d-aea9-11e3-9d5a-835b769c0d9c
[ec2-user@ip-172-31-10-242 ~]$

無事実行が確認できました。これで、Pythonコードのコマンドの部分を修正すれば、cronの代替として動かすことが可能そうですね!

実用性

ここまでLambda + Run Commandで擬似Cronを実装する方法を書いてきました。ですが、この仕組みを実運用には載せないほうがよいと考えます。実行時刻がOS時刻と完全に同一になるとは限りませんし、エラー時のトラブルシューティングは考えたくありません。

同一のCronを実行したいサーバが数十台存在するようなケースではLambdaを書き換えるだけで全台のCron設定を書き換えることができ一見便利そうですが、そういったケースではAnsible等の構成管理ツールを導入して全台への設定配布の仕組みを構築したほうがよいでしょう。

あくまでも上陸記念のネタ投稿&こんなことができるよ、というご紹介ということで。Run Commandは色々と便利に使えそうなのでぜひ皆様お試し下さい!

脚注

  1. 先日のアップデートにより、Schedule自体はCloudWatch Eventで管理されるようになりました。

AWS Cloud Roadshow 2017 福岡