EKSにClusterAutoscalerを導入しました。 運用にこなれるまではノードの増減をSlack通知で知りたいというモチベーションがあったので、SNSとLambdaを使って通知の仕組みをシュッと作りました。
オーバービュー
次の流れでAutoScalingグループのEC2インスタンスの開始/終了イベントをSlackに通知します。
- AutoScalingグループの起動/終了通知をSNSに通知する
- 受け取ったメッセージをトリガにLambdaを起動
- 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.