LambdaでEC2内のシェルスクリプトを実行


はじめに

本記事の対象となる読者

特定のトリガーでLambdaを起動して、EC2内のシェルスクリプトを実行したい

システム概要

上記のやりたかったことを実現するために使用したサービスは以下です。

  • AWS Lambda(以後Lambda)
  • AWS System Manager(以後SSM)
  • AWS Elastic Computer Cloud(以後EC2)

書かないこと

  • EC2インスタンスの構築方法

EC2

Amazon Linux2を使用して、無料利用枠でインスタンスを用意します。

EC2インスタンスを作成

EC2インスタンスを作成する前に以下のポリシーをアタッチしたIAMロールを作成しておきます。仮に名前はAWSServiceSSMRoleとしておきます。

  • AmazonEC2RoleforSSM
  • AmazonSSMFullAccess

上記で作成したロールをインスタンスに割り当てインスタンスを作成します。

ssm-agentのインストールは必要ありません。

インスタンス内にbashスクリプトを作成

ssh接続をして以下を実行していきます。

# 自身で設定した情報を使ってSSH接続
$ ssh ec2-user@host -i ~~.pem

# 現在地の確認
$ pwd
# 結果:/home/ec2-user

# test.txtを作成
$ touch test.txt

# 実行するshファイルを作成
$ vi test.sh

vimでtest.shを開いたら以下を入力します。

#!/bin/bash

echo hoge >> test.txt

入力したら、保存します。

$ sh test.sh

と実行するとtest.txthogeという文字列が書き込まれているかと思います。

Lambda

今回はAPI Gatewayを使用して、そのエンドポイントにPOSTがあったらLambdaを実行するように作成します。

serverless frameworkを使用して、lambdaを作成します。

$ sls create -t aws-nodejs -p myfunc

$ cd myfunc

上記を実行すると、 handler.jsserverless.yml が作成されます。

serverless.yml

API Gatewayを使用するので、eventsを設定します。

service: myfunc
provider:
  name: aws
  runtime: nodejs12.x

functions:
  hello:
    handler: handler.hello
    events:
    - http:
        path: hoge
        method: post
        integration: lambda

handler.js

AWS-SDK SSMクラスで用意されているsendCommandメソッドを使用します。
詳しくはこちらを参照してください。

sendCommandはSSMにあらかじめ登録されているドキュメントのコマンドを実行します
今回はデフォルトで用意されているAWS-RunShellScriptドキュメントを使用します。
AWS-RunShellScriptはただEC2のシェルスクリプトを実行するだけですが、もちろんSSMに自分でドキュメントを作成してそれを実行することも可能です。

lambdaではaws-sdkは標準でインストールされていますので、npm installなどでインストールする必要はありません。

'use strict'

const AWS = require('aws-sdk')
const SSM = new AWS.SSM({region: 'ap-northeast-1'})
const REMOTE_WORKING_DIR = '/home/ec2-user'

module.exports.main = async event => {
  try {
    // 実行したいコマンド
    let command = 'sh exec.sh'

    let params = {
      DocumentName: 'AWS-RunShellScript',
      InstanceIds: ['作成したEC2のインスタンスID'],
      Parameters: {
        commands: [command], // 配列で指定するので複数実行も出来る
        workingDirectory: [REMOTE_WORKING_DIR] // どの階層で実行するかを指定
      },
      // SSMの実行結果をCloudWatchにロギング
      CloudWatchOutputConfig: {
        CloudWatchLogGroupName: 'SSMLogs',
        CloudWatchOutputEnabled: true
      },
      // タイムアウト設定
      TimeoutSeconds: 3600 // 1 hour
    }

    SSM.sendCommand(params, function(err, data){
      if(err){
        console.log(err, err.stack)
      } else {
        console.log(data)
      }
    })
  } catch(e){
    console.log(e);
  }
}

上記を保存後エンドポイントにPOSTして実行します。

$ curl -X POST "serverlessで作成されたエンドポイント" -d "{}"

以上となります。