EKSに導入したClusterAutoscalerのSlack通知(SNS+Lambda)@Terraform

Posted on
EKS Kubernetes

EKSにClusterAutoscalerを導入しました。 運用にこなれるまではノードの増減をSlack通知で知りたいというモチベーションがあったので、SNSとLambdaを使って通知の仕組みをシュッと作りました。

オーバービュー

次の流れでAutoScalingグループのEC2インスタンスの開始/終了イベントをSlackに通知します。

ClusterAutoscaler Overview

  1. AutoScalingグループの起動/終了通知をSNSに通知する
  2. 受け取ったメッセージをトリガにLambdaを起動
  3. SlackのIncoming WebhooksにメッセージをPOST

構築

AutoScalingグループとSNSの紐付け

AutoScalingグループでインスタンスが起動/終了イベントが発生した時にSNSに通知を送信する設定をおこないます。Terraformではaws_autoscaling_notificationというリソースを使って作成します。

resource "aws_autoscaling_notification" "cluster_notification" {
  group_names = [
    aws_autoscaling_group.cluster_notification.name
  ]

  notifications = [
    "autoscaling:EC2_INSTANCE_LAUNCH",
    "autoscaling:EC2_INSTANCE_TERMINATE",
    "autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
    "autoscaling:EC2_INSTANCE_TERMINATE_ERROR",
  ]

  topic_arn = aws_sns_topic.cluster_notification.arn
}

SNSトピックを作成

紐付ける割きのSNSトピックを作成します。

resource "aws_sns_topic" "cluster_notification" {
  name = "${var.environment}-cluster-notification"
}

resource "aws_sns_topic_subscription" "cluster_notification" {
  topic_arn = aws_sns_topic.cluster_notification.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.cluster_notification.arn
}

Lambdaを作成する

SNSトピック経由で起動するLambdaを作成します。

resource "aws_lambda_function" "cluster_notification" {
  filename         = "lambda_deploy/cluster_notification/lambda.zip"
  function_name    = "${var.environment}-cluster-notification"
  role             = aws_iam_role.cluster_notification.arn
  handler          = "cluster_notification.lambda_handler"
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256
  runtime          = "ruby2.7"
  timeout          = 10

  environment {
    variables = {
      SLACK_HOOK_URL = local.cluster_notification_slack_hook_url[var.environment]
    }
  }
}

resource "aws_lambda_permission" "cluster_notification_sns" {
  statement_id  = "AllowExecutionFromSNS"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.cluster_notification.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.cluster_notification.arn
}


data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "lambda_deploy/cluster_notification/workspace"
  output_path = "lambda_deploy/cluster_notification/lambda.zip"
}

Lambda関数

Incoming WebhooksにPOSTするLambda関数のコードです。

require 'json'
require 'net/http'

def lambda_handler(event:, context:)
    slack_hook_url  = ENV['SLACK_HOOK_URL']

    # payloadからSlackにPOSTする情報を抽出
    payload            = JSON.parse(JSON.parse(event.to_json)['Records'][0]['Sns']['Message']);
    instance_id = payload['EC2InstanceId']
    asg_name    = payload['AutoScalingGroupName']
    cause       = payload['Cause']
    event       = payload['Event']

    case event
    when "autoscaling:EC2_INSTANCE_LAUNCH" then
        message_title = "[スケールアウト]EC2インスタンス起動 :smile:"
    when "autoscaling:EC2_INSTANCE_TERMINATE" then
        message_title = "[スケールイン]EC2インスタンス削除 :ghost:"
    when "autoscaling:EC2_INSTANCE_LAUNCH_ERROR" then
        message_title = "EC2インスタンスの起動に失敗しました :pienface:"
    when "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" then
        message_title = "EC2インスタンスの削除に失敗しました :pienface:"
    end

    message_body = "*" + message_title + "* \n\n" + "InstanceID:" + instance_id + "\nCause:" + cause

    # Incoming WebhooksにPOSTするhashを生成
    post_hash = {
        'blocks' => [
            {
                'type' => 'section',
                'text' => {
                    'type' => 'mrkdwn',
                    'text' => message_body
                }
            }
        ]
    }

    # Slackにpost
    Net::HTTP.post_form(URI.parse(slack_hook_url), { payload: post_hash.to_json })
end

通知結果

ClusterAutoscalerによりAutoScalingグループのDesired Countが書き換えられ、スケールイン/スケールアウトされたタイミングで、以下のメッセージがSlackにPOSTされます。

[スケールアウト]EC2インスタンス起動 :smile: :ghost:
InstanceID:i-XXXXXXXXXXXXXXXXXx
Cause:At 2021-01-27T07:06:47Z a user request update of AutoScalingGroup constraints to min: 2, max: 2, desired: 2 changing the desired capacity from 1 to 2.  At 2021-01-27T07:07:11Z an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 1 to 2.