この記事は, Mackerel Advent Calendar 2015の21日目の記事です. 昨日の記事は, @stanakaさんの年末年始のディスク容量アラートを回帰分析で回避しようでした.
はじめに
...まず最初に, せっかくの機会なので自分とMackerelの関係(?)について書いておこうかと思います. Mackerelを使い始めたのはかなり初期で, 記憶ではベータ版が公開されてすぐに登録して, 私用で使っているVPSに導入して試していた記憶があります. とはいえいきなり事業に投入! という訳にはいかず(社内には既にZabbixなどを使った監視の仕組みがあったので), そこから1年くらいは定期的に開催されるMackerel Meetupに参加するなどして情報を集めていました.
流れが変わったのは, 今年の春のことです. いろいろあってReactioチームに所属することになり, そこでインフラの大規模なリファクタリング(?)を担当することになりました. Reactioチームはまだ立ち上がったばかりで, タスクは多いがメンバーは少数精鋭(というのはあれから半年経った今でも同じですが...)という状況だったので, インフラ面はIaaSやSaaSなどを活用して, なるべく運用に手がかからないようにして, 「サービスを開発すること」そしてそれによって「Reactioユーザに価値を届けること」に集中したいと考えました.
そのためにMackerel Meetup #3での@myfinderさんの発表, Mackerelを中心に考える2015年代のサービス運用環境などを参考にして, Reactioというサービスのサーバなどのインフラ管理を「Mackerelを中心に」して組み立てていきました.
そこからはMackerelを非常に便利に使わせて頂いていて, いくつかブログに記事を書いたりもしています.
papix.hatenablog.com papix.hatenablog.com papix.hatenablog.com papix.hatenablog.com
@myfinderさんが開発されていたMackerelのAPIのPerlクライアント, WebService::MackerelにPull Requestを送って, 結果人生初のco-maintをもらったのもいい思い出です.
またいろいろな偶然が重なって, MackerelとReactioの連携機能も実装して頂くことができました(今ならMackerelとReactioを同時に使うことで, Reactioの料金が6ヶ月無料になる「障害対応完璧プラン」キャンペーンも実施中です!).
https://reactio.jp/campaign/mackerelreactio.jp
Mackerelからのアラートで自動的にReactioのインシデントが立ち上がり, すぐさま障害対応に取り掛かれる... Reactioは社内でもドッグフーディング的に利用していますが, Mackerelと組み合わせることでより便利に使えるようになりました.
こんな感じで(Mackerelが障害を検知する度に)インシデントが立ち上がり, チャットでコミュニケーションしながら解決に向けて進むことが出来る. 俺達が欲しかったのはこれや! これなんや!!!! pic.twitter.com/K6oodFrLH1
— 万札紛失 (@__papix__) 2015, 10月 23
...なんというか, もはやMackerelに足を向けて寝られない日常を過ごしております.
Mackerelを使ったインフラの仕組み 〜Reactio編〜
というわけでMackerel Advent Calendar 2015, 何を書こうかと思ったのですが, せっかくなので(?)Mackerelを使ったReactioの裏側を全てお見せしようと思います. とはいえ全てをしっかり説明するのは時間的に厳しいので, 概要っぽい感じになりますが...
ちなみに, ここから先書くことを要訳すると, ほとんどの内容はMackerelを中心に考える2015年代のサービス運用環境に書いてある通りです. Reactioのインフラを構築するにあたって, この資料はまさに「バイブル」的な存在でした.
IaaS
基本的に, Reactioのサービスを提供するための仕組みは全てAWSの上に載せています(サーバはEC2, MySQLはRDS, RedisはElasticache, など). とはいえReactioの場合, 将来的にずっとAWSを使い続けるかどうかというと読めない部分がある(サービス化する前はオンプレに載せていた時代もありました)ので, なるべく「AWS以外の環境」にも持っていけるよう, 利用するAWSのサービスは選定しています.
EC2, RDS, Elasticacheの他に使っているAWSのサービスはRoute53とS3くらいでしょうか. 他のIaaSだと, ログのストレージと解析基盤としてGCPのBigQueryを入れている程度です.
プロビジョニング
Reactioを動かすために必要なパッケージやミドルウェアは, Packerを利用してAMIを生成するという形でインストールしています.
Packerには様々なプロビジョナーがありますが, 基本的にはshell
を使ってゴリゴリコマンドを列挙している感じです.
AMIの自動生成については, ReactioのコードはGitHubで管理しているので, GitHubのPackerテンプレート用のリポジトリにpushすると, 自動的にJenkinsがPackerを使ったAMIの生成を実行する, という感じになっています. ここは将来的には, JenkinsではなくCircleCIに寄せたいと構想したりしています.
デプロイ
Packerを使ってAMIを作るタイミングでは, ReactioのコードはAMIの中に入っていません.
Reactioのコードは, AMIにS3に設置されたコード一式(Reactio本体だけでなく, carton install --deployment
でlocal
ディレクトリにインストールされるモジュール群も)を含むtarを取得/展開するスクリプトを用意しておいて, これをAMIからインスタンスを起動する際に実行することで用意しています.
いわゆる「Pull型」のデプロイと言えるでしょう.
なお, 開発したコードはデプロイを行うタイミングでCIサーバ(Jenkins)においてtarで固められ, S3に配置するようになっています.
ちなみにPerlのバイナリについても, プロビジョニング時にインストールするのではなく, CIサーバで(xbuildを使って)ビルドしてtarで固めてS3経由で配布することで, プロビジョニングにかかる時間の短縮を狙っています.
デプロイフローとオペレーション
Reactioのデプロイフローとその中で行われるオペレーションは, 次のようになっています.
- 開発者がBotにリリースフローの開始を宣言する
- (*) BotがGitHubにリリース用Pull Requestを作成する(masterブランチからproductionブランチへのPR)
- github-pr-releaseを使っています: http://papix.hatenablog.com/entry/2015/10/26/123631
- BotがJenkinsにstaging環境へのデプロイを依頼する
- Jenkinsがstaging環境へのデプロイを実施する
- 最新のmasterブランチのコードをtarに固め, staging用のtarとしてS3に配置
- stagingへのデプロイスクリプトを実行
- 開発者やプロダクトオーナーがstagingで最終的な動作確認をする
- リリース用Pull Request(*)をマージする
- Jenkinsがproduction環境へのデプロイを開始する
- staging用のtarをコピーしてproduction用のtarにする
- productionへのデプロイスクリプトを実行
Reactioでは2週間に1回の定期リリースと, その他バグを発見した時の緊急リリースを行っていますが, その全てが上記のデプロイフローに従って実施されています.
かれこれ半年近く運用していますが, デプロイ時に問題が発生したことは皆無で, むしろリリースフローのほとんどが自動化されているので, リリースにかかるコストを大幅に削減することができ, よりサービスの開発に集中出来るようになりました.
デプロイオペレーションの実装
さて, ここからが本題です. Reactioでは, このデプロイフローを実現する為のオペレーションの部分で, Mackerelを活用しています. これらのオペレーションはPerl製のスクリプトで実装されており, 例えばAWSの各種サービスはAWS::CLIWrapperを, Mackerelの操作はWebService::Mackerelを利用して実装しています.
...ちょっと複雑なので図示していきましょう. Reactioは, 障害対応を支援する為のサービスが障害で落ちるという事は避けたいので, AWSの2つのAZにそれぞれインスタンスを立て, それをELBに紐付けることで冗長化しています.
AZ-a | AZ-b ------- | ELB | ------- / : \ / : \ EC2 : EC2 :
まず初めに, AWS::CLIWrapperを利用して, 最新のAMIを利用してEC2インスタンスを立ち上げ, ELBにひも付けます. ここでMackerelを利用して, 既に起動済みのEC2インスタンスについての情報を取得して保持しておきます.
AZ-a | AZ-b ------- | ELB | ------- / | : | \ / | : | \ (旧)EC2 EC2:EC2 EC2(旧) :
ここで,
- Mackerelに, 新しく起動したEC2インスタンスが登録される
- 新しく起動したEC2インスタンスがELBのヘルスチェックに通る
...という2つが満たされれば, デプロイは正しく終了したとみなします.
そしてここで, 最初にMackerelから取得したデータの出番です. MackerelでEC2のインスタンスを管理する場合, そのインスタンスのIDなども取得することができますので, これとAWS::CLIWrapperを利用して, 古いEC2インスタンスを終了します.
AZ-a | AZ-b ------- | ELB | ------- | : | | : | EC2:EC2 :
...これでデプロイは完了です!
補足
上記の例では, ELBに紐づくいわゆる「アプリケーションサーバ」のデプロイなので, 最悪MackerelがなくてもELBで動作確認(ヘルスチェック)が出来ます. しかしながら, 例えばワーカー用のサーバなどはELBにひも付けたりしないので, その場合は起動したインスタンスがMackerelに登録されたことを以ってデプロイ成功とみなしています.
まとめ
Reactioでは, Mackerelを利用することで, シンプルかつ簡単にデプロイフローを構築することができました. 引き続きReactioは, Mackerelを利用しながら, 効率的にサービス開発を進めていきたいと思っています!