Dockerイメージのマルチステージビルド

# はじめに
最近、Dockerの勉強を始めました。
今回は、Spring Bootの開発環境を整えていきます。せっかくなのでMavenも少し触っていきたい。
ゴールとしてはSpring Bootプロジェクトが実行できればいいかなって感じです。

# 環境
- macOS Mojave ver10.14.5
- Docker for Mac ver18.09.2

# 準備
## Spring Bootプロジェクトを用意する
動かすものがないと仕方がないので、[Spring Initializr](https://start.spring.io/)で用意しました。
へー、こんなのがあるんだ。Spring Web Starterでいいのかな？？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178930/90a5fae4-b9c4-5380-a3f3-ac34531e53f8.png)
すると、こんな感じでダウンロードされます。
![スクリーンショット 2019-08-22 19.19.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178930/0dcbe62a-561d-4703-4413-3e834388128c.png)
pom.xmlを編集します。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>hello</groupId>
	<artifactId>hello-world</artifactId><!-- ここ -->
	<version>0.0.1</version><!-- ここ -->
	<name>hello</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
```

続いて「Hello World from Docker」と表示されるように、HelloApplication.javaも編集します。

```java:hello/src/main/java/hello/hello/HelloApplication.java
package hello.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class HelloApplication {

    @RequestMapping("/")
    public String home() {
        return "Hello World from Docker";
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}
```

## とりあえずビルドしてみる
最終的にDockerfileに落とし込んでいきますが、まだまだ経験不足でいきなり書けないので、一度Mavenイメージから作ったコンテナでプロジェクトをビルドしてみます。
`-v`を使うことで、コンテナ側からもhelloディレクトリ以下にアクセスできるようになります。

```sh
docker run --rm -it -v /Users/.../hello:/opt/java maven:3.6.1-jdk-8 /bin/bash
```
コンテナが立ち上がったら、コマンドを叩いてビルドしていきます。
がががーとMavenが動いて、targetディレクトリにjarファイルを書き出してくれるはずです。

```sh:
root@2a00dc872367:# cd /opt/java
root@2a00dc872367:/opt/java# mvn install
(省略)
[INFO] BUILD SUCCESS
(省略)
root@2a00dc872367:/opt/java# ls target
hello-world-0.0.1.jar (他にもファイルがあるが、省略)
```

これでjarファイルの生成ができました！
では、ここまでのことを踏まえてDockerfileを書いていきます。

# 本題
## Dockerfileの記述
次のことがコンテナで行われるよう、マルチステージビルドを利用して書いていきます。
1. Spring Bootプロジェクトをビルド(jarファイルの生成)
2. jarファイルを実行用のコンテナにコピー
3. 実行

```dockerfile
# 1.jarファイルの生成
FROM maven:3.6.1-jdk-8 as builder
RUN mkdir -p /opt/java/src
  # 必要なソースを /opt/java へコピー
COPY ./hello/ /opt/java/
  # mvn install によりtargetディレクトリにjarが生成される
RUN cd /opt/java && mvn install

FROM openjdk:8-jre-alpine
RUN mkdir -p /opt/app/
# 2.builderコンテナの中にあるjarファイルを /opt/app/ にコピー
COPY --from=builder /opt/java/target/hello-world-0.0.1.jar /opt/app/
  # 8080ポートを開ける
EXPOSE 8080
# 3.アプリを実行
CMD  ["java","-jar","/opt/app/hello-world-0.0.1.jar"]
```

##コンテナ作成・起動
下記のコンテナでコンテナを作成・起動します。

```sh
docker build -t java_server .
docker run --rm -it -p 8080:8080 java_server:latest -d
```

では、ブラウザで`localhost:8080`を叩いてみます。
![スクリーンショット 2019-08-23 11.56.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178930/3151c73b-c99e-7919-b52c-10e6d42eb182.png)
やったー

#最後に
Dockerfileがうまく書けないときは、とにかくコンテナを作成・起動してみて、そこまでの過程をDockerfileに落とし込んでいくというやり方がいいと思いました。

##参考
[Dockerで始めるSpring Boot](https://qiita.com/ken0909/items/a3f8594ce677bbc7c4c2)
[CodeBuildでDockerイメージのマルチステージビルド](http://cloudfish.hatenablog.com/entry/2018/08/09/204635)
[Dockerfileを書いてみる](https://qiita.com/nl0_blu/items/1de829288db2670276e8)
[DockerfileのADDとCOPYの違いを結論から書く](https://qiita.com/YumaInaura/items/1647e509f83462a37494)
[よく使うMavenコマンド集](https://qiita.com/KevinFQ/items/e8363ad6123713815e68)

##更新
###Dockerfile
イメージが小さくなるように書く。
ローカルのファイルをコピーするだけなら`ADD`コマンドより`COPY`を使ったほうがいい。
できるだけDockerfileが短くなるようする。

```dockerfile
ADD ./hello/pom.xml /opt/java/
ADD ./hello/src /opt/java/src

COPY ./hello/ /opt/java/
```

なんとなくjdk使ってたのですが、実行するだけならjreで十分

```dockerfile
FROM openjdk:jdk-alpine
FROM openjdk:8-jre-alpine
```

起動に必要なのはjarファイルだけなので、targetディレクトリを丸ごとコピーする必要はない。

```dockerfile
COPY --from=builder /opt/java/target/ /opt/app/
COPY --from=builder /opt/java/target/hello-world-0.0.1.jar /opt/app/
```