ローカルのKubernetesの実験環境としてminikubeを使用する

# はじめに

ローカルに実験的にKubernetesを立てようと思った時、本番環境との構成の違いに悩んでしまうことがあります。Kubernetes自体は動かすことはできますが、PODのボリュームやIngressを同じように作成できないことなどがあります。そのすべてを解決するものではありませんが、ある程度の機能を持つものとしてminikubeがあります。

この記事では、minikubeのインストールからリソースの作成を行い、ローカルでのKubernetesを少しでも身近に感じてもらえれば幸いです。

# minikubeの特徴

この記事では以下のminikubeの特徴について、後述のハンズオンで触れてみます。

・PersistentVolumeの動的プロビジョニングの機能を持つ
　クラウドのKubernetesクラスタでは、ボリュームの場所を動的に確保しPVを作成できるものがあります。minikubeでもVMドライバーの場所を動的に確保しPVを作成することができます。

・ローカルのDockerイメージを使用できる
　クラウドではコンテナレジストリを使用しますが、minikubeでは不要でローカルのイメージを使用できます。

・Ingressコントローラーをアドオンとして使うことができる
　minikubeにはアドオンの機能があり、楽にNginxのIngressコントローラーを作成することができます。お手軽にIngressに触れることができます。

詳細は以下のドキュメントになります。

https://minikube.sigs.k8s.io/docs/

# minikubeを動かすためのCPU,メモリー

参考にKubernetesも載せておきます。minikubeが軽いわけではないですね。

||CPUコア数|メモリー|参照URL|
|---|---|---|---|
|minikube|2|2GB|https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/|
|Kubernetes|2|2GB|https://minikube.sigs.k8s.io/docs/start/|

# minikubeのインストールとstart

前提としてOSはAmazon Linux 2です。
まずはDockerをインストールします。

```bash
$ sudo yum install -y docker-20.10.17-1.amzn2.0.1
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo gpasswd -a ec2-user docker
```

ユーザーのグループを変更したので、いったん再ログインします。
その後、minikubeをインストールします。

```bash
$ curl -LO https://storage.googleapis.com/minikube/releases/v1.27.1/minikube-linux-amd64
$ sudo install minikube-linux-amd64 /usr/local/bin/minikube
```

minikubeをstartさせます。オプションとしてKubernetesのバージョン指定やリソースの制限などができます。

```bash
[ec2-user@ip-10-0-0-50 ~]$ minikube start \
--cpus='2' \
--memory='2g' \
--driver='docker' \
--kubernetes-version=v1.25.2
😄  minikube v1.27.1 on Amazon 2 (xen/amd64)
    ▪ MINIKUBE_ACTIVE_DOCKERD=minikube
✨  Using the docker driver based on user configuration
📌  Using Docker driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.25.2 preload ...
    > preloaded-images-k8s-v18-v1...:  385.41 MiB / 385.41 MiB  100.00% 41.56 M
🔥  Creating docker container (CPUs=2, Memory=2048MB) ...
🐳  Preparing Kubernetes v1.25.2 on Docker 20.10.18 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: default-storageclass, storage-provisioner
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
```

この時点で、kubectlコマンドを使うことができます。

```bash
$ minikube kubectl -- get node
NAME       STATUS   ROLES           AGE     VERSION
minikube   Ready    control-plane   2m34s   v1.25.3
```

しかし、コマンドが長く面倒なのでaliasを設定します。

```bash
$ alias k="minikube kubectl --"
$ echo 'alias k="minikube kubectl --"' >> ~/.bash_profile
```

これで「minikube kubectl --」から「k」にコマンドを短くできました。
また、このままではDockerでビルドしたイメージを使うことができません。以下のコマンドを実行します。

```bash
$ eval $(minikube docker-env)
$ echo 'eval $(minikube docker-env)' >> ~/.bash_profile
```

# ハンズオン

## PVの動的プロビジョニングの確認

それではクラスター上にKubernetesリソースを作成してみます。
以下のサンプルからStatefulSet、Serviceを作成します。

https://kubernetes.io/ja/docs/tutorials/stateful-application/basic-stateful-set/

```bash
[ec2-user@ip-10-0-0-50 ~]$ k apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/ja/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created
```

StatefulSetのボリュームテンプレートから、自動で動的にボリュームが作成されます。

```bash
[ec2-user@ip-10-0-0-50 ~]$ k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-257531da-0c5f-4142-b50b-faf3f91897a0   1Gi        RWO            Delete           Bound    default/www-web-0   standard                12m
pvc-af257a5b-ddd5-40be-b753-d836a2e5757a   1Gi        RWO            Delete           Bound    default/www-web-1   standard                11m
[ec2-user@ip-10-0-0-50 ~]$ k get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-257531da-0c5f-4142-b50b-faf3f91897a0   1Gi        RWO            standard       12m
www-web-1   Bound    pvc-af257a5b-ddd5-40be-b753-d836a2e5757a   1Gi        RWO            standard       12m
```

どこにボリュームの場所が作成されているかですが、PVをdescribeするとわかります。

```bash
[ec2-user@ip-10-0-0-50 ~]$ k describe pv pvc-257531da-0c5f-4142-b50b-faf3f91897a0
Name:            pvc-257531da-0c5f-4142-b50b-faf3f91897a0
Labels:          <none>
Annotations:     hostPathProvisionerIdentity: 8481dbd2-eea1-4c46-90ba-17db4f7a928b
                 pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Bound
Claim:           default/www-web-0
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/hostpath-provisioner/default/www-web-0
    HostPathType:
Events:            <none>
```

minikubeではVMドライバー上に場所が確保されます。minikubeの起動ログに`Using Docker driver with root privileges`とあったのでroot権限でコンテナを動かしていそうです。確認してみます。

```bash
[ec2-user@ip-10-0-0-50 ~]$ sudo docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED          STATUS          PORTS                                                                                                                                  NAMES
440ec508be92   gcr.io/k8s-minikube/kicbase:v0.0.35   "/usr/local/bin/entr…"   20 minutes ago   Up 20 minutes   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
[ec2-user@ip-10-0-0-50 ~]$ sudo docker exec -it minikube bash
root@minikube:/# ls /tmp/hostpath-provisioner/default/
www-web-0  www-web-1
```

minikubeのコンテナ上にPVの場所を確認することができました。コンテナ上なので、再起動すると消える一時的なものになると思います。

## ローカルのDockerイメージの使用

コンテナのイメージを別のNginxに切り替えてみます。

```bash
$ docker pull nginx
$ docker tag nginx:latest my-nginx:v1.0
$ k edit statefulset web #imageをmy-nginx:v1.0に書き換える。
```

PODの参照するイメージが切り替わり、再起動できていればローカルのイメージを使えていそうです。

```bash
[ec2-user@ip-10-0-0-20 ~]$ k get pod
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          10s
web-1   1/1     Running   0          14s
[ec2-user@ip-10-0-0-20 ~]$ k describe pod web-0 | grep 'Image:'
    Image:          my-nginx:v1.0
```

自動で再起動されていました。もし`eval $(minikube docker-env)`を実行しなければローカルのイメージを使えず、実行後イメージの再ビルドをすることになりました。。

## Ingressコントローラーをアドオンとして使用する

Ingressコントローラーをminikubeで使いたい場合、アドオンとして楽に作成できます。しかし、以下のドキュメントのようにクラウド、クラスタによって適したIngressコントローラーは異なります。環境毎に違うものになってしまうため、お手軽にIngressを試したい場合に以下の手順が参考になると思います。

https://kubernetes.io/ja/docs/concepts/services-networking/ingress-controllers/

以下のコマンドで作成します。

```bash
$ minikube addons enable ingress
```

Ingressコントローラーを追加できると、Namespace「ingress-nginx」が自動で作られDeploymentができています。

```bash
[ec2-user@ip-10-0-0-20 ~]$ k get deployment -n ingress-nginx
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           107s
[ec2-user@ip-10-0-0-20 ~]$ k get pod -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-l5qzs        0/1     Completed   0          2m3s
ingress-nginx-admission-patch-2tnrq         0/1     Completed   0          2m3s
ingress-nginx-controller-5959f988fd-bvfww   1/1     Running     0          2m3s
```

早速Ingressコントローラーを使って、前に作ったStatefulSetのPODにアクセスできるようにしてみます。
まず、POD内部でアクセスできるようになっているか確認します。

```bash
[ec2-user@ip-10-0-0-20 ~]$ k exec web-0 -it -- bash
root@web-0:/# curl localhost #htmlファイルがないのでエラーになります。
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.23.2</center>
</body>
root@web-0:/# echo "hogehoge web-0" > /usr/share/nginx/html/index.html #表示できればよいので内容はなんでも良しとします。
root@web-0:/# curl localhost
hogehoge web-0
</html>
```

同様にもう1台のPODもhtmlファイルを作成しておきます。
次にIngressを作成します。

```yaml:test-nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: test-nginx
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 80
```

```bash
$ k apply -f test-nginx-ingress.yaml
```

Ingressが存在して、バックエンドにPODが存在しているかを確認します。

```bash
[ec2-user@ip-10-0-0-20 ~]$ k get ingress
NAME         CLASS   HOSTS        ADDRESS        PORTS   AGE
test-nginx   nginx   test-nginx   192.168.49.2   80      83s
[ec2-user@ip-10-0-0-20 ~]$ k describe ingress
Name:             test-nginx
Labels:           <none>
Namespace:        default
Address:          192.168.49.2
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host        Path  Backends
  ----        ----  --------
  test-nginx
              /   nginx:80 (172.17.0.3:80,172.17.0.4:80)
Annotations:  nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
  Type    Reason  Age                From                      Message
  ----    ------  ----               ----                      -------
  Normal  Sync    47s (x2 over 94s)  nginx-ingress-controller  Scheduled for sync
```

バックエンドにはPODが2台ともいました。
また、Ingressの設定におかしなところがないかはIngressコントローラーのログを見るとわかります。

```bash
$ k logs -n ingress-nginx ingress-nginx-controller-5959f988fd-bvfww
```

また、IngressにはIPアドレス「192.168.49.2」が割り当てられており、アクセスすることができます。

```bash
[ec2-user@ip-10-0-0-20 ~]$ curl 192.168.49.2:80
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
```

404エラーなので、バックエンドのPODには到達せずIngressコントローラーから応答が返ってきています。
バックエンドのPODにアクセスするには、ホスト名を指定します。

```bash
[ec2-user@ip-10-0-0-20 ~]$ curl 192.168.49.2:80 -H "Host:test-nginx"
hogehoge web-0
[ec2-user@ip-10-0-0-20 ~]$ curl 192.168.49.2:80 -H "Host:test-nginx"
hogehoge web-1
[ec2-user@ip-10-0-0-20 ~]$ curl 192.168.49.2:80 -H "Host:test-nginx"
hogehoge web-1
[ec2-user@ip-10-0-0-20 ~]$ curl 192.168.49.2:80 -H "Host:test-nginx"
hogehoge web-0
```

アクセスできました。繰り返すと応答が異なっており2台のPODにアクセスしていることがわかります。

# minikube実験後のKubernetesへの移行について

minikubeでの実験後、Kubernetes本番で検証することになると思います。この時、どうしても設定の違いは出て切ると思います（annotations,パラメータなど）。
そのため、minikubeとKubernetesを行き来するたび、設定を書き換えなくてはならないかと思ってしまいます。
そこでKustomizeの使用が検討されることになると思いますので、今後調べたいと思っています。

https://github.com/kubernetes-sigs/kustomize/tree/master/examples

# 終わりに

今回はローカルでKubernetesを動かす方法について触れました。
記事にすると、今まで学んだ様々なことを俯瞰して整理できてよいと思いました。
今後も勉強します。