TomcatなコンテナでWebアプリを動かすのもいいけど、お手軽にSpringBootでビルドしたjarをコンテナ化して動かしてみようの巻(SpringBootが手軽かどうかは意見が分かれそうだが)。
前提知識としては、
AWSのアカウント作った直後の状態からECS+FargateでTomcatのDockerコンテナを起動する
Docker初心者が「なんとなく理解した」レベルになるまでの記事まとめ
あたりで、EC2上に環境整えていて、Dockerのコマンド群をなんとなく理解していること。
元ネタ
一からSpringBootなアプリを作って試してみるのも一興ではあるものの、コンテナ化されていないアプリケーションをコンテナ化する方が面白いと思って元ネタを探す。
シンプルなSpring Boot アプリをAWS Code シリーズを利用して自動デプロイするハンズオン
良い感じのものがあった。
これならこの後に、CI/CDに繋ぐこともできるので、これを元ネタにしてみよう。
このハンズオンのStep3まで進めて、ローカル環境でDynamoDBに繋がったWebアプリが起動できることまで確認しておく。
SpringBootなWebアプリをコンテナ化する
さて、Javaの実行環境は何が良いか?というのを考えて
alpine:3.10でopenjdk11がサポートされていたので、AdoptOpenJDK(alpine)と比較する
を見てみると、「何でも良い」と書いてあるので、今回はハンズオンで作ったaws-codeseries-spring-webapp-handson
のディレクトリ配下に以下のDockerfileを作成。
FROM adoptopenjdk/openjdk11
COPY target/my-greeting-web-0.1.0.jar /var/my-greeting-web-0.1.0.jar
CMD ["java","-jar","/var/my-greeting-web-0.1.0.jar"]
※Webアプリを/var配下に置くのは行儀が悪い気がするが、気にしないでほしい……。
で、Dockerコンテナをビルドする。
$ docker build -t my-greeting-web .
~(中略)~
Successfully tagged my-greeting-web:latest
$
こんな感じで成功したら、以下のようにコンテナイメージが出来上がっている。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-greeting-web latest 6414dffeb1bf 3 hours ago 449MB
$
サイズがでかい。DockerfileのFROMのタグをlatestではなくてx86_64-alpine-jre-11.0.4_11とかにした方が軽量なんだろうな……。チューニングは後で実施する。
Dockerコンテナを起動する
ハンズオンのStep3までやっていると
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
xxxxxxxxxxxx amazon/dynamodb-local "java -jar DynamoDBL?Β 2 hours ago Up 2 hours 0.0.0.0:8000->8000/tcp dynamodb-local
$
な感じでローカルのDynamoDBが起動しているはず。実際は、ハンズオンの起動パラメータだとNAMESがテキトーなものになっているので、分かりやすくするために--name dynamodb-local
を付けて上げ直しておくと良い(上の出力イメージの通りになる)。
で、いよいよビルドしたコンテナを起動!
$ docker run -d --name my-greeting-web -p 80:8080 --env AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx --env AWS_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --env SPRING_PROFILES_ACTIVE=dev --link dynamodb-local:localhost my-greeting-web
よしよし、ちゃんと表示された。
ちなみに、--envで指定している環境変数AWS_ACCESS_KEY_ID/AWS_SECRET_KEYは、SPRING_PROFILES_ACTIVE=dev で動作させる分には不要(↑で起動したローカル)。prodで実際のDynamoDBにcat ~/.aws/credentials
で確認できる。
ローカル環境で起動するときには、良い感じにcredentialsを参照してくれるのだが、コンテナではファイルがないので、環境変数を食わせてあげないと以下のエラーで起動できない(ハマりポイント1)。
There was an unexpected error (type=Internal Server Error, status=500).
Unable to load AWS credentials from any provider in the chain: [EnvironmentVariableCredentialsProvider: Unable to load AWS credentials from environment variables (AWS_ACCESS_KEY_ID (or AWS_ACCESS_KEY) and AWS_SECRET_KEY (or AWS_SECRET_ACCESS_KEY)), SystemPropertiesCredentialsProvider: Unable to load AWS credentials from Java system properties (aws.accessKeyId and aws.secretKey), com.amazonaws.auth.profile.ProfileCredentialsProvider@xxxxxxxx: profile file cannot be null, com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper@xxxxxxxx: The requested metadata is not found at http://xxx.xxx.xxx.xxx/latest/meta-data/iam/security-credentials/]
環境変数SPRING_PROFILES_ACTIVE
は、SpringBootの起動パラメータ。元ネタのハンズオンのサンプルプログラムでは、SPRING_PROFILES_ACTIVE=dev
の時にローカルのDynamoDBに接続しにいく。SPRING_PROFILES_ACTIVE=prod
にすると、実際のDynamoDBに読み書きしにいくので、事前にAmazonDynamoDBFullAccess
な権限を持ったIAMロールを、動かしているEC2に設定しておく。
また、--link dynamodb-local:localhost
を指定しているのは、コンテナを跨いで通信をするための設定。この設定を入れていないと、以下のようにコンテナ間の通信でエラーが発生してしまう(ハマりポイント2)。
There was an unexpected error (type=Internal Server Error, status=500).
Unable to execute HTTP request: Connect to localhost:8000 [localhost/127.0.0.1] failed: Connection refused (Connection refused)
パラメータ指定のdynamodb-local
は、ローカルのDynamoDBを起動するときに---name
で指定したコンテナ名、localhost
は、以下のSpringBootのconfigで指定されている通信先に合わせる。
$ cat target/classes/config/application-dev.yml
amazon:
dynamodb:
local: true
endpoint: http://localhost:8000
region: localtest
tableprefix: dev-
credential:
profile: default
$
※実は、このcredentialのprofileを何かいじれば環境変数を渡すまでもなく起動できたかもしれないが、イマイチよく分からなかった…。
なお、Dockerの通信設定関連は
さくらのナレッジ:Docker入門(第五回)〜コンテナ間通信〜
が分かりやすくて良かった。このサイトによると、--link
使用は非推奨となっているが、実験目的でやっているので気にしないことにする。
コンテナサイズをチューニングする
dockerhubのadoptopenjdk/openjdk11を見てみると、色々なタグがあるので、その中からslim的なキーワードのあるものを探してみる。
$ docker images adoptopenjdk/openjdk11
REPOSITORY TAG IMAGE ID CREATED SIZE
adoptopenjdk/openjdk11 alpine-slim 2eea1e531ebc 2 weeks ago 253MB
adoptopenjdk/openjdk11 alpine 3c3df581b122 2 weeks ago 345MB
adoptopenjdk/openjdk11 slim b831d1a48a80 2 weeks ago 348MB
adoptopenjdk/openjdk11 latest b6b04edad768 5 months ago 422MB
$
alpine-slimが一番小さいので、これをベースにmy-greeting-web用のDockerfileを書き直してリビルドしてみたが、なぜかこれだとコンテナ間通信でエラーになってしまった。
どうにもalpineシリーズがうまくいかないようだ。dockerhub内のDockerfileを比較すれば原因に辿り着けるかもしれないが、時間がかかりそうなので今回は割愛。
slimをベースにリビルドしたら、
$ docker images my-greeting-web
REPOSITORY TAG IMAGE ID CREATED SIZE
my-greeting-web latest e8407f96287b 15 minutes ago 374MB
$
といった具合に、元の449MBよりも75MBほど削減できた。
ECSでも起動してみよう
これを見つつ、EC2で動作確認したコンテナをECRに登録後、ECS+Fargateで起動する。
当然ながら、参考にしたハンズオンではDynamoDBにアクセスするので、タスクに設定するIAMロールにDynamoDBアクセスのためのポリシ(AmazonDynamoDBFullAccess
など)を適用するのを忘れないように。
あとは、環境変数を本物のDynamoDBに向けるために、タスク定義のコンテナ編集時に以下の環境変数を渡せば良い。
これで、SpringBootなWebアプリがサーバレスで動かせるようになった!