見出し画像

【AWS】cronの代わりにSystems Managerを使ってコマンドを定期実行する方法のご紹介

Japan Digital Design株式会社(以下JDD)でインフラエンジニアをしている村田と申します。今回はcronを使わずにEC2インスタンスでコマンドを定期実行させる方法を調べたのでご紹介したいと思います。

背景

JDDでは運用負荷を考慮し、できる限りサーバレスな仕組みを使うことを推奨しています。
しかし、どうしてもEC2を使わなければならない場合が出てくるため、その場合でも可能な限りマネージドサービスを使った運用ができるように工夫しています。

今回はその工夫の一つとしてサーバ内部の定期実行タスクをcronを使わずに実現する方法をご紹介します。

やりたいこと

  • EC2内部で定期的にコマンドを実行させたい

  • 失敗時にはslackに通知させたい

  • できる限りIaC化したいのでcronは使いたくない

実現方法

構成図

今回構築したシステムの全体像は以下の通りです。

利用するAWSサービスとその役割

  • AWS Systems Manager(SSM) RunCommand:EC2インスタンス内で任意のコマンドを実行させる

  • Amazon EventBridge Rule:定期的に RunCommandを実行する

  • Amazon Simple Notification Service(SNS):失敗した時などにSlackに通知させる

前提条件

  • SNSは事前に作成した上でARNを取得しておいてください。

  • コマンドを実行するインスタンスはマネージドノードにしておく(適切なIAMロールを付与する、SSMエージェントを設定するなど)必要があります。詳細はこちらをご確認ください。


実装

CloudFormation Template例

上記構成のうちEventBridgeとSSMを構築するためのCloudFormation Templateの例を記載します。

AWSTemplateFormatVersion: "2010-09-09"
Description: "EventBridge rule for execute SSM RunCommand"

Parameters: 
  
  Schedule:
    Type: String
    Description: Process Check Schedule
    Default: "cron(0 * * * ? *)"
  
  AlarmSnsTopicArn:
    Description: Alarm SNS ARN
    Type: String

  TargetInstanceId:
    Description: Target Instance of run command
    Type: String

  Command:
    Description: Command executed inside instance
    Type: String

Resources:
  
  # 1. コマンドを定期実行するEventBridge
  CronEvent:
    Type: AWS::Events::Rule
    Properties:
      Description: Execute command periodically
      Name: CronEvent
      ScheduleExpression: !Ref Schedule
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:ssm:${AWS::Region}::document/AWS-RunShellScript
          Id: RunShellCommand
          RoleArn: !GetAtt [EventBridgeForRunCommandRole, Arn]
          Input: !Sub '{ "commands": ["${Command}"] }'
          RunCommandParameters:
            RunCommandTargets:
              - Key: InstanceIds
                Values: 
                  - !Ref TargetInstanceId

  # 2. 1のEventBridgeに設定するロール
  EventBridgeForRunCommandRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: EventBridgeForRunCommandRole
      Path: "/"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: events.amazonaws.com
      Policies:
        - PolicyName: AllowRunCommand
          PolicyDocument:
            Version: "2012-10-17"
            Statement: 
              - Sid: AllowSendCommand
                Effect: Allow
                Action:
                  - "ssm:sendCommand"
                Resource: 
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*"
                  - !Sub "arn:aws:ssm:${AWS::Region}:*:document/AWS-RunShellScript"

  # 3. 1のコマンド実行結果を参照しFailedかTimeoutだったらアラームをあげるルール 
  CronMonitorEvent:
    Type: AWS::Events::Rule
    Properties:
      Description: Monitoring execute command result
      Name: CronMonitorEvent
      State: ENABLED
      RoleArn: !GetAtt [EventBridgeForPublishSnsRole, Arn]
      EventPattern:
        source:
          - "aws.ssm"
        detail-type:
          - "EC2 Command Invocation Status-change Notification"
        detail:
          instance-id:
            - !Ref TargetInstanceId
          document-name:
            - !Sub arn:aws:ssm:${AWS::Region}::document/AWS-RunShellScript
          status:
            - "Failed"
            - "Timedout"
      Targets:
        - Arn: !Ref AlarmSnsTopicArn
          Id: 'AlarmSnsTopicCronEventMonitor'

  # 4. 3のEventBridgeに設定するロール
  EventBridgeForPublishSnsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: EventBridgeForPublishSnsRole
      Path: "/"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: events.amazonaws.com
      Policies:
        - PolicyName: AllowPublishSns
          PolicyDocument:
            Version: "2012-10-17"
            Statement: 
              - Sid: AllowSendAlarm
                Effect: Allow
                Action:
                  - "sns:Publish"
                Resource: 
                  - !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:*"

解説

今回作成した4つのリソースについて解説します。

1. コマンドを定期実行するEventBridge

今回の主な目的であるコマンド実行を行うリソースです。
このEventBridgeからSSMドキュメント(今回は "AWS-RunShellScript"というドキュメント)を実行します。
このドキュメントに渡す引数を "Input"で指定します。この引数が今回は実行するコマンドとなります。

詳細は以下のリファレンスをご参照ください。


2. 1に設定するIAM Role

今回はEventBridgeからコマンドを実行するためロールによりAWSのAPI実行権限を付与してあげる必要があります。

AWSでは大まかに言うと「誰に」「何の権限」を付与するかを設定する必要がありますが、Roleでは「誰に」の部分を、Policyでは「何の権限」を規定します。

今回はEventBridgeが実行主体なので「誰に」にあたる Principalには "Service: events.amazonaws.com" を指定します。

また今回は "EC2に" "コマンドを実行させる" ことが目的ですので、「何の権限」をの部分には Resource: EC2インスタンス, Action: "ssm:sendCommand" を指定します。

詳細は以下のリファレンスをご参照ください。


3. 1のコマンド実行結果を参照しアラームをあげるルール

1のEventBridgeが起動すると、「イベント」が生成されます。このイベントには様々な情報が含まれるのですが、1のEventBridgeのイベントはコマンドの実行結果が含まれます。

このコマンドの実行結果が「Timedout」か「Failed」だった場合にはSNSを通じてアラームを発報します。

AWSでは常に様々なイベントが生成されているので、どのイベントかを特定する情報を "EventPattern" に記載します。今回は以下の条件でイベントを特定します。
(以下cloudformationのコード再掲です)

      EventPattern:
        source:
          - "aws.ssm"
        detail-type:
          - "EC2 Command Invocation Status-change Notification"
        detail:
          instance-id:
            - !Ref TargetInstanceId
          document-name:
            - !Sub arn:aws:ssm:${AWS::Region}::document/AWS-RunShellScript
          status:
            - "Failed"
            - "Timedout"
  • source: イベントの発行主体(EventBridgeではないことに注意)

  • detail-type: イベントの種類
     (イベントは種類毎に格納される情報が異なるためここで指定する)

  • detail: イベントを特定するための詳細情報

    • instance-id:コマンドを実行したEC2のID

    • document-name: 実行されたSSMドキュメントのARN

    • status: 実行結果

イベントについては以下をご参照ください。


4. 3に設定するIAM Role

3のEventBridgeはコマンド実行が失敗 or Timeoutの場合にSNSを通じてアラームを発報する役割を持つためSNSの権限を付与しています。


感想

オンプレミスやプライベートクラウドの時代にはcronを使ってコマンドを定期実行するのが主流でしたが、エラー時の処理のためにメールを設定したりする必要があり、ちゃんと設計すると面倒だったイメージがありました。

今回、SSMやEventBridgeを活用することで意外と簡単に且つ運用が楽な方法でこれらを実現できてしまうので改めてAWSの利便性を感じました。
EC2の運用に課題をお持ちの方は是非お試し頂ければと思います。

最後に

JDDでは各種エンジニアを募集しております。少しでも気になることがありましたら、カジュアル面談も実施しておりますのでお気軽にご連絡下さい。



この記事に関するお問い合わせはこちらまでお願いします。


Japan Digital Design 株式会社
Technology & Development Division
Engineer

Ken Murata




この記事が気に入ったらサポートをしてみませんか?