アプリを Docker 、Docker Compose でコンテナを動かしてみる

# はじめに

記載情報は CI/CD に関連します。  
実務上関わった環境・機能を身近な情報としてお届けしたく、投稿をはじめました。  
各学習上、お金のかからない方法を選択していきます。

以下の手順を実施することで、アプリケーション開発者がアプリを Docker 、Docker Compose でコンテナを動かしてみる工程を模倣します。

## 対象者と関連環境・機能

この記事は下記のような人を対象にしています。

- CI/CD 初学者
- プログラミング初学者
- 駆け出しエンジニア

記載している環境・機能は以下です。

- Java
- Spring Boot
- Spring Web
- Docker
- Docker Compose

## 事前準備

- 検証用 OS(ここでは ubuntu) を準備する
- ubuntu に Docker をインストールする　[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
- ubuntu に Docker Compose をインストールする（お好みでどうぞ）　[Install Docker Compose](https://docs.docker.com/compose/install/)
- サンプルアプリケーションをコピーする

### サンプルアプリケーション

[Dockerfile 追加](https://github.com/oad3/application-springboot/tree/feature/add-Dockerfile)

最新はこちらとなりますが、clone 後に初期状態を遡って確認頂けます。  
[サンプルアプリ の最新](https://github.com/oad3/application-springboot)

## 事前準備

Dockerfile を作成する

```Dockerfile
FROM openjdk:11

# アプリケーションディレクトリを作成
WORKDIR /usr/src/myapp

# アプリケーション配置
COPY ./build/libs/application-springboot-0.0.1-SNAPSHOT.jar ./application-springboot-0.0.1-SNAPSHOT.jar

# コンテナの公開ポート
EXPOSE 8080

# アプリケーション起動
CMD [ "java", "-jar", "application-springboot-0.0.1-SNAPSHOT.jar" ]
```

Docker build をする。  
「/build/libs/application-springboot-0.0.1-SNAPSHOT.jar」が存在しない場合、「gradlew build」を実行して生成しておきます。  
最後の「.」が Dockerfile のパスを指し、カレントディレクトリに Dockerfile が存在する状態で実行しています。  
（後々、パイプライン上で gradlew build、docker build、docker push と一連流れる処理を個別に手動実行しているイメージとなっています。）

```docker build
$ docker build -t oad3jp999/application-springboot:0.0.1 .
Sending build context to Docker daemon   19.9MB
Step 1/5 : FROM openjdk:11
 ---> e782567c0965
Step 2/5 : WORKDIR /usr/src/myapp
 ---> Using cache
 ---> ac967f34611a
Step 3/5 : COPY ./build/libs/application-springboot-0.0.1-SNAPSHOT.jar ./application-springboot-0.0.1-SNAPSHOT.jar
 ---> 7cf31c6b2dca
Step 4/5 : EXPOSE 8080
 ---> Running in e621abd4f320
Removing intermediate container e621abd4f320
 ---> 46bceec1e334
Step 5/5 : CMD [ "java", "-jar", "application-springboot-0.0.1-SNAPSHOT.jar" ]
 ---> Running in 359899160e01
Removing intermediate container 359899160e01
 ---> d43e7721a88d
Successfully built d43e7721a88d
Successfully tagged oad3jp999/application-springboot:0.0.1
```

docker image が作成されました。

```docker images
$ docker images
REPOSITORY                                   TAG                IMAGE ID       CREATED         SIZE
oad3jp999/application-springboot             0.0.1              d43e7721a88d   4 seconds ago   667MB
```

コンテナを動かしてみます。

```docker run
$ docker run -itd d43e7721a88d
37840a75bb94fef3f7f249053d7a74cebfb59a22632822481f5614b9e1f9fbd3
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS      NAMES
37840a75bb94   d43e7721a88d   "java -jar applicati…"   6 seconds ago   Up 3 seconds   8080/tcp   modest_tharp
```

コンテナのログを見てみます。  
冒頭に「Hello LibraryJar!」が表示され、作成ライブラリを取り込んだ形でビルドが行われています。

```docker logs
$ docker logs 37840a75bb94
 =========================================
 Hello LibraryJar!
 =========================================


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.7)

2022-04-30 22:26:32.027  INFO 1 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.12 on 37840a75bb94 with PID 1 (/usr/src/myapp/application-springboot-0.0.1-SNAPSHOT.jar started by root in /usr/src/myapp)
2022-04-30 22:26:32.032  INFO 1 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
2022-04-30 22:26:34.319  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-04-30 22:26:34.349  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-04-30 22:26:34.349  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.62]
2022-04-30 22:26:34.741  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-04-30 22:26:34.741  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2524 ms
2022-04-30 22:26:35.183  INFO 1 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2022-04-30 22:26:35.469  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-04-30 22:26:35.497  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 4.632 seconds (JVM running for 8.231)
```

コンテナの IP を確認すると、ホストから「172.17.0.2」で見えている事が確認できます。

```ip
$ docker exec -it 37840a75bb94 /bin/bash
root@37840a75bb94:/usr/src/myapp# ip -d a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 minmtu 0 maxmtu 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 minmtu 68 maxmtu 65535
    veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0　※ホストからみれば「172.17.0.2」で見えている
       valid_lft forever preferred_lft forever
root@37840a75bb94:/usr/src/myapp# exit
```

ホストからコンテナに http アクセスしたところ index.html の内容が返り結果良好。  
このイメージを利用して、Docker Hub へ docker image をプッシュできるように環境を整えます。

```curl
$ curl http://172.17.0.2:8080/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Hello SpringBoot Web Page</title>
</head>
<body>
    <H1>Hello SpringBoot Web Page</H1>
    </br>
    <form method="post" action="/result">
        Please input name<br>
        <input type="text" name="inputvalue"/>
        <input type="submit" value="submit" />
    </form>
</body>
```

前提作業  
[Docker Hub アカウントの登録](https://docs.docker.jp/docker-hub/accounts.html#registration-for-a-docker-hub-account)  
[Docker Hub にログイン](https://docs.docker.jp/docker-hub/accounts.html#login)

```docker login
$ docker login
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /home/xxx/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
```

Docker Hub へ作成したイメージをプッシュ。  
（Docker Hub で全公開することはほぼないものかと思いますが、お見せするのに手ごろな例として利用させてもらいます）

```docker push
$ docker push oad3jp999/application-springboot:0.0.1
The push refers to repository [docker.io/oad3jp999/application-springboot]
0a37caee56e1: Pushed
52a5b831373a: Mounted from ・・・
0.0.1: digest: sha256:1b2c6bc37e1784f9fcf9c8c12fc75b76295cef4b457e60bb9a60c0fcf5faef92 size: 2213
```

![UseDockerImage_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2738816/9e077c9a-cb4d-a6f2-113d-ba7109345d71.png)


以下はお掃除につきおまけ情報です。（コンテナ停止、ローカルイメージ削除）

```docker rmi
$ docker ps　※動いているコンテナを表示
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS      NAMES
37840a75bb94   d43e7721a88d   "java -jar applicati…"   33 minutes ago   Up 32 minutes   8080/tcp   modest_tharp
$ docker stop 37840a75bb94　※動いているコンテナを停止
37840a75bb94
$ docker ps　※コンテナが動いていないことを確認
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker images　※docker imageを確認
REPOSITORY                                   TAG                IMAGE ID       CREATED          SIZE
oad3jp999/application-springboot             0.0.1              d43e7721a88d   34 minutes ago   667MB
$ docker rmi -f d43e7721a88d　※先ほどビルドして動かしていたローカルイメージを消します。今後は Docker Hub にプッシュしたイメージを使います。
Untagged: oad3jp999/application-springboot:0.0.1
Untagged: oad3jp999/application-springboot@sha256:1b2c6bc37e1784f9fcf9c8c12fc75b76295cef4b457e60bb9a60c0fcf5faef92
Deleted: sha256:d43e7721a88df9d6f431630eed6c364c74e4919343bee586771c86faff44ff29
Deleted: sha256:46bceec1e33486061f54dbbe6616f60bbde8299ffd773fe58c0d534799e34ff3
Deleted: sha256:7cf31c6b2dcac8c4a8b296420a32fb840730485021ab85fbab4e6f24e8a45798
$ docker images
REPOSITORY                                   TAG                IMAGE ID       CREATED         SIZE
```

ここまで行うと環境をまたいで Docker Image の利用が行いやすくなってきます。  
別の Docker ランタイム環境でイメージを利用する例として、docker-compose をご紹介。

サンプルアプリケーション
[manifest-files 追加](https://github.com/oad3/application-springboot/blob/feature/add-manifest-files/docker-compose.yml)

docker-compose.yml を作成

```docker-compose.yml
version: '3'
services:
  myapp:
    image: oad3jp999/application-springboot:0.0.1
    container_name: application-springboot
    ports:
     - "8080:8080"
```

作成した yml の配置階層で、docker-compose コマンドを使用し、コンテナを起動する形です。

```docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build unknown
$ docker-compose up -d　※作成したymlの配置階層で、docker-composeコマンドを使用し、コンテナを起動
Creating network "application-springboot_default" with the default driver
Pulling myapp (oad3jp999/application-springboot:0.0.1)...
0.0.1: Pulling from oad3jp999/application-springboot
627b765e08d1: ...
Digest: sha256:1b2c6bc37e1784f9fcf9c8c12fc75b76295cef4b457e60bb9a60c0fcf5faef92
Status: Downloaded newer image for oad3jp999/application-springboot:0.0.1
Creating application-springboot ... done
$ docker-compose ps　※立ち上がったコンテナを確認
         Name                       Command               State                    Ports
----------------------------------------------------------------------------------------------------------
application-springboot   java -jar application-spri ...   Up      0.0.0.0:8080->8080/tcp,:::8080->8080/tcp
$ docker ps　※dockerコマンドでも確認可能
CONTAINER ID   IMAGE                                    COMMAND                  CREATED          STATUS          PORTS                                       NAMES
96f6d7f1c9be   oad3jp999/application-springboot:0.0.1   "java -jar applicati…"   28 seconds ago   Up 25 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   application-springboot
$ docker exec -it 96f6d7f1c9be /bin/bash　※コンテナ内の情報収集
root@96f6d7f1c9be:/usr/src/myapp# ip -d a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 minmtu 0 maxmtu 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 minmtu 68 maxmtu 65535
    veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
       valid_lft forever preferred_lft forever
</html>root@96f6d7f1c9be:/usr/src/myapp# exit
exit
$ curl http://172.19.0.2:8080/　※curlで接続良好
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Hello SpringBoot Web Page</title>
</head>
<body>
    <H1>Hello SpringBoot Web Page</H1>
    </br>
    <form method="post" action="/result">
        Please input name<br>
        <input type="text" name="inputvalue"/>
        <input type="submit" value="submit" />
    </form>
</body>
$ docker-compose stop　※コンテナ停止
Stopping application-springboot ... done
$ docker ps　※稼働コンテナなし
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```

## 関連記事

- [CI/CD とは?](https://qiita.com/wf-kenji-ikeda/items/bfb6d5e21b33d0cca83b)
- [Azure Devops (Artifacts) を無償、無料で使う](https://qiita.com/wf-kenji-ikeda/items/9c21859d8b5b6a63246e)
- [Spring Tool Suite 4 インストール、Spring Initializr 、gradlew の利用](https://qiita.com/wf-kenji-ikeda/items/d00766da8a4076ec43ed)
- [Spring Boot Web アプリケーションを作る](https://qiita.com/wf-kenji-ikeda/items/1e64e44094ce8a1d997f)
- [Jar の Library を作り、Azure Artifacts を利用して成果物を配布する](https://qiita.com/wf-kenji-ikeda/items/696bda335a8933abfcb3)
- [ライブラリ開発者より指定されたバージョンを利用してアプリを作る](https://qiita.com/wf-kenji-ikeda/items/5bf06ce19c52e73a5463)
- [アプリを Docker 、Docker Compose でコンテナを動かしてみる](https://qiita.com/wf-kenji-ikeda/items/e6604ea6ae257f3925a7)
- [Azure Devops (PileLines) を無償、無料で使う](https://qiita.com/wf-kenji-ikeda/items/451838da3b4ac45a8d65)
- [Azure Devops (PileLines) でテスト、ビルド、イメージプッシュをする](https://qiita.com/wf-kenji-ikeda/items/5ddf6158bb98c4174db9)
- [minikube(kubernetes) でコンテナを動かしてみる](https://qiita.com/wf-kenji-ikeda/items/95604d83850a17b91c46)
- [nginx のコンテナを Docker や minikube(kubernetes) で動かし、静的コンテンツ公開工程を模倣](https://qiita.com/wf-kenji-ikeda/items/bf6cb1b7a047f562d309)