% cat

BananaCI というツールを運用しています

こんにちは。@endotakuya です。
突然ですが皆さんは CI/CD 、してますか?
オンプレミス型の Jenkins や Drone 、クラウド型の CircleCI や Google Cloud Build などを使っている方もいらっしゃることでしょう。

本記事では、社内で開発して1年以上運用している BananaCI というツールについてご紹介します。

BananaCI とは

GitHub の Pull Request(以下、PR)ごとにステージング環境を構築する、GitHub Apps ベースの CI です。
昨年(2017年)の6月頃から開発をはじめ、8月頃に運用開始。公開はしておらず社内限定のツールではあるものの、ferret留学くらべ~る 等の主要サービスで使用しています。

弊社サービスのほとんどは開発環境を Dockernize しており、コンテナで動作するWebアプリケーションを Google Kubernetes Engine(以下、GKE)にデプロイすることで、ステージング環境を作成しています。

次章から詳しく説明していきます。

構成

使用している言語・サービスは次の通りです。

  • Ruby / Ruby on Rails
  • Docker
  • Kubernetes
  • GKE
  • GCR(Google Container Registry)

以下の図に沿って説明していきます。

① GitHub Apps をインストールしたリポジトリで PR を作成し、特定のラベル(bananaci)を付与
② Webhook が発火
③ BananaCI 上で、リポジトリのメインブランチを Clone
④ 該当の PR を Fetch
⑤ 指定の設定ファイルから環境を docker build し、Docker イメージを GCR に Push
⑥ ⑤ の Docker イメージを対象に、GKE へデプロイ
⑦ アクセス用の IP(External IP)を取得
⑧ PR のコメントで IP を通知

それほど複雑な構成ではないと思います。

BananaCI で作成した環境を削除するときは、ラベルを外すか、PR の Merge / Close アクションがフックになります。
また、追加コミットを行った際は一度環境が削除され、再度構築し直します。勘の鋭い方は、追加コミット分を fetch してきて差分で Docker イメージを作成後、GKE の環境を更新し直せばいいのでは?と思われる方もいるかと思いますが、この辺りに関しては後ほどご説明します。

ステージング環境が構築されるまでの時間についてはプロジェクトの規模にもよりますが、ラベルを付与してから7分〜12分程かかります。
ボトルネックになっている部分は、docker build です。BananaCI 用の Dockerfile を作成して不要なパッケージを削除したり、一般的な Docker イメージのスリム化で行われる RUN 命令の連結化・レイヤーの最小化、multi-stage build などを行っていますが、インパクトのある解決には至っていません。

運用までのさまざまな道のり

no space left on device

Docker を触ったことのある人なら、一度は出会ったことがあるであろうVMの容量不足メッセージです。BananaCI の開発時は、1日に何十回と docker build をしてデバッグし、イメージを大量生産していました。
開発当時、私は Docker 初心者だったので、「さっきまで問題なく動いてたのに!」と、苦闘した記憶があります。

キューイングシステム

当初は Sidekiq を使用して並列処理によって環境を構築していました。しかし、Sidekiq はスレッドベースで処理が行われるため、複数の処理が同タイミングで走るとリソースの競合が起こり、正常に環境が作成されない、または意図しない環境が生まれることがありました。
解決方法としてはプロセスベースである Resque に乗り換えることで対応できましたが、比較的大きな変更であったため苦労したことを覚えています。

追加コミットの扱い

開発を進める中で議論になったことの1つが、追加コミット時の構築方法です。
構成の説明時にもありましたが、すでに環境が作成されている場合は差分で作成した Docker イメージによって環境の更新は可能です。
しかし、環境を作成中の PR において比較的短い時間で追加コミットが Push された場合は、並列処理による処理完了の前後関係によって意図しない環境ができてしまうことがありました。対策として、先に動いている処理のリソースを削除し、常に最新のリソースで同じ環境を最初から構築する方針となりました。

Kompose

Webアプリケーションを Dockernize する際、複数コンテナを Docker Compose で扱うのが一般的かと思います。
Konpose は、docker-compose.yaml に書かれた構成を k8s 用に変換してくれるスグレモノです。これにより、余分な設定ファイルをリポジトリに置く必要はなく、既存のコンテナ構成ファイル( docker-compose.yaml )によって k8s の環境構築が可能です。が、開発中に kompose 側の仕様変更があり、かつ開発が活発でこれから更に仕様の変更が起きる可能性が大きいことも考慮し、途中まで kompose に寄せていた設計を k8s 標準の設定に書き換えることになりました。

BananaCi によるメリット

利点が全くなければ1年以上も運用していられませんよね。
BananaCI のメリットを2つほど挙げておきます。

レビューフローの高速化

BananaCI が導入される前のほとんどのサービスでは、AWS の EC2 インスタンスをステージング環境用に用意し、コードレビューが済んだ PR を1つまたはタイミングを合わせて複数の PR をまとめてデプロイして動作確認を行っていました。しかし、PR の内容によってコンフリクトをしてしまったり、それを避けるためにデプロイ待ちの時間が発生し、なかなかマージできないといった課題がありました。
BananaCI を導入した現在では、PR ごとに動作確認の可能な環境が作成されるため、コードレビューと並行してスムーズに動作確認ができるフローが可能となりました。

エンジニア以外もステージング環境の構築が可能

静的ページの文言修正や画像の差し替えなど、エンジニア以外でも PR を出すことがあります。その際、これまでステージング確認をするときはエンジニアにデプロイを依頼後、エンジニア側の状況によって反映されるまでのタイムラグが発生していました。
BananaCI 運用後は、PR にラベルを付与するだけで環境が作成されるため、エンジニアにデプロイを依頼することなく、誰でもステージング環境を構築することが可能になりました。

料金

気になる料金ですが、ノードを3台〜15台でオートスケールし、平日のみ可動させた状態で月あたり約8万円ほどかかっています。
導入しているリポジトリ数が多いこともありますが、それでもあまり安くはない金額だと思います。この点については要改善ですね。

今後の展望

コスト削減もそうですが、1年以上運用をしてきて改善したい点・追加したい機能も盛りだくさんです。

Docker Build の高速化

Rails アプリの Dockernize ポイントとして bundle install が遅いので、Gemfile / Gemfile.lock だけを先に COPY して bundle install & cache を効かせたり、依存性のないレイヤーを並列処理させたりと、まだまだ試せそうなことはありそうです。そもそものベースイメージを変える手もありますが、BananaCI 用に最適化しすぎて、コンテナのメリットである各環境における統一性が損なわれてしまうのは避けたいので、イメージ変更は最終手段でいきたいですね。

ログの可視化

個人的には今一番欲しい機能です。現在は、なにか不具合が起きた際に Slack へ通知が来るようになっているのですが、そのたびに担当者が BananaCI のサーバーに入り、丁寧にログファイルから該当の箇所を探して原因を見つけ出し、PR 作成者へ報告を行っています。スマートじゃないです。理想としては CircleCi のような見やすいログビューワが欲しいですね。

導入のしやすさ

今回はツールの紹介だっため、導入部分の詳しい説明は省きましたが、実は Bananaize するには、いくつかの設定ファイルを作成してリポジトリ内に置く必要があります。例えば、GKE 構築用の k8s 設定ファイルや、ステージング用の Docker イメージなどをビルド・GCR へ Push するシェルスクリプトなどです。この辺りは、システム側で巻き取れる部分もあるので、できるだけ簡潔にしていきたいです。

まとめ

いかがでしたでしょうか。
まだまだ改善の余地はあれど、BananaCI の導入によって、開発フローをより簡潔にすることが可能となりました。GitHub Apps と Docker の組み合わせは、業務効率化や開発スピードの向上など様々な面でメリットがあると言えます。BananaCI は現在クローズドなサービスですが、皆さんからの要望があれば、パブリックサービスとして提供することもあるかも…しれません(し、全くないかもしれません。気になる方はぜひ Twitter 等からご連絡くださいw)

CI/CD で楽をしていきましょう。

最新記事

BananaCI というツールを運用しています

こんにちは。[@endotakuya](https://github.com/endotakuya) です。 突然ですが皆さんは CI/CD 、してますか? オンプレミス型の Jenkins や Drone 、クラウド型の Cir...

endotakuya
2018年12月06日

シグナルと kill コマンドについてちゃんと調べてみた

開発中にプログラムが固まった時に `kill プロセスID` って打ったことがある方は多いかと思います 僕もそうで、 kill コマンドはプロセスを強制終了するためのコマンドだと思っていました ですが puma のログ...

tkhr0
2018年12月06日

Rails における弊社の CSS 設計

Hi. [rigani_c](https://twitter.com/rigani_c) です。 これはベーシックアドベントカレンダーの記事です。仲間たちの投稿一覧はこちら 🎄https://qiita.com/advent-...

rigani
2018年12月02日