はじめに
さまよえるアラフォー男子 @artifactsauce です。
突然ですが、弊社は「外資就活ドットコム」というWebサービスを開発・運営している会社です。サービスイン当初はイケイケガンガンで高速開発・高速リリースをうたっていましたが、開発者が増えるにしたがって様々な問題が発生してきました。今回はプロダクトのリリースにまつわる問題を解決するために弊社で採用した開発ワークフローについて紹介します。
どんな問題が起こっていたのか?
Capistranoによる自動デプロイは実現していた我々ですが、それですべてがうまくいったわけではありませんでした。具体的には以下の様な問題が発生していました。
- デプロイできる環境を用意するのが面倒である。
- 各デプロイ担当者がデプロイツールをインストールする必要がある。
- デプロイツールを更新していない場合には失敗する。
- デプロイ対象サーバーにデプロイ担当者の権限を与える必要がある。
- 全員がデプロイできるようにしたいため、人数が増える度に設定する。
- 各デプロイ担当者がデプロイツールをインストールする必要がある。
- リリースしたものの、すぐに不具合が発覚することが度々ある。
- 開発環境と本番環境との差が原因であることもしばしばある。
1. に関しては、各人がデプロイ環境を準備するのではなく、すでに準備された環境上でデプロイを実行することができれば解決できそうです。 2. に関しては、本番によく似た検証環境で事前に確認できるようにすれば防げそうです。
そこで今回の目標は、次の2点を中心に、開発ワークフローを再設計することにします。
- デプロイ担当者各人の環境に依存しないデプロイプロセスの構築
- 検証環境を含めた運用環境の構築
運用環境の種別
まずは検証環境を含めた運用環境の種別ついて考えましょう。Webサービスでは開発から本番までをフェーズに区切り、それぞれのフェーズにおいて別々の環境で運用するのが典型的な形式だと思います。我々は4つのフェーズと環境に分けることにしました。
production - 本番環境
一般ユーザーが利用する環境であり、一般的には本番環境と呼ばれます。常に安定して動作していることが求められます。環境としては1つだけ存在します。
staging - 事前検証環境
production環境とほぼ同一の環境です。これも1つだけ存在します。この環境を用意することで前述の問題点(2.)を解決します。この環境が行うべき「検証」にはWebアプリケーションのリリース直前の動作確認だけでなく、以下の点も含まれることになりました。
- サービスのサポートを行うオペレーターが日々の作業を事前に検証する
- 開発・運用の担当者がChefのレシピの適用を事前に検証する
testing - 機能検証環境
開発された機能を検証するための環境です。リリース直前だけでなく、開発中からプロダクトマネージャーやオペレーターの方に実際に使ってみてもらって、仕様との齟齬がないかなどを確認します。開発された機能ごとに作成することになるので、これは常に複数の環境が存在しえます。リリース前にはここで入念に動作テストを行います。
development - 開発環境
開発者が機能を実装するための環境です。現在は開発者各自のラップトップ内にVagrantでVMを起動し、Chefで本番と似たような環境を構築します。詳細は別の記事に譲ります。
デプロイを実行する環境
デプロイを実行する環境をどこに準備すべきでしょうか?それには継続的インテグレーション(CI)サービスを利用するのが最適でしょう。
継続的インテグレーションとは、ソフトウェア開発において品質改善のためにビルドやテストなどを継続的に実行していく手法のことを指します。CIツールで最も有名なのはJenkinsなのではないかと思いますが、ここ数年ではCircle CIなどがSaaSとして提供しています。どのツール/サービスを使うかはともかく、これらは多くの場合、デプロイのためのステップや機能も提供しています。デプロイ環境をCIツール内に構築することで、デプロイ担当者各人が用意する必要は無くなりますし、また各人に運用サーバーへのアクセス権限を与える必要も無くなります。またこのようなサービスではCIはソースコードのPUSHをきっかけに実行されるので、デプロイのきっかけもソースコードのPUSHのタイミングになります。CIツールを使えば各開発者が特に気を払わなくても自動的にデプロイする環境が構築できます。
我々はCircle CIを採用し、自動デプロイの機能を使って、前項で構築した環境にデプロイするプロセスを構築することにしました。しかしここで必要となるのが、どのような条件でどの環境にデプロイするのかというルールです。ルールが無ければ自動化できません。そこで次に、これまでの条件にマッチするブランチングモデルを規定することにしました。
ブランチングモデル
結果的に Git-flow と GitLab-flow を混ぜたようなモデルになりました。下記がその概略図です。
弊社の開発チームでは以前から上記とほぼ同じブランチングモデルが規定されており、特に問題なく運用されていました。今回はそのモデルに対して、自動デプロイにまつわる変更を少し加えただけです。以下でそれぞれのブランチの役割について解説しますが、自動デプロイにまつわるものだけでなく、元々の開発スタイルについての考慮が含まれていることをご承知おきください。
ブランチの役割
master - 開発のメインブランチ
masterブランチは開発のメインブランチです。常に存在し、そして常にデプロイ可能な状態を保つことが期待されます。マージする際はPull Requestを経由したもののみ受け付けます。 マージされるとユニットテストがCIで自動的に実行されます。テストが成功すると自動的にstaging環境へデプロイするように設定します。必然的にstaging環境と常に同一になります。
topic - 小規模な機能開発
topicブランチは比較的小規模な機能開発、または不具合を修正する際に作成します。その粒度が小規模かどうかは主観的に判断してかまいません。目安としては、1人で開発が完了するようなものと考えて差し支えないでしょう。topicブランチはmasterから分岐します。ブランチ名はprefixに topic/
を付けます。ブランチ名は機能を端的に示すものが望まれます。
release & feature - 中規模以上の機能開発
releaseブランチは中規模以上の機能開発をする際に作成します。何をもって中規模とするかは主観的に判断してかまいません。masterブランチにマージするまでに相当な時間を要することが予想される場合と考えれば良いと思います。releaseブランチはmasterブランチから分岐します。ブランチ名はprefixに release/
を付けます。中規模の開発では複数の機能が実装されることが多く、releaseブランチは機能を端的に示す名前を付けられないかもしれません。その場合はリリース目標の日付やプロジェクト名などをつけましょう。弊社ではかつて、 release/Atami
や release/Beppu
といったブランチ名が付けられたことがあります。「このリリースが終わったら、俺、温泉に行くんだ…。」といった願望が込められています。releaseブランチが作成されたらtesting環境を構築することが望まれます。
featureブランチはreleaseブランチから派生する細かい単位の機能開発をする際に作成します。releaseブランチとfeatureブランチとの関係はmasterブランチとtopicブランチとの関係とよく似ています。1つのブランチに対して複数人で直接コミットすればパニックになること必至です。そこでより小さな機能単位で開発するため、releaseブランチからさらに分岐させます。ブランチ名はprefixに feature/{NAME}/
を付けます。 {NAME}
には先ほどのreleaseブランチの名前が入ります。そうすることで、最終的にreleaseブランチをmasterブランチにマージする際、あらかじめマージしておくべきfeatureブランチの取りこぼしを防ぐことができます。前述の例を借りれば、 feature/Atami/fix-func1
といったブランチ名になります。
production - 本番環境と同一
productionブランチはproduction環境にデプロイするための特別なブランチです。開発者がチェックアウトして利用することは基本的にありません。productionブランチにマージされるとCIを経由して自動的にproduction環境へデプロイするように設定します。masterブランチからのPull Requestのみを受け付けるようにします。テストが成功したリビジョンのみがマージされるように設定することで、CIによるテストは除外しても問題ありません。
GitHub flow や GitLab flow との違い
ここで規定した我々のブランチングモデルは既存のモデルとどう違うのでしょうか?GitHub-flow と GitLab-flow は非常によく似たモデルです。そしてその2つのモデルと我々のモデルとの大きな違いはreleaseブランチとfeatureブランチではないかと思っています。GitLab-flow にもreleaseブランチはあるのですが役割が違います。役割が違うのに同じ名前なので、読まれている方は混乱してしまうと思いますが、我々のreleaseブランチはメンテナンスブランチではありません。 名前はともあれ、releaseブランチとfeatureブランチは、中規模機能開発において作業ブランチがmasterブランチから大きく乖離する場合でも開発が滞らないようにするために作られています。GitHub-flow と GitLab-flow の2つのモデルはmasterブランチ以外のブランチからさらにブランチを切るフローがないため「ビッグバンリリース」が非常に難しいと考えています。細かい変更を順次リリースし続けることが理想的ではありますが、世の中そんなにうまくはいかないものです。
開発ワークフロー
運用環境とブランチングモデルを規定し、それぞれが紐付いたところで、これらをワークフローとしてまとめてみましょう。ここでは要件定義などの実装前プロセスは省いているのであしからず。
1.実装
まずはdevelopment環境でプログラムの実装です。プログラムを書き上げたら(書き上げなくても)GitHubにプッシュします。実装に関して議論が必要な場合はこの時点でPull Requestを作成して同僚に意見を求めることができます。
2.受け入れテスト
プログラムの実装が完了したらプロダクトマネージャーやオペーレーターに受け入れテストを依頼します。受け入れテストを行うのはtesting環境です。testing環境の起動はHubotで実行できるようにしてあります。
3.コードレビュー
受け入れテストが完了したらPull Requestを作成し、他の開発者にコードレビューを依頼します。コードレビューの詳細については別の記事に譲ります。
4.staging環境へデプロイ
コードレビューに合格したら作業ブランチをmasterブランチにマージします。コンフリクトしていなければGitHubの画面上でボタンを押すだけでマージできます。masterブランチマージされたことが自動的にCIに通知され、CIツールがでユニットテストを実行します。ユニットテストに合格したら、自動的にstaging環境へデプロイされるように設定してあります。staging環境へデプロイされたら軽く動作確認を行います。
5.production環境へデプロイ
任意のタイミングでmasterブランチをproductionブランチにマージします。マージするためには改めてGitHubでmasterブランチからproductionブランチへのPull Requestを作ります。デプロイ前の作業を確認した上でマージボタンを押して、あとはCIが行うデプロイプロセスを確認します。これでproduction環境へのデプロイが完了します。
副次的な効果
この開発ワークフローで運用を始めた結果、当初の目的には含まれていない良い効果も得られました。代表的なものは以下の2点になります。
デプロイの権限をGitHubの管理と統合できた
これまでデプロイを実行する際には公開鍵をデプロイ対象のサーバーに配置するといった作業が発生しており、リポジトリに書き込みできる人とデプロイを実行できる人に意図しない差が発生することがありました。細かい権限管理を行いたい組織にとってはそれでも良いかもしれませんが、我々の規模では過剰な権限分けであり、注意を払わなければいけない範囲を拡大させるだけでした。しかしCIでデプロイするようにしたことでその準備作業も無くなり、また該当リポジトリへのWrite権限があればデプロイできるというように開発に必要な権限とデプロイに必要な権限が一致するようになりました。
デプロイが可視化されるようになった
デプロイがGitHubのPull Requestをきっかけに実行されることによって、いつ誰がどのような変更をデプロイしたのかがPull Requestを見ることにより明確かつ探しやすくなりました。これまでもデプロイ時にコミットメッセージをSlackへ通知していたのですが、この程度の情報では誰がどのような変更をデプロイしたのかをすぐに把握することができませんでした。またCIでデプロイするようにしたことで、CIの管理画面で誰でも同時に、または後からデプロイの実行結果を確認することができ、より大きな安心感につながっています。
問題点
何とか練り上げてきたこのワークフローですが、まだまだ足りないところも多いと感じています。
QAプロセスが不十分
現状のフローではmasterブランチにマージした後、staging環境で挙動を確認する時間をあまりとっていません。testing環境で確認を充分に行っているので良しとしていますが、やはりmasterブランチにマージされた状態で確認しなければ本当の確認になっていないとも言えます。しかし我々はできるだけ早くユーザーに価値を届けることを重視し、開発速度を上げてサービスの提供と修正を細かく繰り返すことで必要な品質レベルを確保しつつ新規機能を最速で提供するべきだと判断し、このワークフローを採用しました。
ロールバックがむしろ面倒になった
仮にあるデプロイによって問題が発生した場合はロールバックをする必要がありますが、CIでロールバックを行うことはできないため、デプロイツールを利用するか、サーバーへログインして操作するか、マージコミットをRevertして再度デプロイする必要があります。CIでデプロイできることによってデプロイツールが手元に用意されていないことが多くなったため、リリース時のトラブルの際にはむしろ慌てるという状況になってしまいました。
まとめ
リリースにまつわる問題点を、運用環境およびデプロイプロセスを見直し、開発ワークフローを再設計することで解決しました。記事の都合上、これらの施策は一気に行ったような書き方になりましたが、実際はそういうわけではなく、段階を経て進めていたものです。もし我々と同じような開発の問題を抱えているチームがありましたら、少しずつ部分的にでも取り入れてみていただければと思います。
このワークフローは現在の弊社の状況に最適化された方法であり、会社の風土や規模、さらには技術レベルが変わることによって内容は常に変わってくるものであると思います。皆さんもそれぞれのチームに合ったワークフローを構築してみてください。
参考
- A successful Git branching model を翻訳しました - 見えないチカラ
- GitHub Flow (Japanese translation) - Gab-km
- GitLab flowから学ぶワークフローの実践 - POSTD
継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化
- 作者: David Farley,Jez Humble,和智右桂,高木正弘
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2012/03/14
- メディア: 大型本
- 購入: 24人 クリック: 567回
- この商品を含むブログ (53件) を見る
今回紹介した一連のプロセス改善については、上記の書籍を読めばより深く理解できると思います。私はもっと早く読んでおけば良かったと後悔しました。この本の中では繰り返し、1) 頻繁にテスト&リリースすること、2) そのプロセスを徹底的に自動化すること、が説かれています。開発に混乱をもたらし、リリース作業に苦労し、ユーザーへ価値を届けられない状況を打破する方法を指南しています。ご一読あれ。