EmbulkのDockerコンテナをHerokuで動かす

HerokuでEmbulkを動かすためにHerokuの [Container Registry](https://devcenter.heroku.com/articles/container-registry-and-runtime) を利用する必要がありました。
Heroku上でDockerコンテナを動かすためにいくつかポイントがあるので後世のために共有します。

## 前提条件
以下のようなユースケースでの実装例になります。
他のユースケースでは使用するEmbulkのプラグインが異なると思いますが基本的な部分は大差ないはずです。

* Heroku上でEmbulkを実行する
    - `Heroku Scheduler` を利用して日次実行する
    - 過去分のデータ移行の処理も必要
* S3のログファイルをBigQueryにロードする処理
    - 記事の本題ではないのでEmbulkを実行するスクリプトなどは割愛しています

## 1. Dockerfileの記述例
S3のログファイルをBigQueryにロードする処理で以下の3つのプラグインを使用しています。

* embulk-input-s3
* embulk-output-bigquery
* embulk-parser-jsonl

```docker
FROM openjdk:8
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get install bash

WORKDIR /app

# 本題から逸れるのでアプリケーションコードの配置などは省いています

RUN curl -o /bin/embulk --create-dirs -L "http://dl.embulk.org/embulk-latest.jar" && chmod +x /bin/embulk

RUN embulk mkbundle bundle
RUN echo "gem 'embulk-input-s3', '0.3.4'" >> bundle/Gemfile
RUN echo "gem 'embulk-output-bigquery', '0.4.4'" >> bundle/Gemfile
RUN echo "gem 'embulk-parser-jsonl', '0.2.0'" >> bundle/Gemfile

WORKDIR bundle
RUN embulk bundle

WORKDIR /app
```

ポイントになるのは以下の2点です。
特に2点目がハマりどころです。

1. Embulkを `/bin` 以下に配置することでパスの設定の手間を省く
2. [mkbundle](https://qiita.com/sonots/items/979c8b0810ccffc47317) を使用してプラグインのgemのインストール先を指定する

### mkbundleを使用しない場合に発生する問題について
HerokuにコンテナをデプロイしてEmbulkを実行すると以下のエラーが発生して実行できません。

```ruby
2019-09-15 19:22:17.628 +0900 [INFO] (main): Gem's home and path are set by default: "/app/.embulk/lib/gems"
2019-09-15 19:22:22.575 +0900 [INFO] (main): Started Embulk v0.9.18
org.embulk.config.ConfigException: InputPlugin 's3' is not found.
org.jruby.proxy.org.embulk.config.ConfigException$Proxy1: Unknown input plugin 's3'. embulk/input/s3.rb is not installed. Run 'embulk gem search -rd embulk-input' command to find plugins.
```

ポイントになるのは以下の部分です。

```
Gem's home and path are set by default: "/app/.embulk/lib/gems"
```

Embulkはインストールされたプラグインを `/app/.embulk/lib/gems` 以下に探しにいくのですが
実際には `/root/.embulk/lib/gems` 以下に配置されます。

#### プラグインインストール時のログ
```
Gem plugin path is: /root/.embulk/lib/gems
```

ローカルで実行した場合にはこの問題は発生しないのでHerokuのDockerコンテナのデプロイ、実行環境の問題だと思われるのですが詳細なメカニズムは分かりません。
対応としては以下の2点が挙げられるのですが `1` の方がスマートですね。

1. `mkbundle` を利用してプラグインのインストール場所を指定する
2. Dockerfileで `WORKDIR /root` にしてプラグイン、アプリケーションコードを全て `/root` 以下に配置する

なお、Embulkの実行は `-b` オプションでプラグインのインストール先を指定します。
このケースの場合は以下のようになります。

```bash
embulk run -b /app/bundle /app/config.yml
```

## 2. Herokuへのコンテナのデプロイ
`Heroku Scheduler を利用して日次実行する` という要件を満たすためには `web` でデプロイする必要があります。
これはHerokuの仕様で詳しくは [Heroku Schedulerは --type=web のDockerコンテナしか実行しない](https://qiita.com/cobot00/items/069c485a2bcd8957118a) を参照ください。

```bash
heroku container:push web --app [APP_NAME]
heroku container:release web --app [APP_NAME]
```

一方でデバッグや同期実行のために `worker` でもデプロイする必要があります。
どういうわけか `web` でデプロイした方に `heroku run bash` で接続すると1分間で強制切断されます。
HerokuのHTTPリクエストが1分でタイムアウトさせられる仕様が誤作動しているような印象です・

```bash
heroku container:push worker --app [APP_NAME]
heroku container:release worker --app [APP_NAME]
```

まとめると、スケジュール実行が必要な処理をHeroku上で行う場合は `web` `worker` の両方に同じコンテナをデプロイしなければなりません。

## 3. Dyno Typeについて
Embulkは実行時の消費メモリが大きいのでHerokuで実行するには `Dyno Type` は最低でも `standard-2x` は必要です。
Herokuへ接続する際や `One-Off` 実行する際は必ず `--size` オプションを指定しましょう。

### Herokuへのbash接続コマンド例
```bash
heroku run bash --type=worker --size=standard-2x --app [APP_NAME]
```