KVMで作ったVM上にKubernetesをインストールする

# About this page
自宅でKubernetesクラスタを作りたかったが、
物理マシンは配線がめんどくさいのでVMでクラスタを作ることにした。
KVMのインストール〜Kubernetesのインストールまでのただの作業記録。

# Host setup

## install KVM

```bash
sudo apt-get install kvm
```

## install GUI tools
GUI使わない場合は、不要。

```bash
sudo apt-get install virt-manager virt-viewer
```

## hostからguestの名前解決ができるようにする

`ssh <vm_name>`でsshが通るようにしたい。

```bash
sudo apt-get install libnss-libvirt
```

`/etc/nsswitch.conf`を開き、`hosts`行に`libvirt`と`libvirt_guest`を追記する。
- libvirt: Guest OSに設定したHostnameで名前解決できるようになる
- libvirt_guest: libvirtで作成したVM名で名前解決できるようになる


```vim
# /etc/nsswitch.conf
hosts:       files libvirt libvirt_guest dns
```

https://libvirt.org/nss.html#configuration


## create pool
VMのディスク領域を格納するためのリソース。targetのディレクトリは予め作り、libvirt-qemuがアクセスできるようにパーミッションを与えておく。

```bash
virsh pool-define-as default dir --target /media/data/libvirt/images/
```

# Guest OS setup

## download linux iso

適当にlinuxのisoイメージをダウンロードしてくる。ここではubuntu22.04を選択した。

https://jp.ubuntu.com/download

## create vm

vmを作成する。

```bash
virt-install --name myvm1 \
  --memory 2048 --vcpus 2 --disk size=8 \
  --cdrom /media/data/libvirt/isos/ubuntu-22.04.4-desktop-amd64.iso
```


# VMにlogin

## GUI

```bash
virt-manager
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290024/b314b942-0d70-2445-f7b9-bf487d8210c8.png)

立ち上げるVMをダブルクリックすると、GUIが立ち上がる。
もしくは、virt-viewerで直接VMを指定してもOK。

```bash
virt-viewer myvm1
```

## CUI

virsh consoleでコンソールに接続できる。
```bash
$ virsh console myvm1

Connected to domain 'myvm1'
Escape character is ^] (Ctrl + ])

me@node01:~$ 
```

# Guest OS設定
いつものように、普通にUbuntuをインストールする…。

## sshdの設定

sshdを有効にして外部からsshでログインできるようにする。

```bash
sudo apt-get update
sudo apt-get install ssh vim -y
sudo vim /etc/sshd_config
```

以下を追加する。

```bash
Port 22
PermitRootLogin no
PasswordAuthentication yes
```

設定を変更したのでsshdサービスを再起動する。

```bash
sudo service sshd restart
```

ホストからsshでログインできるようになる。

```bash
$ ssh myvm1
```

上記の手順を2回行い、Ubuntu VMを2つ準備した。


# Kubernetesをインストールする

## swapをoff

> kubeletが正常に動作するためにはswapは必ずオフでなければなりません。

```bash
# swapの確認
sudo swapon --show

# swapの無効化
sudo swapoff -a
```
`/etc/fstab` をエディタで開いてswapの行をコメントアウト

```vim
# /etc/fstab: static file system information.
#/swapfile   none   swap    sw       0       0
```

## cri-o, kubelet, kubeadmのinstall

コンテナランタイムは、適当にcri-oを選ぶ。

https://github.com/cri-o/packaging/blob/main/README.md#distributions-using-deb-packages

上記はcri-oのインストール手順のページだが、kubeletやkubeadmのインストール手順まで、丁寧にほぼすべて書いてある


依存パッケージのインストール
```bash
sudo apt-get update
sudo apt-get install -y software-properties-common curl
```

バージョンの指定
```bash
KUBERNETES_VERSION=v1.30
PROJECT_PATH=prerelease:/main
```

Kubernetesのリポジトリを追加する
```bash
curl -fsSL https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/deb/Release.key |
   sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/deb/ /" |
   sudo tee /etc/apt/sources.list.d/kubernetes.list
```

CRI-Oのリポジトリを追加する
```bash
curl -fsSL https://pkgs.k8s.io/addons:/cri-o:/$PROJECT_PATH/deb/Release.key |
    gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://pkgs.k8s.io/addons:/cri-o:/$PROJECT_PATH/deb/ /" |
    tee /etc/apt/sources.list.d/cri-o.list
```

cri-o, kubelet, kubeadm, kubectlのインストール
```bash
apt-get update
apt-get install -y cri-o kubelet kubeadm kubectl
```

cri-oの起動
```bash
systemctl start crio.service
```

bootstrapの設定
```bash
modprobe br_netfilter
sysctl -w net.ipv4.ip_forward=1
```
ここまでは共通

### Controle Plane Nodeの設定

後でCalicoを入れたいので、pod-network-cidrを以下のように指定する。

```bash
kubeadm init --pod-network-cidr=192.168.0.0/16
```
https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart


しばらく待っていると、次のようなスニペットがターミナルに表示される。
```
kubeadm join 192.168.122.XXX:6443 --token <<token>> \
	--discovery-token-ca-cert-hash <<hash>> 
```

### Worker Nodeの設定

上記のコマンドをコピペして、実行するだけ。

## kubeconfigの設定

/etc/kubernetes以下に生成されているので、ローカルにコピーする。
```bash
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```

kubectlを叩くと、node一覧を取得できた。

```bash
$ kubectl get nodes

NAME     STATUS   ROLES           AGE   VERSION
node01   Ready    control-plane   45m   v1.30.0
node02   Ready    <none>          43m   v1.30.0
```

## CNIのインストール

前述の通り、Calicoをインストールする。
公式の手順そのまま。

https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart

operatorとcrdをインストールする。

```bash
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/tigera-operator.yaml
```

calicoをインストールするために必要なCustom Resourceを作成する。

```bash
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/custom-resources.yaml
```

1分ちょっと待つと、calicoのpodが起動する。

```bash
$ kubectl get po -n calico-system
NAME                                      READY   STATUS    RESTARTS   AGE
calico-kube-controllers-58dc9899f-9v5zr   1/1     Running   0          93s
calico-node-57xs2                         1/1     Running   0          94s
calico-node-fv5zs                         1/1     Running   0          94s
calico-typha-65b5b98d54-k9jzv             1/1     Running   0          94s
csi-node-driver-8r5cg                     2/2     Running   0          93s
csi-node-driver-b92xh                     2/2     Running   0          93s
```

## 通信テスト

node01と02にそれぞれpodを立てて、通信できることを確認してみる。
まずはサーバ用のPodを作成する。特に何も指定しないので、worker nodeであるnode02の方にPodが作成されるはず。

```bash
kubectl run nginx --image nginx
kubectl expose nginx --port 80
```

クライアント用のPodをたてる。こちらはnode01に作成したい。node01はcontrol-planeのためtaintがついており、そのままではPodが作成できないのでtolerationを設定した上でnodeNameを指定する。

```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: client
  name: client
spec:
  containers:
  - image: ubuntu
    name: client
    command:
      - sleep
      - "3600"
  nodeName: node01
  tolerations:
  - key: "node-role.kubernetes.io/control-plane"
    operator: "Exists"
    effect: "NoSchedule"
EOF
```

作成したPodの確認。想定通り別々のnodeに立っている。

```bash
$ kubectl get pod -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP                NODE     NOMINATED NODE   READINESS GATES
nginx    1/1     Running   0          19m   192.168.140.67    node02   <none>           <none>
client   1/1     Running   0          10m   192.168.196.131   node01   <none>           <none>
```

clientに入る。

```bash
kubectl exec -it client -- bash
```

serverへのアクセスを確認。

```bash
$ apt-get update && apt-get install -y curl
$ curl nginx

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

ちゃんと通信できた。