電通総研 テックブログ

電通総研が運営する技術ブログ

Raspberry Pi 5でオンプレKubernetesに入門

はじめに

こんにちは。金融ソリューション事業部エンジニアリングオフィスの加藤です。
仕事でGKEやAKSといったマネージドKubernetesサービスを利用したインフラ構築を経験してきました。
この記事では、Raspberry Pi 5でK8sクラスタを構築しながら、マネージドK8sのありがたみについて学んでいきます。

ゴール

以下の作業を通して、Kubernetesクラスタのオンプレ環境における構築手順の全体的な流れと、設計判断のポイントについてざっくり理解する

  • Raspberry Piをセットアップする
  • Kubernetesクラスタを作る
  • Prometheus, Grafanaを入れてクラスタの監視環境をセットアップする
  • Cloudflare tunnelを利用して、セキュアにインターネット経由からのアクセスをできるようにする

構成

Raspberry Pi 5は手元に3台あります。以下の戦略で構築します。

  • マスター1, ノード2 のIPv4シングルスタックのKubernetesクラスタをkubeadmで構築する
  • あとでマスターにもノードを追加できるように、高可用性の構成とする
Host Name IP Address Description
PC 192.168.3.x 作業用のPC
master-1 192.168.3.81 マスター1: Raspberry piUbuntu 24.04 LTS
node-1 192.168.3.82 ノード1: Raspberry piUbuntu 24.04 LTS
node-2 192.168.3.83 ノード2: Raspberry piUbuntu 24.04 LTS
kubernetes-frontend 192.168.3.90 コントロールプレーンのAPIサーバ負荷分散のための仮想IP
(LoadBalancer Service用) 192.168.3.91-99 Service Type:Loadbalacerで使用するIPレンジ

構成イメージは以下のとおりです。

構成イメージ

大まかな流れ

  1. 準備: Raspberry Pi を台数分セットアップ

    • Raspberry Piのセットアップ
    • カーネルの設定
    • コンテナランタイムの設定
    • ツールのインストール
  2. クラスタの構築

    • HA構成のAPIServer
    • クラスタ作成
    • Nodeの追加
    • テスト用のPodをデプロイして確認
  3. PrometheusとGrafanaのセットアップ(オプション)

    • kube-prometheus-stack Helmチャートによる簡単なセットアップ
    • インターネット経由でアクセスできるようにする

1. 準備

記事のボリュームの関係上、Raspberry Piそのものについての記述は少し端折ります。

  • Raspberry Pi Imagerを利用して、Ubuntu 24.04 LTSのイメージをSDカードに焼きます。
  • 複数台ありますので、自宅環境の要件を満たすための汎用的な設定はuser-dataに書いた上で起動したりshellスクリプトにしたりすると、セットアップが捗ります。user-dataのサンプルを一番下に書きましたので、一つの参考としてご参照ください。

環境

SDカードを入れて起動します。以下のようになりました。

# 各種情報確認
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

$ uname -a
Linux node-1 6.8.0-1004-raspi #4-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 20 02:29:55 UTC 2024 aarch64 aarch64 

カーネルの設定

K8sクラスタのシステム要件を確認しながら、設定します。

# カーネルの設定
## カーネルモジュールのロード
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
$ sudo modprobe overlay
$ sudo modprobe br_netfilter

## カーネルパラメータの設定
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 確認
$ sudo sysctl net.bridge.bridge-nf-call-iptables
$ sudo sysctl net.bridge.bridge-nf-call-ip6tables
$ sudo sysctl net.ipv4.ip_forward
$ sudo sysctl --system

コンテナランタイムの設定

コンテナランタイム、CNI Pluginを入れていきます。今回はcotainerd + runcの一般的な構成としました。また、コンテナ管理にcgroupを利用するように設定します。

# containerdのインストール
$ ARCH="arm64"
$ CONTAINERD_VERSION="1.6.32"
$ curl -fsSL https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz -o containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz
$ curl -fsSL https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz.sha256sum -o containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz.sha256sum
$ sha256sum -c containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz.sha256sum
$ sudo tar -C /usr/local -xzvf containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz
$ rm containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz containerd-${CONTAINERD_VERSION}-linux-${ARCH}.tar.gz.sha256sum

# containerdの設定
$ sudo mkdir -p /etc/containerd
$ containerd config default | sudo tee /etc/containerd/config.toml
$ curl https://raw.githubusercontent.com/containerd/containerd/main/containerd.service | sudo tee /etc/systemd/system/containerd.service > /dev/null

# containerdをサービスとして起動
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now containerd
$ sudo systemctl status containerd
# runcのインストール
$ sudo apt-get update -y
$ sudo apt-get install -y libseccomp2 gpg
$ curl -fsSL https://github.com/opencontainers/runc/raw/main/runc.keyring -o runc.keyring
$ gpg --import runc.keyring
$ RUNC_VERSION="1.1.12"
$ curl -fsSL https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/runc.${ARCH} -o runc
$ curl -fsSL https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/runc.${ARCH}.asc -o runc.asc
$ gpg --verify runc.asc runc
$ sudo install -m 755 runc /usr/local/sbin/runc
$ rm runc runc.asc runc.keyring

# インストール確認
$ containerd -v
$ runc -v
# CNI Pluginのインストール
$ sudo mkdir -p /opt/cni/bin
CNI_VERSION="1.1.1"
$ curl -fsSL https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz -o cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz
$ curl -fsSL https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz.sha256 -o cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz.sha256
$ sha256sum -c cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz.sha256
$ sudo tar -C /opt/cni/bin -xzvf cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz
$ rm cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz cni-plugins-linux-${ARCH}-v${CNI_VERSION}.tgz.sha256
# systemdを利用するようにcontainerdの設定を変更
$ sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak
$ sudo sed -i '/\[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options\]/,/\[/ s/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
$ diff /etc/containerd/config.toml /etc/containerd/config.toml.bak 
127c127
<             SystemdCgroup = true
---
>             SystemdCgroup = false
$ sudo systemctl restart containerd
# swapをOFFにする
$ sudo swapoff -a

Kubernetes関連のツールのインストール

今回はクラスタの構築に kubeadm を利用するので、kubeadmをインストールします。その他、kubeletも必要になるのでインストールしておきます。kubectlは必要に応じて入れます。

$ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

$ sudo apt-get update -y
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl

# 確認
$ kubeadm version
$ kubectl version --client=true
$ kubelet --version

2. クラスタの構築

今回は以下の戦略とします。

  • Kubernetes v1.30を入れる
  • 高可用性クラスタとして構成する
    • ※現時点ではマスターノードは1台だけですが、将来的に追加できるようにするため。途中で変更できないため、クラスタ作成時にこの構成にしておく必要があります
  • マスターノード上で以下のように構成する
    • HAProxyでコントロールプレーンのAPI Server宛のリクエストをリバースプロキシする
    • Keepalivedで仮想IPを設定する
      • 各ノードのkubelet,kubectlからはこの仮想IPを使用するようにする
    • 上記の名前解決は簡易的にhostsを用いる

HA構成のAPIServer

まず、Keepalivedをインストールし、固定したIP(今回は192.168.3.90)でアクセスできるように設定します。

#Keepalivedのインストール/設定
$ sudo apt update
$ sudo apt install keepalived

$ K8S_VIP="192.168.3.90"

$ cat <<_EOF_ | sudo tee /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        ${K8S_VIP}
    }
}
_EOF_

# keepalivedを利用するためのカーネルモジュールのロード設定: https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/load_balancer_administration/s1-initial-setup-forwarding-vsa
$ echo "net.ipv4.ip_nonlocal_bind = 1" | sudo tee -a /etc/sysctl.d/k8s.conf
$ sudo sysctl net.ipv4.ip_nonlocal_bind
$ sudo systemctl start keepalived
$ sudo systemctl enable keepalived

# 確認
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 2c:cf:67:2c:e4:9a brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.81/24 brd 192.168.3.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.3.90/32 scope global eth0            # ←これができる
       valid_lft forever preferred_lft forever
    ...中略...

# hostsを編集して名前解決できるようにしておく
# /etc/cloud/templates/hosts.debian.tmplは起動時にhostsを生成するテンプレートのため、一旦リブートするか、hostsにも直接書くかどちらが必要
$ cat <<_EOF_ | sudo tee -a /etc/cloud/templates/hosts.debian.tmpl 
# These entry is for testing keepalived configuration for k8s apiserver
${K8S_VIP} kubernetes-frontend.local 

_EOF_

続いて、HAProxyを設定します。

# HAProxyのインストール
$ sudo apt update
$ sudo apt install haproxy
$ sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg_bak
# frontendにKeepalived, backendに後で作成するKubernetes API Serverを設定
$ cat <<_EOF_ | sudo tee /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    log /dev/log local1 notice
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    tcp
    timeout connect 5000
    timeout client  50000
    timeout server  50000

frontend kubernetes-frontend
    bind *:8443
    default_backend kubernetes-backend

backend kubernetes-backend
    mode tcp
    balance roundrobin
    server master-1 127.0.0.1:6443 check verify none
_EOF_

$ sudo systemctl reload haproxy

クラスタ作成

K8sクラスタを作成し、Podをデプロイできる状態にします。以下のような流れになります。

  • コントロールプレーンの設定
  • Pod Network アドオンの設定
  • Nodeをクラスタに追加
  • MetalLBの設定
  • テスト用のPodをデプロイして確認

コントロールプレーンの設定

kubeadm init を使います。コントロールプレーンの作成は、以下のような処理が行われることで実現しているようです。

# https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/#synopsis
# 以下の順序で処理される
preflight                    Run pre-flight checks
certs                        Certificate generation
  /ca                          Generate the self-signed Kubernetes CA to provision identities for other Kubernetes components
  /apiserver                   Generate the certificate for serving the Kubernetes API
  /apiserver-kubelet-client    Generate the certificate for the API server to connect to kubelet
  /front-proxy-ca              Generate the self-signed CA to provision identities for front proxy
  /front-proxy-client          Generate the certificate for the front proxy client
  /etcd-ca                     Generate the self-signed CA to provision identities for etcd
  /etcd-server                 Generate the certificate for serving etcd
  /etcd-peer                   Generate the certificate for etcd nodes to communicate with each other
  /etcd-healthcheck-client     Generate the certificate for liveness probes to healthcheck etcd
  /apiserver-etcd-client       Generate the certificate the apiserver uses to access etcd
  /sa                          Generate a private key for signing service account tokens along with its public key
kubeconfig                   Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file
  /admin                       Generate a kubeconfig file for the admin to use and for kubeadm itself
  /super-admin                 Generate a kubeconfig file for the super-admin
  /kubelet                     Generate a kubeconfig file for the kubelet to use *only* for cluster bootstrapping purposes
  /controller-manager          Generate a kubeconfig file for the controller manager to use
  /scheduler                   Generate a kubeconfig file for the scheduler to use
etcd                         Generate static Pod manifest file for local etcd
  /local                       Generate the static Pod manifest file for a local, single-node local etcd instance
control-plane                Generate all static Pod manifest files necessary to establish the control plane
  /apiserver                   Generates the kube-apiserver static Pod manifest
  /controller-manager          Generates the kube-controller-manager static Pod manifest
  /scheduler                   Generates the kube-scheduler static Pod manifest
kubelet-start                Write kubelet settings and (re)start the kubelet
upload-config                Upload the kubeadm and kubelet configuration to a ConfigMap
  /kubeadm                     Upload the kubeadm ClusterConfiguration to a ConfigMap
  /kubelet                     Upload the kubelet component config to a ConfigMap
upload-certs                 Upload certificates to kubeadm-certs
mark-control-plane           Mark a node as a control-plane
bootstrap-token              Generates bootstrap tokens used to join a node to a cluster
kubelet-finalize             Updates settings relevant to the kubelet after TLS bootstrap
  /experimental-cert-rotation  Enable kubelet client certificate rotation
addon                        Install required addons for passing conformance tests
  /coredns                     Install the CoreDNS addon to a Kubernetes cluster
  /kube-proxy                  Install the kube-proxy addon to a Kubernetes cluster
show-join-command            Show the join command for control-plane and worker node

kubeadm init を実行する際のオプションは、コマンド引数として渡すか、yamlで書いて渡すことができます。今回は以下のとおり kubeadm-config.yaml を書きました。デフォルトから変更する設定項目は以下のとおりです。

  • コントロールプレーンを高可用性構成にするための controlPlaneEndpoint / apiServer.certSANs
  • Podに割り当てるサブネットを設定するための networking.podSubnet
$ cat <<_EOF_ > ~/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: 1.30.0
controlPlaneEndpoint: "kubernetes-frontend.local:8443"
apiServer:
  certSANs:
  - kubernetes-frontend.local # hostname of keepalived
  - 192.168.3.90 # VIP of keepalived
  - master-1.local # hostname of master-1
  - 192.168.3.81 # IP of master-1 
networking:
  podSubnet: "10.64.0.0/16"
_EOF_

# kubeadm initを実行する
# 他のノードを構成する際の手順を簡略化するために `--upload-certs` オプションをオプションを追加
$ sudo kubeadm init \
  --config kubeadm-config.yaml \
  --upload-certs
# 以下が出力されたら完了
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/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join kubernetes-frontend.local:8443 --token {token} \
        --discovery-token-ca-cert-hash sha256:{discovery-token-ca-cert-hash} \
        --control-plane --certificate-key {certificate-key}

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

$ kubeadm join kubernetes-frontend.local:8443 --token {token} \
        --discovery-token-ca-cert-hash sha256:{discovery-token-ca-cert-hash} 


# 完成したらkubectlを使えるようにしておく
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

$ source <(kubectl completion bash)
$ echo "source <(kubectl completion bash)" >> ~/.bashrc

Pod Network アドオン

この時点では、まだcorednsが起動してきません。Podが動作するようになるには、Pod Network アドオンをインストールします。
Pod Network アドオンは、主に以下のような責務を負うコンポーネントです。

  • コンテナに対するNICの設定
  • IPアドレスの管理、動的割り当て
  • ネットワークポリシーによる分離(Calicoの場合)
  • クロスホスト通信

Pod Network アドオンはCalicoやFlannelなど複数の選択肢がありますが、今回はCalicoを使用します。

  • 現時点で、Calicoは、KubeadmプロジェクトがE2Eテストを行っている唯一のアドオンだそうです

今回はCalicoをオペレータを使ってインストールします。

# Calicoをインストールするためのマニフェストを一旦ダウンロード
$ curl https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/tigera-operator.yaml -O
$ curl https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/custom-resources.yaml -O

kubeadm init 時と同様、Podサブネットをこちらにも設定する必要があります。

# custom-resource.yaml

# クラスタ構築時に指定したPod SubnetのCIDRを指定する
# This section includes base Calico installation configuration.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.Installation
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
-     cidr: 192.168.0.0/16
+     cidr: 10.64.0.0/16
$ kubectl create -f tigera-operator.yaml
$ kubectl create -f custom-resources.yaml

Nodeの追加

クラスタへのノードの追加は、先ほどの kubeadm init を実行した後に出力されていた内容に従うだけで簡単にできます。
今回はワーカーノードを2台追加します。

# 先に、API Serverの仮想IPに解決されるホスト名をhostsに追記しておく(リブートして反映)
$ cat <<_EOF_ | sudo tee -a /etc/cloud/templates/hosts.debian.tmpl 
# These entry is for testing keepalived configuration for k8s apiserver
${K8S_VIP} kubernetes-frontend.local

_EOF_

# kubeadm init時の出力に従ってノードごとに実行
$ sudo kubeadm join kubernetes-frontend.local:8443 --token {token} \
        --discovery-token-ca-cert-hash sha256:{discovery-token-ca-cert-hash}
# nodeが上がっている
$ kubectl get node
NAME       STATUS   ROLES           AGE     VERSION
master-1   Ready    control-plane   6d11h   v1.30.1
node-1     Ready    <none>          6d11h   v1.30.1
node-2     Ready    <none>          6d11h   v1.30.1

# Calicoの状態を確認
# Calicoでは、各ノードがBGPピアリングして経路情報を交換し、L3のレイヤでルーティングする
$ sudo ./calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+------------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |   SINCE    |    INFO     |
+--------------+-------------------+-------+------------+-------------+
| 192.168.3.82 | node-to-node mesh | up    | 2024-06-07 | Established |
| 192.168.3.83 | node-to-node mesh | up    | 2024-06-07 | Established |
+--------------+-------------------+-------+------------+-------------+

IPv6 BGP status
No IPv6 peers found.

MetalLBの設定

オンプレ環境のK8sクラスタ環境では、Type: LoadBalancer のServiceを公開するためにL4のLBを用意する必要があります。今回はMetalLBをLayer2モードで使用し、また、特定のIPアドレスが自動的にアタッチされるように設定します。

# MetalLBのインストール
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml

# ServiceのExternal-IPに割り当てるIPアドレスを設定する
cat <<_EOF_ | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.3.91-192.168.3.99
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
_EOF_

テスト用のPodをデプロイして確認

準備ができたので、テスト用のNginxをデプロイし、複数のタイプのService経由で疎通できるかどうかを確認します。

#テスト用Nginx
kubectl create deployment test-nginx --image=nginx --replicas 3
kubectl get pod -o wide
NAME                          READY   STATUS    RESTARTS        AGE    IP             NODE     NOMINATED NODE   READINESS GATES
test-nginx-56cc9db4bc-26955   1/1     Running   1 (6d19h ago)   34d    10.64.84.143   node-1   <none>           <none>
test-nginx-56cc9db4bc-67ddw   1/1     Running   1 (6d19h ago)   34d    10.64.247.16   node-2   <none>           <none>
test-nginx-56cc9db4bc-6gffj   1/1     Running   1 (6d19h ago)   34d    10.64.84.142   node-1   <none>           <none>

cat <<_EOF_ | kubectl apply -f -
# Service type: ClusterIP
apiVersion: v1
kind: Service
metadata:
  name: test-nginx-clusterip
spec:
  type: ClusterIP
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: test-nginx
---
# Service type: NodePort
apiVersion: v1
kind: Service
metadata:
  name: test-nginx-nodeport
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30080
  selector:
    app: test-nginx
---
# Service type: LoadBalancer
apiVersion: v1
kind: Service
metadata:
  name: test-nginx-metallb
spec:
  type: LoadBalancer
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: test-nginx

_EOF_

Pod / Node / PCなどいろいろな場所から疎通確認してみます

# Pod間通信をテストする用のPodを作成
kubectl run test --image=ubuntu -it --rm

# From test pod
## -> APIServer
curl https://kubernetes/healthz #OK
## -> Type: ClusterIP / NodePort / LoadBalancer
curl http://test-nginx-clusterip:8080 #OK
curl http://test-nginx-nodeport:8080 #OK
curl http://test-nginx-metallb:8080/ #OK

# From Node
## -> Type: LoadBalancer
curl http:/192.168.3.91:8080/ #OK

# From PC
## -> Type: LoadBalancer
curl http:/192.168.3.91:8080/ #OK

3. PrometheusとGrafanaのセットアップ

kube-prometheus-stack Helmチャートによる簡単なセットアップ

最後に、PrometheusとGrafanaを設定して監視基盤を構築します。

  • AlertManager、各種Monitor、Ruleなどの他にも、NodeExporter、Grafanaなど必要なコンポーネントがたくさんあるので、今回は一括構築・管理が可能なHelmチャートである kube-prometheus-stackを使います。
    • いくつか手段が提供されていますが、設定のカスタマイズや管理をする上ではHelmチャートを使うのが融通が利きそうでした
  • Raspberry Pi 5にもM2.SSD HATが発売されたので、master-1をNFSサーバとしても設定していました。今回、取得した各メトリクスを共有ディレクトリに保存したくなったのですが・・・PrometheusはNFSをサポートしていないようでした。知りませんでした。
# https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# GrafanaダッシュボードとPrometheusのUIはPort-ForwardなしでPCから確認したいので、ServiceのTypeを変更
cat <<_EOF_ > values.yaml
prometheus:
  service:
    type: LoadBalancer
grafana:
  service:
    type: LoadBalancer
_EOF_

# monitoring Namespaceを作ってそこにデプロイ
kubectl create ns monitoring
helm install prom-stack prometheus-community/kube-prometheus-stack -n monitoring -f values.yaml

ダッシュボードは初期の状態でたくさん設定されていますが、追加で、Raspberry Pi & Docker Monitoring というのを入れてみました。Raspberry Pi は温度を気にしたくなりますので、良い感じに表示されていて嬉しいです。

Grafanaダッシュボード

インターネット経由のアクセス (オプション)

このK8sクラスタのメンテナンスを外出時にもしたい、Grafanaダッシュボードを外出先からも確認したい、となった場合、どのようにすれば良いでしょうか。
Cloudflare を使ってみて、非常に体験が良かったので最後に紹介します。

簡単にいうと、オンプレ側のマシンにcloudflaredをインストールして、そこからCloudflareが管理するエッジ環境に対してトンネルを張り、その経路を利用してセキュアに通信できる、かつその仕組みを利用する際、認証機能、サブドメイン用の自動証明書管理、セキュリティを維持するための各種機能も提供されているというものです。自分のドメインさえ持っていれば、今回の用途では無料で利用できます。

  • Cloudflare Tunnel の作成
    • UIでの作成方法 に従うと、簡単に設定できます。
    • cloudflaredは、すべてのサーバに入れる必要はなく、トンネルを作成したいマシンに対して入れれば足ります。残りの設定はUIから行っていきます(今回はmaster-1に入れました)
    • Public Hostname(サブドメインとして利用できるHostnameになります)とService(ローカル側からアクセス可能なIP/ポートの組み合わせ)を関連付けます。今回はGrafanaダッシュボードと、SSHアクセスを有効にしたいので、以下をサービスとして登録し、それぞれに対してサブドメインを割り当てました。
      • http://prom-stack-grafana.monitoring.svc.cluster.local:80
      • ssh://localhost:22
  • Applicationの設定
    • 上記で設定したPublic Hostnameに対して、アクセスポリシー、認証の設定などを有効化できます。
    • 自分のメールアドレスだけアクセスできるようにしつつ、パスワードをメールワンタイムパスワードに設定しました。
    • Browser SSHタイプでApplication設定を作成すると、ブラウザ上でターミナルが表示されます。

まとめ

  • Raspberry PiK8sクラスターを作ってみました。これから育てていきます
  • HA構成や家庭のLAN環境特有の問題に対処することで、L2/L3周りの学びを得る機会になりました
  • オンプレKubernetesを業務運用する際の難易度の高さを改めて実感しました(N/W、マシン、ストレージ、可用性・信頼性の確保、継続的な改善とメンテナンス、大規模運用・・・)
  • Cloudflareはもっと色々な用途に使ってみたいです

user-data のサンプル

初期設定系はすべてこちらに突っ込みたくなりますが、うまくいかない時のデバッグが難しいので、簡単な最低限の設定項目だけをuser-dataに、それ以外はスクリプトを書くなどして初回起動後に実行する方が個人的には好きです。

#cloud-config
hostname: { HOST_NAME } # Configured by Raspberry Pi Imager
manage_etc_hosts: true

+package_update: true
+package_upgrade: true

packages:
  - avahi-daemon
+  - libseccomp2
+  - gpg
+  - apt-transport-https
+  - ca-certificates
+  - curl
+  - raspi-config
apt:
  ...中略...

users:
  ...中略...

+write_files:
+  # Configure IP Addrress Fixation
+  - path: /etc/netplan/99_manual_config.yaml
+    owner: root:root
+    permissions: "0600"
+    content: |
+      network:
+        version: 2
+        ethernets:
+          eth0:
+            dhcp4: true
+            addresses:
+              - 192.168.3.83/24
+            nameservers:
+              addresses:
+                - 192.168.3.1

runcmd:
  - localectl set-x11-keymap "us" pc105
  - setupcon -k --force || true
+  - netplan apply
+  - |
+    cat <<_EOF_ | tee -a /etc/cloud/templates/hosts.debian.tmpl 
+    # These entry is for testing keepalived configuration for k8s apiserver
+    192.168.3.90 kubernetes-frontend.local
+
+    _EOF_

参考

執筆:@kato.shota、レビュー:@kobayashi.hinami
Shodoで執筆されました