K3sでクラスターを作ってみよう

# はじめに

[株式会社ピー・アール・オー Advent Calendar 2023](https://qiita.com/advent-calendar/2023/pro-japan)の1日目がなんと空いているので参戦しました。どうぞよろしくお願いします。
今回はK3sでクラスタを作成するまでを記事にします。

# K3sとは？
[K3s](https://k3s.io/)とは、Rancher Labsが開発した軽量なKubernetesディストリビューションです。
公式サイトを見ると、以下のような特徴と利点が挙げられています。

- エッジコンピューティングやIoTに最適
- 単一のバイナリファイルとしてパッケージングされているので導入が楽
- ARM用に最適

端的に述べると、とても軽量で導入が容易なコンテナオーケストレーションシステムということです。
今回はRaspberry Pi 4と、このK3sを使用し、クラスターの構築を行います。

# 動作確認環境

|端末|OS|ノードタイプ|
|---|---|---|
|Raspberry Pi 4 Model B / 8GB|Ubuntu 20.04.4 LTS|Server|
|Raspberry Pi 4 Model B / 8GB|Ubuntu 20.04.4 LTS|Agent|
|Raspberry Pi 4 Model B / 8GB|Ubuntu 20.04.4 LTS|Agent|

OSのインストール手順は[Ubuntu公式サイトのチュートリアル](https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi)に従うとよいでしょう。
# 各ノードの共通設定

## ローカルIPアドレスの固定
`99-config.yaml`を編集し、各ノードのローカルIPアドレスを固定します。

```bash
# cd /etc/netplan/
# vim 99-config.yaml
```

```yaml:99-config.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      addresses:
        - 192.168.0.16/24
      gateway4: 192.168.0.1
      nameservers:
          addresses: [8.8.8.8]
```

以下のコマンドで適用します。
```bash
# netplan apply
```

我が家の環境ではそれぞれ以下の通りに設定しました。

- Server
	- 192.168.0.16
- Agent1
	- 192.168.0.17
- Agent2
	- 192.168.0.18
## ホストネーム設定
各ノードにホスト名をユニークになるよう設定します。
これは、K3sはデフォルトでホスト名を kubernetes ノード名として使用するためです。
```bash
# hostnamectl set-hostname <ホスト名>
```

## NFSのインストール
必要に応じ、NFSをインストールします。
PersistentVolumeを使う場合などは用意しておいた方がよいでしょう。
```bash
# apt install nfs-common
```

以下のコマンドで外付けHDDをマウントします。
```bash
# mount -t nfs 192.168.0.4:/external-hdd/ /mnt/ -o hard,intr
```
## OSの日本語化
必須ではないですが、日本語化しておきます。
```bash
# apt -y install language-pack-ja-base language-pack-ja ibus-kkc
# localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"
# source /etc/default/locale
# echo $LANG
```

## WireGuardのインストール
WireGuardをFrannelのバックエンドとして使用することで、クラスター内の通信を暗号化することができ、よりセキュアな運用を行うことができます。
ここでは、クラスター内の全ての端末にWireGuardをインストールし、適用する手順を記載します。

```
# apt install wireguard -y
```

### キーペアの作成
キーペアを作成します。このキーペアの内容を設定ファイルに記載します。
```bash
# wg genkey | tee wg.key | wg pubkey > wg-pub.key
```

### 設定ファイルの作成
`/etc/wireguard/wg0.conf`を作成します。
我が家の環境では、それぞれ以下のIPアドレスを利用するようにしました。
- Server
	- 172.16.0.1
- Agent1
	- 172.16.0.2
- Agent2
	- 172.16.0.3
なお、WireGuardのインターフェース名は`wg0`としています。
#### サーバーの場合 
```:wg0.conf(Server)
[Interface]
Address = 172.16.0.1
ListenPort = 51871
PrivateKey = <サーバーで作成した秘密鍵>

[Peer]
PublicKey = <エージェント1で作成した公開鍵>
AllowedIPs = 172.16.0.2

[Peer]
PublicKey = <エージェント2で作成した公開鍵>
AllowedIPs = 172.16.0.3
```
#### エージェントの場合 
```:wg0.conf(Agent1)
[Interface]
Address = 172.16.0.2
ListenPort = 51871
PrivateKey = <エージェント1で作成した秘密鍵>

[Peer]
PublicKey = <サーバーで作成した公開鍵>
Endpoint = 192.168.0.16:51871
AllowedIPs = 172.16.0.1
```

```:wg0.conf(Agent2)
[Interface]
Address = 172.16.0.3
ListenPort = 51871
PrivateKey = <エージェント2で作成した秘密鍵>

[Peer]
PublicKey = <サーバーで作成した公開鍵>
Endpoint = 192.168.0.16:51871
AllowedIPs = 172.16.0.1
```
### WireGuardの起動
全てのサーバー、エージェントで自動起動を有効にし、起動します。
```
# systemctl enable wg-quick@wg0
# systemctl start wg-quick@wg0
```
# サーバー側作業

## K3sのインストール作業

### ファイアウォールの設定
以下のポートを開けます。UFWを使用している場合の例です。
```bash
# ufw enable
# ufw allow 22
# ufw allow 6443/tcp
# ufw allow 7946/tcp
# ufw allow 10250/tcp
# ufw allow 51820/udp
# ufw allow 51871/udp
```

### メモリサブシステムの有効化
`/boot/firmware/cmdline.txt`を編集し、行末に`cgroup_memory=1 cgroup_enable=memory`を追加します。
これにより、cgroupのメモリサブシステムを有効化します。
```:cmdline.txt
elevator=deadline net.ifnames=0 console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash cgroup_memory=1 cgroup_enable=memory
```

追記後、マシンをrebootしてください。
```
# reboot -h now
```

### K3sインストールスクリプトの実行
K3sのインストールスクリプトを実行し、サーバーとしてインストールします。
デフォルトでいくつかのPodが自動的にデプロイされるようになっていますが、`--disable`オプションを付与することで無効化することができます。
対象は`coredns, servicelb, traefik, local-storage, metrics-server`となります。
なお、以前のバージョンでは`--no-deploy`で無効化することができましたが、名称が変わっています。旧称で指定するとエラーとなるので注意が必要です。
また、我が家の環境はIPv6 over IPv4に固定IPオプションを利用しています。
そのため、`--tls-san`オプションを付与し、明示的に割り当てられた固定IPをAPIサーバーの証明書のSANに設定しています。(ローカルIPも一応指定しています)
これを指定しないと、IPv6アドレスのみSANに設定されてしまい、固定IPでアクセスした際、証明書の検証に失敗してしまうためです。

```bash
# curl -sfL https://get.k3s.io | sh -s - --disable traefik,local-storage,servicelb --node-name master --tls-san 固定したIPv4アドレス(グローバル) --tls-san 192.168.0.16 --tls-san 127.0.0.1`
```

### トークンの確認
インストール後、サーバーのトークンを取得します。
このトークンはエージェントのインストール時に使用するため、値を控えておきます。
```bash
# cat /var/lib/rancher/k3s/server/node-token
```

### Systemdサービスの編集
`/etc/systemd/system/k3s.service`を編集し、起動オプションと、WireGuardの設定を追加しておきます。
```service:k3s.service
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=notify
EnvironmentFile=-/etc/default/%N
EnvironmentFile=-/etc/sysconfig/%N
EnvironmentFile=-/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
    server \
        '--disable' \
        'traefik,local-storage,servicelb' \
        '--node-name' \
        'server' \
        '--tls-san' \
        '固定したIPv4アドレス(グローバル)' \
        '--tls-san' \
        '192.168.0.16' \
        '--tls-san' \
        '127.0.0.1' \
        '--flannel-backend' \
        'wireguard-native' \
        '--flannel-external-ip' \
        '--flannel-iface' \
        'wg0' \
```

Unitファイルの変更を反映し、K3sを再起動します。
```bash
# systemctl daemon-reload
# systemctl restart k3s
```

# エージェント側作業
本環境ではエージェントを2台用意するため、それぞれの端末に対して手順を実施しています。
## K3sのインストール作業
### ファイアウォールの設定

以下のポートを開けます。UFWを使用している場合の例です。
```bash
# ufw enable
# ufw allow 22
# ufw allow 7946/tcp
# ufw allow 10250/tcp
# ufw allow 51820/udp
# ufw allow 51871/udp
```

### メモリサブシステムの有効化
サーバーと同様に、`/boot/firmware/cmdline.txt`を編集し、行末に`cgroup_memory=1 cgroup_enable=memory`を追加します。
これにより、cgroupのメモリサブシステムを有効化します。
```bash:cmdline.txt
elevator=deadline net.ifnames=0 console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash cgroup_memory=1 cgroup_enable=memory
```

### K3sインストールスクリプトの実行
K3sのインストールスクリプトを実行し、エージェントとしてインストールします。
`K3S_TOKEN`にはサーバーから取得したトークンを指定します。
また、`node-name`はクラスタ内でユニークなものにしてください。
```bash
# curl -sfL https://get.k3s.io | K3S_URL=https://192.168.0.16:6443 K3S_TOKEN="サーバーから取得したトークン" sh -s - --node-name agent
```

# 各ノードの確認
`kubectl`コマンドでノードを確認すると、クラスタの構築を確認することができます。
ちなみに、Kubeconfigは`/etc/rancher/k3s/k3s.yaml`に出力されています。
```bash
# kubectl get nodes
NAME      STATUS   ROLES                  AGE    VERSION
agent2   Ready    <none>                 209d   v1.26.4+k3s1
server   Ready    control-plane,master   209d   v1.26.4+k3s1
agent1   Ready    <none>                 209d   v1.26.4+k3s1
```

# おわりに
今回はクラスタの構築で一旦終了となりますが、この後時間があれば、実際にPodを載せるところまで別記事に書ければと思っています。