HerokuでOpenCVを使うためのdocker imageの作り方

docker-pluginを使ってちょっと特殊なアプリケーションをHerokuで動かすための自前imageを作る方法について書きました。間違いなどあればご指摘ください。

## Buildpack と Docker ##

Herokuでは、公式サポート対象の言語で簡単なアプリケーションを動かす場合は`git push`するだけで簡単に済みますが、サポート外の言語でのアプリケーションやデフォルトインストールされていないソフトウェアを使用する場合などは、それ用の実行環境を用意する必要があります。(逆にいうと、それを用意できれば多少特殊なアプリケーションでも動かすことができる、ということになります)

現在、公式サポートされているもの以外の言語・ソフトウェアを使う環境を作る方法としては以下の2種類があります。

### Buildpackを作る ###

https://devcenter.heroku.com/articles/buildpacks

これは古くから用いられているもので、アプリケーションをデプロイする際に実行されるスクリプトを用意しておくことで任意の環境を作れる、というものです。実際にはデプロイのたびにソースをダウンロードしてビルドしたりする必要が無いよう、予め対象のソフトウェアをHerokuのCedar Stack環境下でインストールし、そのインストール先ディレクトリごと固めてAmazon S3などに上げておいて buildpackスクリプトではそれをダウンロードして展開するだけ、とするのが定石のようです。

Herokuで公式サポートされている言語環境も、それぞれの言語のバイナリをダウンロードしてきて展開する形でbuildpackが提供されていたりします。

https://devcenter.heroku.com/articles/buildpacks#officially-supported-buildpacks

公式でなくとも仕様に沿っていればどんなbuildpackも指定して使うことができるので、そうやって作られたThird-Party Buildpacksも以下で検索したり自分で作って公開しておくことで利用できます。

https://elements.heroku.com/buildpacks/

### Docker imageを作る ###

https://devcenter.heroku.com/articles/docker

こちらは2015年5月にリリースされた、比較的新しい方法です。[Docker](https://www.docker.com/)を使ってCedar Stack環境を簡単に用意でき、その中で動くアプリケーションを開発したらそのimageを(ほぼ)そのままHerokuにデプロイして動かせる、というものです。

Herokuから公式に各言語ランタイムをインストール済みのimageが公開されていて、それらを使うことで各言語のアプリケーションを動かすことができます。

https://hub.docker.com/u/heroku/

これも自分でimageを作成してデプロイできるので、Cedar Stackのimageを元に自分で使いたい言語やソフトウェアをインストールしたimageを作成すれば、それをデプロイして動かすことができます。

ちなみに公式のDocker imageたちは`Dockerfile`からの[Automated Build](https://docs.docker.com/docker-hub/builds/)で作られているので、そのDockerfileを含むリポジトリを読むことで どうやって作られたimageかを知ることができるというのも有り難いですね。


## OpenCVインストール済みのDocker imageを作る ##

先日、画像処理ライブラリ[OpenCV](http://opencv.org/)を使ったアプリケーションをHerokuで動かしたくて、それ用のDocker imageを作りました。以下はそのメモです。

[Docker Image of Python with OpenCV 3.0 for Heroku - すぎゃーんメモ](http://d.hatena.ne.jp/sugyan/20151130/1448809273)

### 既存のimageから継承 ###

公式で提供されているものを拡張する形でimageを作りたければ、Dockerfileは
```
FROM heroku/python
```
のように継承してから追加していけば良さそうです。まったく新しい言語ランタイムをインストールするところから始める場合は`heroku/cedar:14`から継承して作っていくことになると思います。

### OpenCVのインストール ###

依存パッケージを入れた上でソースをダウンロードしてきてビルドしていくわけですが、そのへんも含めて各ディストリビューション向けにスクリプト一発で最新のものを入れられるようにしてあるものが公開されています。今回はこれを利用しました。

https://github.com/jayrambhia/Install-OpenCV/

ただ、そのままではHerokuで動かす用には不都合があったので幾つか変更しています。

#### インストール先は`/app`以下に ####

重要なこととして、[heroku-docker plugin](https://devcenter.heroku.com/articles/docker)で`heroku docker:release`デプロイする際には、imageの内容そのまますべてではなく`/app`以下を`tar`で固めて作成されたslugというものをアップロードする形になるので、Docker環境内で`/usr`以下などにインストールしてもそれは反映されません。

なので、ビルド時の`configure`オプションで`--prefix=/app/.heroku/foobar`と指定してから`make`するなど、`/app`以下にインストールするようにする必要があります。Install-OpenCVでは`cmake`に`-D CMAKE_INSTALL_PREFIX=/app/.heroku/opencv`を渡すようにしました。

#### python2 moduleが有効になるように ####

`heroku/python`を継承した場合、pythonの実行環境は`/app/.heroku/python`以下にインストールされていて、そこから使えるようにするためにその場所を教える必要もあるようで、以下も`cmake`の引数に追加しました。
```
-D BUILD_opencv_python2=ON -D PYTHON_INCLUDE_DIR=/app/.heroku/python/include/python2.7
```

あと、それによって作られる`cv2.so`をpythonからロード出来るように、インストールされた先の`/app/.heroku/opencv/lib/python2.7/site-packages`を`PYTHONPATH`に追加します。


こうして作られたDocker imageを

https://hub.docker.com/r/sugyan/heroku-python-opencv/

に置いています。

### 自前のDocker imageからアプリを作る ###

これでOpenCVインストール済みのimageが誰でも使えるので、それをベースにアプリケーションを作ってHerokuにデプロイできます。
サンプルアプリケーションを用意してみましたのでお試しください。

https://github.com/sugyan/heroku-docker-python-opencv-example

```
$ git clone git@github.com:sugyan/heroku-docker-python-opencv-example.git
$ cd heroku-docker-python-opencv-example
$ heroku create
$ heroku docker:release
```

## Dockerを使う利点 ##

既にBuildpackが提供されていればわざわざDocker imageを作る必要は無いかもしれません。
ただ その内容がちょっと古いものだったり自分の欲しいものと違っていたら、どうしても自分で環境を用意する必要がでてくると思います。
そんな新規に環境を作る際にはBuildpackよりDockerでimageを作るのが良いと思います。`Dockerfile`で記述しておいてAutomated Buildされるようにしておけば バージョン変更などの際もDockerfileを変更するだけで修正できるので、自分で再インストールしてディレクトリごと固めてS3に上げ直して…などの手間もないのでラクですよね。
あとやっぱり実際のHeroku環境に近い状態で開発できるのでローカル環境との違いに苦戦したり、が少なくなると思います。


## まとめ ###
Herokuで標準サポートされているもの**以外**の環境でアプリケーションを動かす方法について、主にDockerを利用する方法、を説明しました。
「こんなWebをアプリ作りたいんだけど、あのライブラリが必要だからHerokuでは無理かなぁ…」という場面でも諦めずに自分で実行環境を用意して動かしてみましょう。