AWS Fargateを使ってサーバレスでCloudFrontをMackerel監視することを検討して実際に試してみた
TL;DR
- Mackerel を使ってAWSリソースを監視するには、ものによってはプラグインを使うと便利
- ただしその場合、
mackerel-agent
を動作させるコンピューティングリソース(EC2 など)が必要 - 管理リソースを極小にするという目標の下、PoC として AWS Fargate 上で
mackerel-agent
を動作させてみた - 現状だと、動作させることは可能だが実運用するには課題が残る
背景
みなさん、Mackerel で監視してますか!(挨拶)
Mackerel で AWS リソースを監視するためには AWSインテグレーション という機能を使うと便利です。ただし '18/06 時点で、以下のサービスにしか対応していません。
これ以外の AWS サービス、例えば CloudFront を監視しようとする場合は、mackerel-agent
にプラグイン mackerel-plugin-aws-cloudfront
を追加設定することが一般的なのかなと思います。
- mackerelio/mackerel-agent-plugins: Plugins for mackerel-agent
- ミドルウェアのメトリック可視化に公式プラグイン集を使う - Mackerel ヘルプ
その場合 mackerel-agent
を動作させるための EC2 等が必要になる、あるいは既に動作している EC2 上の mackerel-agent
に設定を追加することになるのですが、そうするとその EC2 を管理しなくてはならなくなる、あるいは本来の役割(おそらく何かしらのサービスを提供しているでしょう)以外のこともすることになってしまいます。
その点、AWS Fargate なら管理すべき EC2 は無いですし、公開されている既存のコンテナイメージを使えば、管理すべきリソースは極小にできそうです。本当に可能か、想定外の問題は無いか、PoC として検証してみました。手順をすべて説明している関係上長くなってしまいましたが、我慢してお付き合い下さい。
なお mackerel-plugin-aws-cloudfront
を使う以外のやり方として、fluentd を使って CloudWatch メトリクスを取得する 方法も公式で紹介されていますが、今回は「管理するリソースを極小にする」ことを目標にしたので考慮外としました。そのうち試してみたいと思っています。
構成について
監視イメージとしては下記のようになります。
今回利用する Docker イメージは下記になります(はてな公式が配布 しているものです)。
本来はコンテナ内・他コンテナの監視に使用する目的のものですが、最初から mackerel-agent
とプラグインが導入されているので、今回は PoC でもあることですし、サポート外と認識しつつ利用させてもらいます。
こちらのイメージに、追加で必要となるパッケージやスクリプトを置くのですが、このためにイメージや Dockerfile の管理をしたくないので、下記のようにしました。
- コンテナ起動時にスクリプトをダウンロードし実行する
- 必要なパッケージの導入は、そのスクリプト内で実行する
スクリプトでは CloudFront ディストリビューションの一覧の取得や、CloudFront のメトリクスを投稿するための Mackerel のホストの作成、 mackerel-plugin-aws-cloudfront
を実行するための設定ファイルの作成などを行い、最後に mackerel-agent
を起動します。
スクリプトはどこにおいてもいいのですが、今回は手軽さを目的に Gist に置きました。実環境であれば CodeCommit や GitHub などのリポジトリ、S3 や SSM パラメータストア等に置いてもいいでしょう。該当スクリプトはパブリックにしていますが、動作保証できるものではないので、あくまで参考にとどめてください。
なお、 今回のスクリプト は記述にあたり、mackerel/mackerel-agent
に含まれている start.sh
スクリプトを参考・一部そのまま引用させてもらいました。
それでは以下、構築手順をご説明します(マネジメントコンソールでの操作とします)。結論としては「可能だが問題もある」というものなので、手順が不要というかたは最後の考察のところまでお進み下さい。
なお、Mackerel は既にご存じという前提で話を進めます。Mackerel についての詳しいお話はしませんのでご了承下さい。
Fargateの準備
まずは AWS Fargate が使えるようにします。
Fargate は現在東京リージョンでは使用できない1 ため、今回は北米のN.バージニアリージョン( us-east-1 )を使用しました。
必要な手順はざっくりと下記になります。
- コンテナ(タスク)が動作するための IAM ロールを用意する
- ECS にてクラスターを用意する
- コンテナ(タスク)に割り当てるセキュリティグループを用意する
IAM ロールの用意
mackerel-agent
が動作するコンテナに割り当てる IAM ロールを作成します。今回は fargate-mackerel-poc-role
という名前で作成することにします。IAM はグローバルサービスなので、リージョンを意識する必要はありません。
- マネジメントコンソールから IAM コンソールを開き、ロール > 「ロールの作成」をクリック
- 信頼されたエンティティ : AWSサービス
- このロールを使用するサービス : Elastic Container Service
- ユースケース : Elastic Container Service Task
- アクセス権限ポリシーをアタッチ
CloudFrontReadOnlyAccess
CloudWatchReadOnlyAccess
似たようなポリシー名なので混乱しますが、CloudFront と CloudWatch メトリクス用の権限になります。
今回はスクリプト内で CloudFront のディストリビューション一覧を取得する関係で CloudFrontReadOnlyAccess
が必要になっています。
最後にロール名の設定と、内容を確認し、問題なければ「ロールの作成」をクリックします。
ECSクラスターの用意
Fargate コンテナが動作するためのクラスタを用意します。コンテナやクラスタがどういう関係にあるかは 公式ドキュメント や、下記弊社ブログの記事を参照して下さい。
ここでは、Fargate 用に fargate-mackerel-poc-cluster
という名前で作成します。VPC は既存のものも選択できるのですが、せっかくなので新規に作成します。この際 ECS から CloudFormation が呼び出されるので、操作者の権限にはご留意ください。
- マネジメントコンソールから ECS (Elastic Container Service) のコンソールを開きます
- コンソール自体のリージョンは 米国東部 (バージニア北部) にしておいてください
- クラスター から「クラスターの作成」をクリック
- クラスターテンプレートの選択 : 「ネットワーキングのみ / AWS Fargate を使用」を選択し次へ
- クラスターの設定
- クラスター名 :
fargate-mackerel-poc-cluster
- VPCの作成にチェックを入れる(CIDR等はデフォルトのまま)
ECS が自動的に ECS クラスターと VPC を作成します。「クラスターの表示」をクリックして確認してみて下さい。
ECS クラスターに EC2 インスタンスは含まれないため、この時点でもまだ課金は発生していません。
セキュリティグループの用意
ECSクラスターを動作させる VPC が作成されたので、その中にセキュリティグループ( SG )を作成します。ECS クラスター上で動作するコンテナは全て、ひとつ以上の SG を割り当てる必要があります。
今回動作させるコンテナは全てアウトバウンド(外向き)トラフィックしか必要としない(インバウンドは Established な通信のみ)なので、SG のルールは空っぽで問題ありません。後々のために、 VPC の画面から作成しておきます。名前は fargate-mackerel-poc-sg
としました。
- マネジメントコンソールから VPC のコンソールを開く
- セキュリティグループ > 「セキュリティグループの作成」
- グループ名 :
fargate-mackerel-poc-sg
- 説明 :
fargate-mackerel-poc-sg
(同上) - VPC : ECS クラスターで作成/設定したものを選択
名前や説明はある意味適当でも大丈夫ですが、VPC の選択は間違えないようにしてください。
タスクとコンテナの定義
次に、Fargate で起動するコンテナのためのタスク定義(task-definition)を用意します。実際に起動するコンテナの定義もその中に含まれます。ここではタスク名:fargate-mackerel-poc-task
、コンテナ名:fargate-mackerel-poc-container
としました。
- ECS のコンソールから タスク定義 > 「新しいタスク定義の作成」をクリック
- 起動タイプで「FARGATE」を選択(何故か全部大文字になっていますが Fargate です)
- タスクとコンテナの定義の設定を行います
- タスク定義名 :
fargate-mackerel-poc-task
- タスクロール : 先ほど作成した
fargate-mackerel-poc-role
を選択 - ネットワークモード :
awsvpc
しか選べませんのでそのまま
「タスクの実行 IAM ロール」ですが、これはコンテナ(タスク)ではなく ECS クラスターが使用するロールです。既に ECS を使用されたことがある場合は、標準で ecsTaskExecutionRole
という名前のロールがあると思うのでそれを選択して下さい。初めて使う場合は「新しいロールの作成」を選択しておきます。
- タスクサイズ
- タスクメモリ : 0.5 GB
- タスクCPU : 0.25 vCPU
タスクサイズは最小を選択します。もし CloudFront ディストリビューションが大量にあるような場合は、もしかしたら増やす必要があるかもしれません2 が、PoCの時点ではこれでも十分かと思われます。
- 「コンテナの追加」をクリック
- コンテナ名 :
fargate-mackerel-poc-container
- イメージ :
mackerel/mackerel-agent
メモリ制限やポートマッピングは設定不要です。
- HEALTHCHECK
- Command :
echo,ok
と指定 - Interval :
30
second(s) - Timeout :
5
second(s) - Start period :
300
second(s) - Retries :
3
コンテナが正常に動作しているかどうかの、ヘルスチェックの設定です。ここでは簡単に、コンテナ内で「echo ok」が実行できるかどうかでチェックしています。
インターバルの値などは基本的にデフォルト値ですが、Start period(起動後にヘルスチェックを始めるまでの時間)は念のため長くしてあります。
- 環境
- エントリポイント :
sh,-c
- コマンド :
curl -fsSL https://gist.githubusercontent.com/cm-watanabeseigo/65d4d769c280505a13b1d1ab23803f75/raw/af023d37a9ac2ef0077ea5fedc8e45893993f7ef/fargate-mackerel-cloudfront-poc-start-mon.sh | /usr/bin/sh
(改行なし1行) - 環境変数 :
- キー :
apikey
- 値 : <MackerelのAPIキー>
エントリポイントとコマンドの使い分けはよく練られていませんが、とりあえずこうしてみました。Gist からスクリプトをダウンロードし、それをそのまま実行しています。
また、mackerel/mackerel-agent
Dockerイメージと同じく、APIキーは環境変数 apikey で渡すようにしてあります。本格運用する場合には、後述するように AWS Systems Manager パラメータストア を使うなど考えた方が良さそうですね。
上記以外の、ネットワーク設定やストレージ・ログ設定などはそのままにして「追加」をクリックします。
画面が「タスクとコンテナの定義の設定」に戻ってくると思うので、「作成」をクリックして下さい。実行ロール、タスク定義、CloudWatch ロググループが作成されます。
問題なければ「タスク定義の表示」をクリックして中身を確認して下さい。
以上で準備ができました。
タスクの作成と起動
いよいよコンテナを起動します。
まずは ECS のコンソールから、先ほど作成した fargate-mackerel-poc-cluster
をクリックします。
「タスク」タブが開かれている状態で「新しいタスクの実行」をクリックしてください。タスクの実行の画面で、下記のように空欄になっている各フォームを埋めます。それ以外のところはとりあえずそのままで大丈夫です。
- 起動タイプ : FARGATE
- クラスター VPC : 先ほど作成された ECS クラスター用のVPC
- サブネット : プルダウンから表示されるサブネットを全て選択して下さい
- セキュリティグループ : 「編集」をクリックし、「既存のセキュリティグループの選択」から、先ほど作成した
fargate-mackerel-poc-sg
を選択し保存します - パブリックIP : ENABLED
「タスクの実行」をクリックすると、コンテナの起動が開始されます。下記を確認してみて下さい。
- 前回のステータスが RUNNING になっていること
- 「実行中のタスクの数」が
1 Fargate
になっていること
デバッグについて
何かしら問題があると、タスクは起動しません。ログを見てみましょう。STOPPED になったタスクの一覧から、タスクのIDをクリックすると開く詳細画面から Logs を選択します。
こちらは、コンテナの定義において「コマンド」の書き方を間違えた(全体をダブルクォートでくくってしまった)場合のログです。
また、タスクの詳細画面で「コンテナ」のところを開くと、状態の理由が分かる場合もあります。
この例は、タスク実行の際にパブリックIPを付けなかった(DISABLED を選択)した場合です。外部に通信ができずコンテナイメージがダウンロードできなかったと書いてあります。
原因が判明したら修正するのですが、タスク定義はバージョンコントロールされていて「設定変更」という操作はできず、「新しいリビジョンの作成」を行うことになります。タスク実行の際、「タスク定義」のプルダウンメニューでリビジョンを指定することになるので、設定変更後のタスク起動の際にはそこをご確認下さい。
うまくいったら
コンテナが正常に起動しているようであれば、Mackerel のコンソールからホストが作成されていることを確認して下さい。
- CloudFront
- ip-XX-XX-XX-XX.ec2.internal
Hosts に追加されたホストのうち、後者は監視コンテナそのものになります。
また前者においては、グラフが描かれるまでにすこし(数分)時間がかかるかもしれません。気長にまってみてください。
グラフには CloudFront ディストリビューションID が記述されます。これでは分かりにくいという場合は、Mackerel 上で変更が可能(グラフごとの右上の歯車アイコン)なので、そこで変更してみて下さい。
うまくいったことが確認できたら、いちどタスクを停止して下さい。通知が飛んでしまわないよう、Mackerel 上でコンテナそのもののホストはステータスを Maintenance
あるいは poweroff
に変更してからが良いでしょう。
タスクの停止は、クラスターの画面の「タスク」タブから行います。
サービスへの登録
このように、タスクは止めたらそのまま止まってしまいます。
タスクが止まったら自動的に再起動(オートヒーリング)させるためには、サービスに登録します。クラスター画面の「サービス」タブから「作成」をクリックして下さい。サービスの設定の画面で下記のように設定します。
- 起動タイプ : FARGATE
- タスク定義 :
- Family :
fargate-mackerel-poc-task
- Revision : 「(latest)」とついているもの
- サービス名 :
fargate-mackerel-poc-service
- タスクの数 :
1
今回は平行して動かすものではないので、タスク数は「1」になります。他はデフォルトで構いません。次に進んで下さい。
- VPC とセキュリティグループ
- タスクの起動時に設定した内容と同様に設定下さい
- Elastic Load Balancing(オプション)
- ELB は不要なので「なし」を選択して下さい
- サービスの検出 (オプション)
- こちらも不要なので、「サービスの検出の統合の有効化」のチェックを外して下さい(デフォルトはONです)
次に進むと「 Auto Scailing 」の設定になりますが、スケールもしなくていいので、そのままさらに「次のステップ」へ進みます。
これまで入力した内容を確認されるので、良ければ「サービスの作成」をクリックして下さい。問題がなければ、すぐにタスクの起動が始まります。
今度はサービスの画面でタスクが確認できます。必要数 1、実行中の数 1 となっていますでしょうか。
うまく行ったようなら、また Mackerel のコンソールを見てみて下さい。またホストが増えていることが確認できると思います。古い(先ほど poweroff にした)ホストは不要なので退役させましょう。
また、CloudFront のグラフの更新が再開されたことを確認して下さい。
タスク停止時の挙動について
サービスから起動したことにより、タスクが停止しても自動的に再起動(オートヒーリング)されるはずです。試しにタスクを停止してみましょう。先の手順と同じく、クラスターの「タスク」タブから行います。(Mackerel上で、該当ホストのステータスを変更することをお忘れ無く)
停止したときのログがないのでちょっと分かりにくいのですが、18:23 頃に停止したあと 18:25 に無事新しいタスクが起動してきました。実測で 2 分ちょっとで回復していますので、ヘルスチェックの設定値(30秒 x リトライ3回)を考えても概ね計算があいます。Mackerel のグラフ上も、データの取りこぼしは 2つ(2分ぶん)となっていました。
後片付け
以上で、動作の確認は終了です。不要になった ECS クラスタやサービスは削除して構いませんが、もしそれらを残しておきつつコンテナ(タスク)だけ止めたい場合は、サービスを更新し「タスクの数」を 0
にします。保存すると、タスクは自動的に停止されます。
コスト感について
ここで少しコストのことも考えてみます。AWS Fargate は、秒単位の従量課金となっています。
今回は監視用なので 24/7 で動かしっぱなしになります。ざっくり計算すると 1日あたり $0.46 程度。日本円で考え直すと月 ¥1,500 ちょっと、年額 ¥19,000 弱というところでしょうか。
( 0.25(vCPU) x 0.00001406(USD) + 0.5(GB) x 0.00000353(USD) ) x 3600(秒) x 24(時間) = 0.456192(USD/日)
実際にはこれに、CloudWatch Logs やトラフィックに関する料金が上乗せされます。
CloudFront ディストリビューションひとつ分の監視としては少々お高い感じがしますが、複数のディストリビューションを監視できると考えるとどうでしょうか。ただしその場合は、タスクのCPU/メモリスペックの見直しと併せて考える必要があります。
独断と偏見による考察
以上のことから、AWS Fargate 上で mackerel-agent
を動作させるプランは一定の現実性があるように思えました。ただし難点もあるように思います。
1. コンピューティングリソース(EC2インスタンス)を管理しなくて良い
利点としてはこれが最初に来ますし、そもそもの目的でもあります。もちろんコンテナを起動しているのでまったくゼロというわけではないですが、出来合のパブリックな Docker イメージを使用し、サーバーレスでコンテナを起動しています。こちらで記述したスクリプトはひとつ3 でそんなに規模のあるものではありません。それもマネージドなストレージに置くことが可能です。
2. タスクが再起動するたびにホストが増える
デメリットとしてはこれが一番問題かと思います。Mackerel はホスト数で課金なので、無視できません。
mackerel-agent
はホスト名を設定でコントロールすることが出来ず、起動したコンテナの名前をそのまま使用します。通常であれば起動するコンテナのホスト名は任意に設定でき(docker run
コマンドでいうところの--hostname
)、もちろん ECS にもその設定はあるのですが、AWS Fargate については残念ながらその機能が使えない制限となっています。
(正確には、Fargate では ECS のネットワークモードとして awsvpc
しか選択できず、awsvpc
ではホスト名が変更できない、ということになっています)
もちろん、再起動するたびに退役させたりすることは可能ですが、例えば深夜に、何らかの理由で起動 -> 停止を繰り返したりしたとすると、都度ホストが増えていくことになります。そんな可能性は看過できないでしょう。
解決策としては、新しいタスクが起動する際に、古いホストが残っていたら退役させる、という作り込みが考えられます。タスクが終了する際に自分自身を退役させる、というのは、突然死を考えると現実的ではないので、スクリプトの最初に何かしらをキーにして旧ホストを検索、残っていたら退役、という機能を仕込むことが良いと思います。ただしシーケンシャルに行うのであれば、その分タスク起動から監視開始までのタイムラグが大きくなりますので、並列かつ非同期に行うことが理想です。
あるいは mackerel-agent
およびプラグインを使わず、サービスメトリクスとして Mackerel にポストする仕組みに変える、という、そもそも論的な話にもつながります。
3. コンテナが重い
以下は課題です。今回は自動的に CloudFront のディストリビューションを取得(list-distributions
)して設定を作り、必要に応じて Mackerel 側でホストの作成を行っているため、スクリプト内で AWS CLI と mkr
コマンドのパッケージをインストールしています。
この設計には利点も欠点もあるのですが、下記の条件がのめるのであれば、まず AWS CLI パッケージのインストールも不要で CloudFrontReadOnlyAccess
権限も不要になります。
- 監視する CloudFront ディストリビューションは手動で指定する(環境変数など)
また、Mackerel 上でのホスト作成を手動でおこなう、あるいは REST API を使用する、ということであれば mkr
パッケージの導入も不要になるでしょう。頑張れば AWS API を curl
でたたくことも不可能ではありません。
今回の場合、多少なりと管理する項目が増える・作り込む範囲が広がることを避けてこのような作りにしました。そこが問題にならないのであれば、停止 -> 再起動の時間短縮が見込めますし、外部依存性を下げることにもつながるので、一考の価値はありそうです。
4. Mackerel API の扱い
mackerel-agent
の動作に API キーは不可欠です。特に今回はスクリプトを汎用化したので4、どうにかしてスクリプト以外のところに保管し、スクリプト実行時に動的に渡さなければなりません。
今回は環境変数で渡しましたが、本来はこれは暗号化されているべきです。AWS Systems Manager の パラメータストア が使えれば暗号化して管理できますが、その場合 AWS CLI が必要になるので、前述の 3. の課題の繰り返しになります。
5. エージェントならびにプラグインの仕様との相性
当然ながら mackerel-agent
もそのプラグインも、このような使われ方が想定されていません。そのため前述したような制限や望まない挙動となる場合があります。
例えばですが、mackerel-plugin-aws-cloudfront
はログ出力の抑制機能がありません。該当の CloudFront ディストリビューションにまったくトラフィックがない場合、mackerel-plugin-aws-cloudfornt
は STDERR にメッセージを出力します。通常であればコンソールログとして捨てられるメッセージと思われますが、Fargate の場合律儀に全て CloudWatch Logs に送信されます。ご存じのとおり CloudWatch Logs は GB 単位で課金がされます ので、極力不要なログは出力しないようにしたい(とはいえ全く無しにしてしまうのも怖い)ところですが、そのコントロールは今のままでは出来ないことになります。
6. スクリプトの置き場
今回は Gist を使いましたが、どこに置くのが適切かは一考する必要があると思います。例えばオリジナルは別で管理して、VPC 単位でアクセス制限をかけた S3 バケットから配布する、という方法が考えられます(その場合は何らかのデプロイの仕組みが必要になります)。
VPC 単位で S3 へのアクセス制限を行う場合は、下記の記事が参考になります。
6. コスト
動かし続けた場合は一日あたり $0.46 と上述しましたが、これが例えば t2.nano
だと、東京のオンデマンドインスタンスでも $0.18/日 なので 2/5 程。マネージドであることと復帰が早いことなどのメリットと併せて、ペイするかどうかは検討する必要があります。
まとめ
mackerel/mackerel-agent
コンテナイメージを利用し、AWS Fargate から CloudFront のメトリクスを Mackerel に送るための構成について考えてみました。mackerel-agent
を動作させるための EC2 インスタンスが不要で、再起動時の復帰も早いという利点がある一方で、そのままでは解決できない問題点もみつかりました。
Mackerel の AWS インテグレーション機能は今後も拡張されていくと期待されますが、ひとまずは現状打破(および Fargate を使った汎用監視の PoC)ということで、対象を CloudFront 以外のメトリクスにまで広げつつ、より良い方法を模索していきたいと思います。
注釈
- AWS Summit Tokyo 2018 にて、今年の 7月に利用できるようになるとのアナウンス がありました。楽しみですね! ↩
- Mackerelで監視できますね! ↩
- もしかしたら、この構成を実運用する際には CloudFormation テンプレートを作成する必要があるかもしれません ↩
- そもそも、ハードコーディングするのは設計として間違っていますし。 ↩