Ubuntu 24.04 に k8s を入れてみる

# はじめに
kubernetes（以下 k8s）の Network がどうなっているか、試したい。GNS3 上で Master/Worker VMを構築し、外部の VyOSと eBGP接続することを目指す。
「なんで動くのか？」の言語化とエビデンスの確認が目的のため、少し冗長かもしれない。

## 動作環境
Master x1, Worker x2の基本的な構成。vCPU/メモリ/ディスクは最小推奨構成。最初これより少ないメモリで試したら、そもそもインストールできなかった。
| Host名 | OS | vCPU | メモリ | ディスクサイズ |NICのアドレス|
|:-:|:-:|:-:|:-:|:-:|:-:|
|k8s-master| ubuntu 24.04.2|4|8192MB|40GB|192.168.11.121/24| 
|k8s-worker1| ubuntu 24.04.2|4|8192MB|40GB|192.168.11.122/24| 
|k8s-worker2| ubuntu 24.04.2|4|8192MB|40GB|192.168.11.123/24| 

### ターゲットk8s
2025年4月末時点での最新安定版を使用。

| ソフトウェア | Version | コメント |
|:-:|:-:|:-:|
|k8s| 1.33  |   |
|containerd|1.7.24-0 | dockerを利用しない|
|calico   |3.29.3|   |


## 事前準備（Master/Workerノードとも）
### SWAPを無効にする
```console
ubuntu@k8s-master:~$ sudo swapon --show
ubuntu@k8s-master:~$
```
試した環境では 最初からSWAP は有効にされていなかった。

### /etc/hosts の編集
/etc/hostsにMaster/Workerを追加しておく
```console
ubuntu@k8s-master:~$ cat /etc/hosts
192.168.11.121 k8s-master
192.168.11.122 k8s-master
192.168.11.123 k8s-worker
```

### カーネルモジュールの追加
```console
ubuntu@k8s-master:~$ sudo modprobe overlay
ubuntu@k8s-master:~$ sudo modprobe br_netfilter
```

:::note info
overlay：
コンテナのファイルシステム（Unionファイルシステム）を支えるカーネルモジュール。
これがないと、コンテナのレイヤー管理が動かない。
:::

:::note info
br_netfilter：
Linuxカーネルで「bridgeネットワーク越しのパケット」もiptablesなどでフィルタできるようにするモジュール。
KubernetesではPod-to-Pod通信などでbridgeを使うので、これがないとNetworkPolicyやiptables制御が効かない。
:::
#### 永続化対応
VM再起動時に読み込まれるよう、```modules.conf```に設定を追加。
```console
ubuntu@k8s-master:~$ sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
```

### カーネルパラメータの設定
```console
ubuntu@k8s-master:~$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
```

```sudo sysctl --system``` で設定を反映させる。k8s では仮想 Bridgeを利用しているが、仮想 Bridge間通信で iptables を有効にするためには```net.bridge.bridge-nf-call-iptables``` と```net.bridge.bridge-nf-call-ip6tables``` が必要。
Pod間 Routing を行うために```net.ipv4.ip_forward```を有効にする。

#### 正常性確認
```console
ubuntu@k8s-master:~$ sudo sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1
ubuntu@k8s-master:~$ sudo sysctl net.bridge.bridge-nf-call-ip6tables
net.bridge.bridge-nf-call-ip6tables = 1
ubuntu@k8s-master:~$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
```
すべて1であれば正しく設定されている。

### コンテナエンジンのインストール
```console
sudo apt install -y containerd
```

k8s推奨の```containerd```を使用する。docker はk8s 1.20 以降では非推奨。

### containerd設定ファイル作成
```console
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
```

### Systemd Cgroupにする
```console
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
```
Podのコンテナプロセスを管理するために cgroup（コントロールグループ）を使ってる。cgroupとは、LinuxでCPU、メモリ、ネットワーク帯域などのリソース制御をする仕組み。
containerd 自体でCgroupを管理する行うか、Systemdでシステム一括とするかがある。k8s では後者が推奨されており、かならず ```SystemdCgroup = true```とする必要がある。
公式情報は以下。

https://kubernetes.io/docs/concepts/architecture/cgroups/

### containerd 再起動と有効化
```console
ubuntu@k8s-master:~$ sudo systemctl restart containerd
ubuntu@k8s-master:~$ sudo systemctl enable containerd
```

## k8s のインストール
これでk8s をインストールする準備ができた。k8s のパッケージは ubuntu の標準リポジトリには存在しないため、公開鍵をインポートし、k8s リポジトリを登録する。

そしてインストール。k8s 1.33においてはkuelet, kubeadm, kubectl は別パッケージ。
それぞれの役割は、公式ページによるとざっくり以下。

| コマンド名| 概要 |
|:-:|:-:|
|kubeadm |クラスターを起動するコマンド|
|kubelet| クラスター内のすべてのマシンで実行されるコンポーネント。Podやコンテナの起動などを行う|
|kubectl| クラスターにアクセスするためのコマンドラインツール|


### k8s リポジトリの追加
```console
ubuntu@k8s-master:~$ sudo curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/kubernetes.gpg
ubuntu@k8s-master:~$ echo "deb https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
```

### パッケージインストール
```console
ubuntu@k8s-master:~$ sudo apt update
ubuntu@k8s-master:~$ sudo apt install -y kubelet kubeadm kubectl
ubuntu@k8s-master:~$ sudo apt-mark hold kubelet kubeadm kubectl
```

最後のはk8sのバージョン固定。```apt upgrade```でもって不用意に上げないようにするためのもの。 Version upするときには 一時的に ```unhold```して作業を実施する。

```console
sudo apt-mark unhold kubelet kubeadm kubectl
sudo apt update
sudo apt install kubelet=1.33.1-00 kubeadm=1.33.1-00 kubectl=1.33.1-00
sudo apt-mark hold kubelet kubeadm kubectl
```
ここで書いたサンプルのバージョンはあくまでも例。書いている段階ではまだ存在していない。

## Master ノードでの作業
ようやっとここで Master ノードの初期化を行う。k8sのPodに割り当てるアドレスレンジ（Pod Cider）と、k8s 内部でService を識別するために使用する Cluster IPは、このタイミングでしか設定できない。 これらのアドレスは外部ネットワークと重複不可。歴史の長いオンプレミス環境ではこのアドレスをどうやって確保するかが一大事となる（溜息）

### 本検証でのパラメータ
| Network | 付与するNWアドレス |
|:-:|:-:|
|Pod CIDR | 100.64.0.0/16  |
|Cluster IP| 172.16.0.0/16  |

なぜここまで大きなアドレスレンジにするかだが、以下の理由による模様。
1. どちらのアドレスレンジも後から拡張できない
2. Pod/Serviceが廃棄された後、アドレスが使いまわされるまでにはある程度の時間がかかる

### 実際に作成する
```console
ubuntu@k8s-master:~$ sudo kubeadm init \
--pod-network-cidr=100.64.0.0/16 --service-cidr=172.16.0.0/16
(snip)
Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.11.121:6443 --token jkorog.c9yonl55bvubfjjm \
        --discovery-token-ca-cert-hash sha256:e4cbc98a34b42a394b24a82d63d86b9af6828b07fde5a9dd955fd84c4e00aeaf
``` 

この最後の```kubeadm --join``` を Worker1/2でも実行することで、MasterにWorkerノードが組み込まれる。このコマンドには制限時間（24時間？）あり、失効すると Worker の登録ができない。その場合は```sudo kubeadm token create --print-join-command``` で **新しい有効なjoinコマンド** が出てくるのでそれを利用する。

### kubectl の準備
kubectlが接続する「k8sクラスタ」「ユーザ」「利用する認証情報」を指定するため、```admin.conf```をローカルにコピーする。
```console
ubuntu@k8s-master:~$ mkdir -p $HOME/.kube
ubuntu@k8s-master:~$ sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
ubuntu@k8s-master:~$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
### CNI のインストール
CNIもいくつかあるが、今回は Calico を採用する。
Pod 間通信するには必ず必要なので、最初に入れる。後から変えるのはかなり厄介。

```console
ubuntu@k8s-master:~$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/calico.yaml
```

#### 正常性確認
名前空間`kube-system` 上に `calico-kube-controllers-*` と `calico-node-*` があり、STATUSがRunning であることを確認する。`calico-node-*`は Worker ノードが増えていけばその分だけ増えていく。
```console
ubuntu@k8s-master:~$ kubectl -n kube-system get pod
NAME                                      READY   STATUS    RESTARTS        AGE
calico-kube-controllers-79949b87d-k488c   1/1     Running   4 (12h ago)     3d8h
calico-node-8r8kj                         1/1     Running   6 (7h13m ago)   3d8h
```
:::note info
調べていくと、一部資料では calico.yaml をダウンロードした後、このyaml ファイル内で```name: CALICO_IPV4POOL_CIDR```の値を Pod CIDRと同じ値に変更するものもあった。しかし、今回試したバージョンでは no effect. This should fall within `--cluster-cidr`. となっており、```kubeadm init``` で指定した値に自動で組み込まれるように変更されていた。どのバージョンから仕様が変わったかは調査しきれず。
:::

### Worker ノード側での作業
Worker ノードをjoinする。このコマンドはMaster ノードで```kubeadm init```実行した際、最後のほうの行に出てくるのでコピーして実行。 
```console
ubuntu@k8s-worker1:~$ kubeadm join 192.168.11.121:6443 --token jkorog.c9yonl55bvubfjjm \
        --discovery-token-ca-cert-hash sha256:XXXXXXXXXXXXXXXXXXXXX
```

### Master ノードでの正常性確認
```kubectl get nodes``` で確認する。STATUS が ReadyになっていればOK。
```console
ubuntu@k8s-master:~$ kubectl get nodes
NAME          STATUS   ROLES           AGE    VERSION
k8s-master    Ready    control-plane   4d2h   v1.33.0
k8s-worker1   Ready    <none>          3d1h   v1.33.0
k8s-worker2   Ready    <none>          3d1h   v1.33.0
```
:::note info
Worker ノードで```kubectl get nodes``` を実行してもこの結果は得られない。```kubectl``` コマンドはkube-apiserverにリクエストを送るコマンドであり、workerノードでは kube-apiserverの情報がないため、localhostへ取りに行って失敗する動きになる。原理的にはMasterノードの```~/.kube/config```をworkerノードへコピーすればできるはずだが、***kubectl は Master ノードで実行するのが基本***であり、あえて行う必要はない。
:::


## 切り戻し方
k8s クラスタを作ったはよいけれど、設定を間違ったから削除したい。完全削除は次のように行う。Master/Worker ノード両者で行う必要がある。

### Master ノードでの完全初期化
```console
# 1. kubeadm による初期化をリセット
ubuntu@k8s-master:~$ sudo kubeadm reset -f

# 2. 各種残存ファイルを削除（念のため）
ubuntu@k8s-master:~$ sudo rm -rf /etc/kubernetes /var/lib/etcd /var/lib/kubelet /etc/cni /opt/cni /var/lib/cni

# 3. ネットワーク設定（iptables）をクリア
ubuntu@k8s-master:~$ sudo iptables -F
ubuntu@k8s-master:~$ sudo iptables -t nat -F
ubuntu@k8s-master:~$ sudo iptables -t mangle -F
ubuntu@k8s-master:~$ sudo iptables -X

# 4. CNI のブリッジインターフェース（cni0 など）が残っていれば削除
ubuntu@k8s-master:~$ sudo ip link delete cni0 2>/dev/null
ubuntu@k8s-master:~$ sudo ip link delete flannel.1 2>/dev/null

# 5. CNI削除
ubuntu@k8s-master:~$ sudo rm -rf /etc/cni/net.d

# 6. kubeconfig 削除（kubectl 設定）
ubuntu@k8s-master:~$ rm -rf ~/.kube/config

# 7. 明示的なノード削除（kubeadm reset 後、kubectl get nodesで残っていた場合）
ubuntu@k8s-master:~$ kubectl delete node <ノード名>
```

:::note info
CNI 設定の削除
```kubeadm reset``` は ```/etc/cni/net.d``` ディレクトリを削除しません。異なる CNI プラグインを導入する場合、前の干渉する可能性があるため、手動で削除することが推奨されている。
:::


### Worker ノードでの完全初期化
```console
# 1. kubeadm join の設定を解除
ubuntu@k8s-worker1:~$ sudo kubeadm reset -f

# 2. 設定ディレクトリ等を削除
ubuntu@k8s-worker1:~$ sudo rm -rf /etc/kubernetes /var/lib/kubelet /etc/cni /opt/cni /var/lib/cni

# 3. ネットワーク関連を削除
ubuntu@k8s-worker1:~$ sudo iptables -F
ubuntu@k8s-worker1:~$ sudo iptables -t nat -F
ubuntu@k8s-worker1:~$ sudo iptables -t mangle -F
ubuntu@k8s-worker1:~$ sudo iptables -X

ubuntu@k8s-worker1:~$ sudo ip link delete cni0 2>/dev/null
ubuntu@k8s-worker1:~$ sudo ip link delete flannel.1 2>/dev/null
```

## もう少し高度は k8s 構築方法
1セットの Master/Workerノードに複数のk8s context を作りたくなる時がある。試験用のクラスタを作るとか。こういった際、kubeadm init する際に ```--config```を使用し、より詳細な設定を行うことができる。

### ```cluster-config.yaml```
ここでは k8s cluster の名前を明示してみる。色々設定できる様子だが、Pod CIDR, Cluster IP, cluster名称だけを変更する。
```yaml cluster-config.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
clusterName: shakapon-lab
kubernetesVersion: "1.33.0"
networking:
  podSubnet: 100.64.0.0/16        # Calico が使用
  serviceSubnet: 172.16.0.0/16    # ClusterIP に使用
```

https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta4/

### kubeadm init
```cluster-config.yaml```を利用して初期構築実施可能。
```console
ubuntu@k8s-master:~$ sudo kubeadm init --config=cluster-config.yaml
```
これ以降の作業は特に変わらない。

### 正常性確認
せっかくなので context が正しく設定されていることを確認する。
```console
ubuntu@k8s-master:~$ kubectl config current-context
kubernetes-admin@shakapon-lab
ubuntu@k8s-master:~$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@shakapon-lab shakapon-lab kubernetes-admin
```