ごきげんよう。皆さんはWebアプリ開発の際に環境構築ってめんどくさいなぁと思ったこと、ありませんか?私はあります。もし「そんなこと経験したことないなぁ」という方であれば、レガシーソフトウェア改善ガイドの第7章を読みましょう。
かいつまんで説明すると、散らばったビルドスクリプトや環境構築手順書が腐っている上に、ひとつひとつコマンドを叩いたり、DBアカウントの申請が必要だったりと、レガシーソフトウェアはその開発環境の構築すらも困難になっていることがあります。それを vagrant + ansible で自動化し、「仕事を始める日」(テイク2)
では、vagrant up
を起動した後にコーヒーマシンの使い方を調べる余裕までできちゃうぜ、っていうストーリーです。
これ自体はすごく素敵なことなのですが、最近の macOS だと vagrant が動かないことがあったりして、そもそもの vagrant 環境構築が辛くなってきました。ので、vagrantの代わりに docker を使って幸せになりましょう、というのがこの記事の趣旨です。
ローカル開発環境自動化、とは?
vagrantもansibleも聞いたことない、っていう、ちょっと昔の私からすると「ローカル開発環境自動化ナニソレ?おいしいの?」って感じだったので、改めてそのメリットを整理します。ローカル開発環境自動化を行うことで、以下のような開発体験が得られます。
- dockerが入っているPCであれば、あなたのアプリを動作させることができます。
- コマンド1つで、DBの立ち上げ、複数のアプリの立ち上げだって可能です。
- ソースコードはdockerコンテナと共有できるので、コンテナ側でホットリロード状態で起動していれば、コードの変更を検知して自動で再コンパイルさせることもできます。
ただし、現状dockerコンテナは基本的にはlinuxなので、Xcode Toolchainを要求するようなmacOS/iOS開発だと、恩恵が得にくいかもしれません...
基本戦略
ここでは、 PlayFramework を例に、ローカル開発環境を自動化してみましょう。手始めにこんな docker-compose.yml
を用意します。
version: '3'
services:
app:
build:
context: .
dockerfile: ./docker/local/app/Dockerfile
command: sbt run
environment:
- SBT_OPTS=-Dsbt.boot.directory=.sbt -Dsbt.coursier.home=.coursier
ports:
- "9000:9000"
volumes:
- .:/app
FROM hseeberger/scala-sbt:11.0.14.1_1.6.2_2.13.8
# locale
RUN apt-get update && apt-get install -y locales
RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && locale-gen
ENV LC_ALL=ja_JP.UTF-8 TZ=Asia/Tokyo
# volumes
WORKDIR /app
これを docker compose up
すると、
- 日本語ロケールで
- sbt/coursierはホスト側にキャッシュして(2回目以降はキャッシュからjarを解決できる)
- ソースコードはコンテナと共有し
- ホットリロード状態で起動
したPlayアプリが http://localhost:9000
で立ち上がります。あとはソースコードを変更したら勝手にリコンパイルされるので、ブラウザで変更を確認できます。便利でしょ?
ここからは、これを前提として、細かい tips を紹介します。
特定のディレクトリはホストと共有させない
例えばnode_modules
のように、OSに依存したバイナリを含むディレクトリをmacOSホスト+Linuxコンテナで共有すると、ホストかコンテナのどちらかでしか起動できなくなります。そういうめんどくさい環境は作りたくないですよね?
全てのソースコードを共有しましたが、一部のフォルダだけ共有したくない場合はdocker-compose.yml
のvolumes
にそのフォルダ名を書くと、共有から外れます。
volumes:
- .:/app
- /app/node_modules
ただ、こうしてしまうとコンテナ側には node_modules
がないので、npm ci
が失敗してしまいます。なので、このアプリの場合はDockerfileの中でnode_modulesを構築してしまいます。
# (上の続き)
# install node packages
COPY package*.json /app/
RUN npm ci
Dockerfileビルド時はappディレクトリには何もマウントされてないので、npm ci
に必要なpackage*.json
をコンテナにコピーしておきます。
https でアクセスしたい
アプリがなんらかの外部リソースにアクセスする際にhttpsを要求すること、あると思います。これは https-portal を使いましょう。
services:
...
https-portal:
image: steveltn/https-portal:1
ports:
- "80:80"
- "443:443"
links:
- app
restart: always
environment:
STAGE: local
DOMAINS: "localhost.hoge.jp -> http://app:9000"
volumes:
- ./.ssl_certs:/var/lib/https-portal
127.0.0.1 localhost.hoge.jp # 追加
STAGE: local
という設定は自己証明書を自動で作成する設定になります。作成された証明書は.ssl_certs
に配置されます(キャッシュ用)。これで https://localhost.hoge.jp
でアプリにアクセスすることができます。
一緒にDBも立ち上げたい
例えば postgres だとこんな感じ。
services:
db:
image: postgres:10.3
environment:
- POSTGRES_DB=${POSTGRES_DB_NAME:-app}
- POSTGRES_USER=${POSTGRES_DB_USER:-app}
- POSTGRES_PASSWORD=${POSTGRES_DB_PASSWORD:-app}
ports:
- "5432:5432"
app:
...
environment:
- POSTGRES_DB_URL=jdbc:postgresql://db:5432/${POSTGRES_DB_NAME:-app}
- POSTGRES_DB_USER=${POSTGRES_DB_USER:-app}
- POSTGRES_DB_PASSWORD=${POSTGRES_DB_PASSWORD:-app}
links:
- db
postgresでは「デフォルトのDB名(POSTGRES_DB
)」「ユーザー名(POSTGRES_USER
)」「パスワード(POSTGRES_PASSWORD
)」くらいは設定に含めておくと良さげ。使う側はJDBC用のURLとユーザー名、パスワードを環境変数として設定しておく感じ。links
にdb
サービスを指定することで、app
はdb
の立ち上げ後に立ち上がり、かつdb
をホスト名として利用することができます(上でJDBC用のURLにdb:5432
として利用している)。
通常は(Playみたいなリッチなフレームワーク使ってると)フレームワークのマイグレーションツール使うのが楽ですが、仮に使ってなかったとしても、そういうDockerfile用意してlinks
にdb
を指定してあげるといい感じです。
FROM postgres:10.3
# install flyway
# https://flywaydb.org/documentation/usage/commandline/#-linux
RUN apt-get update && apt-get install -y wget
RUN VERSION=8.5.7 && wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/${VERSION}/flyway-commandline-${VERSION}-linux-x64.tar.gz | tar xvz && ln -s `pwd`/flyway-${VERSION}/flyway /usr/local/bin
# volumes
WORKDIR /app
# setup
ENTRYPOINT ["/app/docker/local/db_setup/entrypoint.sh"]
#!/bin/sh
set -euC
# なんかいい感じのマイグレーション処理
flyway \
-user="$POSTGRES_DB_USER" \
-password="$POSTGRES_DB_PASSWORD" \
-url="$POSTGRES_DB_URL" \
-locations=filesystem:db/migration/ \ # db/migration フォルダにいい感じに作っとく
migrate
services:
...
db_setup:
build:
context: .
dockerfile: docker/local/db_setup/Dockerfile
environment:
- POSTGRES_DB_URL=jdbc:postgresql://db:5432/${POSTGRES_DB_NAME:-app}
- POSTGRES_DB_USER=${POSTGRES_DB_USER:-app}
- POSTGRES_DB_PASSWORD=${POSTGRES_DB_PASSWORD:-app}
links:
- db
volumes:
- .:/app
独自ホストを解決したい
例えば、別でホストされているアプリと接続する必要があり、開発環境では、開発環境からしか繋げないような場所にあるオンプレのサーバーに接続したいとか、まあ、あると思います。そんな時は extra_hosts
を使います。
services:
app:
...
extra_hosts:
- qa1:192.168.0.101
- qa2:192.168.0.102
これでapp
サービス内ではqa1
ホストやqa2
ホストにアクセスできます。
まとめ
私はこれまでDockerの主な用途はクラウド上にいい感じにホストしてうまうま、という世界しか知らなかったので、このようにローカル開発環境自動化としても利用できるのはちょっと目から鱗でした。今後も便利に使っていきたいと思います。