在树莓派监控摄像头上部署motioneye的方式,使用kubernetes进行部署
首先
在「CloudNative Days TOKYO 2020」上,有一个关于使用Jetson的k3s部署监控摄像头系统的会议,这让我有了一些启发,决定在家里的树莓派kubernetes集群上也尝试一下。
环境
由于家庭网络是多层次的私有网络结构,因此在设备方面一度感到困扰。
本次工程中,我们增加的部分如下图所示的红色区域。
kubernetes集群的网络在家庭私有网络内创建了一个额外的有线网络,并使用主节点作为路由器进行外部连接。如果只考虑家庭私有网络,只需要连接到WiFi路由器即可,但最初的想法是想将kubernetes设置为有线局域网并观察数据包,所以采用了这种架构。
因此,我们无法在远处放置kubernetes节点。解决方法是在kubernetes集群的网络中安装WiFi接入点并进行WiFi连接,这是我能想到的唯一选择。在搜索衣柜深处时,我找到了曾经使用过的Ethernet转换器套装的WiFi路由器,因此我决定将其设置为AP模式使用。
-
- Wifi AP + Ethernet Converter (自宅にあった昔使ってた余り物)
Aterm WR8700N + WL300NE-AG
Raspberry Pi 4B
Rspberry Pi OS 64bit(beta)
USB3のSSDからのBOOT。
インストールは『Raspberry Pi 4 の 64bit版と USB Boot』を参考にさせていただきました。
motionEye (ccrisan/motioneye)
参考にした情報 wiki/Install In Docker
USBカメラ
ELP-USBFHD06H-MFV(2.8-12)
Raspberry Pi 3B+の時に買ったもので、「USB2.0」「低照度」「UVC」「マニュアルフォーカス」「産業用カメラ」のキーワードで探して購入しました。少々高いですが、室温40度の窓際でも2シーズン壊れてません。
夜間は人間の目で見るより若干暗く映る感じで、外灯があればそこそこ見れる感じです。
建立
将Raspberry Pi OS 64位(测试版)设置为Kubernetes的工作节点。
第一步:在 Worker 节点上
我的IP地址被固定,内核参数被调整,交换空间被禁用。
# curl -sSL https://get.docker.com | sh
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
# echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" >> /etc/apt/sources.list.d/kubernetes.list
# apt update
# apt install kubelet=1.17.0-00 kubeadm=1.17.0-00 kubectl=1.17.0-00
# update-alternatives --set iptables /usr/sbin/iptables-legacy
# iptables --policy FORWARD ACCEPT
因为我从2019年底开始构建,所以我指定版本为1.17.0-00。请将其替换为您所使用的版本。我个人是通过systemd进行服务化和配置的,不知道最近的操作系统或者Docker是否需要将FORWARD的默认策略设置为ACCEPT,否则无法正常运行。
步骤2:在主节点上操作
# kubeadm token create
4vstbr.hod97s2tevtsmolh
# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
5511e8fe84cb1da5c62ad756e72f3725da8a06fde0f6179563d58df283444ba0
记下token和hash。由于token有期限,最好不要重复使用。
第三步:在Worker节点上执行
# kubeadm join 10.0.0.1:6443 --token 4vstbr.hod97s2tevtsmolh --discovery-token-ca-cert-hash sha256:5511e8fe84cb1da5c62ad756e72f3725da8a06fde0f6179563d58df283444ba0
# kubectl label node chiya node-role.kubernetes.io/worker=
请用您自己的节点名称来替换并阅读该句话。
最初是使用armhf进行构建的,但现在添加了arm64节点!
# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
chino Ready master 251d v1.17.0 10.0.0.1 <none> Raspbian GNU/Linux 10 (buster) 4.19.97-v7l+ docker://19.3.5
chiya Ready worker 15h v1.17.0 10.0.0.5 <none> Debian GNU/Linux 10 (buster) 5.4.51-v8+ docker://19.3.12
cocoa Ready worker 251d v1.17.0 10.0.0.2 <none> Raspbian GNU/Linux 10 (buster) 4.19.75-v7l+ docker://19.3.5
rize Ready worker 251d v1.17.0 10.0.0.3 <none> Raspbian GNU/Linux 10 (buster) 4.19.75-v7l+ docker://19.3.5
syaro Ready worker 135d v1.17.0 10.0.0.4 <none> Raspbian GNU/Linux 10 (buster) 4.19.118-v7l+ docker://19.3.11
# kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
chino Ready master 251d v1.17.0 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=chino,kubernetes.io/os=linux,node-role.kubernetes.io/master=
chiya Ready worker 16h v1.17.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=chiya,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
cocoa Ready worker 251d v1.17.0 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=cocoa,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
rize Ready worker 251d v1.17.0 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=rize,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
syaro Ready worker 135d v1.17.0 beta.kubernetes.io/arch=arm,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm,kubernetes.io/hostname=syaro,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
处理类似于查看 arch 的操作将获取与 arch 相匹配的容器镜像。
# kubectl get pod -n kube-system -o wide | grep flannel
kube-flannel-ds-arm-2gdmm 1/1 Running 41 251d 10.0.0.1 chino <none> <none>
kube-flannel-ds-arm-758n9 1/1 Running 14 251d 10.0.0.2 cocoa <none> <none>
kube-flannel-ds-arm-l6fkd 1/1 Running 15 251d 10.0.0.3 rize <none> <none>
kube-flannel-ds-arm-xd9rm 1/1 Running 0 135d 10.0.0.4 syaro <none> <none>
kube-flannel-ds-arm64-dj57r 1/1 Running 4 16h 10.0.0.5 chiya <none> <none>
# kubectl describe pod kube-flannel-ds-arm64-dj57r -n kube-system | grep -i image:
Image: quay.io/coreos/flannel:v0.11.0-arm64
Image: quay.io/coreos/flannel:v0.11.0-arm64
太棒了!
在Kubernetes上运行motionEye。
我认为,通过查看motioneye的Docker Hub镜像标签,就可以发现没有arm64版本。总之,我试着部署后,armhf的Docker也可以运行arm64版本。虽然我没有详细研究,但我认为因为所有库相关内容都是由armhf组成的镜像,所以才能正常运行。不过,我还是有点担心与内核相关的处理是否会出现错误…这次我要使用相机设备(/dev/video0),应该没问题吧?有些不确定。(虽然最后还是成功了)
从Wiki的“Install-In-Docker”页面中列出的命令,我会编写kubernetes的yaml文件。
docker run --name="motioneye" \
-p 8765:8765 \
--hostname="motioneye" \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/motioneye:/etc/motioneye \
-v /var/lib/motioneye:/var/lib/motioneye \
--restart="always" \
--detach=true \
ccrisan/motioneye:master-amd64
以下是一个可能的中文翻译选项:
所使用的镜像是”ccrisan/motioneye:master-armhf”,端口号是”8765″。
另外就是卷的设置了。您只需将宿主机的/etc/localtime显示出来,将配置文件放在/etc/motioneye并将录像数据映射到/var/lib/motioneye的永久卷上即可。
我没有考虑卷的策略。目标部署节点是固定的,而且也没有CSI,所以我们会简单地使用hostPath进行创建。性能也非常强大…
所以,这是最重要的视频设备部分。
--device=/dev/video0
这个也可以像/etc/localtime那样,在hostPath中定义。
以下是用于录制数据的yaml文件。
apiVersion: v1
kind: PersistentVolume
metadata:
name: motioneye-pv
labels:
type: local
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
hostPath:
path: /opt/motioneye/data
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: motioneye-pvc
labels:
app: motioneye
tier: motioneye
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
以下是用于配置文件的yaml。
apiVersion: v1
kind: PersistentVolume
metadata:
name: motioneye-etc-pv
labels:
type: local
spec:
capacity:
storage: 100Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
hostPath:
path: /opt/motioneye/etc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: motioneye-etc-pvc
labels:
app: motioneye
tier: motioneye
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
在Service中使用nodePort定义端口号进行公开。
apiVersion: v1
kind: Service
metadata:
name: motioneye
labels:
app: motioneye
spec:
type: NodePort
ports:
- port: 8765
targetPort: 8765
nodePort: 30876
protocol: TCP
selector:
app: motioneye
最后是Deployment的定义。
为了使用主机的/dev/video0,我们在securityContext中授予了root权限。
我们使用nodeSelector指定了部署的节点名称。(我记得不推荐使用kubernetes.io/hostname,所以请确保正确地添加标签。)
apiVersion: apps/v1
kind: Deployment
metadata:
name: motioneye
labels:
app: motioneye
spec:
replicas: 1
selector:
matchLabels:
app: motioneye
template:
metadata:
labels:
app: motioneye
spec:
containers:
- image: ccrisan/motioneye:master-armhf
name: motioneye
ports:
- containerPort: 8765
name: motioneye
securityContext:
privileged: true
volumeMounts:
- name: motioneye-local-storage
mountPath: /var/lib/motioneye
- name: motioneye-etc-storage
mountPath: /etc/motioneye
- name: etc-localtime
mountPath: /etc/localtime
- name: dev-video0
mountPath: /dev/video0
volumes:
- name: motioneye-local-storage
persistentVolumeClaim:
claimName: motioneye-pvc
- name: motioneye-etc-storage
persistentVolumeClaim:
claimName: motioneye-etc-pvc
- name: etc-localtime
hostPath:
path: /etc/localtime
- name: dev-video0
hostPath:
path: /dev/video0
nodeSelector:
kubernetes.io/hostname: chiya
一旦yaml全部完成后,将它们一次性应用于文件所在的目录。
# kubectl apply -f .
我要确认部署的状态。
# kubectl get pod,pv,pvc,svc -o wide | grep motioneye
pod/motioneye-664556dcdb-bb2hb 1/1 Running 1 13h 172.16.6.10 chiya <none> <none>
persistentvolume/motioneye-etc-pv 100Mi RWO Recycle Bound default/motioneye-etc-pvc 19h Filesystem
persistentvolume/motioneye-pv 100Gi RWO Recycle Bound default/motioneye-pvc 20h Filesystem
persistentvolumeclaim/motioneye-etc-pvc Bound motioneye-etc-pv 100Mi RWO 19h Filesystem
persistentvolumeclaim/motioneye-pvc Bound motioneye-pv 100Gi RWO 20h Filesystem
service/motioneye NodePort 10.96.185.61 <none> 8765:30876/TCP 20h app=motioneye
……我对于存储状态的好坏还不太清楚。这对于未来是我的功课。
谷歌浏览器的设置
根据Chrome的版本,可能无法通过监视摄像头进行流媒体观看。
如果遇到这种情况,请尝试更改以下设置。
chrome://flags/#enable-lazy-image-loading
motionEye的配置
我通过家里的电脑使用master节点(192.168.1.9)设置的nodePort端口(30876)进行访问。
在初始状态下,我可以无需输入密码即可登录。
虽然非常晚了,但只要有户外灯光,不需要摄像机照明和红外线,感觉应该可以看得很清楚。
我拿手头的数码相机进行对比,觉得可能有ISO 4000以上的感光度。
关于动体检测的技巧,我在感光度提高的夜晚将“显示帧变化”设为开启,“自动噪声检测”设为关闭,并调整右上角的数字为10以上50以下,将“帧变化阈值”调整为150至200。
因为还要考虑相机的性能,所以请您根据自己的情况进行调整。
由于「帧更改阈值」接近0%,建议直接从GUI编辑设置文件。
设置文件大约位于部署motion-eye节点的「/opt/motioneye/etc/camera-1.conf」。
我特意去拍摄动态检测。
最后
看起来motioneye是通过视频的帧差异像素数量进行动态检测的。调整可能有些困难,但是如果能够调整得当,应该能够正确地进行所期望的检测。
另外,由于它具有丰富的云上传和事件处理功能,因此将动态检测时的图像文件传送到物体识别API中进行处理也是一件有趣的事情。
个人以前曾经努力通过GCP的AutoML Vision API来创建模型,用于判断是自己的车辆还是邻居的车辆,并通过Twitter进行通知。
因为图像偏紫色,所以那时我们使用了红外摄像机。当时还有一些GCP的信用额度,所以我们激烈地使用了,但是免费的额度用完后,我们开始担心费用,于是我们从GCP撤退了。能做到这一步真的很有趣对吧。
嗯,撤退只是开个玩笑,现在我们购买了“Coral Edge TPU”,所以我想再次在GCP上尝试制作AutoML Vision模型,玩玩看。(尽管我已经快一年了…)
如果可以在Kubernetes上部署监视相机应用程序,那么只需要在相同的边缘设备上进行相同的标记,就可以一次性部署多个监视相机。如果正确准备持久性存储,那么配置文件和录制的视频的集中管理也会更加方便。这样一来,工厂和工业物联网可能会有更多的应用场景。不过在那之前,首先需要解决局域网电缆的问题,所以需要本地5G。