Raspberry PiでおうちKubernetesクラスタを構築する

kubernetesraspberrypi

Raspberry Pi 3台でKubernetesクラスタを構築する、2020年版おうちKubernetesインターンの資料が公開されていたのでやってみる。 Kubernetesのバージョンはv1.20.6にした。

おうちで「おうち Kubernetes インターン」を実施しました | CyberAgent Developers Blog

材料調達

ほとんどの材料は書いてあるURLで揃えられたが、PoE HATは在庫が切れていたのでスイッチサイエンスで買った。Raspberry Pi 3 Model B+専用と書いてあるが4でも使える。 PoE(Power over Ethernet)はIEEE802.3afとして標準化されているLANケーブルで給電する技術で、48Vで最大15.4Wまで出力できる。 PDではない通常のUSB Type-Cが5Vで15Wなのでそれとほぼ同等のワット数だ。

microSDは最初Kingstonのものを買ったが、 Raspberry Pi ImagerでOSを書き込む際に3枚ともVerifying write failedになってしまったのでSanDiskのものを書い直した。これにはSDカードアダプタが付いていなくて書き込む際にKingstonに付いてきたのを使ったので完全に無駄にはならなかった。

起動後SSHの設定を行うのにUSBキーボードが必要だが、Type-CのHHKBしかなかったので、Type-C to Type-A変換ケーブルも買った。諸々で5万円くらいかかった。

材料

組み立て

ヒートシンクを貼る事ができれば貼ると書いてあったので全ての黒いチップに貼ってみた結果、HATがつけられなくなりCPU以外から剥がすことになった以外は 特に問題なく進められた。ケースにドライバーが付いているので工具も必要ない。

10cm四方のアクリル板の上に収まっていてすごく良い。

組み立て後

SSH接続

Raspberry Pi ImagerでmicroSDにUbuntu Server 21.04 LTSを書き込んで 起動し他マシンからsshしようとしたが繋がらない。 sshdのstatusを見ると起動に失敗している。Test modeで実行してhostkeyがないことが分かったので、作成すると無事起動し繋がるようになった。

$ systemctl status ssh
...
  Active: failed (Result: exit-code)

$ sshd -t
sshd: no hostkeys available -- exiting

$ sudo ssh-keygen -A
$ sudo systemctl restart ssh

次にアドレスがDHCPで変わってしまわないようにする。 ルータの方でMACアドレスに対して払い出すアドレスを固定することもできるが、 今回はNetplanの設定でアドレスを固定しDHCPを無効にした。 ついでにPodへのRoutingも行っている。 今は他と一緒のサブネットに入れているが、無線LANのNICを追加したらサブネットを/24にしてクラスタ内通信を分離しようと考えている。

$ sudo vi /etc/netplan/60-eth0-fix-address.yaml
network:
  ethernets:
    eth0:
      dhcp4: no
      addresses: [192.168.10.1/23]
      gateway4: 192.168.11.1
      nameservers:
        addresses: [8.8.8.8]
      routes:
      - to: 10.10.2.0/24
        via: 192.168.10.2
      - to: 10.10.3.0/24
        via: 192.168.10.3
  version: 2

$ sudo netplan apply

KubernetesのNode名になるhostnameを設定する。

$ sudo hostnamectl set-hostname r4k1

SSHしやすいように/etc/hostsに追記する。

$ sudo vi /etc/hosts
192.168.10.2 r4k2
192.168.10.3 r4k3

Kubernetesのインストール

せっかくなのでkubeadmの方ではなくHard Wayでやる。

証明書の生成

まずコンポーネント間の通信で認証に用いられるクライアント証明書や、apiserverの証明書を生成する。

IstioのSidecarでmTLS認証を行いServiceAccountによるアクセス制御を行う - sambaiz-net

用意されているスクリプトを実行すると、cfsslでオレオレ証明書ca.pemとその秘密鍵ca-key.pem及び、それで署名したNodeやコンポーネント、admin userの証明書と秘密鍵が作られる。 Kubernetes Masterを動かす1台目で実行している。

$ sudo apt install -y golang-cfssl
$ wget https://raw.githubusercontent.com/CyberAgentHack/home-kubernetes-2020/master/how-to-create-cluster-logical-hardway/generate-cert.sh 
$ bash generate-cert.sh
Hostname of Node1: r4k1
Hostname of Node2: r4k2
Hostname of Node3: r4k3
Addresses of Node1 (x.x.x.x[,x.x.x.x]): 192.168.10.1
Addresses of Node2 (x.x.x.x[,x.x.x.x]): 192.168.10.2
Addresses of Node3 (x.x.x.x[,x.x.x.x]): 192.168.10.3
Address of Kubernetes ClusterIP (first address of ClusterIP subnet): 10.32.0.1
...
---> Complete to generate certificate

$ ls cert/
admin-csr.json	ca.csr				  kube-proxy-key.pem	   kubernetes-csr.json	r4k1.pem       r4k3.csr
admin-key.pem	ca.pem				  kube-proxy.csr	   kubernetes-key.pem	r4k2-csr.json  r4k3.pem
admin.csr	kube-controller-manager-csr.json  kube-proxy.pem	   kubernetes.csr	r4k2-key.pem   service-account-csr.json
admin.pem	kube-controller-manager-key.pem   kube-scheduler-csr.json  kubernetes.pem	r4k2.csr       service-account-key.pem
ca-config.json	kube-controller-manager.csr	  kube-scheduler-key.pem   r4k1-csr.json	r4k2.pem       service-account.csr
ca-csr.json	kube-controller-manager.pem	  kube-scheduler.csr	   r4k1-key.pem		r4k3-csr.json  service-account.pem
ca-key.pem	kube-proxy-csr.json		  kube-scheduler.pem	   r4k1.csr		r4k3-key.pem

他Nodeにca.pemと証明書を送る。

$ cd cert
$ scp r4k2.pem r4k2-key.pem ca.pem [email protected]:/home/ubuntu &&
  scp r4k3.pem r4k3-key.pem ca.pem [email protected]:/home/ubuntu

Masterのコンポーネント起動時にフラグで渡すファイルは、kubeadmと同じパスに配置することがk8sのドキュメントで推奨されていたのでそれに合わせることにした。

$ sudo mkdir -p /etc/kubernetes/pki/etcd &&
  sudo cp ca-key.pem /etc/kubernetes/pki/etcd/ca.key &&
  sudo cp ca.pem /etc/kubernetes/pki/etcd/ca.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/apiserver-etcd-client.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/apiserver-etcd-client.crt &&

  sudo cp ca-key.pem /etc/kubernetes/pki/ca.key &&
  sudo cp ca.pem /etc/kubernetes/pki/ca.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/apiserver.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/apiserver.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/apiserver-kubelet-client.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/apiserver-kubelet-client.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/etcd/server.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/etcd/server.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/etcd/peer.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/etcd/peer.crt &&

  sudo cp kubernetes-key.pem /etc/kubernetes/pki/etcd/healthcheck-client.key &&
  sudo cp kubernetes.pem /etc/kubernetes/pki/etcd/healthcheck-client.crt &&

  sudo cp service-account-key.pem /etc/kubernetes/pki/sa.key &&
  sudo cp service-account.pem /etc/kubernetes/pki/sa.pub

kubeconfigの生成

これもスクリプトが用意されていて、実行するとkubectl config set-cluster/credentials/contextしたkubeconfigが作られる。

$ wget https://storage.googleapis.com/kubernetes-release/release/v1.20.6/bin/linux/arm64/kubectl
$ chmod +x kubectl
$ sudo mv kubectl /usr/local/bin
$ wget https://raw.githubusercontent.com/CyberAgentHack/home-kubernetes-2020/master/how-to-create-cluster-logical-hardway/generate-kubeconfig.sh
$ bash generate-kubeconfig.sh
Hostname of Node1: r4k1
Hostname of Node2: r4k2
Hostname of Node3: r4k3
Address of Master Node: 192.168.10.1
...
---> Complete to generate kubeconfig

$ ls kubeconfig/
admin.kubeconfig  kube-controller-manager.kubeconfig  kube-proxy.kubeconfig  kube-scheduler.kubeconfig	r4k1.kubeconfig  r4k2.kubeconfig  r4k3.kubeconfig

$ cat kubeconfig/admin.kubeconfig 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1C...tLS0K
    server: https://127.0.0.1:6443
  name: kubernetes-the-hard-way
contexts:
- context:
    cluster: kubernetes-the-hard-way
    user: admin
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: admin
  user:
    client-certificate-data: LS0tLS1C...tLQo=
    client-key-data: LS0tLS1C...tLQo=

他のNodeで使うものを送る。

$ cd kubeconfig
$ scp r4k2.kubeconfig kube-proxy.kubeconfig [email protected]:/home/ubuntu &&
  scp r4k3.kubeconfig kube-proxy.kubeconfig [email protected]:/home/ubuntu

コンポーネントのデプロイ

資料に従いコンポーネントをデプロイしていく。

  • Kubernetes Master
    • etcd: 一貫性、高可用性を持った分散KVS
    • kube-apiserver: コントロールプレーンのフロントエンド
    • kube-controller-manager: NodeControllerやReplicationControllerなどを実行する
    • kube-scheduler: PodにNodeを割り当てる
  • kubelet: コンテナランタイム(今回はcontainerd)とCRIでやり取りしてコンテナがPodで実行されていることを保証する
  • kube-proxy: iptablesを更新するなどしてServiceの向き先を制御する
  • coredns: クラスタ内での名前解決を行う

KubernetesのCustom Resource Definition(CRD)とCustom Controller - sambaiz-net

前準備としてcgroupのmemory subsystemを有効にすると書いてあったが元からenabledになっていた。 これによりグループのメモリ使用量を出したり制限をかけることができる。

$ cat /proc/cgroups
#subsys_name	hierarchy	num_cgroups	enabled
cpuset	2	1	1
cpu	7	41	1
cpuacct	7	41	1
blkio	3	41	1
memory	10	86	1
devices	6	41	1
freezer	11	2	1
net_cls	4	1	1
perf_event	5	1	1
net_prio	4	1	1
pids	9	46	1
rdma	8	1	1

kube-apiserverのデプロイ時に次のエラーが出た。v1.20でstableになったService Account Token Volume Projectionに関するもので、audienceや有効期限などを指定したServiceAccount TokenをPodのProjected Volumeにマウントしてくれるようだ。

May 04 10:28:12 r4k1 kube-apiserver[9746]: Error: [service-account-issuer is a required flag, --service-account-signing-key-file and --service-account-issuer are required flags]

次のフラグを追加したら起動した。

--service-account-signing-key-file=/etc/kubernetes/pki/sa.key \\
--service-account-issuer=api \\

あとは証明書のパスを変えたので設定ミスでapiserver-etcd間の通信がremote error: tls: bad certificateになったりした。 大体のエラーは設定ミスか、ファイルの作り忘れか、修正が反映されてないことによるものだったが、どこに問題があるか探すのに苦労した。

# etcd
--cert-file=/etc/kubernetes/pki/etcd/server.crt \
--key-file=/etc/kubernetes/pki/etcd/server.key  \
--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt \
--peer-key-file=/etc/kubernetes/pki/etcd/peer.key \
--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt \
--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt \
# apiserver
--etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt \
--etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt \
--etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key \

動作確認

Nodeが認識されて、corednsも動いている。

$ export KUBECONFIG=${PWD}/admin.kubeconfig
$ kubectl get node
NAME   STATUS   ROLES    AGE   VERSION
r4k1   Ready    <none>   39m   v1.20.6
r4k2   Ready    <none>   17h   v1.20.6
r4k3   Ready    <none>   16h   v1.20.6

$ kubectl get pod -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
coredns-75774bf574-4mrp4   1/1     Running   0          43m
coredns-75774bf574-h69gs   1/1     Running   0          67m

$ kubectl run test --image busybox:1.28 --restart Never -it --rm -- nslookup kubernetes
If you don't see a command prompt, try pressing enter.
Name:      kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local
pod "test" deleted

nginxを起動しport-forwardしてアクセスする。

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created

$ kubectl get pods -l app=nginx
NAME                     READY   STATUS    RESTARTS   AGE
nginx-6799fc88d8-9f7m5   1/1     Running   0          43s

$ kubectl port-forward nginx-6799fc88d8-tkd4w 8081:80 &
$ curl localhost:8081 -o /dev/null -w '%{http_code}\n' -s
200

クラウドのマネージドサービスを使っていると触らない所なので面白かった。

参考

計装豆知識|PoE(IEEE802.3af)

Raspberry Pi 3および4で使うSDカード(microSDカード)の選び方 ― 2021年新春 - SORACOM公式ブログ

第4回 Linuxカーネルのコンテナ機能[3] ─cgroupとは?(その2):LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社