メインコンテンツまでスキップ

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

· 約3分
ごとけん
Solutions Architect

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.