AWSでSpringBootベースのWebアプリを起動してみる(Docker on EC2編/ECS＋Fargate編)

TomcatなコンテナでWebアプリを動かすのもいいけど、お手軽にSpringBootでビルドしたjarをコンテナ化して動かしてみようの巻(SpringBootが手軽かどうかは意見が分かれそうだが)。

前提知識としては、
[AWSのアカウント作った直後の状態からECS＋FargateでTomcatのDockerコンテナを起動する](https://qiita.com/neruneruo/items/82c9daf90d5782f150d2)
[Docker初心者が「なんとなく理解した」レベルになるまでの記事まとめ](https://qiita.com/neruneruo/items/d900d712c76da4fee742)
あたりで、EC2上に環境整えていて、Dockerのコマンド群をなんとなく理解していること。

# 元ネタ
一からSpringBootなアプリを作って試してみるのも一興ではあるものの、コンテナ化されていないアプリケーションをコンテナ化する方が面白いと思って元ネタを探す。

[シンプルなSpring Boot アプリをAWS Code シリーズを利用して自動デプロイするハンズオン](https://blog.nijot.com/aws/autodeployment-spring-boot-app-with-aws-code-series/)

良い感じのものがあった。
これならこの後に、CI/CDに繋ぐこともできるので、これを元ネタにしてみよう。

このハンズオンのStep3まで進めて、ローカル環境でDynamoDBに繋がったWebアプリが起動できることまで確認しておく。

# SpringBootなWebアプリをコンテナ化する
さて、Javaの実行環境は何が良いか？というのを考えて

[alpine:3.10でopenjdk11がサポートされていたので、AdoptOpenJDK(alpine)と比較する](https://qiita.com/h-r-k-matsumoto/items/975fb4f2d05b1e939b85)

を見てみると、「何でも良い」と書いてあるので、今回はハンズオンで作った```aws-codeseries-spring-webapp-handson```のディレクトリ配下に以下のDockerfileを作成。

```dockerfile: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
$ 
```

こんな感じで成功したら、以下のようにコンテナイメージが出来上がっている。

```shell
$ 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までやっていると

```shell
$ 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```を付けて上げ直しておくと良い(上の出力イメージの通りになる)。

で、いよいよビルドしたコンテナを起動！

```shell
$ 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で指定されている通信先に合わせる。


```shell
$ 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入門（第五回）〜コンテナ間通信〜](https://knowledge.sakura.ad.jp/16082/)
が分かりやすくて良かった。このサイトによると、```--link```使用は非推奨となっているが、実験目的でやっているので気にしないことにする。

# コンテナサイズをチューニングする
[dockerhubのadoptopenjdk/openjdk11](https://hub.docker.com/r/adoptopenjdk/openjdk11/)を見てみると、色々なタグがあるので、その中からslim的なキーワードのあるものを探してみる。

```shell
$ 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をベースにリビルドしたら、

```shell
$ docker images my-greeting-web
REPOSITORY         TAG       IMAGE ID        CREATED           SIZE
my-greeting-web    latest    e8407f96287b    15 minutes ago    374MB
$
```

といった具合に、元の449MBよりも75MBほど削減できた。

# ECSでも起動してみよう
[これ](https://qiita.com/neruneruo/items/82c9daf90d5782f150d2)を見つつ、EC2で動作確認したコンテナをECRに登録後、ECS＋Fargateで起動する。

当然ながら、参考にしたハンズオンではDynamoDBにアクセスするので、タスクに設定するIAMロールにDynamoDBアクセスのためのポリシ(```AmazonDynamoDBFullAccess```など)を適用するのを忘れないように。

あとは、環境変数を本物のDynamoDBに向けるために、タスク定義のコンテナ編集時に以下の環境変数を渡せば良い。
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/407aa015-ffc0-59df-2f47-7a2dbcc217d4.png)

これで、SpringBootなWebアプリがサーバレスで動かせるようになった！