"初めてのHeroku開発"の前に〜Herokuのクセを理解する〜

# Herokuのクセとうまく付き合う

こんにちは。
ここ数年、Heroku + Javaでの開発プロジェクトによく参画しています。
いくつかのプロジェクトを通し、Herokuならではクセに直面してきました。
弊社の @yonyonsaeki の[記事](https://qiita.com/yonyonsaeki/items/eb57278061c9930ca206)と合わせて、これからHerokuプロジェクトをはじめられる方に向けて、私が知らずにハマったクセ(もしくは地雷)についてご紹介していきます

## Web Dynoは60秒以内に起動しないといけない

Web Dynoは[起動後の60秒以内で`$PORT`をlisten](https://devcenter.heroku.com/articles/dynos#web-dynos)しなくてはなりません。
60秒を過ぎてもlisten状態にならない場合、[R10 - Boot timeout](https://devcenter.heroku.com/articles/error-codes#r10-boot-timeout)となり、Dynoはクラッシュします。

## Web Dynoは30秒以内にレスポンスを返さないとダメ

Web DynoはHTTPリクエストを受信してから[30秒以内にレスポンス](https://devcenter.heroku.com/articles/request-timeout)を返さなければなりません。
30秒を経過すると、Heroku routerによりHTTPコネクションが切断されてしまいます。
レスポンスに時間のかかるAPIなどは注意しましょう。

## Dynoは1日ごとに再起動する

Web Dynoは、[およそ1日に一回必ず再起動](https://devcenter.heroku.com/articles/dynos#restarting)されます。
期限の長いセッション情報などをメモリに持っておくと、再起動でリセットされてしまうためやめておきましょう。
~~再起動時にダウンタイムはありません。これは、実行中のDynoの他に新しいDynoが新規で起動し、新しいDynoがlisten状態になるまでHeroku routerは古い実行中のDynoにリクエストをルーティングするためです。~~
**2019/12/16 追記** 
ダウンタイムをなくすためには`Preboot`機能の有効化が必要でした。
有効化するには「[Heroku の Preboot 機能を深掘りした - Feedforce Developer Blog](https://developer.feedforce.jp/entry/2018/12/12/120000)」を参照してください。
@masutaka さん、ありがとうございます！

## ファイルを保存してはいけない

DynoはDockerのコンテナの様なものです。
Dyno内のファイルシステムに作成したファイルは、再起動すると消えてしまいます。
また、Dyno間でファイルを共有できません。複数のWeb Dynoなどを立ち上げていると、web1にはファイルがあるけどweb2にはファイルがない、なんてことが発生します。
ストレージが必要な時は[Bucketeer](https://elements.heroku.com/addons/bucketeer)の様な[Heroku AddonsのData Stores](https://elements.heroku.com/addons)を検討してみましょう。

## ログは標準出力に出す

ログはファイルに出力せずに、全て標準出力へ出しましょう。
Heroku CLIで`heroku logs --tail --app <App名>`と実行すると、リアルタイムのログを見ることができます。
Herokuのダッシュボードからも`More -> View logs`で参照できます。
さらに、[Papertrail](https://elements.heroku.com/addons/papertrail)の様な[LoggingのHeroku Addons](https://elements.heroku.com/addons)を利用してみましょう。検索機能がとても便利です。

## スケジューラで起動したジョブは、次のスケジュール時間までに終了しないと強制終了される

[Heroku Scheduler](https://devcenter.heroku.com/articles/scheduler)を利用すると、cron的にコマンドを実行できます。コマンドはOne-off Dynoで実行されます。
スケジューラで実行したOne-off Dynoは、次のスケジュール時刻までに実行しないと強制終了されます。コマンドの最後でDBにコミットする様なケースでは、ロングランして強制終了->ロールバック->次のスケジュールでコマンド実行->ロングラン->強制終了...のループが繰り返されます（やらかしてとても困った）。

## 他の環境変数を参照する環境変数は設定できない

`heroku config:set VAR=value`の様に、Appに対して環境変数を定義できます。
この時に`FOO=$BAR`の様な他の環境変数を参照する定義はできません。
他の環境変数の参照が必要な場合は`.profile`を追加してみてください。
以下は`REDIS_URL`をコピーした`SPRING_REDIS_URL`を定義する`.profile`の例です。

```
export SPRING_REDIS_URL="$REDIS_URL"
```

## Slugサイズは500MBまで

AppはSlugというものにパッケージされます。Slugには構築したアプリの他に依存関係のモジュールなどが含まれます。
DynoはSlugを展開し、起動されます。
この[Slugには500MBのサイズ上限](https://devcenter.heroku.com/articles/slug-compiler#slug-size)があります。
サイズの大きなファイルを利用する場合などは注意してください。
また、Gitのコミット数が多くなり未参照のオブジェクトが増えたり、依存関係のキャッシュで圧迫される様な場合、[Heroku CLIのrepoプラグイン](https://github.com/heroku/heroku-repo)を利用してゴミ掃除をしてみましょう。

- [Heroku の Slug は友達](https://masutaka.net/chalow/2018-12-21-1.html)
- [HerokuアプリのSlug Sizeを簡単に減らす方法](https://qiita.com/kanatano_mirai/items/b300152a315c2a2ecb10)

そもそもSlugに含めたくないファイル・フォルダがある場合（Dynoでアプリを実行する際に不要なファイル）は`.slugignore`に指定することで、Slugから除外することができるので活用しましょう。
ただし、除外されるタイミングはビルドの前です。ビルド後に生成されるファイルをこの方法では削除できないため注意しましょう。

- [Ignoring files with .slugignore ](https://devcenter.heroku.com/articles/slug-compiler#ignoring-files-with-slugignore)

## アクセス元のIPアドレスは X-Forwarded-For ヘッダからとる

Web Dynoから接続元のIPアドレスを取得する時は[`X-Forwarded-For`ヘッダから取得](https://devcenter.heroku.com/articles/http-routing#heroku-headers)しましょう。
Web DynoはHeroku Routerと呼ばれるリバースプロキシの後ろで実行されています。
そのため、`REMOTE_ADDR`はHeroku RouterのIPアドレスとなります。

## DynoのIPアドレスは固定できない

HerokuはAWS上に構築されており、DynoのIPアドレスはAWSのアドレス範囲のどれかになります。
外部システムとの連携において、IPアドレスの固定化が要求として上がるケースがあります。
HerokuからのアウトバウンドのIPアドレスを固定したい場合は、[Proximo](https://elements.heroku.com/addons/proximo)などのAddon利用を検討してください。
※Heroku Private Spacesを利用している場合は例外です。[固定のIPアドレス](https://devcenter.heroku.com/articles/private-spaces#view-information-about-a-private-space)が割り当てられます。