Vagrant使ってkubeadmとcontainerdでkubernetes1.31を構築する

## 内容がわかりにくかったので、[こちらのページ](https://qiita.com/nkoseki/items/4c63607fb71778ae3ce9)に書き直しました。

# はじめに
kubernetesをkubeadm使って構築する際、Vagrant環境だとNATアドレスが優先されてしまう。
それに対処する個別の設定方法はネットで探すと出てくるものの、毎回探すのが大変。
さらに[kubernetesの本家の手順](https://v1-31.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)はVagrant環境意識していないので手順通りにやるとハマったり、いろんな情報が混在して、結局何を設定すればいいのかわかりにくかったため、自分の備忘録用に整理。

# 前提
- Vagrantで作ったVM2台にインストール（Master用とWorker用にそれぞれ一つ）
    - Masterノード：192.168.56.53
    - Workerノード：192.168.56.54
- VMはUbuntu20.04を利用
- kubernetes構築はkubeadmを利用
- Container Runtimeはcontainerdを利用
- cgroupドライバはsystemdを利用
- CNIプラグイン実装はcalicoを利用
- calicoで使うアドレス帯`192.168.0.0/16`がVagrantのアドレス帯`192.168.56.0/24`と重複しているので、calicoのアドレス帯を`172.168.0.0/16`に変更

# 手順
以下の手順で進めた。VagrantでのVMはすでにある前提で省略。
1. [Masterノードの構築](#masterノードの構築)
    1. Container Runtime(containerd)をインストール
    1. kubeadm, kubelet, kubectlのインストール
    1. kubernetesクラスタの作成（Masterノード側）
    1. CNIプラグインのインストール
1. [Workerノードの構築](#workerノードの構築)
    1. Container Runtime(containerd)をインストール
    1. kubeadm, kubeletのインストール
    1. kubernetesクラスタを作成（Workerノード側）

# Masterノードの構築
最初Masterノードから。

## Container Runtimeのインストール

### swapをoffにする
`/etc/fstab`のswapをコメントアウト
```
$ sudo vi /etc/fstab

# コメントアウトしてswap を永続的にOFFにする
# /swap.img  none  swap  sw  0 0
```
シャットダウンしswapを無効にする
```
$ sudo shutdown -r now
```
起動後に再度ログインしてswap確認
```
$ free -m
              total        used        free      shared  buff/cache   available
Mem:           1983         124        1630           0         228        1711
Swap:             0           0           0

```

### パケットforward設定
昔の手順見ると他にも設定項目があったが、kubernetes1.31ではこれだけでいいらしい。
```
# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF

# Apply sysctl params without reboot
$ sudo sysctl --system
$ sysctl net.ipv4.ip_forward
[結果：設定されているなら１]
net.ipv4.ip_forward = 1
```

### cgroupドライバをどれにするか決める。
`cgroupfs driver`と `systemd cgroup driver` の２種類のドライバがあるが、
systemd起動しているシステムでは`systemd cgroup driver`を使えとあるので`systemd`にする。
kubernetesインストール時に指定するので、今は何もしない。

### containerdのインストール
[containerdの説明ページ](https://v1-31.docs.kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd)から辿れる[containerdのインストールページ](https://github.com/containerd/containerd/blob/main/docs/getting-started.md)には公式のバイナリでのインストールする方法と、Dockerでインストールする方法があり、ネットで調べてもDockerでインストールする方法が多く紹介されている。
ここでは公式のバイナリでのインストールにする。（Dockerインストール試したけどうまくいかなかったので諦めた・・）

containerdインストールの大まかな流れは以下のとおり。

- Step1. containerdインストール
- Step2. runcインストール
- Step3. CNIプラグインインストール

 #### Step1.containerdインストール
[ダウンロードページ](https://github.com/containerd/containerd/releases)から取得。
2.x系もあったが、最近出たばっかりでハマりそうだったので1.7系をインストールする。
この後インストールするruncのバージョン1.2.2に対応しているとのこと。
```
# 資材をdownloadしてインストール
VER=1.7.27
curl -OL https://github.com/containerd/containerd/releases/download/v${VER}/containerd-${VER}-linux-amd64.tar.gz
sudo tar Cxzvf /usr/local containerd-${VER}-linux-amd64.tar.gz

# service起動用スクリプトもdownlodして設定する
curl -OL https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
sudo cp ./containerd.service /lib/systemd/system/containerd.service
sudo systemctl daemon-reload
sudo systemctl enable --now containerd

## 起動していることを確認
systemctl status containerd
```

 #### Step2. runcインストール
 containerdに対応しているruncのバージョンがわからなかったので、containerdのページにある[runc version情報](https://github.com/containerd/containerd/blob/main/script/setup/runc-version)のバージョンをいれる。
 ```
VER=1.2.6 
 curl -OL https://github.com/opencontainers/runc/releases/download/v${VER}/runc.amd64
sudo install -m 755 runc.amd64 /usr/local/sbin/runc
```

 #### Step3. CNIプラグインインストール
 CNIもバージョンが不明だったので最新バージョンにする。
 ```
 VER=1.6.2
 curl -OL https://github.com/containernetworking/plugins/releases/download/v${VER}/cni-plugins-linux-amd64-v${VER}.tgz
sudo mkdir -p /opt/cni/bin
sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v${VER}.tgz
```

ここまででMaster,Worker共通なContainer Runtimeの設定は終了。
これ以降はMasterでやる作業なので、Workerでやっている場合はWorkerのページに戻る。

## kubeadm, kubelet, kubectlのインストール
基本的にkubernetesのインストール手順に従って実施する。

必要なパッケージのインストール
```
sudo apt-get update
# apt-transport-https may be a dummy package; if so, you can skip that package
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
```
とりあえず、マニュアルに書かれている通りに・・・
```
sudo mkdir -p /etc/apt/keyrings
# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below.
# sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
```
技術者として理解することを放棄し・・・
```
# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
```
エラーが出ないことだけを祈りつつ・・・
```
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl

# パッケージが更新されないようにする
sudo apt-mark hold kubelet kubeadm kubectl
```
自動起動を有効にしてkubeletサービス起動
```
sudo systemctl enable --now kubelet
```
この段階ではkubeletサービスは再起動を繰り返して正常に起動しないが問題ないとインストールガイドに書いている。確かに`activating`状態のままであり、まともに立ち上がっていない。
```
$ systemctl status kubelet

## 結果
vagrant@kubeadm:/etc/containerd$ systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /usr/lib/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: activating (auto-restart) (Result: exit-code) since Mon 2024-12-30 21:47:54 JST; 7s ago
       Docs: https://kubernetes.io/docs/
    Process: 14992 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARG>
   Main PID: 14992 (code=exited, status=1/FAILURE)
```

## kubernetesクラスタの作成（Masterノード側）

### 事前準備
cgroup driverでsystemdを利用する宣言と、node-ipを設定する。
`--node-ip`オプションをつけないと`kubectl get node -o wide`で出力されるINTERNAL_IPがVagrantのNAT IP(10.2.0.15)が優先的に割り当てられる。

```
cat << EOF | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS=--cgroup-driver=systemd --node-ip=192.168.56.53
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet
```

### Masterノードのクラスタ作成

`--apiserver-advertise-address`でクラスタ構成のIPを指定。`--node-name`はノード名を変更したい場合のみに指定。指定しない場合はホスト名がノード名となる。
```
sudo kubeadm init --pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.56.53 --node-name=master
```
```
[出力結果]
sudo kubeadm init --pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.56.53 --node-name=master
I0102 10:54:02.171604    5513 version.go:261] remote version is much newer: v1.32.0; falling back to: stable-1.31
[init] Using Kubernetes version: v1.31.4
[preflight] Running pre-flight checks
        [WARNING Hostname]: hostname "master" could not be reached
        [WARNING Hostname]: hostname "master": lookup master on 127.0.0.53:53: server misbehaving
・・・

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.56.53:6443 --token z8vnl0.lugg1c3jvihk2dg6 \
        --discovery-token-ca-cert-hash sha256:7c41629dc4f0ac5abaea5b1712dbb3264842ea71580203be607875367685599d 
```
正常終了後の出力結果をそのまま実行
```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
このタイミングでkubeletサービスが正常に起動される。
またkubeletサービスにcgroup driverでsystemd、node-ipが設定されていることがわかる。
```
# kubeletサービスがactive, オプションにsystemdとnode-ipの引数が設定されている
systemctl status kubelet

[出力（抜粋）]
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /usr/lib/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Thu 2025-01-02 10:59:08 JST; 8min ago
       Docs: https://kubernetes.io/docs/
   Main PID: 6384 (kubelet)
      Tasks: 11 (limit: 2274)
     Memory: 33.5M
     CGroup: /system.slice/kubelet.service
             └─6384 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --hostname-override=master --pod-infra-container-image=registry.k8s.io/pause:3.10 --cgroup-driver=systemd --node-ip=192.168.56.53
```
またノード名、INTERNAL_IPも正しく設定されている。nodeのSTATUSは`NotReady`でよい。またcoredns以外のPodも正常に起動されているのがわかる。
corednsは[インストールガイドによると](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/troubleshooting-kubeadm/#coredns-%E3%82%82%E3%81%97%E3%81%8F%E3%81%AF-kube-dns-%E3%81%8C-pending-%E7%8A%B6%E6%85%8B%E3%81%A7%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B)CNIプラグインをインストールするまでPending状態とのことなので問題なし。
```
$ kubectl get node -o wide
NAME     STATUS     ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master   NotReady   control-plane   18m   v1.31.4   192.168.56.53   <none>        Ubuntu 20.04.4 LTS   5.4.0-110-generic   containerd://1.7.24

# dns以外のpodは正常起動
$ kubectl get pod -A
NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-7c65d6cfc9-jcfjb         0/1     Pending   0          19m
kube-system   coredns-7c65d6cfc9-zjlgw         0/1     Pending   0          19m
kube-system   etcd-master                      1/1     Running   0          20m
kube-system   kube-apiserver-master            1/1     Running   0          20m
kube-system   kube-controller-manager-master   1/1     Running   0          20m
kube-system   kube-proxy-tddbf                 1/1     Running   0          20m
kube-system   kube-scheduler-master            1/1     Running   0          20m
```

## CNIプラグインのインストール
CNIプラグインのcalicoをインストールする。
基本的に[インストールガイド](https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart)の通りやるだけ。

### Calicoオペレータを追加

```
# install Calico operator
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/tigera-operator.yaml
```

### 
Calicoではcidrとして`192.168.0.0/16`を使っている。これはVagrantと被るので、Masterノード作成時に指定した`172.16.0.0/16`へ修正する。
```
# cidrを記載しているので、修正して適用するため一旦ダウンロード
curl -OL https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/custom-resources.yaml

# cidrを修正
vi custom-resources.yaml
      cidr: 192.168.0.0/16
      ↓
      cidr: 172.16.0.0/16

# 修正したyamlファイルを適用しCalicoをインストール
kubectl apply -f ./custom-resources.yaml

# Podの正常起動を確認（全Podが起動するまで数分程度かかる）
kubectl get pod -A
[結果]
NAMESPACE          NAME                                       READY   STATUS    RESTARTS   AGE
calico-apiserver   calico-apiserver-697587c558-2ltzq          1/1     Running   0          2m2s
calico-apiserver   calico-apiserver-697587c558-v5fkh          1/1     Running   0          2m2s
calico-system      calico-kube-controllers-6dbdfd75fd-ccf42   1/1     Running   0          2m2s
calico-system      calico-node-b45wn                          1/1     Running   0          2m2s
calico-system      calico-typha-7d7889b79-bdvhw               1/1     Running   0          2m2s
calico-system      csi-node-driver-7fwr2                      2/2     Running   0          2m2s
kube-system        coredns-7c65d6cfc9-jcfjb                   1/1     Running   0          56m
kube-system        coredns-7c65d6cfc9-zjlgw                   1/1     Running   0          56m
kube-system        etcd-master                                1/1     Running   0          56m
kube-system        kube-apiserver-master                      1/1     Running   0          56m
kube-system        kube-controller-manager-master             1/1     Running   0          56m
kube-system        kube-proxy-tddbf                           1/1     Running   0          56m
kube-system        kube-scheduler-master                      1/1     Running   0          56m
tigera-operator    tigera-operator-76c4976dd7-rk7qm           1/1     Running   0          2m44s
```
### Masterノードのtaint削除
~~taint削除してPodのSchedulingを可能とする。~~

【2025年4月12日更新】
上記の記載がcalicoのページに記載があったので載せたけど、本手順ではPodを配置するためのWorkerノードを構築するのでこの手順は不要。
Workerノードがなく、Masterノード上にPodを作成したい場合のみに必要な手順でした。
```
kubectl describe node master |grep -i taint
[結果]
Taints:             node-role.kubernetes.io/control-plane:NoSchedule

kubectl taint nodes --all node-role.kubernetes.io/control-plane-
[結果]
node/master untainted
```
Masterノード、Podが正常に動作しているなら構築完了。
```
kubectl get node -o wide
kubectl get pod -A
```

# Workerノードの構築

## Container Runtimeのインストール(Workerノード)
containerdのインストールはMasterノードと同様のため[そちらを参照](#container-runtimeのインストール)

## kubeadm, kubeletのインストール
Masterノードのインストール手順とほぼ同様。唯一の違いはkubectl設定がないだけ。手順だけ記載。

```
###
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

###
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/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:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

###
sudo apt-get update
sudo apt-get install -y kubelet kubeadm
sudo apt-mark hold kubelet kubeadm

###
sudo systemctl enable --now kubelet
systemctl status kubelet

```

## kubernetesクラスタを作成（Workerノード側）

### クラスタ作成での事前準備
Masterノード同様、cgroup driverでsystemdを利用する宣言と、node-ipを設定する。
ここで設定変更後、kubeletを再起動してもまだkubeletサービスは正しく起動していないので、`kubeadm join`後に変更した方がいいかもしれないが未調査。
```
cat << EOF | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS=--cgroup-driver=systemd --node-ip=192.168.56.54
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet
```
### Workerノードのクラスタ作成

Masterノードインストール時に発行されたトークンは有効期限があるため、Tokenを再発行しておく。Masterノードで実行。

```
# Masterノードで実施すること！
sudo kubeadm token create --print-join-command

# 出力結果を控えておく。これをworkerノードで実施することでクラスタに参加できる
[出力結果]
kubeadm join 192.168.56.53:6443 --token 45plun.h2blf7b92p691pgu --discovery-token-ca-cert-hash sha256:e9ed63a06df0e9efb111b14f0550463bf4efaabd469cfffc93d596244d2ede0a
```

`kube join`コマンドでMasterノードに参加する。これはWorkerノードで実施。
Masterノードで発行したTokenにアドバタイズするWorkerノード自身のIPアドレス`192.168.56.54`を追加する。

```
# Workerノードで実施する。
# 先ほどの出力結果にアドバタイズするIPアドレスを追加
sudo kubeadm join 192.168.56.53:6443 --token 45plun.h2blf7b92p691pgu --discovery-token-ca-cert-hash \
sha256:e9ed63a06df0e9efb111b14f0550463bf4efaabd469cfffc93d596244d2ede0a \
  --apiserver-advertise-address=192.168.56.54
```
### Workerノードが作成されたことを確認

#### Workerノードで確認

まずはWorkerノードで`kubeletサービス`が正しく起動され、node-ipが設定されていることを確認する

```
systemctl status kubelet
[結果（抜粋）]
...
Active: active (running) since Thu 2025-01-02 13:02:30 JST; 22min ago
...
     CGroup: /system.slice/kubelet.service
             └─12478 /usr/bin/kubelet (略) --cgroup-driver=systemd --node-ip=192.168.56.54
```

#### Masterノードで確認

続いてMasterノードにWorkerノードがクラスタに参加されたことを確認する。またINTERNAL_IPがVagrantのNAT IPでないことを確認する。
```
kubectl get node -o wide
[結果]
NAME      STATUS   ROLES           AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master    Ready    control-plane   155m   v1.31.4   192.168.56.53   <none>        Ubuntu 20.04.4 LTS   5.4.0-110-generic   containerd://1.7.24
worker1   Ready    <none>          27m    v1.31.4   192.168.56.54   <none>        Ubuntu 20.04.4 LTS   5.4.0-110-generic   containerd://1.7.24
```

うまくいけば、同様の手順でWorkerノードを追加できる。

# その他

## ノードのリセット

`kubeadm init`や`kubeadm join`をやり直す

### Masterノードのリセット

```
sudo kubeadm reset
rm -rf ~/.kube
```

### Workerノードのリセット

Masterノードで実施、クラスタからWorkerノードを切り離す。
```
kubectl drain worker1 --ignore-daemonsets --force
kubectl delete node worker1
```
Workerノードで実施、Workerノードのリセット
```
sudo kubeadm reset
rm -rf ~/.kube
```


## INTERNAL_IPの変更
#### INTERNAL_IPの変更手順：その１
`/etc/default/kubelet`の`KUBELET_EXTRA_ARGS`で指定した`node-ip`を変更しkubeletを再起動してもINTERNAL_IPに反映されない。
反映させる場合の手順は以下。

- `/etc/default/kubelet`からnode-ipを削除してkubelet再起動
→このタイミングでNATのIPがアドバタイズされている模様（INTERNAL_IPがNATのIPに変わる）
- 再びnode-ipを設定してkubelet再起動

#### INTERNAL_IPの変更手順：その２
`/etc/default/kubelet`にnode-ipを記載しないで、別のファイルに書いてもいいらしい。
```
sudo vi /lib/systemd/system/kubelet.service.d/10-kubeadm.conf 
# KUBELET_CONFIG_ARGSにnode-ipを追加
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=x.x.x.x"

# 再起動
sudo systemctl daemon-reload
sudo systemctl restart kubelet
```

# まとめ
cgroup driverの設定部分がネットで調べていると迷子になってしまい、結果動いてはいるものの設定が設定不足の可能性あります。誤り等あればコメント頂けたらありがたいです。
これまでminikube使っていたけど、これだとクラスタ組めるのでノード停止時の動作とか色々検証できるはず（やる気があれば）