DockerでとりあえずJettyを動かす最低限の構成をメモ

# 導入

## この記事の目的
備忘録です。

## やったこと
DockerのJettyイメージを使って、JavaサーブレットのHello Worldを実装しました。
サーブレットやWARに必要なものをいつも忘れてしまうので、メモとして残します。

## 使ったツール
- Docker
- Docker Compose
- Jetty
- Gradle

### Docker
コンテナ型仮想化基盤。
Jettyのインストールの手間を省くため、そのままで動く仮想環境を構築するために使いました。

### Docker Compose
複数のDockerコンテナの管理を補助するツール。
なくてもDockerは使えますが、dockerコマンドに渡す引数が複雑になりがちなので、それらをYAMLファイルとして残せるツールとして使いました[^dockerコマンドの引数が複雑になる例]。

[^dockerコマンドの引数が複雑になる例]: Jettyコンテナを8080ポートで起動するだけでも、`docker run -p "8080:8080" -t jetty:9.4.1-alpine`のように、シェルファイルに書き残したくなる引数の多さです。ボリュームをマウントするとなおさらです。

### Jetty
Javaサーブレットコンテナ。
WARをデプロイして使えるアプリケーションサーバーです。

### Gradle
Javaビルドツール。
依存関係の定義と、WARをビルドするために使いました。

## 検証環境
- macOS Sierra (10.12.3)
- Docker for Mac (1.13.0)
- Docker Compose (1.10.0)
- JDK (1.8.0_102)
- Gradle (3.2.1)


# 実施手順

## Jettyコンテナを起動する

作業フォルダー直下に以下のファイルを作成します：

```yaml:docker-compose.yml
# バージョン2記法で記述する
version: '2'

# 起動するコンテナを定義する
services:

    # Jettyコンテナ
    jetty:
        # Dockerfileからビルドするのではなく、Docker Hubの公式イメージを使う
        # サイズが小さいAlpineベースのイメージを使う
        image: jetty:9.4.1-alpine

        # コンテナ内のポートをホストに公開する
        # 書式 "HOST:CONTAINER"
        ports:
            # Jettyのポートを同じ番号でホストに公開する
            - "8080:8080"
```

docker-compose.ymlと同じフォルダーで`docker-compose up`コマンドを実行します。
すると以下のようにJettyコンテナが起動し、Jettyのログが表示されます：

```bash
Mac-mini-K:jetty-docker kazuma$ docker-compose up
Creating jettydocker_jetty_1
Attaching to jettydocker_jetty_1
jetty_1  | 2017-02-09 10:14:37.178:INFO::main: Logging initialized @460ms to org.eclipse.jetty.util.log.StdErrLog
jetty_1  | 2017-02-09 10:14:37.417:INFO:oejs.SetUIDListener:main: Setting umask=02
jetty_1  | 2017-02-09 10:14:37.426:INFO:oejs.SetUIDListener:main: Opened ServerConnector@6767c1fc{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
jetty_1  | 2017-02-09 10:14:37.427:INFO:oejs.SetUIDListener:main: Setting GID=101
jetty_1  | 2017-02-09 10:14:37.429:INFO:oejs.SetUIDListener:main: Setting UID=100
jetty_1  | 2017-02-09 10:14:37.434:INFO:oejs.Server:main: jetty-9.4.1.v20170120
jetty_1  | 2017-02-09 10:14:37.456:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///var/lib/jetty/webapps/] at interval 1
jetty_1  | 2017-02-09 10:14:37.477:INFO:oejs.AbstractConnector:main: Started ServerConnector@6767c1fc{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
jetty_1  | 2017-02-09 10:14:37.478:INFO:oejs.Server:main: Started @760ms
```

http://localhost:8080/ にアクセスすると、Jettyからの404が返ってくるので、起動確認ができます：

![スクリーンショット 2017-02-09 19.18.48.png](https://qiita-image-store.s3.amazonaws.com/0/118759/d57faa32-1a17-0516-0c86-5770c0601c26.png)


## WARを作成する

### Eclipseのプロジェクトを作成する

作業フォルダーに、src/main/javaフォルダーを作成しておきます。
また、以下のファイルも作成し、同じフォルダーで`gradle eclipse`を実行します：

```groovy:build.gradle
// Javaのビルドスクリプト

// プラグイン指定
// Javaをコンパイルするのに必要
apply plugin: 'java'

// フォルダーをEclipseプロジェクト化するのに必要
apply plugin: 'eclipse'

// WARを生成するのに必要
apply plugin: 'war'

// リポジトリーとしてMavenリポジトリーを指定
repositories {
    mavenCentral()
}

// 依存関係を記述する
dependencies {
    // providedCompile: コンパイル依存だがWARに含めない
    // サーブレットを作成するのに最低限必要なJARを追加
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}
```

作業フォルダーがEclipseプロジェクトになるので、以下のようにEclipseへインポートしておきます：

![スクリーンショット 2017-02-09 19.24.20.png](https://qiita-image-store.s3.amazonaws.com/0/118759/ee106dba-eb22-f424-b0f4-9525fab48dd4.png)


### サーブレットとweb.xmlを作成する

以下のようなサーブレットクラスを作成します。
ネームスペースは任意のものをつけてください：

```java:src/main/java/com/github/kazuma1989/jetty/TestServlet.java
package com.github.kazuma1989.jetty;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=UTF-8");

        // GETメソッドのときは、パラメーターによらず Hello World! を返すのみ
        PrintWriter out = resp.getWriter();
        out.println("Hello World!");
    }
}
```

また、src/main/webapp/WEB-INFフォルダーを作成し、以下のファイルを作成します：

```xml:src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID"
         version="3.1">

    <display-name>TestDynamicWebApp31</display-name>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>com.github.kazuma1989.jetty.TestServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/TestServlet/*</url-pattern>
    </servlet-mapping>
</web-app>
```

`<servlet-class>com.github.kazuma1989.jetty.TestServlet</servlet-class>`の箇所は、先ほど作成したサーブレットクラスのFQCNを記入します。

※ 参考リンク [[備忘録!]GradleでWARを作る - Qiita](http://qiita.com/_daisuke0802/items/f314709b3baa0e0f65b5)

作業フォルダーで`gradle war`コマンドを実行すると、build/libs以下にWARが生成されます：

```bash
Mac-mini-K:jetty-docker kazuma$ gradle war
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war UP-TO-DATE

BUILD SUCCESSFUL

Total time: 0.868 secs
Mac-mini-K:jetty-docker kazuma$ ls build/libs/
jetty-docker.war
```


## JettyにWARをデプロイする

docker-compose.ymlの最後に以下の記述を追加します。
ホストのbuild/libsフォルダーにあるWARを、Jettyコンテナ内の/var/lib/jetty/webappsに置くことでデプロイします：

```yaml:docker-compose.yml
…
        # コンテナ内のフォルダーをホストとマウントする
        # 書式 HOST（docker-compose.ymlからの相対パス）:CONTAINER（絶対パス）
        volumes:
            # Jettyのデプロイフォルダーに、GradleがWARを生成する先をマウントする
            # foo.war には http://localhost:8080/foo でアクセスできるようになる
            - ./build/libs:/var/lib/jetty/webapps
```

docker-compose.ymlを修正したので、Jettyコンテナを作り直します。
念のため削除して、再生成します：

```bash
Mac-mini-K:jetty-docker kazuma$ docker-compose stop && docker-compose rm -f
Stopping jettydocker_jetty_1 ... done
Going to remove jettydocker_jetty_1
Removing jettydocker_jetty_1 ... done
Mac-mini-K:jetty-docker kazuma$ docker-compose up
Creating jettydocker_jetty_1
Attaching to jettydocker_jetty_1
jetty_1  | 2017-02-09 10:45:01.994:INFO::main: Logging initialized @524ms to org.eclipse.jetty.util.log.StdErrLog
jetty_1  | 2017-02-09 10:45:02.278:INFO:oejs.SetUIDListener:main: Setting umask=02
jetty_1  | 2017-02-09 10:45:02.289:INFO:oejs.SetUIDListener:main: Opened ServerConnector@6767c1fc{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
jetty_1  | 2017-02-09 10:45:02.289:INFO:oejs.SetUIDListener:main: Setting GID=101
jetty_1  | 2017-02-09 10:45:02.291:INFO:oejs.SetUIDListener:main: Setting UID=100
jetty_1  | 2017-02-09 10:45:02.297:INFO:oejs.Server:main: jetty-9.4.1.v20170120
jetty_1  | 2017-02-09 10:45:02.320:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///var/lib/jetty/webapps/] at interval 1
jetty_1  | 2017-02-09 10:45:02.623:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=71ms
jetty_1  | 2017-02-09 10:45:02.832:INFO:oejs.session:main: DefaultSessionIdManager workerName=node0
jetty_1  | 2017-02-09 10:45:02.832:INFO:oejs.session:main: No SessionScavenger set, using defaults
jetty_1  | 2017-02-09 10:45:02.833:INFO:oejs.session:main: Scavenging every 600000ms
jetty_1  | 2017-02-09 10:45:03.113:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@20398b7c{/jetty-docker,file:///tmp/jetty/jetty-0.0.0.0-8080-jetty-docker.war-_jetty-docker-any-8862506664121339371.dir/webapp/,AVAILABLE}{/jetty-docker.war}
jetty_1  | 2017-02-09 10:45:03.120:INFO:oejs.AbstractConnector:main: Started ServerConnector@6767c1fc{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
jetty_1  | 2017-02-09 10:45:03.121:INFO:oejs.Server:main: Started @1650ms
```

jetty-docker.warを読み込んでいるログが出力されています。
再び http://localhost:8080/ にアクセスすると、先ほどはなかったリンクが表示され、WARが読み込まれたことを示しています：

![スクリーンショット 2017-02-09 19.46.33.png](https://qiita-image-store.s3.amazonaws.com/0/118759/688db120-c4d5-a4cd-87bc-d23a15046997.png)

http://localhost:8080/jetty-docker/TestServlet/foo （fooの部分は任意）にアクセスすると、作成したサーブレットが実行され、成功です：

![スクリーンショット 2017-02-09 19.49.51.png](https://qiita-image-store.s3.amazonaws.com/0/118759/c9883ce3-0583-e064-894b-a0a4d38f84bb.png)



# まとめ

自作のサーブレットクラスをJettyにデプロイし、実行する手順メモを残しました。
作成したファイルは以下の4つだけなので、非常に単純です。

    build.gradle
    docker-compose.yml
    src/main/java/com/github/kazuma1989/jetty/TestServlet.java
    src/main/webapp/WEB-INF/web.xml

Javaサーブレットで手取り早く何かを試したいときに有用かもしれません。