タケユー・ウェブ日報

Webシステム受託会社の業務の中での気づきや調べごとのメモ。

Lambda から EC2 インスタンス内でコマンドを実行する

Amazon SSM を利用することで、Lambda を使って、EC2インスタンス内で任意のコマンドを実行することができます。

これを CloudWatch Events と組み合わせると、従来CRONによって行っていたような定期実行タスクを、特定のEC2インスタンスをSPOFにすることなく実装することができ、オートスケーリングを有効にしたEC2クラスタ環境などで便利です。

blog.takeyuweb.co.jp

Amazon EC2 Simple Systems Manager (SSM)

対象のEC2インスタンスには事前にSSMエージェントをインストールしておく必要があります。 手動や、cloud-initなどでインストールしておきます。

docs.aws.amazon.com

CloudFormation テンプレート(抜粋)

f:id:uzuki05:20200501174049p:plain

Parameters:
  Command:
    Type: String

  # 実行したい Lambda Function
  # 起動中のEC2インスタンスから1件を取り出してコマンドを実行する
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |+
          const AWS = require('aws-sdk');
          const ssm = new AWS.SSM({apiVersion: '2014-11-06'});
          const ec2 = new AWS.EC2({apiVersion: '2016-11-15'});
          const { Command } = process.env;

          const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
          const debug = (key, object) => { console.log(`DEBUG: ${key}\n`, JSON.stringify(object)); }

          class InstanceNotFoundError extends Error {
            constructor(message) {
              super(message);
              this.name = 'InstanceNotFoundError';
            }
          }

          exports.handler = async (event, context) => {
            console.log("INFO: request Recieved.\nEvent:\n", JSON.stringify(event));

            const describeInstancesParams = {
              Filters: [
                {
                  Name: "tag:Service",
                  Values: [
                    "app"
                  ]
                },
                {
                  Name: "instance-state-name",
                  Values: [
                    "running"
                  ]
                }
              ]
            };
            debug("describeInstancesParams", describeInstancesParams);
            const describeInstancesResult = await ec2.describeInstances(describeInstancesParams).promise();
            debug("describeInstancesResult", describeInstancesResult);
            const reservation = describeInstancesResult.Reservations[0];
            if (!reservation) {
              throw new InstanceNotFoundError("App is Not Found");
            }

            const instanceStatus = reservation.Instances[0];
            const sendCommandParams = {
              DocumentName: 'AWS-RunShellScript',
              InstanceIds: [instanceStatus.InstanceId],
              Parameters: {
                commands: [Command],
                executionTimeout: ['3600']
              },
              MaxConcurrency: '1',
              MaxErrors: '0',
              TimeoutSeconds: 3600,
            };
            debug("sendCommandParams", sendCommandParams);
            const sendCommandResult = await ssm.sendCommand(sendCommandParams).promise();
            debug("sendCommandResult", sendCommandResult);

            const results = {
              ec2InstanceId: instanceStatus.InstanceId,
              sendCommandParams: sendCommandParams,
              sendCommandResult: sendCommandResult
            };
            debug("results", results);
            return results;
          };
      Environment:
        Variables:
          Command: !Ref Command
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: "nodejs10.x"
      MemorySize: 128
      Timeout: 60

  # Lambda の実行ロール
  # 標準の AWSLambdaBasicExecutionRole サービスロールポリシーに加えて、
  # SSMコマンド実行用の ssm:SendCommand
  # EC2インスタンス一覧取得用の ec2:describeInstances
  # をインラインポリシーに追加
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - "sts:AssumeRole"
      Path: /
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: run-command
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - ssm:SendCommand
                  - ec2:describeInstances
                Resource: "*"