Translate

2023年2月14日火曜日

Raspberry Pi OS bullseye/64bit を使って Kubernetes クラスタを構築

 Kubernetesを勉強するためにPlay with Kubernetesを使っていたのだけど..そもそもどんな構成になっているのかが気になり、余ったRaspberry Pi4B(2GB)とRaspberry Pi3B+(1GB)×3で作ってみた。


 

先に書いておくとコントロールプレーン/マスターノードにするRaspberry Piはメモリが2GB以上ないと動作しない。実は全部3B+(1GB)で色々こねくり回してみたのだけど、kubelet を動かすところで失敗してしまった。

ただ..後述の追記を読んでもらえるとわかると思いますが、運用するとワーカーノードにした3B+はバンバン落ちる..正直flannelのノードを維持することすら..4B 4GB秋月で14,800円(2023/2/15時点)..もっと待てば下がると思うんだよなあ..まだ上がるかもしれないしなあ..試しに動かすだけなのに購入するのは..ということで、まだ購入してません

自宅でやったので、いわゆる家庭用WiFiルータを使っている。デフォルトで192.168.11.0/24ネットワークをDHCPで割り当てる設定だが、DHCP 割当対象外の IP アドレスを固定 IP として割り当て直している。

有線LANの方は使用しなかった。CloudStackの経験から2つのNICをつかえたらいいんじゃとwlan0eth0両方有効にしてはじめたが、とたんにセットアップの難易度が上がってしまい、結局WiFiのみにしてしまった。

以下、セットアップの手順である。

動作確認は2023/2/13時点なので、最新バージョンをいれているところで、動作が変わっている場合がある。参考にする場合は注意のこと。

※上図の構成は予め組んでいる状態とします。

Raspberry Pi OS のセットアップ

  • WiFi接続したPCからpingを打ち、レスポンスのないIPアドレスをメモ
  • OSイメージを書き込んだSDカードを4枚作成
    https://www.raspberrypi.com/software/ からRaspberry Pi Imagerをダウンロード
  • SDカードを刺す(フォーマット)
  • Raspberry Pi OS Imagerを起動
  • OSイメージはRaspberry Pi OS Lite 64bitを選択

試行したバージョンは、bullseye 2022/9/22版 64bit

  • ホスト名(node01~04)、SSH、WiFi、ロケール・キーボードを設定し書き込みを押下
  • 1台づつ書き込んだSDカードをRaspberry Pi に刺し起動、WiFi接続したPCからpingを再度打ちDHCP割り当てされたIPアドレスをメモ

Kubernetesのセットアップ

TerminalソフトウェアからメモしたIPアドレスへSSH接続し、以下の作業を実行

cgroup 有効化

sudo sed -i 's/$/ cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory/g' /boot/cmdline.txt

NTP 設定

  • sudo vi /etc/systemd/timesyncd.conf を実行し、以下の行を `[Time]` 内に記述、保存
NTP=ntp.jst.mfeed.ad.jp ntp.nict.jp
FallbackNTP=time.google.com

  • 以下のコマンドを実行

sudo timedatectl set-ntp true
sudo systemctl daemon-reload

ファイルシステム領域の拡張

sudo raspi-config  --expand-rootfs

固定IPアドレス指定

  • WiFiルータ(DHCP)から割り当てられたIPアドレスが XXX.YYY.ZZZ.AAA である場合、node01~node04までの固定IPアドレス割り当てされない範囲でアドレスを決める
  • ここでは node01 ~ node04 に XXX.YYY.ZZZ.201 ~ XXX.YYY.ZZZ.204 を割り当てることとする
  • WiFiルータのIPアドレスは XXX.YYY.ZZZ.1/24 とする
  • sudo vi /etc/dhcpcd.conf を実行し、以下のように `wlan0` 設定を変更、保存する (以下の記述は node01 の場合)

interface wlan0
static ip_address=XXX.YYY.ZZZ.201/24
static routers=XXX.YYY.ZZZ.1
static domain_name_servers=XXX.YYY.ZZZ.1

hosts 設定

  • sudo vi /etc/hosts を実行し、固定IPアドレスを記述、保存

XXX.YYY.ZZZ.201    node01
XXX.YYY.ZZZ.202    node02
XXX.YYY.ZZZ.203    node03
XXX.YYY.ZZZ.204    node04

固定IPアドレスでSSH接続

  • sudo reboot を実行し、再起動
  • Terminal ソフトウェアで、割り当てた固定IPアドレスでSSH接続

OS/パッケージ の最新化

sudo apt update && sudo apt dist-upgrade -y
sudo apt update && sudo apt upgrade -y

スワップの無効化

sudo swapoff --all
sudo systemctl stop dphys-swapfile
sudo systemctl disable dphys-swapfile

Kubernetes 事前設定

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

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sudo apt install -y iptables arptables ebtables
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy

sudo sysctl --system

cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

OS 再起動

  • sudo rebootTerminal ソフトウェアで、割り当てた固定IPアドレスでSSH接続

CRIO のインストール

OS=Raspbian_10
VERSION=1.24
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
OS=Raspbian_11
VERSION=1.24
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -

sudo apt update
sudo apt upgrade -y
sudo apt install -y cri-o cri-o-runc
sudo systemctl daemon-reload
sudo systemctl enable crio
sudo systemctl start crio

執筆時点(2023/2/13)で、Raspbean_11のcontainerdがなかった。もうできているかもしれない..

Kubernetes のインストール

sudo apt update && sudo apt 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 update
sudo apt upgrade -y
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
cat <<EOF | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS=--container-runtime-endpoint='unix:///var/run/crio/crio.sock'
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet

マスターノードの設定

  • node01 にて以下のコマンドを実行

sudo kubeadm init --apiserver-advertise-address=192.168.11.201 --pod-network-cidr=10.244.0.0/16

  • 表示されている以下の箇所をメモ(以下は、実行時のコンソール表示例)

pi@node01:~ $ sudo kubeadm init --apiserver-advertise-address=192.168.11.201 --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.26.1
[preflight] Running pre-flight checks
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local node01] and IPs [10.96.0.1 XXX.YYY.ZZZ.201]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost node01] and IPs [192.168.11.201 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [localhost node01] and IPs [192.168.11.201 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 34.004780 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node node01 as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node node01 as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: xua9wk.gwe59et1dix4dw1q
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

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.11.201:6443 --token xua9wk.gwe59et1dix4dw1q \
        --discovery-token-ca-cert-hash sha256:4775e46c92965e5fc383731aa58429bb1d1246af22e85768bf8474f5869a4666

config 設定

  • node01 にて以下のコマンドを実行

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

以降、node01では sudo なして kubectl が実行可能になる

ワーカーノードの設定

ワーカーノードのjoin

  • 全ワーカーノードに対して、先の kubeadm init コマンド実行結果最後に表示されたコマンドを sudo をつけて実行

sudo kubeadm join 192.168.11.201:6443 --token xua9wk.gwe59et1dix4dw1q \
        --discovery-token-ca-cert-hash sha256:4775e46c92965e5fc383731aa58429bb1d1246af22e85768bf8474f5869a4666

  • マスターノードにて kubectl get nodes を実行し、全ワーカーノードがReadyであることを確認(以下、実行例)

pi@node01:~ $ kubectl get nodes
NAME     STATUS   ROLES           AGE    VERSION
node01   Ready    control-plane   3m7s   v1.26.1
node02   Ready    <none>          111s   v1.26.1
node03   Ready    <none>          74s    v1.26.1
node04   Ready    <none>          72s    v1.26.1
pi@node01:~ $
kubectl get pod -A
NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-787d4945fb-6brdq         1/1     Running   0          3m38s
kube-system   coredns-787d4945fb-fm7bs         1/1     Running   0          3m38s
kube-system   etcd-node01                      1/1     Running   0          3m49s
kube-system   kube-apiserver-node01            1/1     Running   0          3m49s
kube-system   kube-controller-manager-node01   1/1     Running   0          3m53s
kube-system   kube-proxy-254ht                 1/1     Running   0          3m38s
kube-system   kube-proxy-9dtjv                 1/1     Running   0          2m38s
kube-system   kube-proxy-p9qmf                 1/1     Running   0          2m1s
kube-system   kube-proxy-q24qn                 1/1     Running   0          119s
kube-system   kube-scheduler-node01            1/1     Running   0          3m49s
pi@node01:~ $
kubectl get ns -A
NAME              STATUS   AGE
default           Active   4m12s
kube-node-lease   Active   4m14s
kube-public       Active   4m14s
kube-system       Active   4m14s
pi@node01:~ $

Flannelのインストール

  • コントロールプレーンノード(node01)上にて、以下のコマンドを実行し最新版の Flannel をデプロイする。
pi@node01:~ $ 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
pi@node01:~ $


PodのCIDR指定を10.244.0.0/16以外にした場合、直接kubectl applyしないで、node01上にyamlをcurl保管してネットワーク指定を変更する必要がある、とflannelドキュメントにかかれている。

 

2023/2/15 注記:

ワーカノード(Raspberry Pi 3B+)を参加(kubeadm join)させた状態で、flannelを以下の手順でkubectl applyすると、 OSが突然落ちますkubectl deleteして、ワーカノードを再起動すると動作します。

flannelは全ワーカノードにコンテナをあげるので、おそらくメモリ1GBだとそれらを上げきることはできないのだとおもいます。

 

 

Swapの再有効化

Swapを無効化するのは、Kubernetesをインストールする際の前提となっている。しかし今回のワーカノードはRaspberry Pi 3B+なので、メモリが1GBしかない。

なのでここではワーカノードだけSwapを再有効化しておく。

  • sudo vi /var/lib/kubelet/config.yaml を実行し、最終行に以下の行を追加、保存
failSwapOn: false
  • 以下のコマンドを実行する
sudo systemctl daemon-reload
sudo systemctl enable dphys-swapfile
sudo systemctl start dphys-swapfile
sudo systemctl restart kubelet

 

2023/2/15 追記:

ワーカノードのSwapを再度有効化した状態でkubeadm joinをかけると以下のようなエラーがでて動作しませんでした。

pi@node02:~ $ sudo kubeadm join 192.168.11.201:6443 --token 0hfvvz.63g939bbbv4iuoqz \
        --discovery-token-ca-cert-hash sha256:e4fad29f4cadb1fc68464bbae4767e18766365d1fa8c46678301c26a1f0911c7
[preflight] Running pre-flight checks
        [WARNING Swap]: swap is enabled; production deployments should disable swap unless testing the NodeSwap feature gate of the kubelet
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
[kubelet-check] Initial timeout of 40s passed.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.

Unfortunately, an error has occurred:
        timed out waiting for the condition

This error is likely caused by:
        - The kubelet is not running
        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)

If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
        - 'systemctl status kubelet'
        - 'journalctl -xeu kubelet'
error execution phase kubelet-start: timed out waiting for the condition
To see the stack trace of this error execute with --v=5 or higher

このあとSwap無効化してkubeadm resetしたあと、再度kubeadm joinしたらkubectl get nodeにReadyで登場するようになりました。


以上

Dockerの場合、ネットワークを管理するコンテナを隠しでいれてくれているので、意識しなくても良かったが、Kubernetesの場合はインストールする側がきちんと選んでインストールしないといけない。ドキュメントによると、ネットワークポリシーを適用するCalicoなどがあるが、このあたりはサードパーティ製になるので、Kubernetesの責務の外というかんじのようだ。

CloudStackやOpenStack、Docker (Swarm)と学習していると、オーケストレーションを実現する思想的なものの違いがなんとなく見えてくる。こういったソフトウェアの責任範囲の切り分けがどこにあるかを見極められると、理解が少し早くなるのだと思う。

 再度書きますが、Raspberry Pi 3B+ をkubernetesクラスタに加えることは、ほぼできないと考えておいたほうが良いです。インストールはできますが、それまでです。書籍の学習でちょっとうごかしてみよう的な環境でも落ちるかもしれません。そうしたらsyslogやjouralctlとにらめっこが始まってしまい、学習は中断..という風になるでしょう。そういう場合はDocker Desktop(いつまでKubernetesが動くか..)やplay with kubernetes 環境(いつまで..再び)を使いましょう。

 

 

0 件のコメント:

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...