Open62

おうちKubernetes2024実況

matorurumatoruru

この2箇所にはヒートシンクを貼れません!貼るとPoE HATと干渉して取り付け不可になります!

※万が一貼ってしまったり、PoE HATが途中で止まってしまっても両方慎重にやれば手で取り外せます

matorurumatoruru

ここのヒートシンクもこんな具合にずらして貼らないとPoE HATのネジと干渉します

matorurumatoruru

3台分のPoE HAT接続完了!!

次はMicro SDにUbuntuをインストールしていきます。

モニタとケーブルがないので、WindowsからRaspberry PiにSSHで接続して起動確認したいです。初回の起動確認だけなのでセキュリティは無視のパスワード認証でログインできる設定にします。設定方法は次のコメントで

matorurumatoruru

こちらのRaspberry Pi Imagerを使ってMicro SDカードにUbuntuを焼いていきます。Raspberry Pi Imagerのバージョンは1.8.4でした。

デバイスはRaspberry Pi 4
OSは"Other general-purpose OS">Ubuntu>"Ubuntu Server 22.04.3 LTS(64-bit)
を選択します。

NEXTボタンを押して、EDIT SETTINGSを押します。次のOS Customization画面では

GENERALタブ

Set hostname: raspi01
Set username and password: matoruru/password
Configure wireless LAN: 無効
Set locale settings: Asia/Tokyo

SERVICESタブ

Enable SSH: Use password authentication

に設定し、SAVE。

Would you like to apply OS customization settings?にYes>Yesを押して焼きこみ開始。

matorurumatoruru

ルータとPoE SwitchとRaspberry Piをこんな感じで繋いで...

パソコンを同ルータにWi-Fi接続します(LAN内で完結するのでインターネットとは繋がってなくてOK)。

matorurumatoruru

arp -aでRaspberry Piを検出できなかったので、PCのIPアドレスを確認してそれに続く連番のアドレスあたりにpingをしてから、ヒットしたものにssh接続をトライしていきます。

> ipconfig
...
IPv4 Address. . . . . . . . . . . : 192.168.11.3
...

> ping 192.168.11.4
(応答なし)

> ping 192.168.11.5
Reply from 192.168.11.5: bytes=32 time=1ms TTL=64
Reply from 192.168.11.5: bytes=32 time=1ms TTL=64

> ping 192.168.11.6
Reply from 192.168.11.6: bytes=32 time=1ms TTL=64
Reply from 192.168.11.6: bytes=32 time=1ms TTL=64

なぜ11.4が欠番になっていたのかはいまいち不明ですが、上の結果から5番と6番にSSHをトライしていきます。

> ssh matoruru@192.168.11.5
The authenticity of host '192.168.11.5 (192.168.11.5)' can't be established.
ED25519 key fingerprint is SHA256:liv0M6v+BC68760fZxqW69T3WydQ7i3vvPyXgO0TT+U.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.11.5' (ED25519) to the list of known hosts.
matoruru@192.168.11.5's password:

来ました!パスワードを打つと無事SSHログインできました。

matoruru@raspi01:~$ whoami
matoruru
matoruru@raspi01:~$ echo Hello, world!
Hello, world!

来ました!残りのRaspberry Piにも同じように接続します。

追記(同日)

for /l %i in (0,1,50) do ping -w 1 -n 1 192.168.11.%i

でarpテーブルを更新して、各IPを発見できます。50は255でもいいけどそんなに大きい数字が振られそうでなければ適宜減らしましょう。11も環境により異なります。

追記(2/2)

SSHのパスワード認証を無効にして、公開鍵認証に切り替えました。以下に流れを残しておきます。

クライアントで以下のコマンドを実行

ssh-keygen -t ed25519 # 全部エンターで飛ばしてOK(気になる方は適宜入力)
ssh-copy-id -i ~/.ssh/id_ed25519.pub matoruru@100.64.1.101

以下の内容をクライアント上の~/.ssh/configに追加する(なければ新規作成)

Host raspi01
  HostName 100.64.1.101
  User matoruru
  IdentityFile ~/.ssh/id_ed25519

今後はssh raspi01で安全に接続できる!

パスワード認証を無効にする(オプション)

この手順は必須ではないですが、認証方法を公開鍵認証のみにすることでセキュリティ強度を上げることができます。

⚠️もし失敗した場合リモート接続することが大変困難になるため、万が一の時に作業を切り戻せるように、実施する場合は必ず別コンソールでSSH接続を保持したまま行なうこと⚠️

マスターノードのマシンにSSH接続して、/etc/ssh/sshd_config.d/50-cloud-init.confPasswordAuthentication yesPasswordAuthentication noに書き換えて保存します。その後sudo systemctl restart sshdでSSHのサービスを再起動します。

ここで本当にパスワード認証が無効になっているのか確かめたいですが、公開鍵認証を設定済みなのでパスワードの入力を求められることなくログインしてしまいます。そのため、一旦秘密鍵の名前を変えて無効化してからパスワードでのログインを試みてみます。

> mv ~/.ssh/id_ed25519 ~/.ssh/id_ed25519_
> ssh raspi01
ssh raspi01
no such identity: /Users/matoruru/.ssh/id_ed25519: No such file or directory
matoruru@100.64.1.101: Permission denied (publickey).

無事パスワードを求められることなく弾かれました!今後は秘密鍵を所持していないとマスターノードのマシンにログインできません!

このままだと公開鍵認証もできないので、先ほど壊した秘密鍵ファイルを元に戻しておきます。

mv ~/.ssh/id_ed25519_ ~/.ssh/id_ed25519
matorurumatoruru

とりあえず全端末の起動・接続確認が済んだのでクラスタを組み上げていきます

matorurumatoruru

電源ぶち切るのはお行儀が悪いので各端末にログインしてsudo poweroffでおねんねしてもらいます。

matorurumatoruru

もし4台目が来た場合は上に積み上げていくことになるので、下からホスト名raspi01、raspi02、..と積み上げていきます

matorurumatoruru

底面になるアクリル板だけファンの穴がないので留意しておく

matorurumatoruru

上から見た図。ファンの一部を覆ってしまっている。結構ずれるなあ🤔

matorurumatoruru

どの端末に接続しているかわからなくなった時は、適当にキーボード連打してキー入力を送ればLANケーブルのあたりが点滅するので特定できます

matorurumatoruru

ここからはついにKubernetesの設定に取り掛かろうと思う、がその前にごはん...

matorurumatoruru

固定IPの設定方法

現在のIPとサブネットを調べる

> ip a
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether d8:3a:dd:88:37:4c brd ff:ff:ff:ff:ff:ff
    inet 100.64.1.53/22 metric 100 brd 100.64.3.255 scope global dynamic eth0
       valid_lft 603768sec preferred_lft 603768sec
    inet6 2400:4050:a743:5700:da3a:ddff:fe88:374c/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 2591812sec preferred_lft 604612sec
    inet6 fe80::da3a:ddff:fe88:374c/64 scope link
       valid_lft forever preferred_lft forever

より、現在のIPは100.64.1.53でサブネットマスクは/22

ネームサーバを調べる

> cat /etc/resolv.conf | grep nameserver
nameserver 127.0.0.53

デフォルトゲートウェイを調べる

< ip route | grep default
default via 100.64.1.1 dev eth0 proto dhcp src 100.64.1.54 metric 100

より、100.64.1.1

集めた情報を使って設定ファイルを編集作成する

デフォルトの/etc/netplan/50-cloud-init.yamlを弄るのはお行儀が良くないらしいので、99-fixed-ip.yamlを作成してデフォルト設定を上書きすることにする。

> cd /etc/netplan
> sudo vim 99-fixed-ip.yaml

内容は以下の通り。

network:
    ethernets:
        eth0:
            dhcp4: false
            addresses: [100.64.1.101/22]
            nameservers:
              addresses: [8.8.8.8]
            routes:
            - to: default
              via: 100.64.1.1
    version: 2

このあとsudo rebootで再起動すれば同ラズパイ端末のIPが100.64.1.101に固定される。
ほかの端末にも同様に102、103の連番で設定していく。

matorurumatoruru
> arp -a
Interface: 100.64.1.31 --- 0x11
  Internet Address      Physical Address      Type
  ...
  100.64.1.101          d8-3a-dd-88-37-4c     dynamic
  100.64.1.102          d8-3a-dd-88-35-9d     dynamic
  100.64.1.103          d8-3a-dd-88-5b-59     dynamic
  ...

IPアドレスを固定できた!

インターネットにアクセスできるかどうかも確認しておこう。

curl google.com
matorurumatoruru

スワップを無効化する

sudo swapoff -a

IPv4フォワーディングを有効化し、iptablesからブリッジされたトラフィックを見えるようにする

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# この構成に必要なカーネルパラメーター、再起動しても値は永続します
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
matorurumatoruru

pstreeでinitシステムを確認

matoruru@raspi01:~$ pstree
systemd─┬─2*[agetty]
        ├─bluetoothd
        ├─cron
        ├─dbus-daemon
        ├─hciattach
        ├─irqbalance───{irqbalance}
        ├─multipathd───6*[{multipathd}]
        ├─networkd-dispat
        ├─rsyslogd───3*[{rsyslogd}]
        ├─snapd───13*[{snapd}]
        ├─sshd───sshd───sshd───bash───pstree
        ├─systemd───(sd-pam)
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-network
        ├─systemd-resolve
        ├─systemd-timesyn───{systemd-timesyn}
        ├─systemd-udevd
        ├─unattended-upgr───{unattended-upgr}
        └─wpa_supplicant

systemdでした。

matorurumatoruru

パッケージの更新

sudo apt update && sudo apt upgrade

containerdのインストール

sudo apt install containerd
matorurumatoruru

containerd config defaultのコマンドでcontainerdのデフォルト設定を取得できるようなので、

sudo sh -c 'mkdir /etc/containerd && containerd config default > /etc/containerd/config.toml'

で当該ファイルを作成。

matorurumatoruru

下記のコマンドを使って、上で生成したファイルのSystemdCgroup = falseSystemdCgroup = trueに変更。

sudo sed -ie 's/\(\s* SystemdCgroup = \)false.*/\1true/' /etc/containerd/config.toml

そして

sudo systemctl daemon-reload
sudo systemctl enable --now containerd

でcontainerdを自動起動設定を有効にし、起動する。

matorurumatoruru

kubeadm、kubelet、kubectlをインストールする

このページに従う。

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://dl.k8s.io/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | 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
matorurumatoruru

kubeletが使用するcgroupドライバの設定

Ubuntuではsystemdを使っているので以下のように設定する。

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

kubeletの再起動

sudo systemctl daemon-reload
sudo systemctl restart kubelet
matorurumatoruru

ワーカーノードの1台にもkubeadm、kubelet、kubectlをインストールした

matorurumatoruru

マスターノードで

sudo kubeadm init

を実行。クラスタ起動まで1〜2分くらいかかる。

matorurumatoruru

kubectlをnon-rootユーザで実行できるように以下のコマンドを実行する

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

試しにkubectlを実行

> kubectl get nodes
NAME      STATUS     ROLES           AGE     VERSION
raspi01   NotReady   control-plane   3m14s   v1.28.2

動いた!!!🎉

matorurumatoruru

マスターノードでkubectl get nodesを実行してノードの参加状況を確認。

kubectl get nodes
NAME      STATUS     ROLES           AGE   VERSION
raspi01   NotReady   control-plane   16m   v1.28.2
raspi02   NotReady   <none>          63s   v1.28.2

入ってきた!!!

matorurumatoruru

原因調査のためkubectl describe nodes raspi01でマスターノードの状態を確認したところ、

container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized

というメッセージを確認できた。

CNIプラグインをインストールする必要があるみたい。ドキュメント見落としかな・・

matorurumatoruru

マスターノードで以下のコマンドを実行してCNIプラグインFlannelをインストール。

> kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
namespace/kube-flannel created
serviceaccount/flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
matorurumatoruru
> kubectl get nodes
NAME      STATUS   ROLES           AGE   VERSION
raspi01   Ready    control-plane   32m   v1.28.2
raspi02   Ready    <none>          16m   v1.28.2

Readyになった!!!

matorurumatoruru

FlannelからCalicoへ移行

Flannelはだいぶ前にKubernetesのドキュメントから消えたらしく、Network Policyにも非対応で非推奨のように見えるので、一旦クラスタを削除して別のCNIプラグインを入れたい。

Calicoを入れようと思う(FlannelのGitHubにもNetwork Policy使いたいならCalico等にしろって書いてある)

matorurumatoruru

nodeの削除 (コントロールプレーンノードで行う)

> kubectl drain raspi02 --delete-local-data --force --ignore-daemonsets
> kubectl delete node raspi02
node "raspi02" deleted
> kubectl get nodes
NAME      STATUS   ROLES           AGE   VERSION
raspi01   Ready    control-plane   2d    v1.28.2

kubeadmの設定のリセット(ワーカーノードのマシンで行う)

sudo kubeadm reset
sudo rm -rf /etc/cni/net.d/

kubeadmの設定のリセット&初期化(コントロールプレーンのマシンで行う)

sudo kubeadm reset
sudo rm -rf /etc/cni/net.d/
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

ワーカーノードの追加

kubeadm initの実行ログに表示されたkubeadm joinコマンドをワーカーノードのマシン上で実行しておく。

ノードの確認

> kubectl get nodes
NAME      STATUS     ROLES           AGE    VERSION
raspi01   NotReady   control-plane   2m9s   v1.28.2
raspi02   NotReady   <none>          74s    v1.28.2

初期化完了!

matorurumatoruru

Calico CNI Pluginをインストールする

https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart#install-calico
に従う。

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml
# 10.244.0.0/16は`kubeadm init`のときに使ったランダムなCIDR
curl -s https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/custom-resources.yaml | sed 's/192.168.0.0\/16/10.244.0.0\/16/' | kubectl create -f -

インストールしたPodがRunningになるまで待つ

Statusを眺めるコマンド

watch kubectl get pods -n calico-system

いつまで経ってもRunningにならない・・・

> kubectl -n calico-system get all
NAME                                           READY   STATUS              RESTARTS   AGE
pod/calico-kube-controllers-566964964d-g4rv2   0/1     Pending             0          16m
pod/calico-node-l9wh7                          1/1     Running             0          37m
pod/calico-typha-c96b57dcb-sfnhc               1/1     Running             0          37m
pod/csi-node-driver-fmc8p                      0/2     ContainerCreating   0          37m

こちらの記事を参考にlinux-modules-extra-raspiをインストールし、ラズパイを再起動してみる(ワーカーノードのマシンにも必要)。

sudo apt install -y linux-modules-extra-raspi
sudo reboot
> kubectl get pods -n calico-system
NAME                                       READY   STATUS    RESTARTS       AGE
calico-kube-controllers-566964964d-g4rv2   1/1     Running   0              22m
calico-node-l9wh7                          1/1     Running   1 (2m8s ago)   43m
calico-typha-c96b57dcb-sfnhc               1/1     Running   1 (2m8s ago)   43m
csi-node-driver-fmc8p                      2/2     Running   0              43m

ついにCalicoをデプロイできた!!

> kubectl get nodes
NAME      STATUS   ROLES           AGE   VERSION
raspi01   Ready    control-plane   46m   v1.28.2

ノードもReadyになった。

ワーカーノードをjoinさせたいが、kubeadm起動直後のjoinコマンドをロストしたため、以下のコマンドで再生成してもらう(生成されたコマンドをワーカーノードのマシンで実行する)。

kubeadm token create --print-join-command

ワーカーノードを追加してしばらくするとワーカーノード側にも自動的にcalicoのPodが配置されて、ノードもReadyになる。

> kubectl -n calico-system get pod -o wide
NAME                                       READY   STATUS    RESTARTS        AGE   IP               NODE      NOMINATED NODE   READINESS GATES
calico-kube-controllers-566964964d-g4rv2   1/1     Running   0               39m   26.125.95.195    raspi01   <none>           <none>
calico-node-j9nkc                          1/1     Running   1 (2m29s ago)   12m   100.64.1.102     raspi02   <none>           <none>
calico-node-l9wh7                          1/1     Running   1 (18m ago)     60m   100.64.1.101     raspi01   <none>           <none>
calico-typha-c96b57dcb-sfnhc               1/1     Running   1 (18m ago)     60m   100.64.1.101     raspi01   <none>           <none>
csi-node-driver-fmc8p                      2/2     Running   0               60m   26.125.95.196    raspi01   <none>           <none>
csi-node-driver-xkgtw                      2/2     Running   0               12m   26.125.133.129   raspi02   <none>           <none>
matorurumatoruru

試しにサンプル用のDeploymentファイルを使ってNginxのPodを5つデプロイしてみる。

kubectl apply -f https://raw.githubusercontent.com/matoruru/playground/main/playground-nginx-deployment-sample/multi-nginx-pods.yaml
> kubectl get pods -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-687f7bcd54-2dfsd   1/1     Running   0          83s   26.125.95.7      raspi03   <none>           <none>
nginx-deployment-687f7bcd54-7bfxq   1/1     Running   0          83s   26.125.133.133   raspi02   <none>           <none>
nginx-deployment-687f7bcd54-ms4b9   1/1     Running   0          83s   26.125.133.132   raspi02   <none>           <none>
nginx-deployment-687f7bcd54-n2v7m   1/1     Running   0          83s   26.125.95.9      raspi03   <none>           <none>
nginx-deployment-687f7bcd54-tb9pz   1/1     Running   0          83s   26.125.95.8      raspi03   <none>           <none>

ちゃんと各ワーカーノードに分散配置されていることがわかる!

matorurumatoruru

ここまでのゴチャついた作業履歴は別記事にまとめて近々投稿します😀

matorurumatoruru

MetalLBでロードバランサーは導入済みだが、各サービスごとにExternal IPを振るのはイケてない感じがするので、Nginx Ingress Contrllerを導入してそこへトラフィックを集約、そこからIngressのルールに従って各サービスへ振り分ける構成にする

matorurumatoruru

Nginx Ingress Controllerのインストールは簡単で、単にhttps://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yamlをapplyするだけ。

Kustomize形式でGitOpsなどしたい場合はKustomizationファイルのresourcesから読み込む。

matorurumatoruru

Hosted zonematoru.ruにAレコードでサブドメインk8s.を設定してみた。トラフィック先は192.168.11.200。WiFiに接続している時だけ、インターネットを経由してクラスタにアクセスできることを試したい

matorurumatoruru
> dig k8s.matoru.ru

; <<>> DiG 9.16.1-Ubuntu <<>> k8s.matoru.ru
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22856
;; flags: qr rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;k8s.matoru.ru.         IN A

;; ANSWER SECTION:
k8s.MATORU.ru.    0  IN A  192.168.11.200

きた!!

matorurumatoruru

リソース監視のためにPixieを導入してみた!Self-hosted版もあったがまずはお試しのために導入が簡単なクラウド版を使ってみる。

matorurumatoruru

kubeadmでクラスタを作成するとPersistentVolumeとStorageClassが作成されてないので、StatefulSetを作れない。それぞれ作成する必要がある。手順↓

  1. AzureのStorage Account作成