読者です 読者をやめる 読者になる 読者になる

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を非常に便利に使わせて頂いていて, いくつかブログに記事を書いたりもしています.

http://papix.hatenablog.com/entry/2015/07/27/144628 http://papix.hatenablog.com/entry/2015/07/28/194622 http://papix.hatenablog.com/entry/2015/10/20/175641 http://papix.hatenablog.com/entry/2015/11/09/105301

@myfinderさんが開発されていたMackerelのAPIのPerlクライアント, WebService::MackerelにPull Requestを送って, 結果人生初のco-maintをもらったのもいい思い出です.

https://metacpan.org/pod/WebService::Mackerel

またいろいろな偶然が重なって, MackerelとReactioの連携機能も実装して頂くことができました(今ならMackerelとReactioを同時に使うことで, Reactioの料金が6ヶ月無料になる「障害対応完璧プラン」キャンペーンも実施中です!). 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に設置されたコード一式を含むtarを取得/展開するスクリプトを用意しておいて, これをAMIからインスタンスを起動する際に実行することで用意しています. いわゆる「Pull型」のデプロイと言えるでしょう. なお, 開発したコードはデプロイを行うタイミングでCIサーバ(Jenkins)においてtarで固められ, S3に配置するようになっています.

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

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

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

    1. 開発者がBotにリリースフローの開始を宣言する
    1. BotがGitHubにリリース用Pull Requestを作成する(masterブランチからproductionブランチへのPR)
    2. github-pr-releaseを使っています: http://papix.hatenablog.com/entry/2015/10/26/123631
    1. BotがJenkinsにstaging環境へのデプロイを依頼する
    1. Jenkinsがstaging環境へのデプロイを実施する
    2. 最新のmasterブランチのコードをtarに固め, staging用のtarとしてS3に配置
    3. stagingへのデプロイスクリプトを実行
    1. 開発者やプロダクトオーナーがstagingで最終的な動作確認をする
    1. リリース用Pull Request(2.)をマージする
    1. Jenkinsがproduction環境へのデプロイを開始する
    2. staging用のtarをコピーしてproduction用のtarにする
    3. 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を利用しながら, 効率的にサービス開発を進めていきたいと思っています!