AWS Systems Managerを用いてEC2上のコマンドを動かしてみた


こんにちは。streampackのfadoです。厳しい暑さが続いておりますがみなさん、いかがお過ごしでしょうか。まだまだ油断は禁物ですのでうがいと手洗いを忘れないようにしましょう。さて、今回は表題にも書きました通り、AWS Systems Managerを利用し、AWS Lambda及びAmazon API Gatewayと連携したシステムでAmazon EC2インスタンス上でコマンドを実行させる方法を簡単に説明していきたいと思います。

注意事項

  • Linuxコマンド及びAWS Lambda,Amazon API Gateway,AWS Systems Managerの知見がある方を対象にしています。
  • AWS LambdaはPython3で記載しています。
  • ffmpegのRTMP送信先はAWS Elemental MediaLive -> AWS Elemental MediaPackageにしていますがスコープ外ですのでセットアップ等は割愛させていただきます。

リソース

  • Amazon EC2 (以下 EC2)
  • AWS Systems Manager(以下 SSM)
  • AWS Lambda (以下 Lambda)
  • Amazon API Gateway(以下 API Gateway)
  • AWS IAM (IAM ロールとIAM ポリシー)

手順

EC2

  • OSはAmazon Linux 2を使っています。AWS Systems Managerエージェントとffmpegが動く環境であればどのOSでも問題ありません。
  • AmazonSSMManagedInstanceCoreポリシーがアタッチされたIAM ロールを対象EC2インスタンスにつけます。 AmazonSSMManagedInstanceCoreEC2インスタンスがSSMのメイン機能を取り扱えるように許可してくれるポリシーです。
  • 例ではffmpegコマンドを利用しますので事前にインストールしておきましょう。
  • 動画はフリー素材を使っています。

SSM

AWS Systems Managerコンソールにて「インスタンスとノード」 -> 「マネージドインスタンス」のタブで対象EC2インスタンスが登録されていることを確認します。表示に出ていない場合はEC2にアタッチされているロールにAmazonSSMManagedInstanceCoreポリシーが漏れている可能性があります。

Lambda

LambdaIAM ロールAWS Systems Managerssm:SendCommandssm:ListCommandInvocationsアクションを利用しているのでこの二つのアクションが含まれるポリシーを付けます。LambdaIAM ロール専用のポリシーにしたいので、インランポリシーでの作成になります。

関数コードは次の通りです。

lambda_handler.py
import boto3
import time
import json

apigw = boto3.client('apigateway')
ssm = boto3.client('ssm')

## 対象インスタンスを定義
instance_id = ['i-XXXXXXXXX']

## 実行するコマンドを定義
start_cmd = "/usr/local/bin/ffmpeg -re -stream_loop -1  -i /home/ec2-user/media/bbb_sunflower_1080p_30fps_normal.mp4 -c:v copy -c:a aac -flags +loop-global_header -f flv rtmp://XX.XX.XX.XX:1935/test/live < /dev/null > /tmp/ffmpeg.log 2>&1 &"
stop_cmd = "/usr/bin/killall ffmpeg"

def lambda_handler(event, context):
    try:
        ## API GatewayのURL pathによって実行するコマンドを区別
        api_path = event['path']
        if "start" in api_path:
            cmd_to_execute = {'commands': [start_cmd]}
        elif "stop" in api_path:
            cmd_to_execute = {'commands': [stop_cmd]}
        else:
            return "No matches,exiting..."

        ### send-commandを実行
        response = ssm.send_command(
            DocumentName = "AWS-RunShellScript",
            InstanceIds = instance_id,
            Comment = 'Executing ffmpeg command...', 
            TimeoutSeconds = 600, 
            Parameters = cmd_to_execute
        )
        command_id = response['Command']['CommandId']

        ## send_commandのCommandIdid値が取得できるまで数秒かかることもあるため5秒間隔をおく
        time.sleep(5)

        ## ステータスを取得
        list_invocations = ssm.list_command_invocations(
            CommandId = command_id,
            Details = True
            )

        ### Api Gatewayに渡すレスポンスを定義
        body = list_invocations['CommandInvocations'][0]['Status']

        return { 
            'statusCode': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': body
            }

    except Exception as e:
        raise e

詳細な説明は省きますが上記はAPI Gatewayから送られてきたURLを判別し、対象EC2インスタンスに対しstartまたはstopコマンドの実行命令を送ります。コマンド実行が成功ならSuccess,失敗ならFailedのレスポンスが戻ってくる流れとなっています。

API Gateway

次のように設定します。リソース名は分かりやすいものにしましょう。
尚、メソッドはGETではなくPOSTにします。APIのURLを叩くだけで実行されないようにするためです。

プロトコル メソッド リソース名
REST POST /start
REST POST /stop

メソッドを設定する際に「Lambda プロキシ統合の使用」にチェックを入れます。これはAPI Gatewayが受信リクエストをLambda関数の入力eventパラメータにマッピングさせるためです。

検証

startコマンド

1.設定したstart用APIでPOSTメソッドを用いて実行します。「Success」レスポンスが返ってきたら問題なくコマンドが実行されたことになります。「Failed」レスポンスの場合は、EC2インスタンスのamazon-ssm-agent.logをご確認ください。

$ curl -X POST https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/ffmpeg/start
Success

2.EC2インスタンス上でffmpegプロセスが起動できていることを確認できました。

$ ps axu|grep ffmpeg
root      3119  9.9  1.0  91388 21100 ?        S    01:23   0:00 /usr/local/bin/ffmpeg -re -stream_loop -1 -i /home/ec2-user/media/bbb_sunflower_1080p_30fps_normal.mp4 -c:v copy -c:a aac -flags +loop-global_header -f flv rtmp://54.249.218.4:1935/test/live
ec2-user  3121  0.0  0.1 110536  2236 pts/0    S+   01:23   0:00 grep --color=auto ffmpeg

3.配信が問題なくできていることも確認できました。
(配信はスコープ外なのでできていなくてもffmpegプロセスさえ動いていれば問題ありません)

stopコマンド

1.設定したstop用APIでPOSTメソッドを用いて実行します。「Success」レスポンスが返ってきたら問題なくコマンドが実行されたことになります。「Failed」レスポンスの場合は、EC2インスタンスのamazon-ssm-agent.logをご確認ください。

$ curl -X POST https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/ffmpeg/stop
Success

2.EC2インスタンス上でffmpegプロセスが起動できていることを確認できました。

$ ps axu|grep ffmpeg
ec2-user  3108  0.0  0.1 110536  2236 pts/0    S+   01:22   0:00 grep --color=auto ffmpeg

SSMからのコマンド実行に問題がないかEC2インスタンスのログでも確認することができます。

/var/log/amazon/ssm/amazon-ssm-agent.logの一部

  },
  "documentStatus": "Success",
  "documentTraceOutput": "",
  "runtimeStatus": {
    "aws:runShellScript": {
      "status": "Success",
      "code": 0,
      "name": "aws:runShellScript",
      "output": "",
      "startDateTime": "2020-09-17T16:08:17.477Z",
      "endDateTime": "2020-09-17T16:08:17.488Z",
      "outputS3BucketName": "",
      "outputS3KeyPrefix": "",
      "stepName": "",
      "standardOutput": "",
      "standardError": ""
    }

結論

AWS Systems Manager,AWS LambdaAmazon API Gatewayを活用すればEC2インスタンス上でコマンドを簡単に実行させることができます。今回は単純な処理を例に挙げて説明しましたが踏み込んだコマンドを実行するスクリプトを用意すればさらに高度な処理ができ、かつその実行結果を表示させることも可能です。それではまた次回!

参考文献

https://aws.amazon.com/jp/systems-manager/
https://aws.amazon.com/jp/lambda/
https://aws.amazon.com/jp/api-gateway/
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html