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
クラウドのマネージドサービスを使っていると触らない所なので面白かった。
参考
Raspberry Pi 3および4で使うSDカード(microSDカード)の選び方 ― 2021年新春 - SORACOM公式ブログ
第4回 Linuxカーネルのコンテナ機能[3] ─cgroupとは?(その2):LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社