個人開発のプライベートなリポジトリを CI する方法の検討

しばらくブログを書いていないと思い、気がつけば 2 年近く更新が止まっていた…。2018 年も間もなく終わりだが、今年最初のエントリ。
頻度は低いものの、Android アプリの更新は続けている。

もう少し高頻度に開発したいが、どうしても時間がかかるのがテスト。特に最近は Kotlin に切り替えたり、各種のライブラリをアップデートしたりとテストを入念にしておきたい要素が多い。そうするとやはり CI の仕組みを整えたい。

まだやってなかったの?という感じはあるが、クラウドサービスだとプライベートリポジトリでは基本的に有料なためなかなか手が出せていなかった。
OSS として開発している場合には、Travis など各種の CI サービスが無料で使えることが多いので基本的に困らないのだが。かといって、CI サービスを無料で使うためにこのアプリのコードを公開するのは避けたい。

では PC 上でテストを流すので我慢しようというのも微妙だなぁと思うので、OSS の開発と同様に、Git リポジトリにコードを push するとインターネット上のどこかの環境で CI が実行されるような環境の整備を目指したい。費用はゼロとは言わないが、最小限に。

そんなモチベーションのもとで、個人開発のプライベートリポジトリに対して CI をするにはどんなやり方があるかを検討してみた。

まずは、クラウドサービスを利用するのか、インフラを含めて自前で用意するかが大きいポイントで、もしインフラから用意する場合は CI ツールとして何を使うかが検討の必要なところだろう。まず前提をいくつかあげて、それに対して主な選択肢をあげて自分なりの評価をしてみる。

注意:このエントリは長いです。また、実装面の話は出てこず、結論もはっきりは出ていません。

前提事項

予算

理想的には数百円/月に収めたい。
なぜなら、VPS だと 1000 円/月くらいで 1 つ契約できてしまい、そこで Jenkins を構築すれば、パフォーマンス等に不満はあるだろうけれど実現できてしまうだろうから。
検討するからには、これより安く、良いものができるかを基準に考えたい。

リポジトリの所在

GitHub のプライベートリポジトリで、複数ある想定。

CI の実行方法

プルリクエスト単位で実行できれば良い。
可能であれば 1 日 1 回などの頻度で定期的にも実行したいが、これは OSS のプロジェクトでもできているわけではないので保留。

GitHub 連携

GitHub のプルリクエストと何らかの方法で連携でき、結果をプルリクエストに反映できること。

対象のプロジェクトの内容

Android アプリのプロジェクト。
ビルドツールは Gradle、言語は Kotlin、ビルドには Android SDK が必要で、Gradle を利用して外部からライブラリを多数ダウンロードする必要がある。
UI を含むテストなどではエミュレータを使ってテストを実行するため、ある程度のメモリが必要。(最低限で 4GB くらい?)

利用頻度

Android プロジェクトも対象ということで、長めに 1 ビルドあたり 30 分としておく。
作業時間中、絶え間なく push するかは微妙だが、一応そのように仮定しておく。
土日は半日以上触っていることもあり得るだろうが、平日は数時間が限界。
そうすると、2 日 x 6H + 5 日 x 2H = 22H/週。22H x 4 週 = 88H/月、がビルド時間。
ビルド回数は 88H / 0.5H = 196 回/月。
(まあ今のペースから考えたらこんなにやれないだろうけど。)

同時実行数

一人でやることもあり 1 で十分と考えておく。
複数リポジトリがあったとしても、同時には 1 つで十分。

主な選択肢

以下などを参考にしつつ、ピックアップして検討。

Travis

Bootstrap プランで$69/月。
リポジトリ、ビルド時間は無制限だが、同時には 1 ジョブのみ。
1 ドル 112 円だとして約 7700 円。
想定利用頻度からすると高い。

CircleCI

最小だと Parallelism が 1 で無料だが、無料なのは 1000 分/月 = 16H/月。
前提からすると、これはさすがに少ない。
Parallelism を 2 にするとビルド時間は無制限になるが $50/月 = 約 5600 円/月。

Shippable

最小の free プランだとプライベートリポジトリは一つしか使えないように見える。
150 回/月。必要量よりは少ないが許容範囲内。
取り急ぎ使うのは良いけれど他に転用出来ないのがいまいちか。

自宅サーバ

費用面では場合によってはメリットが出るかもしれないが、物理的にも運用が大変なので今回は外す。

VPS

1000 円以下でも使えるので、安く済む。
ただ、1000 円くらいだとスペックが弱すぎて使い物にならないかもしれないので、さくらインターネットの VPS 4GB プランくらいを想定しておくとして約 4000 円/月。こうしてしまうと CircleCI あたりと大差ないな…。
インフラを持つことになるものの、後述の AWS に比べれば、固定費なので安心感はある。
サーバ構築・運用の手間もかかる点が大きなデメリットだが、それはそれで勉強にはなる。
ただ、今やるノウハウ的な意味でもプラス要素が少ないように思う。
無駄ではないが、今さら時間をかけてやりたくはない。

AWS

クレデンシャルを公開するような失敗をしなければ・・
また DDoS のような攻撃によって跳ね上がるリスクを負わなければ、色々なサービスを組み合わせることで良い候補となるかもしれない。
業務に活きそうな知識としても AWS だとプラスになるものがありそう。
普通にサーバを構築するときの発想からか、サーバというと 24 時間起動してるイメージもあるが、必要分だけ利用して従量課金に近づけることも考えれば、費用面でメリットが出せるかもしれない。
AWS での実現方法をもう少し深掘りしてみる。

CodeBuild

まさにそのためのサービスがあるのだから使ってみたらどうかと見てみたが、EC2 などを運用するよりも費用が高くつきそう。
従量課金ということで、最小の build.general1.small を選び、Linux でビルドするとすると、26.4/月 = 約 3000 円/月。
思ったほど悪くないが高い。

EC2 で普通に Jenkins 構築

まあ普通な案。
確実にできるけれど、納得いくスペックで起動し続けていたらお金がかかるはずなのでもうひと工夫したい。
インスタンスタイプは t3.medium くらいだろうか。
一つのインスタンスで Jenkins とジョブそのものも動かすと
39/月 = 約 4500 円/月。
もしこれをスレーブにして、Jenkins そのもの(マスター)はもう少し安価なインスタンスに分けておくなら、マスターは t2.small で 22/月 = 約 2500 円/月。
合計 7000 円/月。
実際には転送量だとか、もっといろいろかかるはず。

EC2 の Jenkins を必要なときのみ起動

EC2 インスタンスを必要なときのみ起動することで、起動時間を短くする。個人なので、必要なときに起動する運用でも実は問題ないのかも。push してから起動してないことに気づく、なんていうことは普通にありそうだが我慢できる気もする。
あとはインスタンスの停止忘れ。
Lambda で色々ガードしておくのがよさそう。
一定時刻をすぎたらインスタンス停止。
HTTP リクエストが来たら起動とか。
あとはジョブで使うなら EC2 よりコンテナ関連サービスの方が良さそうな気もする。

想定の利用時間ぴったりの分だけ起動するなら
マスター: 0.0304 x 88H = $3 = 約 300 円/月。
スレーブ: 0.0544 x 88H = $5 = 約 560 円/月。
合計 860 円/月。圧倒的に安い。

ジョブの実行は 88H で済むかもしれないが、Jenkins の画面はもっと高頻度で見るかもしれない。
そうすると先ほどの結果からマスターは 2500 円/月として約 3000 円。
これなら CodeBuild を使うよ…。

LambCI

上記の結果だと、Jenkins のマスターを常時起動しているのが響いている。
何を見たいのかはもう少し整理したいが、静的なレポートとしてどこかに保存しておけば済むのかもしれない。
また、ジョブの起動なんかは Web の API として提供できれば、イベントドリブンで全部済ませることができて、サーバーレスな構成でできるのではないか。

…そういう観点では、LambCI が使えそう。Lambda で CI できる。
ただ、使用できる言語や実行時間などいろいろと制約がある。
Lambda の実行時間が 15 分に延びたとはいえ、さすがに難しいだろう。
また、Java を動かす場合の説明はあったが、Android のエミュレータとなると難しいと思われる。
ECS を駆使すればできるかもしれない。
しかしジョブそのものの実現に苦労しそうなので、一旦保留しておく。
ただ、なんとなく、本質的には求めているのはこの構成な気がするので戻ってきそうな気はする。

Concourse CI

一人で CI するなら、意外にどれもせいぜい数千円で収まるようだった。これくらいは投資として払わないと…とも言えるが、やはりさらに少額に抑えることは検討したい。
また、クラウドサービスの利用が憚られるようなケースでの利用(仕事で)にも活用できるやり方を、と考えたりすると、AWS で EC2 上に Jenkins などで準備するのが無難そう。

ただ、環境として AWS というだけで、そこで動かすものが本当に Jenkins で良いのかは別途検討する必要はある。

その観点で調べてみると、以下のように Concourse CI の方がコンセプトとしては適していそう。

  • 必要となるリソース(メモリ等)が Jenkins より少ない(らしい)
  • CLI 操作できる
  • コンテナベース
  • yml で設定できる

実際に試してみると、docker-compose up -d で起動した concourse に、hello world の チュートリアル の内容でジョブを動かすことはできた。チュートリアルをやるとそれなりに理解はできる。

ただ、チュートリアルを進める中で作業を中断し、PC をスリープさせたところ動作がおかしくなってしまった。
どうしてもエラーが解消せず、利用できない状態に…。
Windows 上で Docker Toolbox を使って Concourse を構築したためかもしれない(と信じたい)が、いずれにしても、今自分自身ですぐには扱えそうにない。

結論

というわけで、あまり面白くないが、現状で最善かつすぐに実現できそうな選択肢は AWS EC2 + Jenkins を必要時間だけ起動する、になりそう。
Concourse がもっとスムーズに動けばこちらにしたかったのだが…。
LambCI も捨てがたい。
awesome-ci に挙げられている他の OSS の CI プロダクトを試してみるのも良いかもしれない。
これらは後日さらに検討したい。


以下、まだ確認できていないが、実際に構成を作るタイミングでは検討したいポイント。

環境をバージョン管理する(極力コードで)

一人だからこそ、属人的に環境を作り込んでしまうと再現できなくなる。時間が経つと本当にわからなくなるので大事。
Jenkins の GUI でいろいろ設定したりするのは注意が必要。

docker-compose を使えるようにする

複数のコンテナからなるアプリの場合、CI でも同様の構成で起動することができるかどうかということ。テストの実行でも、RDB、MongoDB などと連携が必須なケースはあるだろう。

Android 用のコンテナ環境をつくる

エミュレータを起動する、となると結構大変かもしれない。

ライブラリのキャッシュの維持

Gradle の dependencies をキャッシュするとか、高速化する工夫が必要と思われる。

ビルド用のコンテナのビルド

ビルド用に使うコンテナそのものも公開されているものに少し手を加えたりすることもあるだろうから、場合によっては CI する必要があるだろう。

成果物をどこかに保存して閲覧する

LambCI のように S3 などに保存して閲覧するのが良さそう。

結果をどこかに通知する

これも LambCI のように AWS SNS 経由での Slack などか?

© 2010 ksoichiro