CloudWatch + Telegraf + InfluxDB + Grafanaなダッシュボードを作る(CloudWatch基礎知識編)
CloudWatchの概念
CloudWatchのデータをブラウザ上で眺めるだけの場合はそこまで気にすることでもないんですが、カスタムメトリクスをCloudWatchに登録したり、CloudWatchのデータをAPIなどで取得する場合には必要な理解についてまとめました。
必須ではない要素は一部省略しているので、それらは公式ドキュメント参照で。
リージョン(region)
シングルリージョン利用の場合はほとんど気にすることがないためか、CloudWatchメトリクスの説明でリージョンが出てくることが少ないです。しかしながら実際はリージョン毎にメトリクスが管理されているため、メトリクス管理の分類では最上位概念といえるかと思います。
当然マルチリージョン環境下では明示的に指定が必要になる要素です。
なお、リージョンAのカスタムメトリクスをリージョンBのCloudWatchに登録することはもちろん可能ですが、リージョンという無駄なディメンションを作ることになるので原則おススメしません。
名前空間(namespaces)
データを分類するための一番大きな単位。アプリケーションやリソースの種類ごとに名前を定義します。
どんな粒度かは、AWS/
で始まるAWSデフォルトの名前空間が参考になるかと思います。
メトリクス(metrics)
CloudWatchでの文脈としては「定量化(値に加工)されたデータ」のことを指します。
- 名前(MetricName) : メトリクスの名前
- 値(Value) : データ値
解像度(resolution)と集約(statistics)
値の取得時に指定する項目。CloudWatchには最小で1秒単位のメトリクスが保存されていますが、それらを取得する際には細かすぎる場合があります。
これを1分毎、5分毎といった期間毎の個数や最大値、平均値やパーセンタイルなどの値に集約して取得することができます。
取得した側で集約処理をしなくて良いので使用する側からすると都合がよいです。
ディメンション(dimensions)
メトリクスの固有情報を表したもので、メトリクスの小分類ともいえます。複数定義可能です。
- 名前
- 値
タイムスタンプ
メトリクスの発生した時刻です。
メトリクスのレコード表現
例としてEC2のCPU使用率をこれらの概念で表に表すとこうなります。
名前空間 | タイムスタンプ | ディメンション | メトリクス名 | メトリクス値 |
---|---|---|---|---|
InstanceId | ||||
AWS/EC2 | 2023-09-20T01:20:15Z | i-000xxxxxxxxxxxxx1 | CPUUtilization | 0.33000000000000002 |
AWS/EC2 | 2023-09-20T01:20:15Z | i-000xxxxxxxxxxxxx2 | CPUUtilization | 99.670000000000002 |
このようにして時間やInstanceIdの異なる複数のメトリクスが区別されて管理されています。
ひとりごと
こうしてみるとCloudWatchは監視サービスでありながら、時系列データベースと同じスペックを持ったサービスと捉えることができますね。
Telegraf送信編に続きます(後日執筆)。
CloudWatch + Telegraf + InfluxDB + Grafanaなダッシュボードを作る(概要編)
AWS上に構築したシステムのメトリクスを見ることができるダッシュボードを作ると、自分の仕事がちょっと減って嬉しいので CloudWatch/Telegraf/InfluxDB/Grafanaな構成のダッシュボードを構築してみました、の概要。
構成図
なぜInfluxDBに保存するのか
- カスタムメトリクスは2週間で検索できなくなるので欲しい値だけ別で残したい
- ものすごく小規模な内輪向けサービスなのでコンパクトにまとめたい
なぜGrafanaを使うのか
- 非エンジニアがCloudWatchを操作するのはハードルが高い
- CloudWatchよりいい感じなダッシュボードが作れる
- ダッシュボードをいっぱい作りたい
- CloudWatchのカスタムダッシュボードは個数で維持費がかかる
なぜTelegrafを使うのか
- (別にTelegrafじゃなくてもよかった)
- InfluxDBと開発元が一緒だから
- CloudWatchにデータをputするにもgetするにも使える
- そういえばCloudWatch AgentもTelegrafをフォークしたものだった
CloudWatch基礎知識編に続きます。
PHP Exceptionクラスの聖域
PHPのExceptionのフィールドtraceがprivateなので仕方なくThrowableを実装したクラスをイチから書くなど。
— ちょなそ (@chonaso) 2018年9月3日
うわああああ・・・ pic.twitter.com/tSOsanOxHx
— ちょなそ (@chonaso) 2018年9月3日
PHPの触れてはいけない部分に触れてしまったようです。
Throwableを直接実装したいなんて今思えば相当追い込まれていたのかもしれません。
https://www.php.net/manual/ja/class.throwable.php
注意: PHP のクラスが Throwable インターフェイスを直接実装することはできません。 そのかわりに、Exception を継承する必要があります。
PHPのfalseの闇
PHPで闇深いといわれる代表選手は ==
ですが、これについては使わなければいいだけですので、そこまで目くじらを立てる必要はなさそうです。
個人的に闇深いと感じたのが false
の使われ方です。
規定の型 or false
PHPの関数によくある返り値が規定の型 or false
というパターン。
代表的なのがstrpos。
返り値
needle が見つかった位置を、 haystack 文字列の先頭 (offset の値とは無関係) からの相対位置で返します。 文字列の開始位置は 0 であり、1 ではないことに注意しましょう。
needle が見つからない場合は FALSE を返します。
これがなかなか扱いづらいのです。
アノテーション
そういった関数のラッパー的な関数を書くときにこの流儀を引き継ぐと、アノテーションを書くときに
/**
* @return int|boolean
*/
みたいな感じになるわけですが
PHPdocのreturnでよく見かける@return array|bool
— ちょなそ (@chonaso) 2017年10月12日
って、@return array|false
じゃねーの?っていつも思う。
といった心の叫びが漏れてしまってました。
Union Type V2では int|false
みたいな書き方が許されてるようですね。
お前どっちのfalseよ!?
phpredisってインタフェース設計がほんとクソ。
— ちょなそ (@chonaso) 2018年10月31日
getやhGetで値の設定がない時にfalseを返すんだけど、これじゃ値がbooleanの場合はまずexistsやhExistsしなきゃならないからアトミックにならない。
つまりRedisにbooleanの値が格納されていることが期待できる状況でfalse
が返ってきた場合、値がセットされていないのかfalse
という値が取れたのか区別がつかないわけです。
これに対してこういうコードを書きたくなるわけですが、これはgetしたときには値が消されている可能性が否定できないのでアウトです。
$value = $redis->exists('hoge') ? $redis->get('hoge') : getFromDatabase('hoge');
ワークアラウンドとして、boolean型の値を取得したい場合は一度自動シリアライズを無効にするってところでしょうか。
// デシリアライズしない $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // redis上でbooleanのfalseが格納されていたら b:0 が返ってくる、存在しなかったら booleanのfalseが返ってくる。 $ret = $redis->get('hoge'); // シリアライズ設定戻す $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // Redisから値が取得できている場合は手動でデシリアライズする $value = ($ret !== false) ? unserialize($ret) : getFromDatabase('hoge');
こういった事情からこんなコードを書きたくなってしまうわけですが、
if($ret = $redis->get('hoge')){
$value = unserialize($ret);
}else{
logger.debug("******************");
$value = getFromDatabase('hoge');
}
これはさすがにIDEにはやめろと言われます。
ダメなの?(テストは通る)
— ちょなそ (@chonaso) 2017年12月8日
PHPがアレなのかNetBeansがアレなのか… pic.twitter.com/3mtDDD3RwZ
とりあえずfalseでなきゃOK的な
他にもあるかもしれませんが。ユニットテストにassertNotFalseなんてのが存在のするはPHPだけじゃなかろうか。
— ちょなそ (@chonaso) 2018年4月17日
おまけ
これは true
null
にも言えることなんですが、大文字・小文字の区別が無いという点もなかなか闇深事案ですね。
PHPマニュアル内でも一貫性が無いという徹底ぶりです。
PHPの配列とハッシュ
これは当時の憤りから漏れ出たtweetですが、PHPあるあるの一つなんでしょうけどこれにはクラッと来ました。
実際気をつけないとハマることもあるでしょう。
これが通るPHPはやっぱクソだわ。 pic.twitter.com/TnFvZDcclR
— ちょなそ (@chonaso) 2018年1月25日
こういうJSONを出力する必要があったんですが、
{ "0": 1, "1": 5, "2": 2}
こんなコードを書くと、
$json = json_encode( [ 0=>1, 1=>5, 2=>2 ] );
$json
の中身はこうなります。
[1, 5, 2]
こう書くと?
$json = json_encode( [ 1=>1, 2=>5, 3=>2 ] );
$json
の中身はこう。
{ "1":1, "2": 5, "3": 2 }
つまりはハッシュキーがゼロ始まりだと配列になってしまうわけですが、 この辺はPHPマニュアルには
PHP の配列は、実際には順番付けられたマップです。マップは型の一種で、 値をキーに関連付けます。 この型は、さまざまな使い道にあわせて最適化されます。 配列としてだけでなく、リスト (ベクター)、 ハッシュテーブル (マップの実装の一つ)、辞書、コレクション、スタック、 キュー等として使用することが可能です。 PHP の配列には他の PHP 配列を値として保持することができるため、 非常に簡単にツリー構造を表現することが可能です。
などと自慢気に書いてあります。
ちなみにこう書くと
$json = json_encode( [ 1=>5, 2=>2, 0=>1 ] );
$json
の中身はもちろんこう。
{ "1": 5, "2": 2, "0":1 }
これは誰がうれしいんでしょうねぇ…
json_encodeに配列をつっこんでも0始まりのハッシュで出す方法があった気がするんですが失念したので思い出したら追記します...
PHP浦島太郎
2017年夏頃から2019年初頭まであるゲームのサーバサイド担当としてPHP業に勤しんでいました。
PHPに最初に触ったのは学生時代で、その頃のPHPに対する認識は
print "Content-type: text/html\n\n";
を自動でつけてくれるPerlのエイリアス(しかもCGIより速い)というイメージでした。
新卒時はPHP5でPHP4っぽいコードを書くプロジェクトにいたこともありましたが、
ここ10年ほどのPHPとの関わり方は
- SSIをPHPで置き換えましたみたいなページの保守
- PHPからOracleを使うために手動でPHPをビルド(しかもWebアプリじゃなくPHPで書かれた業務バッチで使う)
- Java&Tomcatで運用してますっつってるのになぜかPHPで納品されたものを使わなきゃならなくなってQuercus(Java上でPHPスクリプトを動かすやつ)を組み込む
などコーディングとはあまり関係ないものがほとんどでした。
そんなPHP力ほぼゼロの状態で無慈悲にも即戦力としてPHPプロジェクトにブチ込まれたわけですが、とりあえずPHP公式ページを読んでみると、
「俺の知ってるPHPと違う…」 「一瞬Javaかと思ったけど$が付いてる」
といった状態。 どうも自分の知識はPHPの3か4くらいで止まってたようです。
PHP3系→4系の変更インパクトも割と大きかったですが、PHP4系→5系・PHP5系→PHP7系もなかなか変わってますね。
Wikipediaの既述の変遷も興味深く、影響を受けた言語にはJavaが追加され、型付けは「弱い動的型付け」から「強い動的型付け」へと変化しています。 いったいPHPはどこに向かってるんでしょうね。
新規開発ということもあり、PHPのバージョンは7系だったのでよかったです。 アプリのローンチギリギリまでバージョンは追従させてました(たぶん)。 逆に今PHP5で書けって言われたら発狂するかもしれませんね。
ってことで、久々のPHPでは色々と悶絶させられたのでその辺のネタをちょくちょく書いていこうかと思います。
近況など(2019年末)
一昨年・昨年に引き続き、異動先のグループ会社で働いています。 昨年書いたときは別の会社のスマホゲームを作っていましたが、2019年2月いっぱいで開発チームから抜けて自社の仕事をしています。
ちなみにそのスマホゲームはすでにサービス終了しています。
B2C Webサービスの開発運用に携わっていた時にもサービス終了を迎えたものがありますが、今回は初期開発から参画したものでしたので残念な気持ちはより大きいです。
これについての公式な振り返り内容を聞いているわけではないですが月々の売上や運営状況はフォローしていましたので自分でも思うところは色々あります。
(当たり障りのないところで)得た教訓としてはスマホゲームは何をKPIとし、日々どのような運営をし、何を禁じ手としなければならないのかがうっすら分かったというところでしょうか。
そういった経験も踏まえつつ、自社(正確には親会社)向けのKPIデータ集約システムの提案~開発を行ない年末に稼働を始めました。年明けから本格運用となります。
今年は初めて海外出張を経験しまして、プライベート含めて初めて日本国外に出ました。 (そういえば10数年前に転職して以来泊りがけの出張ってこれが初めてでした)
まぁ、ビデオ会議システムの性能が飛躍的に向上しているので個人的にはリモートでもいいのでは?と思わないこともないのですがやはり直接顔を合わせることもたまには必要でしょうか。
基本的に上司に帯同だったので困ることはほぼありませんでしたが、時差には結構ハマりました。 なぜ多くのSNSやチャット、メッセンジャー類の投稿日時が「〇〇分前」などの相対表示なのかというのを今更ながら理解しました。
新しい試みとしてはゲームサーバの性能評価をいくつか実施させていただきました。
昨今スマホゲーがリリース直後に高負荷でサービス停止・メンテナンス入りに追い込まれるケースが散見されますが、その多くはやはり負荷試験不足に起因していると個人的には考えています。
最低でもβテストを実施していればかなりの部分が事前にわかったのでは?という気もしますが、βテストの準備自体相当のマンパワーを要するためなかなか実施できないという事情もわかりますので悩ましいところです。
システムを安定的に運用する責任は運営チームではなくエンジニアにあります。 昨年開発に携わったゲームもβテスト後から正式リリースまでの間は私はほぼパフォーマンス改善に時間を費やしていました。
性能評価といえばシナリオやテスト条件をもらってツール実行するだけ、と思われる方もいらっしゃるかと思います。
自分達が行なった性能評価は実際にゲームをプレイし、ソースコードを読み、どの箇所あるいはどんなシナリオでどのような条件でテストするかといったコード修正以外のほとんどをカバーする内容でした。時にはゲームサーバのパフォーマンスチューニングも提案しています。
明確に「××が遅い」原因を調査するわけではなく「特段遅い部分があるという認識が無い」中での評価は暗中模索な部分があります。
これに対してゲーム開発から得たゲーム特有の負荷ポイント知識・長年のB2Cシステム開発経験から培われたWebシステムのチューニング知識、ゲームそのものに対する理解が非常にモノをいいました。 スマホゲームの開発スケジュールは往々にしてタイトであるためこれらを短納期が求められるのはなかなかシビれます。
性能評価を行なったゲームは負荷問題が発生することなく運用されているようです。
昨年はみっちりPHPでしたが、今年は公私ともにAWS屋さんでした。 自社のシステム保守や新規システムの構築、上述の負荷試験(EC2インスタンスの量がハンパない!)などはすべてAWSを使用していました。
とにかくサービスや設定項目が非常に多岐にわたるため多くを習得するのは大変ですが、やはりAWSスタックで構築する早さは目を見張るものがありますね。
今のところAWSを使っていれば許される、という状況ですが、今後はコストやリスクマネジメントなどの観点からも他のIaaS/PaaS/SaaSにも手を出していかないとならないですね。
気づけば異動から2年経過したので再異動ロックも解除されていました。 今のところ(正式な)オファーがあるわけではないので気にしていませんでしたが、中年サラリーマンとしては自分がどういう立場に置かれるかというのは若干気になるところではあります。
立場で思い出しましたが、肩書が「開発部部長」になってました。 ただ今のところあまり部長らしい仕事はしていないです。 (給料も変わってない😇)
立場が仕事を作るのか資質が立場を作るのか、来年はその辺が見えるかもしれません。