Edited at

Play Framework 2.6 アプリをDocker上で開発する

More than 1 year has passed since last update.


動機

Play Framework でアプリを開発するにあたって


  • ローカルの環境にあれこれ(色々なバージョンのJDKなど)インストールしたくない

  • 開発メンバーと開発環境を手軽に共有したい(エディタ除く)

  • エティタは個人のものを使いたい(ソースコードはローカルの環境から編集したい)

  • 出来上がったアプリはDocker環境でデプロイしたい

という希望があって、Dockerで開発環境を構築したのでその記録です。

Play Frameworkは開発が活発で、バージョンごとに変更点も多いです。この記事では現時点(2018/2/6)で最新の Play Framework 2.6について、記述します。


コツ

たとえばRuby on Railsでしたら、一つのDockerコンテナで開発からデプロイまで全部まかなうことができます(実際にはデプロイではまったりしますが)。

参考:Quickstart: Compose and Rails (docker docs)

しかし、Play Framework 2.6の場合、


  • プロジェクトの初期化

  • 開発

  • 本番の実行環境

と、3つのDocker環境を分けて考えたほうがうまくいきます。

これを順を追って説明していきます。


全体の流れ

Dockerの有無に関わらず、Play Framework 2.6では、プロジェクトのビルドにsbtを使用します。

公式ドキュメントに従うと、Play Frameworkによるアプリの開発は


  1. sbt をインストールする

  2. sbt new でプロジェクトを初期化する

  3. SBT consoleを起動してアプリケーションを開発する

  4. 出来上がったアプリケーションを本番環境にデプロイする

という流れになります。

これら一連の作業をDockerの環境内に封じ込めてしまいたいというのが本記事の主旨です。

Dockerはインストール済みであるという前提で話を進めていきます。


sbt のインストール

まずはsbtをインストールしたDockerコンテナを用意します。

hseeberger さんが Docker Hub に、sbtインストール済みのコンテナを公開してくれています。これを利用するのが早くて確実です。

hseeberger/scala-sbt

これを使えば

$ docker run -it --rm hseeberger/scala-sbt:8u151-2.12.4-1.1.0

でsbtのイメージを含んだDocker環境を利用できます。


  • -it はDockerコンテナをインタラクティブな状態で起動するためのオプションです

  • --rm は使い終わったコンテナを自動で破棄するためのオプションです

  • Javaのバージョンは大事なので、タグまでがっつり指定しています

上記のコマンドを実行すると、Dockerコンテナ内の/bin/bashが起動しますので、bash上でsbtを起動することができます。

root@72ece8e13c72:~# sbt

※ここでもしも個人公開のリポジトリを使うのが不安だというのでしたら、hseeberger さんが公開してくれているDockerfileを見て、オリジナルのDockerfileを作成しておくのもありです。シンプルなDockerfileですので、一見の価値ありです。

※Java8を使っているのがポイントです。Java9以降でPlay Framework2.6を使おうとするとモジュール不足で色々と大変なことになります。これはDockerの有無とは関係ありません。また、現時点でこの問題を解決できたという情報を見つけられていません。


プロジェクトの初期化

作業するディレクトリに移動します。このディレクトリの直下にアプリ開発用のディレクトリが新たに作られることになります。

たとえば、~/workspacesディレクトリにhelloディレクトリを作ってそこにhelloプロジェクトを収めたい場合は、

cd ~/workspaces

して、ここからsbtのコマンドを使ってhelloディレクトリを作っていくことになります(あせって$ mkdir helloしないこと)。

作業ディレクトリにcdしたらプロジェクトの初期化のためのDockerコンテナ(コンテナその1)を起動します。

$ docker run -it --rm -v=$(pwd):/root hseeberger/scala-sbt:8u151-2.12.4-1.1.0


  • -vはコンテナ内のディレクトリをローカルのディレクトリにマウントするためのオプションです。ここでは、Dockerコンテナ内の/rootディレクトリが、ローカルの作業用のディレクトリ(~/workspaces)にマウントされます。

※Dockerコンテナを起動したときに/rootがコンテナ内のカレントディレクトリになるので、このような指定をしています。

Dockerコンテナが起動したら、コンテナ内でsbt newを実行します。helloはこれから作るアプリケーション名です。

# sbt new playframework/play-scala-seed.g8 --name=hello

ここまでで一段落です。

Dockerコンテナを抜けて、カレントディレクトリにプロジェクトが生成されているのを確認します。

$ ls hello

app build.sbt gradle gradlew.bat public
build.gradle conf gradlew project test

共同開発の場合は、このhelloディレクトリ以下をgitなどで共有することになります。


開発

開発の際には、まずプロジェクトのディレクトリに移動します。 

$ cd hello

開発環境(コンテナその2)を起動します。このコンテナは先ほどのプロジェクト初期化用のコンテナとは別物と考えたほうがすっきりいけます。

$ docker run -it --rm -p=9000:9000 -v=$(pwd):/root hseeberger/scala-sbt:8u151-2.12.4-1.1.0


  • -p はDockerコンテナのポートをローカル環境のポートに接続するためのオプションです。

Dockerコンテナ内で/bin/bashが起動されるので、そのコマンドラインからsbtコンソールを起動します。

# sbt

ここでsbtコンソールの起動に時間がかかる(手元の環境で10分以上)ので、このコンテナはなるべく起動しっぱなしにしておいたほうが時間の節約になります。

sbtが起動すると、sbtのコマンドライン上で

$ ~run

などが使えるようになります。

開発中のアプリのソースにはローカルの ~/workspaces/hello以下の通常のファイルとしてアクセスできるので、好きなエディタやIDEを使って開発できます。


デプロイ

上記の開発環境内のsbtのコマンドライン上で

$ dist

とすれば、ローカルの~/workspaces/hello/target/universalフォルダにhello-1.0-SNAPSHOT.zipといったファイルができます。これはDockerを使わない開発手順と何ら変わりがありません。

このzipファイルを本番サーバーに持って行って展開する、というのがPlay Frameworkの通常のデプロイ手順になります。もちろん、サーバー上にJavaの実行環境が正しくインストールされている必要があります。

しかし、サーバー上にたくさんのバージョンのJava実行環境がインストールされているのは美しくないのでDockerでいきましょう。

ここでは、本番サーバー上でDockerが稼働している前提で、docker-compose upできるようにしていきます。


Docker用ファイルの準備

Play Frameworkでは、プロジェクトのディレクトリ内にdistというディレクトリを作ってそこにファイルを置いておくと、そのファイルがデプロイの際の配布物にそのまま取り込まれます。

ですので、まずdistディレクトリを作成します。

$ cd ~/workspaces/hello

$ mkdir dist

作成したdistディレクトリ内にDockerfiledocker-compose.ymlを用意します。

Dockerfile は以下のようになります。


Dockerfile

FROM openjdk:8u151-jre

COPY . /root/
RUN chmod +x /root/bin/hello


  • 開発環境と、Javaのバージョンを揃えるように注意してください。

  • 最後の RUN chmod...は、zip形式でアーカイブする際に必要になるものです。tgzなどファイル属性を保持できる形式でアーカイブするのであれば必要ありません。

docker-compose.ymlは以下のようになります。


docker-compose.yml

version: '3'

services:
app:
build: .
command: /root/bin/hello -Dplay.http.secret.key=xxxxxxxxxxxx
ports:
- "9000:9000"


  • play.http.secret.keyは適宜設定してください。


デプロイ用アプリケーションの作成

Docker用のファイルの準備ができたら、開発用のコンテナ(コンテナその2)のsbtコマンドライン上で、$ dist(zip形式でアーカイブする場合。tgzの場合は$ universal:packageBin)を実行します。

~/workspaces/hello/target/universalディレクトリにhello-1.0-SNAPSHOT.zip(tgz形式の場合はhello-1.0-SNAPSHOT.tgz)ができます。

このファイルを本番用サーバーに持っていって展開すると

Dockerfile      conf            lib

bin docker-compose.yml share

といったファイル群が取り出せます。

このディレクトリで

$ docker-compose up

すればDockerコンテナ(コンテナその3)内でアプリを起動することができます。


結論

Play FrameworkアプリをDocker上で開発する場合はDockerコンテナを3つ用意して


  • Dockerコンテナその1は、sbt new専用

  • Dockerコンテナその2は開発専用

  • Dockerコンテナその3は本番環境専用

と分けて考えるとうまくいきます。