Raspberry Piでkubernetesクラスタを組んでみた(kubernetes-the-hardway編)

2022/11/19に公開

はじめに

おうちkubernetesを構築してみました。
こちらを参考にしました。
https://github.com/CyberAgentHack/home-kubernetes-2020/tree/master/how-to-create-cluster-physical
↑ 実際に構築してみて、自分で調べた点などを補足したり、残しておきたい点を抜粋しながら紹介します。

材料

商品 数量 備考
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からの接続許可

ネットワーク

https://github.com/CyberAgentHack/home-kubernetes-2020/tree/master/how-to-create-cluster-logical-hardway#ネットワーク
参考通りで設定

  • Node 用サブネット
    • 10.0.0.0/24
      • Node1: 10.0.0.11
      • Node2: 10.0.0.12
      • Node3: 10.0.0.13
  • Pod 用サブネット
    • 10.10.0.0/16
      • Node1: 10.10.1.0/24
      • Node2: 10.10.2.0/24
      • Node3: 10.10.3.0/24
  • ClusterIP 用サブネット
    • 10.32.0.0/24

証明書の生成

kubernetesではTLSを使って通信していて、下記証明書を利用している

  • kubelet が API サーバーに対して認証するためのクライアント証明書
  • APIサーバーがkubeletsと通信するためのKubeletサーバー証明書
  • API サーバー エンドポイントのサーバー証明書
  • クラスターの管理者が API サーバーに対して認証するためのクライアント証明書
  • kubelets と通信するための API サーバーのクライアント証明書
  • API サーバーが etcd と通信するためのクライアント証明書
  • コントローラ マネージャが API サーバと通信するためのクライアント証明書/kubeconfig
  • スケジューラーが API サーバーと通信するためのクライアント証明書/kubeconfig。
  • フロント プロキシのクライアントおよびサーバー証明書

https://kubernetes.io/docs/setup/best-practices/certificates/#how-certificates-are-used-by-your-cluster

cfsslを使って、まず認証局を生成(ca-XXX) -> CSR(証明書要求)をもとに、各種証明書・秘密鍵を生成

生成された中身見ると

  • hoge-key.pem(秘密鍵)
  • hoge.csr(証明書要求)
  • hoge.pem(サーバ証明書)

の三種類が生成される

(参考)
https://qiita.com/tmiki/items/626d0932f413fdc7d7fe
https://qiita.com/iaoiui/items/fc2ea829498402d4a8e3

kubeconfigの生成

↑ 証明書使って設定ファイル生成

  • admin.kubeconfig
  • kube-controller-manager.kubeconfig
  • kube-scheduler.kubeconfig
  • raspi-0001.kubeconfig
  • raspi-0002.kubeconfig
  • raspi-0003.kubeconfig

中身はこんな感じ

raspi-0001.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コントロールプレーンのフロントエンドになります。

https://kubernetes.io/ja/docs/concepts/overview/components/

  • バイナリ用意
  • 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からの指示に基づいて、コンテナの作成・管理など
https://medium.com/nttlabs/runc-overview-263b83164c98

  • 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はできる)
(参考)
https://www.mtioutput.com/entry/kube-what-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

原因: ホスト解決できてない??

/etc/hosts
10.0.0.11 raspi-0001
10.0.0.12 raspi-0002
10.0.0.13 raspi-0003

を追記して動いた

Discussion