はじめに
前回の記事でIBM Cloud Code Engineにサンプルアプリケーションをデプロイしましたが、今回は自作のアプリケーションをデプロイしてみようと思います。アプリケーションはコンテナになっていればどんな言語やフレームワークを使ってもいいはずなので、今回は(あまりネット上にも情報がなさそうだった)Play Frameworkを使ったアプリケーションをデプロイしてみようと思います。
Play Frameworkとは
Play Frameworkとは、公式サイトによると
The High Velocity Web Framework For Java and Scala
とのことです。日本語だと JavaとScalaを使った高速ウェブアプリケーションフレームワーク といったところでしょうか。私はJavaの経験が長いので今回はこちらのフレームワークを選んでみます。
Dockerを使った開発
Play Frameworkを動かすにはJavaはもちろんsbtが動く環境が必要です。ですが、Javaもsbtもローカル環境にインストールするとバージョンやら何やらの依存関係で案外管理が難しくなりそうです。なので今回はDockerを使ってなるべく実行環境をコンテナ内に閉じ込めて開発するスタイルを探っていきます。
開発環境
まず開発環境の方は、Javaとsbtが既に入っているscala-sbtを使わせてもらうことにします。以下のような docker-compose.yml
を用意して簡単に起動できるようにしておきます。
version: "3.9"
services:
sbt:
image: hseeberger/scala-sbt:11.0.13_1.5.8_2.13.7
ports:
- 9000:9000
volumes:
- ./root:/root
これを使ってコンテナを起動します。
$ mkdir root
$ docker-compose up -d
[+] Running 1/1
⠿ Container play_test_sbt_1 Started
$ docker exec -it play_test_sbt_1 bash
root@8a50fee5f6b7:~# pwd
/root
こうなっているので、このディレクトリに作られたファイルはローカルの ./root
に保持されます。
アプリケーションの作成
では早速この環境を使ってアプリケーションを作成してみます。ドキュメントにある通りにコマンドを実行します。
root@8a50fee5f6b7:~# sbt new playframework/play-scala-seed.g8 --name=fterui-play-test
[info] welcome to sbt 1.5.8 (Oracle Corporation Java 11.0.13)
[info] set current project to new (in build file:/tmp/sbt_56c7b9f2/new/)
結構時間がかかるのでしばらく待ちます。アプリケーションが作成されると以下のようになります。
root@8a50fee5f6b7:~# cd fterui-play-test/
root@8a50fee5f6b7:~/fterui-play-test# ls
app build.sbt conf project public test
早速実行してみます。
root@8a50fee5f6b7:~/fterui-play-test# sbt run
[info] [launcher] getting org.scala-sbt sbt 1.5.2 (this may take some time)...
多くのライブラリをダウンロードした後アプリケーションが起動します。
--- (Running the application, auto-reloading is enabled) ---
[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0.0.0.0:9000
(Server started, use Enter to stop and go back to the console...)
このように表示されれば成功です。このコンテナの9000番ポートはローカルにマップされていますので、ブラウザにlocalhost:9000
と入力すればアプリケーションにアクセスできます。
アプリケーションの開発
上記のコンテナの/root
は./root
がマウントされているので、ローカルのファイルシステムにあるファイルを編集すれば自動的に実行環境に反映されます。つまりローカルのPCにインストールされている好みのエディタやIDEを使って開発を行うことができます。ここでは例としてこのトップページにHello World!
の文字列を表示する変更を加えたいと思います。 app/views/index.scala.html
を開くと以下のようになっていると思います。
@()
@main("Welcome to Play") {
<h1>Welcome to Play!</h1>
}
これを以下のように変更します。
@()
@main("Welcome to Play") {
<h1>Welcome to Play!</h1>
Hello World!
}
これを保存してブラウザをリロードすると、ちゃんと変更が反映されていることがわかります。
アプリケーションのコンソールの方でもリロードされたことが確認できます。
2021-12-23 08:26:54 INFO play.api.Play Application started (Dev) (no global state)
[info] compiling 1 Scala source to /root/fterui-play-test/target/scala-2.13/classes ...
--- (RELOAD) ---
2021-12-23 08:35:17 INFO play.api.http.EnabledFilters Enabled Filters (see <https://www.playframework.com/documentation/latest/Filters>):
play.filters.csrf.CSRFFilter
play.filters.headers.SecurityHeadersFilter
play.filters.hosts.AllowedHostsFilter
2021-12-23 08:35:17 INFO play.api.Play Application started (Dev) (no global state)
アプリケーションのビルド
この状態のアプリケーションはあくまでも開発用なので、実際にデプロイする前にはプロダクション用にビルドする必要があります。ビルドはこれもsbt
コマンドから簡単に行うことができます。
root@8a50fee5f6b7:~/fterui-play-test# sbt dist
[info] welcome to sbt 1.5.2 (Oracle Corporation Java 11.0.13)
[info] loading settings for project fterui-play-test-build from plugins.sbt ...
[info] loading project definition from /root/fterui-play-test/project
[info] loading settings for project root from build.sbt ...
...(snip)...
[success] All package validations passed
[info] Your package is ready in /root/fterui-play-test/target/universal/fterui-play-test-1.0-SNAPSHOT.zip
[success] Total time: 21 s, completed Dec 23, 2021, 8:40:10 AM
このように表示されたら完了です。 target/universal
以下にできたzipファイルが成果物となります。試しにこちらを起動してみます。
Play Frameworkは起動するときにapplication secretを要求します。詳しくはドキュメントにありますので参照してください。例えば以下のようなコマンドで作成することができます。
# head -c 32 /dev/urandom | base64
HORVsaoVwdmKFsw8Pxl2vvWhCwmOCEuMCUjf/IFawzs=
sbt dist
で作成されたzipファイルを展開したディレクトリで、以下のコマンドでアプリケーションを実行できます。実行ファイル名はプロジェクト名です。
# ./bin/fterui-play-test -Dplay.http.secret.key=HORVsaoVwdmKFsw8Pxl2vvWhCwmOCEuMCUjf/IFawzs=
...(snip)...
2021-12-23 08:51:08 INFO play.api.Play Application started (Prod) (no global state)
2021-12-23 08:51:09 INFO play.core.server.AkkaHttpServer Listening for HTTP on /0.0.0.0:9000
開発中同様9000番ポートにアクセスすればアプリケーションの動作が確認できます。
デプロイするイメージの作成
上で作成したzipファイルを使ってデプロイするイメージを作成します。実行にはjavaが必要ですがsbtは不要なので、もっとサイズの小さいイメージを利用できます。ここではopenjdkの11-jre-slimを使います。以下のようなDockerfile
を作成し、zipファイルを展開したディレクトリに置きます。
FROM openjdk:11-jre-slim
COPY . /root/
CMD ["/root/bin/fterui-play-test", "-Dplay.http.secret.key=HORVsaoVwdmKFsw8Pxl2vvWhCwmOCEuMCUjf/IFawzs="]
実際の運用ではsecret keyは外から与えることになると思いますが、ここではそのままイメージ内で指定します。
イメージを作成して動作確認を行います。
$ docker build . -t fterui_play_test
...(snip)...
$ docker run --rm -p 9001:9000 fterui_play_test
...(snip)...
2021-12-23 09:07:21 INFO play.api.Play Application started (Prod) (no global state)
2021-12-23 09:07:21 INFO play.core.server.AkkaHttpServer Listening for HTTP on /0.0.0.0:9000
開発用のコンテナが9000番ポートを使っているので、こちらは9001番にマップしました。ブラウザからlocalhost:9001
にアクセスするとプロダクション用のアプリケーションにアクセスできます。
問題なく動作していることが確認できました。
IBM Cloud Container Registryへpush
作成したイメージをIBM Cloud Code Engineで実行するためには、Code Engine側からpullできる場所へイメージをpushしておく必要があります。
まずibmcloud cr
コマンドが使えるようになっている必要があります。ドキュメントによるとIBM Cloud CLIをインストールすると使えるようになっているはずですが、私のところではプラグインがインストールされていなかったので手動でインストールしました。
$ ibmcloud cr
失敗
'cr' は登録済みコマンドではありません。 インストール済みプラグインのリストを確認してください。 'ibmcloud help' を参照してください。
$ ibmcloud plugin install container-registry
リポジトリー 'IBM Cloud' から 'container-registry' を検索しています...
プラグイン 'container-registry 0.1.553' がリポジトリー 'IBM Cloud' 内で見つかりました
バイナリー・ファイルをダウンロードしようとしています...
11.50 MiB / 11.50 MiB [=============================================================================================================] 100.00% 0s
12058224 バイトがダウンロードされました
バイナリーをインストールしています...
OK
プラグイン 'container-registry 0.1.553' は /Users/fterui/.bluemix/plugins/container-registry に正常にインストールされました。 'ibmcloud plugin show container-registry' を使用して詳細を表示してください。
$ ibmcloud cr
名前:
ibmcloud cr -
使用法:
ibmcloud cr command [arguments...] [command options]
...(snip)...
コマンドが使えるようになったら、まずnamespaceを作成します。
$ ibmcloud cr namespace-add fterui_test_ns
レジストリー jp.icr.io のアカウント Fumihiko Terui's Account 用のリソース・グループ「Default」に、名前空間「fterui_test_ns」を追加中...
名前空間「fterui_test_ns」は正常に追加されました
OK
ローカルのdockerをcontainer registryに接続させます。
$ ibmcloud cr login
「jp.icr.io」にログインしています...
「jp.icr.io」にログインしました。
OK
接続したら、pushしたいローカルのイメージにタグ付けをします。
$ docker tag fterui_play_test:latest jp.icr.io/fterui_test_ns/fterui_play_test:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
fterui_play_test latest fbf7830ee235 28 minutes ago 264MB
jp.icr.io/fterui_test_ns/fterui_play_test latest fbf7830ee235 28 minutes ago 264MB
いざpushします。
$ docker push jp.icr.io/fterui_test_ns/fterui_play_test:latest
The push refers to repository [jp.icr.io/fterui_test_ns/fterui_play_test]
cde2813440c5: Pushed
7fe0bd342c72: Pushed
053ceb7fe9fc: Pushed
b549e2f2b0ec: Pushed
f397c6a338e4: Pushed
1c79be3b9ceb: Pushed
latest: digest: sha256:ef8a1cb7baacac3b55b87325b602aab7c1f01d342f90a200dface3605bb56701 size: 1577
これでローカルのイメージがレジストリにpushされました。
アプリケーションのデプロイ
Pushしたイメージからアプリケーションをデプロイします。IBM Cloudのコンソールからプロジェクトを選んでアプリケーションの作成を選択します。名前を決定してコンテナー・イメージを選択するところまではサンプルの時と同様で、イメージ参照のところに先ほどpushしたイメージを指定します。このイメージはパブリックではないレジストリに置いてあるので場所を指定する必要があります。「イメージの構成」をクリックすると自分のアカウントに結び付けられているレジストリー・サーバーや名前空間などを選択するフライアウトが表示されます。それぞれ1つしか作成してなければ自動的に選択された状態になりますね。
Listenポートに9000を指定するのをお忘れなく。全て設定したら「作成」をクリックして完了です。デプロイされるまでお茶でも飲んで待ちます。
が、実際は待てど暮らせど起動しません。流石にそんなに遅いはずはないと思い、ログを確認してみました。
$ ibmcloud ce application logs -n fterui-play-test
アプリケーション 'fterui-play-test' のすべてのインスタンスのログを取得中...
OK
fterui-play-test-00001-deployment-699c85577-flctb/user-container:
standard_init_linux.go:228: exec user process caused: exec format error
これは実行ファイルのアーキテクチャが違ってる時とかに出るやつですね。私の開発環境はMacbook Air(M1)なのでもしかしたらこれが原因かもと。あともう一つ、Code Engineではrootユーザーで実行してはいけないに引っかかってる可能性もありました。なのでまずDockerfile
を変更します。
FROM openjdk:11-jre-slim
COPY . /root/
RUN addgroup nonroot --gid 1100 && \
adduser nonroot --ingroup nonroot --uid 1100 --disabled-password && \
chown -R nonroot:nonroot /root
USER nonroot
WORKDIR /root
CMD ["/root/bin/fterui-play-test", "-Dplay.http.secret.key=HORVsaoVwdmKFsw8Pxl2vvWhCwmOCEuMCUjf/IFawzs="]
さらにM1 Mac上でビルドする時にplatformを指定します。
$ docker build . -t fterui_play_test --platform linux/amd64
そしてもう一度 docker tag
とdocker push
をしてイメージを更新してアプリケーションを作成したところ、無事起動しました!
喜び勇んでアプリケーションにアクセスしたところ、見事以下のような… orz
これは play.filters.hosts.AllowedHostsFilter
によって許可されてないホストからのアクセスとみなされたためですね。アプリケーションを起動したときにコンソールに出ているやつです。このフィルターにアプリケーションのホスト名を追加してやらないといけなさそうです。詳細はこちらの公式ドキュメントを参照してください。
アプリケーションのapplication.conf
に以下のようなエントリを作成します。
play.filters.hosts {
allowed = ["your_host_name", "localhost:9000"]
}
(もちろんyour_host_name
は実際のホスト名を入れることになります。)
開発環境で起動して普通にアクセスできることを確認し、それからもう一度イメージをビルドしてローカルのdockerで起動してアクセスしてみます。すると以下のように表示されます。
これはポートを9001番にマップしているせいで許されてないホストからのアクセスだと認識されて拒否されているわけです。開発環境で起動した9000番は大丈夫だったのでちゃんと設定が効いていることが確認できました。このイメージを再度レジストリにpushして、コンソールのアプリケーションから「編集して新規リビジョンを作成」を選び、そのまま何も変更せず作成してしばらく待ちます。デプロイが終わったらいざ確認です。
まとめ
IBM Cloud Code Engineを使ってPlay Frameworkを利用したアプリケーションのデプロイを行うのに必要な手順と陥りがちな注意点についてまとめました。完了してみるとある意味straight forwardでしたが、気にしてないとハマる点もいくつか見えたので、同じことを試される方の一助になれば良いかなと思っております。