#はじめに
こんにちは。
Mikatus 株式会社でインフラ担当として、AWS を中心にインフラの構築、運用保守等を担当しています。
今回は MySQL のスロークエリを Chatwork へ通知してみた件を書いてみようと思います。
当社システムでは Amazon RDS for MySQL を使用しており、スロークエリに関しては監視ツールを利用して発生状況は監視していましたが、リアルタイムに通知をすることで継続的な改善が行えるように、Chatwork へ通知してみました。
また、先日 AWS CLI v2 プレビュー版が発表されたので、せっかくなので CLI v2 を使って AWS リソースを作成してみました。
##この記事について
この記事では、AWS CLI v2 を用いて AWS Lambda の設定及び Amazon CloudWatch Logs の設定を操作した手順を記載しています。
Amazon RDS のログエクスポート機能、Chatwork の API設定の詳細な手順については触れていません。
##実際の Chatwork 通知内容
最終的に Chatwork に通知される内容です。
関係者が参加しているグループチャットに、bot さんがスロークエリの内容を投稿してくれます。
基本的には CloudWatch Logs に出力している内容をそのまま出力しています。
Time のみ UTC でスロークエリログが出力されるため、JST に変換して通知しています。
##処理概要図
RDS から CloudWatch Logs への出力は RDS のログのエクスポート機能を利用しています。
CloudWatch Logs ではサブスクリプションフィルタを使用して Lambda 関数へ送信し、Python で書かれた関数でログメッセージをパースし Chatwork へ通知しています。
RDS のパラメータグループの long_query_time にて、CloudWatch Logs へ出力するスロークエリを制御できますが、RDS の設定は比較的緩めにして、Lambda 側でしきい値を設け、制御できるようにしています。
(今回は RDS の long_query_time を「5」秒にして、Lambda のしきい値を「10」秒にしました。)
#設定内容
設定した内容を記載していきます。
##AWS CLI v2 のインストール
手元の mac で AWS CLI v2 をインストールします。
インストール手順はこちら
注意点として、まだプレビュー版なので実稼働環境では使用しないでね、とのことです。
テストのプレビューおよび評価として、AWS CLI バージョン 2 が提供されます。現時点では、これを実稼働環境では使用しないことをお勧めします。
下記バージョンがインストールされました。
$ aws2 --version
aws-cli/2.0.0dev2 Python/3.7.4 Darwin/17.7.0 botocore/2.0.0dev1
印象として、CLI v1 では Python のバージョン依存などがあり、若干導入時の手間がありましたが、
CLI v2 では Python がなくてもインストールできるようになっており、だいぶ楽になっていました。
##IAMロールを作成する
###設定値
- ロール名
- post-slowquery-to-chatwork-LambdaRole
- 信頼されたエンティティ
- lambda
- Attach アクセス権限ポリシー
- AWSLambdaBasicExecutionRole (AWS managed policy)
###ロール作成
$ IAM_ROLE_POLICY_DOC="iam-role-policy.json"
# 信頼されたエンティティとして Lambda を設定する
$ cat <<EOF > ${IAM_ROLE_POLICY_DOC}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
$ IAM_ROLE_NAME="post-slowquery-to-chatwork-LambdaRole"
$ IAM_POLICY_ARN="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
# IAMロールを作成する
$ aws2 iam create-role \
--role-name ${IAM_ROLE_NAME} \
--assume-role-policy-document file://./${IAM_ROLE_POLICY_DOC}
# 作成した IAMロールの ARN を取得する
$ IAM_ROLE_ARN=`aws2 iam get-role \
--role-name ${IAM_ROLE_NAME} \
--query 'Role.Arn' \
--output text`
# IAMロールに AWSLambdaBasicExecutionRole をアタッチする
$ aws2 iam attach-role-policy \
--role-name ${IAM_ROLE_NAME} \
--policy-arn ${IAM_POLICY_ARN}
##Lambda 関数を作成する
ランタイムは python3.7 を使用します。
###コード
import json
import base64
import gzip
import re
import os
import datetime
import urllib
import urllib.request
LONG_QUERY_TIME = int(os.environ['LONG_QUERY_TIME'])
chatwork_endpoint = "https://api.chatwork.com/v2"
chatwork_apikey = os.environ['chatwork_apikey']
chatwork_roomid = os.environ['chatwork_roomid']
path = "/rooms/{0}/messages".format(chatwork_roomid)
def lambda_handler(event, context):
print(json.dumps(event))
#Base64 でエンコードされ、gzip 形式で圧縮されているため、中身を抽出する
log_events = json.loads(gzip.decompress(base64.b64decode(event['awslogs']['data'])))
db_name = log_events['logStream']
message = log_events['logEvents'][0]['message']
#ログからクエリ時間を抽出する
query_time = re.search(r'Query_time: ([0-9]+.[0-9]+)', message)
#環境変数で指定した時間より大きい場合(今回は 10秒以上)に通知を行う
if LONG_QUERY_TIME < float(query_time.group(1)):
timestamp = re.search(r'timestamp=([0-9]+);', message)
#timestamp を JST 時刻へ変換する
date = datetime.datetime.fromtimestamp(int(timestamp.group(1))) + datetime.timedelta(hours=9)
log_message = re.sub(r'# Time:.*\n', '# Time: %s(JST)\n' % str(date), message)
post_to_chatwork({'body': '[info][title]%s[/title]%s[/info]' % (db_name, log_message)})
def post_to_chatwork(data=None):
try:
if data != None:
data = urllib.parse.urlencode(data).encode('utf-8')
headers = {"X-ChatWorkToken": chatwork_apikey}
req = urllib.request.Request(chatwork_endpoint + path, data=data, headers=headers)
with urllib.request.urlopen(req) as res:
print(res.read().decode("utf-8"))
return
except urllib.error.HTTPError as e:
print('Error code: %s' % (e.code))
sys.exit('post_to_chatwork Error')
###環境変数
- LONG_QUERY_TIME
- Chatwork へ通知するしきい値(秒)
- 例:10
- chatwork_apikey
- Chatwork の APIトークン
- chatwork_roomid
- Chatwork のルーム ID
###Lambda 関数作成
$ LAMBDA_FUNCTION_NAME="post-slowquery-to-chatwork"
$ LAMBDA_RUNTIME="python3.7"
$ LAMBDA_ZIP_FILE="${LAMBDA_FUNCTION_NAME}.zip"
$ LAMBDA_HANDLER="lambda_function.lambda_handler"
$ LAMBDA_ENV="{\
LONG_QUERY_TIME=10,\
chatwork_apikey=XXXXX,\
chatwork_roomid=XXXXX}"
# コードを zip 圧縮する
$ zip ${LAMBDA_ZIP_FILE} lambda_function.py
# Lambda 関数を作成する
$ aws2 lambda create-function \
--function-name ${LAMBDA_FUNCTION_NAME} \
--runtime ${LAMBDA_RUNTIME} \
--zip-file fileb://${LAMBDA_ZIP_FILE} \
--handler ${LAMBDA_HANDLER} \
--environment Variables=${LAMBDA_ENV} \
--role ${IAM_ROLE_ARN}
##CloudWatch Logs サブスクリプションフィルタを設定する
$ LOG_GROUP_NAME="/aws/rds/instance/[rdsinstance]/slowquery"
$ LOG_FILTER_NAME="LambdaStream_post-slowquery-to-chatwork"
$ LAMBDA_ARN=`aws2 lambda get-function \
--function-name ${LAMBDA_FUNCTION_NAME} \
--query 'Configuration.FunctionArn' \
--output text`
$ LOG_ACTION="lambda:InvokeFunction"
$ LOG_PRINCIPAL="logs.ap-northeast-1.amazonaws.com"
$ SOURCE_ACCOUNT=`aws sts get-caller-identity \
--query 'Account' \
--output text`
$ SOURCE_ARN="arn:aws:logs:ap-northeast-1:${SOURCE_ACCOUNT}:log-group:${LOG_GROUP_NAME}:*"
# CloudWatch Logs に、関数を実行するためのアクセス権限を付与する
$ aws2 lambda add-permission \
--function-name ${LAMBDA_FUNCTION_NAME} \
--statement-id ${LAMBDA_FUNCTION_NAME} \
--action ${LOG_ACTION} \
--principal ${LOG_PRINCIPAL} \
--source-arn ${SOURCE_ARN} \
--source-account ${SOURCE_ACCOUNT}
# サブスクリプションフィルタを作成する。フィルターパターンは空("")にします
$ aws2 logs put-subscription-filter \
--log-group-name ${LOG_GROUP_NAME} \
--filter-name ${LOG_FILTER_NAME} \
--filter-pattern "" \
--destination-arn ${LAMBDA_ARN}
以上で設定が完了です。
#さいごに
今回、RDS、Lambda、CloudWatch Logs を利用して、スロークエリを通知させてみました。
これまではスロークエリが発生したら、CloudWatch Logs にログを見に行くというちょっとつらい運用を行なっていたので、これが Chatwork に通知されることで、つらさは軽減されそうです。
将来的にはログ解析ツールなどを駆使して、より詳細な可視化ができればと思っておりますが、またの機会に実装してみようと思います。
AWS CLI v2 についても、今回使用してみた範囲内においては、メリットはあまり享受できませんでしたが、積極的に使ってみようと思います。