DockerとKubernetesをハンズオンで体験（Windows WSL2 Ubuntu Docker Kind）

# 本稿の内容
Docker や Kubernetes の全体像を大まかに理解した上で、実際にコンテナオーケストレーションの簡易環境（Windows環境）を作って動作を体験したい方に適した内容です。Windows10以降の環境があれば、環境構築・設定、サンプルの動作確認を含め**特にトラブルがなければ2時間程度**で実施できるくらいの内容になっています。一応前提としては多少 Linux の経験がある方向けです。（※ 特に問題が発生しなければ本稿のコピー＆ペーストだけで実施できますが、何らかのトラブルの場合には Linux の知識が必要になるかもしれません）

Docker や Kubernetes の知識がないネイティブアプリケーション開発者がプロジェクに参画する際に実施したハンズオンレクチャー内容をまとめました。クラウド環境もすぐには使えなかったため、各自のWindowsマシンでコンポーネントの大まかな理解と操作ができるよう WSL2、Ubuntu22.04、Docker、Kind（Kubernetes in Docker） の構成を使っており、コンテナ間でローカルのボリューム共有を行うサンプルで解説しています。できるだけノイズになりそうな操作は省いていますので、**並行して情報検索を心掛けてください**。

冒頭にも書きましたが、一応 **Docker や Kubernetes について一般的な解説など何でも良いので大まかに理解してから始めてください**。
以下 Docker と Kubernetes を説明しているリンクです。他にもっと分かりやすい解説があるかもしれませんが。。
Docker とは：https://www.redhat.com/ja/topics/containers/what-is-docker
Kubernetes とは：https://www.redhat.com/ja/topics/containers/what-is-kubernetes

WSL の概要リンクです。
Windows Subsystem for Linux (WSL) とは何ですか。：https://learn.microsoft.com/ja-jp/windows/wsl/faq

# Windows WSL2、Docker、Kind、kubectlの導入
メンバー達は Windows 11 と Windows 10 を使用していましたが、全ての手順で差異はありませんでした。
必要なコンポーネントの導入についてはこのリンク記事（[今更ですが Docker Desktop から...](https://qiita.com/SHIRANO/items/deff9493a5345fc7040d)）の手順を参照してください。
今回 ※ Ubuntu は 22.04 を使用しています。

上記リンクの **kind (Kubernetes in Docker) のインストール** まで完了したら本稿に戻ってください。

# テキストファイル編集
知ってるようで知らない人も多いので今一度確認してほしいのですが、、
Docker や Kubernetes のユーザー設定ファイルは基本的に全てテキストファイルです。後々の不要なトラブルを避けるために、YAMLファイルなどテキストファイルの改行コードは **Linux標準の LF のみでファイル保存**するよう心掛けてください。**Windows のメモ帳は改行コードが Windows標準の CRLF で保存される**ため、改行コードを選択できるエディターアプリ（サクラエディタなど）の使用をお勧めします。Linux の中で vi を使うのであれば問題ありませんが、、折角 WSL2 で ファイルアクセスが容易ですので GUI のエディターを使いたいところです。
Windows WSL2 環境ではファイルエクスプローラーから Ubuntu のファイルに直接アクセスできるため大変便利です。<font color="Orange">（ファイルエクスプローラーに &yen;&yen;wsl$ と直接入力すればアクセスできます。**この記事のコピーではなく、円円wslドル と直接手入力してください**。ショートカットを作っておくと便利です）</font>
さらに、テキストファイル中の日本語を、コメントとしてではなくデータとしてテキストファイルに記述する場合は、文字コードを UTF-8 BOM なしで保存してください。

# 自分でビルドしたコンテナを run させる
書き込みと読み取りそれぞれの役割を担当するコンテナ2つを run させてローカルに作成したボリューム上のファイルを共有するサンプルを動かしてみましょう。

以降の手順で作成するファイルは、全て同じディレクトリに置き、コマンドもそのディレクトリで実行してください。特に意図がなければ、**/home/ユーザー名** の下にファイルを作成してください。

Docker コンテナをビルドするために **Dockerファイル**と呼ばれる定義ファイルを作成します。コンテナを2つ作るため2つの Dockerファイルを作ります。
以下の Dockerファイルでは ubuntu のベースコンテナイメージにユーザーシェルを組み込み、コンテナ run 時にはそのユーザーシェルが呼び出されるように定義しています。
~~~
# ファイル名：mywriter.txt
FROM ubuntu

ADD mywriter.sh /mywriter.sh
RUN chmod +x /mywriter.sh

CMD ["/mywriter.sh", "/mywork"]
~~~
~~~
# ファイル名：myreader.txt
FROM ubuntu

ADD myreader.sh /myreader.sh
RUN chmod +x /myreader.sh

CMD ["/myreader.sh", "/mywork"]
~~~
上記 Dockerファイル中に定義しているユーザーシェル2つ（現在時刻をファイルに書き込むシェルと、ファイルの内容を表示するシェル）を作成します。**以下のシェルは改行コードを必ず LF のみで保存してください。改行コードが CRLF の場合は動作しません。**
~~~
# ファイル名：mywriter.sh
#!/bin/bash

export TZ=JST-9
mkdir $1
count=0

while true
do
  count=`expr $count + 1`
  if [ $count -gt 30 ]; then
    date +"%Y/%m/%d %p %I:%M:%S" > $1/log.txt
    count=0
  else
    date +"%Y/%m/%d %p %I:%M:%S" >> $1/log.txt
  fi
  sleep 1
done
~~~
~~~
# ファイル名：myreader.sh
#!/bin/bash

export TERM=linux

while true
do
  tail -n 5 $1/log.txt
  sleep 1
done
~~~

上記の必要なファイルを作成したらコンテナをビルドします。
~~~
# コンテナイメージのビルド
docker build -t mywriter:001 -f mywriter.txt .
docker build -t myreader:001 -f myreader.txt .

# ローカルに存在するコンテナイメージの表示
docker images

REPOSITORY     TAG       IMAGE ID       CREATED       SIZE
mywriter       001       a900d412314a   1 hours ago   77.8MB
myreader       001       5669d29bd494   1 hours ago   77.8MB
~~~

コンテナイメージがビルドされました。
ではコンテナを run させてみましょう。

~~~
# 事前に共有用のボリュームの作成
docker volume create myvolume

# バックグラウンドオプションで writerコンテナを run
docker run -d --rm --volume myvolume:/mywork mywriter:001

# フォアグラウンドオプションで readerコンテナを run
docker run -it --rm --volume myvolume:/mywork myreader:001
~~~
正常に動作すれば writerコンテナが1秒毎に書き込んでいる現在時刻を、readerコンテナが標準出力に表示しているのが確認できます。

コンテナを run させる際のオプションは必要最小にしています。各コンテナで共通のボリュームをマウントしているっぽいとか、-d、-it の違いとか、最後にコンテナイメージ名を指定しているとか、これまでコンテナに触れていない人でも何となく想像できると思います。オプションの詳細はぜひご自身で調査してください。


動作しているコンテナを表示してみます。フォアグラウンドで動作している **readerコンテナは Ctrl + c で終了させてください**。
~~~
# コンテナの表示
docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS     NAMES
4a9658e6abe0   mywriter:001   "/mywriter.sh /mywork"   7 minutes ago   Up 7 minutes             flamboyant_kapitsa
~~~

writerコンテナがまだ動いているのが分かります。
<font color="Blue">余談ですが、、コンテナ run 時には --name オプションでコンテナに任意の名前を付けられますが、省略すると上記のように Dockerエンジンが名前を割り振ります。形容詞と科学者などの人名の組み合わせでその環境下でユニークになるよう命名されます。実際の値はこの（[Dockerのソースコード](https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go)）で確認できます。開発者の遊び心を感じますね。。</font>

writerコンテナを終了させます。
~~~
# コンテナの停止
docker stop flamboyant_kapitsa
    または
docker stop 4a9658e6abe0

# コンテナの表示
docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS
~~~
上記のように特定のコンテナを操作するようなコマンドでは、コンテナ名、またはコンテナIDを指定します。
docker ps コマンドでコンテナが全て終了した事を確認します。

<font color="Orange">コンテナはいつ終了するのでしょうか？
コンテナイメージをビルドする際に使用する Dockerファイル中の CMD（または ENTRYPOINT 違いは検索してください）に記述されたコマンド（コマンドは1個のみ記述が可能）が完了した時点で run されたコンテナは自動的に終了します。または docker stop、docker rm -f などのコマンドで強制的にコンテナを終了させる事も可能です。</font>


# Kubernetes でコンテナを動作させる
続いてコンテナオーケストレーション環境での動作を確認していきます。現在コンテナの運用は Docker などのコンテナエンジンだけではなく、Kubernetes などのコンテナオーケストレーション環境での運用が一般的です。本稿では Kubernetes の簡易検証などに利用される Kind（Kubernetes in Docker）を使用します。

何度もくどいのですが、Kubernetes 全般や、コンテナと Pod の関係など一般的な解説で大まかにでも理解してから進めてください。

まず Kubernetes のクラスターを作成します。
~~~
# 最小構成でクラスターを作成
kind create cluster
~~~
上記コマンドでコントロールプレーンと呼ばれるマスターノードのみで構成されるクラスターが生成されます。通常はマスターノードと、コンテナを動作させるためのワーカーノードを持つクラスター構成にするのが一般的ですが、Kind のような簡易環境ではマスターノードのみでもコンテナの検証は可能です。また kind のクラスターをデフォルトで作成した場合、クラスターの名前は「kind」になりますが、クラスターを識別したいような場合は任意の名前を指定してクラスターを生成します。

<font color="Blue">現在クラウドベンダーが商用で公開している環境では、マスターノードは公開せずに、ワーカーノードのみをユーザーに操作させるような形態が一般的です。</font>

ノードを確認します。
~~~
# 作成されたノードの確認
kubectl get nodes

NAME                 STATUS   ROLES           AGE   VERSION
kind-control-plane   Ready    control-plane   10m   v1.27.3
~~~
使用しているコマンドが kubectl になっています。ノードを生成したり削除するのは kind コマンド、既に存在するクラスタ内のノードの操作や Pod の操作などは Kubernetes のコマンドである **kubectl コマンド**を使用します。実際のクラウド環境を操作する場合はいろいろなコマンドが存在するのでレイヤーやコンポーネントを意識しないと混乱してしまうため各コマンドの意味を理解するよう心掛けてください。
Kubernetes環境において kubectl コマンドは多用するためオプションや使用方法を確認してください。

では作成したクラスターに Pod をデプロイしてみましょう。
Pod をデプロイするための YAML を定義します。1つの Pod に 2つのコンテナを含む記述になっています。Docker 環境のみで動作させた時とは、volume 辺りの定義も YAML に記述しているのが分かります。
YAML記述を複雑に感じて苦手意識を持つ人もいるみたいですが、今回はシンプルで分かりやすい内容になっているので大まかに目を通していただき何とかこのまま最後まで実施してください。。

~~~
# ファイル名：mypod.yml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  restartPolicy: Never

  volumes:
  - name: myvolume
    emptyDir: {}

  containers:
  - name: mr1
    image: myreader:001
    volumeMounts:
    - name: myvolume
      mountPath: /mywork

  - name: mw1
    image: mywriter:001
    volumeMounts:
    - name: myvolume
      mountPath: /mywork
~~~
上記YAML中の kind: Pod の記述は、Kind（Kubernetes in Docker）ではなく、種類の意味であり、一般的な Kubernetes の YAML 記述になっています。

Pod を起動する前にクラスター内へコンテナイメージを転送しておく必要があります。

~~~
# 2つのコンテナイメージをクラスターに転送する
kind load docker-image mywriter:001 myreader:001
~~~

ではいよいよ Pod を動かしてみます。

~~~
# Pod の YAML定義をクラスターに適用する
kubectl apply -f mypod.yml
~~~
上記の YAML適用で問題がなければクラスター内で（マスターノード上で） Pod が動作しています。
Pod の状態を確認します。
~~~
# Pod の表示
kubectl get pods -o wide

NAME    READY   STATUS    RESTARTS   AGE   IP           NODE                 NOMINATED NODE   READINESS GATES
mypod   2/2     Running   0          10s   10.244.0.5   kind-control-plane   <none>           <none>
~~~

2つのコンテナを含む Pod が Running になりました。

Pod内のコンテナが動作しているかを確認するため readerコンテナのログを表示します。
~~~
# Pod の名前とコンテナの名前を指定してログを表示
kubectl logs mypod -c mr1
~~~

Dockerだけの環境と同様に、writerコンテナがファイルに書き込んだ内容を readerコンテナが標準出力に出しているのを確認できると思います。
<font color="Orange">もし timeoutでログが表示できない場合は、WSL2 を完全に停止させることで解決する事があります。以降に記述している Pod の削除、クラスターの削除を行い、Ubuntu を exit して Windows のコマンドライン（管理者で実行）で、wsl --shutdown を実行してから、Ubuntu を再起動し、再度導入以降の手順を試行してみてください。</font>

Pod を削除します。
~~~
# Pod の削除
kubectl delete pod mypod

# Pod の表示
kubectl get pods -o wide
~~~

先ほどと同じ Pod を表示するコマンドで削除された事が分かります。
最後に kindコマンドでクラスターも削除します。
~~~
# クラスターの削除
kind delete cluster
~~~

以上で本稿の全ての手順は終了です。

Docker や Kubernetes の実際の動きが何となくでも理解できたとともに、色々と疑問に感じた事や、さらにもっと試したい事が出てきたのではないでしょうか。
Pod はいつ終わるの？とか、コンテナの起動順序の制御は？とか、勝手に再起動されるの？とか、このケースは Pod に向いてないのでは？（Job が良いのでは？）とか、コンテナ間で共有メモリーとか使えるの？とか、ソケット通信アプリは使えるの？（ポートのサービス定義は本稿では入れられなかったですね・・）などなど。。でも検索すれば大抵の事は見つかると思います。

# まとめ
実際のプロジェクトではクラウド環境を使う事になると思いますが、コンテナイメージの作成、コンテナの単体動作、Kubernetes 上の Pod での動作などコンテナオーケストレーション環境の基本的な仕組みや流れを初心者が学習したい時に、いきなりクラウド環境で試してしまうと、さらにクラウドの知識を要求されてしまい、自分で入力しているコマンドがどのレイヤーのどのコンポーネントに作用しているのか理解が追い付かずに混乱しているメンバーを度々見かけてきました。
コンテナ、Kubernetes にあまり触れるタイミングがなかった方々の第一歩として、大まかな流れを理解するために本稿がその一助となれば幸いです。

本稿では必要最低限のコマンドのみを記述していますが、コマンドの意味や関連コマンドもぜひご自身で調査して実際に試してください。