AWS CLI の s3 sync
を使うだけでよかった。
s3 cp
とか使って頑張る必要なかった。
s3_bucket_url=s3:/your-app-name/performlogs metrics_root_dir=/var/log/your-app-name/perform aws s3 sync $s3_bucket_url $metrics_root_dir
CloudWatch のメトリクスを Elasticsearch に突っ込むスクリプトを Python の標準ライブラリだけ使って書いたけど、 Elasticsearch クライアントを使えばよかったことに今さら気付いた。
pip install elasticsearch
でインストールしたら、スクリプトはこんな風に書き直すことができた。
# -*- coding: utf-8 -*- import os import sys import json from elasticsearch import Elasticsearch ELASTICSEARCH_URL = "localhost:9200" METRICS_ROOT_DIR = "/var/log/perform/my-app-name" INSTANCES = [ "rds", ] METRICS = [ "CPUUtilization", "DatabaseConnections", "DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency", "NetworkReceiveThroughput", "NetworkTransmitThroughput", ] es = Elasticsearch(ELASTICSEARCH_URL) def post_datapoint(index_name, type_name, datapoint): # Elasticsearch クライアントを使ってインデックスにデータを登録 es.index(index=index_name, doc_type=type_name, id=datapoint["Timestamp"], body=datapoint) def post_instance_metrics(dir_path, instance): for metric in METRICS: file_path = os.path.join(dir_path, metric + ".json") with open(file_path) as f: data = json.load(f) for datapoint in data["Datapoints"]: post_datapoint(instance, metric, datapoint) for instance in INSTANCES: metrics_dir = os.path.join(METRICS_ROOT_DIR, instance) post_instance_metrics(metrics_dir, instance)
スッキリ。 awscli は pip でインストールしたのに、Elasticsearch は標準ライブラリだけ使うというのも、 今考えればおかしな話だ。
CloudWatch からメトリクスをダウンロードし、 S3 にバックアップするところまで出来た。
次はいよいよ、ダウンロードしたメトリクスの可視化にとりかかる。 CloudWatch と同じようにチャートで見れるようにしたい。 このために CloudWatch からメトリクスをダウンロードしたからね。
可視化には、前から目をつけていた Elasticsearch と Kibana を試すことにした。
公式サイトから zip ファイルをダウンロードし、適当な場所に展開。
https://www.elastic.co/downloads/elasticsearch
https://www.elastic.co/downloads/kibana
Web ブラウザで Elasticsearch に保存されているデータを見るために、 head プラグインをインストールしておく。
plugin install mobz/elasticsearch-head
Elasticsearch インストールフォルダ内にある bin\elasticsearch
と
Kibana インストールフォルダ内にある bin\kibana
を実行。
簡単に言えば、RDS のデータベースに対応するものが index、テーブルに対応するものが type。 その index と type のスキーマを定義した mapping.json を作成する。
{ "mappings": { "CPUUtilization": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "DatabaseConnections": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "DiskQueueDepth": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "FreeableMemory": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "FreeStorageSpace": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "NetworkReceiveThroughput": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "NetworkTransmitThroughput": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "ReadIOPS": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "ReadLatency": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "WriteIOPS": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } }, "WriteLatency": { "properties": { "Timestamp": { "type": "date", "format": "dateOptionalTime" }, "Average": { "type": "double" } } } } }
curl で Elasticsearch の REST API を呼び出して、インデックスを作成する。
curl -XPOST localhost:9200/rds -d @mapping.json
curl で REST API を呼び出してデータを Elasticsearch に PUT できるけど、 メトリクスは JSON になっているので、加工が超メンドイ。
# -*- coding: utf-8 -*- import os import sys import json import urllib import httplib ELASTICSEARCH_URL = "localhost:9200" METRICS_ROOT_DIR = "/var/log/perform/my-app-name" INSTANCES = [ "rds", ] METRICS = [ "CPUUtilization", "DatabaseConnections", "DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency", "NetworkReceiveThroughput", "NetworkTransmitThroughput", ] def post_datapoint(index_name, type_name, datapoint): conn = httplib.HTTPConnection(ELASTICSEARCH_URL) path = "/" + index_name + "/" + type_name + "/" + datapoint["Timestamp"] params = json.dumps(datapoint) headers = { "Content-type": "application/json", } conn.request("PUT", path, params, headers) response = conn.getresponse() print(response.read()) conn.close() def post_instance_metrics(dir_path, instance): for metric in METRICS: file_path = os.path.join(dir_path, metric + ".json") with open(file_path) as f: data = json.load(f) for datapoint in data["Datapoints"]: post_datapoint(instance, metric, datapoint) for instance in INSTANCES: metrics_dir = os.path.join(METRICS_ROOT_DIR, instance) post_instance_metrics(metrics_dir, instance)
こいつを実行して、メトリクスが Elasticsearch の index に格納。
_index:rds AND _type:CPUUtilization
みたいな検索クエリで LineChart の Visualization を作成する。
_type
のところを他のメトリクス名に変えて作成しまくったら、
ダッシュボードに配置。
出来上がった Kibana のダッシュボードはこんな感じ。
なかなか良いかも。
CloudWatch から RDS インスタンスのメトリクスをダウンロードして、 それを Elasticsearch + Kibana で可視化できた。
ただ、RDS のインスタンスは今後増えていきそう。 RDS インスタンスごとに Elasticsearch のインデックスを分けたいので、 インデックスも増えていきそう。
インデックスが増えるたびに Kibana のダッシュボードを手動で作るのは、 これまたメンドイので、そこを自動化するのが今後の課題だな。
天神地下街にあるチーズタルト専門店『BAKE』は、 2015 年 9 月にオープンしてから 10 ヶ月くらい経つのに、 まだ行列が出来ている。 半年も経てば落ち着くと思っていたんだけどな。 行列が出来なくなったら行こうと思っていたんだけど、 さすがにもう待てなくなって、15 分くらい並んで買ってみた。
売っているのはチーズタルト1つのみという潔さは好き。 その肝心のチーズタルトは、生地がカリッと焼いてあってザクザクと香ばしい。 チーズはふわっとしていて口溶け良く、濃厚さと甘さ、柑橘系のほのかな酸味が丁度良いバランス。 非常に軽い口当たりで、1人1個じゃ食べたり無かった。 2~3個はペロリと食べれそう。
ずっと行列が耐えなかったけど、その理由も分かった。 この味ならリピートするね。 自分もチーズタルトが食べたくなったら、新しい店でもオープンしない限りは、 次もここに買いに行くと思う。
親から、祖父の一周忌の準備を手伝って欲しいと依頼された。 お寺の予約だけは入れたけど、それ以外の準備がまったく進んでいないらしい。 で、何日を予約したのか聞いたら、なんと1週間後。 マジですか。 1ヶ月前に予約入れたらしいけど、それから今まで何してたんだ…。
親や弟に任せたらこのままズルズル何も進まないのは目に見えているので、 自分が残り(というかほぼ全て)の準備をやることになった。
最終的にお金を出すのは親なので、 後で文句をつけられないように、 まずは要望を聞き取っておいた。
で挙がってきた要望は
といったところ。なんとも注文が多い。 それにお茶は年寄りくさいって、出欠確認する方々は、親世代かそれより上なので、もうお年(自主規制)。
かといって過去の経験上、気に入らない部分があったらずっとグチグチ言われ続けるので、 要望通りにやっておかないと後々面倒。 しぶしぶ了承。
親が電話での出欠確認をどうしてもやりたくないと言うので、 仕方ないから出席して欲しい人の名簿を、 弟経由でメールで受け取る。 名簿は CSV や Excel ではなく本文に書いてあったので、 せっせと Excel にまとめた。
名簿の列順は氏名・郵便番号・住所にしておく。 これが後々役に立った。
まず 1週間では到底無理なので、お寺にお願いして日にちを変えてもらった。 幸い、ゴールデンウィーク明けが空いていたので、その日に変更。 命日よりも前なので問題なし。 1週間の猶予が2週間になっただけだがな!
一周忌後の食事で使うような、法要プランのある旅館を地元で探し、電話で予約した。 名簿から予想される出席者は 13 ~ 17 人の範囲という、アバウトな人数だったけど、 なんとか予約を入れることができた。
大半がご年配の方々なので、食事を豪華にしても、食べきれずに残すことは想像に難くない。 一番安いコースを注文しておいた。 お返しの品で頑張る方針。
大きく人数が変わらないなら前日に最終的な人数を伝えればいい、ということだったので、 出欠確認が終わったらまた電話することに。
ここだけの話、地元は田舎でお店のあてが全く無かった。 Google 先生に聞いても教えてくれなかった。 そこで、祖父の葬儀で利用した葬儀会社のホームページに載っていた提携している旅館に、 直接予約を入れてみた次第。
ご仏前のお返しの品をどこで買えばいいか皆目見当が付かなかったので、ネットで調査。 どうやらデパートで買えば良さそう。 職場は天神なので、岩田屋・三越・大丸が使えて助かった。
会社の昼休みに岩田屋に行き、総合案内でお返しの品を扱ってそうな店を聞いたら、 食品ギフト売り場を紹介してくれた。
海苔やお茶やコーヒーといった、ド定番のものしかなかったが、 他に良いものが見つからなかったときの最終手段として候補に入れておく。 この時点では人数が確定していないから、まだ注文しない。
念のため、いつまでに注文すれば間に合うかを店員に聞いておいた。 5/1 に注文すれば間に合うとのことだったので、人数確認の期限は 4/30 に決定。
人数分の往復はがきを郵便局で購入し、家のプリンターで印刷することにした。 往復はがきはインクジェット版が無かったけど、写真を印刷するわけじゃないので問題なかった。
往復はがきの作成には Word を使った。 Word で往復はがきまで作成できるとはね。 往復はがきの文面はネットで良さそうなテンプレートを探して使った。 宛名も Excel で作成した名簿を読み込んで印刷できた。 Word スゴイ。 はがき作成専用のアプリはもう不要だな。
往復はがきの返信先は実家で、返信の期限は 4/29。 はがきが届いてから1週間くらいしか猶予が無いのは申し訳なかった。 本当なら1ヶ月前には送って、2週間くらいは猶予を設けるべきなのに。
往復はがきの作成は夜中までかかったが、なんとか失敗も無しに全て印刷できたので、 出勤時にポストに投函。
あとはお返しの品の手配と、旅館に最終的な人数を伝えるのみ。 この2つはある程度はがきが返ってこないと数が分からないので。
ちなみに準備の依頼を受けてからここまで2日。
ここから、はがきの返信期限である1週間後までは、 実家にはがきが届いたかをこまめに確認しつつ、 デパートを回ってお返しの品を探した。
返信はがきが結構早い段階で集まってきて、一周忌に出席する人数がだいぶ固まったので、 お返しの品の手配を行った。
今回お返しの品に選んだのは、OSUYA 銀座のデザートビネガーセット。 ゴールデンウィーク期間中&母の日が近いということで、 7500 円のセットが期間限定で安くなっていたので即決定。
店員に話を聞いたら、一周忌のお返しの品にも問題ないし、 のしと紙袋も付けてくれるとのことだった。
食品ギフトのコーナーではなくて、デパ地下に出店している洋菓子や和菓子のショップでも、 お願いすれば一周忌のお返しの品として手配してくれるかもしれない。
はがきの返信がほとんど届いて、人数が確定したので、旅館に最終的な人数を連絡。 最初予約するときに伝えた 13 ~ 17 人の間に収まったので特に問題も無く、 当日お願いします、で終わった。
食事はニコニコ現金払いなので、当日必要な金額をメールで連絡。 メールなら証拠残るからね。 お返しの品は実家にまとめて届いたようなので、 車に積んで持ってくるように念を押しておいた。
お返しの品を渡す人数、旅館で食事する人数、どちらもピッタリで一安心。 食事中の飲み物のことはすっかり頭から抜け落ちていたので、その分が誤算だったけど、 御仏前でもらった金額と比べて丁度良い具合に収まったはず。 御仏前が総額いくらになったのかは聞いてないけど。
幹事みたいなことは苦手ではないので、準備の残りを引き受けたけど、 終わるまで結構ストレスだった。 決して安くは無いお金が絡んでいるからねぇ。
そもそも祖父の葬儀をお願いした葬儀会社に、一周忌も依頼するのがベストだった。 親が葬儀会社ともめて頼めない状況に陥ったらしいが。 まったく、そんな役に立たない面子は捨ててしまえ。
このぶんだと三回忌も自分が準備することになりそうだ。
過去 2 週間分しか取得できない CloudWatch のメトリクスを、 毎日ダウンロードするようにスクリプトを書いた。
このメトリクスはいずれツールを使って分析に使いたいので、 ローカルにだけ保存しておくのは心もとない。 念のためバックアップしておきたい。
というわけで、S3 にアップロードするスクリプトも書いたのでメモしておく。
# ログの出力先 # メトリクスもこの下に保存されている logdir=/var/log/your_app_name/perform today=`date -u +%Y/%m/%d` logfile=${logdir}/cloudwatch.log # アップロード先のバケット bucket=your_bucket_name # アップロードするメトリクス metrics="CPUUtilization DatabaseConnections FreeStorageSpace FreeableMemory ReadIOPS WriteIOPS ReadLatency WriteLatency DiskQueueDepth NetworkTransmitThroughput NetworkReceiveThroughput" # ログ出力 function write_log() { echo -e "`date -u +%Y-%m-%dT%TZ` ${1}\r\n" >> ${logfile} } # メトリクスをアップロード function upload_metrics() { instance_id=${1} for metric in $metrics; do metric_file=${logdir}/${instance_id}/${today}/${metric}.json upload_url=s3://${bucket}/performlogs/${instance_id}/${metric}/${today}/${metric}.json if [ -f ${metric_file} ]; then write_log "${metric_file} を S3 にアップロードします。" # S3 にアップロード aws s3 cp ${metric_file} ${upload_url} write_log "${upload_url} にアップロードしました。" fi done } # RDS インスタンス取得 ids=`aws rds describe-db-instances | jq -r '.DBInstances[].DBInstanceIdentifier' | perl -pe 's/\r\n/ /g'` for id in ${ids}; do upload_metrics ${id} done
AWS CLI で CloudWatch のメトリクスを取得できるけど、 過去 2 週間までしかさかのぼれないみたいだ。 2 週間よりも前のメトリクスを見たくなったときに備えて、 あらかじめダウンロードしておく必要があるな。
そういうわけで、1 日分のメトリクスをダウンロードする bash スクリプトを書いてみた。 このスクリプトを cron で毎日実行してローカルに保存しておく。
# ログの出力先 # メトリクスもこの下にダウンロードする logdir=/var/log/your_app_name/perform/ today=`date -u +%Y/%m/%d` logfile=${logdir}cloudwatch.log # aws コマンドに渡すパラメータ namespace=AWS/RDS statistics=Average starttime=`date -u -d '1 days ago' +%Y-%m-%dT%TZ` endtime=`date -u +%Y-%m-%dT%TZ` metrics="CPUUtilization DatabaseConnections FreeStorageSpace FreeableMemory ReadIOPS WriteIOPS ReadLatency WriteLatency DiskQueueDepth NetworkTransmitThroughput NetworkReceiveThroughput" # ログ出力 function write_log() { echo -e "`date -u +%Y-%m-%dT%TZ` ${1}\r\n" >> ${logfile} } # CloudWatch のメトリクスをダウンロード function download_metrics() { instance_id=${1} # ログフォルダが無ければ作る instance_logdir=${logdir}${instance_id}/${today} [ ! -d ${instance_logdir} ] && mkdir -p ${instance_logdir} for metric in $metrics; do write_log "${instance_id}/${metric} を取得" aws cloudwatch get-metric-statistics \ --namespace ${namespace} \ --dimensions Name=DBInstanceIdentifier,Value=${instance_id} \ --metric-name ${metric} \ --statistics ${statistics} \ --start-time ${starttime} \ --end-time ${endtime} \ --period 300 > ${instance_logdir}/${metric}.json done } # RDS インスタンス取得 ids=`aws rds describe-db-instances | jq -r '.DBInstances[].DBInstanceIdentifier' | perl -pe 's/\r\n/ /g'` for id in ${ids}; do download_metrics ${id} done