Elixir & PhoenixのサービスをDockerイメージでHerokuにデプロイした時の備忘録です。
背景として、元々GCE上で動作させていましたが、GCEのIPが有料になるかもという情報があったのでHerokuに移行することにしました。ただ、無料枠で運用している限りだとIPも有料にはならないかもということなので意味はなかったかもしれません。
ただ、もう誰も使っていないサービスでデータも少ないですし、ElixirとかNodeのようなビルドがある言語のサービスは本番サーバーでビルドするとサーバーが止まってしまうため本来行うべきではないため、気分転換にDockerでのリリース形式への変更を試してみるのには良かったかなと思いました。
ちなみにHerokuではよく使われているElixir用のBuildpackがあるため、本来はそちらを利用したほうが簡単だと思います。しかし、今回のサービスは結構バージョンが古く、ちらっと試したところ動かないようでした。何か設定がおかしかったのかもしれませんが、とにかく試行錯誤するよりは元々Linuxサーバー上で動いているものなのでDocker化してデプロイするようにした方が早くて確実そうだったのでそちらの形にしました。
できたのがこんな感じです。
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を指定しています。
ENV MIX_HOME=/root/.mix MIX_ENV=prod
HerokuのDockerデプロイではrootユーザーではなく、その場で適当なユーザーが作られそちらで起動などのコマンドが実行されます。そのため、Dockerイメージビルド時にhexやrebarを追加した時にrootのディレクトリにインストールされたものが認識されず、サーバー起動コマンド実行時にまたインストール&ビルドが行われてしまいます。すると時間が経ちすぎてデプロイに失敗します。そのためイメージビルド時に処理したものを正常に認識させるため、MIX_HOMEを指定しています。
dockerfile内でユーザーの作成&指定も可能ですが、これは無効となります。
EXPOSE 4000
を書いていますがこれは不要です。後述しますが、デプロイ時にPORT
の環境変数が渡されるため、そちらでサーバーを起動するように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などで利用することを考えると全部環境変数を利用する形にしておく必要がありますし、そもそも設定も環境毎で別にする必要もなくなるため環境変数を利用する方がシンプルだと思います。
pool_size: 5
Herokuの無料DBはいくつか種類があります。今回はJawsDBを使っていますが、どれにしろ最大接続数が決まっていますので、それより低くpool_sizeを設定しておかないと接続エラーで起動できなくなります。各DBの仕様を見て最大数よりもちょっと少なめにしておきましょう。
前述したとおり、ポート番号は環境変数で指定されるため、それでListenする必要があります。そのため下記のように指定します。
http: [port: System.get_env("PORT")],
通常は不要かもしれませんが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は誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント