2019-09-25に投稿

Elixir+PhoenixのサービスをHeroku+Dockerで動かす

Elixir & PhoenixのサービスをDockerイメージでHerokuにデプロイした時の備忘録です。

背景として、元々GCE上で動作させていましたが、GCEのIPが有料になるかもという情報があったのでHerokuに移行することにしました。ただ、無料枠で運用している限りだとIPも有料にはならないかもということなので意味はなかったかもしれません。

ただ、もう誰も使っていないサービスでデータも少ないですし、ElixirとかNodeのようなビルドがある言語のサービスは本番サーバーでビルドするとサーバーが止まってしまうため本来行うべきではないため、気分転換にDockerでのリリース形式への変更を試してみるのには良かったかなと思いました。

ちなみにHerokuではよく使われているElixir用のBuildpackがあるため、本来はそちらを利用したほうが簡単だと思います。しかし、今回のサービスは結構バージョンが古く、ちらっと試したところ動かないようでした。何か設定がおかしかったのかもしれませんが、とにかく試行錯誤するよりは元々Linuxサーバー上で動いているものなのでDocker化してデプロイするようにした方が早くて確実そうだったのでそちらの形にしました。

環境

  • Elixir 1.6.0
  • Phoenix 1.3.0

Dockerfileをつくる

できたのがこんな感じです。

FROM elixir:1.6.0-slim

RUN apt-get update && \
    apt-get install -y dbus git curl
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
RUN apt-get install -y nodejs inotify-tools
RUN apt-get install -y --reinstall build-essential
RUN apt-get clean && \
    rm -rf /var/lib/apt/lists/*

ENV MIX_HOME=/root/.mix MIX_ENV=prod

COPY . /app
WORKDIR /app

RUN mix local.hex --force && \
    mix local.rebar --force && \
    mix deps.get && \
    mix compile

EXPOSE 4000

CMD mix phx.server

開発もDockerで行っており、本番もLinuxでデプロイ方法はメモしていたのでここは特に問題ありませんでした(余分な処理は混じっているかもしれませんが)。JavaScriptのビルドもありますがこれはローカルでビルドしてそのまま使っています。今回はもう更新しないサービスのDocker化のため処理を入れていませんが、必要であればここでビルドもしておくと良いかもしれません。

細かい部分をメモがてら解説しておきます。

MIX_HOMEを指定

環境変数としてMIX_HOMEを指定しています。

ENV MIX_HOME=/root/.mix MIX_ENV=prod

HerokuのDockerデプロイではrootユーザーではなく、その場で適当なユーザーが作られそちらで起動などのコマンドが実行されます。そのため、Dockerイメージビルド時にhexやrebarを追加した時にrootのディレクトリにインストールされたものが認識されず、サーバー起動コマンド実行時にまたインストール&ビルドが行われてしまいます。すると時間が経ちすぎてデプロイに失敗します。そのためイメージビルド時に処理したものを正常に認識させるため、MIX_HOMEを指定しています。

dockerfile内でユーザーの作成&指定も可能ですが、これは無効となります。

EXPOSEは不要

EXPOSE 4000を書いていますがこれは不要です。後述しますが、デプロイ時にPORTの環境変数が渡されるため、そちらでサーバーを起動するようにPhoenix側で設定する必要があります。

Phoenixの設定

config/prod.exsを設定します。元々のサーバーの設定から変えたところはとりあえずこんな感じになりました。

config :profile, ProfileWeb.Endpoint,
  load_from_system_env: false,
  url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443],
  http: [port: System.get_env("PORT")],
  cache_static_manifest: "priv/static/cache_manifest.json"

config :profile, Profile.Repo,
  adapter: Ecto.Adapters.MySQL,
  username: System.get_env("DB_USERNAME"),
  password: System.get_env("DB_PASSWORD"),
  database: System.get_env("DB_DATABASE"),
  hostname: System.get_env("DB_HOST"),
  charset: "utf8mb4",
  ssl: true,
  pool_size: 5

環境変数を使う形にする

下記のように、可能な限り環境変数を使う形にします。

  username: System.get_env("DB_USERNAME"),
  password: System.get_env("DB_PASSWORD"),
  database: System.get_env("DB_DATABASE"),
  hostname: System.get_env("DB_HOST"),

今はどうか知りませんが、Phoenixのプロジェクトを初期化するとprod.secret.exsを作ってそれがprod.exsから読み込まれることで本番の設定をする、という形になっていましたのでついついその形でやってしまいがちかもしれませんが、Herokuなどで利用することを考えると全部環境変数を利用する形にしておく必要がありますし、そもそも設定も環境毎で別にする必要もなくなるため環境変数を利用する方がシンプルだと思います。

DBのpool_sizeを減らす

  pool_size: 5

Herokuの無料DBはいくつか種類があります。今回はJawsDBを使っていますが、どれにしろ最大接続数が決まっていますので、それより低くpool_sizeを設定しておかないと接続エラーで起動できなくなります。各DBの仕様を見て最大数よりもちょっと少なめにしておきましょう。

ポートの設定

前述したとおり、ポート番号は環境変数で指定されるため、それでListenする必要があります。そのため下記のように指定します。

  http: [port: System.get_env("PORT")],

URLの設定

通常は不要かもしれませんがURLの設定もしています。

  url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443],

OGPを利用しているため、Routerのヘルパーで正しい完全なURLをテンプレート上に出力する必要がありました。正しく設定をしないとhttp://localhost:11242みたいなURLが出力されてしまうため、上記のような設定を追加しています。port: 443は不要かなと思いましたが、設定しておくとURLのポート表記を消すことができます。

デプロイ

諸々の設定ができたらデプロイします。これはHerokuのダッシュボードのDeployページで全部確認できますので細かくは書きませんが、とりあえず

heroku container:push web

で手元のDockerfileのイメージがビルド&pushされ、

heroku container:release web

でpushしたイメージでリリースされます。あとはheroku runでマイグレーションを行ったり、heroku logsでログを確認しておかしなところを解決すれば動くと思います。下記は実際に動いているものです。(絵文字が文字化けしていますが他のところでは正常に表示されているので単にここの不具合のようです。もう修正はしませんが…)

https://torima.appllis.net/users/dala00

独自ドメイン

独自ドメインは解約したかったのでついでに適当なサブドメインにURLを変更しました。ただどちらにしろ独自ドメインの設定をHeroku側で行わなければなりません。ただ、どうもHerokuでの独自ドメインは有料プランでないとできないようでした。

しかし、Cloudflare経由にして無理やりSSLにすることもできるようですので、今回はそのようにしました。~~.herokuapp.comをCNAMEとしてDNSレコードを追加すればよいようです。Herokuの画面で一緒にDNS Targetというものも表示されますが、これはCloudflare経由の場合は関係ないようです。

まとめ

Buildpackがあればそれを使えば楽ですが、Dockerを使えば特殊なパッケージをインストールしたサービスも動かす事ができますので、必要に応じてHerokuのDockerデプロイを使ってみると良さそうです。CI上でビルド&pushすればGitHubにpushするだけでも可能そうです。

容量が少ないですが言語問わず無料でRDBが使えて簡単にデプロイできるのはHerokuだけだと思いますので、ユーザーやアクセスの少ない初期のサービスなどはやっぱりここが良さげだなと感じました。


だら@Crieit開発者

Crieitの開発者です。 主にLAMPで開発しているWebエンジニアです(在宅)。大体10年程。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel5, CakePHP3, JavaScript, RoR 趣味:Elixir, Phoenix, Node, Nuxt, Express, Vue等色々

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント