PlayframeworkアプリケーションをAWSにdockerでデプロイしてCloudWatch Logsでログ収集した話

Playframework2.5のアプリケーションをdockerでAWS EC2にデプロイしてCloudWatch Logsでログを集約した話です。デプロイの自動化はここでは触れません。すごいまとめていうとdockerのawslogs logging driverが楽で便利だから使おうぜーってだけの話です。

dockerの1.9以上のバージョンでAmazon CloudWatch Logs logging driverを使いました。
環境は下記の通りです。

## 環境

* Playframework 2.5
* Amazon Linux
* Java 1.8 (EC2ではopenjdk1.8を利用)
* docker 1.12.6

## 手順

1. 本番用のlogbackの設定
2. playframeworkでdockerのビルド
3. CloudWatch Logsの設定
4. EC2インスタンスでアプリケーション起動

## 1. 本番用のlogbackの設定

dockerのロギングドライバーはdockerコンテナ内の標準出力・エラー出力の内容をハンドルしてくれるので、playframeworkのアプリケーションログをファイルに出力しないですべてdocker内のコンソールに出力するようにします。

logback-production.xmlを用意して下記のようにコンソールログだけにします。SQLのログもオフにしています。

```xml:logback-production.xml
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
<configuration>

  <conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
    </encoder>
  </appender>

  <appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="STDOUT" />
  </appender>

  <logger name="play" level="INFO" />
  <logger name="application" level="INFO" />

  <!-- Off these ones as they are annoying, and anyway we manage configuration ourselves -->
  <logger name="com.avaje.ebean.config.PropertyMapLoader" level="OFF" />
  <logger name="com.avaje.ebeaninternal.server.core.XmlConfigLoader" level="OFF" />
  <logger name="com.avaje.ebeaninternal.server.lib.BackgroundThread" level="OFF" />
  <logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />

  <root level="INFO">
    <appender-ref ref="ASYNCSTDOUT" />
  </root>

</configuration>
```

## 2. playframeworkでdockerのビルド

build.sbtにdockerビルド用の設定を下記のように追記します。Dockefileの内容を記述できます。ここではOracke Java8のdockerイメージを利用しています。

```scala:build.sbt
//
// docker build
//
enablePlugins(DockerPlugin)

import com.typesafe.sbt.packager.docker.{ExecCmd, Cmd}

dockerExposedPorts in Docker := Seq(9000)
dockerCommands := Seq(
  Cmd("FROM","nimmis/java:14.04-oracle-8-jdk"),

  Cmd("MAINTAINER","MychaelStyle"),
  Cmd("EXPOSE","9000"),

  Cmd("RUN","apt-get -y upgrade && apt-get -y update"),
  Cmd("RUN","apt-get -y install language-pack-ja ntp sysv-rc-conf"),
  Cmd("RUN","update-locale LANG=ja_JP.UTF-8"),

  Cmd("ADD","stage /"),
  Cmd("WORKDIR","/opt/docker"),
  Cmd("RUN","[\"chown\", \"-R\", \"daemon\", \".\"]"),
  Cmd("RUN","[\"chmod\", \"+x\", \"bin/MyApp\"]"),

  Cmd("USER","daemon"),
  Cmd("ENTRYPOINT","[\"bin/MyApp\", "
    +" \"-Dplay.cripto.secret=\\\"your secret!!\\\"\","
    +" \"-Dlogger.resource=logback-prod.xml\","
    +" \"-Dconfig.resource=production.conf\","
    +" \"-J-Xms512m\", \"-J-Xmx4096m\", \"-J-server\""
    +"]"),
  ExecCmd("CMD")
)
```

試しにビルドしてみます。

```bash
./bin/activator docker:stage
```

ビルドすると`[プロジェクトフォルダ]/target/docker`フォルダ内に必要なファイルとDockerfileができています。フォルダに移動してdockerイメージを作成します。

```bash
cd target/docker
docker build -t MyAppImage .
```

イメージがビルドできたらコンテナを起動して見ます。起動に問題なければとりあえずOK

```bash
docker run -t -d -p 9000:9000 --name=MyApp MyAppImage
```

エラーなく起動すればOK! エラーがある場合やhttp://localhost:9000/でアクセスできない場合はdocker execなどでコンテナにログインして
## 3. CloudWatch Logsの設定

### 3.1. IAMロールでCloudWatch Logsを許可

公式のドキュメントを見ながらごにょごにょすればできます。
http://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/QuickStartEC2Instance.html


まずはインスタンスで使う予定のロールにCloudWatchLogsのフルアクセスを許可するようにポリシーを設定します。ポリシー名は`CloudWatchLogsFullAccess`です。インスタンス用に新規にロールを作成する場合にもこのポリシーを含めるのを忘れないようにしましょう。

ポリシーの表示をしてみるとこんなコードになっています。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
```

アタッチしたインスタンスをAmazon Linuxで起動します。使ったのは2017年2月現在で`Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Type - ami-56d4ad31`のAMIです。最初の状態だと色々と入っていないので色々と入れました。

```bash
sudo yum groupinstall -y "Development tools"
sudo yum install -y docker java-1.8.0 awscli awslogs
```

最初に入るjavaが1.7なので1.8をいれてalternativesで1.8に切り替えます。

```bash
sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
*  1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
 + 2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2
```

awslogsエージェントのために設定をします。

```
vim /etc/awslogs/awscli.conf
```

リージョンを東京に変更して試しにエージェントで/var/log/messagesを送信する設定をしてみます。設定ファイルの項目は公式のガイドにあります。
http://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/AgentReference.html

```text:/etc/awslogs/awscli.conf
[plugins]
cwlogs = cwlogs
[default]
region = ap-northeast-1
```

```text:/etc/awslogs/awslogs.conf
[logstream1]
log_group_name = MyLogsTest
log_stream_name = {instance_id}
datetime_format = %Y-%m-%dT%H:%M:%S%z
time_zone = UTC
file = /var/log/messages
file_fingerprint_lines = 1
multi_line_start_pattern = {datetime_format}
initial_position = start_of_file
encoding = utf_8
buffer_duration = 10000
batch_count = 10000
batch_size = 1048576
```

エージェントを起動して見ます。

```
sudo service awslogs start
sudo chkconfig awslogs on
```

AWS CloudWatchのログの画面からロググループが作成されて閲覧できるようになっています！
さて、awslogs.confの設定とエージェントの起動手順はただのテストで、インスタンスにAWS CloudWatch Logsが使えるIAMロールが設定されて入ればdockerのコマンドだけで事足ります。ただし、dockerのあawslogs-logging-driverはロググループは自動で作ってくれないのでAWSマネジメントコンソール->CloudWatch->ログ の画面から「アクション」 -> 「ロググループの作成」でアプリケーション用のロググループを作成しておきます。ここでは画面は省略、[MyAppLogGroup]を作成したこととして進めます。

## 4. EC2インスタンスでアプリケーション起動

dockerhubとか使ってやったほうが楽なのでしょうがデプロイのテストのためインスタンスにログインしてgithubからpullしてビルドして稼働させるという手順でやってみます。

```bash
cd
mkdir webapps
cd webapps
github clone ...MyApp
cd MyApp
./bin/activator docker:stage
```

activatorでビルドできたらdockerイメージをビルドして起動します。

```bash
cd target/docker
docker build -t MyAppImage .
docker run -it -d --name MyApp -p 80:9000 \
  --log-driver=awslogs \
  --log-opt awslogs-region=ap-northeast-1 \
  --log-opt awslogs-group=MyAppLogGroup \
  --log-opt awslogs-stream=MyAppWebApplication \
  MyApp
```

以上でインスタンスにポート80でアクセスできるようになり、AWSの管理コンソールのCloudWatch -> ログの画面でログが閲覧できるようになりました！