在树莓派监控摄像头上部署motioneye的方式,使用kubernetes进行部署

首先

在「CloudNative Days TOKYO 2020」上,有一个关于使用Jetson的k3s部署监控摄像头系统的会议,这让我有了一些启发,决定在家里的树莓派kubernetes集群上也尝试一下。

环境

由于家庭网络是多层次的私有网络结构,因此在设备方面一度感到困扰。
本次工程中,我们增加的部分如下图所示的红色区域。

スクリーンショット 2020-09-10 11.48.03.png

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)进行访问。
在初始状态下,我可以无需输入密码即可登录。

スクリーンショット 2020-09-10 2.47.36.png

虽然非常晚了,但只要有户外灯光,不需要摄像机照明和红外线,感觉应该可以看得很清楚。
我拿手头的数码相机进行对比,觉得可能有ISO 4000以上的感光度。
关于动体检测的技巧,我在感光度提高的夜晚将“显示帧变化”设为开启,“自动噪声检测”设为关闭,并调整右上角的数字为10以上50以下,将“帧变化阈值”调整为150至200。
因为还要考虑相机的性能,所以请您根据自己的情况进行调整。

スクリーンショット 2020-12-29 0.51.04.png

由于「帧更改阈值」接近0%,建议直接从GUI编辑设置文件。
设置文件大约位于部署motion-eye节点的「/opt/motioneye/etc/camera-1.conf」。

スクリーンショット 2020-09-10 3.06.15.png

我特意去拍摄动态检测。

スクリーンショット 2020-09-10 9.49.54.png

最后

看起来motioneye是通过视频的帧差异像素数量进行动态检测的。调整可能有些困难,但是如果能够调整得当,应该能够正确地进行所期望的检测。
另外,由于它具有丰富的云上传和事件处理功能,因此将动态检测时的图像文件传送到物体识别API中进行处理也是一件有趣的事情。
个人以前曾经努力通过GCP的AutoML Vision API来创建模型,用于判断是自己的车辆还是邻居的车辆,并通过Twitter进行通知。

twitter_EhYUKjtUcAAjvyQ.jpg

因为图像偏紫色,所以那时我们使用了红外摄像机。当时还有一些GCP的信用额度,所以我们激烈地使用了,但是免费的额度用完后,我们开始担心费用,于是我们从GCP撤退了。能做到这一步真的很有趣对吧。

嗯,撤退只是开个玩笑,现在我们购买了“Coral Edge TPU”,所以我想再次在GCP上尝试制作AutoML Vision模型,玩玩看。(尽管我已经快一年了…)

如果可以在Kubernetes上部署监视相机应用程序,那么只需要在相同的边缘设备上进行相同的标记,就可以一次性部署多个监视相机。如果正确准备持久性存储,那么配置文件和录制的视频的集中管理也会更加方便。这样一来,工厂和工业物联网可能会有更多的应用场景。不过在那之前,首先需要解决局域网电缆的问题,所以需要本地5G。

广告
将在 10 秒后关闭
bannerAds