AWS Spot Fleetでインフラ作ったら安すぎたので本当は内緒にしておきたい話
会社で昔発表したやつを汎用的な感じにして残しておきます。
EC2のお値段が性能そのままに8割お安くなりましたという話
Spot Fleet前のAWSのECSでクラスター構成について
- クラスターのEC2インスタンスはオンデマンド(定価)のautoscaling
- Docker化でスケールアップ・インが容易に
- 増強したい場合はautoscalingでクラスタサイズを増やすだけでOK😎
問題点
- Rolling updateなのでデプロイ時に空き容量が必要
- 動的ポートマッピングができなかったので1インスタンスに同じサービスが入れない
- クラスタのインスタンスを多めにしておく必要がある😇
動的ポートマッピング
- AWSの
Application Load Balancer
orNetwork Load Balncer
という新しいタイプのELBならサポートしている - terraform module化して雛形作った
- これで一つのインスタンスに複数のサービスが入れるようになった
EC2インスタンスの価格見直し
- 今はオンデマンド(定価)で買っている
- spotインスタンスというオークションのような形式で割安(約80%OFF)で変える仕組み
- ただしspotインスタンスは入札価格以下になると問答無用でシャットダウン!!
- spot fleetという複数のスポットインスタンスに入札できる仕組みがある
- 死んでもすぐに復活しやすいコンテナにはぴったり!!
spot instanceの仕組み
ここでクイズです。この場合いくらになるでしょう?
こうなります
入札してる価格より低かったらそれを払うだけでよい
おこぼれが減ったら
3番目移行の人は問答無用でシャットダウン
おこぼれがなくなったら
全員シャットダウン!!!
耐障害性を増やすには
- 落ちる時には2分前に通知がくるので5秒ごとに見張るスクリプトを追加
- 落ちたタイミングでコンテナを他のインスタンスに逃す
- ざっくりこんなイメージの処理内容
while sleep 5; do if [ -z $(curl -Isf http://169.254.169.254/latest/meta-data/spot/termination-time)]; then /bin/false else # draining fi done
分散投資
人気がない安定した銘柄に入札
人気銘柄
まだオンデマンドで消耗してるの?
2017年の振り返り
前回1年間の振り返り書いたのは転職前が最後だったのか。っていうか転職して丸3年経ったのか、早い〜。
今年はインフラエンジニアになっていきたいと思ってやっていった1年で去年の自分と比べて新しいことをたくさん覚えられてとても嬉しい。何か新しいことを覚えるということが自分にとってかなりの仕事のモチベーションになるんだというのが32歳になってやっと分かってきた気がする。
1月〜3月
幸か不幸かインフラエンジニアをやっていきたいと思い始めたタイミングでインフラをやってた人が転職することになりその引き継ぎをやることになった。もともとコードはterraformで書かれていて、プロビジョニングもitamaeでレシピが書いてあったのでこの辺を理解しつつ気軽な開発環境が欲しいということで色々勉強しながら 既存のインフラを保守しつつterraformのmoduleを使って環境作れた。このころはAWSのこととかネットワークとか色々分かってなかった頃なので進捗が出せないのを耐え泥水をすすって基礎を理解することに時間をかけた。
4月〜6月
社内でGKEでDeisを導入しようとして整備をしていった。数文字削除しただけだけど、OSSにPR出すとかもできた。
Deisのことももちろんだけど、AWSとGCPの違いとか、Kubernetesのことを知るきっかけになった。
Kubernetesは調べれば調べるほどすごいすごいとなってLTやってきた。インフラ界隈で初の勉強会登壇できたのでちょっと嬉しかった。
7月〜9月
育休をとるので育休前にドキュメント整備したり普段やってる情シス作業の自動化などをしておいた。
GitHubの草がすっぽり空いててあー育休かっていうのと、意外とだいたい何らかのコード書いてたんだなーと思った。
育休から帰ってきたあとは、社内でリリースされたサービスのあとのインフラとか監視環境を整えた。ECSクラスターをSpot Fleet化したり、module化したり、Datadogのalertを整えたりしてた。
10月〜12月
インフラ環境を整えるのを引き続きやっていっていった。errbitをsentryに切り替えたり、AWS WAF使ってリクエストはじいたり、デプロイ環境を整備したり。
そして行ってみたいと思ってたre:Inventに行くことができた。EKSが出てくれ出てくれと念じながら参加して色々と楽しめた。 re:Invent後はテックブログ1本と、 tech.recruit-mp.co.jp
JAWS-UG コンテナ支部に初参加して発表してきた。インフラ関係で登壇できたのでこれも嬉しかった。
帰ってきてからは意外と色々なまとめに時間がとられてあんまり業務できなかったけどまとめを書くために調べた内容が業務に関連してたのでまぁよかったかなと思う。
あと年末に社内で開かれた社内ISUCONで優勝できてほんとに嬉しかった。正直ほとんど一緒に組んだメンバーのおかげだけど、当時手も足も出なかったこのころから比べて成長を実感できてなんかとても本当に嬉しかった。
社内ISUCON優勝して普通に嬉しくてツイートしたくなった pic.twitter.com/GbBIabiQHU
— オオシママサト (@_mpon) 2017年12月15日
社内表彰制度でも表彰してもらえたり今年はなりたいなと思う方向に切り替えていって多少なりとも認めてもらえて嬉しかった。来年は引き続きインフラ関連やっていきたいけど、ソフトウェアエンジニアですと堂々と言えるように何らかのソフトウェアを作って課題を解決できるようなエンジニアになっていきたいと思っている。
Jenkinsfileでビルドするときにこれをつけておくと最高になる
Before & 最高なAfterのJenkinsfileのdiff 🍻
currentBuild.description = に好きな値を設定するだけ ❗️❗️
stage ("start notification") { steps { slackSend color: "good", message: "ビルド開始しました" + script { + currentBuild.description = "ここに好きな値をいれる。例えばビルドのparameterなど" + } } }
ジョブ一覧でさっと情報が見れるようになる
Jenkinsfileで複数ジョブまたがるやつをコード化できました
Jenkins pipline scriptとは?
昔はpluginで提供されてたみたいだけど、Jenkins2からは標準pluginになったという代物。Groovy DSLと、groovyのscriptでJenkinsおじさんのジョブをコードに落とせます。
Jenkinsfileでジョブを書くときのメリットとデメリットでまとめました。デメリット多そうですがそれ自体は小粒なのと、がんばれば解消できると思うのでメリットの方が大きいかなと思ってます!
今回使いたいと思った理由として、あるパラメータを指定したときだけは、既存のマイグレーションジョブをtriggerして、そうじゃないときはスキップしてほしいというのを実現したかったんですが、shだけじゃできませんでした。 これができるのがJenkins pipline scriptだけなので、これがでかい気がします💯
pros
- shでは無理だった条件付きでの別ジョブのトリガーやスキップができるようになる!!
- ステージごとにビルドごとの時間が表示されてCircleCiっぽい見た目になる
- ジョブの全て(パラメータ設定からビルド後のアクションまで)をコード化できる
cons
- ヒアドキュメントで複数行のshを書いた時にパイプが使えない・・
- groovyスクリプトでやるか、sh自体をファイルにしといて実行するだけにする
- groovyのsandbox環境で実行されるので、自作データクラスとかGroovyのリッチなクラスがそのままだと使えない
- 設定で権限解放みたいなことすれば使えるみたい(試してない)
- shの標準出力を受けたらtrimしないと改行が入る
- Slackのnotifier pluginなどGUIだったらポチポチとチェックボックスつけるだけでいい感じにメッセージ通知してくれたけど、あれを全部自分で書かないといけない・・
- 誰かが書いてそれを部全体でパクればOK
- Groovyとしては正しいんだけど、JenkinsのPipilineとしては受け付けないみたいなやつがあるので最初辛い
- パラメータのデフォルト値を変えるときはリポジトリへのプッシュが必要(画面から上書きできない)
実際のJenkinsfile
ポイントはこの辺ですね。
parameters
でパラメータ設定ができるstage
がビルドの論理的な箱的な感じwhen
でstage
をskipするか設定できるbuild job
で別jobをtriggerできるpost
ディレクティブでビルド後の処理を書く
下のやつは動かない仮のコードですが雰囲気を味わってもらうことはできるかと思います。
pipeline { agent any parameters { choice( choices: "develop\nstaging\nproduction", description: "デプロイ先の環境", name: "ENV") string ( defaultValue: "", description: "ビルドのコミットハッシュ", name : "BUILD_VERSION") } stages { stage ("start notification") { steps { slackSend color: "good", message: "$JOB_NAMEのデプロイが開始されました" } } stage ("migration") { when { expression { // as you like } } steps { build job: migration_job, parameters: [ string(name: "ENV", value: ENV), string(name: "BUILD_VERSION", value: BUILD_VERSION) ] } } stage ("deploy") { steps { script { echo "start deploy" slackSend color: "good", message: "$JOB_NAMEのデプロイが完了しました :ok_woman: リビジョン: ${BUILD_VERSION}" } } } post { success { slackSend color: "good", message: "$JOB_NAME [$ENV] SUCCESS - ${currentBuild.displayName} ${BUILD_VERSION}" } unstable { slackSend color: "warning", message: "$JOB_NAME [$ENV] UNSTABLE - ${currentBuild.displayName} ${BUILD_VERSION}" } failure { slackSend color: "danger", message: "$JOB_NAME [$ENV] FAILURE - ${currentBuild.displayName} ${BUILD_VERSION}" } } }
Datadogの効果的なモニタリングとAlertについてシリーズを読んだ
狼少年
少年が繰り返し同じ嘘をついたので、本当に狼が現れた時には大人たちは信用せず、誰も助けに来なかった。そして村の羊は全て狼に食べられてしまい、死亡した。 Wikipedia
サーバーの監視でalertやwarningが増えすぎると、 だんだん暗黙の了解みたいのが入ってきてこんな状態になってしまうことはないでしょうか? 🤔
- 最近の人 「なんかwarnがいっぱい出てるけど大丈夫でしょうか?」
- 昔からいる人 「あーこれは大丈夫なやつ」
ぼくはどっちかというとそういう暗黙の了解側にいてしまうので、モニタリングというのは何をするべきなのかというのを勉強とまではいかないけど、Datadogが出しているMonitoring 101というシリーズ記事があるのでそれを読んでみみた。
alertすべきはWork metrics
metricsの種類
metricsには、Work metrics、Resource metrics、Eventに分かれる。
Work metrics
Work metricsはシステムが正常に動いてるかどうかを判断できるメトリクスのこと。Work Metricsはさらに4つに分かれていて、throughput, success, error, performanceとなる。 例えばウェブサーバーのWork metricsはこういう値
DBだったらこう
Resource metrics
Resource metricsはDisk IOやCPU使用率などのメトリクスの事を指す。同様にutilization
、saturation
、errors
、availability
の4つに分かれる。
ついついぼくが設定してしまっていたのは、CPU使用率が90%overです。みたいなalert。これは実はalertとしては筋が悪い。別にCPU使用率が高くてもレスポンスタイムなどが遅くなっていなければ問題ないのだ。
Event
eventはPRがmergeされたとか、deployされたとかバッチが失敗したとかそういう出来事のことを指す。
効果的なモニタリングとAlert
Alertは緊急度によって3種類に分けられる。
- 夜だろうがなんだろうがすぐに対応しないといけない
Page
- すぐではないけどいずれやらないといけない
Notification
- とりあえず記録しておいて通知はしなくてよいOKな
Record
という分類。
そして、Page
に分類されるものとして通知をするときに、本当にすぐに対応しなければならないのか、緊急度がとても高いものかどうかということを考える。
ちなみにPager
が英語でポケベルみたいなもののことを指すので、多分Page
っていうんだと思う。
なので効果的なAlert、モニタリングとしては以下のようなやり方になる。
- 緊急度が高いWork Metricsが想定外の値であれば
Page
(電話やSlack通知) - 残りディスク容量が少ないなどのResource Metricsは
Notification
(Warning的なSlack通知) - Pageの場合はいったい何が起きているのかをResource MetricsやEventを使って調査していく
Page
に該当するものはなんだろう
この記事で紹介されている例
実際の現場でいうと、このあたりになるのかなと考えてみた。本当に失敗してはいけないバッチとかって今ってjenkinsの結果見るだけしかやってなくて、それだと埋もれてしまうのでそういうのも本来は別途通知しないといけないなと思った。
- 外形監視でのヘルスチェックに失敗した
- LBのヘルスチェック成功しているインスタンスの数が0になった
- LBのレスポンスタイムが平均xxx
ms
以上になっていないか - サーバーのHTTPのステータスコードの5xx系の全体に占める割合がxx%を超えていないか
- バッチが失敗した
ECSの概念を理解しよう
※ 追記 結構ちょこちょこブクマしてもらっているので意外と需要あるのかな。 もし、記事見て分からないところあったら Twitterなり気軽に質問してもらって大丈夫です!!
社内でインフラエンジニア増やしたいなと思ってECSの概念を理解してもらおうと思って書いたやつです。
問題を間に挟みつつ理解の手助けになればいいなと思ってます。
今更ですがDockerとは?
分かってる人はもううんざりかもしれませんが、一応復習。ECSの概念を理解するのに必要なコンテナを起動すると何が起きてるのかを再確認します。
普通の仮想サーバー
普通の仮想サーバーの場合は、sshでログインして、yumみたいなパッケージ管理システムでinstall、サービスをデーモンでバックグラウンドで起動しておくという感じですね。
Dockerの場合
Dockerはコンテナという単位で何かのプロセスを起動する仕組みです。基本的には、1コンテナで1プロセスだけが動いていると思っていてください。 あるマシンの上でDocker Daemonが起動していて、そこに命令することでコンテナを起動することができます。
Dockerが普通の仮想サーバーと違うのは、こんな風に仮想サーバー上でnginxサービスをデーモンで起動しておくのではなく、1つのプロセスとしてコンテナを起動しておくというところです。 この図のnginxコンテナは最後にnginxをスタートさせてそのプロセスがフォアグランドで起動しつづけています。それをdocker daemonがデーモンとして動かしているという感じです。
問題
Dockerコンテナで最後に、tail /var/log/bootstarp.log
を実行するコンテナ、tail -f /var/log/bootstrap.log
を実行するコンテナをdaemonモードで動かしたときにそれぞれどのような挙動になるでしょうか?具体的に言うと、
-f
がついてないやつ。
FROM ubuntu CMD ["tail", "/var/log/bootstrap.log"]
-f
がついてるやつ。
FROM ubuntu CMD ["tail", "-f", "/var/log/bootstrap.log"]
これらを、 docker run -d
したときに該当のコンテナは生きてるでしょうか?死んでるでしょうか?
答えは動かして確認してみましょう。
普通のインフラの構成を見てみよう
ECSの構成に行く前に、普通のインスタンスでサーバー動かしてたころのやつってどうなってるんだというのを見ておきましょう。
例えば、 https://www.example.com
っていうただ単にnginxが動いてるサーバーがあったとして、その場合はDNSがロードバランサーの名前に変換して、ロードバランサーがyum installしたnginxがデーモンで起動しているEC2インスタンスにリクエストを割り振って、レスポンスをそこからもらうっていう流れです。
Tips: 443がLBで80に変えてあるのはSSL終端(ssl terminate)と呼ばれてます。
これを1台のEC2でdockerコンテナでやるとしたら?
ここで賢い人々は、よし、Dockerはコンテナがたくさんあげれるからお金を減らすためにEC2インスタンスを1台にしよう!と考えます。それを図にするとこうなります。
そう、あえて意図的に赤くしましたが、同じインスタンスなので、ポートを変えてあげないといけないのです。あと、Dockerにはsudo権限みたいのを渡しておかないと80みたいなwell-known portは使わせてもらえないかも。(ここは曖昧だけどあんまり本質ではないので気にしないでください)
- 要は、portを管理しないといけないってこと。ロードバランサーとコンテナのポートのひもづけはやらないといけない。
- せっかくDokcerでimmutableにしたのに、結局はSSHログインしないといけないこと(Dockerデーモンに命令を出さないといけない)
- これ、今インスタンス1台だからいいけど、増えたらどうすんの。辛くない?
- コンテナ死んだらどうする?sshログインしてまたdocker起動しますか?
はい、これでECSのようなオーケストレーションツールが必要になってくるという訳です。
ECSの仕組み
ECSは正式名称はAmazon EC2 Container Serviceです。EC2というのが入ってるので、EC2をベースとしているのです。 ※ いつのまにか名前が変わってElastic Container Serviceになってました AWSのEC2を使って、クラスタ構成を作ってくれて、ロードバランサーのポートマッピングなどをやってくれて、コンテナの生死監視と自動復旧などをやってくれます。さっきあげた問題点が全て解決してますね。
じゃあ、ECSが内部的にはそれをどうやって実現しているか見てみましょう。結構単純です。
コンテナインスタンス
AWSのEC2のクラスタ構成を作ってくれる部分が最初ぼくは謎だったんですが、分かるとなんだそんな単純なことかってなります。
AWSのコンソール上から見えるClusterという概念。これは複数のEC2インスタンスを取りまとめてるだけなんですが、それをどうやって実現してるんでしょうか。
それは、各EC2インスタンスに、ecs-agent
というdockerのコンテナを起動してるだけなんです!また、そのagentはOSSで公開されてます。
要はさっきの説明でnginxとか起動させてましたがそれと同列でecs-agent
っていうのをコンテナとして起動してconfにcluster名を渡してあげるだけです。それだけで、人間は、クラスタというまとまった単位で管理できる訳です。
実際は、手動でインスタンスを起動してそこにecs-agentを入れてみたいなことはせずに、autoscaling groupというAWSの機能を使ってガッとインスタンスをたてて、そのときの起動スクリプトでecs-agentを動かします。
どうでしょう?簡単じゃないですか??
taskとは?
clusterが起動する理屈はわかりました。では、今度は、コンテナがどう起動しているかを見ていきます。これは、社内メンバーのまとめで気づいたんですが、ECSでは単にコンテナを起動して終了させるだけなら、TASKという概念でまかなえるのです。 Serviceという概念があることを知ってる人もいると思いますが、ここでは、単にTaskが何なのかを見ていきましょう。
さっきDockerの問題のところでやったTailするだけの例に出てきてもらいましょう。tailだけを実行するコンテナを実行するための情報のことをTask Definitionといい、それをもとに、ECSがコンテナを起動してくれます。起動されたコンテナのことはTaskと呼ばれます。 Taskの場合は、コンテナが死んでようが生きていようが気にせずに、単にコンテナを動かすだけになります。この場合は、コンテナはtailコマンドを実行したあとすぐにkillされてしまいます。これがTaskの一生です。 この1回起動したら、死んでよいというのは、バッチのジョブなどに向いています。
Serviceとは?
ECSを構成する概念の一つです。 オーケストレーションツールなしでDockerだけで運用する場合の問題としてコンテナの死活管理がありましたが、それを解決するものです。
通常のWebサービス等でECSを使う場合は、下記の理由でServiceを使うことになります。
- 基本起動しっぱなしでリクエストを待ち受けて欲しい
- 死んだら再起動してほしい
- 外部からの接続を受けてポートマッピングして欲しい
図に表すとこうなります。リクエストがきて、ロードバランサーで各コンテナにリクエストが振られるところまでは基本的に何も変わりがありません。ただ、各コンテナがServiceというくくりでまとめられているところが違います。
では、Serviceのすごいところを説明していきます。 まず、Serviceというか、ALBというApplication Load Balancerのすごいところでもあるんですが、ポートを動的にマッピングしてくれます。 さっきのDockerの例だと、同じインスタンス内でポートがかぶらないようにと人間が気を使って設定していましたが、それはやる必要がなくサービスがやってくれます。
次に、desired count=4
というところです。これはどういうことかというと、コンテナ4つたててくれーって指定しておいたら、コンテナが死んだとしても自動的に復旧してくれます!desiredとは人間が要望している数ですね。それに従って機械が何度でも復活させてくれる訳です。
もう一つ、これは完全にそうとは言えないんですが、ある程度賢くコンテナを分散して配置してくれます。この部分は設定をがんばったりしないといけないので、完璧とは言えない部分ですが、そういうこともやってくれるという訳です。
問題
ここまでを踏まえて、以下のことをやってみましょう。
- 手動でインスタンスをたちあげてECSクラスターをたててみる
- autoscaling groupでECSクラスターをたててみる
- 何かサービスを動かしてみる
ECSのlogdriverにawslogsを指定した場合はawslogs-stream-prefixをつけたほうがいい
awslogs-stream-prefix
をつけない場合
log stream名が "${docker psのCONTAINER ID}${randomな文字列}"
となる。
これだと、例えば、あるコンテナのログだけ見たいっていう場合に、sshログインして、docker psしないといけない😱
💯 awslogs-stream-prefix
をつける場合
なんでもいいからprefixを追加するだけで、なぜか、急に顧客が本当に欲しかったStream名が手に入る。
"${awslogs-stream-prefix}/${container name}/${ecs-task-id}"
これはawslogs-stream-prefixにplay
という文字列を設定した場合の例。
これでどのコンテナのログか見ることが簡単にできる。なので、とりあえず、awslogs-stream-prefix
はつけておいた方がいい!!
prefixなしのときも、この命名規則で作ってくれよとは思う。
参考: http://qiita.com/bohebohechan/items/8943786929ab5833d2a8
プロダクションレディマイクロサービスを読んでハッとさせられた
とにかくハッとさせられた
この本を読んで、マイクロサービスとか関係なく、社内のインフラもまだまだやれることあるなー、というかこの本に書いてあるレベルにしなければという気になった。
最初、雑にマイクロサービスってこういうものだよーみたいな本かと思って読み始めてみたけど全然違っていた。マイクロサービスを本番で使えるレベルにするにはどうなってないといけないかという本だった。
どんな本?
UberのSREの人が書いた本。その人が1000以上あるマイクロサービスを標準化するために8つの原則を考え出してそれについてそれぞれ語られていくみたいな話。組織論みたいなところまで話がいくのでコードはでてきません。どっちかっていうとプロセスに重きをおいた抽象的な感じ。
なので逆に言えば、マイクロサービスじゃないから関係ないやという感じではなくたとえEC2のインスタンスで運用してようとためになる話だなと思った。 個人的には、Uberに1000以上もマイクロサービスがある気がしないんだよな〜🤔 (Googleが1000以上あるって言うならならなんとなく分かる)
きっと、自分が思ってるマイクロサービスの粒度よりもすごく小さいか、それか本当にいろいろなサービスがあるんだろうな。
8つの原則って??
- 安定性
- 信頼性
- スケーラビリティ
- 耐障害性
- パフォーマンス
- 監視
- ドキュメント
- 大惨事(カタストロフィ)対応
これらの基準を満たしたサービスのことをプロダクションレディと表現している。
第1章はモノリシックなサービスを比較対象にしつつ、マイクロサービスとはなんぞやを説明してくれています。 で、その後の章で8つの原則について掘り下げつつプロダクションレディにするためのチェックポイントを書いていってくれています。
やっていき😤
他社のすごい事例とか見たり、自分が思いついたようなことは既に2〜3年前ぐらいから考えられていたことだったりして、今さらがんばってもしょうがないのかもしれないと弱気になることもある。 でも、立ち止まらずに一歩ずつ進んでいこう、目新しくなくたっていいから基本的なことをちゃんとやっていこうという前向きな気持ちになった。
もっと開発のtry and errorを素早く試せる環境とか、リリースまでのスピードをあげたり、品質を高くしたり、何をどうすればいいかすぐには分からないけど、そんな感じのことをやっていきたいという気になれた。
Cloudwatch Logsが簡単便利安いだった
事の発端
Railsのlogrotateされたあとのファイルをどこに保存しようかと迷ったのがきっかけでした。 fluentdなどでS3に保存していくとか作るかーと思ってたんですが、社内のメンバーに相談してみたところAWSのCloudwatch Logsで簡単にできるよとのことで、調べてみたら本当に簡単便利安いだった。
インストールと設定
- installは
yum install awslogs
でさらっとできる - 設定もあんま深く考えなければこれだけでできる
[/u/apps/log/production.log] datetime_format = %Y-%m-%dT%H:%M:%S file = /u/apps/log/production.log buffer_duration = 5000 log_stream_name = {hostname} initial_position = start_of_file log_group_name = /u/apps/log/production.log
ロググループとログストリーム
これの概念がとてもいい感じで、同じ設定ファイルでもログストリームを分けることでホストごととかで分類できるようになる。
ロググループは同じなんだけど、log_stream_name = {hostname}
という設定を入れているので勝手にホストごとに分かれてくれる。便利。
検索もさらっとできる
S3に入れていたらAthenaとかでやるのかなーめんどうそうだなーとか色々考えてたけど、文字入れたり、日付でフィルターしたり、結果もすぐ返ってくる。 cloudwatch logsは昔はこんな簡単に検索できなかったそうな。本当いい時代に生まれました。
ECSのログドライバーにもできる
ECSのコンテナのログってlogdriverに設定したところに送られるようにできるんだけど、fluentdで集めてElasticsearchに送ってKibanaで見るとかそういうの作るのも一手間あってちょっとめんどいなーと思ってたけど、Cloudwatch logsにも送ることもできる。
なので、さくっとログ確認したいなー用途はこれでいいかも。
お値段
取り込みに$0.76/G
、保存に$0.033/G
なので、月間100Gあったとしたら(0.76+0.033)*100G
で$79.3なので1万円弱。
保存できて検索までできてこのお値段!!
fluentdのS3 output pluginでは権限に注意しよう
現象
fluentdが集めてきたログをS3にputしていたつもりが、全体のうちの1/3ぐらいしかputされずそれ以外がロストしていたという話です。
調べてみると・・エラー吐いてた
fluentdのヘルスチェックとかは確認していて、fluentdが死んでいる様子はない。 S3にもログ自体は吐かれている、冗長化している片方のインスタンスが死んでいるわけでもない。 ということですぐに気づけませんでした。
fluentdがエラーを吐いてたので見てみるとS3の権限っぽいエラーが。
2017-04-21 12:50:46 +0000 [warn]: temporarily failed to flush the buffer. next_retry=2017-04-21 12:50:48 +0000 error_class="Aws::S3::Errors::Forbi dden" error="" plugin_id="object:2b1bacaec3f4" 2017-04-21 12:50:46 +0000 [warn]: suppressed same stacktrace
Aws::S3::Errors::Forbidden
というエラー内容からIAMとかBucket Policyだとピンときて確認してみるも、PutObject権限はきちんとついていて路頭に迷うことに。
Aws::S3::Errors::Forbiddenでググりまくると・・
https://groups.google.com/forum/#!topic/fluentd/Vvok7FTVuq4 このページの真ん中あたりのこの部分がヒントになった。
If an IAM user has permissions to the bucket but doesn't have !AM permissions to s3 that user can write files but @bucket.objects[s3path].exists? will fail with 'Permission Denied' when true but not when false (Weird).
fluentdがs3にbufferをs3にwriteしに行く際に、ある特定の区切りの時間のオブジェクトはまだ存在しないので書き込みできる。
で、そのあと同じs3のファイルに対して一度existsを確認してから追記しにいくんだろうけど、このexistsで失敗していると思われる。
実際に、現状のIAMロールはs3:*
に対してAllowされてるけど、bucket policyはs3:PutObject
のみになっていた!!!!
fluent-pluginの該当コード部分
pluginのこの該当コード https://github.com/fluent/fluent-plugin-s3/blob/master/lib/fluent/plugin/out_s3.rb#L237 のwhile条件を抜けてしまう。
end while @bucket.object(s3path).exists?
なので、1回目は書き込みにいけるが、その後追記しにいくときに失敗する。
1/3ぐらいしか残っていないというのも、残ってるのは特定の時間で新規でwriteした分で、本来であれば残りのログもそのobjectに対してwirteしにいけたが、bucketのポリシーが足りないためexists確認ができずに、失敗してしまったと思われる。
対応
- bucket policyを
s3:PutObject
ではなく、s3:*
にする - READMEにiam-policy載っていました😜 https://github.com/fluent/fluent-plugin-s3#iam-policy
バージョンによるみたいですがv0.8.0からはcheck_bucket
とcheck_object
のオプションが追加されていて、それだとs3:PutObject
だけでいけるみたいです。
Example when check_bucket=false and check_object=false When the mentioned configuration will be made, fluentd will work with the minimum IAM poilcy, like: "Statement": [{ "Effect": "Allow", "Action": "s3:PutObject", "Resource": ["*"] }] https://github.com/fluent/fluent-plugin-s3
Release 0.8.0 - 2016/12/20
- out_s3: Add check_object / check_bucket parameters for only put permission
- Remove fluent-mixin-config-placeholders dependency https://github.com/fluent/fluent-plugin-s3/blob/7a2b3144246bb7b492f7b0ef72a0192908b0f881/ChangeLog#L3
まとめ
最初動いたからといって油断してはいけない
Rails5とpumaを使ったときのlogrotateでハマった話
現象
普通Railsのアクセスログってproduction.logに出力されますが、出力されないことがあったという怪談の話です。
対応を1行でまとめると、Puma使うときはRails5からはRAILS_LOG_TO_STDOUT
を設定しようということです。
👇 ここからは怪談の詳細です
原因1: logrotateの後処理の実行タイミングが悪かった
開発環境で確認してみたところ、logがログローテートされたあとファイル名の後ろにyyyy-mm-ddが付与されますが、 そのファイルに対して引きつづきログが出力されつづけていたのです🙀
で、既存のログローテートでこんな設定が書いてありました。
lastaction puma_pid=/path/to/puma.pid test -s $puma_pid && /usr/bin/kill -HUP -U root "$(cat $puma_pid)" > /dev/null 2>&1 || : endscript
ざっくり言うと、lastactionなのでgzに圧縮されたタイミングで、pumaのプロセスにSIGHUP
シグナルを送るという処理が行われてます。
logrotateスクリプトの調査これを参考にするとlastactionはgzに圧縮されたタイミングなので、こういうことになっていたんでしょう。
今回はpostrotate
が適切と考えたので変更しました。
- lastaction + sharedscripts + postrotate
原因2: pumaとシグナルの関係
さっきのlastactionでHUPのシグナルを送っています。このシグナルはpumaにとってどんなことを意味するんでしょうか。 signals.md | puma/pumaを見ると、
HUP reopen log files defined in stdout_redirect configuration parameter
stdout_redirect設定したファイルに対してログファイルをreopenしますと書いてあります。
なんかよくわからないけどログをreopenしてくれるんだろう☺️ めでたしめでたしと思って手動でkillコマンドでHUPを送るもreopenされない・・・
よくよく調べてみると、pumaのstdout_redirectの設定はcapistrano-pumaのデフォルトを使ってるんですが、対象はpuma_access.log
でした。
なので、HUPを送ってもrailsのlogではなくpuma_access.logをreopenするだけなのです😢
logrotate後にRailsのlogの場所が分からなくなったpuma君がRailsのログを書き込めなくなってたということでした。
Rails5 + pumaの組み合わせではRAILS_LOG_TO_STDOUT
とstdout_redirect
を使おう
RAILS_LOG_TO_STDOUT
なしの場合、workerプロセスのloggerがそれぞれproduction.log
をつかみます。
pumaにworkerのloggerの出力先を書き換える機能がないのでUSR2
or USR1
シグナルでworkerをまるごと再起動しないと、古いログファイルを掴んだままになります。
pumaのstdout_redirect
でproduction.log
を指定しているとき、workerの標準出力の向き先がproduction.log
になります。
RAILS_LOG_TO_STDOUT
を設定しておくと、loggerは標準出力にログを出力するので、標準出力の向き先のproduction.log
にログが出力されます。
HUP
シグナルを送ると、この標準出力の向き先だけが書き換わります。workerをまるごと再起動する必要はありません。
実際の設定方法
PumaとRails5で特に何も設定しないと、Railsのloggerは直接log/production.log
を開くようになってます。
以下の設定をすることで変更することができます。
RailsでRAILS_LOG_TO_STDOUTの設定
環境変数RAILS_LOG_TO_STDOUT
を指定するとログをSTDOUTに吐くようになります。
if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end
pumaを使って標準出力をファイルに書き出すstdout_redirectの設定
pumaの場合stdout_redirect
オプションを使うとworkerの標準出力を指定したファイルに向けます。
まとめ
なんとなく誰かがいい感じに設定してくれていたlogrotate。 きっとうまくいってると思って見過ごしてたけど奥が深いなと感じました。
GASでconstの挙動がおかしいので使わない方がよさそう
GASだとconstのスコープが違う?
最近のJavascriptとかTypeScript界隈では、var
、let
、const
が変数の宣言として使えて、scalaとかswift脳からするとなるべく再代入は避けたいのでconst
を使いたい。
例えばfor文内で一時的な変数を宣言して利用しようとした場合こんなコードがあったとして、
function test() { for (var i = 0; i < 5; i++) { const k = i * 2; var m = i * 2; Logger.log('k = ' + k + ', m = ' + m); } }
kもmもiが2倍された値が表示されるはずなんだけど、constで宣言した方は0のままになってしまう。一度代入されたらそれ以降変更されないようだ。グローバルスコープっぽい動き。
なので、GASでconstを使う場合は、本当に一度でも代入したら変更しない定数にのみ使う方がよい。というかあんまり使わない方がいいかも。
node.jsでやってみるとちゃんとconstに再代入されてる
これが想定していた挙動
AWSのRoute53でpublic/private zoneの名前のつけ方に注意
Route53のzoneの名前を同じにしていると・・
Route53では、外部公開用のpublic zoneとVPC内に閉じたprivate zoneで、それぞれレコードを登録することができます。 しかし、public/privateなzoneでdomain nameを同じにするとpublic zoneの名前が引けなくなってしまいます。
例としてこんな設定のときに、
zone | type | VPC |
---|---|---|
mpon.me. | Public | |
mpon.me. | Private | 開発用VPC |
次のようにPublicなゾーンにレコードを登録していたとします。
zone | record |
---|---|
Public | dev-www |
これで、例えば、開発用VPCにいるEC2インスタンスなどからdev-www
の
名前を引こうと思ってもひけなくなってしまいます。
対処法としてprivate zoneにも同じ内容のDNSレコードを登録しました。
zone | record |
---|---|
Public | dev-www |
Private | dev-www |
これからインフラを作ることがある人は
zoneを作るときに、Domain nameを以下のように変えればOKです。かぶらないようにしましょう!
Domain name | type |
---|---|
mpon.me. | Public |
mpon.internal | Private |
Datadogでいい感じにAlertの通知を出し分けるやり方
出しわけたいユースケース
例えば、GCPの2つのprojectからCloud SQLの複数のDatabaseのmetricsが送られてくる場合。 project Aの場合は、こっちのチャンネルに通知、project Bの場合は、そっちのチャンネルに通知したいみたいなときに有効です。
metricsの設定方法
まずは、出しわけたいグループごとに、 avg by
部分に設定していく。
こうすると、project_idごと、database_idにmetricsが分かれて表示されて、個別にalertしてくれるようになる。
メッセージ部分の設定方法
template部分で、#is_match
を使うと、if文みたいなことが書けます。
これとタグの値を組み合わせれば出し分けたりができるわけですが、
このときに注意が必要なのが、グルーピングに利用したタグの値しか利用できませんでした。
利用できる変数は{{
を入力すると予測変換が出てくるので、そこに表示されてなければ利用できないと思った方がよいです。
公式ページを見る感じだと、タグを使えますって書いてあるからなんでもいいかと思ってたけどそうじゃなかったでした。
ここで言えば、avg byで指定した、project_id.nameとdatabase_id.nameが条件として利用できます。nameまでつけるのがポイント。
crondはtimezone変えただけだと反映されない
cronはUTCのまま
JSTにタイムゾーン変更したときに、crondをrestartしないと実行がUTCのままでした。
service crond restart
で反映しました。
参考: Timezoneを設定したらcronの実行時間がずれる
EC2の初期スクリプトとかでrebootいる?
AWSのEC2のuser data等で初期スクリプトで設定すると思うけど、それは大丈夫なのかなとふと思いました。 もろもろやったあと、rebootしてるからたまたま大丈夫なのかやらなくても大丈夫なのかちょっと気になりました。
Linux インスタンスの時刻の設定 にもrestart必要と書いてあった。
システムを再起動し、すべてのサーバーとアプリケーションで新しい時間帯情報を取得します。