Raspberry Piでkubernetesクラスタを組んでみた(kubernetes-the-hardway編)
はじめに
おうちkubernetesを構築してみました。
こちらを参考にしました。
↑ 実際に構築してみて、自分で調べた点などを補足したり、残しておきたい点を抜粋しながら紹介します。
材料
商品 | 数量 | 備考 |
---|---|---|
Raspberry Pi 4 8GB | 3 | 最近在庫があまりない |
micro SD 64GB | 3 | |
PoE Hat | 3 | こちらを使用しない場合はUSB-Cによる電源供給 |
スイッチングハブ(PoE 対応) | 1 | |
LAN ケーブル 0.15m | 3 | |
Raspberry Pi ラックケース | 3 | |
micro SD カードリーダー | 1 |
PoE Hat について
正直、購入して失敗しました。理由は以下3点です。
- うるさい
ファンがよく回ります。コイル鳴きみたいな音も結構うるさいです。常時稼働をするのであれば、外すと思います。 - 壊れやすい
取ったら壊れる。 - 熱い
3つのうち1つのPoE Hatでsudo shutdown -h now
でシャットダウンした後、コイル鳴きが続いて熱をかなり持っていました。(故障?) - 高い
一つ3000-4000円します。スイッチングハブもPoE対応を使う必要があります。これを使わなければ、1万円くらいはコストを削減できました。
組み立て
使うネジとかは考える必要があります。
PoE Hatとの干渉を防ぐため、ヒートシンクは大きいの2つしか付けられないと思います。
クラスタ構築
2通りのやり方があるようです。
- kubeadm
- kubernetes-the-hardway
今回は、勉強目的なので、kubernetes-the-hardwayで構築しました
ラズパイ設定
HDMIとキーボードはつないでおいて起動
os
ubuntu server 20.04 LTS
WIFI・Ethernetの固定IP・ルーティング
cd /etc/netplan
cp 50-cloud-init.yaml 99-custom.yaml
sudo vi 99-custom.yaml
network:
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses: [10.0.0.11/24]
routes:
- to: 10.10.2.0/24
via: 10.0.0.12
- to: 10.10.3.0/24
via: 10.0.0.13
version: 2
wifis:
wlan0:
optional: true
access-points:
"ssid":
password: "password"
dhcp4: true
sudo netplan --debug apply
hostname変更
sudo hostnamectl set-hostname raspi-0001
sudo reboot
mDNSの有効化
sudo apt install avahi-daemon
ssh接続確認
mDNSを有効化したので、IPアドレス知らなくても
ssh ubuntu@raspi-0001.local
で、sshできるようになる
よく使うコマンド
sudo reboot
wifi-status
Kubernetesクラスタの構築作業
概要
- 証明書の生成
- kubeconfigの生成
- master構築
- etcdのデプロイ
- kube-api-server
- kube-controller-manager
- kube-scheduler
- node構築
- cgroup memory_subsystem有効化
- kubelet
- kube-proxy
- routing設定
- kubeletのkube-apiserverからの接続許可
ネットワーク
参考通りで設定
- Node 用サブネット
- 10.0.0.0/24
- Node1: 10.0.0.11
- Node2: 10.0.0.12
- Node3: 10.0.0.13
- 10.0.0.0/24
- Pod 用サブネット
- 10.10.0.0/16
- Node1: 10.10.1.0/24
- Node2: 10.10.2.0/24
- Node3: 10.10.3.0/24
- 10.10.0.0/16
- ClusterIP 用サブネット
- 10.32.0.0/24
証明書の生成
kubernetesではTLSを使って通信していて、下記証明書を利用している
- kubelet が API サーバーに対して認証するためのクライアント証明書
- APIサーバーがkubeletsと通信するためのKubeletサーバー証明書
- API サーバー エンドポイントのサーバー証明書
- クラスターの管理者が API サーバーに対して認証するためのクライアント証明書
- kubelets と通信するための API サーバーのクライアント証明書
- API サーバーが etcd と通信するためのクライアント証明書
- コントローラ マネージャが API サーバと通信するためのクライアント証明書/kubeconfig
- スケジューラーが API サーバーと通信するためのクライアント証明書/kubeconfig。
- フロント プロキシのクライアントおよびサーバー証明書
cfsslを使って、まず認証局を生成(ca-XXX) -> CSR(証明書要求)をもとに、各種証明書・秘密鍵を生成
生成された中身見ると
- hoge-key.pem(秘密鍵)
- hoge.csr(証明書要求)
- hoge.pem(サーバ証明書)
の三種類が生成される
(参考)
kubeconfigの生成
↑ 証明書使って設定ファイル生成
- admin.kubeconfig
- kube-controller-manager.kubeconfig
- kube-scheduler.kubeconfig
- raspi-0001.kubeconfig
- raspi-0002.kubeconfig
- raspi-0003.kubeconfig
中身はこんな感じ
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: (認証局証明書の内容)
server: https://10.0.0.11:6443 (マスターIP:k8s apiのポート)
name: kubernetes-the-hard-way
contexts:
- context:
cluster: kubernetes-the-hard-way
user: system:node:raspi-0001
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: system:node:raspi-0001
user:
client-certificate-data: (clientの証明書の内容)
client-key-data: (クライアントの鍵の内容)
Masterの設定
etcdのデプロイ
- バイナリ用意
- 証明書配置
- systemctlで動かすためのユニットファイルの作成
ETCD_NAME=kubernetes-the-hard-way
INTERNAL_IP=10.0.0.11
[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \\
--name ${ETCD_NAME} \\
--cert-file=/etc/etcd/kubernetes.pem \\
--key-file=/etc/etcd/kubernetes-key.pem \\
--peer-cert-file=/etc/etcd/kubernetes.pem \\
--peer-key-file=/etc/etcd/kubernetes-key.pem \\
--trusted-ca-file=/etc/etcd/ca.pem \\
--peer-trusted-ca-file=/etc/etcd/ca.pem \\
--peer-client-cert-auth \\
--client-cert-auth \\
--initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
--listen-peer-urls https://${INTERNAL_IP}:2380 \\
--listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
--advertise-client-urls https://${INTERNAL_IP}:2379 \\
--initial-cluster-token etcd-initial-token \\
--initial-cluster ${ETCD_NAME}=https://${INTERNAL_IP}:2380 \\
--initial-cluster-state new \\
--data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
Environment=ETCD_UNSUPPORTED_ARCH=arm64
- 動作させる
sudo systemctl daemon-reload
sudo systemctl enable hogehoge
sudo systemctl start hogehoge
- 動作確認
sudo ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/ca.pem \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem
kube-apiserverのデプロイ
APIサーバーは、Kubernetes APIを外部に提供するKubernetesコントロールプレーンのコンポーネントです。 APIサーバーはKubernetesコントロールプレーンのフロントエンドになります。
- バイナリ用意
- etcdデータ暗号化のための設定ファイル(encryption-config.yaml)
- systemctlで動かすユニットファイル
INTERNAL_IP=10.0.0.11
CLUSTER_IP_NETWORK=10.32.0.0/24
ExecStart=/usr/local/bin/kube-apiserver \\
--advertise-address=${INTERNAL_IP} \\
--allow-privileged=true \\
--apiserver-count=3 \\
--audit-log-maxage=30 \\
--audit-log-maxbackup=3 \\
--audit-log-maxsize=100 \\
--audit-log-path=/var/log/audit.log \\
--authorization-mode=Node,RBAC \\
--bind-address=0.0.0.0 \\
--client-ca-file=/var/lib/kubernetes/ca.pem \\
--enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
--etcd-cafile=/var/lib/kubernetes/ca.pem \\
--etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
--etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
--etcd-servers=https://${INTERNAL_IP}:2379 \\
--event-ttl=1h \\
--encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
--kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
--kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
--kubelet-https=true \\
--runtime-config='api/all=true' \\
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\
--service-cluster-ip-range=${CLUSTER_IP_NETWORK} \\
--service-node-port-range=30000-32767 \\
--tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
--tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
--v=2
- systemctlで動かす
kube-controller-managerのデプロイ
- バイナリの用意
- kubeconfigの移動
- systemctl用ユニットファイル
POD_NETWORK=10.10.0.0/16
CLUSTER_IP_NETWORK=10.32.0.0/24
ExecStart=/usr/local/bin/kube-controller-manager \\
--bind-address=0.0.0.0 \\
--cluster-cidr=${POD_NETWORK} \\
--cluster-name=kubernetes \\
--cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
--cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
--kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
--leader-elect=true \\
--root-ca-file=/var/lib/kubernetes/ca.pem \\
--service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
--service-cluster-ip-range=${CLUSTER_IP_NETWORK} \\
--use-service-account-credentials=true \\
--v=2
Restart=on-failure
RestartSec=5
- systemctlで動かす
kube-schedulerのデプロイ
- バイナリ用意
- kubeconfig配置
- systemctl用ユニットファイル
ExecStart=/usr/local/bin/kube-scheduler \\
--config=/etc/kubernetes/config/kube-scheduler.yaml \\
--v=2
Restart=on-failure
RestartSec=5
- systemctlで動かす
masterの動作チェック
kubectl get componentstatuses --kubeconfig admin.kubeconfig
Nodeの構築
準備
- cgroupのmemory subsystemの有効化(
/boot/firmware/cmdline.txt
に追記)
-> メモリのリソース使用制限・監視とかをしたい??から、cgroupを使う?
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
- 有効化確認
cat /proc/cgroups
- パッケージインストール
sudo apt update
sudo apt -y install socat conntrack ipset
kubelet
kubelet: Podを動かすコンポーネント
- 証明書配置・設定ファイル領域作成
- バイナリ用意
- パッケージインストール
sudo apt -y install containerd runc
- Podネットワークの設定
CNI: Container Network Interface
POD_CIDR=10.10.1.0/24
cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
"cniVersion": "0.3.1",
"name": "bridge",
"type": "bridge",
"bridge": "cnio0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [
[{"subnet": "${POD_CIDR}"}]
],
"routes": [{"dst": "0.0.0.0/0"}]
}
}
EOF
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
"cniVersion": "0.3.1",
"name": "lo",
"type": "loopback"
}
EOF
- containerdの設定
コンテナランタイム: k8sからの指示に基づいて、コンテナの作成・管理など
- kubeletの設定
- systemctl用設定ファイル
ExecStart=/usr/local/bin/kubelet \\
--config=/var/lib/kubelet/kubelet-config.yaml \\
--container-runtime=remote \\
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
--image-pull-progress-deadline=2m \\
--kubeconfig=/var/lib/kubelet/kubeconfig \\
--network-plugin=cni \\
--register-node=true \\
--v=2
Restart=on-failure
RestartSec=5
kube-proxy
- バイナリ、ファイル領域作成、kubeconfig配置
- systemctl用ファイル
[Service]
ExecStart=/usr/local/bin/kube-proxy \\
--config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=on-failure
RestartSec=5
ルーティング設定の追加
ラズパイ設定時に設定済み(↑99-custom.yaml参照)
kubelet kube-apiserver認証RBAC設定
kubeletがkube-apiserverの接続を許可するように設定
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-apiserver-to-kubelet
rules:
- apiGroups:
- ""
resources:
- nodes/proxy
- nodes/stats
- nodes/log
- nodes/spec
- nodes/metrics
verbs:
- "*"
EOF
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: system:kube-apiserver
namespace: ""
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-apiserver-to-kubelet
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: kubernetes
EOF
動作確認
- secretの暗号化確認: etcdに直接アクセスしたら暗号化されたデータが見える
sudo ETCDCTL_API=3 etcdctl get \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/ca.pem \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem \
/registry/secrets/default/kubernetes-the-hard-way | hexdump -C
- Podの起動
kubectl create deployment nginx --image=nginx
kubectl get pods -l app=nginx
- Port Forwarding
kubectl port-forward $POD_NAME 8081:80
(別タブで)
curl --head http://127.0.0.1:8081
- NodePort
サービス: IPアドレスPが変動するPodに対してクライアントが一意にアクセスするために作成する(ClusterIP
, NodePort
, LoadBalancer
, ExternalName
の4種類)(ClusterIP
はクラスタ外からの接続無理、NodePort
はできる)
(参考)
kubectl expose deployment nginx --port 80 --type NodePort
kubectl get svc
curl -I http://10.0.0.11:PORT
ハマったポイント
- ubuntu server 22.04 LTSでは動かない
原因: 22.04のcgroupのversionが2で、kubernetes(1.18.6)では対応してないから??
kubectl get node
NAME STATUS ROLES AGE VERSION
raspi-0001 NotReady <none> 27m v1.18.6
raspi-0002 NotReady <none> 22h v1.18.6
kubectl describe node
Ready False Wed, 16 Nov 2022 12:30:27 +0000 Wed, 16 Nov 2022 12:29:36 +0000 KubeletNotReady [container runtime status check may not have completed yet, PLEG is not healthy: pleg has yet to be successful, missing node capacity for resources: ephemeral-storage]
ググるとcgroupエラーか??
- CoreDNSがデプロイ後うまく動かない
kubectl run test --image busybox:1.28 --restart Never -it --rm -- nslookup google.com
Error attaching, falling back to logs: error dialing backend: dial tcp: lookup raspi-0003 on 127.0.0.53:53: server misbehaving
原因: ホスト解決できてない??
10.0.0.11 raspi-0001
10.0.0.12 raspi-0002
10.0.0.13 raspi-0003
を追記して動いた
Discussion