Hyper-Vでkubernetes環境を構築

自宅のHyper-V環境上にkubernetes環境を構築する方法を整理する。

# TL;DR
1. Windows11でHyper-Vを使うための設定
2. kubernetesのノード間で通信に使う仮想ネットワークスイッチをHyper-V上に作成
3. ゲストOSとしてUbuntu22.04をHyper-Vに作成し、kubernetesノードに設定
4. kubeadmでMaster、Workerノードを設定

# Hyper-Vマシンについての注意
初めに実行環境について注意点がある。
今回Hyper-V上にkubernetesを構築するため、Hyper-Vを使えるWindows Proエディションを使用する必要がある。
また、今回kubernetesのノードとして3台のノードをHyper-Vに用意する。
各ノードのスペックはkubeadmの推奨により最低2vCPUと2GB RAMが必要であるため、Hyper-VのマシンとしてはホストOSを動かすことも考えて最低8vCPU、10GB RAMが必要である。
スペックが足りない場合はノード数やvCPU数を調整するなどして実行できる。

# 1. WindowsでHyper-Vを使うための設定
Windows11でHyper-Vを使うための設定をする。
Windowsボタンを右クリックして「設定」→「システム」タブ→「オプション機能」→「Windowsのその他の機能」から、
「Hyper-V」項目のチェックボックスをオンにし、必要に応じて再起動する。

# 2. kubernetesのノード間で通信に使う仮想ネットワークスイッチをHyper-V上に作成
kubernetesノード間の通信ネットワークを用意するために、既存のネットワークをVLANで分割するなどでも可能であるが、
今回は専用のネットワークを用意するために仮想ネットワークスイッチをHyper-Vに作成する。
「Hyper-Vマネージャー」→「仮想スイッチマネージャー」を開き、新しいネットワークスイッチとして、名前を「Internal-Switch」、種類を「内部」を選択して内部ネットワークスイッチを作成する。
次にネットワークのアドレス範囲を設定するために、Windows検索フォームに「ネットワーク接続」と入力してネットワーク接続を開く。
先ほど追加したInternal-Switchが表示されている(vEthernet(Internal-Switch)と表示される)ので
そちらを右クリックで選択して「プロパティ」を選択する。
「インターネットプロトコルバージョン4」を選択してプロパティを選択する。
今回はIPアドレスとして以下を設定する。
- IPアドレス: 172.20.0.1
- サブネットマスク: 255.255.0.0

こちらでkubernetesのノードが通信に使うネットワーク(172.20.0.0/16)を作成できた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517503/60f97959-42fa-2b62-f6f0-a0aad4891e42.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517503/1bf9a00c-2c8e-78ff-75a4-0b3878d7709c.png)


# 3. ゲストOSとしてUbuntu22.04をHyper-Vに作成し、kubernetesノードに設定
Hyper-V上の各マシンの構成は以下とする。kubernetesの3ノードとして同じ内容で3つのマシンを作成する。
- CPU: 4コア
- メモリ: 8GB
- OS: Ubuntu22.04LTS
- NIC:
    - eth0: Internal-Switch
    - eth1: Default Switch (インターネット通信を行うため。別途外部仮想ネットワークスイッチを用意してもよい)

マシンにkubernetesノードとの通信用とインターネット通信用の2つのNICを用意することが注意点である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517503/b377eead-7885-d4f4-2add-f45b992c19f2.png)

マシンの作成方法はメディアとしてUbuntu22.04LTSのisoを個別にダウンロードしてもよいが、
今回はHyper-Vで「クイック作成」からUbuntu22.04を作成した。

作成後は各パッケージのupdate、upgradeを行った後、各マシンのIPアドレスを固定するために、
/etc/netplanに99-netcfg.yamlファイルを作成して以下のように設定する。(以下の例では固定IPアドレスは172.20.0.100)

```
network:
  version: 2
  renderer: NetworkManager
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses: [172.20.0.100/16]
      routes:
        - to: default
          via: 172.20.0.1
      nameservers:
        addresses: [172.20.0.1]

```

各マシンのIPアドレスは以下とする。
- Master: 172.20.0.100
- Worker: 172.20.0.101, 172.20.0.102


# 4. kubeadmでMaster、Workerノードを設定
kubernetesを構築するために各ノードで以下のコマンドを実行する。
今回はkubernetesの構築にkubeadm、コンテナランタイムにcontainerdを使用する。
構築の際の注意点などが書かれているので、kubeadmの以下公式を確認してインストールを進める。
https://kubernetes.io/docs/setup/production-environment/container-runtimes/


- Master、Workerノード共通
```
sudo su

swapoff -a

# swapoff -aの永続化
sed -i -e 's/\/swap.img/#\/swap.img/g' /etc/fstab

# ipのブリッジトラフィック設定
modprobe br_netfilter
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

# containerdのためのネットワーク設定
modprobe overlay
cat > /etc/modules-load.d/containerd.conf <<EOF
overlay
br_netfilter
EOF
cat > /etc/sysctl.d/99-kubernetes-cri.conf <<EOF
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sysctl --system

# containerdのインストール
apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
apt-get update && apt-get install -y containerd.io
mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
systemctl restart containerd

# kubelet、kubeadm、kubectlをインストール
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
```

ここで、kubeletの内部IPアドレスを先ほど設定した固定IPのNIC(eth0)にそろえる必要があるため
/etc/systemd/system/kubelet.service.d/10-kubeadm.confに以下を設定する。(Masterノードの場合。各マシンの固定IPに合わせてnode-ipを設定する)
```
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=172.20.0.100"
```

またcontainerdのcgroupをsystemdで管理するための設定を行う。(kubeadm公式参照)
/etc/containerd/config.tomlを開き、「SystemdCgroup」を「true」に設定する。
```
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  ...
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true
```

その後systemctl daemon-reload、systemctl restart kubelet、systemctl restart containerdを実行し、kubelet、containerdを再起動する。

以下はMasterノード、Workerノードでコマンドが異なる。

- Masterノード
```
# kubernetesで使うポートを開放(Master)
ufw allow 22/tcp
ufw allow 443/tcp
ufw allow 6443/tcp
ufw allow 2379/tcp
ufw allow 2380/tcp
ufw allow 10250/tcp
ufw allow 10251/tcp
ufw allow 10252/tcp
ufw allow 30000:32767/tcp
ufw enable

# kubeadmコマンド実行
# FlannelをCNIでデフォルト設定で使うためのpod-network-cidrは以下。Calicoなどを使う場合は変える必要がある
kubeadm init --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint=172.20.0.100:6443 --apiserver-advertise-address=172.20.0.100

# kubernetesのクレデンシャルファイル作成(kubectlを使用するため)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# CNIとしてFlannelを導入
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
```

- Workerノード
```
# kubernetesで使うポートを開放(Worker)
ufw allow 22/tcp
ufw allow 10250/tcp
ufw allow 30000:32767/tcp
ufw enable

# kubeadm joinコマンドでkubernetesクラスタにノードとして参加させる
# こちらはMasterノードのkubeadmコマンド実行後に表示されたコマンドをそのまま実行する
# コマンドを見逃した場合はMasterノードで「kubeadm token create --print-join-command」を実行
(※ダミー)kubeadm join 172.20.0.100:6443 --token ##### --discovery-token-ca-cert-hash #####
```

以上でkubernetes環境が構築できたが、きちんと構築されているか確認するためにMasterノードで以下を実行し、ノードが「Ready」状態であることを確認する。
```
> kubectl get nodes -o wide
NAME   STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
k8s0   Ready    control-plane   9h    v1.28.2   172.20.0.100   <none>        Ubuntu 22.04.4 LTS   6.2.0-1019-azure   containerd://1.6.28
k8s1   Ready    <none>          9h    v1.28.2   172.20.0.101   <none>        Ubuntu 22.04.4 LTS   6.2.0-1019-azure   containerd://1.6.28
k8s2   Ready    <none>          9h    v1.28.2   172.20.0.102   <none>        Ubuntu 22.04.4 LTS   6.2.0-1019-azure   containerd://1.6.28
```

また、podが実際にデプロイできることを確認するために以下のコマンドでダミーのpodをデプロイして確認する。
```
# ダミーでalpine linuxをデプロイ
> kubectl run -it --rm alpine --image alpine

# 別ターミナルを開いてpodを確認する
> kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
alpine   1/1     Running   0          5s
```


これでkubernetes環境が構築できた。
今回構築した環境は最低限のkubernetes環境であり、
次回は実際にサービスをデプロイするために便利だったり必要なリソースを導入していく。