minikube で始める kubernetes 学習

# はじめに
仕事で k8s を触ることになりそうなので基礎知識をとりあえず学習してみる。
触るのは client 側だけだけど minikube 使ってローカル環境でクラスターを立ててみたいと思う。

ってことで学びながら　Zenn のスクラップに書いた内容を Qiita にまとめておく。

Zenn の記事
https://zenn.dev/sakana1115/scraps/34917d4a56f6e9

# kubernetes とは
コンテナ化されたアプリケーションを管理するためのプラットフォーム
https://kubernetes.io/ja/docs/concepts/overview/

前提として、社内などのたくさんのアプリケーションがあるような状況を想定しているっぽい
## メリット
### コンテナ管理の簡略化
複数のコンテナ管理を自動化して、アプリケーションのデプロイ、変更の適用、スケールアップやスケールダウンを行ってくれる。コンテナがダウンしてしまったり、間違えて削除してしまったりした場合も自動で回復させることが可能。

これを実現させているのが、**宣言的設定**である。
宣言的設定とは、あるべき状態だけが定義され、その状態にするための手順は指示しない設定のこと。これによりあるべき状態から外れた場合にその状態に戻そうとしてくれる。宣言的設定と対照的なのが、命令的設定で操作やコマンドを記述した設定であり、Dockerfile がこれにあたる。

### 物理サーバーとアプリケーションの間の抽象レイヤー
物理サーバーとアプリケーションの間に抽象レイヤーとして入り、両者の間の依存関係を断ち切ってくれる (アプリケーションはk8sにだけ依存する)

**複数の物理/仮想サーバーを束ねる**ことが出来き、複数のサーバーで複数のコンテナを動かすことができる。さらに物理サーバーが1つダウンしても他のサーバーでカバーされ、**アプリケーション開発者は物理サーバーの構成やメンテナンス、障害を気にする必要がなくなる**。
![](https://storage.googleapis.com/zenn-user-upload/ff693fb7c0c7-20230610.png)

## 参考
k8s を使う理由は下記の説明がわかりやすかった
https://zenn.dev/esaka/articles/2d117655af1f03cf2444
https://cybozu.github.io/introduction-to-kubernetes/introduction-to-kubernetes.html

# 前提知識
https://kubernetes.io/ja/docs/concepts/overview/components/
![](https://storage.googleapis.com/zenn-user-upload/e46b0772c303-20230610.png)

## コントロールプレーン (Control Plane)
Kubernetesクラスタのマスターコンポーネント(kube-apiserver, kube-controller-manager, kube-scheduler, etcdなどを実行) で、クラスタの状態を管理・制御を行う。マスターノード上で実行される。

## ノード (Node)
クラスタ内の個々の物理または仮想マシンを指し、コンテナ化されたアプリケーションの実行環境
- ワーカーノード: アプリケーションのコンポーネントであるPodをホストする
- マスターノード: クラスタ内のワーカーノードとPodを管理する Control Plane を実行する

一般的にどのNodeでどのアプリケーションを動かすかなどを指定することはなく、**アプリケーション側は実行環境であるNodeを意識することはない** (どこのNodeで稼働しているかは確認できる)


## クラスタ (Cluster)
Kubernetes クラスタは、コンテナ化されたアプリケーションを実行するためのノードの集合。Kubernetes を実行しているときは、クラスタが実行される。
クラスタは、1 つのControl Planeと、1 つ以上のNodeで構成される。この2つは物理レイヤの話しであり、クラスタはこの上のレイヤー

これ以降出てくる用語はクラスター内部の話。
![](https://storage.googleapis.com/zenn-user-upload/b2141adb94c4-20230610.png)

## ポッド (Pod)
Kubernetesの最小実行単位であり、1つ以上のコンテナをまとめて実行するためのひとつの空間。Pod内には複数のコンテナを配置することができる。

## サービス (Service)
クラスタの内部および外部からPodとの通信を容易に行うためにエンドポイントの提供を行ってくれる機能。クラスター内のPod (コンテナ) に対して一意なネットワークアクセスを提供する抽象化されたリソース。
通常、NodeのIPアドレスは一定ではないが、サービスによってアプリケーションの安定したネットワーク接続を実現する。

## デプロイメント (Deployment)
アプリケーションのデプロイとアップデートを管理するためのKubernetesリソース
指定された数のレプリカセットを作成し、Podのデプロイとスケーリングを簡素化する。アプリケーションのバージョンアップやロールバックなどの操作もデプロイメントを介して行われる。

またk8s の特徴である**宣言的設定**を実現する。
(宣言的設定とは、あるべき状態だけが定義され、その状態にするための手順は指示しない設定のこと。これによりあるべき状態から外れた場合にその状態に戻そうとしてくれる）

## ネームスペース (Namespace)
クラスタ内のリソースを論理的に分割するための仕組み。異なるチームやプロジェクト、環境などを区別するために使用される。


# ツール
学習に必要なツールたち
| ツール名 | 用途 |
| --- | :--- |
| kubectl | Kubernetesのコマンドラインツール (CLI) <br> Kubernetesと接続してkubernetesを操作するツール |
| minikube | Kubernetesをローカルで実行するツール |

通常、k8s環境はサーバーなどで大規模な環境に構築するもの。それをローカルなどの小規模な環境でk8s環境を構築できるようにしたものが`minikube`。
Dockerなどの仮想環境上でシングルノードのKubernetesクラスターを構築する。

## ツールたちの関係性
簡単に書くと下記のような関係性
PC/Mac -> [kubectl] -> minikube (Docker上で動く) 
![](https://storage.googleapis.com/zenn-user-upload/87742cbdd790-20230610.png)
> 引用元: https://qiita.com/fukumasa/items/884eadd2694de19d64ff
上記だとVMになっているがMacで立ち上げたDocker container 内で minikube は動作していると思う。

(ここでは触れないが、ややこしいのはnode上のクラスターの上でさらにDocker containerが動作していること)

## ツールのインストール
大前提としてDockerがインストールされていること

[kubectl](https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/) をインストール
```sh
brew install kubectl
```

[minikube](https://minikube.sigs.k8s.io/docs/start/) をインストール
```sh
brew install minikube
```

ちなみに 自分の環境では Mac で Docker 動かすのに使っている [Rancher Desktop](https://rancherdesktop.io/) に入っているので kubectl のインストールは不要だった。

# minikube を試す
前提知識を学んだ後で、やっと最初に戻ってチュートリアル。
https://kubernetes.io/ja/docs/setup/learning-environment/minikube/

# クラスタ構築
通常の k8s だと複数の Node 上にクラスタを構築するが、minikube では一つの Node 上に構築する (=シングルノード)
## 構築
下記のコマンドでクラスタが構築される (初回はminikubeのdockerコンテナをbuildするので時間かかる)
```sh
minikube start
```
Docker が入っていれば VM は Docker が選ばれるらしいが、一応確認 -> いるのでOK
```sh
$ docker ps
CONTAINER ID   IMAGE                                 COMMAND                   CREATED         STATUS         PORTS                                                                                                                                  NAMES
6c854c5670cb   gcr.io/k8s-minikube/kicbase:v0.0.39   "/usr/local/bin/entr…"   8 minutes ago   Up 8 minutes   127.0.0.1:49157->22/tcp, 127.0.0.1:49156->2376/tcp, 127.0.0.1:49155->5000/tcp, 127.0.0.1:49154->8443/tcp, 127.0.0.1:49153->32443/tcp   minikube
```
ちゃんとクラスタが動作しているかも確認 -> RunningになってるのでOK
```sh
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
```

## 停止
停止するのは下記のコマンド
```sh
minikube stop
```
止めるとStoppedになることも確認
```sh
$ minikube status
minikube
type: Control Plane
host: Stopped
kubelet: Stopped
apiserver: Stopped
kubeconfig: Stopped
```
もう一度`minikube start`すれば、再度クラスタが立ち上がる。

# クラスタを操作
minikube によってクラスタが構築されたので、次にクラスタに接続＆操作する。
先述した通り、kubectl を用いてクラスタを操作する。

## 接続するクラスタの設定
なお本来は接続するクラスタを kubectl で設定する必要があるが、`minikube start`によってその設定も自動で行われ、設定はkubeconfig (`~/.kube/config`) に書き込まれている。

下記で設定された内容を確認可能 (複数のクラスタに接続したい場合は、[こちら](https://kubernetes.io/ja/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)に従ってcontext を追加する)
```sh
$ kubectl config view
```

念のため、現在の context が minikube になっていることを確認
```sh
$ kubectl config current-context
minikube
```

ちなみに context の切り替えは下記で可能 (minikube ところに 切り替え先のcontext 名をいれる)
```sh
$ kubectl config use-context minikube
Switched to context "minikube".
```

# クラスタの構成を確認
接続するクラスタの設定が完了したので、kubectl でクラスタを構成する Nodeを確認してみる。

`minikube start`によってクラスタを構築すると下記のようなイメージでクラスタが作成される。
![](https://storage.googleapis.com/zenn-user-upload/a8a95358fd16-20230611.png)

minikube はシングルノードのためクラスタを構成する Node は一つであり、マスターノードも兼ねる。
下記でコマンドで構成するNodeを確認できるので確認してみると 、
```sh
$ kubectl get node              
NAME       STATUS   ROLES           AGE     VERSION
minikube   Ready    control-plane   4h59m   v1.26.3
```
1つだけしか表示されないのでシングルノードだとわかる

## マルチノード化
ここはやらなくてもOKなので参考までに。
minikube の標準ではシングルノードだけれどもワーカーノードを増やしてマルチノード化することもできる。下記の例ではワーカーノードを2つにしている
```sh
minikube node add --worker 2
```
Nodeを確認してみると2つに増えている
```sh
$ kubectl get node            
NAME           STATUS   ROLES           AGE     VERSION
minikube       Ready    control-plane   4h59m   v1.26.3
minikube-m02   Ready    <none>          7s      v1.26.3
```
下記のようにクラスタを構成するワーカーノードが2つになる。これによって k8s のメリットである**複数のサーバーを束ねることができている**のがわかる。
![](https://storage.googleapis.com/zenn-user-upload/65c7158b0d32-20230611.png)

### 参考
本来、Node は別のマシンで動くはずなのに「**なぜ同じMac上で複数 Node を実現できるの？**」となると思うが、minikube では Node 自体がコンテナ上で動いており、複数の Node コンテナを立てることでマルチノードを実現できている。

試しに稼働中の Docker コンテナを確認してみると、2つのコンテナが稼働していることがわかる
```sh
$ docker ps
CONTAINER ID   IMAGE                                 COMMAND                   CREATED        STATUS        PORTS                                                                                                                                  NAMES
a3a21d3ffd77   gcr.io/k8s-minikube/kicbase:v0.0.39   "/usr/local/bin/entr…"   9 hours ago    Up 9 hours    127.0.0.1:49167->22/tcp, 127.0.0.1:49166->2376/tcp, 127.0.0.1:49165->5000/tcp, 127.0.0.1:49164->8443/tcp, 127.0.0.1:49163->32443/tcp   minikube-m02
6c854c5670cb   gcr.io/k8s-minikube/kicbase:v0.0.39   "/usr/local/bin/entr…"   14 hours ago   Up 14 hours   127.0.0.1:49162->22/tcp, 127.0.0.1:49161->2376/tcp, 127.0.0.1:49160->5000/tcp, 127.0.0.1:49159->8443/tcp, 127.0.0.1:49158->32443/tcp   minikube
```

# Deployment の作成
k8s では 各アプリケーションは Pod という単位でデプロイされ、実行される。 「**どんな Pod を用意するか**」を決めるためのものが Deployment であり、Deploymentに記述した Pod が自動で起動する。
このような宣言的設定 (あるべき姿の定義) を Deployment で行う。

## Deployment の作成 (manifestを使用しないケース)
まずはシンプルに、[チュートリアル](https://kubernetes.io/ja/docs/setup/learning-environment/minikube/)に従い Deployment を作成してみる。

はじめに Pod が起動していないことを確認
```sh
$ kubectl get pod                         
No resources found in default namespace.
```
Deployment を作成 
```sh
$ kubectl create deployment hello-minikube --image=kicbase/echo-server:1.0
deployment.apps/hello-minikube created
```
これは`--image=`で指定したコンテナイメージの Pod を`hello-mminikube`という名前で用意するという意味。(チュートリアルの image だとうまくいかないので[こちら](https://minikube.sigs.k8s.io/docs/start/)のものを用いた)

再度、Pod を確認すると、新たに Pod が起動していることを確認できる
```sh
$ kubectl get pod                                                         
NAME                              READY   STATUS    RESTARTS   AGE
hello-minikube-77b6f68484-8pvbw   1/1     Running   0          1s
```
この時点で下記のような状態になる
![](https://storage.googleapis.com/zenn-user-upload/f4c530fb64de-20230611.png)

# Service の作成
無事 Pod が立ち上がったが、このままだとクラスタの中からしかアクセスできない。そこで Pod との通信を容易に行うためにエンドポイントの提供してくれる Service 作成する。

下記コマンドで hello-minikube という名前の Deployment を外部に公開する Service を作成することができる。`--type=NodePort`とは Node のIPアドレスを使って公開すること指している ([参考](https://cloud.google.com/kubernetes-engine/docs/concepts/service?hl=ja#service_of_type_nodeport))。
```sh
$ kubectl expose deployment hello-minikube --type=NodePort --port=8080    
service/hello-minikube exposed
```
このService によって Deployment によって定義された Pod のポート (8080) が公開され、外部からアクセスが可能になる
![](https://storage.googleapis.com/zenn-user-upload/9043c2ac4126-20230611.png)

# Port forward 設定
実はまだローカル環境からはアクセスできない。
localhost のあるポートへ届いたパケットを 先ほど公開した Pod の 8080 へ転送するように設定する必要がある。下記コマンドでポート転送できる (今回は locahost:7080 を 8080 へ繋げる)
```sh
$ kubectl port-forward service/hello-minikube 7080:8080

Forwarding from 127.0.0.1:7080 -> 8080
Forwarding from [::1]:7080 -> 8080
```
この状態のまま、別の terminl を開いて curl で導通確認する
```sh
$ curl http://localhost:7080/ 
Request served by hello-minikube-77b6f68484-8pvbw

HTTP/1.1 GET /

Host: localhost:7080
Accept: */*
User-Agent: curl/7.88.1
```
response が返ってきていれば、導通確認はOK

これで立ち上げた Pod を公開することができた。

# 掃除 (Service, Deployment の削除)
最後に作成した Service, Deployment, Pod を削除させておく。

Service の削除
```sh
$ kubectl delete services hello-minikube
service "hello-minikube" deleted
```
Deployment の削除
```sh
$ kubectl delete deployment hello-minikube
deployment.apps "hello-minikube" deleted
```
Deployment が削除されたことで 定義されていた Pod も停止する
```sh
$ kubectl get pod
No resources found in default namespace.
```
作成した Service も削除されている
```sh
kubectl get svc
``` 

# マニフェストの適用
上記で Deployment および Service を作成したが、通常はマニフェストと呼ばれる yaml ファイルで定義しておき、それを使ってリソースを作成することができる。

## マニフェストの定義
マニフェストは下記のように定義される
**deployment.yaml**
```yaml
apiVersion: apps/v1
# 定義の種類
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  # 指定した条件に一致する Pod をこの Deployment が管理する (ここではlabelが一致すること)
  selector:
    matchLabels:
      app: nginx
  # レプリカ数: レプリカとは複数のことで、ここの数字分の Pod を常に起動させる
  replicas: 3
  # 作成する Pod の定義
  template:
    metadata:
      labels:
        app: nginx
    spec:
      # Pod で起動するコンテナの定義 (Docker hubから取ってくる image を記載する)
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80
```
**service.yaml**
```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  # 指定したラベルにマッチする Pod に Service へのアクセスが割り振られる
  type: NodePort
  selector:
    app: nginx
```
## マニフェストの適用
Deployment の定義を適用
```sh
$ kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment created
```
Service の定義を適用
```sh
$ kubectl apply -f service.yaml
service/nginx-service created
```

## 適用されたリソースを確認
### Pod の確認
上記のマニフェストでは Pod のレプリカ数を3としているので、起動中の Pod を確認すると3つ起動しているのを確認することができる。
```sh
$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-699bfcdcb6-7rf4q   1/1     Running   0          78s
nginx-deployment-699bfcdcb6-h9ntg   1/1     Running   0          78s
nginx-deployment-699bfcdcb6-xh7tl   1/1     Running   0          78s
```

ちなみに Pod を一つ削除してみても
```sh
kubectl delete pod nginx-deployment-699bfcdcb6-xh7tl
```
Deployment に定義された状態に戻そうと**自己復旧**するので、すぐに別の Pod が立ち上がる
```sh
$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-699bfcdcb6-7rf4q   1/1     Running   0          112s
nginx-deployment-699bfcdcb6-h9ntg   1/1     Running   0          112s
nginx-deployment-699bfcdcb6-tzcks   1/1     Running   0          13s
```

### Deployment の確認
```sh
$ kubectl get deployment                       
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           10m
```
### Service の確認
```sh
$ kubectl get service                          
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP        20h
nginx-service   NodePort    10.105.159.180   <none>        80:32563/TCP   10m
```

## 導通確認
ポート転送を行い
```sh
$ kubectl port-forward service/nginx-service 7080:80
Forwarding from 127.0.0.1:7080 -> 80
Forwarding from [::1]:7080 -> 80
Handling connection for 7080
```
上記の状態で下記のURLへアクセスする
http://localhost:7080/

下記が表示されれば、接続できている
![](https://storage.googleapis.com/zenn-user-upload/646a12a2e171-20230611.png)


作成したリソースのイメージは下記の通り。
Deployment のレプリカセットから3つの Pod が起動する。また今回の場合、Pod が起動する Node の指定を特にしていないので、2つの Node に分かれて配置されている。`kubectl describe pod`で確認可能
Pod への通信は Service によって適切な Pod へルーティングされる。
![](https://storage.googleapis.com/zenn-user-upload/4b9faf592b46-20230611.png)

ちなみに pod のログは下記コマンドで確認できる
```sh
kubectl logs -p {pod name} -n {namespace}
```

# Namespace の作成
クラスタ内のリソースを論理的に分割するための仕組みである Namespace を作成する。
複数のチーム・プロジェクトにまたがる多くのユーザーがいる環境で、クラスタを分けて管理運用した場合に使われる仕組みであり、小規模プロジェクトでは不要っぽい
https://kubernetes.io/ja/docs/concepts/overview/working-with-objects/namespaces/

## Namespace の作成
下記コマンドで作成できる。(もちろんマニフェストでも作成可能)
```sh
$ kubectl create namespace sample
namespace/sample create
```
作成した Namespace を確認すると、さきほど作成した sample があるのがわかる
```sh
$ kubectl get namespace
NAME                   STATUS   AGE
default                Active   21h
kube-node-lease        Active   21h
kube-public            Active   21h
kube-system            Active   21h
kubernetes-dashboard   Active   19h
sample                 Active   66s
```

## Namesapce ごとにリソース作成
Namespace ごとにマニフェストを適用させることができる。`-n {namespace}`で指定
(マニフェストに適用させる Namespace を指定しておくこともできる -> 通常こっちの方法）
```sh
$ kubectl apply -f deployment.yaml,service.yaml -n sample
deployment.apps/nginx-deployment created
service/nginx-service created
```
論理的に分割されており、default の Namespace からは作成した Pod を確認できない
```sh
$ kubectl get pod   
No resources found in default namespace.
```
Namespace を指定してあげると見れる
```sh
$ kubectl get pod -n sample
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-57c68fcd95-8mzgl   1/1     Running   0          22s
nginx-deployment-57c68fcd95-dmkp5   1/1     Running   0          22s
nginx-deployment-57c68fcd95-xfhq2   1/1     Running   0          22s
```

Namespace を導入した場合のイメージは下記である
![](https://storage.googleapis.com/zenn-user-upload/4a76f1d15456-20230611.png)

# おわりに
k8s 特有の用語が多数出てくるのでとっつきにくかったが、実際に操作しながら試すことで、基本知識はイメージ込みで習得できたと思う。（イメージ図を書き出したのが良かった気がする）

業務で使うのは k8s 自体の構築ではなく、各アプリケーション Client 側ってパターンはよくあると思うが、最低限の知識はもって置くのがよいと思う。