突然Dockerコンテナ作成してみる話 DockerBuildしよう

## Dockerコンテナを作ろう

さてここまでの作業で、コンテナの中で動作するApplicationを作成、修正することが可能になりました。ここからApplicationをコンテナimageに入れる方法に話題を移します。今回はコンテナビルド用の環境を作成し、コンテナをビルドするところまで一気に駆け抜けます。ちょっと長くなりますが、よろしくお願いします。

## 前提

* Host OSとしてWindows 10/11を利用し適切にupateされていること
* [WSLに関する設定が完了していること](https://sig9.hatenablog.com/entry/2020/07/21/000000)
* 前回までに作成したAPI実装が、GitHubにpushされていること

## 作業スコープ
今回の作業スコープです。広いなー
![ProjectImages-Docker環境.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/8ab05daf-80f1-40e9-4816-a4a5b5bee1ae.png)

### 作業の流れ
作業スコープだけではよくわからないので、今回の作業の流れを整理します。
![ProjectImages-2部構成.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/87a5a2a5-bfc6-faeb-1296-b6930692c09b.png)

1. Podman  環境を作って
1. コンテナをビルドして
1. 実行してみる

って流れです。

### ん？Podman？聞きなれないな？

PodmanはOSSのコンテナビルド、実行ソフトウェアです。のOCIコンテナiamgeを作成し実行します。Dockerからの移行を容易にするため、Dockerコマンドと同じように使えるように配慮されています。

  **Dockerでよくね？** 普通にそう思いませんか？私もそう思っていました。しかし、DockerはあくまでDocker社のものです。彼らに利益が泣ければ存続不可能です。マネタイズが必要です。Docker デスクトップは一定の条件下で有料利用となります。そして弊社・・・・対象なんですよね。なので、**自分の勉強のために** Dockerデスクトップではなく、Podmanを利用してみました。

## 1. Podmanビルド環境を作ろう
では、早速Podmanビルド環境を作っていきます。
![ProjectImages-環境説明.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/965df50a-0c96-d6ff-dc13-be38b6a95199.png)

### 導入環境

|No.|ソフトウェア|備考|
|--|--|--|
|1|Ubunut|ゲストOS verion 20.0.4 LTS|
|2|JDK|SpringBoot ビルド用|
|3|Maven|SpringBoot ビルド用|
|4|podman|Docker(podman)コンテナビルド用|
|5|ansible|環境構築用|
|6|git|Playbook取得用|

### Ubuntu 導入

#### Install
コマンドプロンプトを起動して以下のコマンドを実行する

``` CMD
>winget install "Ubuntu 20.04 LTS"
見つかりました Ubuntu 20.04 LTS [9N6SVWS3RX71] バージョン Unknown
このパッケージは Microsoft Store から提供されています。
…
発行元は、お客様がインストール前に上記の情報を表示し、契約に同意することを必要としています。
使用条件に同意しますか?
[Y] はい  [N] いいえ:Y←入力後Enter
…
インストールが完了しました
```

インストール完了後、Ubuntuを起動する。
![WS000024.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/9ab0b1e9-e54f-204f-507a-7dc452f8a529.jpeg)

#### 初期起動時の設定

* usernameを決定する※1
![WS000025.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/7d3c7499-8db5-66a7-0c5b-056aa97c69d6.jpeg)

* passwordを決定する※2
![WS000026.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/b1e5de23-1593-63a4-6c8f-260f9c150e5f.jpeg)

* passwordを確認する※3


**以後sudo のたびにこのパスワードが必要になりますので、大切に保管（記憶）してください。**



はい。Ubuntuが起動しました。これからはいつでもこのLinux環境を好きなだけ使えます。楽しんでください。

```bash
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: shupeluter ※1
New password: ※2
Retype new password: ※3
…
Installation successful!
```

#### 初期パッケージ導入
起動したUbuntuのターミナルで以下を実行

```
$ sudo apt-get update
[sudo] password for shupeluter:★パスワードを入力する
…
Get:42 http://archive.ubuntu.com/ubuntu focal-backports/multiverse amd64 c-n-f Metadata [116 B]
Fetched 20.9 MB in 6s (3354 kB/s)
Reading package lists... Done

$ sudo apt-get install -y ansible git

```

#### 初期パッケージ動作確認
起動したUbuntuのターミナルで以下を実行

```
$ git --version
$ ansible --version

★それぞれのversion情報が出力されることを確認

```
### local プロビジョニング
コンテナ構築の前提ソフトウェアをAnsibleで導入する

#### Playbook取得

Ubuntuターミナルで以下を実行

```
$ git clone -b podmanbuild https://github.com/atowaito/4qiita.git
…
Unpacking objects: 100% (44/44), 5.96 KiB | 1.19 MiB/s, done.

$ cd 4qiita
```
#### 設定変更

##### ユーザID変更
以下のファイルを編集し、usernameを※1で設定した値に変更する

|No.|ファイル名|変数名|
|--|--|--|
|1.|group_vars/all|USER_NAME|
|2.|main.yml|user|

#### Playbook実行

Ubuntuターミナルで以下を実行

``` ansible
cd ~/4qiita
$ ansible-playbook main.yml --ask-become-pass
BECOME password:★passwordを入力後Enter
... 
TASK [02.installPodman : パッケージインストール] ******************************************************************
changed: [localhost]

PLAY RECAP *********************************************************************************************
localhost                  : ok=9    changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

### 動作確認

以下のコマンドを実行して、環境を確認

```
source ~/.bashrc
★以下バージョン情報が出力されることを確認する。

$ java --version
$ $JAVA_HOME/bin/java --version
$ mvn --version 
$ podman --version
```

## PlayBookの内容をちょっと解説しちゃうよ

今回の手順では環境構築にAnsibleを利用している。Ansinleは人気のプロビジョニングツールで、サーバ等の環境を自動的に構築する。それこそ、1台～数百台まで、全てまったく同じ環境にするのが得意。
たとえばLinuxサーバを初期設定するとなると真っ先にシェルスクリプトを思い出すが、シェルスクリプトを利用する単純な構築自動化では、「2度目実行する場合」想定することが難しい。例えばuser addをシェルで実行した場合、「既にそのuserが存在する場合」を考慮しない限り、該当処理を実行した瞬間に、スクリプトは異常終了してしまう。Ansible等のプロビジョニングツールではこの点が考慮されていて、「何度実行しても同じ結果」になるように**冪等性**がツール側で最大限に考慮されている。

さて、ファイル構成を見てみよう。

### ファイル構成と役割

|ディレクトリ|役割|
|--|--|
|├── ansible.cfg|プレイブックの設定|
|├── group_vars|プロビジョニング対象サーバ全体もしくは種別毎にで共有する変数|
|│   └── all||
|├── inventory|Ansible Playbook自体の稼動環境の定義|
|│   └── hosts|構成対象サーバの情報|
|├── main.yml|playbook本体|
|└── roles|Ansible Roleの格納ディレクトリ|
|&nbsp;&nbsp;&nbsp;&nbsp;├── 01.settings4Java|Java関連の設定|
|&nbsp;&nbsp;&nbsp;&nbsp;│   ├── tasks||
|&nbsp;&nbsp;&nbsp;&nbsp;│   │   └── main.yml||
|&nbsp;&nbsp;&nbsp;&nbsp;│   └── vars||
|&nbsp;&nbsp;&nbsp;&nbsp;│  &nbsp;&nbsp;&nbsp;&nbsp; └── main.yml||
|&nbsp;&nbsp;&nbsp;&nbsp;└── 02.installPodman|Podmanの導入|
<!--
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── tasks||
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│   └── main.yml||
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── vars||
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── main.yml||
-->

### それぞれのファイルで何をやっているのか

#### main.yml

``` yaml:main.yml
- hosts: localhost                 ★対象ホスト
  user: shupeluter                 ★対象ホストにこのユーザでログインして
  become: true                     ★sudo するかな？ するよ！！(true)
  gather_facts: true 
  roles:                           ★roles配下の以下のプレイブックを呼び出すよ
    - 01.settings4Java　           ★後述
    - 02.installPodman　           ★後述

```
ここでのポイントは```become: true``` パッケージのインストールをしなくてはならなかったりするので、管理ユーザで実行したい。ただしWSLでは「パスワードなしsudo」はデフォルトNGになっているので実行時に```--ask-become-pass```を指定してパスワード入力を有効にする必要がある。通常複数台での構築時にはうざすぎるのでsudoresを変更して、パスワードなしsudoを許可する処理を最初に流しておく。

#### roles/01.settings4Java

java関係のパッケージ導入。ユーザ設定実施するためのrole。まずは変数から。注意。ここでは何かしら意味があるように読めるが、ただの変数定義なので、これ自体は何もしない。(あたり前だけど。)

```yaml:vars/main.yml
---
# vars file for roles/01.settings4Java
pkgs:
  - openjdk-17-jdk
  - maven
  - unzip
## 導入パッケージの一覧。リスト型で表現

JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
## 各ユーザに設定するJAVA_HOMEの値。

M2_HOME: /opt/apache-maven-3.8.3
### Maven導入ディレクトリ。。しまったこれも変数で組み立てるべきだ！！

MAVEN_VERSION: 3.8.3
### Mavenのバージョン。バージョン変わるだけでいろいろめんどいので組み立て変数に。そのくせM2_HOMEはそのままトホホ

MAVEN_URL: "https://archive.apache.org/dist/maven/maven-3/{{MAVEN_VERSION}}/binaries/apache-maven-{{MAVEN_VERSION}}-bin.zip"
### Maven導入元URL

ENVS:
    - {name: "JAVA_HOME", value: "{{JAVA_HOME}}"}
    - {name: "M2_HOME", value: "{{M2_HOME}}"}
### ユーザ導入環境変数
```

変数を定義したので、本体を見てみます。

```yaml:task/main.yml
- name: "パッケージインストール"
  apt:
    state: present
    pkg:
      "{{pkgs}}"
```

まずは利用するパッケージの導入。ubuntuはdebian系なので、パッケージマネージャはaptです。注目してほしいのは。```state```このパッケージを**install**するわけではないのが世界観の全てです。「状態」を定義しています。installだと今導入されているか、されていないか考えなければなりませんが、stateであれば「状態」としてpresetにするよと宣言しているわけです。最近はパッケージ関連は引数というかパラメータにリスト型を引き受けてくれるようになりましたが、以前はwith_itemsで回していました。

```yaml:task/main.yml
- name: Unarchive a file that needs to be downloaded (added in 2.0)
  unarchive:
    src: "{{MAVEN_URL}}"
    dest: "/opt"
    remote_src: yes
```

マニュアルからほぼコピペ（笑）{{MAVEN_URL}}のURLから圧縮ファイルを持ってきて、/optに展開してねって内容。

```yaml:task/main.yml
- name: "環境変数の設定"
  lineinfile:
    backup: true
    regexp: "^export {{item.name}}"
    path: "/home/{{USER_NAME}}/.bashrc"
    line: "export {{item.name}}={{item.value}}"
  with_items: "{{ENVS}}"
```

ユーザに環境変数を設定していきます。with_itemsは設定された値（リスト)をループさせて実行するということ。このとき対象の情報はitem変数に格納されます。今回はディクショナリを引数に持つので、値を引き出す際にitem.name item.valueとそれぞれのキー値で引き出しているのが特徴的です。

lineinfileは対象のファイルに```regexp```に該当する**行がなければ**対象ファイルに```line```で指定した値を追加します。対象のファイルの状態が不定な場合に利用します。決まっているのならtemplateが妥当でしょう。今回はJAVA_HOMEとM2_HOMEを入れています。cat ~/.bashrcしてみてください。
また、```backup:true```も見逃せないポイントです。こうしておくことで、ファイルが自動で日付付きバックアップされます。```ls ~/.bashrc*```してみてください。変更前の物がバックアップされているはずです。

あ、変数展開する場合、""で囲むのがAnsibleのお約束なので、気を付けてください。

02.installPodmanの内容は割愛します。ここまでの説明とほぼ同じなので興味があればご覧ください。大したことはやっていません。

## 環境お掃除

いらないものをため込むと、運用にこまるので、利用し終わったPlaybookは作っと消します。

```
$ cd ..
$ rm -fr 4qiita
$ ls 
    ★4qiitaが無くなっていることを確認
```

## 2. Podmanビルド

** さぁビルドを始めよう **

うーんこのネタ、見飽きたな。。

### VS CodeからWSL上のLinuxにアクセスする

次のコマンドをubuntuで実行する[^1]

```
$ mkdir workspace
$ cd workspace
$ code .
```

注意：このコマンドはWSL拡張機能が入っていないと動作しません。あらかじめ導入ください。

### VS Code でターミナルを開く

メニューバーのターミナルから、「ターミナル」⇒「新しいターミナル」を選択します。

右下にターミナルウィンドウが開き、Linux標準のbashが動作していることがわかります。



### Git HubからリポジトリをCloneする。
以下を、ターミナルウィンドウで実行する。

```
$ git clone https://github.com/atowaito/sampleapi.git api★
Cloning into 'api'...
remote: Enumerating objects: 67, done.
remote: Counting objects: 100% (67/67), done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 67 (delta 19), reused 66 (delta 18), pack-reused 0
Unpacking objects: 100% (67/67), 13.65 KiB | 1.52 MiB/s, done.

```
★は[前回のリソース](https://qiita.com/shupeluter/items/3d25603dd7e03404bdf0)を格納したご自身のリポジトリのURLを利用してください

### まずはSpring Applicationをつくる

``` java
$ mvn -f api/spring-server/pom.xml package

[INFO] Scanning for projects...

…
[INFO] 
[INFO] ---------------------< io.swagger:swagger-spring >----------------------
[INFO] Building swagger-spring 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] --- spring-boot-maven-plugin:2.1.16.RELEASE:repackage (default) @ swagger-spring ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.970 s
[INFO] Finished at: 2021-12-08T00:00:42+09:00
[INFO] ------------------------------------------------------------------------
$ ls api/spring-server/target/*.jar
api/spring-server/target/swagger-spring-1.0.0.jar

```

### Dockerfileを作ろう

VSCodeでDockerfileを作成します。

``` Dockerfile
#ベースイメージの指定
FROM docker.io/library/openjdk
#ローカルに生成したファットJARをイメージ内にコピー
COPY api/spring-server/target/swagger-spring-1.0.0.jar /tmp
#コンテナ起動時の自動起動設定を追加
ENTRYPOINT java -jar /tmp/swagger-spring-1.0.0.jar

```

もうまんまですね。（笑

### docker Buildをしよう

```
$ cd ~/workspace
$ podman build . -t mycontainer
```
## 3. Podman Run

### docker runで遊んでみよう

```
$ podman run -p 8080:8080 mycontainer


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

2021-12-07 21:56:32.475  INFO 1 --- [           main] com.qiita.api.Swagger2SpringBoot         : Starting Swagger2SpringBoot v1.0.0 on c01820c35f58 with PID 1 (/tmp/swagger-spring-1.0.0.jar started by root in /)
2021-12-07 21:56:32.477  INFO 1 --- [           main] com.qiita.api.Swagger2SpringBoot         : No active profile set, falling back to default profiles: default
2021-12-07 21:56:33.340  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-12-07 21:56:33.366  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-12-07 21:56:33.366  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.37]
2021-12-07 21:56:33.431  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/v1]     : Initializing Spring embedded WebApplicationContext
2021-12-07 21:56:33.431  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 911 ms
2021-12-07 21:56:33.801  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-12-07 21:56:34.049  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '/v1'
2021-12-07 21:56:34.050  INFO 1 --- [           main] com.qiita.api.Swagger2SpringBoot         : Started Swagger2SpringBoot in 1.859 seconds (JVM running for 2.195)
2021-12-07 21:57:18.434  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/v1]     : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-12-07 21:57:18.434  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-12-07 21:57:18.437  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms
```

さて無事起動したのでブラウザから```http://localhost:8080/v1```にアクセスします。いつもの画面で恐縮ですが。。。
![WS000038.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/837176/565f3ab6-3f91-3300-782d-99ef87f3fd8b.jpeg)

おめでとうございます。Docker Imageを自分で作成し実行することができました。


[^1]: Git Hubの認証にはUser/Passwrdではできません。WindowsであればOSとブラウザがうまく橋渡ししてくれますが、LinuxではToken取得したりわりと面倒です。ブラウザを使って認証できるメリットを考えてVS Codeでターミナルを使います。

## まとめ
今回は盛りだくさんでした

* WSLでUbuntuが使えるようになったよ
* Ansibleで自動構築できるようになったよ
* Dockerファイルを自分で作れるようになったよ
* Podman Buildできるようになったよ
* Podman runできるようになったよ

いやーほんと駆け足ですまん。

### 免責事項

この資料は、本作業を行うために作成したものであり、本作業によって生じた損害については一切の責任を負いません。

[＜＜前の記事　　](https://qiita.com/shupeluter/private/3d25603dd7e03404bdf0) [お品書き](https://qiita.com/shupeluter/items/475b8354047c51e11173#%E3%81%8A%E5%93%81%E6%9B%B8%E3%81%8D)