在树莓派4(和3B)上进行面向边缘计算的Kubernetes构建和传感器数据收集尝试
在Raspberry Pi 4(和3B)上尝试搭建适用于边缘计算的Kubernetes,并收集传感器数据。
使用三台树莓派构建了针对边缘计算的Kubernetes集群,并利用Python/Prometheus/Grafana收集、存储和可视化传感器数据,将边缘应用部署在集群上。

请注意
截至2022年3月6日,如果使用kubernetes 1.23.4/kubelet 1.23.4/kubeadm 1.23.4,会遇到以下kubelet/cAdvisor的问题,导致kubelet无法正常启动,无法构建kubernetes。cAdvisor的修补程序预计将在下一个kubernetes版本(1.24)中合并。
因此,截至2022年3月6日,1.22系列(观察时最新版本为1.22.7)可以正常运行。
https://github.com/kubernetes/kubernetes/issues/106977
内容
我们将按照以下顺序对降构建方法进行解释。
-
- 集群的特征和验证环境
-
- 准备树莓派
-
- 安装Kubernetes,构建集群
针对边缘应用开发和部署到集群
安装温湿度传感器DHT11
开发从传感器收集和传送数据的应用程序
通过Prometheus收集和存储从应用程序获取的数据
通过Grafana进行可视化
应用程序的执行结果
感想
1. 集群的特征/组成
这次的簇的特点如下。
-
- ノードに 比較的大きなSDカード(128GB/256GB) を使った。(コンテナイメージが多いと逼迫すると考えたため)
メモリを潤沢に積んだラズパイ4と非力なラズパイ3の混成とした。(エッジは非力な装置しか置けない状況が多いと考え、それを再現するため)
构成成分
所准备的设备包括三台树莓派和一台开关。每台树莓派需要一个电源(约5V x 3A)。
Raspberry Pi 4 Model B
(Memory 8GB)
+ SDカード 128GB (Samsung)ラズパイ#2ワーカーノード
Raspberry Pi 4 Model B
(Memory 8GB)
+ SDカード 256GB (Samsung)ラズパイ#3 (エッジ用)ワーカーノード
Raspberry Pi 3 Model B+
(Memory 1GB)
+ SDカード 32GB (Samsung)
+ 温度・湿度センサーDHT11ネットワークスイッチNETGEAR 8-Port Gigabit Ethernet Unmanaged Switch (GS308)Ethernetケーブル1Gbps対応のもの x 3本WiFiアクセスポイント(インターネットアクセス用)自宅にあったWifiルータを援用端末Macbook Pro
OS version: 11.1 Big Sur
连接线和IP地址 hé IP
-
- ラズパイとネットワークスイッチをEthernet ケーブルでつなぐ。
- なければWifiのみで構築も可能。
[ インターネット ] [ 端末(Mac) ]
| |
[-----Wifi(外部接続用)------] [ Switch(クラスタ内部用) ]
192.168.0.1 |
├--(無線)--192.168.2.101--[ RasPi#1 (rp4-1) ]-192.168.1.101--(有線)--┤
├--(無線)--192.168.2.102--[ Raspi#2 (rp4-2) ]-192.168.1.102--(有線)--┤
└--(無線)--192.168.2.103-[ Raspi#3 (rp3) ]-192.168.1.103--(有線)--┘
2. 树莓派的准备
此项目适用于所有的树莓派设备。是进行第3项Kubernetes所需的前提条件。
将操作系统安装在SD卡上。
MacにRaspberry Pi Imagerをインストールする。
ラズパイ#1と#2のSDカードは32GBを超えているため、フォーマットを変更し初期化。
Samsung 128GB/256GBはいずれもexFATで初期化されている。
Raspberry Piは、32GBを超えるSDカードがFAT32フォーマットでないと認識しない。
端末MacのDisk Utilityを使って、MS-DOS (FAT)を選択し初期化する。
Raspberry Pi Imagerを起動し、Choose OSでRaspberry Pi OS (other) –> Raspberry Pi OS Lite (32-bit)を選択、SD Cardは先ほど初期化したSDカードを選択する。WRITEで書き込み開始。10分程で終了。

2-2. 启动
-
- SDカードを挿入してから、ラズパイの電源を入れる。
最初はディスプレイとキーボード類を接続する (そのため一台一台やる必要がある)
TTYでのログインは、ユーザ名pi, 初期パスワードraspberry。
2-3. 树莓派的网络设置。
登录后,使用以下命令开始配置。
sudo raspi-config
変更する内容は以下。
Hostnameをラズパイごとに異なる名前にする
(私の場合はrp4-1, rp4-2)
SSHを有効化する
sudo vi /etc/dhcpcd.conf
IPアドレスを固定する。
# クラスタ内部用(有線)
interface eth0
static ip_address=192.168.1.101/24
# 外部接続用(Wifi)
interface wlan0
static ip_address=192.168.2.101/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1
sudo vi /etc/hosts
/etc/hostsにクラスタ全ノード分のホスト名を追記しておく。このIPアドレスはクラスタ内で使用されるもの。したがって、スイッチにつながったNICのIPとする。
192.168.1.101 rp4-1
192.168.1.102 rp4-2
192.168.1.103 rp3
sudo reboot
終了したらhostnameを反映するために再起動します。
2-4.(选项)简化登录
这节的内容是为了方便 SSH 即时登录,但并非必需。
-
- ユーザpiのパスワード変更
ユーザ*piでログイン後、passwdコマンドでユーザpi*のパスワードを変更する (デフォルトパスワードではセキュリティアラートが出る場合がある)
SSHに秘密鍵でログイン可能にする
アクセス元端末で一度だけ、SSH用のキーをssh-keygenで作成しておく
(作成したキーは 秘密鍵.ssh/raspi/id_rsaと公開鍵.ssh/raspi/id_rsa.pubとする)
端末から各ノードに対して公開鍵を送付する
ssh-copy-id -i ~/.ssh/raspi/id_rsa.pub pi@
ログイン直後のWelcomeメッセージを省略する設定
各ノードで以下コマンドを実行し、*.hushlogin*ファイルを作成
touch .hushlogin
端末の/etc/hostsに各ホスト名を登録。このホスト名は外部接続用。
192.168.2.101 rp4-1
192.168.2.102 rp4-2
192.168.2.103 rp3
.ssh/configを設定し、sshコマンドのオプションを何度も打たなくて良いようにしておく
Host rp*
User pi
IdentityFile ~/.ssh/raspi/id_rsa
Host rp4-1
HostName rp4-1
Host rp4-2
HostName rp4-2
Host rp3
HostName rp3
端末の.zshrcもしくは.bashrcに 短縮コマンドを登録しておく。
.zshrc の場合
alias rp41=”ssh rp4-1″ と書いておけば、以降はコマンドrp41でログインできるようになる
端末、ノードそれぞれにtmuxをインストールする (SSHセッションが切れても作業を残すため)
sudo apt update && sudo apt install -y tmux
2-5. 安装Docker
使用Debian安装步骤。
get-docker.shをダウンロードする
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
登錄了 apt 庫的存儲庫,安裝了 20.10.1 版本,但後續使用 kubeadm 會導致錯誤。
Client: Docker Engine - Community
Version: 20.10.1
(中略)
Server: Docker Engine - Community
Engine:
Version: 20.10.1
(後略)
卸载一次docker-ce。
sudo apt remove docker-ce docker-ce-cli
从这里开始,按照”安装Docker引擎”中的”按照特定版本进行安装”的方式重新安装Docker。
检查其他docker-ce版本。
apt-cache madison docker-ce
docker-ce | 5:20.10.1~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:20.10.0~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:19.03.14~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:19.03.13~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:19.03.12~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:19.03.11~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
docker-ce | 5:19.03.10~3-0~raspbian-buster | https://download.docker.com/linux/raspbian buster/stable armhf Packages
安装最新发现的东西在这里。
sudo apt-get install docker-ce=5:19.03.14~3-0~raspbian-buster docker-ce-cli=5:19.03.14~3-0~raspbian-buster
在docker-ce的安装后出现错误,但当再次执行安装命令时,会正常结束。
最后,赋予docker命令的权限。
sudo usermod pi -aG docker
为了反映变动,先退出登录一次(如果tmux正在运行,也要先停止它一次)。
重新登录到树莓派,通过docker version命令确认客户端和服务器均为19.03版本。
$ docker version
Client: Docker Engine - Community
Version: 19.03.14
(中略)
Server: Docker Engine - Community
Engine:
Version: 19.03.14
(後略)
kubeadmの準備
为了避免在使用kubeadm安装Kubernetes时出现错误,需要在以下项目中提前做好准备。
运行docker info命令时,最后会输出如下警告。
$ docker info
(ellipsis)
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: No kernel memory limit support
WARNING: No kernel memory TCP limit support
WARNING: No oom kill disable support
为了解决这个问题,在内核命令行/boot/cmdline.txt中添加选项。(cgroup_enable=memory用于启用cgroup的内存控制,最后的swapaccount=1用于限制交换空间)(参考)
- console=serial0,115200 console=tty1 root=PARTUUID=66dee106-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
+ console=serial0,115200 console=tty1 root=PARTUUID=66dee106-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait cgroup_enable=memory cgroup_memory=1 swapaccount=1
由于Kubernetes建议关闭交换(swap),因此建议按照以下方式配置以删除dphys。后两条命令用于防止节点重新启动后dphys被恢复。
参考链接:
https://raspberrypi.stackexchange.com/questions/84390/how-to-permanently-disable-swap-on-raspbian-stretch-lite
sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo update-rc.d dphys-swapfile remove
sudo apt purge dphys-swapfile
检查Docker信息以确保没有出现警告。
$ docker info
(no warning)
为了Kubernetes的kubeproxy等功能而启用iptables,不再使用iptables而是使用nftables。
sudo iptables -F
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo reboot
- cgroup driverを設定。
参考使用树莓派搭建一个Kubernetes集群。
sudo cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl restart docker
如果没有进行此设定,将在后续的kubeadm操作中出现以下警告。
[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
请在以下确认是否已经正确设置。
$ docker info
(中略)
Cgroup Driver: systemd
br-netfilterが有効かのチェック
按照kubeadm进行安装。
lsmod | grep br_netfilter
- iptableが有効なことをチェック。
参考安装kubeadm的步骤。
$ sudo sysctl net/bridge/bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1
$ sudo sysctl net/bridge/bridge-nf-call-ip6tables
net.bridge.bridge-nf-call-ip6tables = 1
安装 Kubernetes。
按照kubeadm上所提到的Ubuntu/Debian操作步骤进行安装。
3-1. 所有节点都需要共同准备的事项
kubeadm, kubelet, kubectlのインストール
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
# kubernetes 1.19の場合 (2021年1月検証時)
sudo apt-get install -y kubelet=1.19.6-00 kubeadm=1.19.6-00 kubectl=1.19.6-00
# kubernetes 1.22の場合 (2022年3月検証時)
sudo apt-get install -y kubelet=1.22.7-00 kubeadm=1.22.7-00 kubectl=1.22.7-00
sudo apt-mark hold kubelet kubeadm kubectl
请注意,在使用以下命令安装的最新版本kubelet 1.20.1时,kube-apiserver、controller-manager和scheduler会发生平均每10分钟重启一次的现象。因此,必须再次使用上述命令将版本回退到1.19.6(这个过程花费了大约一周时间)。
【修正后:出现故障】
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
建立控制平面节点。
按照 kubernetes.io 的步骤进行执行。
以下是基本命令。
kubeadm init --pod-network-cidr=10.244.0.0/16 --ignore-preflight-errors=Mem
在我的情况下,我添加了以下选项。
-
- クラスタ内用ネットワークがあるので、そのIPアドレス192.168.1.101をAPIサーバに使ってもらうオプション設定。
–apiserver-cert-extra-sansは、公開用のIPアドレスもしくはホスト名を,区切りで列挙する。kubernetes.io参考。
Memを入れるのは、kubeadmのメモリ計算バグを回避するため。
kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.1.101 --apiserver-cert-extra-sans=192.168.2.101,rp4-1 --ignore-preflight-errors=Mem
记住以下命令,它会在正常结束时显示。
kubeadm join 192.168.1.101:6443 --token urrb14.moqc3cofej5c23n6 \
--discovery-token-ca-cert-hash sha256:0cd452e9011994c2e58622e6f098e58df67414ec57e321645fb804715385e20e
此外,您还需要复制用于集群连接的kubeconfig配置文件,并在最后进行显示。
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
添加工作节点
在工作节点上执行在构建kubeadm控制平面节点时显示的命令。
kubeadm join 192.168.1.101:6443 --token urrb14.moqc3cofej5c23n6 \
--discovery-token-ca-cert-hash sha256:0cd452e9011994c2e58622e6f098e58df67414ec57e321645fb804715385e20e
如果以正常方式显示,则表示已成功结束。
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
如果控制平面节点以以下方式显示,那就代表成功。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
将kubeadm生成的.kube/config文件复制到终端设备。
为了从控制面节点复制到终端,从终端执行以下操作。
scp pi@rp4-1:/home/pi/.kube/config ~/.kube/rpkubeconfig
如果要从终端访问树莓派,则必须使用外部IP地址,因此先前获取的config文件需要进行修改以适应目标地址。
vi ~/.kube/rpkubeconfig
改正的内容。
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DEDACTED
- server: https://192.168.1.101:6443 # 内部向けIPアドレス
+ server: https://rp4-1:6443 # 外部向けIPアドレス
在设备上预先安装kubectl。
为了最后确认,使用kubectl连接到集群。
$ export KUBECONFIG=$HOME/.kube/rpkubeconfig
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
rp3 NotReady <none> 28m v1.20.1
rp4-1 NotReady control-plane,master 21h v1.20.1
rp4-2 NotReady <none> 21h v1.20.1
安装CNI(Flannel)。
只需在终端或控制平面节点中执行上述操作一次。
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
再次检查节点状态,因为通过CNI已经建立了集群内部网络,所以每个节点都变为Ready状态。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
rp3 Ready <none> 30m v1.20.1
rp4-1 Ready control-plane,master 21h v1.20.1
rp4-2 Ready <none> 21h v1.20.1
创建一个用于存储数据的存储类。
初始化用于存储Kubernetes数据所需的存储类别。
- 様々なストレージの種類があるが、今回はクラスタの各ノードのローカルディスクを使うLocal path provisionerを使用する
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
- 作成したストレージクラスをデフォルト設定にする
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
- 確認コマンドでlocal-pathが表示されればインストール完了。
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 87m
将节点的类型注册为标签。
- 大きなディスクを積んだラズパイにラベルを貼る (後の3-4でPrometheusを選択的にデプロイするため)
kubectl label nodes rp4-1 raspi-disk=large
kubectl label nodes rp4-2 raspi-disk=large
- エッジとして使用するラズパイにラベルを貼る (3-4でWeb APIを選択的にデプロイするため)
kubectl label nodes rp3 raspi-humid=true
kubectl label nodes rp3 raspi-temp=true
-
- ラベルが正しく振られているかを確認する
大きなディスクを積んだrp4-1とrp4-2に正しくラベルraspi-disk=largeが振られている
エッジとして使用するrp3に正しくラベルraspi-temp=trueとraspi-humid=trueが振られている
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
rp3 Ready <none> 5d19h v1.20.1 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=rp3,kubernetes.io/os=linux,raspi-humid=true,raspi-temp=true
rp4-1 Ready control-plane,master 6d16h v1.20.1 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=rp4-1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,raspi-disk=large
rp4-2 Ready <none> 6d16h v1.20.1 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=rp4-2,kubernetes.io/os=linux,raspi-disk=large
引入一种软件负载均衡器,可以自动分配外部集群访问的IP。
-
- MetalLBをKubernetes内に導入する
インストールはMetalLB: INSTALLATION、設定はLayer 2 Configuration
まず以下のコマンドでMetalLBをインストールする。
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/metallb.yaml
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
- インストール後、以下のファイルを端末で作成し、kubectlコマンドで流し込むことで設定が完了する。
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.0.70-192.168.0.99
kubectl apply -f config.yaml
在这里分配的IP地址范围192.168.0.70-99指定了无线局域网IP范围内未使用的部分。另外,MetalLB将以Layer2模式运行。
promisc設定
通常情况下,MetalLB在为服务分配范围内的IP地址后,需要通过wlan0广播ARP以进行通告。
但是,在树莓派上可能无法通过网络接口卡进行此设置。详细信息请点击此处。根据该页面所述,必须使用混杂模式。
在这个特定环境下,确实在未进行设置的情况下,无法从外部访问到 IP 地址。
为了让你可以访问,需要进行以下设置。
sudo ifconfig wlan0 promisc
在设置后,使用ifconfig命令输出将显示如下,表示处于混杂模式(PROMISC):
$ ifconfig wlan0
wlan0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST> mtu 1500
如果要取消设置(例如不再使用MetalLB),请执行以下操作。
sudo ifconfig wlan0 -promisc
然而,当节点重新启动时,该解锁将被撤销。为了在重新启动后保持相同的配置,执行sudo crontab -e命令。
@reboot sudo ifconfig wlan0 promisc
在最后一行添加。
如果显示“crontab: installing new crontab”,就表示没有问题。
3-9. 完成集群后,请确认节点状态。
以下是控制平面节点的内存。
$ free -m
total used free shared buff/cache available
Mem: 7875 476 6706 9 693 7220
Swap: 0 0 0
内存容量为8GB,使用量为476MB,没有被缓存使用的空闲内存约为6.7GB,有充足的余量。
第二个树莓派工作节点
$ free -m
total used free shared buff/cache available
Mem: 7875 206 7208 9 461 7450
Swap: 0 0 0
免费空间为7.2GB,并且还有更多的额外空间。
我在搭载1GB内存的最小且性能较弱的树莓派#3上确认了内存使用情况。
$ free -m
total used free shared buff/cache available
Mem: 924 181 98 18 644 684
Swap: 0 0 0
搭载的1GB内存使用量为181MB,还有很多余量。然而,其中以缓冲区所使用的部分为644MB,占据了较大的比例。
4. 面向边缘开发和部署的应用程序开发。
在已经完成的集群中部署边缘应用程序并构建数据收集系统。
4-1. 边缘应用程序的概述
目标是实时可视化温湿度传感器的信息,并可在手机等设备上进行确认(参见下图)。

通过将传感器数据解析的Python应用程序与后端存储和收集系统(Prometheus)以及可视化前端界面(Grafana)相结合,构建系统。
使用Python应用程序和Prometheus/Grafana的配置是参考了这篇文章。
https://dev.to/pdambrauskas/monitoring-apartment-temperature-humidity-with-raspberry-pi-prometheus-grafana-1i48
使用Python应用程序和Prometheus/Grafana来进行配置,我们参考了这篇文章。
4-2. 安装温湿度传感器
我将DHT11传感器连接到树莓派RP3上。

连接端子的关系
VCC
5V pinDATA
GPIO19
(プルアップは付属基盤内で完結しているため抵抗は用意せず直結)GND
GND pin4-3. 应用程序开发
-
- Pythonでアプリを開発
センサーデータのパースには、Pythonライブラリ dht11を使用する。
取得したデータをFlaskでWeb APIとして公開する。
データのフォーマットは後述のPrometheusのexporterの形式である。
環境変数DTH11_PIN_NUMBERから19などの文字列を受け取り、データ読み取りPIN番号として活用する。
from flask import Flask
import dht11
import RPi.GPIO as GPIO
import os
GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM)
pin = int(os.environ.get('DHT11_PIN_NUMBER'))
interval = int(os.environ.get('DHT11_INTERVAL'))
tag = os.environ.get('DHT11_TAG')
instance = dht11.DHT11(pin)
app = Flask(__name__)
@app.route("/metrics")
def metrics():
dht11_data = ""
result = instance.read()
if result.is_valid():
dht11_data = f"""temperature{{{tag}}} {result.temperature}
humidity{{{tag}}} {result.humidity}"""
return f"{dht11_data}", 200, {'Content-Type': 'text/plain; charset=utf-8'}
if __name__ == "__main__":
app.run(host='0.0.0.0')
-
- アプリをコンテナ化する
ラズパイ対応のイメージをベースにする必要がある(今回はbalenalib/raspberrypi3-debian-pythonを使用)
dht11がgccやを必要とするため、apt installで導入しておく
pipでdht11やflaskを導入する
FROM balenalib/raspberrypi3-debian-python:latest
WORKDIR /usr/src/app
RUN sudo apt update && sudo apt install -y gcc libc6-dev
RUN pip install dht11 flask
COPY app/ /usr/src/app/
CMD ["python", "./app-exporter.py"]
- コンテナをビルドし、Dockerhubにpushする。以下のコマンドを実行 (hiroyukiosaki/raspi-py-dht11はDockerhubに格納するためのタグ名。Dockerhubのユーザ名を取得し変更が必要)
$ docker build . -t hiroyukiosaki/raspi-py-dht11
$ docker push hiroyukiosaki/raspi-py-dht11
4-4. (Option) 使用docker-compose来构建应用程序- Prometheus – Grafana 作为测试环境。
docker-compose.yaml
version: ‘3.7’
x-shared_environment: &shared_environment
DHT11_PIN_NUMBER: 19
DHT11_INTERVAL: 5
DHT11_TAG: “sensor_type=\”dht11\”,sensor_location=\”desk\””
services:
app:
image: hiroyukiosaki/raspi-py-dht11:latest
build:
context: .
environment:
<<: *shared_environment
privileged: true
ports:
– 5000:5000
restart: always
prometheus:
image: prom/prometheus:v2.23.0
user: root
volumes:
– ./prometheus/:/etc/prometheus/
– /var/prometheus:/prometheus
command:
– ‘–config.file=/etc/prometheus/prometheus.yml’
– ‘–storage.tsdb.path=/prometheus’
– ‘–storage.tsdb.retention.time=30d’
– ‘–web.console.libraries=/usr/share/prometheus/console_libraries’
– ‘–web.console.templates=/usr/share/prometheus/consoles’
ports:
– 9090:9090
depends_on:
– app
restart: always
grafana:
image: grafana/grafana:7.3.6
depends_on:
– prometheus
ports:
– 80:3000
volumes:
– ./grafana/provisioning:/etc/grafana/provisioning
restart: always
使用docker-compose命令在Raspberry Pi 3上构建3个容器(应用程序、Prometheus和Grafana)。
$ docker-compose up -d
尝试访问Raspberry Pi 3上的每个服务。如果显示任何内容,则容器已启动。
Prometheus http://rp3:9090
Grafana http://rp3
如果Raspberry Pi上没有安装docker-compose,请参考以下文档进行安装:
在树莓派上安装Docker和Docker Compose的方法
4-5. 将应用部署到Kubernetes
raspi-temp-app:5000
raspi-temp-app-xxxxxxxxx-xxxxx
(4-2で開発したアプリ)raspi-temp-prometheus:9090
prometheus-xxxxxxxxx-xxxxx
grafana:80
grafana-xxxxxxxxx-xxxxx
部署所需的命令
将部署到三个容器(应用程序/Prometheus/Grafana)中的指令如下所示。
git clone https://github.com/onelittlenightmusic/raspi-py-dht11-docker.git
cd raspi-py-dht11-docker
kubectl apply -f k8s/
4-5-1. 应用在集群内的布置

硬件包括了三个树莓派,其中一个树莓派(rp3)配备了传感器。rp4-1和rp4-2通过连接大容量硬盘,并且CPU也更加强大(采用Cortex A72架构)。
这个软件会在容器中分别启动传感器读取应用程序、Prometheus和Grafana。
软件布局策略是针对每个应用选择以下适当的节点作为策略。
rp3
)に配置raspi-disk=large
Prometheus/Grafanaデータ蓄積や処理にディスク・CPUを要するので、rp4-1
もしくはrp4-2
に配置raspi-temp=true
とraspi-humid=true
利用Kubernetes的调度器在软件部署方面做出了努力。由于已在Kubernetes中添加了上表所示的标签,可以通过使用nodeSelector来实现所需的配置。
各个容器的详细信息如下。
4-5-2. 传感器读取应用的设置。
-
- デプロイ時の設定ファイルの内容は以下
securityContextにより、ラズパイ接続デバイスへのアクセス権を付与
nodeSelectorはraspi-temp: “true”とraspi-humid: “true”を指定。
(ellipsis)
spec:
containers:
- envFrom:
- configMapRef:
name: raspi-app-configmap
image: hiroyukiosaki/raspi-py-dht11:latest
name: app
ports:
- containerPort: 5000
resources: {}
securityContext:
privileged: true
restartPolicy: Always
nodeSelector:
raspi-temp: "true"
raspi-humid: "true"
所有内容都在app-deployment.yaml文件中。
4-5-3. Prometheus/Grafana的设置
-
- デプロイ時の設定ファイルの内容は以下
Prometheusでは、/prometheusにデータが保存されるので、ここに用意したPersistentVolumeClaimマウントする。
nodeSelectorはraspi-temp: “true”とraspi-humid: “true”を指定。
spec:
containers:
- args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.console.libraries=/usr/share/prometheus/console_libraries
- --web.console.templates=/usr/share/prometheus/consoles
image: prom/prometheus:v2.23.0
name: prometheus
ports:
- containerPort: 9090
resources: {}
volumeMounts:
- mountPath: /etc/prometheus/
name: prometheus-config
- mountPath: /prometheus
name: prometheus-data
restartPolicy: Always
volumes:
- name: prometheus-config
configMap:
name: prometheus-configmap
- name: prometheus-data
persistentVolumeClaim:
claimName: prometheus-data-pvc
nodeSelector:
raspi-disk: large
YAML设定内容分别在grafana-deployment.yaml和prometheus-deployment.yaml中。
4-6. 由Prometheus实现的可视化
在以下位置确认由负载均衡器提供的Prometheus地址,并访问该地址。
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
raspi-temp-prometheus LoadBalancer 10.103.205.90 192.168.0.71 9090:31849/TCP 38h
在上述情况下,地址为http://192.168.0.71:9090。
获取湿度的2分钟移动平均线的图表。
可以使用avg_over_time(humidity[2m])获取。

获取温度的2分钟移动平均值的图表。
可以使用avg_over_time(humidity[2m])来获取。

4-7. 使用Grafana进行数据可视化
访问Prometheus的地址http://192.168.0.72是由负载均衡器提供的。
从数据源添加画面来看,

选择 Prometheus,并在URL中指定 Prometheus 群集中服务的 URL,http://raspi-temp-prometheus:9090。

点击保存并测试。
只需要一个选项:从仪表板添加界面,

点击“添加面板(Add Panel)”,在“指标(Metrics)”中输入传感器数据名称“temperature”,然后点击“应用(Apply)”。

同样,添加了专为湿度设计的面板,仪表板就完成了。

5. 考试
我尝试了获取温度变化并通过移动设备进行访问。
5-1. 获取温度变化
我们可以从数据中观测到温度的变化吗?让我们尝试打开下方的暖气15分钟(21:49-22:04)进行测试。
写照下方的圆柱体是供暖设备。


可以从数据中观察到温度确实上升了。
从手机设备访问仪表板
拿起手机(iPhone)来查看仪表盘。

由于在Android上显示为”IP不可达”,因此无法获取屏幕截图。只要正确设置”混杂模式”,就可以从Android上访问。
6. 完成构建和验证后的感想
-
- ラズパイでKubernetesクラスタを構築するために、2章の/boot/cmdline.txtを変更するなどラズパイならではの部分が少し戸惑うところ。しかし、それをクリアすれば通常のkubeadmによるKubernetes構築はスムーズ。
-
- 今回のエッジアプリはセンサデータをWeb APIで公開するだけの機能を持つ。Prometheus用のデータのフォーマットに変換する実装は、アプリからサイドカーに外出しすれば、アプリのコードがクリーンになり保守性がよくなると考える。(今後の課題)
-
- アプリをDockerコンテナ化したことにより、ラズパイ上にPythonライブラリをインストールしなくて済み、エッジにアプリを配布しやすいという効果があった。
-
- エッジをKubernetesクラスタに内包した効果
データ読み取りアプリとバックエンドPrometheus間がクラスタ内通信で閉じるので、お互いに物理的なホスト名などを意識せずとも通信可能
エッジアプリと同時にバックエンドをデプロイできるので、システムがスケール容易
今回はラベルとnodeSelectorを用いたが、この方法ではセンサがどこに接続されているかを把握してラベル付与が必要。おそらくKubeEdgeなどを駆使し、「エッジに接続されたセンサリソースまでKubernetesに登録しスケジューラを活用することで、アプリデプロイ時にアプリが必要とするセンサに合わせノード選択を自動化する」というところまでいけばさらに価値が高そう(今後の課題)
エッジであるはずのノードが物理結線でスイッチに繋がってしまっているのは自由度が低いと感じる。Wifi経由接続でも良かった。(今後の課題)
Prometheusは時系列データベースというだけでなく、一定周期でWeb APIにアクセスしてくれるスクレイピング機能を有し、メトリクス収集として強力だと感じる。
Grafanaも、Prometheusからのシームレスな接続とリアリタイムなレンダリングがあり、時系列データに親和性が高い。(今更感がありますが)
Kubernetesの接続問題
kubeletおよびkubeadmのバージョンを1.20.1にすると、API server/controller managerなどのKubernetesコンポーネントが頻繁に再起動する事象が起きた。これが発生すると、kubectlなどが疎通しない。2021/1月現在では1.19までダウングレードすることで接続ができた。