Ruby/Pythonで依存パッケージをローカルインストールして開発環境構築やCIビルドを高速化する

By raimon, 2015-11-08(日), in category Git

CI, Git, GitHub, Python, Ruby

一般的にRuby/Pythonで書かれたアプリケーションの依存パッケージはBundler/pipでインストールされるが、rubygems.orgPython Package Indexからの取得・展開に時間がかかり、またこれらの中央サーバがまれにダウンしていると何もできなくなってしまうケースがある。

回避策の一つとして、依存パッケージをGitリポジトリに飲んでしまい、パッケージリポジトリとは通信せずローカルインストールで済ませる、いわゆるvendoring(ベンダリング)と呼ばれる方法がある。

サンプルリポジトリ

それぞれのサンプルとなるGitリポジトリをGitHubに作成した。

Ruby + Bundlerの場合

Ruby + Bundlerの場合 Gemfile に依存パッケージを宣言し、bundle package コマンドでローカルに保存できる。

インストールの場所は慣例的に vendor 以下が使われる。

# 依存パッケージの宣言
$ cat Gemfile
source "https://rubygems.org"

gem "minitest"
gem "minitest-reporters"

# 依存パッケージのインストール
$ bundle install --path vendor/bundle

# 依存パッケージをローカルに保存
$ bundle package --all

保存された *.gem ファイルはvendor/cache以下に管理される。

GitHubのCreate New repository画面でRuby用の .gitignore ファイルを自動生成している時は、このキャッシュファイルがバージョン管理下に置かれるよう設定を1行追加すると良い。

diff --git a/.gitignore b/.gitignore
index 28f4849..9c7d638 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ build/
 ## Environment normalisation:
 /.bundle/
 /vendor/bundle
+!/vendor/cache/*.gem
 /lib/bundler/man/

上記の設定を追加することで、ローカル *.gem ファイルをバージョン管理の対象として追加できるようになる。

$ git add vendor/cache
$ git commit -m  'Packaging Gems'

依存パッケージが全てGitリポジトリに含まれるようになったため、開発メンバーの環境やCI環境ではこのファイルを使って --local オプションを指定することでローカルインストールが可能になった。

$ git clone git://github.com/raimon49/ruby-local-gems-sample.git
$ cd ruby-local-gems-sample
$ bundle install --path vendor/bundle --local

例としてTravis CIでローカルインストールを使う設定を載せておく。

install:
  bundle install --path vendor/bundle --local

ローカルインストールを使ってCIビルドを走らせると、bundle install は1秒かからず完了していることが分かる。

Python + pipの場合

Pythonの場合はpipとwheelパッケージの組み合わせによって pip wheel コマンドが使えるようになり、ローカルに保存できる。

インストールの場所は慣例的に wheelhouse 以下が使われる。

# 依存パッケージをインストール
$ pip install [Package A] [Package B]...

# 依存パッケージの書き出し
$ pip freeze > requirements.txt

# 依存パッケージをローカルに保存
$ pip install wheel
$ pip wheel -r requirements.txt
$ git add wheelhouse
$ git commit -m 'Packaging wheels'

依存パッケージが全てGitリポジトリに含まれるようになったため、開発メンバーの環境やCI環境ではこのファイルを使って --no-index -f wheelhouse オプションを指定することでローカルインストールが可能になった。

$ git clone git://github.com/raimon49/python-local-wheels-sample.git
$ cd python-local-wheels-sample
$ pip install -r requirements.txt --no-index -f wheelhouse

例としてTravis CIでローカルインストールを使う設定を載せておく。

install:
  - pip install -r requirements.txt --no-index -f wheelhouse

ローカルインストールを使ってCIビルドを走らせると、pip install は1秒かからず完了していることが分かる。

まとめ

RubyやPythonで書かれたアプリケーションの依存パッケージをvendoringで管理する方法で、開発環境構築やCIビルドを高速に行うことができる。

高速化の他にも、開発サーバやプロダクションサーバからのHTTP/HTTPS通信先が絞られているケースや、ビルド・デプロイを公式パッケージリポジトリのダウン影響を受けず安定化させる効果も期待できる。

一方で、依存パッケージを丸ごとGitリポジトリに飲むのは、リポジトリサイズの肥大化という面で、ある意味で富豪的なアプローチと言える。チーム・組織のサイズによって、例えばパッケージリポジトリのミラーを立ち上げるといった別の方法が適している事も十分に考えられる。

この記事では例としてTravis CIでのビルドにローカルインストールを利用しているが、Travis CIを使っていてCIビルドの時間を短縮化したいだけならCaching Dependencies and Directories機能を使っておくのも良い(CircleCIにもYAMLでの書式は違うが同様の機能がある)。