👌

ASGのインスタンス起動・停止時にSlackに通知するCloudFormationテンプレート

takeyuweb2023/01/20に公開

AWS Auto Scalingを利用することで、Amazon EC2 インスタンスを自動的に起動および停止させることでオートスケーリングやオートヒーリングを実現できます。

EC2インスタンスの起動および終了をSlackに通知することで、インスタンスの入れ替えを知ったり、入れ替えが頻発するなど障害発生の検知に役立ちます。

https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-sns-notifications.html

この記事では、SNSとLambdaの連携、および Lambda から Slack Incoming Webhook を叩く操作を、CloudFormationテンプレートでどのように表現するのかを紹介します。

通知の仕組み

  1. ASGのアクティビティ通知先として、Amazon SNS トピックを指定
  2. Amazon SNS トピックをLambda関数で購読
  3. Lambda関数でSlack Webhook URLにPOST

SNSトピックとLambda関数を作成するCloudFormationテンプレート

次のコードは、ASGのアクティビティ通知先として指定できるSNSトピックと、トピックを購読し、イベントの発生をトリガーに起動するLambdaを作成するテンプレートです。
テンプレートパラメータとして Slack Incoming Webhook のURLを与えます。

Parameters:
  WebhookUrl:
    Type: String

Resources:
  TopicEC2ASGNotification:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: asg-notification
  SNSSubscriptionEC2ASGNotification:
    Type: AWS::SNS::Subscription    
    Properties:
      Endpoint: !GetAtt LambdaEC2ASGNotification.Arn
      Protocol: lambda
      TopicArn: !Ref TopicEC2ASGNotification
  LambdaEC2ASGNotification:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |+
          const url = require('url');
          const https = require('https');
          const util = require('util');
        
          const { WebhookUrl, Region } = process.env;

          const debug = (key, object) => { console.log(`DEBUG: ${key}\n`, JSON.stringify(object)); }

          const notifySlack = async (subject, message) => {
            const detail = `AccountId: ${message.AccountId}\nDescription: ${message.Description}\nCause: ${message.Cause}`;
            const link = `https://${Region}.console.aws.amazon.com/ec2/home?region=${Region}#AutoScalingGroupDetails:id=${message.AutoScalingGroupName};view=activity`;
            const blocks = {
              blocks: [
                {
                  type: "section",
                  text: {
                    type: "mrkdwn",
                    text: subject + "\n```" + detail + "```\n<" + link + "|Link>"
                  }
                }
              ]
            };

            const postData = JSON.stringify(blocks);
            const url = new URL(WebhookUrl);
            const requestOptions = {
              hostname: url.host,
              port: 443,
              path: url.pathname,
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(postData)
              }
            };
            return new Promise((resolve, reject) => {
              const req = https.request(requestOptions, (res) => {
                const data = [];
                res.on("data", (d) => data.push(d));
                res.on("end", () => resolve(data.join("")));
              });
              req.on("error", reject);
              req.write(postData);
              req.end();
            });
          };

          exports.handler = async (event, context, callback) => {
            console.log("INFO: request Recieved.\nEvent:\n", JSON.stringify(event));
            context.callbackWaitsForEmptyEventLoop = false;
            const snsData = event.Records[0].Sns;
            const message = JSON.parse(snsData.Message);
            const results = await notifySlack(snsData.Subject, message);
            debug("results", results);
            return results;
          };
      Environment:
        Variables:
          WebhookUrl: !Ref WebhookUrl
          Region: !Ref AWS::Region
      Handler: index.handler
      Role: !GetAtt LambdaEC2ASGNotificationRole.Arn
      Runtime: "nodejs16.x"
      MemorySize: 128
      Timeout: 30
  LambdaEC2ASGNotificationRole:
    Type: "AWS::IAM::Role"
    DeletionPolicy: Retain
    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"
  LambdaPermissionEC2ASGNotification:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt LambdaEC2ASGNotification.Arn
      Principal: sns.amazonaws.com
      SourceArn: !Ref TopicEC2ASGNotification

Lambda実行テスト

実行テスト結果

作成されたLambda関数のテストに次のテストイベントJSONを渡すと、通知を確認できます。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:012345678901:TEST-ASG-TopicEC2ASGNotification-Gx4PHPpQDYxR:800000000-0000-0000-0000-000000000000",
      "Sns": {
        "Type": "Notification",
        "MessageId": "ece92d9b-504b-56fc-8d23-71dbd88bf54a",
        "TopicArn": "arn:aws:sns:ap-northeast-1:012345678901:TEST-ASG-TopicEC2ASGNotification-Gx4PHPpQDYxR",
        "Subject": "Auto Scaling: termination for group \"EC2AutoScalingGroup\"",
        "Message": "{\"Origin\":\"AutoScalingGroup\",\"Destination\":\"EC2\",\"Progress\":60,\"AccountId\":\"012345678901\",\"Description\":\"Terminating EC2 instance: i-0000000000000000\",\"RequestId\":\"d413bc11-0548-4ec3-810f-1863ff8bbe0f\",\"EndTime\":\"2023-01-17T11:29:04.571Z\",\"AutoScalingGroupARN\":\"arn:aws:autoscaling:ap-northeast-1:012345678901:autoScalingGroup:a563dcba-2eb7-4d10-8f8d-c2e79a6ff460:autoScalingGroupName/EC2AutoScalingGroup\",\"ActivityId\":\"d413bc11-0548-4ec3-810f-1863ff8bbe0f\",\"StartTime\":\"2023-01-17T11:22:37.321Z\",\"Service\":\"AWS Auto Scaling\",\"Time\":\"2023-01-17T11:29:04.571Z\",\"EC2InstanceId\":\"i-0000000000000000\",\"StatusCode\":\"MidTerminatingLifecycleAction\",\"StatusMessage\":\"\",\"Details\":{\"Subnet ID\":\"subnet-0a7508392f1c3bbba\",\"Availability Zone\":\"ap-northeast-1a\"},\"AutoScalingGroupName\":\"EC2AutoScalingGroup\",\"Cause\":\"At 2023-01-17T11:22:37Z an instance was taken out of service in response to an instance refresh.  At 2023-01-17T11:22:37Z instance i-0000000000000000 was selected for termination.\",\"Event\":\"autoscaling:EC2_INSTANCE_TERMINATE\"}",
        "Timestamp": "2023-01-17T11:29:04.630Z",
        "SignatureVersion": "1",
        "Signature": "XiTi7Zs6lRwOuN05fGROkZK8Wy9StvI20U79tzCgHk0LCiPPSh0izW+8qLucnCp2HMO44Qq+ju6AyCExer+HpVpjDZIqh3DX28/Cv0KgkVCzR5ZGKWW5W7P1CIgGya7rl2CGnL8RqEVpcmOyjKUsQdDfdSYNsq/pTd2U+uQt+0DO27n1A/nU4hCF+nZV8g7+2KseDOD8KZldErh/lSx6Y5u5d4OUyuDsUaPwugqvSn8r7f1242Iwky561W1xh07w28Y/+RQPfcDlFSXHTFgQE/WXcpINJGMG7trZj+cGYXHy5JIHGE5SGFp/SoMJt2mIYqkCuaIObbBwFgHGUvBiqQ==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-000000000000000000000000000000.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:012345678901:TEST-ASG-TopicEC2ASGNotification-Gx4PHPpQDYxR:800000000-0000-0000-0000-000000000000",
        "MessageAttributes": {}
      }
    }
  ]
}

EC2 ASGを作成して通知先としてトピックを設定するCloudFormationテンプレート

ASGのアクティビティ通知先は NotificationConfigurations で次を指定します。

  • 通知するイベントの種類
  • 通知先のトピックARN

ASGを作成するテンプレートは案件により様々ですし長くなるので、通知の設定箇所のみ紹介します。

  EC2AutoScalingGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      # (省略)
      NotificationConfigurations:
        - NotificationTypes:
            - autoscaling:EC2_INSTANCE_LAUNCH
            - autoscaling:EC2_INSTANCE_LAUNCH_ERROR
            - autoscaling:EC2_INSTANCE_TERMINATE
            - autoscaling:EC2_INSTANCE_TERMINATE_ERROR
          TopicARN: !Ref TopicEC2ASGNotification

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-notificationconfigurations.html

マネジメントコンソール上で設定する方法

CloudFormationを使用せずASGを構成している場合、公式ドキュメントの通知を送信するように Auto Scaling グループを設定するの手順に従って設定できます。

タケユー・ウェブ株式会社

Ruby on Rails や AWS が得意なWebサービス受託開発会社です。 中小規模のWebサービスの新規開発の他、他の個人開発者などから引き継いで保守運用を行ったりしています。 新規開発、お手伝いや顧問、レガシーなRailsプロジェクトの保守など、ニーズにあわせて対応できます。ご相談ください。

Discussion

ログインするとコメントできます