はじめに
弊社でもプロジェクトごとにSlackを使っていて、GitHubの通知やテスト結果の通知などの開発関係の通知をSlackのチャンネルに流すということをやっています。
CloudWatch の通知も Slack のチャンネルに流せればいいと思っていたのですが、CloudWatch 単体ではその機能が無かったため、以前は amazon-cloudwatch-to-slackを使って Slack に流していました。
amazon-cloudwatch-to-slack は、CloudWatch からの通知を Slack に転送するためのものです。
それで運用してみた結果
- サーバーを立てないといけないのが手間
- 常時サーバーを動かす必要があるでその分のコストが掛かる
という問題がありました。
Lambdaを使うことで、サーバーを立てる必要がなくなったのと、Lambdaはリクエスト回数と処理時間に対して課金なのでコストが抑えられるようになりました。
動かす手順
- KMSで鍵を作成
- KMSでSlackのをWebhook URLを暗号化する
- Lambda 関数のロールを作成
- Lambda 関数を作成
- SNSのtopicにLambda 関数を追加
- CloudWatchの通知にtopicを追加
Blueprintのcloudwatch-to-slackはSlackのWebhook URLをKMSで暗号化するので。KMSの設定もする必要が出ます。
手動でセットアップするのが手間なので、Lambda 関数の作成処理をスクリプト化しました。
セットアップスクリプト
lambda_policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
kms_policy.json.erb
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1443036478000",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"<%= arn %>"
]
}
]
}
setup.rb
require 'rubygems'
require 'aws-sdk'
require 'erb'
require 'zip'
require 'dotenv'
def kms_policy(arn)
ERB.new(File.read('./kms_policy.json.erb')).result(binding)
end
def create_role(role_name, arn)
client = Aws::IAM::Client.new
response = client.create_role({
role_name: role_name,
assume_role_policy_document: File.read('./lambda_policy.json'),
})
client.attach_role_policy({
role_name: role_name,
policy_arn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
})
client.put_role_policy({
role_name: role_name,
policy_name: 'kms',
policy_document: kms_policy(arn),
})
response
end
def create_function(function_name, role, zip_file)
client = Aws::Lambda::Client.new
options = {
function_name: function_name,
runtime: 'nodejs',
role: role,
handler: 'index.handler',
code: {
zip_file: zip_file
},
timeout: 3,
memory_size: 128,
publish: true,
}
client.create_function(options)
end
def create_zip_file(kms_encypted_hook_url, slack_channel)
source = ERB.new(File.read('./index.js.erb')).result(binding)
Zip::OutputStream.write_buffer do |f|
f.put_next_entry('index.js')
f.write(source)
end.string
end
def create_kms_key(alias_name)
client = Aws::KMS::Client.new
response = client.create_key
client.create_alias({
alias_name: "alias/#{alias_name}",
target_key_id: response.key_metadata.key_id
})
response
end
def kms_encrypt(key_id, plaintext)
ciphertext_blob = Aws::KMS::Client.new.encrypt(key_id: key_id, plaintext: plaintext).ciphertext_blob
Base64::strict_encode64(ciphertext_blob)
end
def main(name, web_hook_url, slack_channel)
kms = create_kms_key(name)
iam = create_role(name, kms.key_metadata.arn)
kms_encypted_hook_url = kms_encrypt(kms.key_metadata.key_id, web_hook_url)
zip_file = create_zip_file(kms_encypted_hook_url, slack_channel)
sleep(25)
create_function(name, iam.role.arn, zip_file)
end
Dotenv.load
main(ARGV[0], ARGV[1], ARGV[2])
mainの中でsleepしてるのは、Lambda 関数を作成する処理で、引数で渡したロールがまだ作成できなくてエラーになっていたからです。
おそらく成功したレスポンスで返って来てても、内部的には即時反映されていなくてタイムラグが出てるんじゃないかと思います。
AWS SDK for Ruby V2には処理が終わるまで待って、処理が終わってから次の処理が実行できるwaitersという仕組みがあります。
今回の場合は該当するものがなくて使えなかったです。
Aws::IAM::Client.new.waiter_names
=> [:instance_profile_exists, :user_exists]
アラート表示の修正
Blueprintのcloudwatch-to-slackのままだと通知がシンプルなので
amazon-cloudwatch-to-slackの通知の仕方を参考に、通知を見やすくなるように
Blueprintのcloudwatch-to-slackのコードを修正しました。
var processEvent = function(event, context) {
var message = JSON.parse(event.Records[0].Sns.Message);
var subject = ' ' + event.Records[0].Sns.Subject;
var attachments = [
{
'fallback': subject,
'text' : subject,
'color': message.NewStateValue == 'OK' ? 'good' : 'danger',
'fields': [
{
'title': 'Alarm',
'value': message.AlarmName,
'short': true
},
{
'title': 'Status',
'value': message.NewStateValue,
'short': true
},
{
'title': 'Reason',
'value': message.NewStateReason,
'short': false
}
]
}
];
var slackMessage = {
channel: slackChannel,
text: subject,
attachments: attachments
};
postMessage(slackMessage, function(response) {
if (response.statusCode < 400) {
console.info('Message posted successfully');
context.succeed();
} else if (response.statusCode < 500) {
console.error("Error posting message to Slack API: " + response.statusCode + " - " + response.statusMessage);
context.succeed(); // Don't retry because the error is due to a problem with the request
} else {
// Let Lambda retry
context.fail("Server error when processing message: " + response.statusCode + " - " + response.statusMessage);
}
});
};
セットアップスクリプトを実行する
IAMユーザに権限を付ける
IAMでAPIを実行できるユーザーを作成して、そのユーザーのアクセスキーを作成します。
今回のスクリプトで使ってる、LambdaとKMSとIAMの実行できる権限を付けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1443036478000",
"Effect": "Allow",
"Action": [
"kms:CreateKey",
"kms:CreateAlias",
"kms:Encrypt",
"iam:CreateRole",
"iam:PassRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"lambda:CreateFunction"
],
"Resource": "*"
}
]
}
実行する
IAMでAPIを実行できるユーザーのアクセスキーを設定します。
$ aws configre
awsコマンドはインストールする必要があります。
実行例
$ bundle exec ruby setup.rb 'cloudwatch-to-slack' 'hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX' '#alert'
セットアップが終わったら、先ほど追加したポリシーとアクセスキーを削除します。
SNSの設定を追加
まずtopicを作成します。
とのtopicに対して
Lambda 関数をサブスクリプションに追加します。
CloudWatchのアラーム作成
通知の送信先を先ほど作成したtopicを指定します。
これでCloudWatchからSlackに通知されるようになります。
リポジトリ
https://github.com/f96q/cloudwatch-to-slack
最後に
Lambdaを使うことで、今までサーバーを立ててやっていたことが、サーバーを立てなくても済むようになることが便利でした。
宣伝
DocBaseとは
情報共有を活発にし、チームを育てるをコンセプトにした情報共有サービスです。
柔軟な権限設定と使いやすさ、社内の人も社外の人も全員を招待できるから、さまざまな人やツールに散らばっていた情報を一元化できます。
積極的な情報共有と業務の効率化を実現し、チームの成長を促します。
詳しくはこちらから。
https://docbase.io
このエントリーに対するコメント
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
「いいね!」で応援よろしくお願いします!