Dockerコンテナを作ろう
さてここまでの作業で、コンテナの中で動作するApplicationを作成、修正することが可能になりました。ここからApplicationをコンテナimageに入れる方法に話題を移します。今回はコンテナビルド用の環境を作成し、コンテナをビルドするところまで一気に駆け抜けます。ちょっと長くなりますが、よろしくお願いします。
前提
- Host OSとしてWindows 10/11を利用し適切にupateされていること
- WSLに関する設定が完了していること
- 前回までに作成したAPI実装が、GitHubにpushされていること
作業スコープ
作業の流れ
作業スコープだけではよくわからないので、今回の作業の流れを整理します。
- Podman 環境を作って
- コンテナをビルドして
- 実行してみる
って流れです。
ん?Podman?聞きなれないな?
PodmanはOSSのコンテナビルド、実行ソフトウェアです。のOCIコンテナiamgeを作成し実行します。Dockerからの移行を容易にするため、Dockerコマンドと同じように使えるように配慮されています。
Dockerでよくね? 普通にそう思いませんか?私もそう思っていました。しかし、DockerはあくまでDocker社のものです。彼らに利益が泣ければ存続不可能です。マネタイズが必要です。Docker デスクトップは一定の条件下で有料利用となります。そして弊社・・・・対象なんですよね。なので、自分の勉強のために Dockerデスクトップではなく、Podmanを利用してみました。
1. Podmanビルド環境を作ろう
導入環境
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
コマンドプロンプトを起動して以下のコマンドを実行する
>winget install "Ubuntu 20.04 LTS"
見つかりました Ubuntu 20.04 LTS [9N6SVWS3RX71] バージョン Unknown
このパッケージは Microsoft Store から提供されています。
…
発行元は、お客様がインストール前に上記の情報を表示し、契約に同意することを必要としています。
使用条件に同意しますか?
[Y] はい [N] いいえ:Y←入力後Enter
…
インストールが完了しました
初期起動時の設定
以後sudo のたびにこのパスワードが必要になりますので、大切に保管(記憶)してください。
はい。Ubuntuが起動しました。これからはいつでもこのLinux環境を好きなだけ使えます。楽しんでください。
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ターミナルで以下を実行
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の格納ディレクトリ |
├── 01.settings4Java | Java関連の設定 |
│ ├── tasks | |
│ │ └── main.yml | |
│ └── vars | |
│ └── main.yml | |
└── 02.installPodman | Podmanの導入 |
それぞれのファイルで何をやっているのか
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。まずは変数から。注意。ここでは何かしら意味があるように読めるが、ただの変数定義なので、これ自体は何もしない。(あたり前だけど。)
---
# 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}}"}
### ユーザ導入環境変数
変数を定義したので、本体を見てみます。
- name: "パッケージインストール"
apt:
state: present
pkg:
"{{pkgs}}"
まずは利用するパッケージの導入。ubuntuはdebian系なので、パッケージマネージャはaptです。注目してほしいのは。state
このパッケージをinstallするわけではないのが世界観の全てです。「状態」を定義しています。installだと今導入されているか、されていないか考えなければなりませんが、stateであれば「状態」としてpresetにするよと宣言しているわけです。最近はパッケージ関連は引数というかパラメータにリスト型を引き受けてくれるようになりましたが、以前はwith_itemsで回していました。
- 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に展開してねって内容。
- 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.
★は前回のリソースを格納したご自身のリポジトリのURLを利用してください
まずはSpring Applicationをつくる
$ 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を作成します。
#ベースイメージの指定
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
にアクセスします。いつもの画面で恐縮ですが。。。
おめでとうございます。Docker Imageを自分で作成し実行することができました。
まとめ
今回は盛りだくさんでした
- WSLでUbuntuが使えるようになったよ
- Ansibleで自動構築できるようになったよ
- Dockerファイルを自分で作れるようになったよ
- Podman Buildできるようになったよ
- Podman runできるようになったよ
いやーほんと駆け足ですまん。
免責事項
この資料は、本作業を行うために作成したものであり、本作業によって生じた損害については一切の責任を負いません。
-
Git Hubの認証にはUser/Passwrdではできません。WindowsであればOSとブラウザがうまく橋渡ししてくれますが、LinuxではToken取得したりわりと面倒です。ブラウザを使って認証できるメリットを考えてVS Codeでターミナルを使います。 ↩