Heroku で Docker を使う場合の諸注意

本日、かしゆかの誕生日です、どうぞみなさまお祝いください。

さて、本日の内容です。

# Container Registry が GA になったよ!!

2017/9/5 の changelog で [Container Registry GA - Deploy Docker images to Heroku](https://devcenter.heroku.com/changelog-items/1255)と公開されたとおり、これまでβサービスとして提供されていた [Docker](https://www.docker.com/) 向けコンテナサービスが、本番向けにリリースされました。

これまで buildpack の制約や、docker でせっかく準備した開発サイクルのために、Herokuの利用を躊躇されていたみなさまには、朗報です。はやいところ、[Heroku Pipelines](https://devcenter.heroku.com/articles/pipelines) 上で [Heroku CI](https://devcenter.heroku.com/articles/heroku-ci) つかって、Docker のCI/CD まわしたいですね!! Heroku は世界を支配しますよ!!
(もしかすると、https://devcenter.heroku.com/articles/heroku-yml-build-manifest これ使うと、Pipelines も Review Apps もいけるかもしれないんですが、いまテスト中です。ややこい)

とは言え、Docker完全サポートではありません。Dockerで動きさえすればHeroku上でも動く、というわけでは残念ながらありません。Docker を Heroku上で動かすためには、いくつかの注意事項がありますので、そちらをご紹介します。

# 前提条件

Docker を Heroku上で動かすためには、最低限の条件が3点あります。

1. Heroku アカウントを持っていること
2. Heroku CLI コマンドを導入して、利用可能にしていること
3. docker コマンドを導入して、利用可能なこと

現時点では、Dockerイメージを Heroku 上にデプロイするためには CLIが必須です。また、ローカルでdockerコマンドを実行して制御するため、Docker コマンドが必須です。Docker 使おうってんだから、みんな入ってるはずですけど。

# デプロイの流れとコマンド

実際に、ローカルに準備した `Dockerfile` をHeroku上へデプロイする場合の流れは、次のとおりになります。すべて、コマンドで実行した場合です。一部、GUIでも実行可能な部分については、その旨を追記しています。

1. `heroku login` で、Heroku アカウントへログインさせます。
2. `heroku container:login` で、Heroku 上の Container Registry へログインします。
3. `heroku create` で、Docker を動かすための Herokuアプリを作成します(GUIでも可)。
4. `heroku addons:create` で、必要なアドオンを追加します(GUIでも可、アドオン不要なら実行不要)。
5. `heroku container:push <process-type>` で、ローカルの Dockerfile もろもろをデプロイします。
6. docker のイメージが作成され、Heroku 上で動作するように配置されます。
7. `heroku open` で、デプロイされたアプリへ、ブラウザでアクセスします(GUIでも可)。

# 設定・デプロイ上の注意事項

とまぁ、コマンドがちょっとちがうものの、HerokuへのDockerデプロイは、それほど困難はありません。ただ、Heroku固有の制約があります。それらについて説明します。

1. `PORT` 環境変数で Listen する Webアプリケーションであること
2. Network link は未サポート
3. Default working directory = '/'。変更するときは `WORKDIR` を指定する。
4. `CMD`が必須。`ENTRYPOINT`はオプション。
5. `VOLUME`、`EXPOSE`、`STOPSIGNAL`、`SHELL`、`HEALTHCHECK`は未サポート

Heroku は Webアプリケーションを動かすためのプラットフォームです。また、環境変数 `PORT` で指定されたポートで LISTENしていないと、「こりゃ不適合アプリだな」としてアプリをクラッシュさせて停止します。厳密には、`PORT` 変数で LISTENさえしていれば、Web応答をするかどうかに関係なく実行はできるようです(2017/12/18現在)

したがって、ポート番号を強制するような`EXPOSE`も利用できませんし、そもそも外部のファイルデバイスを利用するという仕組みのないHerokuでは、`VOLUME` も利用できないということになります。

`ENTRYPOINT` はオプション指定ですが、何も指定していなければ自動的に `/bin/sh -c` が指定されます。

# 複数のイメージを push するには

ディレクトリごとに`Dockerfile`を準備して、個別にも、いっぺんにでも、まとめてHerokuアプリへpushできます。例えば、次のようなディレクトリ構成だとします。

```bash:directory
web/
 +- Dockerfile.web

worker-dyno/
 +- Dockerfile.worker

worker-test/
 +- Dockerfile.test
```

`heroku container:push` に `--recursive` を付与して実行します。

```bash:push
heroku container:push --recursive
```

この場合、`web` `worker-dyno` `worker-test` にある全ての `Dockerfile` のイメージが作成され、一つずつが `Dyno` として Heroku アプリ上に作られます。この場合では、３つの `Dyno`が作られるということですね。

ディレクトリ名はなんでもいいんですが、`Dockerfile` のあとは重要な識別子になります。これが `Dyno` のプロセスタイプになります。Heroku は `Web Dyno` が必要で、それが Webアプリとして実行される `Dyno` になります。その他のプロセスタイプの場合は、`Worker Dyno`として定義され、バックエンドで実行させることができるようになります。

今回のケースだと、`web` 「Web Dyno」 一つと `worker` `test` という２つの「Worker Dyno」がデプロイされるということになります。このプロセスタイプを、`Dockerfile` のあとに「.」をつけて付与する必要があります。ただの「Dockerfile」では認識されませんので、お気をつけください。

また、`push`時にデプロイするプロセスタイプを指定できます。例えば、`web` と `test` だけデプロイしたい、ということであれば。

```bash:push
heroku container:push web test --recursive
```
と指定することになります。この場合でも `--recursive` は必要ですのでお忘れなく。

※ Herokuチームにはさんざんレビューいただいて、ホント感謝です。

# 参考

[Container Registry & Runtime (Docker Deploys)](https://devcenter.heroku.com/articles/container-registry-and-runtime)