Heroku Container Registry & Runtime とは（Heroku Dev Center の私家訳）

この記事は、 Heroku Advent Calendar 2017 ４日目の記事です
([dev center の記事](https://devcenter.heroku.com/articles/container-registry-and-runtime)を私訳します。 2017/11/23 最終更新の記事)
(Last updated 02 May 2019 の記事に追従 (2019/05/30))

-----
Heroku Container Registry では、Docker ベースのアプリケーションを Heroku にデプロイすることができます
[Common Runtime](https://devcenter.heroku.com/articles/dyno-runtime#common-runtime) も [Private Space](https://devcenter.heroku.com/articles/private-spaces) もサポートされています

もし、Heroku 上で Docker image をビルドする場合、Review app を利用することもできます
[building Docker images with heroku.yml](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml) を確認してください


## まずはじめに
Docker がインストールされ、動作する環境を用意します(例: `docker ps`)。heroku へコマンドラインからログインしておきます(`heroku login`)

Container Registry へログインします:

```bash
$ heroku container:login
```

Alpine ベースの python サンプル image を入手します

```bash
$ git clone https://github.com/heroku/alpinehelloworld.git
```

アプリケーションのディレクトリに移動し、 heroku app を create します

```bash
$ heroku create
Creating salty-fortress-4191... done, stack is cedar-14
https://salty-fortress-4191.herokuapp.com/ | https://git.heroku.com/salty-fortress-4191.git
```

image をビルドし、 Container Registry へプッシュします

```bash
$ heroku container:push web
```

そして、release 操作を行います

```bash
$ heroku container:release web
```

ブラウザからアプリケーションを開きます

```bash
$ heroku open
```

## registry へのログイン
Heroku は Container Registry を、 `registry.heroku.com` 上で実行しています
HerokuCLI を利用している場合、次のようにしてログインすることが可能です:

```bash
$ heroku container:login
```

もしくは、Docker コマンドから直接ログインすることもできます:

```bash
$ docker login --username=_ --password=$(heroku auth:token) registry.heroku.com
```

## image(s) のプッシュ
### image のビルドとプッシュ
image をビルドし、registry.heroku.com へプッシュするには、アプリケーションのディレクトリに `Dockerfile` が存在することが必要です. 以下を実行します:

```bash
$ heroku container:push <process-type>
```

### 既存の image をプッシュする
Heroku へ image をプッシュするために、 Docker Hub から pull されたものであっても、 tag 付けを行い、 naming template に従ってプッシュしてください:

```bash
$ docker tag <image> registry.heroku.com/<app>/<process-type>
$ docker push registry.heroku.com/<app>/<process-type>
```

tag にプロセスタイプを指定することで、CLI を使用して image をリリースできます[(release the image using the CLI)](https://devcenter.heroku.com/articles/container-registry-and-runtime#cli)
もし、tag にプロセスタイプを指定したくない場合は、`image_id` を使った API 経由のリリースが可能です[(release via the API)](https://devcenter.heroku.com/articles/container-registry-and-runtime#api)

### 複数の image をプッシュする
複数 image をプッシュするために、Dockerfile を Dockerfile.<process-type> のようにリネームしてください:

```bash
$ ls -R

./webapp:
Dockerfile.web

./worker:
Dockerfile.worker

./image-processor:
Dockerfile.image
```

それから、プロジェクトのルートディレクトリから、実行します:

```bash
$ heroku container:push --recursive
=== Building web
=== Building worker
=== Building image
=== Pushing web
=== Pushing worker
=== Pushing image
```

３つの image をビルドし、プッシュします
もし、特定の image のみをプッシュしたい場合は、プロセスタイプを指定することができます:

```bash
$ heroku container:push web worker --recursive
=== Building web
=== Building worker
=== Pushing web
=== Pushing worker
```

## image のリリース
### CLI
container registry への push が成功したあと、新しい release を作ることができます

```bash
$ heroku container:release web
```

もし、複数の image がある場合は、リストとして渡します

```bash
$ heroku container:release web worker
```

> caution
> 多数のプロセスタイプをもつ app の場合で、ひとつのプロセスタイプのみを release した場合、
> (e.g., `heroku container:relase web`)
> すべてのプロセスタイプがリスタートされます


### API
```bash
$ curl -n -X PATCH https://api.heroku.com/apps/$APP_ID_OR_NAME/formation \
  -d '{
  "updates": [
    {
      "type": "web",
      "docker_image": "$WEB_DOCKER_IMAGE_ID"
    },
    {
      "type": "worker",
      "docker_image": "$WORKER_DOCKER_IMAGE_ID"
    },
  ]
}' \
  -H "Content-Type: application/json" \
  -H "Accept: application/vnd.heroku+json; version=3.docker-releases"
```

### Docker image ID を取得する

```bash
$ docker build -t my_image .
...
Successfully built acf835bc07f5
Successfully tagged my_image:latest

docker inspect my_image --format={{.Id}}
acf835bc07f5
```

## ワンオフdyno
もし、アプリケーションが複数の Docker image で構成される場合、ワンオフdyno を作るときに、プロセスタイプを指定することができます:

```bash
$ heroku run bash --type=worker
Running bash on ⬢ multidockerfile... up, worker.5180
```

プロセスタイプが指定されていなかった場合、 `web` image が使用されます

## CI/CD を利用する

> caution
> 現時点では、コンテナビルドのテストに Heroku CI は使えません

もしサードパーティの CI/CD プラットフォームを使用している場合、registry に image をプッシュできます

初回の認証は、下記の情報が必要です:

 - Registry URL: `registry.heroku.com`
 - Username: `your Heroku email address`
 - Email: `your Heroku email address`
 - Password: `your Heroku API key`

多くの CI/CD プロバイダが、Docker registry へ image をビルドしプッシュするためのドキュメントを用意しています:

 - [CircleCI](https://circleci.com/docs/docker/#deployment-to-a-docker-registry)
 - [Bamboo](https://confluence.atlassian.com/bamboo/configuring-the-docker-task-in-bamboo-720411254.html#ConfiguringtheDockertaskinBamboo-push)
 - [TravisCI](https://docs.travis-ci.com/user/docker/#Pushing-a-Docker-Image-to-a-Registry)
 - [Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/CloudBees+Docker+Build+and+Publish+plugin)
 - [Codeship](https://documentation.codeship.com/pro/getting-started/docker-push/)

## Release フェイズ
[Release フェイズ](https://devcenter.heroku.com/articles/release-phase) を使用するためには、Docker image の名前を `release` とします

```bash
$ heroku container:push release
```

`heroku container:release` によって Docker image をリリースする場合、プロセスタイプの指定が必要です

```bash
$ heroku container:release web release
Releasing images web,release to your-app-name... done
Running release command...
Migrating database.
```

> release フェイズ実行中の、ストリーミングログを見たい場合、Docker image 内に `curl` が必要です
> Docker image 内に `curl` がない場合、release フェイズのログはアプリケーションログ内で利用可能です
> [Heroku-16](https://devcenter.heroku.com/articles/heroku-16-stack#heroku-16-docker-image) もしくは [Heroku-18](https://devcenter.heroku.com/articles/heroku-18-stack) スタックをベースイメージとして使用している場合、`curl` はイメージ内に含まれています

## Dockerfile コマンドとランタイム
Docker image の dyno 上での実行において、[slug が実行する方法と同じ](https://devcenter.heroku.com/articles/dynos)であり、また、同じ制約の下実行されます:

- `web` プロセスはHerokuによって設定される`$PORT`においてHTTPトラフィックをリッスンしなければならなりません. Dockerfile 内の `EXPOSE` は優先されませんが、ローカルでのテスト時に使用することができます. また、HTTPリクエストのみがサポートされます
- dyno 同士のネットワークリンクはサポートされません
- ファイルシステムはエフェメラルです
- 実行時のディレクトリは`/`です.`WORKDIR`によって他のディレクトリを指定できます
- 環境変数設定のための `ENV` がサポートされています
  - センシティブなクレデンシャルが、偶発的にソースコードに紛れることがなくすために、`ENV` は実行時変数(例: `GEM_PATH`)の使用や、クレデンシャル用に `heroku config` を使用することが推奨されます
- `ENTRYPOINT` はオプションです. もし設定されない場合`/bin/sh -c` が使用されます
  - [config vars](https://devcenter.heroku.com/articles/config-vars)がプロセスで使用可能になるために、`CMD` は shell によって常に実行されます
  - シングルバイナリの実行や、shell 無しでの image 実行には `ENTRYPOINT` を利用してください

>ローカルでの image のテストにはroot以外のユーザでの実行を強く推奨します
>Heroku 上では、container は root 権限では実行されません

### サポートされない Dockerfile コマンド
- `VOLUME` - ボリュームマウントはサポートされません. [dyno のファイルシステムはエフェメラルです](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem)
- `EXPOSE` - `EXPOSE` はローカルでのテスト時には使用することができます. しかし、 Heroku container runtime ではさぽーとされません. かわりに web プロセスなどで、環境変数の `$PORT` を取得するようにしてください
- `STOPSIGNAL` - dyno マネージャは、プロセスが SIGLILL シグナルに準拠した[SIGTERM signal](https://devcenter.heroku.com/articles/dynos#graceful-shutdown-with-sigterm)を送られる事によるグレースフルシャットダウンを要求します.`STOPSIGNAL` は推奨されません
- `SHELL` - Docker image のデフォルトのシェルは `/bin/sh` です. 必要であれば、`ENTRYPOINT` によって上書きすることができます
- `HEALTHCHECK` - `HEALTHCHEK` は現在サポートされていませんが、 Heroku Dyno マネージャは実行中コンテナのherth check を自動的に行っています

## ローカルで image をテストする
ローカルで image をテストするとき、いくつかのベストプラクティスがあります
このベストプラクティスは [Dockerfile 例](https://github.com/heroku/alpinehelloworld/blob/master/Dockerfile) で使用されています

### 非root として実行する
ローカルでの image のテストにはroot以外のユーザでの実行を強く推奨します
Heroku 上では、container は root 権限では実行されないためです
Dockerfile の`CMD`行の直前に下記のコマンドを追加することができます:

Alpine 使用の場合:

```Dockerfile
RUN adduser -D myuser
USER myuser
```

Ubuntu 使用の場合:

```Dockerfile
RUN useradd -m myuser
USER myuser
```

自身の containar が非ルートユーザで実行されていることを確認し、実行中の container に attach し、 `whoami` コマンドを実行します:

```bash
$ docker exec <container-id> bash
$ whoami
myuser
```

Heroku へのデプロイ時、非ルートユーザで container も実行します
(Dockerfile に `USER` が指定されていたとしても使用しません)

```bash
$ heroku run bash
$ whoami
U7729
```

### 環境変数からのポート番号の取得
テスト実行のために、Dockerfile やコード上で、環境変数から `$PORT` を読み込むようにすることを提案します
例えば:

```Dockerfile
CMD gunicorn --bind 0.0.0.0:$PORT wsgi
```

Docker container をローカルで実行するとき、`-e`フラグによって環境変数を設定することができます:

```bash
docker run -p 5000:5000 -e PORT=5000 <image-name>
```

### 複数の環境変数を設定する
もし`heroku local`で実行しているならば、config var を [`.env` ファイル](https://devcenter.heroku.com/articles/heroku-local#set-up-your-local-environment-variables)に設定することができます
`heroku local`が実行されているとき、 `.env` は読み込まれ、name/value ペアが環境変数に設定されます
同じ`.env`ファイルを Docker を使用するときにも使用することができます:

```bash
docker run -p 5000:5000 --env-file .env <image-name>
```

`.env`ファイルは、[`.dockerignore`ファイル](https://docs.docker.com/engine/reference/builder/#dockerignore-file)に追加することを提案します

### マルチコンデナアプリケーション用のdocker-composeの活用

マルチコンテナアプリケーションを作ったならば、ローカル開発環境を定義するdocker-composeを使用することができます
ローカル開発のためのdocker-composeの使い方は[こちら](https://devcenter.heroku.com/articles/local-development-with-docker-compose)

### 参考情報
- Docker image をローカルで実行するための情報は、[Docker社のオフィシャルドキュメント](https://docs.docker.com/engine/reference/run/)にあります
- ローカル開発環境でのdocker-composeの使用法は[こちら](https://devcenter.heroku.com/articles/local-development-with-docker-compose)です

## images をスタックする
[Heroku-16](https://devcenter.heroku.com/articles/heroku-16-stack) と [Heroku-18](https://devcenter.heroku.com/articles/heroku-18-stack) の Docker image が利用可能です
しかしながら、いかなる base image も使用することができます
Heroku スタックイメージを使用する必要はありません
もし Heroku スタックイメージを使用することを選択するならば Heroku-18 (406 MB)  をお勧めします

Heroku-18 を base image として使用する場合、 Dockerfile に次のように記述してください:

```Dockerfile
FROM heroku/heroku:18
```

### latest image を取得する
すでにアプリケーションで Heroku-18 を使用していて、[latest version](https://hub.docker.com/r/heroku/heroku/builds/) に更新したい場合は、下記を実行します

```bash
$ docker pull heroku/heroku:18
```

そして、再度アプリケーションの deply を実行してください

## デプロイ方法の変更
Container Registry へアプリケーションをデプロイしたならば、スタックは `container` にセットされます
これは、アプリケーションが、カスタム container を使用し、 [Heroku-curated スタック](https://devcenter.heroku.com/articles/stack)をもはや使用しないということです
`container` に設定されると、`git`経由でアプリケーションをプッシュすることはできません
container registry 経由でアプリケーションをデプロイする必要が無く、代わりに`git`を使用したい場合は、`heroku stack:set heroku-18`を実行してください

## 既知の制限とイシュー

- Review apps はサポートされません. Docker を Review apps とともに利用する場合、Docker image を Heroku 上でビルドするために [heroku.yml manifest](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml) とともにアプリケーションを定義してください
- Docker image は([slug](https://devcenter.heroku.com/articles/limits#slug-size)と違い)サイズ制限の対象ではありませんが,[dyno の起動時間制限](https://devcenter.heroku.com/articles/limits#boot-timeout)の対象です. レイヤ数/image サイズが大きくなると、dyno の起動時間も長くなります
- ４０以上のレイヤを持つ image は Common Runtime では起動に失敗するでしょう 
- Pipeline promotion はサポートされません
- [このコマンドリスト](https://devcenter.heroku.com/articles/container-registry-and-runtime#unsupported-dockerfile-commands)はサポートされません