Masteries

技術的なことや仕事に関することを書いていきます.

Mackerelを使ったデプロイの仕組み 〜Reactio編〜

この記事は, 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をもらったのもいい思い出です.

metacpan.org

またいろいろな偶然が重なって, MackerelとReactioの連携機能も実装して頂くことができました(今ならMackerelとReactioを同時に使うことで, Reactioの料金が6ヶ月無料になる「障害対応完璧プラン」キャンペーンも実施中です!).

https://reactio.jp/campaign/mackerelreactio.jp

Mackerelからのアラートで自動的にReactioのインシデントが立ち上がり, すぐさま障害対応に取り掛かれる... Reactioは社内でもドッグフーディング的に利用していますが, Mackerelと組み合わせることでより便利に使えるようになりました.

...なんというか, もはや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 --deploymentlocalディレクトリにインストールされるモジュール群も)を含むtarを取得/展開するスクリプトを用意しておいて, これをAMIからインスタンスを起動する際に実行することで用意しています. いわゆる「Pull型」のデプロイと言えるでしょう. なお, 開発したコードはデプロイを行うタイミングでCIサーバ(Jenkins)においてtarで固められ, S3に配置するようになっています.

ちなみにPerlのバイナリについても, プロビジョニング時にインストールするのではなく, CIサーバで(xbuildを使って)ビルドしてtarで固めてS3経由で配布することで, プロビジョニングにかかる時間の短縮を狙っています.

デプロイフローとオペレーション

Reactioのデプロイフローとその中で行われるオペレーションは, 次のようになっています.

  • 開発者がBotにリリースフローの開始を宣言する
  • (*) BotがGitHubにリリース用Pull Requestを作成する(masterブランチからproductionブランチへのPR)
  • 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を利用しながら, 効率的にサービス開発を進めていきたいと思っています!