Posted at

RDSのスロークエリ増加をLambdaでSlackに通知

More than 1 year has passed since last update.


概要

Lambdaのスケジュール実行でRDS(MySQL)のスロークエリログを監視し

サイズが一定以上ならSlackに通知する仕組みを作ります。

Lambdaファンクションのランタイムには'Python 2.7'を使います。


事前準備


  • RDS(MySQL)

  • EC2(MySQLにアクセスできればOK)

  • Slackチーム


手順


RDSでスロークエリ出力を設定する

RDSのインスタンスに設定されているパラメータグループを変更します。

以下の3項目を変更してください。

パラメータ
変更前
変更後
説明

slow_query_log
engine-default
1
スロークエリを出力します

long_query_time
engine-default
2
2秒以上のクエリをスロークエリとします

log_outut
TABLE
FILE
スロークエリはファイルとして出力します

インデックスを使わないクエリを出力するlog_queries_not_using_indexesもありますが

今回の判断軸はクエリが早いか遅いかのみなので、割愛します。

試しにスロークエリを発生させてみます。

意図的にスロークエリを発生させるためにSLEEP()を使いました。

これで実行時間を伸ばしたクエリを自作のテーブルに投げます。


テーブルの作成(既存であれば不要)

mysql> CREATE TABLE item (id int);

Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO item VALUES(1);
Query OK, 1 row affected (0.01 sec)



意図的なスロークエリ

mysql> SELECT id, SLEEP(5) FROM item;

+------+----------+
| id | SLEEP(5) |
+------+----------+
| 1 | 0 |
+------+----------+
1 row in set (5.00 sec)

これでRDSインスタンスのLogsからmysql-slowquery.logを開くと

RDS_·_AWS_Console.png

以下のようにスロークエリが記録されるのがわかります。

RDS_·_AWS_Console2.png


SlackのWebhook用URLを準備する

以降の手順で使用するLambdaファンクションにおいて

以下のような準備が必要であると記載されています。


Follow these steps to configure the webhook in Slack:

1. Navigate to https://<your-team-domain>.slack.com/services/new

2. Search for and select "Incoming WebHooks".

3. Choose the default channel where messages will be sent and click "Add Incoming WebHooks Integration".

4. Copy the webhook URL from the setup instructions and use it in the next section.

Follow these steps to encrypt your Slack hook URL for use in this function:

1. Create a KMS key - http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html.

2. Encrypt the event collector token using the AWS CLI.

$ aws kms encrypt --key-id alias/<KMS key name> --plaintext "<SLACK_HOOK_URL>"

Note: You must exclude the protocol from the URL (e.g. "hooks.slack.com/services/abc123").

3. Copy the base-64 encoded, encrypted key (CiphertextBlob) to the ENCRYPTED_HOOK_URL variable.

4. Give your function's role permission for the kms:Decrypt action.


全体を要約すると、Slackの'Incoming Webhooks'でURLを取得し

それをAWSのKMSという暗号化サービスで暗号化しましょうという流れです。

下記ページの説明がとてつもなくわかりやすいので参考にしました。

3節から5節まででURLの取得と暗号化が行われています。

LambdaのBlueprintを使って、CloudWatchのアラームをSlackに投稿


Lambdaファンクションを作成する

RDSを配置しているリージョンでLambdaのコンソールを開き

'Create a Lambda Function'を選択します。

ブループリントの中のcloudwatch-alarm-to-slack-pythonを選択します。

(参考のために使用しますが、cloudwatchは使いません)

Lambda_Management_Console.png

トリガー設定でのSNSも不要なので、削除します。

Lambda_Management_Console2.png

NameとDescriptionは適当につけましょう。

Runtimeは前述の通り'Python 2.7'です。

肝心のコードの部分は以下の通りです。

大部分はブループリントのコードをそのまま使用しています。

説明はインラインで記載しました。


Lambdaファンクション

import boto3

import json
import logging

from base64 import b64decode
from urllib2 import Request, urlopen, URLError, HTTPError

# スロークエリを調べたいDBのID
RDS_ID = "{id}"

# この数値を超えるログを、スロークエリ増加のトリガーとする
THRESHOLD_BYTE = {byte}

# 暗号化したSlackのWebhookURL
ENCRYPTED_HOOK_URL = '{url}' # Enter the base-64 encoded, encrypted key (CiphertextBlob)

# 通知をしたいSlackチャンネル
SLACK_CHANNEL = '{channel}' # Enter the Slack channel to send a message to

# 暗号化されたURLをKMSでデコード
HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

client = boto3.client("rds")

# 対象のDBでサイズが閾値を超えるスロークエリログを取得
log_files = client.describe_db_log_files(
DBInstanceIdentifier=RDS_ID,
FileSize=THRESHOLD_BYTE,
FilenameContains='slowquery')

# 取得できたログがなければここで終了
if len(log_files['DescribeDBLogFiles']) == 0:
logger.info("Slow query was not found.")
return

# Slackで通知する内容の設定
slack_message = {
'channel': SLACK_CHANNEL,
'username': 'Alert from RDS',
'icon_emoji': ':exclamation:',
'text': 'Slow query was found.'
}

# Slackへのリクエスト
req = Request(HOOK_URL, json.dumps(slack_message))

try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)


ロールは新しく作成し、既に設定されている通り

KMS decryption permissionを付与してください。

その他はすべてデフォルトの設定で、VPCも未使用です。

Lambda_Management_Console.png

これでLambdaファンクションが実行できるようになりましたが

このまま実行すると権限の問題でこけてしまいます。

IAMのコンソールから、先程作成したロールにRDSへのアクセス権限を追加します。

aaa.png

追加するポリシーはAmazonRDSReadOnlyAccessです。

Policy.png


Lambdaのスケジュール実行を設定する

作成したLambdaファンクションのトリガーを作成します。

Triggersタブの'Add Trigger'をクリックします。

Trigger.png

トリガータイプとして'CloudWatch Events - Schedule'を選択し

表示された項目を埋めていきます。

setting.png

RDSのslowquery.logはデフォルトで1時間間隔で24時間分のデータが残るため

平日の1日ごとにファンクションが実行されるように設定しました。

Cronの時刻設定はUTCであることに注意が必要です。

実行間隔は運用方法に応じて(コードも含めて)変更してください。

以上の設定で、スロークエリがあればSlackに通知が行われます。

slack.png


まとめ

スロークエリログの増加をチャット通知する仕組みについて書きました。

なお、download_db_log_file_postion()メソッドを使えば

ログの中身を直接チャット通知することもできそうですが

DBのスキーマやデータに関する情報を載せるのに抵抗があったため避けました。

そもそも通知の発生頻度は高くない(高い状態がおかしい)ので

存在さえ認知できれば十分かなと思っています。