🚨

【ALB・CloudWatch・Lambda】サーバーダウン等の異常をALBで検知した際にSlackに通知する

に公開

自己紹介

はじめまして、はると申します。
駆け出しエンジニアとして働き始めて約3ヶ月が経過しました🐣

概要

サーバーダウンにいち早く気づき、異常の対処や再起動ができるために、ALBの異常を検知した際に、Slackに通知を送る機能を実装します。
下記の構成でEC2にデプロイされており、ロードバランサー(ALB)で1分毎にHTTPリクエストを送信し、サーバーが起動しているか確認する機能はすでに導入されている前提とします。

今回のインフラ構成図のイメージです。

環境

  • Docker
  • Ruby 3.2.3
  • Rails 7.1.3
  • PostgreSQL

Slackに通知するLambdaの作成

Incoming Webhookの設定

  1. Slackアプリ内で、Botの通知メッセージを送信させたいチャンネルを右クリックし、チャンネル詳細を表示するを開きます。

  2. インテグレーションタブを開き、アプリを追加するを選択します。

  3. Incoming Webhookを検索し、インストールを選択します。

  4. ブラウザに遷移するので、Slackに追加を選択します。

  5. 通知メッセージを送信させたいチャンネルを選択し、Incoming Webhookインテグレーションの追加を選択します。

  6. 次のページで、セットアップの手順内にある、Webhook URLを控えておきます。(後から確認することもできます)
    確認できたら、一番下までスクロールし、設定を保存するをクリックします。
    (任意で、Botの名前やアイコンのカスタマイズもできます)

  7. これでSlack側の設定は以上です🎉

Lambda用のIAMロールを作成

  1. IAMにアクセスし、サイドバーのロールロールの作成を開きます。
  2. 信頼されたエンティティタイプはAWSサービス、ユースケースでLambdaを選択します。
  3. 許可ポリシーは、AWSLambdaBasicExecutionRoleを検索し、選択します。
  4. ロールの詳細は、任意のロール名を入力します。他はデフォルトのまま使用します。

Lambdaの関数を作成

  1. Lambdaにアクセスし、関数の作成を開きます。

  2. 一から作成を選択し、任意の関数名を入力し、任意のランタイム(今回はRubyとします)、アーキテクチャを選択します。
    デフォルトの実行ロールの変更では、既存のロールを使用するを選択し、先ほど作成したIAMロールを選択します。詳細設定はデフォルトのままにします。
    このページでの設定内容は以下の画像の通りです。入力を終えたら、関数の作成をします。

  3. 関数が作成されたら、コードソースを下記の内容に編集します。
    ここで、先ほど控えておいたWebhook URLが必要になりますが、直接入力せず環境変数を使用します。
    編集後は、Deployをクリックするのを忘れずに!

    require 'json'     # JSONの生成に使用
    require 'net/http' # HTTPリクエスト送信に使用
    require 'uri'      # URIの読み込みに使用
    
    # CloudWatchのアラーム情報を引数として受け取りLambda関数を実行するメソッド
    def lambda_handler(event:, context:) # eventはLambda関数実行のトリガーとなったイベントのデータ
      puts "Received Event: #{event.inspect}"
    
      alarm_data = event.dig('alarmData') || {}                                   # alarmDataを取得
      alarm_name = alarm_data.dig('alarmName') || "No alarm name provided"        # alarmNameを取得
      state_value = alarm_data.dig('state', 'value') || "No state value provided" # state下のvalueを取得
      reason = alarm_data.dig('state', 'reason') || "No reason provided"          # state下のreasonを取得
    
      slack_message = "Alarm Name: #{alarm_name}\nState: #{state_value}\nReason: #{reason}" # Slackに送るメッセージを生成
      post_slack(slack_message) # ここで下記に定義しているメソッドを呼び出す
    end
    
    # Slackに通知するメソッド
    def post_slack(message)
      webhook_url = ENV['SLACK_WEBHOOK_URL'] # ここで後に取得したSlackのWebhookURLを使用します
      uri = URI.parse(webhook_url)
      header = {'Content-Type' => 'application/json'}
      send_data = {
        "text" => "<!channel> #{message}", # <!channel>を入れることで、@channelのメンション入りで通知できます
        "link_names": 1
      }.to_json # ハッシュをJSON文字列に変換
    
    	# HTTPリクエストを送信
      Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
        request = Net::HTTP::Post.new(uri, header)
        request.body = send_data         # 先ほどJSONに変換したデータを設定
        response = http.request(request) # リクエストを送信し、レスポンスを受け取る
        puts response.body               # 受け取ったレスポンスをログに出力(CloudWatchのロググループでLambda実行のログを見ることができる)
      end
    end
    
    

  4. 環境変数の設定
    設定タブ>サイドバーの環境変数から、環境変数を登録します。
    キーはSLACK_WEBHOOK_URL、値はIncoming WebhookのページよりコピーしたWebhook URLを使用します。

  5. 動作テスト
    テストタブを開き、新しいイベントを作成を選択し、任意のイベント名を設定します。
    テンプレートオプションは今回は使用せず、イベントJSONにテスト用のJSONデータを直接記載していきます。

    下記は、CloudWatchがアラーム状態になった時にLambdaに送られてくるデータを一部加工したテスト用のJSONです。

    {
      "source": "aws.cloudwatch",
      "alarmArn": "test-alb-alarm",
      "accountId": "1234567890",
      "time": "2024-04-09T08:56:06.386+0000",
      "region": "ap-northeast-1",
      "isTest": true,
      "testDescription": "This is a test event for Lambda function.",
      "alarmData": {
        "alarmName": "test-alb-alarm",
        "state": {
          "value": "ALARM",
          "reason": "Threshold Crossed: 1 out of the last 1 datapoints [1.0 (09/04/24 08:54:00)] was greater than or equal to the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition).",
          "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2024-04-09T08:56:06.384+0000\",\"startDate\":\"2024-04-09T08:54:00.000+0000\",\"statistic\":\"Minimum\",\"period\":60,\"recentDatapoints\":[1.0],\"threshold\":1.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2024-04-09T08:54:00.000+0000\",\"sampleCount\":2.0,\"value\":1.0}]}"
        },
        "previousState": {
          "value": "OK",
          "reason": "Threshold Crossed: 1 out of the last 1 datapoints [0.0 (08/04/24 10:13:00)] was not greater than or equal to the threshold (1.0) (minimum 1 datapoint for ALARM -> OK transition).",
          "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2024-04-08T10:15:06.384+0000\",\"startDate\":\"2024-04-08T10:13:00.000+0000\",\"statistic\":\"Minimum\",\"period\":60,\"recentDatapoints\":[0.0],\"threshold\":1.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2024-04-08T10:13:00.000+0000\",\"sampleCount\":2.0,\"value\":0.0}]}"
        },
        "configuration": {
          "metrics": [
            {
              "id": "testid1234567890",
              "metricStat": {
                "metric": {
                  "namespace": "AWS/ApplicationELB",
                  "name": "UnHealthyHostCount",
                  "dimensions": {
                    "TargetGroup": "targetgroup/test-app/1234567890",
                    "LoadBalancer": "app/test-app-ALB/1234567890",
                    "AvailabilityZone": "ap-northeast-1a"
                  },
                  "period": 60,
                  "stat": "Minimum"
                },
                "returnData": true
              }
            }
          ]
        }
      }
    }
    

  6. Lambdaのアクセス権限の追加
    最後に、CloudWatchからLambdaの関数を実行できるためのアクセス権限を、Lambdaに追加します。
    設定タブを開き、サイドバーのアクセス権限リソースベースのポリシーステートメント内にあるアクセス権限を追加をクリックします。
    AWSアカウントを選択し、ステートメントIDは任意のIDを入力し、
    プリンシパルはlambda.alarms.cloudwatch.amazonaws.comと入力、
    アクションはlambda:InvokeFunctionを選択します。
    これで、Lambdaの設定は以上です!🎊

CloudWatchのアラームを作成

では、すでに動いているALBの異常を検知してアラームためのアラームを作成していきます。

  1. CloudWatchにアクセスし、サイドバーのアラームすべてのアラームを開き、アラームの作成を選択します。
  2. メトリクスの選択で、UnHealthyHostCountと検索し、AppELB 別、AZ 別、TG 別メトリクスを選択します。
    該当のALBのUnHealthyHostCountにチェックを入れてメトリクスの選択に進みます。
  3. 下記画像のように設定します。
    条件は、動作テストしやすいようにするため、最初は1分以内に1データポイントにしています。

  4. 次のページ、アクションの設定では、先ほど作成したLambdaを選択します。
  5. 次のページでは、アラームに任意の名前を設定します。
    次のプレビュー画面で入力内容が確認できたら作成完了です!
  6. 動作テスト
    試しに本番環境EC2アプリ内でDockerを停止します。
  7. ALB側で異常を検知していることを確認します。
    その後、CloudWatchのアラームがアラーム状態になることを確認します。
    アラーム状態が続くと設定したSlackチャンネルにメッセージが届くことを確認します。
  8. OKだったらCloudWatchアラームの条件を、5分以内に5回など、任意の条件に設定し直します。これで終了です!🎉

まとめ

サーバーダウン時に通知が来るようにという目的で、インフラの知識が全くない中技術記事を参考に実装してみました。
S3位しか触ったことがなく、苦手意識のあったAWSでしたが、今回を機に調べたり触れたことで苦手意識が少しだけ薄れました🥺
理解はまだまだ浅いため、これからも学習を続けていきます!

参考記事

Discussion