Kubernetes-边缘计算系统-全-
Kubernetes 边缘计算系统(全)
原文:
annas-archive.org/md5/2e531e4b24739995d768c76cf40df599译者:飞龙
序言
边缘计算是指在数据源附近处理数据。为了构建一个边缘计算系统,您需要了解边缘系统用于处理信息的不同层次和组件。通过使用 K3s —— 一个轻量级的 Kubernetes,您可以利用容器的优势来设计分布式系统,并自动化应用程序的更新方式。本书将为您提供创建自己的边缘系统所需的所有工具,涵盖边缘计算的基础知识及不同应用场景。通过阅读本书,您将了解如何实施自己的边缘计算系统,使用 K3s 容器化技术构建 Kubernetes 集群并利用云原生开源软件。
本书适合人群
本书适合那些希望将数据处理任务迁移到边缘的运维或 DevOps 工程师,或者那些希望实现边缘计算系统但没有技术背景的工程师。本书也适合那些希望实施或实验边缘计算以应对不同或潜在使用场景的爱好者和创业者。
本书内容概览
第一章,使用 Kubernetes 进行边缘计算,解释了边缘计算的基本概念,包括其组件、层次、构建这些系统的示例架构,并展示了如何使用交叉编译技术为 Go、Rust、Python 和 Java 等编程语言编写代码,以便在运行 ARM 处理器的设备上运行边缘设备上的软件。
第二章,K3s 安装与配置,描述了什么是 K3s,它的组件以及如何使用不同的配置(如单节点和多节点)安装 K3s,最后解释了 K3s 集群的高级配置,如何使用外部存储代替 etcd,如何通过安装和使用 Ingress 控制器将应用程序暴露到集群外部,如何卸载集群以及一些有用的命令来排除集群安装问题。
第三章,K3s 高级配置与管理,介绍了 K3s 集群的高级配置,包括安装 MetalLB 这一裸金属负载均衡器,安装 Longhorn 作为边缘存储,集群升级以及最终备份和恢复 K3s 集群配置。
第四章,k3OS 安装与配置,重点讲解了如何使用 k3OS —— 一种打包为 ISO 镜像的 Kubernetes 发行版,可以用于在边缘设备上安装。还介绍了如何在 ARM 设备上使用覆盖功能,并通过配置文件执行安装,配置单节点或多节点 K3s 集群。
第五章,K3s 边缘计算实验的家庭实验室,介绍了如何使用前几章描述的所有配置来配置自己的家庭实验室,从而生成一个基本的生产环境,用于运行边缘计算应用程序。首先是集群配置,包括入口控制器配置、应用程序持久化配置以及如何在边缘部署 Kubernetes 仪表盘。
第六章,使用 Ingress 控制器和证书暴露应用程序,介绍了如何配置和使用 NGINX、Traefik 和 Contour 等 Ingress 控制器,结合 cert-manager 使用 TLS 证书在裸金属上暴露运行的应用程序。
第七章,使用 Flux 进行边缘应用程序的 GitOps,探讨了如何使用 GitOps 策略与 Flux 和 GitHub Actions 一起自动化更新边缘应用程序,当源代码更改时进行检测。
第八章,使用 Linkerd 进行可观察性和流量分割,描述了如何使用服务网格实现简单的监控、可观察性、流量分割和故障流量,以提高使用 Linkerd 在边缘运行的服务的可用性。
第九章,使用 Knative 和 Cloud Events 的边缘无服务器和事件驱动架构,介绍了如何使用 Knative Serving 实现自己的无服务器功能。还展示了如何结合 Knative Eventing 和 Cloud Event 实现简单的事件驱动架构,在边缘系统中定义和运行事件。
第十章,边缘的 SQL 和 NoSQL 数据库,探讨了可以用于记录边缘数据的不同类型的数据库。本章具体介绍了 MySQL、Redis、MongoDB、PostgreSQL 和 Neo4j 的配置和使用,涵盖了在边缘运行 SQL 和 NoSQL 数据库的不同用例。
第十一章,使用 Prometheus 和 Grafana 监控边缘,重点介绍了如何使用时间序列数据库 Prometheus 和 Grafana 来监控边缘环境和设备。具体来说,本章侧重于创建自定义实时图表,以展示来自捕捉温湿度数据的边缘传感器的数据。
第十二章,使用 LoRa 进行长距离的边缘设备通信,描述了如何使用 LoRa 无线协议进行远距离的边缘设备通信,以及如何使用 MySQL 和 Grafana 可视化捕获的传感器边缘数据。
第十三章,使用 GPS、NoSQL 和 K3s 集群的地理定位应用,描述了如何使用 GPS 模块和 ARM 设备实现一个简单的地理定位或地理追踪系统,实时显示车辆的移动情况,并报告其在指定日期范围内的追踪日志。
第十四章,使用 Python 和 K3s 集群进行计算机视觉,描述了如何创建一个智能交通系统,在城市驾驶时检测潜在的障碍物,并在高峰时段提供智能警报和实时交通状态报告。书中还一步步介绍了如何使用 Redis、OpenCV、TensorFlow Lite、Scikit Learn 和 GPS 模块在边缘计算设备上实现这一系统。
第十五章,设计你自己的边缘计算系统,描述了创建自己边缘计算系统的基本方法,以及如何使用云服务提供商的托管服务、辅助硬件和软件,并在实施系统时提供一些有用的建议。最后还讨论了边缘计算的其他使用案例。
如何充分利用本书
为了更好地阅读本书,你需要具备一定的 Linux 命令行经验和一些基本的编程知识。在阅读每一章时,务必注意下载源代码,这将简化你对书中所有示例的使用。
本书主要使用 MacOS 进行本地配置。对于 Raspberry Pi 实现,则使用 Linux。最后,有一章使用 Windows 更新 ESP32 固件。
本书中运行示例所需的所有要求都在每章的“技术要求”部分中详细描述。
如果你使用的是本书的数字版本,我们建议你自己输入代码,或者从本书的 GitHub 仓库中访问代码(链接在下一节中提供)。这样做可以帮助你避免与代码复制和粘贴相关的潜在错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件,链接是 github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes。如果代码有更新,它会在 GitHub 仓库中同步更新。
我们还提供了其他来自我们丰富书籍和视频目录中的代码包,您可以在 github.com/PacktPublishing/ 查阅。快去看看吧!
下载彩色图片
我们还提供了一份 PDF 文件,包含本书中使用的截图和图表的彩色图像。你可以在这里下载:packt.link/gZ68B。
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码词汇、函数、服务名称、部署名称、变量、路径名和 URL。例如:“WIFISetUp(void):我们配置 Wi-Fi 连接,在此处您需要将 NET_NAME 替换为您的网络名称,将 PASSWORD 替换为连接密码。”
代码块设置如下:
@app.route('/')
def hello_world():
return 'It works'
任何命令行输入或输出格式如下所示:
$ mkdir code
$ kubectl apply -f example.yaml
粗体:表示新术语、重要词汇或屏幕上显示的单词。例如,菜单或对话框中的单词通常以粗体显示。以下是一个例子:“现在通过点击文件 | 新建来创建另一个文件”
提示或重要说明
如此显示。
联系我们
我们非常欢迎读者的反馈。
一般反馈:如果您对本书的任何内容有疑问,请通过电子邮件联系 customercare@packtpub.com,并在邮件主题中注明书名。
勘误表:虽然我们已尽力确保内容的准确性,但难免会有错误。如果您在本书中发现错误,欢迎向我们报告。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上发现我们的作品有任何非法复制品,我们将感激您提供该位置地址或网站名称。请通过版权邮箱与我们联系,并附上该材料的链接。
如果您有兴趣成为作者:如果您在某个领域具有专业知识,并且有兴趣撰写或为书籍做贡献,请访问 authors.packtpub.com。
分享您的想法
阅读完 《边缘计算系统与 Kubernetes》 后,我们很想听听您的想法!请点击这里直接访问该书的 Amazon 评价页面并分享您的反馈。
您的评价对我们和技术社区非常重要,将帮助我们确保提供优质的内容。
序言
序言
序言
第一部分:边缘计算基础
在本书的这一部分,您将学习边缘计算系统的基本概念、架构、用例以及当前的解决方案,还将学习如何使用 k3s/k3OS 和 Raspberry Pi 设备安装集群。
本书的这一部分包括以下章节:
-
第一章,使用 Kubernetes 进行边缘计算
-
第二章,K3s 安装与配置
-
第三章,K3s 高级配置与管理
-
第四章,k3OS 安装与配置
-
第五章,K3s 边缘计算实验室
第一章:使用 Kubernetes 进行边缘计算
边缘计算是一种新兴的分布式系统范式,其中计算信息的单元靠近该信息的来源。该范式的好处是,它帮助您的系统减少网络中断,并减少通过云进行处理时的延迟。这意味着您可以获得更好的交互体验,尤其是在机器学习或物联网(IoT)应用程序中。本章介绍了边缘计算的基础知识和重要性,以及 Kubernetes 如何用于边缘计算。它还涵盖了使用低功耗设备的不同场景和基本架构,这些设备可以使用私有云和公共云来交换数据。
在本章中,我们将覆盖以下主要主题:
-
使用 K3s 的边缘数据中心和基本的边缘计算概念
-
基于 K3s 的基本边缘计算架构
-
将软件适配到边缘运行
技术要求
在本章中,我们将会在边缘设备上运行我们的边缘计算(例如树莓派),因此我们需要为先进的 RISC 机器(ARM)设置一个交叉编译工具链。
为此,您需要以下设备之一:
-
一台可以访问终端的 Mac
-
安装了 Ubuntu 的 PC,并且可以通过终端访问
-
安装了 Ubuntu 的虚拟机,并且可以通过终端访问
有关更多详细信息和代码片段,请查看 GitHub 上的这个资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch1。
使用 K3s 的边缘数据中心和基本的边缘计算概念
随着云计算的发展,企业和组织开始将处理任务迁移到边缘计算设备上,目的是降低成本,并从他们支付的基础设施中获得更多的好处。作为本书的入门内容,我们需要了解与边缘计算相关的基本概念,并理解为何我们要使用 K3s 进行边缘计算。那么,让我们从基本概念开始吧。
边缘和边缘计算
根据高通和思科公司的定义,边缘可以被描述为“数据在跨越广域网(WAN)之前进行处理的任何地方”;这就是边缘,但什么是边缘计算呢?Cloudwards.net 的 Eric Hamilton 在一篇文章中将边缘计算定义为“沿着网络边缘处理和分析数据,尽可能靠近数据收集的源头,以便使数据变得可操作。” 换句话说,边缘计算是指将数据处理靠近源头,并将计算分布在不同的地方,使用靠近数据源的设备进行处理。
为了提供更多背景信息,我们来看看下一个图示:

图 1.1 – 边缘层的组件
这个图示展示了数据在不同上下文中的处理方式,这些上下文包括以下几种:
-
云层:在这一层,你可以找到云服务提供商,如 AWS、Azure、GCP 等。
-
近端边缘:在这一层,你可以找到电信基础设施和设备,如 5G 网络、无线虚拟设备等类似设备。
-
远端边缘:在这一层,你会找到边缘集群,如 K3s 集群或在云与边缘层之间交换数据的设备,但这一层可以细分为微型边缘层。
-
微型边缘:在这一层,你会找到传感器、终端设备,它们与处理设备交换数据,以及远端边缘的边缘集群。
重要提示
请记住,边缘计算指的是数据在到达目标地点之前先在边缘设备上进行处理,这些目标地点可以是公有云或私有云。
建设边缘集群时,还需要考虑其他重要概念,包括以下内容:
-
雾计算:一种将系统分布在近端边缘和远端边缘设备之间的云服务架构;这些设备可以地理上分布在不同区域。
-
多接入边缘计算(MEC):这将计算分布到大规模网络的边缘,具备低延迟和高带宽,是移动边缘计算的前身;换句话说,处理过程使用电信网络和移动设备。
-
云小型数据中心:这是一个小规模的云数据中心,通常用于资源密集型的使用场景,如数据分析、机器学习(ML)等。
边缘计算的好处
简单介绍完毕,接下来我们来了解边缘计算的主要优势,其中包括以下几点:
-
减少延迟:边缘计算可以在边缘设备上处理大量计算任务,从而减少将信息传输到目的地的延迟。
-
减少带宽:边缘计算可以减少所用带宽,通过将部分数据处理放在边缘设备上,从而减少网络流量。
-
降低成本:减少延迟和带宽意味着降低运营成本,这是边缘计算的最重要优势之一。
-
提高安全性:边缘计算使用数据聚合和数据加密算法来提高数据访问的安全性。
现在让我们讨论容器、Docker 和 containerd。
边缘计算中的容器、Docker 和 containerd
在过去几年里,由于 Docker 的成功,容器技术的采用一直在增加。Docker 在过去几年一直是最受欢迎的容器引擎。容器技术为企业提供了一种使用微服务架构设计应用程序的方法。通过这种方式,公司加速了其开发进程,并在扩展应用程序方面制定了战略。因此,首先需要理解一个基本概念:容器是一个小型的运行时环境,它将你的应用程序与其运行所需的所有依赖项打包在一起。这个概念并不新鲜,但 Docker,这个容器引擎,使得这个概念变得广为人知。简单来说,Docker 使用带有必要依赖项的小型操作系统镜像来运行你的软件。这可以称为操作系统虚拟化。其作用是利用 Linux 的 cgroups 内核功能,限制进程的 CPU、内存、网络、I/O 等资源。其他操作系统,如 Windows 或 FreeBSD,也使用类似的功能来进行隔离并创建这种虚拟化类型。让我们来看下一个图来表示这些概念:

图 1.2 – 操作系统内部的容器化应用程序
该图展示了容器不依赖于特殊功能,如 VMware、Hyper-V 和 Xen 等硬件虚拟化中常见的虚拟机管理程序;相反,应用程序作为二进制文件运行在容器内部,并重用主机的内核。可以说,运行容器几乎就像在一个目录中运行一个二进制程序,但它添加了一些资源限制,在 Linux 容器的情况下,使用 cgroups。
Docker 实现了所有这些抽象。它是一个流行的容器工具链,增加了一些版本控制功能,类似于 Git。这也是它变得非常受欢迎的主要原因,并且它具有操作系统级别的易于移植性和版本控制功能。目前,containerd 是 Docker 和 Kubernetes 用于创建容器的容器运行时。一般来说,使用 containerd,你可以创建没有额外功能的容器;它非常优化。随着边缘计算的爆炸性增长,containerd 已经成为在低资源环境中运行容器的重要软件。
总体而言,使用这些技术,你可以做到以下几点:
-
标准化如何打包你的软件。
-
让你的软件具有可移植性。
-
以更简单的方式维护你的软件。
-
在低资源环境中运行应用程序。
因此,Docker 必须被视为构建边缘计算和低资源环境中的重要软件组件。
分布式系统、边缘计算和 Kubernetes
在过去十年里,分布式系统从使用单体架构的多节点集群发展到使用微服务架构的多节点集群。构建微服务的第一步通常是使用容器,但一旦系统需要扩展,就有必要使用编排工具。这就是 Kubernetes 发挥作用的地方。
举个例子,假设有一个乐团,里面有很多音乐家。你可以看到有音乐家在演奏钢琴、小号等等。但如果乐团没有组织好,你需要什么来组织所有的音乐家?答案是一个指挥或者编排者。这时 Kubernetes 就登场了;每个音乐家就是一个容器,需要与其他音乐家沟通或聆听,当然,还需要听从指挥或编排者的指示。通过这种方式,所有音乐家可以在正确的时机演奏乐器,并且能够发出美妙的音响。
这正是 Kubernetes 所做的;它是一个容器的编排器,但同时它也是一个平台,拥有构建分布式系统所需的所有预构建模块,随时可以扩展,并且设计了最佳实践,帮助你实施敏捷开发和 DevOps 文化。根据你的使用场景,有时候使用 Docker 或 containerd 等小型工具可能更好,但对于复杂或高需求的场景,使用 Kubernetes 会更合适。
使用 K3s 构建的边缘集群——一个轻量级的 Kubernetes
现在,大问题是如何开始构建边缘计算系统。让我们从 K3s 开始。K3s 是由 Rancher Labs 创建的经过 Kubernetes 认证的发行版。K3s 默认不包含一些非必需的额外功能,这些功能在 Kubernetes 中并不是必须的,但可以在后期添加。K3s 使用 containerd 作为其容器引擎,这使得 K3s 可以在使用 ARM 设备的低资源环境中运行。例如,你还可以在生产环境中在 x86_64 设备上运行 K3s。然而,对于本书的目的,我们将使用 K3s 作为构建边缘计算系统的主要软件,并且使用 ARM 设备。
说到边缘集群,K3s 提供了与 Kubernetes 相同的强大功能,但它以一个小巧和优化的方式提供,同时还设计了一些特别适用于边缘计算系统的功能。与其他 Kubernetes 发行版相比,K3s 非常容易使用。它是一个轻量级的 Kubernetes,可以用于边缘计算、沙箱环境或任何你想要的用途,具体取决于使用场景。
使用 ARM 处理器和微型数据中心的边缘设备
现在,是时候谈论边缘设备和 ARM 处理器了,我们先从边缘设备开始。边缘设备被设计用来处理和分析接近数据源位置的信息;这正是边缘计算理念的来源。谈到低能耗设备时,x86 或 Intel 处理器比 ARM 处理器消耗更多的能源并且发热更高。这意味着需要更多的电力和更多的散热;换句话说,你需要为 x86_64 处理器支付更多的费用。另一方面,ARM 处理器计算能力较低,且能耗更低。这就是 ARM 处理器在智能手机设备上成功的原因;它们在处理和能耗之间提供了比 Intel 处理器更好的性价比。
因此,企业有兴趣设计使用 ARM 处理器的微型数据中心以供服务器使用。出于同样的原因,企业也开始将工作负载迁移到使用 ARM 处理器的设备上进行处理。一个例子是 AWS Graviton2,它是一项提供使用 ARM 处理器的云实例的服务。
边缘计算图示以构建你的系统
现在,掌握了容器、编排器、边缘计算及其各层的基本概念后,我们可以专注于五种基本的边缘计算配置图示,这些图示可以用来设计这种系统。所以,让我们以 K3s 作为接下来图示中边缘计算的主要平台。
边缘集群与公共云
这个配置在公共云或私有云与边缘层之间共享和处理数据,但让我们来解释它的不同层次:
-
云层:这一层位于公共云及其提供商处,如 AWS、Azure 或 GCP。该提供商可以提供使用 Intel 或 ARM 处理器的实例。例如,如果你需要 ARM 处理器,AWS 提供了 AWS Graviton2 实例。作为补充,公共云可以提供管理服务来存储数据,如数据库、存储等。私有云也可以处于这一层。你可以找到如 VMware ESXi 或 OpenStack 等软件来本地提供此类服务或实例。你甚至可以选择采用公共云和私有云的混合方法。一般来说,这一层支持你的远端和微型边缘层进行存储或数据处理。
-
近端边缘:在这一层,你可以找到用于在云层和远端层之间传输所有数据的网络设备。通常,这些设备包括电信设备、5G 网络等。
-
远端边缘:在这一层,你可以找到 K3s 集群、类似的轻量级集群如 KubeEdge,以及 Docker 或 containerd 等软件。一般来说,这是你的本地处理层。
-
微型边缘:这是远端边缘内部的一层,你可以找到像智能手表、物联网设备等边缘设备,它们将数据发送到远端边缘。

图 1.3 – 边缘集群与公共云
用例包括以下内容:
-
需要在互联网上或私有云之间共享数据的场景
-
在云端与边缘之间分发数据处理,如生成机器学习模型或进行预测
-
需要扩展物联网应用程序,并且应用响应时间至关重要的场景
-
需要通过数据聚合策略分发数据并在系统中进行加密来保护数据的场景
区域边缘集群和公共云
该配置专注于在不同区域分布处理策略,并通过公共云共享数据。让我们来解释不同的层级:
-
云层:这一层包含托管服务,如数据库,用于在不同区域分发数据。
-
近端边缘:在这一层,你可以找到网络设备,用于在云层和远端层之间移动所有数据。通常包括电信设备、5G 网络等。
-
远端边缘:在这一层,你可以找到跨不同区域的 K3s 集群。这些集群或节点可以共享或更新存储在公共云中的数据。
-
微型边缘:在这里,你可以找到接近每个区域的不同边缘设备,因为远端边缘集群会根据这种分布式配置处理信息。

图 1.4 – 区域边缘集群和公共云
使用案例包括以下内容:
-
跨不同区域的不同集群配置
-
减少应用程序响应时间,选择最近的数据或处理节点位置,这在物联网应用程序中至关重要
-
跨不同区域共享数据
-
在不同区域分配处理
单节点集群和公共/私有云
这是一个基本配置,单个计算机处理所有从小型边缘设备捕获的信息。让我们来解释不同的层级:
-
云层:在这一层,你可以找到系统的数据存储。它可以放置在公共云或私有云中。
-
近端边缘:在这一层,你可以找到网络设备,用于在云层和远端层之间移动所有数据。通常包括电信设备、5G 网络等。
-
远端边缘:在这一层,你可以找到一个单节点 K3s 集群,从小型边缘设备中收集数据。
-
微型边缘:用于捕获数据的设备,如智能手表、平板电脑、摄像头、传感器等。这种配置更适合本地或小规模处理。

图 1.5 – 单节点集群和公共/私有云
使用案例包括以下内容:
-
低成本和低能耗环境
-
可以由太阳能电池板或风力涡轮机提供电力的绿色边缘应用
-
小型处理或使用案例,如分析健康记录或需要本地处理或不太复杂的自主房屋系统
现在让我们将软件适配以在边缘运行。
让您的软件适应边缘运行
在设计边缘计算系统时,一个重要的方面是选择处理器架构来构建您的软件。由于计算消耗较低,ARM 是一种受欢迎的架构,但如果选择 ARM 作为架构,通常需要将现有代码从 x86_64(Intel)转换为 ARM(如 RI 和 AWS Graviton2 实例上的 ARMv7)。以下小节包含了将平台从一个转换到另一个的简短指南;这一过程叫做交叉编译。通过这个过程,您将能够在 ARM 设备上使用 Go、Python、Rust 和 Java 运行您的软件。接下来,让我们开始。
让 Go 在 ARM 上运行
首先,必须在系统上安装 Go。以下是几种安装 Go 的方法。
在 Linux 上安装 Go
在 Linux 上安装 Go,请执行以下步骤:
-
下载并解压 Go 官方二进制文件:
$ wget https://golang.org/dl/go1.15.linux-amd64.tar.gz $ tar -C /usr/local -xzf go1.15.linux-amd64.tar.gz -
设置运行 Go 的环境变量:
$ mkdir $HOME/go -
在终端的配置文件中设置您的
GOPATH,请使用以下行。~/.profile是常见的设置环境变量的文件;让我们修改.profile文件:$ export PATH=$PATH:/usr/local/go/bin $ export GOPATH=$HOME/go -
使用以下命令加载新配置:
$ . ~/.profile $ mkdir $GOPATH/src -
(可选)。如果您愿意,您可以使用以下命令在终端中临时设置这些环境变量:
$ export PATH=$PATH:/usr/local/go/bin $ export GOPATH=$HOME/go -
要检查是否已配置
GOPATH,请运行以下命令:$ go env GOPATH
现在,您已经准备好在 Linux 上使用 Go。让我们继续使用 Mac 进行安装。
在 Mac 上安装 Go
在 Mac 上安装 Go,请执行以下步骤:
-
使用以下命令安装 Homebrew(简称
brew):$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
安装完成后,使用
brew安装 Go:$ brew install go
重要提示
要了解如何安装brew,您可以查看brew.sh的官方页面。
使用 Go 从 x86_64 交叉编译到 ARM
从 x86_64 交叉编译到 ARM,请执行以下步骤:
-
创建一个文件夹来存储您的代码:
$ cd ~/ $ mkdir goproject $ cd goproject -
创建一个初始的 Go 配置,以便在
GOPATH命令之外安装外部 Go 库;为此,请执行以下命令:$ go mod init main -
创建一个内容为
Hello World的example.go文件:$ cat << EOF > example.go package main import "fmt" func main() { fmt.Println("Hello World") } EOF -
假设您的环境是 x86_64,并且您希望进行针对 ARMv7 的交叉编译,请执行以下命令:
$ env GOOS=linux GOARM=7 GOARCH=arm go build example.go
使用以下命令行以支持 ARMv8 64 位:
$ env GOOS=linux GOARCH=arm64 go build example.go
重要提示
如果您想查看其他交叉编译的选项,请参见github.com/golang/go/wiki/GoArm。
设置生成的二进制文件的执行权限:
$ chmod 777 example
$ ./example
- 将生成的二进制文件复制到您的 ARM 设备并测试其是否正常工作。
在下一节中,我们将学习如何让 Rust 在 ARM 上运行。
让 Rust 在 ARM 上运行
首先,必须在系统上安装 Rust。以下是几种安装 Rust 的方法。
在 Linux 上安装 Rust
在 Linux 上安装 Rust,请执行以下步骤:
-
在终端中执行以下命令来安装 Rust:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -
在终端配置文件中设置 Rust 的路径。例如,如果您使用的是 Bash,请将以下行添加到您的
.bashrc中:$ export PATH=$PATH:$HOME/.cargo/bin
在 Mac 上安装 Rust
在 Mac 上安装 Rust,请执行以下步骤:
-
使用以下命令安装 Homebrew:
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
安装完成后,使用
brew安装rustup:$ brew install rustup-init -
运行
rustup命令来安装 Rust 及其所需的工具,使用以下命令:$ rustup-init -
通过将以下行添加到终端配置文件中来设置终端环境变量:
$ export PATH=$PATH:$HOME/.cargo/bin
重要提示
Mac 用户通常使用 ZSH 终端,因此他们需要使用.zshrc。如果您使用其他终端,请寻找正确的配置文件或通用的/etc/profile。
在 Mac 上使用 Rust 从 x86_64 交叉编译到 ARMv7
从 x86_64 交叉编译到 ARM,请执行以下步骤:
-
在您的 Mac 上安装与 ARMv7 架构匹配的编译器和环境变量补充程序;为此,请执行以下命令:
$ brew tap messense/macos-cross-toolchains -
通过执行以下命令下载用于交叉编译的 ARMv7 支持:
$ brew install armv7-unknown-linux-gnueabihf -
现在设置环境变量:
$ export CC_armv7_unknown_linux_gnueabihf=armv7-unknown-linux-gnueabihf-gcc $ export CXX_armv7_unknown_linux_gnueabihf=armv7-unknown-linux-gnueabihf-g++ $ export AR_armv7_unknown_linux_gnueabihf=armv7-unknown-linux-gnueabihf-ar $ export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=armv7-unknown-linux-gnueabihf-gcc -
创建一个文件夹以存储您的代码:
$ cd ~/ $ mkdir rustproject $ cd rustproject -
使用 Rust 创建一个初始的
Hello World项目:$ cargo new hello-rust $ cd hello-rust
生成的 Rust 代码将如下所示:
fn main() {
println!("Hello, world!");
}
源代码将位于src/main.rs。
-
添加对 ARMv7 的支持:
$ rustup target add armv7-unknown-linux-gnueabi -
构建您的软件:
$ cargo build --target=armv7-unknown-linux-gnueabi -
将二进制文件复制到您的设备中并测试其是否工作:
$ cargo build --target=armv7-unknown-linux-gnueabi -
生成的二进制文件将位于
target/armv7-unknown-linux-gnueabi/hello-rust文件夹中。 -
现在将您的二进制文件复制到设备中并测试它是否工作。
重要提示
有关 Rust 交叉编译的更多选项,请查看doc.rust-lang.org/nightly/rustc/platform-support.html和rust-lang.github.io/rustup/cross-compilation.html。有关 Mac 和 AArch64(64 位 ARMv8)工具链的更多信息,请查看aarch64-unknown-linux-gnu,它位于github.com/messense/homebrew-macos-cross-toolchains仓库中。
使 Python 能够在 ARM 上运行
首先,需要在系统上安装 Python。可以通过几种方式进行安装。
在 Linux 上安装 Python
安装 Python,请执行以下步骤:
-
更新您的软件仓库:
$ sudo apt-get update -
安装 Python 3:
$ sudo apt-get install -y python3
在 Mac 上安装 Python
使用 Homebrew 在 Mac 上安装 Python,请执行以下步骤:
-
在 brew 的可用版本列表中检查您所需的 Python 版本:
$ brew search python -
假设您选择了 Python 3.8,您需要执行以下命令来安装它:
$ brew install python@3.8 -
测试您的安装:
$ python3 --version
使用 Python 从 x86_64 交叉编译到 ARM
Python 是目前非常重要且流行的编程语言,它通常用于 AI 和机器学习应用。Python 是一种解释型语言,需要运行时环境(如 Java)来运行代码。在这种情况下,你必须安装 Python 作为运行时环境。它在运行代码时面临与 Java 类似的挑战,但也有其他挑战。有时,你需要从头开始编译库才能使用它。当前标准的 Python 库支持 ARM 架构,但问题出在当你需要使用非标准库时。
作为基本示例,让我们通过执行以下步骤在不同平台上运行 Python 代码:
-
创建一个名为
example.py的基本文件:def main(): print("hello world") if __name__ == "__main__": main() -
将
example.py复制到你的 ARM 设备。 -
在你的 ARM 设备上通过运行以下命令安装 Python 3:
$ sudo apt-get install -y python3 -
运行你的代码:
$ python3 example.py
使 Java 适配 ARM 平台
当谈到在 ARM 设备上运行 Java 时,它略有不同。Java 使用的是一种混合编译器,换句话说,是一种两阶段编译器。这意味着它会生成一种中间代码,称为字节码,并由Java 虚拟机(JVM)解释执行。字节码是跨平台的代码,并且遵循 Java 的理念——一次编译,处处运行,意味着你可以在任何平台上编译,且无需修改即可在任何其他平台上运行。那么,让我们看看如何进行跨平台编译,以便让一个基本的 Java 程序在 ARMv7 和 ARMv8 64 位设备上运行。
在 Linux 上安装 Java JDK
在 Linux 上安装 Java,请执行以下命令:
-
更新 Ubuntu 的当前软件库:
$ sudo apt-get update -
安装官方的 JDK 8:
$ sudo apt-get install openjdk-8-jre -
测试
javac是否可以运行:$ javac
在 Mac 上安装 Java JDK
如果你的 Mac 上没有安装 Java,请按照以下步骤操作:
-
(可选)从以下链接下载 Java JDK,并选择你需要的架构,如 Linux、Mac 或 Windows:
www.oracle.com/java/technologies/javase-downloads.html。 -
(可选)下载并运行安装程序。
要测试 Java 是否存在或者是否正确安装,请执行以下命令:
$ java -version
-
通过执行以下命令测试编译器是否已安装:
$ javac -v
从 x86_64 到 ARM 的 Java 跨平台编译
Java 是一种生成名为字节码的中间代码的语言,字节码可以在 JVM 上运行。假设你有一个名为Example.java的基本代码文件:
class Example {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
要执行你的代码,请按照以下步骤操作:
-
要进行编译,请使用以下命令:
$ javac Example.java
这将生成一个中间代码文件Example.class,该文件可以由 JVM 执行。我们将在下一步进行操作。
-
要运行字节码,请执行以下命令:
$ java Example -
现在,将
Example.class复制到另一台设备,并使用合适的 JVM 通过java命令运行它。
总结
本章解释了关于边缘计算的所有基本概念,以及它与其他概念(如雾计算、MEC 和云计算)之间的关系。还解释了如何使用 Docker、containerd 和 Kubernetes 等容器和编排器来帮助你根据自己的用例构建边缘计算系统。最后,本章还介绍了如何使用 Go、Python、Rust 和 Java 等语言的交叉编译技术,在 ARM 处理器的边缘设备上运行和编译你的软件。
问题
下面是一些问题,用来检验你新的知识:
-
边缘和边缘计算之间有什么区别?
-
你可以使用什么基础设施配置来构建边缘计算系统?
-
容器和编排器如何帮助你构建边缘计算系统?
-
什么是交叉编译,如何使用它在 ARM 设备上运行你的软件?
深入阅读
以下是一些额外的资源,您可以查看以了解更多有关边缘计算的信息:
-
近、远或微小:在云原生世界中定义和管理边缘计算,Keith Basil:
vmblog.com/archive/2021/04/27/near-far-or-tiny-defining-and-managing-edge-computing-in-a-cloud-native-world.aspx -
什么是边缘计算:网络边缘解析,Eric Hamilton:Cloudwards(2018):
www.cloudwards.net/what-is-edge-computing -
建筑师的物联网与边缘计算 – 第二版,Perry Lea,Packt Publishing(2020)
-
一种用于雾计算基础智能电网的安全数据聚合协议:
www.researchgate.net/publication/325638338_A_secure_data_aggregation_protocol_for_fog_computing_based_smart_grids.ng -
HarmonyCloud 推动边缘计算实施:
www.cncf.io/blog/2021/08/31/harmonycloud-promotes-edge-computing-implementation -
Kubernetes – 弥合 5G 和智能边缘计算之间的鸿沟:
www.cncf.io/blog/2021/03/01/kubernetes-bridging-the-gap-between-5g-and-intelligent-edge-computing -
CNCF YouTube 视频列表:2021 年 Kubernetes 在边缘日:
www.youtube.com/watch?v=W1v2Gb6URsk&list=PLj6h78yzYM2PuR1pP14DBLW7aku1Ia520 -
使用 Rust 在 Mac 上进行交叉编译:
github.com/messense/homebrew-macos-cross-toolchains -
使用 Python 进行交叉编译:
crossenv.readthedocs.io/en/latest/quickstart.html -
下载和安装 OpenJDK 的说明:
openjdk.java.net/install
第二章:K3s 的安装和配置
本章快速深入探讨了 K3s。我们将从理解 K3s 及其架构开始,然后学习如何为 K3s 准备 ARM 设备。接下来,您将学习如何从单节点集群到多节点集群进行基本安装,并使用 MySQL 进行后端配置。此外,本章还涵盖了如何安装 Ingress 控制器,使用 Helm Charts 和 Helm 将您的服务暴露在由 NGINX 创建的负载均衡器上。最后,我们将看看如何卸载 K3s 并排除集群故障。在章节末尾,您将找到额外的资源,以实现 K3s 的其他自定义。
在本章中,我们将涵盖以下主要内容:
-
介绍 K3s 及其架构
-
准备您的边缘环境以运行 K3s
-
创建 K3s 单节点和多节点集群
-
使用外部 MySQL 存储 K3s
-
安装 Helm 以在 Kubernetes 中安装软件包
-
更改默认的 Ingress 控制器
-
从主节点或代理节点卸载 K3s
-
排除 K3s 集群故障
技术要求
对于本章,您将需要以下选项之一:
-
建议最低配置为 4 GB RAM 的 Raspberry Pi 4 Model B
-
创建一个 AWS 账号以创建 Graviton2 实例
-
任何安装了 Linux 的 x86_64 VM 实例
-
用于本地 K3s 集群的互联网连接和 DHCP 支持
有了这些要求,我们将安装 K3s 并开始尝试这个 Kubernetes 发行版。所以,让我们开始吧。
介绍 K3s 及其架构
K3s 是由 Rancher Labs 创建的轻量级 Kubernetes 发行版。它包含所有必要的组件在一个小型二进制文件内。Rancher 删除了这个 Kubernetes 发行版运行集群所需的所有不必要的组件,并添加了其他有用的功能,以在边缘运行 K3s,例如将 MySQL 支持作为 etcd 的替代品,优化的 Ingress 控制器,适用于单节点集群的存储等。让我们查看 图 2.1,以了解 K3s 的设计和打包方式:

图 2.1 – K3s 集群组件
在前面的图示中,你可以看到 K3s 有两个组件:服务器和代理。每个组件必须安装在一个节点上。节点是一个裸金属机器或虚拟机,作为主节点或代理节点工作。主节点管理并配置 Kubernetes 对象,如部署、服务和 Ingress 控制器,在代理节点内部运行。代理节点负责处理使用这些对象的信息。每个节点使用图示Figure 2.1中展示的不同组件,这些组件通过一个单一的二进制文件提供,包含运行主节点和代理节点所需的所有必要组件。在进程层面,主节点运行 K3s 服务器,代理节点运行 K3s 代理。对于每个组件,你会找到一个隧道代理,用于将主节点与代理节点(即工作节点)互联。
默认情况下,K3s 的代理节点和主节点运行Flannel 作为默认的容器网络接口(CNI)插件。CNI 是容器网络的规范,CNI 插件是用于管理容器网络连接的接口。它还安装了containerd作为容器引擎,用于创建 Pods。服务器和代理节点有一个共同点,即每个组件都由一个大约 100MB 的单一二进制文件组成,包含运行每个节点所需的所有最小组件。然而,在需要时,你可以添加 K3s 中去除的其他组件,这些组件在原生 Kubernetes 集群中是包含的。
就每个节点的角色而言,主节点被称为控制平面,即管理所有 Kubernetes 集群配置、网络等的节点。相比之下,代理节点被称为数据平面,它上面运行着所有服务、网络流量和处理任务。
准备你的边缘环境以运行 K3s
在安装 K3s 之前,你需要按照以下步骤为你的 ARM 设备配置 K3s 主节点或代理节点。让我们开始吧。
可以使用的硬件
首先,你必须准备好你的设备。关于如何准备设备,有几种选择。第一种是购买一台树莓派设备,开始实验并创建一个低成本的边缘系统。在购买设备时,你需要考虑以下硬件规格和组件:
-
至少拥有 4GB 内存的树莓派 4 Model B 作为 ARM 设备。
-
推荐使用 5V、3A 的电源。
-
一根用于互联网连接的以太网线。
-
一条 Micro HDMI 转 HDMI 的线缆。
-
推荐使用一张 MicroSD 卡:SanDisk Extreme MicroSDHC UHS-1 A1 V30 32GB,或者类似的卡片。
-
一张 MicroSD 卡读卡器。
这个配置将为你带来最大的性价比。你可能会想 为什么选择这个配置? 好吧,让我简要解释一下。与之前的版本相比,Raspberry Pi 4 Model B 在处理速度上有了很多改进。谈到兼容性,Raspberry Pi 配备的 ARMv7 处理器支持多种编程语言和程序。它还支持 ARM64 或 AArch64 处理器的操作系统,这些处理器用于具有 ARMv8 处理器的设备。Raspberry Pi B 型号支持这些处理器架构。然而,对于更多面向生产的设备,你可能会想选择一款 ARM 64 位设备,比如 UDOO X86 II ULTRA,它配备了 64 位处理器。
关于电源,你需要一个 5V、3A 的设备,以防止 Raspberry Pi 运行变慢。你可以使用 5V/2.4A 的电源,但 5V/3A 的电源对 Raspberry Pi 4 Model B 更合适。如果你预算允许,建议选择带有 8GB 内存的 4 Model B。
最后,针对 MicroSD 卡,选择一张高速卡。当你运行软件时,这样的卡会有更好的性能。SanDisk 有一款不错的 MicroSD 卡;只需查看读写速度,并选择一张至少有 32 GB 存储空间的 MicroSD 卡。如果可能的话,不要使用 Wi-Fi;这也是选择使用以太网电缆的原因,以便保证稳定的连接。
适用于 ARM 设备的 Linux 发行版
根据你的使用案例,你可以选择多个 GNU/Linux 发行版或操作系统:
-
Raspbian:这是第一个可以在 Raspberry Pi 设备上使用的发行版,专为 Raspberry Pi 设备优化。它可靠且易于使用。
-
Ubuntu:这个发行版可以在 Raspberry Pi 设备或其他 ARM 64 位设备上使用,包括 x86_64 设备。Ubuntu 的一个优势是它可以在所有主要的云服务提供商中找到,比如 AWS、Azure 和 GCP。
-
Alpine:这是一个小型发行版,具有最小的软件集,旨在成为一个轻量级的发行版。它可以作为你的下一个项目,用来根据项目需求定制你自己的发行版。
-
k3OS:这是一个小型发行版,专门设计用于仅在边缘设备上运行 K3s,但它非常灵活。
还有其他发行版,但你可以使用这些作为边缘项目的快速启动。
在你的 MicroSD 卡中安装 Ubuntu
现在是时候安装你的操作系统了。要在你的 MicroSD 卡中安装 Linux 发行版,首先,你必须下载适合你系统的 Raspberry Pi Imager。在这里,我们使用的是 Mac 版本。你可以在 www.raspberrypi.org/software 下载。
要开始在 Raspberry Pi 设备中安装操作系统,请执行以下步骤:
- 安装前面链接中的二进制文件并打开它;你应该看到类似这样的界面:

图 2.2 – Raspberry Pi Imager 菜单
- 点击 选择操作系统 按钮,选择适用于 ARM64 的 Ubuntu Server 20.04 64-bit 操作系统,可以通过导航到 其他通用操作系统 | Ubuntu 菜单找到:

图 2.3 – 树莓派发行版选择
- 接下来,插入你的 MicroSD 卡(你必须购买适配器来读取 MicroSD 卡)。在点击 选择存储 按钮后,你的设备会显示出来:

图 2.4 – 存储选择
- 然后,点击 写入 按钮:

图 2.5 – 将发行版安装到存储设备的最后一步
- 接受写入设备的选项。Raspberry Pi Imager 会要求你输入用户名和密码,以继续将数据写入 MicroSD 卡:

图 2.6 – 确认写入 MicroSD 卡
- 等待写入过程完成:

图 2.7 – 将操作系统写入 MicroSD 卡
- 等待验证过程完成:

图 2.8 – 验证操作系统是否正确写入
- 拔出你的 MicroSD 卡:

图 2.9 – 写入过程完成时显示的对话框
现在你的 MicroSD 卡已经包含了全新的 Ubuntu 安装。接下来的部分,我们将使用这个全新安装的系统安装 K3s。
在安装 K3s 主节点或工作节点之前配置 Ubuntu
现在,你的设备已经准备好首次运行。执行以下步骤,将其配置并安装为单节点集群:
-
打开你的设备。
-
当 Ubuntu 提示你输入用户名和密码时,输入用户名和密码为
ubuntu;这是首次登录的默认密码。 -
现在,Ubuntu 会要求你更改默认密码。我们使用
k3s123-作为密码。请记住,在实际生产环境中,你必须使用更强的密码。 -
现在,我们来配置网络。默认情况下,Ubuntu 使用
init云服务配置网络。我们通过以下命令和内容创建99-disable-network-config.cfg文件来停用此功能:$ sudo nano /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
以下是文件的内容:
network: {config: disabled}
-
如果你执行
ifconfig命令,你会看到设备是eth0。然而,它也可能被命名为es3或类似名称。我们需要使用以下命令修改50-cloud-init文件:$ sudo nano /etc/netplan/50-cloud-init.yaml -
接下来,修改文件内容,应该类似如下所示:
network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no addresses: - 192.168.0.11/24 gateway4: 192.168.0.1 nameservers: addresses: [8.8.8.8, 1.1.1.1]
注意
请记住,您应该根据需要修改此文件,按照当前网络或互联网连接更改地址、网关和 DNS 服务器。对于这个本地设置,我们使用支持 DHCP 的互联网连接。
-
现在应用配置,您可以重启设备来检查操作系统启动时是否设置了您的 IP 地址。为此,执行以下命令:
$ sudo netplan apply -
现在,通过以下命令和内容编辑
/boot/firmware/cmdline.txt文件来配置启动的内核参数:$ sudo nano /boot/firmware/cmdline.txt -
将以下内容添加到行末,以启用在 K3s 集群中使用
containerd创建容器:cgroup_memory=1 cgroup_enable=memory
注意
如果您使用的是 Raspbian,此文件位于/boot/cmdline.txt。
-
使用
nano编辑/etc/hostname文件,并为其指定唯一名称,例如,主节点可以是master,工作节点可以是worker-1、worker-2,依此类推:$ sudo nano /etc/hostname
这是文件的内容:
master
-
编辑
/etc/hosts文件,添加主机名。至少应该有如下所示的一行:$ sudo nano /etc/hosts
例如,文件内容可能如下所示:
127.0.0.1 localhost master
-
现在重启您的设备:
$ sudo reboot
此配置是为您的设备准备配置 K3s 主节点或代理节点所必需的。在下一节中,您将学习如何在您的设备上安装 K3s。
创建 K3s 单节点和多节点集群
在本节中,您将学习如何在您的 Ubuntu 操作系统上为 ARM 设备配置 K3s 的主节点和代理节点。为了更好地理解我们正在做的事情,让我们更详细地看看图 2.10:

图 2.10 – K3s 集群配置
上图展示了您可以以以下配置安装 K3s 集群:
-
单节点集群:在此配置中,您只有一个节点,它同时承担主节点和代理/工作节点的角色。您可以将这种类型的集群用于小型应用程序。但不适合重负载工作,因为它可能会减慢所有组件的速度。请记住,这个节点同时作为主节点和代理节点工作。
-
多节点集群:在此配置中,您有一个主节点,它控制代理/工作节点;此配置对于高可用性和重处理任务非常有用。
通过这些简短的描述,您可以想象出创建 K3s 集群所需的配置。在下一节中,您将学习如何创建一个单节点集群。
使用 Ubuntu 操作系统创建单节点 K3s 集群
要开始安装 K3s,您应该使用 Ubuntu 作为 K3s 的主要发行版。您可能会问,为什么是 Ubuntu?因为 Ubuntu 有很多预构建的功能,可以节省准备设备时的时间。此外,它支持 32 位和 64 位的 ARM 设备。我推荐使用这个发行版,原因是它的兼容性和支持的软件。那么,让我们开始创建这个单节点的 K3s 集群吧。
要安装 K3s(用于主节点或单节点集群),您必须执行以下步骤:
-
打开您的设备并登录。
-
一旦登录,执行以下命令在您的终端中执行 K3s 的基本安装:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik" sh -s -
注意
此命令安装 K3s,默认情况下不包含traefik作为 Ingress 控制器,并允许您在不使用sudo的情况下执行kubectl命令。您可以添加特定的标志以使用 K3s 的特定版本;请参阅官方文档以了解有关此参数的更多信息。您可以在本章末尾找到链接。
-
(可选)如果您希望在 AWS Graviton 2 实例或其他云提供商上安装 K3s,其中公共 IP 未关联到 OS 中的网络接口,则必须使用以下命令设置外部 IP 参数为实例的公共 IP:
$ PUBLIC_IP=YOUR_PUBLIC_IP|YOUR_PRIVATE_IP $ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik --tls-san "$PUBLIC_IP" --node-external-ip "$PUBLIC_IP"" sh -s - -
(可选)如果您想执行简单的测试,请执行以下命令以使用 K3s 的
LoadBalancer功能公开部署:$ kubectl run nginx --image=nginx --restart=Never $ kubectl expose pod/nginx --port=8001 --target-port=80 --type=LoadBalancer
接下来,通过 K3s 节点的公共或私有 IP 地址访问部署的nginx服务的端口8001;您可以通过执行以下命令测试访问:
$ curl http://YOUR_PUBLIC_OR_PRIVATE_IP:8001
或者,如果您有私有 IP,请运行以下命令:
$ curl http://YOU_PRIVATE_IP:8001
注意
此节点将同时兼作主节点和代理节点。
现在我们已经安装了单节点集群。让我们在下一节中继续添加更多节点到您的新集群。
向您的 K3s 集群添加更多节点以进行多节点配置
那么,如果您想向单节点集群添加更多节点呢?要向集群添加更多节点,首先,您必须按照在您的 MicroSD 卡中安装 Ubuntu部分的步骤为每个新节点安装 Ubuntu。然后,您可以继续以下步骤:
-
登录到您的主节点:
$ ssh ubuntu@MASTER_PUBLIC_OR_PRIVATE_IP -
使用以下命令从主节点提取加入集群的令牌:
$ sudo cat /var/lib/rancher/k3s/server/node-token -
从主节点注销。现在您有了加入额外节点的令牌。
对于每个要加入集群的工作节点,请执行以下步骤(这是更简单的方法)。
-
登录要添加到集群的工作节点:
$ ssh ubuntu@WORKER_PUBLIC_OR_PRIVATE_IP -
使用主节点生成的令牌设置环境变量:
$ export TOKEN=YOUR_MASTER_TOKEN -
使用以下命令注册您的节点:
$ curl -sfL https://get.k3s.io | sh -s - agent --server https://MASTER_PUBLIC_OR_PRIVATE_IP:6443 --token ${TOKEN} --with-node-id
注意
如果所有节点的主机名相同,请添加--with-node-id选项,K3s 将在主机名末尾添加随机 ID,以确保集群内的节点名称唯一。
-
退出工作节点:
$ exit -
登录主节点:
$ ssh ubuntu@MASTER_PUBLIC_OR_PRIVATE_IP -
使用以下命令检查新节点是否正在运行:
$ kubectl get nodes
注意
您需要等待几分钟,直到节点转换为Ready状态。
-
(可选)如果您使用的是与 Ubuntu 不同的 GNU/Linux 发行版,以下步骤将更适合像 Alpine Linux 这样的小型发行版。登录您要添加到集群的工作节点:
$ ssh ubuntu@WORKER_PUBLIC_OR_PRIVATE_IP -
使用以下命令在工作节点内下载 K3s 的二进制文件:
$ curl -sfL https://github.com/k3s-io/k3s/releases/download/v1.21.1%2Bk3s1/k3s-arm64 > k3s > k3s | chmod +x k3s;sudo mv k3s /sbin
注意
请访问 github.com/k3s-io/k3s/releases 下载二进制文件。选择任何方法将此二进制文件放入您的工作节点中。目标是将 K3s 二进制文件下载到工作节点中。请注意,在之前的命令中,选择了版本 v1.21.2+k3s1。因此,请根据您的需要修改 URL 以匹配所需的版本。
-
设置一个包含主节点生成的令牌的环境变量:
$ export TOKEN=YOUR_MASTER_TOKEN $ sudo k3s agent --server https://myserver:6443 --token ${TOKEN} --with-node-id & -
从工作节点退出:
$ exit -
登录到您的主节点:
$ ssh ubuntu@MASTER_IP
如果您想设置节点的角色,请执行以下步骤。
-
(可选)使用以下命令设置新工作节点的角色:
$ kubectl label nodes node_name kubernetes.io/role=worker -
从主节点退出:
$ exit
现在您拥有一个多节点的 K3s 集群,准备好使用了。在下一部分中,您将学习如何使用 kubectl 命令管理您的集群。
提取 K3s kubeconfig 以访问您的集群
现在,使用 kubectl 命令配置从计算机访问您的 K3s 集群。要配置从外部连接到新 K3s 集群,请执行以下步骤:
-
通过运行以下命令安装
kubectl命令(适用于 Mac 安装):$ curl -LO https://dl.k8s.io/release/v1.22.0/bin/darwin/amd64/kubectl $ chmod +x ./kubectl $ sudo mv ./kubectl /usr/local/bin/kubectl $ sudo chown root: /usr/local/bin/kubectl
或者,如果您使用的是 Linux,请运行以下命令:
$ curl -LO "https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl"
$ sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
-
从主节点,将
/etc/rancher/k3s/k3s.yaml文件中的内容复制到本地的~/.kube/配置文件中 -
获取服务器值的以下部分:
server: https://127.0.0.1:6443
并将其更改为以下内容:
server: https://MASTER_IP:6443
-
使用以下命令更改此文件的权限:
$ chmod 0400 ~/.kube/config -
接下来,使用以下命令测试您是否可以访问集群:
$ kubectl get nodes
此命令返回集群节点及其状态的列表。
注意
在将 Rancher kubeconfig 文件复制到您的计算机之前,请记得安装 kubectl 命令行工具。请记住,k3s.yaml 文件的内容必须存储在 ~/.kube/config 中,并且需要设置为 0400 权限。要了解如何安装 kubectl 命令,请访问 kubernetes.io/docs/tasks/tools/install-kubectl-macos。
现在您已经准备好执行更多高级配置来创建新的 K3s 集群。让我们继续下一部分,了解更多内容。
高级配置
现在是时候探索可以用来配置边缘 K3s 集群的更多高级配置了。
使用外部 MySQL 存储 K3s
K3s 支持 MySQL 和 SQLite,而不是 etcd,作为 K3s 集群信息的数据存储。您可以在其他节点、云实例,或像 AWS Aurora 或 Google Cloud SQL 这样的云托管服务中安装 MySQL。例如,让我们尝试在使用 DigitalOcean 的云实例上进行操作。但是,您可以在任何您选择的云中执行此操作。现在,按照以下步骤开始:
-
登录到您的云实例:
$ ssh root@IP_DATASTORE -
使用以下命令安装 Docker:
$ apt-get update $ apt-get install docker.io -y $ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=k3s123- \ -e MYSQL_DATABASE="k8s" -e MYSQL_USER="k3sadm" \ -e MYSQL_PASSWORD="k3s456-" \ -p 3306:3306 \ -v /opt/mysql:/var/lib/mysql \ mysql:5.7 -
使用以下命令登出:
$ exit -
在您的主节点上执行以下操作:
$ curl -sfL https://get.k3s.io | K3S_DATASTORE_ENDPOINT="mysql://k3sadm:k3s456-@tcp(YOUR_CLOUD_INSTANCE_IP:3306)/k8s" INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik" sh -s -
注意
这将使用你云实例中的 MySQL 安装。你必须将 YOUR_CLOUD_INSTANCE_IP 替换为你云实例的 IP 地址。
-
使用以下命令从主节点提取令牌以加入集群:
$ sudo cat /var/lib/rancher/k3s/server/node-token -
从主节点注销:
$ exit
对每个工作节点,执行下一步操作。
-
安装代理以注册并准备工作节点:
curl -sfL https://get.k3s.io | K3S_TOKEN=MASTER_TOKEN sh -s - agent --server https://MASTER_IP:6443
注意
你可以执行 kubectl get nodes 来检查你的工作节点是否已经添加并处于 Ready 状态。
现在,你可以使用外部数据存储来使用集群,而不是 etcd 或 SQLite。在这种情况下,我们使用一个混合解决方案,利用本地实例和公共实例来通过 MySQL 存储 K3s 配置。记住,你也可以使用 MariaDB 或你最喜欢的云服务提供商的其他 MySQL 托管服务。你可以将多个配置为主节点的节点添加到集群中,以提高集群中 Kubernetes API 等核心组件的可用性。
安装 Helm 以在 Kubernetes 中安装软件包
Helm 是 Kubernetes 的包管理器。使用 Helm,你可以通过一个称为 Helm Charts 的包定义将软件安装到 Kubernetes 集群中。你可以使用公共 Helm Chart 仓库或自己的仓库来安装包。要在 Linux 或 Mac 上安装 Helm,执行以下步骤:
-
在 Linux 上安装 Helm,运行以下命令:
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 $ chmod 700 get_helm.sh $ ./get_helm.sh -
在 Mac 上安装 Helm,运行以下命令:
$ brew install helm -
要开始安装 Helm Charts,你需要通过在 Linux 或 Mac 上运行以下命令来添加一个 chart 仓库:
$ helm repo add bitnami https://charts.bitnami.com/bitnami
现在,让我们来看一下如何更改默认的 Ingress 控制器。
更改默认的 Ingress 控制器
在开始本节之前,我们先定义一下什么是 Ingress,然后再定义 Ingress 控制器。根据官方 Kubernetes 网站,Ingress 是一个 Kubernetes 组件,用于暴露匹配集群内服务的 HTTP 或 HTTPS 路由。Service 是 Kubernetes 用来暴露应用程序为网络服务的抽象方式。而 Ingress 控制器是一个负责执行 Ingress 的组件,包括可能配置边缘路由器或代理的负载均衡器。有许多基于不同边缘路由器或代理的 Ingress 控制器实现,如 Traefik、Envoy、Nginx 等。默认情况下,K3s 包含 Traefik 1.0 版本,提供了最基本的功能,可以在不消耗大量资源的情况下路由你的服务。
如果你想使用不同的 Ingress 控制器而不是默认选项(Traefik),请使用以下命令安装主节点:
-
使用以下参数安装主节点:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik" sh -s - -
然后,使用以下命令创建命名空间并安装
nginxIngress 控制器:$ kubectl create ns nginx-ingress -
添加 Helm Charts 仓库:
$ helm repo add ingress-nginx \ https://kubernetes.github.io/ingress-nginx -
更新你的仓库以获取最新版本:
$ helm repo update -
安装 Ingress 控制器:
$ helm install nginx-ingress \ ingress-nginx/ingress-nginx \ -n nginx-ingress
(可选) 如果你想测试nginx-ingress控制器是否工作,请按照接下来的步骤进行操作。
-
使用
nginx镜像创建一个部署:$ kubectl create deployment nginx --image=nginx -
使用
ClusterIP暴露部署:$ kubectl expose deployment/nginx --port=8001 \ --target-port=80 --type=ClusterIP --name=nginx-srv -
使用以下命令创建
my-ingress.yaml文件:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /mypath pathType: Prefix backend: service: name: nginx-srv port: number: 8001 -
使用以下命令创建 Ingress:
$ kubectl create -f my-ingress.yaml -
现在使用以下命令测试它是否有效:
$ curl http://LB_IP/my-path
注意
你必须将LB_IP的值替换为由 NGINX Ingress 控制器安装创建的LoadBalancer服务的 IP 地址。在这种情况下,它与主节点的 IP 地址相同。
-
要检查
nginx-ingress暴露的 IP,请执行以下命令:$ kubectl get services -n nginx-ingress
注意
请考虑到 K3s 在使用 Kubernetes 服务时有其独特的行为。要了解更多信息,请参阅 https://rancher.com/docs/k3s/latest/en/networking。
现在你已经理解了如何安装 Ingress 控制器以及如何使用它,是时候学习如何在必要时从节点卸载 K3s 了。
从主节点或代理节点卸载 K3s
如果你想卸载主节点或代理节点上的 K3s,必须执行 K3s 安装提供的卸载脚本。那么,我们开始吧。
从代理节点卸载 K3s
要从代理节点(即工作节点)卸载 K3s,请执行以下步骤:
-
登录到你的代理节点:
$ ssh ubuntu@AGENT_NODE_IP -
卸载代理守护进程并删除此节点上创建的所有容器:
$ k3s-agent-uninstall.sh $ sudo rm -R /etc/rancher $ sudo rm -R /var/lib/rancher -
从代理节点注销:
$ exit
从主节点卸载 K3s
要从主节点卸载 K3s,请执行以下步骤:
-
登录到你的代理节点:
$ ssh ubuntu@MASTER_NODE_IP -
卸载代理守护进程并删除此节点上创建的所有容器:
$ k3s-uninstall.sh $ sudo rm -R /etc/rancher $ sudo rm -R /var/lib/rancher -
从代理节点注销:
$ exit
所以,你已经学会了如何卸载 K3s,这在你想在设备上尝试新配置时会很有用。接下来,让我们继续学习如何在下一节中排查集群问题。
排查 K3s 集群问题
本节包括一些基本的故障排除命令,你可以使用它们来测试你的集群。有不同的故障排除选项:
-
如果你想查看节点的状态并检查 Kubernetes 是否运行,请执行以下命令:
$ kubectl get nodes -
创建一个 Pod 来检查你的集群是否能调度 Pod:
$ kubectl run nginx --image=nginx --restart=Never -
创建一个 Service 来暴露之前创建的 Pod,并测试
LoadBalancer服务是否有效:$ kubectl expose pod/nginx --port=8001 \ --target-port=80 \ --type=LoadBalancer -
如果你想检查服务和端口是否有效并暴露你的服务,可以执行以下命令,这些服务可以是
LoadBalancer或NodePort:$ kubectl get services -
如果你想实时查看系统日志,请执行以下命令:
$ journalctl -f -
执行以下命令检查
k3s服务是否在你的主节点上运行。此命令必须在代理节点内执行:$ systemctl status k3s -
执行以下命令检查
k3s-agent服务是否在你的代理/工作节点上运行。此命令必须在代理节点内执行:$ systemctl status k3s-agent
注意
关于 K3s 的不同选项和配置的更多详细信息,请访问 rancher.com/docs/k3s/latest/en。
概述
本章涵盖了使用 K3s 的边缘分发版创建和自定义 Kubernetes 集群的第一步。还介绍了高级配置,例如如何为 K3s 配置外部数据存储,以帮助你为边缘 K3s 集群配置更强大且高可用的解决方案。在本章末尾,我们还介绍了一些高级配置,如如何安装不同的 Ingress 控制器、使用 Helm Chart 操作符以及集群的基本故障排除命令。有了这些知识,我们现在可以跳到下一章,了解 k3OS 在快速且轻松地安装 K3s 方面的优势。
问题
这里有一些问题可以验证你在本章中学到的内容:
-
我可以使用什么软件来准备我的 ARM 设备以安装 K3s?
-
如何在 ARM 设备上使用 K3s 安装一个基本的多节点集群?
-
如何安装不同的 Ingress 控制器?
-
如何使用 Helm 在我的集群中安装软件包?
-
如何对我的集群进行故障排除?
进一步阅读
你可以参考以下资料,获取更多本章所涉及主题的信息:
-
Raspberry Imager 软件:
www.raspberrypi.org/software -
K3s 官方文档:
rancher.com/docs/k3s/latest/en -
K3s 安装选项:
rancher.com/docs/k3s/latest/en/installation/install-options -
Helm 官网:
helm.sh -
K3s Helm Chart 操作符:
rancher.com/docs/k3s/latest/en/helm -
查找你想安装的软件的 Helm Charts Hub:
artifacthub.io -
官方 Kubernetes 文档:
kubernetes.io/docs
第三章:K3s 高级配置与管理
本章介绍了 K3s 集群的更多高级配置。默认情况下,K3s 包含一个名为 KlipperLB 的负载均衡器,但它有一些限制。例如,在创建服务时,你不必重复端口号,这会影响你使用常规负载均衡器和 NodePort 服务的方式。它适用于简单的部署。如果你需要替代 Klipper 的其他负载均衡器,我们将介绍如何安装MetalLB,一个裸金属负载均衡器。接着,我们将讲解如何使用高级存储配置,支持 Longhorn 存储卷的读写访问模式,替代 K3s 提供的默认本地存储类。之后,我们将进行一些常见的集群管理,包括升级 K3s、备份和恢复集群。
在本章中,我们将介绍以下主要内容:
-
使用 MetalLB 的裸金属负载均衡器
-
设置 Longhorn 作为存储
-
升级你的集群
-
备份和恢复你的 K3s 配置
技术要求
本章节,你需要以下内容:
-
配备 4 GB RAM(最小推荐)的 Raspberry Pi 4 型 B
-
一台运行 Ubuntu 20.04 LTS 的云服务器或虚拟机
-
你的设备或客户端已安装 Helm v3
这样,我们就准备好学习 K3s 的高级配置了。让我们开始吧。
使用 MetalLB 的裸金属负载均衡器
在本节中,你将探索作为裸金属负载均衡器的 MetalLB,它能够为你提供强大的功能,将服务暴露到边缘。
Kubernetes 中的负载均衡器服务
在开始使用 KlipperLB 之前,有必要了解 Kubernetes 中负载均衡器的背景。Kubernetes 使用服务来通信或访问你的应用程序。ClusterIP 服务会创建一个 DNS 记录,使得该服务可以在集群内部访问。NodePort 服务在每个节点的 IP 上以静态端口暴露服务,端口范围是 30000–32767。最后,Kubernetes 支持一个负载均衡器服务,通过云服务商的负载均衡器将服务暴露到外部。在 K3s 中,默认使用 KlipperLB。
KlipperLB 和 MetalLB 作为裸金属负载均衡器
边缘设备和边缘计算资源有限,因此通常可以找到只有一个节点的集群。通常,Kubernetes 的负载均衡器服务依赖于特定云服务商的实现。它还在第四层(传输层)工作,传输传输控制协议(TCP)和用户数据报协议(UDP),并且这个负载均衡器也与 NodePort 服务连接。因此,在边缘设备的情况下,K3s 实现了 KlipperLB。
KlipperLB 在低资源设备或环境中作为 k3s 的负载均衡器运行非常顺利。但当你拥有多节点集群时,KlipperLB 可能无法提供最佳的服务可用性特性。这时,MetalLB 就发挥作用了。KlipperLB 和 MetalLB 都提供裸金属负载均衡器服务,适用于 Kubernetes。在这种情况下,你可以在 K3s 上使用这些实现。
KlipperLB 和 MetalLB——优缺点分析
现在,让我们从实现、依赖关系和使用场景的角度,谈一谈这些裸金属负载均衡器的优缺点。首先,我们从 KlipperLB 开始。
KlipperLB 的优点如下:
-
相当轻量级
-
对于单节点集群来说,使用简单且功能足够的工具。
KlipperLB 的缺点如下:
-
依赖于 hostPort 或可用端口来暴露一个 Pod。
-
如果端口不可用,负载均衡器服务将保持挂起状态。
说到 MetalLB,它使用第 2 层(数据链路层),该层定义了数据的格式。因此,MetalLB 使用一个节点来进行负载均衡,具有自己的优缺点。下表总结了这些信息:

总体而言,如果你只有单节点集群并且希望避免复杂的安装过程,选择 KlipperLB。对于多节点集群或可以重用端口并且需要更强大负载均衡服务的安装,使用 MetalLB。
安装 MetalLB
你需要一个带有 --disable servicelb 选项的 K3s 安装;如果你已有旧的安装版本,需要重新安装 K3s。按照以下步骤安装带有此选项的 K3s:
-
使用以下命令登录到你的 虚拟机(VM)或设备:
$ ssh ubuntu@YOUR_VM_IP -
使用以下命令安装 K3s。此步骤适用于简单的 ARM 设备,用于基本安装,不包含 KlipperLB:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik --disable servicelb" sh -s -
(可选)使用以下命令安装 K3s。首先,设置 PUBLIC_IP 环境变量为你设备或虚拟机的 IP 地址:
$ PUBLIC_IP=YOUR_PUBLIC_IP|YOUR_PRIVATE_IP
接下来,使用以下命令在具有公共 IP 的节点上安装 K3s:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik --tls-san "$PUBLIC_IP" --node-external-ip "$PUBLIC_IP" --disable servicelb" sh -s -
-
使用官方清单创建一个 MetalLB 命名空间(
metallb-system),执行以下命令:$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml -
在运行安装 MetalLB 的命令之前,你需要在
metallb-system命名空间中创建一个名为metallb-config的 ConfigMap。我们将这个文件命名为config.yaml,并填写以下内容:apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.0.240-192.168.0.250 -
现在,执行以下命令创建 ConfigMap:
$ kubectl apply -f config.yaml -
使用官方清单安装 MetalLB,执行以下命令:
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
现在,通过 YAML 文件安装了 MetalLB,让我们继续使用 Helm 代替 YAML 文件进行安装。
重要提示
如果你想删除此或其他安装,请使用 delete 选项代替 apply,使用相同的命令——例如,kubectl delete -f YOUR_YAML_FILE。
如果你想使用 Helm v3 安装 MetalLB,请按照以下步骤操作:
-
使用以下命令添加 MetalLB 的 Helm Chart 仓库:
$ helm repo add metallb https://metallb.github.io/metallb -
使用 Helm 安装 MetalLB,执行以下命令:
$ helm install metallb -n metallb-system metallb/metallb -
如果你想通过
values.yaml文件安装 MetalLB,请执行以下命令:$ helm install metallb -n metallb-system metallb/metallb -f values.yaml -
你需要创建一个
values.yaml文件,内容如下示例:configInline address-pools: - name: default protocol: layer2 addresses: - 192.168.0.240-192.168.0.250 -
现在,你需要根据安装情况使用
kubectl创建 ConfigMap,并将命名空间更改为metallb-system,名称更改为metallb-config。然后,应用YAML文件:$ kubectl apply -f config.yaml
重要提示
addresses 字段对应 MetalLB 用来分配给服务的 IP 地址范围,每次你在 Kubernetes 中创建 LoadBalancer 服务时都会使用这个范围。
- 现在,MetalLB 已经安装完成并可以使用。
现在,你已经有了一个可以使用的 MetalLB 全新安装。接下来,你需要在下一部分学习如何排查 MetalLB 的故障。
MetalLB 故障排查
有时,我们需要排查安装过程中的问题。如果你在安装过程中遇到问题,以下是一些可以用来排查 MetalLB 新安装问题的命令。以下是你可以使用的步骤和命令:
-
登录到你的虚拟机或设备:
$ ssh ubuntu@NODE_IP -
创建一个 Pod 来检查你的集群是否可以调度 Pod:
$ kubectl run nginx --image=nginx --restart=Never -
创建一个服务以暴露之前创建的 Pod,并测试
LoadBalancer服务是否正常工作:$ kubectl expose pod/nginx --port=8001 \ --target-port=80 \ --type=LoadBalancer -
如果你想检查服务和端口是否正常工作以暴露你的服务,可以执行以下命令,这些服务可以是
LoadBalancer或NodePort:$ kubectl get services -
现在,执行对分配给 NGINX 服务的外部 IP 进行访问检查,并执行以下命令,确认 MetalLB 已经成功暴露了你的服务:
$ curl http://EXTERNAL_IP:8001
如果你想查看 MetalLB 的日志以便排查错误,请查看 metallb-system 命名空间下的下一个 Pod:
-
控制器
-
演讲者
现在,你知道如何进行基本的 MetalLB 故障排查了。接下来让我们进入下一部分,学习如何使用 Longhorn 配置更高级的存储。
配置 Longhorn 存储
在持久化信息方面,你会看到两种类型的容器,无状态和有状态容器。无状态或临时容器不会持久化容器内部生成的信息。有状态容器即使在删除后也能持久化信息。K3s 默认提供一种通过存储类型(在 Kubernetes 中称为 storage class)持久化数据的方式,叫做 local-path。这种存储是一种基本且非常轻量的实现,专为边缘设备设计。Kubernetes 中常用的功能是拥有持久卷声明,允许你的 Pod 从不同节点进行数据读写。这是一个持久卷配置,访问模式键设置为 ReadWriteMany(RWX)。这个功能通常在生产场景中使用,非常重要,因为它使你能够在不同服务间共享信息。Longhorn 提供了这个功能,并且以非常轻量的方式进行了优化,专为边缘设备设计。接下来让我们了解什么是 Longhorn 以及如何安装它。
为什么使用 Longhorn?
Longhorn 旨在成为一个分布式和超融合的存储堆栈。超融合存储意味着虚拟化软件抽象并汇聚存储资源。Longhorn 不会占用大量资源,这使得您可以将其用于边缘设备的高级存储。它甚至可以简化快照、备份甚至灾难恢复的工作流。因此,如果您正在寻找轻量级和高级的边缘存储解决方案,Longhorn 可以满足您的需求。还有其他选项,比如 Rook,但 Longhorn 是一款简单的软件,可以在不牺牲资源消耗的情况下为您提供额外的存储能力。那么,接下来让我们学习如何安装它,并在下一节中为 Pod 创建一个简单的持久卷声明。
安装 Longhorn 时启用 ReadWriteMany 模式
要安装 Longhorn,请按照以下步骤操作:
-
登录到您的虚拟机或设备:
$ ssh ubuntu@NODE_IP -
如果您想安装 ReadWriteMany PVC 模式,您需要在集群中每个安装了 Ubuntu 的虚拟机上安装
nfs-common。为此,请执行以下命令:$ sudo apt install -y nfs-common -
应用官方的 Longhorn 清单,如下所示:
$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.3.1/deploy/longhorn.yaml
重要提示
Longhorn 将安装在 longhorn-system 命名空间中。
-
创建一个
pvc.yaml文件:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: longhorn-volv-pvc spec: accessModes: - ReadWriteMany storageClassName: longhorn resources: requests: storage: 2Gi -
应用
pvc.yaml文件:$ kubectl create -f pvc.yaml
重要提示
您可以使用不同的 PVC 模式,如 ReadWriteOnce 或 ReadOnlyMany。默认情况下,存储类至少支持 ReadWriteOnce。因此,ReadWriteMany 是一个使用 NFS 的特殊功能,并包含在 Longhorn 中。
现在,是时候使用 Longhorn 存储类创建一个 Pod 了。请按照以下步骤操作:
-
创建
pod.yaml文件,以便使用之前创建的 PVC 创建一个 Pod:echo " apiVersion: v1 kind: Pod metadata: name: volume-test namespace: default spec: containers: - name: volume-test image: nginx:stable-alpine imagePullPolicy: IfNotPresent volumeMounts: - name: volv mountPath: /data ports: - containerPort: 80 volumes: - name: volv persistentVolumeClaim: claimName: longhorn-volv-pvc" > pod.yaml -
应用
pod.yaml文件以创建 Pod:$ kubectl create -f pod.yaml
现在,您已经安装并运行了 Longhorn。那么,接下来让我们学习如何在下一节中使用 Longhorn UI。
使用 Longhorn UI
如果您想访问 Longhorn UI,您需要检查 longhorn-system 上创建的服务并执行端口转发;如果您安装了 MetalLB,您可以创建一个 LoadBalancer 服务来暴露 Longhorn UI。
要通过端口转发访问 Longhorn,请执行以下步骤:
-
在本地运行下一个端口转发命令,以便在浏览器中访问 UI:
$ kubectl port-forward svc/longhorn-frontend -n longhorn-system 8080:80 -
现在,打开浏览器,访问
http://localhost:8080,您将看到以下仪表板:

图 3.1 – Longhorn UI
使用此仪表板,您可以通过 UI 管理您的持久卷声明(PVCs);更多参考资料,请访问以下链接:longhorn.io/docs/1.3.1/deploy/accessing-the-ui。
现在您已经知道如何安装和使用 Longhorn,让我们继续进行一些基础故障排除。
Longhorn 故障排除
以之前的示例为参考,要排查使用 Longhorn 创建 PVC 的问题,可以使用以下命令:
-
使用以下命令检查 Longhorn Pod 是否成功运行:
$ kubectl get pods –n longhonr-system -
检查是否已创建 PV:
$ kubectl get pv -
检查是否已创建 PVC:
$ kubectl get pvc -
检查是否已创建使用新 Longhorn 存储类的
pod.yaml中的 Pod:$ kubectl get pods
使用这些命令,你可以找到在 Pod 或部署使用带有 Longhorn 存储类的 PVC 时出现的错误。
重要提示
如果出现问题,前面四个命令会返回错误。有关更多信息,你可以查看 kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage 或 kubernetes.io/docs/concepts/storage/persistent-volumes/#class。
现在,我们准备学习有关升级集群的另一个高级主题。让我们继续进入下一节。
升级你的集群
有时候,你想保持 K3s 的版本和功能更新。接下来的章节将解释如何执行这些升级过程。
使用 K3s Bash 脚本进行升级
要在你的节点上进行升级,你需要按照以下步骤操作:
-
首先,你需要使用以下命令停止设备上的 K3s:
$ /usr/local/bin/k3s-killall.sh -
现在,你需要选择要升级到的版本。通常,有三个选项——选择最新的或最稳定的通道,或选择特定的版本。下一个命令将把你的集群更新到最新的稳定版本:
$ curl -sfL https://get.k3s.io | sh - -
现在,如果你想更新到最新版本(尽管它不太稳定),你可以执行以下命令:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=latest sh - -
最后的选项是选择特定的版本。为此,你需要执行以下命令:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=vX.Y.Z-rc1 sh -
重要提示
你可以访问 update.k3s.io/v1-release/channels 查看最新、稳定或特定版本的 K3s,或者访问 K3s 的官方网站 k3s.io 中的 GitHub 部分。
现在你知道如何使用 K3s 脚本升级集群了。让我们继续在下一节学习如何手动升级。
手动升级 K3s
如果你想手动升级 K3s 版本,可以根据官方 K3s 网站文档,按照以下步骤操作:
-
从发布页面下载你需要的 K3s 二进制文件。要做到这一点,请查看此链接:
github.com/k3s-io/k3s/releases。 -
将下载的二进制文件复制到
/usr/local/bin文件夹。 -
停止旧的 K3s 二进制文件。为此,你可以执行以下命令:
$ /usr/local/bin/k3s-killall.sh -
删除旧的二进制文件。
-
使用以下命令启动新的 K3s 二进制文件:
$ sudo systemctl restart k3s
现在,你知道如何手动操作 K3s,但有一点你需要知道,那就是在应用下一个更改之前必须重启服务。这部分内容将在下一节中讲解。
重启 K3s。
当你进行软件或硬件升级,或需要重启以修复错误时,你可以使用 systemd 和 OpenRC 重启 K3s 服务。
使用 systemd 重启 K3s,请按照以下步骤操作:
-
要在主节点中重启 K3s 服务,请执行以下命令:
$ sudo systemctl restart k3s -
要在代理节点中重启 K3s agent 服务,请执行以下命令:
$ sudo systemctl restart k3s-agent
使用 OpenRC 重启 K3s,请按照以下步骤操作:
-
要在主节点中重启 K3s 服务,请执行以下命令:
$ sudo service k3s restart -
要在代理节点中重启 K3s-agent 服务,请执行以下命令:
$ sudo service k3s-agent restart
现在你已经知道了升级 K3s 集群所需的所有步骤,是时候继续学习其他高级主题——备份和恢复。让我们继续下一个部分来了解这些内容。
备份和恢复 K3s 配置
在生产环境中,Kubernetes 对象的备份和恢复是需要考虑的事项。本节将解释如何在默认存储(SQLite)中执行这些任务,如何在 K3s 上安装和管理etcd,以及如果你使用 K3s 的 SQL 后端时的一些基本资源。
从 SQLite 备份
如果你使用的是默认存储 SQLite,请按照以下步骤操作:
-
登录到主节点:
$ ssh ubuntu@NODE_IP -
使用以下命令停止 K3s:
$ k3s-killall.sh -
切换到
/var/lib/rancher/k3s/目录服务器:$ sudo cd /var/lib/rancher/k3s -
复制
k3s文件夹中的文件夹服务器:$ sudo cp -R /var/lib/rancher/k3s folder_of_destination -
如果需要,可以在另一台设备上下载此文件夹。
从 SQL 数据库 K3s 后端备份和恢复
如果你使用外部存储——例如 MySQL——你需要使用工具或命令来备份你的数据库。
备份 MySQL
对于 MySQL,你可以执行以下步骤来备份 K3s 配置:
-
获取你的数据库凭证,以便使用
mysqldump命令。 -
运行以下命令来备份数据库,这里的数据库名为
k3s,使用YOUR_USER用户,YOUR_PASSWORD密码,并输出为output.sql文件,数据库所在的主机是YOUR_HOST:$ mysqldump -h YOUR_HOST -u YOUR_USER -pYOUR_PASSWORD k3s > output.sql
重要提示
你可以修改 YOUR_HOST、YOUR_USER 和 YOUR_PASSWORD 的值,使用其他数据库名代替 k3s,甚至可以修改输出文件名来自定义备份命令。如果你连接的主机与数据库安装在同一台主机上,–h 选项是可选的。默认情况下,它会连接到 localhost。你可以查看这个链接获取更多 MySQL 备份的示例:www.tecmint.com/mysql-backup-and-restore-commands-for-database-administration。
现在备份已经准备好可以使用。在下一节中,你将使用备份来恢复数据库。
恢复 MySQL
现在是恢复数据库的时刻了。请按照以下步骤进行恢复:
-
获取你的数据库凭证,以便使用
mysql命令操作数据库。 -
运行以下命令来恢复你的数据库备份。我们使用的是
k3s数据库。根据你用于存储k3s集群数据的数据库,修改YOUR_HOST和YOUR_PASSWORD参数。最后,使用output.sql文件加载你的备份并恢复数据库:$ mysql -h YOUR_HOST -u YOUR_USER -pYOUR_PASSWORD k3s < output.sql
重要提示
你可以修改之前命令中的值,通过output.sql文件执行恢复操作。
备份和恢复其他数据存储
如果你使用的是其他 K3s 后端,如 PostgreSQL 或etcd,你可以查看每个数据库的官方文档。
对于 PostgreSQL,请查看以下链接:www.postgresql.org/docs/8.3/backup-dump.html。
对于etcd,请查看以下链接:etcd.io。
现在你已经了解如何恢复 K3s 集群的 MySQL 数据存储,接下来让我们学习如何使用etcd作为你的数据存储。
嵌入式 etcd 管理
etcd是存储所有 Kubernetes 对象的默认存储类型。默认情况下,etcd已从 K3s 中移除,但你可以安装它。K3s 自定义了etcd在集群中的工作方式,包括一些常规 Kubernetes 集群中没有的自定义功能。所以,让我们开始在 K3s 中安装etcd吧。
安装 etcd 后端
如果你想安装它,请按照以下步骤操作:
-
要安装 K3s 并使用
etcd后端,你需要执行以下命令将etcd包含在 K3s 安装中。这个命令需要在主节点上执行:$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --cluster-init" sh -s - -
设置你的
TOKEN变量,使用YOUR_TOKEN主令牌,将节点加入集群:$ TOKEN=YOUR_TOKEN -
现在,如果你需要多集群配置,请执行以下命令:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --server https://MASTER_IP:6443" K3S_TOKEN=$TOKEN sh -s -
现在你已经了解如何为 K3s 安装etcd功能,接下来让我们学习如何为你的 Kubernetes 对象配置创建和恢复etcd快照。
创建和恢复 etcd 快照
K3s 包含了一个实验性功能,用于备份和恢复etcd。在这一部分,你将学习如何执行etcd快照和恢复操作。默认情况下,这个后端启用了快照功能。这些快照存储在/var/lib/rancher/k3s/server/db/snapshots目录下。要创建备份,请手动执行以下步骤:
-
手动创建备份:
$ k3s etcd-snapshot --name=mysnapshot
这将在snapshots文件夹内生成一个文件。
-
要从备份中恢复你的
etcd,请执行以下命令:$ k3s server \ --cluster-reset \ --cluster-reset-restore-path=<PATH-TO-SNAPSHOT> -
你可以使用以下选项来自动化快照生成:
--etcd-snapshot-schedule-cron
想了解更多配置参考内容,请访问此链接:rancher.com/docs/k3s/latest/en/backup-restore/#options。
您甚至可以参考官方的 etcd 文档:github.com/etcd-io/website/blob/main/content/en/docs/v3.5/op-guide/recovery.md。
这就是如何管理您的etcd快照。现在,让我们回顾一下本章的内容。
总结
本章介绍了使用 Ubuntu 和 K3s 配置 Kubernetes 边缘集群的常见高级配置。这些常见配置之一是使用 MetalLB 安装裸金属负载均衡器。我们还讨论了与默认 K3s 负载均衡器 KlipperLB 相比,这种方式的优缺点,并介绍了在何种情况下使用各自的场景。接着,我们介绍了 Longhorn 的高级存储配置,它是一种非常轻量级的存储解决方案,易于安装和配置,支持 ReadWriteMany 存储访问模式。最后,我们了解了如何升级集群,以及在使用不同数据存储(如 SQL 或etcd)时进行备份和恢复。掌握了这些知识,您已经准备好创建一个可投入生产的集群。在下一章中,我们将学习如何使用 k3OS 创建集群,使用 K3s ISO 镜像和覆盖安装。
问题
以下是一些问题,以验证您的新知识:
-
什么时候选择 KlipperLB 或 MetalLB 作为裸金属负载均衡解决方案?
-
我如何排查 MetalLB 安装问题?
-
如何安装 Longhorn,以获得更强大的数据存储解决方案?
-
如何排查我的 Longhorn 安装问题?
-
我可以使用哪些其他数据存储解决方案来替代 Longhorn?
-
升级我的 K3s 集群的步骤是什么?
-
如果我使用 SQL 后端或
etcd,备份或恢复我的 Kubernetes 对象配置的步骤是什么?
进一步阅读
您可以参考以下资料,获取有关本章涉及主题的更多信息:
-
什么是 OSI 模型?:
www.cloudflare.com/en-gb/learning/ddos/glossary/open-systems-interconnection-model-osi/ -
MetalLB 官方文档:
metallb.universe.tf -
MetalLB 层 2 模式:
metallb.universe.tf/concepts/layer2 -
Kubernetes 101:为什么你需要使用 MetalLB:
www.youtube.com/watch?v=Ytc24Y0YrXE -
MetalLB ConfigMap 配置:
metallb.universe.tf/configuration -
Longhorn 官方页面:
longhorn.io -
安装带 RWM 支持的 OpenEBS:
docs.openebs.io/docs/next/rwm.html -
安装带 RWM 支持的 Rook:
rook.io/docs/nfs/v1.7 -
升级 K3s 集群:
rancher.com/docs/k3s/latest/en/upgrades -
备份和恢复 K3s 集群:
rancher.com/docs/k3s/latest/en/backup-restore -
安装选项:
rancher.com/docs/k3s/latest/en/installation/install-options/#registration-options-for-the-k3s-server
第四章:k3OS 安装与配置
在边缘计算的背景下,企业希望简化使用边缘设备的任务。谈到 K3s 的成功采用,一些行业需要一个现成的操作系统,能够包括一个边缘 Kubernetes 发行版。这正是 k3OS 满足行业需求的地方。k3OS 的设计目的是加速在边缘设备上安装 K3s。
k3OS 打包了安装 K3s 所需的所有软件。本章将探讨如何使用 k3OS 的 ISO 镜像在 x86_64 设备上安装 K3s,并介绍如何使用配置文件。你将通过不同的配置示例学习如何自定义你的 K3s 安装,从单节点到多节点。最后,你将学习如何在 ARM 设备上使用覆盖安装安装 k3OS,详细配置包括网络设置、主机名等,特别是在为边缘设备安装 k3OS 时,尤其是使用 ARM 设备时。
在本章中,我们将讨论以下主要内容:
-
使用 ISO 镜像为 x86_64 设备安装 k3OS
-
使用配置文件进行的 k3OS 高级安装
-
多节点 ARM 覆盖安装
技术要求
本章需要以下虚拟机或设备之一:
-
一台 4GB RAM(建议最小) 的 Raspberry Pi 4B 模型
-
一台运行 Ubuntu 20.04 LTS 的云服务器或虚拟机
-
使用 VirtualBox 或其他虚拟化软件创建的虚拟机
此外,你需要一个云存储服务,如 Amazon S3、Google Cloud Storage 或类似服务,用于上传 k3OS 的配置文件。
这样,我们已经准备好学习如何在你首选的边缘设备上安装 k3OS。那就开始吧。
更多详细信息和代码片段,请查看 GitHub 上的资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch4
k3OS – Kubernetes 操作系统
k3OS 是一个 Linux 发行版,包含安装 Kubernetes 所需的最小内核、驱动程序和二进制文件。它提供了一个轻量级的 Kubernetes 发行版,称为 K3s。k3OS 可以作为一种快速操作系统解决方案来安装轻量级 Kubernetes 集群;这意味着 k3OS 镜像可以用于安装主节点和代理节点。k3OS 使用 K3s 作为主要软件,在边缘设备上创建单节点或多节点集群。因此,使用 k3OS,你可以快速运行边缘集群,而不需要花费太多时间。
k3OS 可以通过以下方式安装:
-
ISO 镜像
-
覆盖安装
还有其他安装 K3s 集群的方法,但这是一个更简便的快速入门方式。目前,k3OS 仍在开发中,但已经支持许多 ARM 设备的功能。所以,让我们继续学习下一部分,了解如何使用 k3OS 安装轻量级 Kubernetes 集群。
使用 ISO 镜像为 x86_64 设备安装 k3OS
我们将使用 VirtualBox 创建的虚拟机和适用于 x86_64 架构的 ISO 镜像,在 Mac 上安装 k3OS。请按照以下步骤操作:
- 从此链接下载
k3OS x86_64 v0.20.7 ISO镜像:
github.com/rancher/k3os/releases
- 打开你的 VirtualBox。主窗口将会出现,如下图所示:

图 4.1 – VirtualBox 主窗口
- 输入虚拟机名称以便识别——在此示例中为
k3OS——并选择虚拟机类型为 Linux,版本选择 其他 Linux (64 位)。然后,点击 继续。

图 4.2 – 名称和操作系统对话框
- 选择至少 2048 MB 的 RAM 内存用于 Live CD 和交互式安装。然后,点击 继续。

图 4.3 – 内存大小对话框
- 现在让我们创建硬盘;选择 现在创建虚拟硬盘 并点击 创建。

图 4.4 – 硬盘对话框
- 选择 VDI 作为 硬盘文件类型,然后点击 继续。

图 4.5 – 硬盘文件类型对话框
- 选择 动态分配 作为物理存储,因为这会动态分配硬盘空间。然后,点击 继续。

图 4.6 – 硬盘类型对话框
- 选择至少 4.00 GB 的磁盘空间用于安装,具体大小可根据个人需求选择。然后,点击 创建。

图 4.7 – 磁盘空间对话框
- VirtualBox 将把你带到 主窗口;点击 设置 图标。

图 4.8 – 主窗口和设置图标
- 点击 存储,然后选择 空 驱动器图标。接着,点击 光驱 下拉框旁边的小 CD 图标 (
)。

图 4.9 – 存储对话框
- 选择 选择一个磁盘文件... 菜单。

图 4.10 – 光驱选项
- 现在找到你的 k3OS ISO 镜像 并点击 打开:

图 4.11 – 打开 ISO 对话框
- 点击 Live CD/DVD 复选框,然后点击 确定。

图 4.12 – 启用 Live CD/DVD 功能
- 现在点击网络,在适配器 1下,将附加到的下拉框更改为桥接适配器,使你的虚拟机能够获取到局域网内的 IP 地址。这样可以远程访问虚拟机。然后,点击确定。

图 4.13 – 网络配置对话框
-
VirtualBox 将再次返回主窗口,现在点击启动图标。
-
现在,你应该等待 k3OS 发行版的加载过程。加载完成后,你将看到一个登录提示符。使用
rancher作为用户名和密码。在较新的版本中,你无需输入任何密码。

图 4.14 – k3OS live CD 初次登录
- 然后,执行
sudo k3os install来启动交互式脚本,在虚拟机中安装 k3OS:

图 4.15 – k3OS 启动交互式安装
-
使用以下值完成脚本安装:
-
使用键
1安装到磁盘。 -
使用
N配置 cloud-init 系统。 -
授权 GitHub 用户通过 SSH 连接,使用
N。 -
为 rancher 用户设置并确认新密码,使用
YOUR_PASSWORD。 -
使用
N配置 Wi-Fi。 -
使用
1作为服务器或代理运行。 -
令牌或集群密钥 – 留空后按Enter。
-
你的磁盘将被使用
y格式化。
-
对话框将类似于以下屏幕截图:

图 4.16 – CLI 对话框安装
- 之后,虚拟机会重启;当开始加载时,关闭虚拟机窗口,图 4.17 中的对话框将会出现。选择关闭计算机,然后点击确定。

图 4.17 – 关闭虚拟机
- 一旦虚拟机停止,进入设置,然后在主板标签下的系统部分选择启动顺序,并选择硬盘选项,以防止光盘再次未能加载,并防止在虚拟机启动时启动安装脚本。

图 4.18 – 重新配置为从磁盘启动
-
现在,VirtualBox 将返回主窗口,点击启动以重新启动虚拟机,加载你新安装的 k3OS。
-
现在,你的 k3OS 安装正在运行。你需要输入
rancher用户名和新密码进行登录。

图 4.19 – k3OS 从磁盘首次启动
现在,你已经完成了 k3OS 的全新安装,准备好使用了。
在下一个部分,你将学习如何使用相同的创建 k3OS 虚拟机的步骤进行更高级的配置,接下来我们进入下一个部分。
使用配置文件的 k3OS 高级安装
现在,我们准备学习如何使用配置文件来安装 k3OS;为此,你需要一个公共的 GitHub 仓库,在那里你可以推送这些文件。在创建 config.yaml 文件以安装 k3OS 之前,让我们了解一下这个文件的不同部分。你需要为主节点和代理节点各创建一个文件。
k3OS 配置文件部分
让我们先从配置主机的部分开始。这些部分如下:
-
主机名部分:
hostname: master
这里是设置主机名的位置——在这个例子中是 master。
-
SSH 部分:
ssh_authorized_keys: - ssh-rsa YOUR_KEY
这一部分用于设置可以通过 SSH 访问节点的默认 SSH 公钥。你需要将 YOUR_KEY 替换为你自己的公钥。
-
写入文件部分:
write_files: - path: /var/lib/connman/default.config content: |- [service_eth0] Type=ethernet IPv4=192.168.0.11/255.255.255.0/192.168.0.1 IPv6=off Nameservers=8.8.8.8
这一部分定义了你的网络配置。在这个例子中,我们已将 Raspberry Pi 连接到带有互联网的以太网连接。此节点的 IP 设置为 192.168.0.11,子网掩码为 255.255.255.0,网关为 192.168.0.1,且此连接将使用 8.8.8.8 名称服务器。记住,你可以根据你的互联网服务提供商自定义这些值。
现在,让我们探索配置 K3s 的各个部分。它们如下:
-
k3OS DNS 名称服务器部分:
k3os: dns_nameservers: - 8.8.8.8 - 1.1.1.1
这一部分设置了集群中 pod 的默认 DNS;在这个例子中,设置为 8.8.8.8 和 1.1.1.1。
-
NTP 服务器部分:
ntp_servers: - 0.us.pool.ntp.org - 1.us.pool.ntp.org
这一部分设置了 NTP 服务器来同步时间;在这个例子中,设置为 0.us.pool.ntp.org 和 1.us.pool.ntp.org。
-
密码部分:
password: rancher
这里是设置密码以访问使用 k3OS 的主机的位置。
-
标签部分:
labels: region: america-central-1
这一部分配置了节点的标签;这等同于使用 kubectl labels 命令。
我们已经解释了主节点和代理节点的共同部分。现在,让我们继续介绍那些主节点和代理节点不同的部分。
主节点和代理节点的配置
这一部分描述了配置主节点或代理节点时必须使用的特定部分。让我们从主节点开始:
-
这是主节点的特定配置示例:
k3os: token: myclustersecret password: rancher k3s_args: - server - "--write-kubeconfig-mode" - "644"
这个 k3OS 配置将主节点设置为包括将用于添加新代理节点的令牌、节点的密码以及发送给服务器二进制文件的参数。在此例中,仅修改安装以执行 kubectl 而不使用 sudo。
-
这是代理节点的特定配置示例:
k3os: server_url: https://192.168.0.11:6443 token: myclustersecret password: rancher
这个 k3OS 配置将代理节点设置为连接到在 server_url 中定义的主节点,使用定义的令牌将其加入集群,并使用定义的密码访问节点。
重要提示
若要查看更多选项或参数,可以查看下一个链接:rancher.com/docs/k3s。
我们已经解释了创建集群所需的所有基本部分。接下来的部分,我们将创建基本配置文件来创建一个多节点集群。让我们开始吧。
使用配置文件创建多节点集群
现在是时候使用配置文件配置您的集群了,让我们将所有部分拼凑在一起;主节点的配置文件将如下所示:
hostname: master
ssh_authorized_keys:
- ssh-rsa YOUR_PUBLIC_SSH_KEY
write_files:
- path: /var/lib/connman/default.config
content: |-
[service_eth0]
Type=ethernet
IPv4=192.168.0.11/255.255.255.0/192.168.0.1
IPv6=off
Nameservers=8.8.8.8
k3os:
dns_nameservers:
- 8.8.8.8
ntp_servers:
- 0.us.pool.ntp.org
password: rancher
token: myclustersecret
k3s_args:
- server
- "--write-kubeconfig-mode"
- "644
代理节点的配置文件将如下所示:
hostname: node01
ssh_authorized_keys:
- ssh-rsa YOUR_PUBLIC_SSH_KEY
write_files:
- path: /var/lib/connman/default.config
content: |-
[service_eth0]
Type=ethernet
IPv4=192.168.0.12/255.255.255.0/192.168.0.1
IPv6=off
Nameservers=8.8.8.8
k3os:
server_url: https://192.168.0.11:6443
token: myclustersecret
dns_nameservers:
- 8.8.8.8
ntp_servers:
- 0.us.pool.ntp.org
password: rancher
现在我们已经有了主节点和代理节点的基本配置文件,是时候使用这些文件来部署您的多节点集群,具体过程将在以下部分中讨论。
使用配置文件创建多节点 K3s 集群
在开始创建多节点集群之前,您必须具备以下要求:
-
1 个虚拟机用于主节点。
-
1 个虚拟机用于代理节点。
-
您的
master_example.yaml主配置文件已上传到云存储服务,如 Amazon S3、Google Cloud Storage 或类似服务。例如,如果您使用 Google 存储,您的文件 URL 将类似于:storage.googleapis.com/k3s/master_example.yaml。 -
您的
agent_example.yaml代理配置文件已上传到云存储服务,如 Amazon S3、Google Cloud Storage 或类似服务。例如,如果您使用 Google 存储,您的文件 URL 将类似于:storage.googleapis.com/k3s/agent_example.yaml。
重要提示
主节点和代理节点的虚拟机必须配置为使用 k3OS ISO 镜像启动;在接下来的部分中,我们将解释如何为主节点和代理节点运行安装。
现在,我们准备好使用配置文件创建 K3s 集群。接下来的部分将解释如何使用 k3OS ISO 镜像安装多节点集群。我们开始吧。
使用配置文件创建主节点
创建主节点时,请按以下步骤操作:
-
启动虚拟机(VM),然后登录。
-
使用
rancher用户名和密码登录到虚拟机(VM)。 -
运行以下命令以启动 k3OS 安装:
$ sudo k3os install -
按照以下选项执行安装脚本:
y
记得下次从磁盘启动以加载您的 k3OS 安装,按照使用 ISO 镜像安装 x86_64 设备的 k3OS 安装部分的最后步骤操作。现在,让我们继续进行代理节点的创建。
使用配置文件创建代理节点
现在,按照以下步骤创建代理节点:
-
启动虚拟机(VM),然后登录。
-
使用
rancher用户名和密码登录到虚拟机(VM)。 -
运行以下命令以启动 k3OS 安装:
sudo k3os install -
按照以下选项执行安装脚本:
Choose Operation 1\. Install to disk 2\. Configure server or agent node Select number [1]: 1 Config system with cloud-init file? [y/N] y cloud-init file location (file PATH or http URL): https://storage.googleapis.com/k3s/agent_example.yaml Your disk will be formatted and k3OS will be installed with the above configuration. Continue? [y/N] y
记得像主节点那样从磁盘启动。现在,您的集群已准备就绪,尝试运行kubectl get nodes。要验证节点是否已添加,命令输出应显示节点名称和Ready状态。
您已使用虚拟机安装了包含主节点和代理节点的多节点集群。现在是时候使用 ARM 设备安装多节点集群;在接下来的部分中,我们将探索这种设置。
多节点 ARM 叠加安装
覆盖安装会替换你当前操作系统安装的一些部分或你系统中的一些部分。在这种情况下,当你使用/sbin/init文件时,重启 ARM 设备后,用户空间将被初始化并加载 k3OS。这种安装支持 ARMv7 和 ARM64 设备。一个重要的事项是,你可以通过配置 YAML 文件来自定义此安装,这些文件必须存储在/k3os/system/config.yaml中。
在执行此覆盖安装之前,你需要准备以下内容:
-
一台已安装Ubuntu 20.04 LTS的 ARMv7 或 ARM64 设备,例如一台树莓派(你可以使用树莓派映像工具或balenaEtcher;参考第三章,K3s 高级配置与管理)
-
具有互联网访问权限的网络设备,并且支持动态主机配置协议(DHCP)以自动为你的设备分配 IP 地址
-
一个通过 HDMI 连接到显示器的端口
-
连接的键盘
-
在你的 Macintosh 或 PC 上安装树莓派映像工具
在这种情况下,我们将使用一台配备 8GB RAM 和 64GB Micro SD 卡的树莓派 4B 作为主节点,另外一台配备 4GB RAM 和 32GB Micro SD 卡的树莓派作为代理节点存储。首先,让我们开始为主节点进行覆盖安装。
主节点覆盖安装
按照以下步骤安装主节点覆盖:
- 使用 ARM64 架构安装 Ubuntu 20.04 LTS,方法是导航到操作系统 | 其他通用操作系统 | Ubuntu | Ubuntu 20.04.2 LTS (RPi 3/4/400)。插入你的 Micro SD 卡并在存储中选择它;然后点击写入,如下所示的截图:

图 4.20 – 使用树莓派映像工具安装 Ubuntu 服务器
这将要求你提供凭据以开始安装;当过程完成时,将显示以下屏幕:

图 4.21 – 写入成功对话框
现在,取出你的 MicroSD 卡并将其插入到树莓派的 SD 卡槽中。
-
打开你的树莓派。
-
登录到你的 ARM 设备。Ubuntu 的默认用户名/密码是
ubuntu;系统会要求你设置一个新密码。设置你想要的密码。 -
使用以下命令安装
net-tools软件包,以查看你的 ARM 设备的 IP 地址:$ sudo apt-get update $ sudo apt-get install –y net-tools -
使用以下命令检查你的 IP:
$ sudo ifconfig -
使用 SSH 登录到你的设备,使用
ubuntu用户名和你首次登录 Ubuntu 设备时设置的密码。DEVICE_IP是ifconfig返回的 IP 地址。在你的笔记本电脑上运行此命令进行登录:$ ssh ubuntu@DEVICE_IP -
使用以下命令将
cgroup_memory=1 cgroup_enable=memory选项添加到你的内核参数中,在/boot/firmware/cmdline.txt文件中:$ sed 's/$/cgroup_memory=1 cgroup_enable=memory/' /boot/firmware/cmdline.txt | sudo tee /boot/firmware/cmdline.txt
或者,你也可以使用任何编辑器将这段文本添加到cmdline.txt文件的末尾。
-
运行以下命令安装 k3OS;这将把
tar.gz文件解压到/文件夹中:$ curl -sfL https://github.com/rancher/k3os/releases/download/v0.20.7-k3s1r0/k3os-rootfs-arm.tar.gz | sudo tar zxvf - --strip-components=1 -C /
如果您想配置主节点,请按照以下步骤操作:
-
下载或复制一个配置到您的本地目录;让我们使用先前的主配置,并使用
wget加上 URL:$ wget https://storage.googleapis.com/k3s/master_example.yaml -
使用以下命令将配置复制到
/k3os/system/config:$ sudo cp master_example.yaml /k3os/system/config.yaml
您可以根据需要自定义这些文件;请记住,此示例使用了一个可以用来远程登录到您的节点的 SSH 密钥。
(可选)如果您想配置代理节点,请按照以下步骤操作:
-
下载或复制配置到您的本地目录,让我们使用先前的主配置,使用
wget加上 URL:$ wget https://storage.googleapis.com/k3s/agent_example.yaml -
使用以下命令将配置复制到
/k3os/system/config:$ sudo cp agent_example.yaml /k3os/system/config.yaml
您可以根据需要自定义这些文件;请记住,此示例使用了一个可以用来远程登录到您的节点的 SSH 密钥。
-
使用以下命令为每个节点同步文件系统:
$ sudo sync -
重新启动系统:
$ sudo reboot -f -
登录到您的设备:
$ sudo rancher@DEVICE_IP
请记住,此配置已为您的节点设置了静态 IP。
-
如果您已登录到主节点,您可以运行以下命令检查所有节点是否已被检测到:
$ kubectl get nodes
现在,您的 K3s 集群已安装并准备就绪。您现在知道如何使用覆盖安装配置集群,这比在 K3s 官方网站上找到的默认 K3s 脚本执行更快。
概要
在本章中,我们学习了如何使用 k3OS 安装 K3s,这是一个生产就绪的 Linux 发行版,涵盖了如何准备您的 VM 以便为 x86_64 架构创建集群。然后,我们继续解释了如何使用配置文件执行高级和自定义集群安装,并介绍了如何配置它们以使用 ISO 镜像或覆盖安装创建多节点集群。最后,我们介绍了如何使用覆盖安装创建多节点集群,以减少使用 k3OS 潜力安装 K3s 的手动配置。现在,我们即将在接下来的章节中开始使用案例和真实配置。在下一章中,我们将使用之前章节学到的所有内容创建一个生产就绪的集群。
问题
这里有一些问题可以验证你的新知识:
-
如果您使用 x86 或 x86_64 设备,k3OS 有哪些可用的安装类型?
-
如何使用 ISO 镜像安装 k3OS?
-
如果您使用 ARMv7 或 ARM64 设备,k3OS 有哪些可用的安装类型?
-
如何使用覆盖安装安装 k3OS?
-
如何使用配置文件自定义我的集群安装?
-
如何通过 k3OS 参数部分向主节点或代理节点发送参数?
-
如何使用 k3OS ISO 镜像或覆盖安装创建多节点集群?
-
在哪里可以找到关于 K3s 可用参数的更多信息?
-
k3OS 还有哪些其他类型的安装可用?
深入阅读
你可以参考以下文献以获取更多关于本章内容的信息:
-
官方 k3OS 文档:
github.com/rancher/k3os -
k3OS 的可用版本:
github.com/rancher/k3os/releases -
K3s 安装选项,允许你向配置文件添加自定义参数:
rancher.com/docs/k3s/latest/en/installation/install-options -
k3OS 镜像生成器:
github.com/sgielen/picl-k3OS-image-generator -
在 Raspberry Pi 3B+上安装带 Alpine Linux 的 k3s:
blog.jiayihu.net/install-k3s-with-alpine-linux-on-raspberry-pi-3b -
k3OS 配置文件示例:
www.chriswoolum.dev/k3s-cluster-on-raspberry-pi -
如何从零开始使用 QEMU 启动 ARM aarch64 虚拟机:
futurewei-cloud.github.io/ARM-Datacenter/qemu/how-to-launch-aarch64-vm -
用于 K3s 集群创建的 k3sup 工具:
blog.alexellis.io/test-drive-k3s-on-raspberry-pi
第五章:K3s 家庭实验室用于边缘计算实验
到目前为止,我们已经探讨了创建自己的边缘计算集群的基本主题。前几章介绍了如何配置和安装 K3s 集群。在家中构建小型和大型解决方案涉及到实验。本章中,我们将开始构建一个简单但真实的集群,使用之前章节中获得的知识。我们将这个环境称为 K3s 家庭实验室。创建该集群后,我们将部署一个简单的应用程序。我们将使用这种方法作为快速启动使用 Kubernetes 与集群的方式。在本章的最后部分,我们将使用 Kubernetes 仪表板作为管理 Kubernetes 集群的简单 UI。
在本章中,我们将涵盖以下主要主题:
-
在本地网络上安装多节点 K3s 集群
-
使用
kubectl部署你的第一个应用程序 -
使用 YAML 文件部署简单的 NGINX 服务器
-
为你的应用程序添加持久性
-
部署 Kubernetes 仪表板
技术要求
在本章中,你需要以下硬件来创建你的 K3s 家庭实验室,用于边缘计算应用或实验:
-
两个或更多的 Raspberry Pi 4 B 模型,至少 4GB RAM 和 32GB microSD 卡,Ubuntu 版本 20.04 或更高版本。推荐使用 SanDisk Extreme microSDHC 32 GB UHS-1 A1 V30 或类似的 microSD 卡。
-
以太网电缆,用于连接你的 Raspberry。
-
一个以太网互联网连接,Raspberry 的动态主机配置协议(DHCP)已启用。
-
一个交换机,用来将你的 Raspberry 连接到本地网络。
有了这些硬件,我们就可以开始构建 K3s 家庭实验室了。那么,让我们开始吧。
欲了解更多细节和代码片段,请查看 GitHub 上的这个资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch5
在本地网络上安装多节点 K3s 集群
在开始创建这个家庭实验室之前,让我们了解一下我们将要使用的网络拓扑。以下图示中的每个组件都将在家庭实验室中使用:

图 5.1 – 家庭实验室架构
这是对图中每个组件的简要说明:
-
192.168.0.0/24。 -
交换机:交换机也是一种网络连接设备,用来连接同一网络中的各种设备。
-
路由器:路由器连接跨多个网络的设备。通常,家庭路由器是混合设备,可以让本地计算机访问互联网。它还具有小型交换机功能,允许本地计算机连接,通过无线或以太网端口连接有线设备。
-
公共接口:这是你路由器的接口,具有公共 IP 地址。
-
网关:这是一个 IP 地址,用作你私人网络中的网关。
-
8.8.8.8和1.1.1.1。 -
Master:这是您 K3s 集群的主节点。
-
Agent:这是在 K3s 集群中作为工作节点的代理节点。
-
kubectl命令。
现在—简要解释一下这些组件如何相互作用。所有您的机器将使用192.168.0.0/24网络;在这种情况下,假设您的客户端使用192.168.0.2 IP。通过配置文件或参数安装集群时,您可以为节点选择前述网络中的 IP 范围。在此示例中,主节点使用192.168.0.11 IP,而您的代理节点使用192.168.0.12和192.168.0.13 IP 地址。请记住,您的配置已为节点设置了静态 IP 私有地址,以防止 IP 地址发生变化时出现节点错误。我们假设节点使用从192.168.0.11到192.168.0.13的 IP 地址。我们将使用192.168.0.240到192.168.0.250的 IP 地址范围作为负载均衡器。这只是一个简单的示例,说明如何为您的集群组织 IP。
我们假设您的路由器位于192.168.0.0/24网络中。如前所述,家庭路由器具有某些交换机功能,可以通过配置在路由器内的 DHCP 服务自动分配动态 IP 地址,但这对您的节点并不健康。这是使用静态 IP 地址的主要原因。我们假设使用一些公共 IP 作为示例。我们假设将使用8.8.8.8和1.1.1.1作为 DNS 服务器。
重要提示
所有这些 IP 范围可能会发生变化,具体取决于您的互联网提供商或您使用的路由器设备。我们设置这些值是为了提供如何为您的集群组织网络的示例。
要创建您的家庭实验室,我们需要完成以下任务:
-
在您的 Raspberry 设备上安装 Ubuntu 镜像。
-
配置您的设备以运行 K3s 安装程序。
-
配置 K3s 主节点。
-
配置 K3s 代理节点。
-
安装MetalLB作为负载均衡服务。
-
安装Longhorn作为默认存储类。
-
在外部客户端中配置
kubectl以访问集群。 -
使用
kubectl和 YAML 文件部署您的第一个应用。 -
安装并配置Lens来管理您的集群。
所以,现在让我们快速回顾一下概念,从如何在您的设备上安装 Ubuntu 开始。
在您的 Raspberry 设备上安装 Ubuntu 镜像。
在本节中,我们将安装一个 Ubuntu 镜像到 Raspberry 设备上。您可以跳过本节或参考之前的章节获取更多信息。简要总结,您可以按照以下步骤在 Raspberry 设备上安装 Ubuntu:
-
打开Raspberry Pi Imager。
-
点击CHOOSE OS按钮,选择位于Other general purpose OS | Ubuntu菜单中的 Ubuntu Server 20.04 64 位 ARM64 操作系统。
-
然后,插入您的 microSD 卡(您可能需要购买一个适配器来读取 microSD 卡);当您选择CHOOSE STORAGE按钮时,您的设备将会出现。
-
点击WRITE按钮。
-
接受写入设备的请求;然后,Raspberry Pi Imager 会要求你输入用户名和密码,以继续写入 microSD 卡。
-
等待写入和验证过程完成。
-
弹出你的 microSD 卡。
-
将 microSD 卡插入 Raspberry Pi 并启动它。
-
对于每个将加入集群的 Raspberry Pi 设备,重复这些步骤。
现在,让我们继续配置设备的网络设置和容器支持。
配置你的 Raspberry Pi 以运行 K3s 安装程序
在这一部分,我们将配置网络设置,包括静态 IP 地址、DNS、主机名和 hosts 文件,最后激活对 containerd 所需的 cgroups 支持。现在,按照以下步骤执行最终设置,在为节点安装 K3s 之前;记住,你可以根据自己的网络自定义这些配置:
-
打开设备。
-
当 Ubuntu 要求你输入用户名和密码时,输入用户名和默认密码
ubuntu,这是第一次登录时的默认密码。 -
现在,Ubuntu 会要求你更改默认密码;让我们使用
k3s123-作为密码。 -
现在,配置网络;默认情况下,Ubuntu 使用
cloud-init来配置网络。我们将通过创建99-disable-network-config.cfg文件来禁用它,使用以下命令和内容:$ sudo nano /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
这里是一个内容示例:
network: {config: disabled}
-
如果你执行
ifconfig,你会看到你的设备是eth0,但它可能被命名为es3或类似名称,因此我们需要通过以下命令修改50-cloud-init文件:$ sudo nano /etc/netplan/50-cloud-init.yaml -
然后,修改文件内容,应该类似如下所示:
network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no addresses: - 192.168.0.11/24 gateway4: 192.168.0.1 nameservers: addresses: [8.8.8.8, 1.1.1.1] -
现在,应用配置并重启设备,查看操作系统(OS)启动时是否设置了 IP 地址。为此,执行以下命令:
$ sudo netplan apply -
现在,通过编辑
/boot/firmware/cmdline.txt文件来配置启动时的内核参数,使用以下命令和内容:$ sudo nano /boot/firmware/cmdline.txt
将以下内容添加到行末:
cgroup_memory=1 cgroup_enable=memory
-
使用
nano编辑/etc/hostname文件,将主节点的名称设置为master。将代理节点的主机名设置为node01和node02;使用nano编辑文件:$ sudo nano /etc/hostname
这里是一个内容示例:
master
-
编辑
/etc/hosts文件,添加主机名;至少需要有如下所示的一行:$ sudo nano /etc/hosts
这里是一个内容示例:
127.0.0.1 localhost master
重要提示
你也可以使用 master.local 代替 master,以遵循 互联网工程任务组(IETF)的本地网络命名规范。这也有助于零配置 多播 DNS(mDNS)设置。更多信息,请查看此链接:www.zeroconf.org。
现在,重启你的设备:
$ sudo reboot
该配置是为了准备你的设备以配置 K3s 主节点或代理节点。你也可以按照 IETF 的本地网络设计建议进行操作。在下一部分,你将看到如何为你的主节点安装 K3s。
配置 K3s 主节点
本节介绍如何为 K3s 集群安装主节点;为此,您需要按照以下步骤进行操作:
-
打开您的设备,并使用
ubuntu用户登录。 -
使用
MASTER_IP为192.168.0.11,按照图 5.1中所示运行以下命令,安装您的主节点,适用于 K3s 集群:$ MASTER_IP=<YOUR_PRIVATE_IP> $ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --no-deploy traefik --disable traefik --tls-san "$MASTER_IP" --node-external-ip "$MASTER_IP" --disable servicelb" sh -s -
现在,我们已经安装了主节点。该节点的 IP 地址为192.168.0.11。接下来,让我们在下一节中将代理节点添加到集群中。
配置 K3s 代理节点
本节解释如何通过重复此部分两次来完成初始集群图的配置,以配置两个代理节点。代理节点将使用192.168.0.12和192.168.0.13的 IP 地址。按照以下步骤配置每个代理节点:
-
登录到您的主节点:
$ ssh ubuntu@<MASTER_IP>
我们将提取服务节点令牌以连接代理节点。在这种情况下,主节点的 IP 地址为192.168.0.11。
-
通过运行以下命令提取并复制令牌,将您的代理节点加入集群:
$ sudo cat /var/lib/rancher/k3s/server/node-token -
从您的主节点注销。现在,您已获得令牌,可以将其他节点加入到集群中。
为了让每个代理节点加入集群,按照以下步骤操作(简单方法):
-
登录到您要添加到集群中的代理节点。在这种情况下,
AGENT_IP将是192.168.0.12或192.168.0.13:$ ssh ubuntu@<AGENT_IP> -
设置一个环境变量,其中包含主节点生成的令牌:
$ export TOKEN=<YOUR_MASTER_TOKEN> -
使用以下命令注册您的节点;在这种情况下,
MASTER_IP为192.168.0.11:$ curl -sfL https://get.k3s.io | sh -s - agent --server https://MASTER_IP:6443 --token ${TOKEN}
从您的代理节点退出:
$ exit
现在,我们已经配置了代理节点。让我们安装 MetalLB 以开始为我们的应用程序使用负载均衡器。
安装 MetalLB 作为负载均衡服务
MetalLB 是一个裸金属负载均衡器,可以在使用常规 Kubernetes 集群的负载均衡服务时提供帮助,具有为裸金属设计的网络功能,如 IP 地址分配。因此,让我们通过以下步骤安装 MetalLB 并开始:
-
使用官方清单创建一个名为
metallb-system的 MetalLB 命名空间,执行以下命令:$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml -
在运行安装 MetalLB 的命令之前,您必须在
metallb-system命名空间中创建一个名为metallb-config的ConfigMap资源。我们将这个文件命名为config.yaml,并包含以下内容:apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.0.240-192.168.0.250 -
现在,创建
ConfigMap,执行以下命令:$ kubectl apply -f config.yaml -
使用官方清单安装 MetalLB,执行以下命令:
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
现在,您已经安装了 MetalLB。您可以开始安装使用负载均衡器的服务。这些负载均衡器在许多 Kubernetes 软件中很常见。现在,是时候为我们的存储添加 Longhorn 了。
安装启用 ReadWriteMany 模式的 Longhorn
K3s 包含基本的存储支持。有时,在安装软件时,这些存储可能会导致错误。为了防止这种情况,你需要使用一个与 K3s 默认存储驱动程序不同的存储驱动程序。在这种情况下,你可以使用 Longhorn。使用 Longhorn,你可以安装寻找常规存储驱动程序的 Kubernetes 软件。接下来,让我们按照以下步骤安装 Longhorn:
-
登录到你的 虚拟机 (VM) 或设备:
$ ssh ubuntu@NODE_IP -
如果你想在集群中每个安装了 Ubuntu 的虚拟机上安装
nfs-common,请执行以下命令:$ sudo apt install -y nfs-common -
应用官方的 Longhorn 清单,如下所示:
$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.2/deploy/longhorn.yaml
现在,你已经安装并运行了 Longhorn。接下来,让我们了解如何在个人计算机上配置 kubectl 来管理你的 K3s。
提取 K3s kubeconfig 文件以访问你的集群
现在,是时候配置 kubeconfig 文件,以便通过 kubectl 命令从你的计算机访问 K3s 集群了。要配置外部访问你的 K3s 集群,按照以下步骤操作:
-
根据 Kubernetes 官方文档的说明安装
kubectl(kubernetes.io/docs);在此案例中,我们将使用针对 Macintosh 的安装说明:$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl" $ chmod +x ./kubectl $ sudo mv ./kubectl /usr/local/bin/kubectl $ sudo chown root: /usr/local/bin/kubectl
或者你也可以通过 brew 在 macOS 上安装 kubectl,使用以下命令:
$ brew install kubectl
对于其他自定义安装,例如针对 Apple 新硅处理器、Linux 或 Windows 的 kubectl,请访问 Kubernetes 官方文档:kubernetes.io/docs/tasks/tools/install-kubectl-macos。
-
从主节点,将
/etc/rancher/k3s/k3s.yaml文件中的内容复制到本地的~/.kube/config文件。 -
使用以下命令更改文件的权限:
$ chmod 0400 ~/.kube/config -
将服务器值中的部分
127.0.0.1更改为主节点的MASTER_IP地址;在此情况下,它将是192.168.0.11:server: https://127.0.0.1:6443
这将更改为以下内容:
server: https://MASTER_IP:6443
重要提示
在将 Rancher kubeconfig 文件复制到计算机之前,记得先安装 kubectl。请记住,k3s.yaml 文件的内容必须存储在 ~/.kube/config 中,并且需要设置 0400 权限。有关如何安装 kubectl 命令的信息,请访问 kubernetes.io/docs/tasks/tools/install-kubectl-macos。
现在,我们准备好使用集群了。在下一节中,我们将使用 kubectl 和 YAML 文件,结合 MetalLB 和 Longhorn 部署一个基础应用程序。让我们开始在下一节中使用 kubectl 部署应用程序吧。
使用 kubectl 部署你的第一个应用程序
本节介绍了 Kubernetes 的基础知识。我们首先将使用 kubectl 部署一个应用程序。但在此之前,让我简要介绍一下 Kubernetes 如何与其基本对象一起工作。
基本 Kubernetes 对象
Kubernetes 通过使用容器的对象来为你的应用提供不同的功能。Kubernetes 的目标是编排你的容器。Kubernetes 有两种创建对象的方式。一种是通过命令式命令——在 Kubernetes 中使用kubectl命令。另一种是通过声明式文件,其中定义了对象的状态,Kubernetes 确保对象在其生命周期中保持这个状态。

图 5.2 – Kubernetes 对象
这个图示表示了一些基本对象是如何相互作用来部署和管理应用程序的。接下来,我们来解释这些对象:
-
Pod包含一个或多个容器,你的应用程序就在其中;Pod 中的所有容器共享相同的网络、内存和 CPU。
-
ReplicaSet控制 Pod 的数量保持一致。
-
Deployment是一个高级的ReplicaSet对象,不仅控制 Pod 的数量和版本,还控制 Pod 的变化,提供回滚的方式。
-
Service是一种暴露你的服务的方式。服务有不同的类型。NodePort在所有节点上开放一个随机端口,ClusterIP创建一个 DNS,你可以用它来与 Pod 通信,或者与其他 Pod 或部署进行交互,LoadBalancer则创建一个专用端点,将你的应用发布到外部。
-
Persistent Volume Claim是负责请求持久存储并创建有状态部署的对象。
-
Storage Class是定义如何为应用程序请求存储的对象。
通过这些基本概念,我们来看看如何实际部署,以了解每个组件是如何工作的。在接下来的部分,我们将使用kubectl部署一个简单的 NGINX 服务器。
使用 kubectl 部署一个简单的 NGINX 服务器
在本节中,我们将使用kubectl一步步部署一个 NGINX 服务器。请按照以下步骤操作:
-
创建一个使用
nginx镜像的 Pod:$ kubectl run myserver --image=nginx --restart=Never -
为这个 Pod 创建一个
LoadBalancer类型的服务,用于暴露和访问 NGINX Pod:$ kubectl expose pod/myserver --port=8001 --target-port=80 –type=LoadBalancer -
使用以下命令将 IP 地址分配给你的负载均衡器:
$ IP_SERVICE=$(kubectl get svc mywebserver --output jsonpath='{.status.loadBalancer.ingress[0].ip}') -
使用浏览器或以下命令访问下一个 URL:
$ curl IP_SERVICE:8001
现在,你的 NGINX 服务已启动并运行。接下来,让我们部署一个Redis数据库,你可以在下一节中访问它来存储数据。
使用 Pods 部署 Redis NoSQL 数据库
现在,我们将部署一个 Redis NoSQL 键值数据库,你可以访问它来存储数据。我们选择 Redis 作为一个基础示例,因为它快速且易于使用。接下来,让我们通过以下命令部署 Redis:
-
创建一个使用
redis镜像的 Pod:$ kubectl run myredis --image=redis --restart=Never -
创建一个
ClusterIP服务,你可以使用该服务的名称连接到 Redis:$ kubectl expose pod myredis --port=6379 --type=ClusterIP -
使用以下命令创建一个
ubuntu客户端:$ kubectl run client -it --rm --image=ubuntu:18.04 -- bash -
现在,你已进入客户端,让我们使用以下命令安装 Redis 客户端,以便连接到 Redis pods:
root@client# apt-get update;apt-get install -y redis-tools -
使用以下命令存储值为
1的变量,并从客户端获取该值:root@client# redis-cli -h myredis set a 1 root@client# redis-cli –h myredis get a
最后一条命令返回 a 变量的值,该值为 1。
-
输入
exit然后按 Enter 键退出客户端。由于--rm参数,客户端会被自动删除。 -
现在,让我们通过使用
NodePort来暴露 Redis,作为如何使用节点的 IP 暴露 pod 的示例:$ kubectl expose pod myredis --name=myredis-nodeport --port=6379 --type=NodePort
现在,你可以使用部署 Redis 的主机的 IP 来访问你的 Redis 数据库。
你已经完成了一个简单数据库的安装——在本例中是 Redis。接下来,让我们在下一节中探索部署对象和存储。
使用部署部署和扩展 NGINX 服务器
使用部署的一个优势是,你可以在版本或配置发生变化时管理部署的变更。让我们部署一个简单的 NGINX 服务器,扩展部署,修改镜像,然后执行回滚,来展示部署的强大功能。按照以下步骤部署 NGINX 服务器:
-
使用
nginx镜像创建一个两个副本的部署:$ kubectl create deployment mywebserver --image=nginx --replicas=2 -
创建一个
LoadBalancer服务来暴露你的部署:$ kubectl expose deployment mywebserver --port=8002 --target-port=80 --type=LoadBalancer -
创建
mywebserver的 IP:$ IP_SERVICE=$(kubectl get svc mywebserver --output jsonpath='{.status.loadBalancer.ingress[0].ip}') -
使用
curl访问 web 服务器:$ curl $IP_SERVICE:8002 -
将
mywebserver扩展为0个副本:$ kubectl scale deploy/mywebserver --replicas=0 -
再次尝试访问
mywebserver:$ curl $IP_SERVICE:8002 -
将
mywebserver扩展为两个副本,并等待直到部署准备好;你可以使用以下命令检查:$ kubectl scale deploy/mywebserver --replicas=2 $ kubectl rollout status deploy/mywebserver -
再次尝试访问
mywebserver:$ curl $IP_SERVICE:8002 -
让我们更改部署中的
nginx版本,使用错误的版本:$ kubectl set image deployment/mywebserver nginx=nginx:1.16.1.x -
检查对象描述中的更改:
$ kubectl describe deployment mywebserver | grep -i image -
检查
mywebserver部署的当前 pod 状态:$ kubectl get pods
你会看到一些来自 mywebserver 的错误 pods。
-
让我们回滚到之前的版本:
$ kubectl rollout undo deploy/mywebserver -
检查
mywebserver部署的当前 pod 状态:$ kubectl get pods
你会发现错误的 pods 已经消失,因为你回到了部署所使用的之前的镜像——在这种情况下,是正确的镜像名称。
现在,你已经使用部署对象部署了应用程序。让我们用类似的方法使用 YAML 文件并增加一些持久性。为此,我们进入下一节。
使用 YAML 文件部署一个简单的 NGINX 服务器
此时,我们的示例不存储数据,对象是使用命令式命令创建的。要使用声明式文件,你可以使用 kubectl 命令生成文件。记住,在部署应用时,可以使用 pods 或 deployments——只需选择其中一种方式。首先,让我们使用 YAML 文件创建一个 NGINX pod。
使用 Pod 部署 NGINX 服务器
现在,让我们使用 YAML 文件创建一个 NGINX pod。按照以下步骤操作:
-
如果你想使用 pods,你可以使用下一个 YAML 文件。要生成文件,使用以下命令:
$ kubectl run nginx --image=nginx --dry-run –o yaml > nginx-pod.yaml
nginx-pod.yaml 文件将是这样的:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
-
使用以下命令应用生成的文件:
$ kubectl create -f nginx-pod.yaml
让我们继续在下一节中创建一个 NGINX 部署。
使用部署来部署一个 NGINX 服务器
因此,让我们开始使用 YAML 文件创建一个 NGINX 服务器,使用部署。要做到这一点,请按照以下步骤操作:
-
使用以下命令生成部署的 YAML 文件:
$ kubectl create deployment nginx --image=nginx --replicas=2 --dry-run –o yaml > nginx-deployment.yaml
文件 nginx-deployment.yaml 将如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
-
使用以下命令应用生成的文件:
$ kubectl create -f nginx-deployment.yaml
现在我们已经学会了如何在 Kubernetes 中创建 pod 和部署,让我们继续下一节,使用 YAML 文件来公开这些对象的服务。
使用 ClusterIP 服务和 YAML 文件公开您的 pods
要与其他应用程序通信您的 pod 或部署,您可能需要一个 DNS 记录。ClusterIP 服务类型为您的 pod 或部署创建一个 DNS A 记录。使用此 DNS,集群中的其他对象可以访问您的应用程序。因此,让我们为您的应用程序创建一个 ClusterIP 服务,按照以下步骤进行:
-
要使用 YAML 文件公开您的应用程序,请生成
ClusterIP服务类型的 YAML 文件:$ kubectl expose pod/nginx --type=ClusterIP --port=80 --target-port=8001 --dry-run –o yaml > nginx-clusterip.yaml
文件 nginx-service.yaml 将如下所示:
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8001
selector:
app: nginx
type: ClusterIP
-
使用以下命令应用生成的文件:
$ kubectl create -f nginx-pod.yaml
现在您已经学会了如何使用 YAML 文件创建一个 ClusterIP 服务,让我们继续在下一节为您的应用程序创建一个 NodePort 服务。
使用 NodePort 服务和 YAML 文件公开您的 pods
要为先前创建的 pod 创建一个 NodePort 服务,请按照以下步骤操作:
-
对于
NodePort,使用以下命令:kubectl expose pod/nginx --type=NodePort --port=80 --target-port=8001 --dry-run -o yaml > nginx-nodeport.yaml
文件 nginx-nodeport.yaml 将如下所示:
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8001
selector:
app: nginx
type: NodePort
-
使用下一个命令应用生成的文件:
kubectl create -f nginx-pod.yaml
现在您已经学会了如何为您的应用程序中的 pod 创建一个 NodePort 服务,现在是时候在下一节学习如何在 LoadBalancer 服务中使用了。
使用 YAML 文件公开您的 pods,使用一个 LoadBalancer 服务
要创建一个 LoadBalancer 服务以公开您 pod 中的应用程序,请按照以下步骤操作:
-
对于
LoadBalancer,使用下一个命令:$ kubectl expose pod/nginx --type=LoadBalancer --port=80 -target-port=8001 --dry-run -o yaml > nginx-lb.yaml
生成的 nginx-lb.yaml 文件将如下所示:
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8001
selector:
app: nginx
type: LoadBalancer
-
使用下一个命令应用生成的文件:
$ kubectl create -f nginx-pod.yaml
您已经学会了如何创建一个 LoadBalancer 服务。有了这个,我们已经涵盖了 Kubernetes 中的所有基本服务。现在,我们准备学习如何创建有状态的应用程序。让我们继续下一节,为您的应用程序添加持久性。
为您的应用程序添加持久性
现在是时候为您的应用程序添加存储了;我们将使用 Longhorn 安装的存储类为您的应用程序提供持久性。在本节中,我们将探讨使用持久卷的两个示例。在本书的这一部分中,我们将讨论持久卷和为 Pod 创建存储的过程。但首先,我们需要一个持久卷声明定义来配置此存储。
创建一个带有存储卷的 NGINX pod
要创建使用 Longhorn 存储类的存储卷的 NGINX 应用程序,请按照以下步骤操作:
-
创建
pvc.yaml文件:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: longhorn-volv-pvc spec: accessModes: - ReadWriteMany storageClassName: longhorn resources: requests: storage: 2Gi -
应用
pvc.yamlYAML 文件:$ kubectl create -f pvc.yaml
现在,是时候使用这个 PVC 来创建一个使用 Longhorn 存储类的 Pod 了。请按照以下步骤操作:
-
创建并应用
pod.yaml文件,使用先前创建的 PVC 来创建 Pod:apiVersion: v1 kind: Pod metadata: name: volume-test namespace: default spec: containers: - name: volume-test image: nginx:stable-alpine imagePullPolicy: IfNotPresent volumeMounts: - name: volv mountPath: /data ports: - containerPort: 80 volumes: - name: volv persistentVolumeClaim: claimName: longhorn-volv-pvc
这个示例已经创建了一个使用持久卷和 Longhorn 存储类的 Pod。接下来,我们继续进行第二个示例,展示如何使用存储卷创建一个数据库。
使用持久卷创建数据库
现在,是时候为数据库使用持久卷了;在这个示例中,您将学习如何创建一个带有持久卷的 Redis 数据库。让我们按照以下步骤开始:
-
创建
redis.yaml文件,以创建一个使用之前longhorn-volv-pvcPVC 的 Pod:apiVersion: v1 kind: Pod metadata: name: redis spec: containers: - name: redis image: redis volumeMounts: - name: redis-storage mountPath: /data/redis volumes: - name: redis-storage persistentVolumeClaim: claimName: longhorn-volv-pvc -
应用
pod.yamlYAML 文件来创建 Pod:$ kubectl create -f pod.yaml -
检查并应用
pod.yamlYAML 文件来创建 Pod:$ kubectl create -f pod.yaml -
应用
pod.yamlYAML 文件来创建 Pod:$ kubectl create -f pod.yaml
排查您的部署问题
请记住,您可以使用kubectl logs命令来排查您的部署问题。欲了解更多信息,请访问下一个链接:kubernetes.io/docs/tasks/debug-application-cluster/debug-running-pod/。
现在,您的 Redis 数据库正在运行,并使用持久卷来防止数据丢失。在最后一部分,我们将探索如何安装一个简单的 Kubernetes 仪表盘,使用 UI 来管理您的集群。
部署 Kubernetes 仪表盘
现在,是时候安装 Kubernetes 仪表盘了。接下来的步骤基于官方 K3s 文档。要开始安装仪表盘,请按照以下步骤操作:
-
使用以下命令安装仪表盘:
$ GITHUB_URL=https://github.com/kubernetes/dashboard/releases $ VERSION_KUBE_DASHBOARD=$(curl -w '%{url_effective}' -I -L -s -S ${GITHUB_URL}/latest -o /dev/null | sed -e 's|.*/||') $ sudo k3s kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/${VERSION_KUBE_DASHBOARD}/aio/deploy/recommended.yaml
这将安装仪表盘,但您需要配置如何访问此仪表盘。
-
创建
dashboard-admin-user.yaml文件,以创建一个提供访问仪表盘的服务帐户。该文件的内容如下:apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard -
现在创建
dashboard-admin-user-role.yaml文件。该文件的内容如下:apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard -
现在,使用以下命令应用 YAML 文件:
$ kubectl create -f dashboard-admin-user.yml -f dashboard-admin-user-role.yml -
获取用于访问仪表盘的服务帐户中的令牌:
$ kubectl -n kubernetes-dashboard describe secret admin-user-token | grep '^token'
仅复制令牌内容。
-
使用
kubectl proxy在本地暴露 Kubernetes API,使用以下命令:$ sudo kubectl proxy -
使用以下 URL 访问您的浏览器:
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
使用您获取的管理员用户持有令牌进行登录。选择令牌选项并输入令牌。您将看到如下屏幕:

图 5.3 – Kubernetes 仪表盘登录界面
点击登录按钮后,您将看到仪表盘。探索不同的菜单查看对象的状态,或者点击右下角的加号图标,使用 YAML 文件创建对象:
![图 5.4 – Kubernetes Dashboard 显示 CPU 和内存使用情况]
图 5.4 – Kubernetes Dashboard 显示 CPU 和内存使用情况
我们现在已经完成了所有必要的概念,简要介绍了如何在 Kubernetes 中使用 K3s 使用基本对象。
摘要
在本章中,我们学习了如何为我们的家庭实验室使用 Raspberry Pi 设备设置 K3s 集群。我们还介绍了如何使用基本的 Kubernetes 对象来部署应用程序。我们使用 kubectl 命令以命令式方式部署了示例应用程序。我们也使用 YAML 文件部署了示例应用程序。在本章的最后,我们介绍了如何安装 Kubernetes Dashboard 来管理您的集群。在下一章中,我们将继续为这个部署添加更多内容;我们将使用 ingress 控制器在边缘部署应用程序。
问题
这里有一些问题来验证您的新知识:
-
我需要创建哪些基本的 Kubernetes 对象才能创建应用程序?
-
我如何为我的家庭实验室安装 K3s 集群?
-
我如何使用
kubectl创建我的应用程序? -
我如何使用 YAML 文件创建我的应用程序?
-
我如何使用持久化存储卷?
-
我如何排查我的应用程序问题?
进一步阅读
您可以参考以下文献,了解本章涵盖的更多信息:
-
K3s 安装选项以向配置文件添加自定义参数:
rancher.com/docs/k3s/latest/en/installation/install-options -
Longhorn 官方页面:
longhorn.io -
MetalLB 官方页面:
metallb.universe.tf -
官方 Kubernetes 文档:
kubernetes.io/docs -
Kubernetes Dashboard 安装指南:
rancher.com/docs/k3s/latest/en/installation/kube-dashboard -
使用 Helm 安装 Kubernetes Dashboard:
artifacthub.io/packages/helm/k8s-dashboard/kubernetes-dashboard
第二部分:边缘的云原生应用程序
在这里,您将学习如何使用 GitOps、服务网格、无服务器架构、事件驱动架构以及不同类型的数据库在边缘部署应用程序。
本书的这一部分包含以下章节:
-
第六章,使用 Ingress 控制器和证书暴露您的应用程序
-
第七章,使用 Flux 实现边缘应用程序的 GitOps
-
第八章,使用 Linkerd 实现可观察性和流量分割
-
第九章,使用 Knative 和 Cloud Events 构建边缘无服务器和事件驱动架构
-
第十章, 边缘计算中的 SQL 与 NoSQL 数据库
第六章:使用 ingress 控制器和证书暴露您的应用程序
Ingress 控制器执行由 ingress 对象定义的流量规则,并且需要将流量暴露给系统所使用的 API 或微服务。Ingress 控制器是在 Kubernetes 集群中实现的。作为将部署暴露到集群外部的一种方式,Ingress 控制器为您的部署共享一个负载均衡器,而不是为每个部署使用专用的负载均衡器。默认情况下,Kubernetes 使用 ClusterIP 服务来访问内部集群网络中的部署。为边缘计算创建应用程序涉及到根据轻量化解决方案来配置 ingress 控制器:K3s 及其默认的 ingress 控制器 Traefik v1,使用其他 ingress 控制器,如 NGINX 或 Contour,以及服务通信的安全加密。
本章我们将涵盖以下主要内容:
-
理解 ingress 控制器和 ingress
-
安装 Helm 以进行 ingress 控制器安装
-
安装和配置 cert-manager
-
使用 Traefik 暴露您的应用程序
-
使用 NGINX 暴露您的应用程序
-
故障排除您的 ingress 控制器
-
Traefik、NGINX 和 Contour 的优缺点
-
Ingress 控制器的提示和最佳实践
技术要求
在开始之前,您需要以下内容来运行本章中的示例:
-
安装了 K3s 的树莓派集群
-
配置好的 kubectl 以访问您的集群
-
已安装并配置 Helm
注意
如果您不想使用 Traefik,并且希望在集群中省略默认安装该 ingress 控制器,请在安装主节点时添加 --no-deploy traefik --disable traefik 标志。有关安装 K3s 集群的其他详细信息,请参考 第三章,K3S 高级配置与管理,或者访问 rancher.com/docs/k3s/latest/en/installation/install-options/server-config/。记得安装一个裸金属负载均衡器,如 MetalLB,这对于生成负载均衡服务至关重要,而负载均衡服务是安装 ingress 控制器所需的。
根据这些要求,您将尝试以不同的方式暴露您的应用程序。
有关更多细节和代码片段,请查看 GitHub 上的这个资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch6
理解 Ingress 控制器
Kubernetes 使用入口控制器将您的部署暴露到集群外部。入口控制器是将代理暴露到您的应用程序的适配器,而 Ingress 是使用此适配器的 Kubernetes 对象。入口控制器充当反向代理,如NGINX,通过 HTTP/HTTPS 协议将您的应用程序暴露到负载均衡器。该负载均衡器是将您的应用程序暴露到集群外部的终端节点,负责接收并控制应用程序的流量。这样做的好处是,您可以共享这个负载均衡器,暴露任意多的应用程序,同时使用入口控制器提供的所有功能。不同的入口控制器实现包括 NGINX、Traefik、Emissary 和 Envoy。
以图 6.1为参考,要暴露您的应用程序,您必须创建一个ClusterIP服务,为您的 Deployment 或 Pod 创建一个内部 DNS 名称。该服务会自动将流量转发到服务的不同副本,进行负载均衡。Ingress 使用在安装入口控制器时,您的入口控制器所配置的 LoadBalancer 服务。如果集群不是私有的,这个 LoadBalancer 会有一个公共 IP 地址。这个 IP 地址接收来自集群外部的流量,然后将流量转发到您的应用程序所使用的 ClusterIP 服务。在内部,Ingress 对象使用配置文件作为反向代理。例如,如果您使用的是 NGINX,Ingress 对象将使用在常规 NGINX 配置文件中使用的配置。
在 Kubernetes 的上下文中,Ingress 对象会尝试使用标签匹配与您的应用程序关联的 ClusterIP 服务。这就是 Ingress 在内部的工作方式。您可以将 Ingress 看作是 NGINX 和 Apache 为网站提供的常见虚拟主机功能。

图 6.1 – Kubernetes 中的 Ingress
基于 Kubernetes 的官方文档,创建一个入口控制器的基本 YAML 文件应如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
对于 Ingress 来说,最重要的部分是注解(annotations)和规范(spec)部分。注解将定义要使用的入口控制器,在本例中为 NGINX。此部分可以包含 Ingress 的附加配置,例如重写 URL 或启用身份验证等功能。此示例定义了访问您的应用程序的 /testpath 路由。现在,您必须使用 kubectl 应用 YAML 文件。例如,如果这个文件名为 minimal-ingress.yaml,您必须运行以下命令来创建 minimal-ingress 控制器:
$ kubectl apply -f minimal-ingress.yaml
这就是入口控制器和 Ingress 的工作原理。现在,让我们在下一节中安装 Helm,以安装入口控制器。
安装 Helm 以便安装入口控制器
在我们开始使用 ingress 控制器之前,我们需要安装 Helm。Helm 是 Kubernetes 的包管理器,你可以使用它来安装软件。Helm 使用 Helm Charts,包含安装和配置部署的定义。
要安装 Helm,请按照以下步骤操作:
-
使用以下命令下载 Helm:
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -
修改权限并通过执行以下命令启动安装程序:
$ chmod 700 get_helm.sh $ ./get_helm.sh
现在你已经安装了 Helm,接下来让我们在下一部分中安装 NGINX ingress 控制器。
安装 cert-manager
cert-manager 是你希望安装的软件,如果你想在 Kubernetes 中添加证书或证书问题作为资源类型。这些证书可以被应用程序使用,但在此特定情况下,我们将使用 cert-manager 为你的应用程序添加加密流量,使用 HTTPS 协议。
要安装 cert-manager,我们将使用 Helm。安装 Helm 时,你需要按照以下步骤操作:
-
添加包含 cert-manager 的 Jetstack 仓库:
$ helm repo add jetstack https://charts.jetstack.io -
更新本地 Helm Chart 仓库缓存。为此,请执行以下命令:
$ helm repo update -
现在使用 Helm 安装 cert-manager:
$ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.5.4 \ --set prometheus.enabled=false \ --set webhook.timeoutSeconds=4 \ --set prometheus.enabled=false \ --set installCRDs=true
这将把 cert-manager 安装到 cert-manager 命名空间,版本为 1.5.4。此 cert-manager 安装不包括 Prometheus,但包括 cert-manager 客户资源定义(CRDs)并配置了在生成证书时用于 Webhook 验证的超时参数。
-
在 cert-manager 中创建一个自签名颁发者,为你的本地域创建证书。为此,请创建
self-signed-issuer.yaml文件,内容如下:apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: self-signed-issuer spec: selfSigned: {} -
现在创建一个使用 Let’s Encrypt 创建证书的颁发者,证书可以用于公共域名。为此,请创建
letsencrypt-staging.yaml文件,内容如下:apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-staging spec: acme: server: https://acme-staging-v02.api.letsencrypt.org/directory email: user@example.com privateKeySecretRef: name: letsencrypt-staging solvers: - http01: ingress: class: nginx
这个文件假设你将使用此颁发者作为示例,在一个预发布环境中使用,但你可以根据需要自定义这个文件。
重要说明
请注意,ClusterIssuer 是集群范围的,而 Issuer 是命名空间范围的。
-
现在应用这些文件,在预发布环境中创建自签名颁发者,使用以下命令:
$ kubectl apply -f self-signed-issuer.yaml -f letsencrypt-staging.yaml
现在你已经安装并准备好使用 cert-manager。你还可以为你的应用程序创建基本的颁发者。如果需要的话,这部分将是为你的域配置证书的关键。因此,现在让我们开始安装第一个 ingress 控制器——NGINX。
NGINX ingress 安装
NGINX 是 Kubernetes 上最广泛使用的 ingress 控制器。它具备你所需的所有基本和复杂配置功能,用来暴露你的应用程序。它拥有来自社区的经验和支持。它非常稳定,你仍然可以在使用 ARM 处理器的设备上使用它。
要安装 NGINX ingress 控制器,请按照以下步骤操作:
-
创建一个命名空间来安装 NGINX ingress 控制器:
$ kubectl create ns nginx-ingress -
添加包含 NGINX 入口控制器 Helm Chart 的仓库,并更新 Helm 将要使用的 Chart 仓库:
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx $ helm repo update -
使用以下命令安装 NGINX 入口控制器:
$ helm install nginx-ingress ingress-nginx/ingress-nginx -n nginx-ingress
这将输出安装成功的信息。
-
要检查
nginx-ingress控制器是否已安装,请运行以下命令:$ helm list -n nginx-ingress -
安装
nginx-ingress控制器后,K3s 将提供负载均衡器。在这种情况下,我们假设使用的是 MetalLB。要获取由您的入口控制器配置的负载均衡器 IP 地址,请运行以下命令:$ IP_LOADBALANCER=$(kubectl get svc nginx-ingress-ingress-nginx-controller --output jsonpath='{.status.loadBalancer.ingress[0].ip}' -n nginx-ingress)
这里,IP_LOADBALANCER变量包含入口控制器创建的负载均衡器的 IP,这是您的应用程序的端点。您可以通过运行以下命令检查该值:
$ echo $IP_LOADBALANCER
参考第五章中 K3s 集群的安装,K3s 边缘计算实验室,您将看到一个像这样的 IP:192.168.0.240。
-
您可以使用该 IP 来创建 DNS 记录,将入口指向一个域名,或使用路径访问您的服务。例如,假设返回的 IP 是
192.168.0.241,您可以在浏览器中使用 URLhttp://192.168.0.240访问您的服务。另一个选项是使用路径来访问您的应用程序;例如,URL 会是这样:http://192.168.0.240/myapp。 -
最后,如果您想卸载
nginx-ingress,请运行以下命令:$ helm uninstall nginx-ingress -n nginx-ingress
既然您已经安装了 NGINX 入口控制器,接下来让我们研究一个使用此入口控制器的基本示例。
使用 NGINX 暴露您的应用程序
现在是时候开始使用 NGINX 作为您的入口控制器了。我们将使用 NGINX 暴露您的第一个应用程序。首先,让我们部署一个简单的应用程序。为此,请按照以下步骤操作:
-
使用以下命令创建一个简单的
nginx镜像部署:$ kubectl create deploy myapp --image=nginx -
为
myapp部署创建一个 ClusterIP 服务:$ kubectl expose deploy myapp --type=ClusterIP --port=80 -
使用域名
192.168.0.240.nip.io创建 Ingress。在此示例中,我们假设入口的端点是192.168.0.240。这与入口控制器创建的负载均衡器的 IP 相同。当您访问浏览器时,页面https://192.168.0.241.nip.io将显示 NGINX 的myapp-ingress.yaml文件:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress-tls-nginx annotations: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: self-signed-issuer spec: tls: - hosts: - 192.168.0.241.nip.io secretName: myapp-tls-nginx rules: - host: 192.168.0.241.nip.io http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80
您可以根据需要自定义此文件。如果您不希望为您的应用程序启用 HTTPS,请省略 TLS 部分和注解cert-manager.io/cluster-issuer。如果您使用的是公共域名,请使用以下注解:
cert-manager.io/cluster-issuer: letsencrypt-staging
-
如果您使用的是本地域名,例如
myapp-test-nginx.test,则需要修改/etc/hosts文件,并添加一行如下:192.168.0.241 myapp-test-nginx.test
这对于解析应用程序的本地域名是必要的。还记得修改文件中的tls.hosts和rules.hosts,以便使用诸如myapp-test-nginx.test之类的域名。因此,第二个选项将是这样:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress-tls-nginx
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: self-signed-issuer
spec:
tls:
- hosts:
- myapp-test-nginx.test
secretName: myapp-tls-nginx
rules:
- host: myapp-test-nginx.test
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
-
使用以下命令创建入口,使用前面的文件:
$ kubectl apply -f myapp-ingress-tls-nginx.yaml -
使用 URL
https://192.168.0.241.nip.io或https://myapp-test-nginx.test在浏览器中访问myapp部署。
注意
因为这是一个自签名证书,你必须在浏览器中接受安全异常。
或者,使用 curl 通过以下命令访问页面:
$ curl -k https://192.168.0.240.nip.io
or
$ curl -k https://myapp-test-nginx.test
如果你不想使用 HTTPS,可以在浏览器中使用 URL http://192.168.0.241.nip.io 或 https://myapp-test-nginx.test 访问页面,或者使用 curl 命令访问。
-
如果你想删除 ingress,运行以下命令:
$ kubectl delete -f myapp-ingress.yaml
注意
当你删除 ingress 并且使用自签名证书时,Let’s Encrypt 使用的密钥将不会被删除。你需要手动使用 kubectl 命令删除它。例如,你可以运行以下命令删除之前创建的 ingress 中的密钥:kubectl delete secrets myapp-tls-nginx。
现在你已经学会了如何使用 NGINX。接下来是时候学习如何使用 Traefik 在下一节中暴露你的应用程序。
使用 Traefik 来暴露你的应用程序
Traefik 是 K3s 中默认包含的 ingress 控制器。它使用与 NGINX 相同的配置,如前面的 myapp-ingress.yaml 文件所示。假设你已经按照前一节创建了 myapp 部署。那么,让我们按照给定的步骤开始使用 Traefik:
-
要查找 Traefik 创建的负载均衡器 IP 地址,运行以下命令:
$ IP_LOADBALANCER=$(kubectl get svc traefik --output jsonpath='{.status.loadBalancer.ingress[0].ip}' -n kube-system)
运行以下命令查看 Traefik 安装所配置的负载均衡器的当前 IP 地址。这将用于在 /etc/hosts 文件中创建一个条目:
$ echo $IP_LOADBALANCER
假设返回 192.168.0.240,你需要在 /etc/hosts 文件中添加以下行:
192.168.0.240 myapp-test-traefik.test
现在你已经准备好创建 Ingress 对象了。
-
使用
nip和 TLS 来暴露myapp,创建myapp-ingress-tls-traefik.yaml文件,内容如下:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress-traefik annotations: kubernetes.io/ingress.class: "traefik" cert-manager.io/cluster-issuer: self-signed-issuer traefik.ingress.kubernetes.io/router.tls: "true" spec: tls: - hosts: - myapp-test-traefik.test secretName: myapp-tls-traefik rules: - host: myapp-test-traefik.test http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80 -
使用以下命令应用文件:
$ kubectl apply -f myapp-ingress-tls-traefik.yaml -
(可选) 如果你想使用
nip.io服务,YAML 文件将如下所示:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress-traefik annotations: kubernetes.io/ingress.class: "traefik" cert-manager.io/cluster-issuer: self-signed-issuer traefik.ingress.kubernetes.io/router.tls: "true" spec: tls: - hosts: - myapp-test-traefik.test secretName: myapp-tls-traefik rules: - host: myapp-test-traefik.test http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80 -
(可选) 使用以下命令应用文件:
$ kubectl apply -f myapp-ingress-tls-traefik.yaml
现在你已经配置并使用 Traefik 作为负载均衡器。
请记住,如果你没有使用 –disable traefik 参数,Traefik 将会被安装到你的 K3s 集群中。现在是时候使用 Contour 了。所以,我们进入下一节。
Contour ingress 控制器的安装和使用
Contour 是一个基于 Envoy 的 ingress 控制器。使用 Envoy 的优点是它速度快,并且包括一些在服务网格中找到的强大功能,如速率限制、先进的路由、指标等。如果速度对你的项目至关重要,Contour 在大多数情况下将是最佳的解决方案。Contour 是一个轻量级的解决方案,经过优化,运行速度非常快。这使得 Contour 成为边缘计算的理想选择。现在让我们开始使用 Contour。
安装 Contour,按照以下步骤操作:
-
使用它提供的快速配置安装 Contour:
$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml -
如果你想使用
nip.io,你需要首先找到 Contour 负载均衡器的 IP 地址,并在你的机器的/etc/hosts文件中创建一个条目。要找到 Contour 的 IP 地址,运行以下命令:$ IP_LOADBALANCER=$(kubectl get svc envoy--output jsonpath='{.status.loadBalancer.ingress[0].ip}' -n projectcontour)
如果你运行以下命令,它将显示 Contour ingress 控制器安装时配置的负载均衡器 IP 地址,该地址将用于在 /etc/hosts 文件中创建条目:
$ echo $IP_LOADBALANCER
这将显示 Contour 安装创建的负载均衡器 IP 地址。它将用于在 /etc/hosts 文件中创建条目。
假设返回的是 192.168.0.242。你需要将以下行添加到 /etc/hosts 文件中:
192.168.0.242 myapp-test-contour.test
现在你可以创建 Ingress 对象了。
-
创建一个包含基本配置的 Contour 配置文件。我们将这个文件命名为
myapp-ingress-tls-contour.yaml。该文件将包含以下内容:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress-tls-contour annotations: kubernetes.io/ingress.class: "contour" cert-manager.io/cluster-issuer: self-signed-issuer spec: tls: - hosts: - myapp-test-contour.test secretName: myapp-tls-contour rules: - host: myapp-test-contour.test http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80 -
使用以下命令应用 YAML 文件:
$ kubectl apply –f myapp-ingress-tls-contour
现在我们知道如何使用 Kubernetes 中的 Ingress 对象来使用 Contour。接下来,让我们看看如何在 Kubernetes 中使用 Contour 的自有对象。
使用 Contour 与 HTTPProxy 和 cert-manager
Contour 可以像 NGINX ingress 控制器一样使用,但你还可以使用 Contour 提供的 HTTPProxy 对象。同样的例子,myapp-ingress-tls-contour,可以使用 Contour 对象来创建。让我们看看 Contour ingress 控制器的等效配置。首先,让我们使用 cert-manager 对象创建证书。我们将文件命名为 myapp-tls-contour.yaml。它将如下所示:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myapp-tls-contour
spec:
commonName: myapp-test-contour.test
dnsNames:
- myapp-test-contour.test
issuerRef:
name: self-signed-issuer
kind: ClusterIssuer
secretName: httpbinproxy
myapp-tls-contour.yaml 文件定义创建了供 HTTPProxy 对象使用的证书。接下来,我们将创建 myapp-ingress-http-proxy-tls-contour.yaml 文件,文件内容与 myapp-ingress-tls-contour.yaml 文件的配置等效,但现在使用 HTTPProxy 对象和之前生成的证书。文件内容将如下所示:
myapp-ingress-http-proxy-tls-contour.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: myapp-ingress-http-proxy-tls-contour
spec:
virtualhost:
fqdn: myapp-test-contour.test
tls:
secretName: myapp-tls-contour
rateLimitPolicy:
local:
requests: 3
unit: minute
burst: 1
routes:
- services:
- name: myapp
port: 80
请注意,这个对象设置了每分钟 3 次请求的速率限制,并且有一个附加请求或软限制,总共有每分钟 4 次请求。如果超过限制,Contour 将阻止该请求。你可以使用以下命令访问站点:
$ curl -k https://myapp-test-contour.test
-k 参数跳过了由 cert-manager 创建的自签名证书的验证。
正如你所看到的,Contour 可以使用 Kubernetes ingress 对象,并且你可以使用 Contour 提供的对象添加更多功能,例如限速。现在,轮到你排查 ingress 控制器或 ingress 定义的问题了。让我们进入下一部分。
排查你的 ingress 控制器
这些是一些有用的命令,你可以用来排查 ingress 控制器的问题:
-
要查看 NGINX ingress 控制器的日志,运行以下命令:
$ kubectl logs -f deploy/nginx-ingress-ingress-nginx-controller -n nginx-ingress
这将显示当 ingress 使用 NGINX 作为 ingress 控制器时的日志。
-
要查看 Traefik ingress 控制器的日志,运行以下命令:
$ kubectl logs -f deploy/traefik -n kube-system -
要查看 Contour ingress 控制器的日志,运行以下命令:
$ kubectl logs -f deploy/contour -n projectcontour
这些命令对于检查 ingress 控制器部署中的活动非常有用。现在,以下是一些用于检查你的 ingress 定义是否正常工作的有用命令:
$ kubectl get svc
$ kubectl get ingresses
如果你想使用 ingress 控制器自己的对象,例如 HTTPProxy 等,请运行以下命令:
$ kubectl get OBJECT_NAME
这里,OBJECT_NAME 应该是例如 HTTPProxy、Certificate 等。这取决于你要检查的对象是什么。要查看这些对象的完整列表,你可以查阅 NGINX、Traefik 和 Contour 的官方文档。
现在你已经了解了如何排查 ingress 控制器部署和 ingress 定义的问题,让我们来看看本章中使用过的 ingress 控制器的优缺点。
Traefik、NGINX 和 Contour 的优缺点
所有的 ingress 控制器都有基本的功能来暴露你的应用程序,也就是说,它们与 Kubernetes 中的 Ingress 对象兼容。因此,让我们来探讨每个 Ingress 控制器的优缺点。让我们从这个快速的对比开始:
-
NGINX Ingress 是一个使用 NGINX 来暴露集群中应用程序的 ingress 控制器。
-
优点:它是 Kubernetes 中最广泛使用的 ingress 控制器。它有丰富的文档。开发者和社区支持广泛可得。它背后的社区规模大于 Traefik 和 Contour。
-
缺点:与基于 Envoy 的 ingress 控制器(如 Emissary、Gloo 和 Contour)相比,它可能会较慢。
-
-
Traefik 是由 Traefik Labs 创建的 ingress 控制器。它具有许多功能,可以作为插件使用。它可以用来在仪表盘上可视化你的应用程序。
-
优点:它有一个仪表盘和丰富的文档。它还具有一些服务网格功能。
-
缺点:与 NGINX 和 Contour 相比,它的速度较慢。文档内容不专注于 Kubernetes,且可能难以理解。
-
-
Contour 是一个基于 Envoy 的 ingress 控制器,Envoy 是 VMware 拥有的一款工具。它用于 Tanzu,一个管理 Kubernetes 的平台。这意味着一个大公司支持 Contour。
-
优点:由于其架构和使用的 C 语言二进制文件,它的速度很快。它有足够的功能来暴露你的应用程序。它可以作为服务网格使用。像 Istio 这样的重大项目将 Envoy 用作其默认的 ingress 控制器。Contour 支持 ARM 设备。
-
缺点:Contour 还不成熟,缺少一些功能。与 NGINX 和 Traefik 相比,它的功能较少。
-
Ingress 控制器的使用可能主要是为了暴露你的应用程序。根据你需要的功能,你可以选择前述的 ingress 控制器。如果你想使用一个稳定的 ingress 控制器,可以选择 NGINX。如果你需要自动发现功能或者一个可视化的仪表盘来查看你的端点,也许可以使用 Traefik。如果你追求速度或可定制的 ingress 控制器,可以选择 Contour,或者你可以使用 Envoy 创建自己的解决方案。
Ingress 控制器的技巧和最佳实践
这些是你在使用入口控制器时可以探索的一些想法:
-
使用路由功能:每个入口控制器都有不同的方式来实现路由以暴露你的应用程序。阅读这些入口控制器的官方文档,了解哪个具有你所需的功能。
-
创建概念验证(POC)以评估哪个入口控制器最适合你的使用场景。
-
安装 Traefik 2.0:如果你喜欢 Traefik,或许可以安装 Traefik 2.0。K3s 包含 Traefik 1.0 版本,只有暴露应用程序所需的基本功能。但如果你需要更多高级的反向代理功能,可以安装 Traefik 2.0,它包含一个仪表盘和其他你可能想要使用的功能。
-
引入速率限制:为你的应用程序实现速率限制。当你想要防止流量激增或拒绝服务攻击时,这是一个很好的功能。
-
实现 TLS:这是一个常见的使用场景。建议加密你的流量,以防黑客窃取你的信息。为你的应用程序提供额外的安全性是非常重要的。
-
安装基本认证:这是保护你的端点的最基础安全方式。通过这种方式,你可以设置用户名和密码来访问你的应用程序。
-
使用 JSON Web Tokens(JWTs)保护访问:这是一个很好的功能,可以让你获得更多控制权,并使用令牌访问你的端点。相比使用基本认证方法,这是一个更好、更安全的选项。
现在你有了更多的想法,在使用入口控制器并创建入口定义以暴露你的应用程序时可以实现。现在是对本章节内容进行快速总结的时刻了。
总结
本章节中,我们学习了如何使用不同的入口控制器,如 NGINX、Traefik 和 Contour。这些是最常用的入口控制器,首先是 NGINX,其次是 Traefik,最后是基于 Envoy 的 Contour。本章节展示了如何使用 NGINX、Traefik 和 Contour 来解决真实生产环境中的常见任务。示例涵盖了 TLS、路由以及一些访问应用程序的基本速率限制。此章节涵盖了开始在下一章中实际应用这些技术所需的最后一个主题。
问题
以下是一些问题,用来验证你新的知识:
-
什么是入口控制器?
-
何时可以使用入口控制器?
-
如何创建入口定义以暴露你的应用程序?
-
如何为 NGINX、Traefik 或 Contour 创建入口定义?
-
如何排查入口控制器和入口定义的问题?
-
如何将 MetalLB 与入口控制器一起使用?
进一步阅读
你可以参考以下资源,了解本章节涉及的更多信息:
-
Kubernetes Ingress 文档:
kubernetes.io/docs/concepts/services-networking/ingress -
使用 Helm 安装 cert-manager:
cert-manager.io/docs/installation/helm -
使用 cert-manager 为 Ingress 生成证书:
cert-manager.io/docs/tutorials/acme/nginx-ingress -
Kubernetes Ingress 控制器官方文档:
kubernetes.io/docs/concepts/services-networking/ingress -
安装 NGINX Ingress 控制器:
kubernetes.github.io/ingress-nginx/deploy/#using-helm -
Contour Ingress 控制器入门:
projectcontour.io/getting-started -
Contour 限流设置:
projectcontour.io/docs/v1.15.2/config/rate-limiting -
在 Minikube 中从头创建 Kubernetes TLS Ingress:
www.youtube.com/watch?v=7K0gAYmWWho -
Traefik 和 Kubernetes:
doc.traefik.io/traefik/v1.7/configuration/backends/kubernetes -
JWT 生成器:
jwt.io
第七章:基于 Flux 的 GitOps 用于边缘应用
之前的章节已经介绍了如何使用 K3s 构建家庭实验室的基础知识。现在是时候实现一些简单的使用案例,帮助你在边缘计算中使用了。这一章将介绍如何在边缘计算环境中使用 Flux 实现应用的 GitOps,从 GitOps 的基本理论以及管理部署 Git 仓库所需的工具开始。接着,我们将介绍如何安装 Flux,实施一个基本的 GitOps 工作流来演示应用。此章节还包括如何使用 单一仓库 (monorepo) 配置、Helm 操作符和 Flux 的镜像更新功能来自动化应用的部署。最后,我们将在章节结束时介绍如何在 Flux 中安装基本的监控仪表盘、Flux 的常用故障排除命令以及如何卸载 Flux。
在本章中,我们将涵盖以下主要主题:
-
实现边缘计算中的 GitOps
-
Flux 及其架构
-
为边缘应用设计基于 Flux 的 GitOps
-
使用 GitHub Actions 构建你的容器镜像
-
安装并配置 Flux 以实现 GitOps
-
故障排除 Flux 安装问题
-
安装 Flux 监控仪表盘
-
卸载 Flux
技术要求
在本章中,要实现基于 Flux 的 GitOps,你将需要以下内容:
-
使用具有 ARM 处理器的设备(例如 Raspberry Pi)构建三个单节点的 K3s 集群。
-
具有 Git 的使用经验。
-
GitHub 仓库及其令牌;你还需要一些使用 Git 的基本经验。
-
Docker Hub 账户,用于推送应用的新镜像版本。
这样,你就可以准备好实施这个边缘使用案例,通过 Flux 来实现 GitOps。那么,我们开始吧。
详情和代码片段请查看 GitHub 上的资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch7
实现边缘计算中的 GitOps
为了开始这个话题,我们先从 GitOps 的概念入手。www.gitops.tech/ 网站中提到:“GitOps 是实现云原生应用持续部署的一种方式。它关注的是开发者为基础设施操作提供的以开发者为中心的体验,使用开发者已经熟悉的工具,包括 Git 和持续部署工具。” 这意味着 GitOps 可以帮助你进行 持续部署 (CD) 。在软件工程中,通常使用 CD 缩写来指代持续部署和持续交付。
此外,GitLab 页面 about.gitlab.com/topics/gitops 也提到,GitOps 包含以下基本组件:
-
基础设施即代码 (IaC):这指的是为你的应用提供基础设施或部署的声明性方式。
-
合并请求或拉取请求 (PRs):管理跨多个更改和协作者的基础设施或应用程序代码更新的一种方式。
-
源代码管理 (SCM):像 Git 这样的系统可以启用基于合并请求(merge request)或拉取请求(pull request)的工作流,并提供一种机制来管理这些工作流,通常是通过 Git 仓库来实现。通过这种方式,团队可以拥有一个批准和审查机制来应用更改。
-
持续集成和持续交付 (CI/CD):CI 和 CD 本质上包括了构建、检查以及将应用程序和变更部署到软件应用程序的所有过程。GitOps 用于自动化云原生应用程序的 CD。
在本章中,你将找到提供 CI/CD 自动化管道机制的工具。让我们关注下面的图表,展示 GitOps 工作原理的总结:

图 7.1 – GitOps
启动 GitOps 的基础内容在这里列出,如 图 7.1 所示:
-
提交代码到源代码:在这里,开发者进行更改并将更改推送到源代码或仓库——例如,托管在 GitHub 上的 Git 仓库。
-
同步更改:GitOps 工具,如 Flux,会定期检测源代码中的更改。
-
提供或调和更改:一旦 GitOps 工具检测到更改,它的目标是自动化基于声明性配置更改的部署更新过程。这可能包括修改过程,比如更改配置、更新使用容器的应用程序的容器镜像等。有时,如果资源或部署不存在,你需要提供资源或调和这些资源,比较更改。这意味着 GitOps 工具定期与声明性定义一起工作,以反映基础设施或应用程序的状态。
最后,用户在访问他们的应用程序时将看到更改,你还可以添加一些额外的过程,例如在某个过程完成时的通知,或者在某个过程失败时的警报。
总的来说,这就是 GitOps 过程的工作方式,以及 GitOps 工具的运作方式。在本章中,我们将重点介绍 Flux 作为我们的 GitOps 工具,来实现 GitOps 流程,自动化我们的应用程序部署和更新。
GitOps 原则
如果你使用 GitOps 来自动化 CD 管道,有一些原则是必须遵循的。基于 Weaveworks,这些原则如下:
-
声明性定义:你可以经常通过 YAML 文件找到这些定义,但它们也可以以其他格式出现,例如 JSON。
-
应用程序的状态版本化存储于 Git 中:GitOps 工具使用 Git 仓库来存储应用程序的更改和状态。
-
批准可以自动应用到资源的更改:Git 仓库或服务提供方式来自动触发工具,当检测到某些更改或合并时。
-
监听变更的软硬件代理,进行通知或警报:GitOps 工具还具有守护进程,监听准备启动的动作中的变化,例如在使用容器的应用程序中,改变一个包含新镜像标签的代码库。
GitOps 的好处
现在你已经知道了 GitOps 工具如何工作以及 GitOps 流程如何帮助你自动化 CD 管道,接下来让我们了解一下 GitOps 的好处。
基于Weaveworks,其内容如下:
-
提高生产力:自动化过程减少了执行时间;在这种情况下,你可以在更短的时间内对应用进行更多的变更和更新。
-
增强的开发者体验:GitOps 工具通常会在你的 Git 仓库上启动自动化流程,并且这些流程会自动启动,无需了解其内部工作原理——例如,对于使用 Kubernetes 的应用,开发者在某些情况下无需了解 Kubernetes。然而,这取决于你的应用程序结构。
-
提高稳定性:GitOps 工具的日志默认包含在内,有助于满足一些安全性和监控功能。
-
更高的可靠性:GitOps 工具让你能够实施回滚机制,减少应用程序在变更影响系统运行时的停机时间。
-
一致性和标准化:GitOps 工具具有定义应用程序的结构,并为应用程序的定义、管道或更新提供最佳实践。
-
更强的安全保障:GitOps 工具具备安全功能,如针对机密的加密技术、以及跟踪和管理变更的能力。这为保护你的应用提供了一种方式。
现在,让我们继续了解 GitOps 在云原生环境中的工作原理。
GitOps、云原生与边缘计算
如我们所知,云原生指的是在开发运维(DevOps)文化中,使用容器、微服务和 CI/CD 等技术的应用。因此,当一个 GitOps 工具被设计为在云原生环境中运行时——例如 Kubernetes 集群——你就可以找到该概念的交集。
GitOps 工具可以帮助你自动化 Kubernetes 应用程序的持续交付过程。像 Argo CD 或 Flux 这样的工具可以帮助你为你的应用实现 GitOps。
但在本章中,我们将更侧重于运行在低资源环境下、使用 ARM 处理器的应用。在这种情况下,Flux 支持 ARM,而 Argo CD 不支持。本章将重点介绍如何在 ARM 设备上使用 Flux 实现 GitOps。因此,让我们在下一节开始时简要介绍 Flux。
Flux 及其架构
Flux 网站 fluxcd.io 上写道:“Flux 是一组针对 Kubernetes 的持续和渐进交付解决方案,它是开放和可扩展的。” Flux 使您能够将 Kubernetes 集群与包含应用程序声明式定义的源保持同步,这些定义通常存储在 Git 仓库中。
Flux 还使用 Kubernetes API 来管理其对象。它还使用自己的 GitOps 工具包,为您提供构建 Kubernetes 上 CD 系统的工具。您可以在以下图中看到 Flux 是如何工作的:

图 7.2 – Flux 架构
该图反映了 GitOps 的典型工作流,从提交开始,GitOps 工具不断检查应用程序定义的变化——在这种情况下是 YAML 文件。当 Flux 检测到此变化时,它会自动根据声明式定义为您的应用程序进行协调配置。
为了为 GitOps 提供必要的功能,Flux 提供以下主要特性:
-
支持多个提供商的 Git 仓库
-
支持 Kustomize 和 Helm 等工具的集成
-
事件触发和周期性协调
-
与 Kubernetes 基于角色的访问控制 (RBAC) 集成
-
警报外部系统(Webhook 发送器)
-
外部事件处理(Webhook 接收器)
-
自动容器镜像更新到 Git(图像扫描和修补)
作为 Kubernetes 的 GitOps 工具,Flux 可以安装在 ARM 设备上。这样,Flux 可以很好地与边缘计算匹配。但首先,让我们看看 Flux 如何满足边缘计算要求。
Flux 满足边缘计算要求的原因如下:
-
与 Argo CD、Tekton 等工具相比,GitOps 的复杂性较低
-
可以安装在低资源环境的 ARM 设备上
-
操作时消耗较低资源
这就是 Flux 如何工作以及它如何满足边缘计算要求。现在,让我们看看如何组织我们的应用程序,在下一节中使用 Flux 实现边缘计算的 GitOps。
使用 Flux 为边缘应用程序设计 GitOps
我们将使用 Flux 为边缘计算实现 GitOps,但首先,我们需要解释整个工作流和这一实现的主要部分。为此,让我们探索以下图表,它解释了 GitOps 的组件和工作流,为您的应用程序实现图像自动化更新器:

图 7.3 – 使用图像更新器功能实现 Flux GitOps
我们的 GitOps 工作流实现有以下步骤:
-
开发者更改应用程序并提交变更,通过 PR 将变更合并到主分支。你可以直接更改并推送到主分支,但这不是一个好的实践,因为你可能提交了破坏性或未经审查的更改。在本章后面的示例中,我们使用 GitHub 来托管我们的 Git 仓库。
-
仓库已启用 GitHub Actions,并触发了一个简单的管道,仅用于构建镜像。
-
镜像会在管道中构建并标记为新版本,然后推送到公共 Docker 注册表。在大多数业务场景中,你必须使用云端或本地的私有仓库。
-
镜像更新器功能会检查新更改和为先前生成的镜像标签进行标记,以便你的应用程序使用这些镜像。
-
一旦 Flux 检测到新镜像,它会查找配置为使用新镜像标签更新的文件。一旦 Flux 使用新标签更新了文件定义,变更将被推送到仓库。
-
Flux 检测到已更新的新镜像标签的文件定义的更改。然后,Flux 会触发协调过程以更新你的应用程序。
-
Kubernetes 集群中与定义文件相关联的对象会被更新。然后,应用程序将使用新镜像运行。
为了实现刚才描述的 GitOps 工作流,我们假设以下网络和 GitHub 配置:
-
使用
192.168.0.0/24网络的单节点 K3s 集群,使它们能够访问同一网络。你也可以使用其他私有网络,例如172.16.0.0/16或10.0.0.0/8网络。 -
每个集群都使用 MetalLB 作为裸金属负载均衡服务,使用不同的 IP 范围进行负载均衡。集群 1 使用
192.168.0.51-60范围的 IP 地址,集群 2 使用192.168.0.61-70,集群 3 使用192.168.0.71-80来进行此网络的基本 IP 地址分配。前几个地址通常由 Traefik 的默认负载均衡器使用,因此在你的网络中,这些 IP 地址可能会不同。请参考 第五章,用于边缘计算实验的 K3s 家庭实验室,以使用相同或类似的 IP 范围配置 MetalLB。 -
你有一个 GitHub 账户和一个令牌,可以访问或创建你账户中的仓库。我们正在做的是:
- 我们使用 https://github.com/sergioarmgpl/fluxappdemo GitHub 仓库,该仓库包含一个基本的 Helm chart 用于在我们的集群中部署。你可以在仓库链接中找到更多关于该应用程序的详细信息。
至此,我们已经准备好在下一部分开始实现这个场景。
为 GitOps 创建一个简单的单仓库
对于我们的 GitOps 实现,我们将使用单一代码库(monorepo)。我们选择这样做是为了减少管理多个仓库,并将所有工作集中在一个仓库中。对于这个用例,我们将把集群配置和应用程序定义组织在一个仓库中。让我们通过以下截图来了解我们的新仓库将如何组织:

图 7.4 – 单一代码库结构
现在让我们描述一下每个目录和文件的作用,如下所示:
-
clusters:这是包含所有 Flux 配置和部署的主目录,配置分布在不同的子目录中。 -
cluster1-cluster3:这些文件夹内部,Flux 和您的应用程序的定义被组织起来。cluster1将负责更新所有集群的应用程序的 YAML 定义。因此,cluster2和cluster3在其安装中不需要镜像更新组件。 -
flux-system:包含部署 Flux 的 Flux 定义。包括gotk-components.yaml、gotk-sync.yaml和kustomization.yaml文件,这些文件配置不同的组件,以实施我们的镜像更新 GitOps 工作流。 -
manifests:包含部署您的应用程序所需的定义。 -
namespace.yaml:为您的应用程序创建一个生产命名空间。 -
helm-charts.yaml:用于访问您的 Helm 图表的定义。 -
helm-release.yaml:包含使用helm-charts.yaml中定义的 Helm 图表部署应用程序的定义和值。 -
app-demo-registry.yaml:包含一个在 Docker Hub 上扫描的镜像。 -
app-demo-policy.yaml:包含一个表达式,用于检查您希望更新容器镜像的文件。 -
flux-system-automation.yaml:查找文件夹以更新更改。
这个仓库是为您的应用程序设计的。它是一个用于生产环境的单一代码库,包含多个集群。您可以使用 Kustomize 进行更复杂的配置,但那超出了本章的范围。
重要提示
您还可以在 Flux 网站上找到有关如何组织仓库的一些方法。更多信息,请访问以下链接:fluxcd.io/docs/guides/repository-structure。
现在,是时候查看我们将在边缘计算的 GitOps 用例中实施的工作流了。
理解应用程序和 GitHub Actions
要开始使用 Flux 实现 GitOps,我们必须设置一个小型流水线,每当修改我们应用程序的源代码时就创建一个容器镜像。为了简化我们的工作,此配置将基于 github.com/sergioarmgpl/fluxappdemo 仓库,该仓库包含一个使用 Flask 的简单 Python 应用程序。此应用程序有两个目录:src 和 .github/workflows。src 目录包含应用程序的源代码,而 workflows 文件夹包含 GitHub Actions 配置。
因此,让我们首先探索 src 目录。你可以在以下截图中看到仓库的概述:

图 7.5 – fluxappdemo 仓库
src 目录包含以下文件:
-
Dockerfile: 这个文件包含了构建 Docker 镜像的配置;它还调用了tests.py中包含的一个小单元测试。 -
Operations.py: 这个文件有一个名为Operations的类,其中包含一个名为runningInfo的方法。此函数接收两个参数:msg1和msg2。通过这些参数,它返回以下消息:Running app <msg1> in namespace <msg2>。 -
build_push.sh: 这是一个手动构建镜像的示例脚本。它接收两个参数;第一个是你的 Docker 用户名,第二个是镜像的标签。你可以如下运行它:$ /bin/bash build_push.sh <DOCKER_USERNAME> <IMAGE_TAG> -
index.py: 这是运行我们应用程序的主要 Python 文件。它有一个名为hello_world的函数,获取MESSAGE和NAMESPACE环境变量,然后调用runningInfo函数返回以下消息:Running app <MESSAGE> in namespace <NAMESPACE>。因此,每当你在route /和端口5000调用应用程序时,它将显示该消息,然后/ _health路由返回运行消息,/ _version返回一个自定义消息。你可以使用这个路由来探索应用程序。要查看代码,请访问以下链接:github.com/sergioarmgpl/fluxappdemo/blob/main/src/index.py。 -
requirements.txt: 包含运行代码所需的所有库。 -
tests.py: 这个文件包含了Operations类中runningInfo函数的一个小测试。
你可以在以下截图中看到 src 目录的概述:

图 7.6 – 包含源代码的 src 目录
.github/workflows 包含一个 github-actions-fluxappdemo.yml 文件。该文件包含了一个 CI 管道的定义,用于构建和推送你的容器 ARM 镜像,使用以下名称和标签格式:<DOCKER_USER>/fluxappdemo:RELEASE.YEAR-MONTH-DAYT-HOUR-MINUTE-SECONDZ。
DOCKER_USER 是你的 Docker 用户名。
你可以在以下截图中看到这个文件:

图 7.7 – GitHub Actions workflows 文件
通过这个简要说明,我们继续配置你自己的流水线来构建并推送你的容器镜像。
使用 GitHub Actions 构建容器镜像
要使用 GitHub Actions 构建并推送你的镜像,应该按照以下步骤进行:
- Fork
github.com/sergioarmgpl/fluxappdemo仓库。这将创建一个名为https://github.com/<GITHUB_USER>/fluxappdemo的仓库。
GITHUB_USER 是你的 GitHub 账户的用户名。用你自己的用户名替换它。
- 为你的仓库创建
DOCKERHUB_USERNAME和DOCKERHUB_TOKEN密钥。这些将作为加密的密钥为仓库创建。要创建这些密钥,请在浏览器中打开以下页面:https://github.com/<GITHUB_USER>/fluxappdemo/settings/secrets/actions。
添加变量后,你的仓库将如下所示:

图 7.8 – GitHub 仓库密钥
-
修改
.github/workflows/github-actions-fluxappdemo.yml文件中的最后一行,在tags部分替换为你的用户名。它将如下所示:tags: <DOCKER_USER>/fluxappdemo
这里,DOCKER_USER 是你的 Docker Hub 用户名。
-
提交并推送更改。
-
(可选) 要检查你的 GitHub action 是否在运行,可以访问以下链接:
https://github.com/<GITHUB_USER>/fluxappdemo/actions。以下截图提供了该页面的示例:

图 7.9 – GitHub Actions 显示当前工作流
- (可选) 要查看为你的账户创建的新容器标签,请访问以下链接:
https://hub.docker.com/repository/docker/<DOCKERHUB_USERNAME>/fluxappdemo/tags。以下截图提供了该页面的示例:

图 7.10 – 仓库的 Docker Hub 标签
- (可选) 为了测试你的 GitHub Actions 流水线是否正常工作,修改
src目录中的应用源代码并提交和推送更改。然后,新的工作流将开始运行。
重要提示
要了解有关为仓库创建加密密钥的更多信息,请访问以下链接:docs.github.com/en/actions/security-guides/encrypted-secrets。要创建一个访问 Docker Hub 账户的令牌以推送新镜像,请访问此链接:docs.docker.com/docker-hub/access-tokens。最后,要 fork 一个仓库,请查看以下链接:docs.github.com/en/get-started/quickstart/fork-a-repo。
现在我们已经配置了 GitHub Actions 流水线来自动创建带有标签的容器镜像,接下来是配置 Flux 以完成我们的 GitOps 工作流。
安装并配置 Flux 以进行 GitOps
在配置 Flux 之前,先了解我们将在本节中安装什么。在本节中,我们将安装 Flux 及其组件,这些组件会检测容器的最新镜像标签。一旦检测到新镜像,Flux 会修改您部署仓库中 HelmRelease 的定义文件。然后,Flux 会自动协调更改,更新在该 HelmRelease 文件中定义的应用程序部署,该文件使用的是发布于 sergiops.xyz/helm-charts 的 Helm 图表。与在 使用 GitHub Actions 构建容器镜像 部分中定义的 GitHub Actions 工作流一起,完整的工作流将像这样运行:
-
用户将本地仓库的更改推送到位于
https://github.com/<GITHUB_USER>/fluxappdemo的原始源代码仓库。 -
GitHub Actions 会在
https://hub.docker.com/repository/docker/<DOCKER_USER>/fluxappdemo将镜像构建并推送到 Docker Hub。 -
Flux 会检测到更新镜像时生成的新标签。
-
Flux 会用新的标签替换
HelmRelease定义。为此,Flux 会修改、提交并将更改推送到位于https://github.com/<GITHUB_USER>/fluxdemo-production.git的部署定义仓库。 -
Flux 会协调更改,应用程序将使用新的镜像标签进行更新。
提示
<GITHUB_USER> 和 <DOCKER_USER> 值必须替换为您的 GitHub 和 Docker 用户。
要开始使用 Flux 构建此用例场景,您必须安装 Flux CLI。以下是操作方法:
-
要在 Linux 上安装 Flux CLI,请运行以下命令:
$ curl -s https://fluxcd.io/install.sh | sudo bash -
或者,如果您使用 macOS,您可以使用以下命令通过 Homebrew 安装 Flux:
$ brew install fluxcd/tap/flux
重要提示
您可以在官方网站 fluxcd.io 查找替代安装方法。
现在,您需要安装 Flux 图像更新器功能,这对于每次检测到新的镜像标签时自动化 CD 过程是必要的。要安装 Flux 和所有必要的组件,请按照每个集群上给定的步骤操作:
-
设置您的环境变量;在此情况下,我们需要设置我们的 GitHub 用户和 GitHub 账户的令牌,如下所示:
$ export GITHUB_USER=<YOUR_USER> $ export GITHUB_TOKEN=<YOUR_GITHUB_TOKEN> $ export DOCKER_USER=<YOUR_DOCKER_USERNAME>
提示
查看以下链接以为您的 GitHub 账户创建令牌:docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token。
-
让我们设置与您的 GitHub 账户相关联的电子邮件地址和用户名。为此,请运行以下命令:
$ git config --global user.email "<YOUR_EMAIL>" $ git config --global user.name "<YOUR_NAME>" -
现在,安装 Flux 和所有必要的组件以实现图像更新器功能。为此,运行以下命令:
$ CLUSTER_N=1 $ flux bootstrap github \ --kubeconfig /etc/rancher/k3s/k3s.yaml \ --components-extra=image-reflector-controller,image-automation-controller \ --owner=$GITHUB_USER \ --repository=fluxdemo-production \ --branch=main \ --path=./clusters/cluster$CLUSTER_N \ --read-write-key \ --personal
repository 参数是要创建的 GitHub 仓库的名称——例如,fluxdemo-production。
CLUSTER_N 是一个环境变量,表示您安装 Flux 的集群编号,例如 CLUSTER_1。用于安装 Flux 的 YAML 文件将存储在 clusters/production/<CLUSTER_N> 目录中。
该过程将显示消息 等待 Kustomization "flux-system/flux-system" 被调和。一旦 Flux 安装完成,您将看到消息 所有组件都正常。
该命令将创建一个与您的用户帐户关联的仓库。访问该仓库的链接将是 https://github.com/<GITHUB_USER>/fluxdemo-production.git。
GITHUB_USER 是您的 GitHub 用户名。请将此值替换为您自己的用户名。
重要提示
对于 cluster2 和 cluster3,省略 --components-extra=image-reflector-controller,image-automation-controller 这一行。cluster1 负责更新所有集群的应用定义。如果您使用的是组织、企业或其他版本控制工具,运行 flux bootstrap github --help 可以查看该命令的帮助文档,获取更多选项。
-
克隆新的仓库并将其切换到此目录:
$ git clone https://github.com/$GITHUB_USER/fluxdemo-production.git $ cd fluxdemo-production
这将要求您输入用户名和密码来克隆您的仓库。在接下来的步骤中,这些将用于自定义和创建部署定义。按照以下步骤进行:
-
在名为
clusters/<clusterN>/manifests的目录中为您的应用程序创建一个命名空间。为此,运行以下命令:$ mkdir -p ./clusters/cluster$CLUSTER_N/manifests $ kubectl create ns production --dry-run=client -o YAML > ./clusters/cluster$CLUSTER_N/manifests/namespace.yaml
创建一个指向您自己 Helm 图表的 Helm chart 源 Flux 对象,如下所示:
$ flux create source helm helm-charts \
--kubeconfig /etc/rancher/k3s/k3s.yaml \
--url=https://sergiops.xyz/helm-charts \
--interval=1m \
--namespace=production \
--export > ./clusters/cluster$CLUSTER_N/manifests/helm-charts.yaml
在此示例中,我们使用的是位于 sergiops.xyz/helm-charts 的示例应用程序 Helm 图表。
-
创建一个 Flux
HelmRelease对象,为您的应用程序部署创建 YAML 定义,如下所示:$ flux create helmrelease app-demo --chart app-demo \ --source HelmRepository/helm-charts.production \ --chart-version 0.0.1 \ --interval=1m \ --namespace production \ --export > ./clusters/cluster$CLUSTER_N/manifests/helm-release.yaml
通过运行以下命令,我们将向文件中添加一个名为values的部分:
cat << EOF >> ./clusters/cluster$CLUSTER_N/manifests/helm-release.yaml
values:
replicaCount: 3
containerPort: 5000
dockerImage: $DOCKER_USER/fluxappdemo:RELEASE.2022-01-16T05-42-20Z # {"\$imagepolicy": "flux-system:app-demo"}
namespace: "production"
domain: "app-demo-cluster$CLUSTER_N.domain.tld"
changeCause: "First Deployment cluster $CLUSTER_N"
message: "cluster$CLUSTER_N"
appname: "app-demo-cluster$CLUSTER_N"
node: "machine$CLUSTER_N"
EOF
请注意注释行 # {"$imagepolicy": "flux-system:app-demo"}。
这一部分告诉 Flux 去寻找替换图像的新标签。最终,文件将如下所示:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: app-demo
namespace: production
spec:
chart:
spec:
chart: app-demo
sourceRef:
kind: HelmRepository
name: helm-charts
namespace: production
version: 0.0.1
interval: 1m0s
values:
replicaCount: 3
containerPort: 5000
dockerImage: <DOCKER_USER>/fluxappdemo:RELEASE.2022-01-16T05-42-20Z # {"$imagepolicy": "flux-system:app-demo"}
namespace: "production"
domain: "app-demo-cluster1.domain.tld"
changeCause: "First Deployment cluster 1"
message: "cluster1"
appname: "app-demo-cluster1"
node: "machine1"
这个通过 Flux 安装提供的 HelmRelease 对象提供了一种声明性方法来参数化部署。在这种情况下,values 部分中的值对应于您可以传递给我们 Helm 图表的不同参数。Helm 图表的创建超出了本书的范围,但您可以在本章结尾的 进一步阅读 部分找到相关资源。
提示
您可以访问 github.com/sergioarmgpl/fluxdemo-production 仓库,查看使用前述命令创建和修改演示应用程序配置文件的最终结果。
这个 HelmRelease 对象存储在一个文件中,你需要修改它、提交并推送你的更改到你的仓库。之后,Flux 会检测到更改并更新你的应用程序。这个文件位于你的仓库路径 clusters/cluster$CLUSTER_N/manifests/helm-release.yaml 中。通过这种方式,你可以测试 Flux 如何更新你的应用程序,其中 CLUSTER_N 变量是你正在修改的集群编号。
重要提示
如果你想创建自己的 Helm chart 仓库,可以查看 helm.sh/docs/topics/chart_repository,并进入 GitHub Pages 示例部分。在这个例子中,我们使用的是位于 sergiops.xyz/helm-charts 和 github.com/sergioarmgpl/helm-charts/tree/gh-pages 的 charts。你可以检查或克隆这个仓库来创建你自己的。
如果你正在配置 cluster2 和 cluster3,你可以省略步骤 11-13,因为 cluster1 会负责更新所有部署的定义。
-
(可选) 创建一个镜像仓库来检测你的镜像的新版本或标签,如下所示:
$ flux create image repository app-demo \ --kubeconfig /etc/rancher/k3s/k3s.yaml \ --image=$DOCKER_USER/fluxappdemo \ --namespace=flux-system \ --interval=1m \ --export > ./clusters/cluster$CLUSTER_N/manifests/app-demo-registry.yaml -
(可选) 创建一个镜像策略,定义一个表达式来匹配并检测来自你的镜像仓库的新镜像标签或版本。此用例将使用以下格式来标记新的 Docker 镜像:
RELEASE.YEAR-MONTH-DAYT-HOUR-MINUTE-SECONDZ
这个约定基于 RFC3339 和 ISO 7601,它们是互联网上日期和时间的标准:
$ flux create image policy app-demo \
--image-ref=app-demo \
--namespace=flux-system \
--select-alpha=asc \
--filter-regex='^RELEASE\.(?P<timestamp>.*)Z$' \
--filter-extract='$timestamp' \
--export > ./clusters/cluster$CLUSTER_N/manifests/app-demo-policy.yaml
-
(可选) 现在,到了将所有部分结合起来的时候。为此,你需要创建一个
ImageUpdateAutomation对象,它将检测新的版本并更新你部署 YAML 定义中的镜像。在以下案例中,它将检查 clusters 文件夹以更新所有的 YAML 定义:$ flux create image update flux-system \ --git-repo-ref=flux-system \ --git-repo-path="./clusters" \ --checkout-branch=main \ --push-branch=main \ --author-name=<AUTHOR_NAME> \ --author-email=<AUTHOR_EMAIL> \ --commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \ --export > ./clusters/cluster$CLUSTER_N/manifests/flux-system-automation.yaml
你需要将 <AUTHOR_NAME> 和 <AUTHOR_EMAIL> 标签替换为你自己的值。这将在 Flux 推送镜像标签更改时显示为提交作者。
-
使用以下命令提交并推送更改到仓库:
$ git add -A $ git commit -m "feat: App YAML definitions" $ git push origin main
push 命令将要求你输入你之前创建的用户和令牌,以访问你的 GitHub 账户。
现在,你可以构建一个新的镜像,并等待 Flux 自动更新你的 HelmRelease 文件,检测到新的镜像。1 分钟或更长时间后,你应该会看到变更已生效。你可以预期看到 Flux 在你的仓库中提交了新的标签检测记录,以帮助排查镜像更新器是否正常工作。
-
(可选) 你可以通过运行以下命令强制 Flux 应用此配置,启动 Flux 的协调过程:
$ flux reconcile kustomization flux-system --with-source --kubeconfig /etc/rancher/k3s/k3s.yaml -
Helm chart 将配置一个
LoadBalancer服务类型。要查找配置的 IP 地址,请运行以下命令:$ IP_LOADBALANCER=$(kubectl get svc app-demo-cluster$CLUSTER_N-srv --output jsonpath='{.status.loadBalancer.ingress[0].ip}' -n production)
在这里,IP_LOADBALANCER 变量包含由 HelmRelease 定义创建的负载均衡器的 IP 地址,这是你在该集群中应用程序的端点。你可以通过运行以下命令来检查其值:
$ echo $IP_LOADBALANCER
以图 7.4为参考,你应该会看到一个类似 192.168.0.52 的 IP 地址。
- 假设返回的 IP 是
192.168.0.52,你可以通过以下 URL 访问你的应用程序:http://192.168.0.52:5000。你可以测试访问其他路由,例如/_version或/_health。
现在你已经安装了 Flux,可以开始测试自动协调,通过提交和推送 HelmRelease 文件的更改来更新你的文件。自动协调会更新 Flux 检测到的所有应用程序的新镜像标签。此过程在前一节中已有描述,使用 Flux 为边缘应用程序设计 GitOps。之后,你可以继续下一节,学习如何排查安装问题。
排查 Flux 安装问题
有一些有用的命令可以帮助你排查安装问题;在本节中,我们将了解这些命令。接下来,让我们按照以下步骤进行:
-
要协调 Flux 中的变更,请运行以下命令:
$ watch flux get images all --all-namespaces --kubeconfig /etc/rancher/k3s/k3s.yaml
该命令将显示为你的容器检测到的新标签,以及这些新标签是如何在 HelmRelease YAML 定义文件中设置的。
-
要检查 Flux 中的镜像仓库,请运行以下命令:
$ flux get image repository app-demo --kubeconfig /etc/rancher/k3s/k3s.yaml --namespace=production -
要检查集群中当前的策略,请运行以下命令:
$ flux get image policy app-demo --kubeconfig /etc/rancher/k3s/k3s.yaml --namespace=production -
要获取在 Flux 安装中配置的所有镜像,请运行以下命令:
$ flux get images all --all-namespaces --kubeconfig /etc/rancher/k3s/k3s.yaml -
要协调集群中 YAML 定义的更改,请运行以下命令:
$ flux reconcile kustomization flux-system --with-source --kubeconfig /etc/rancher/k3s/k3s.yaml -
要实时查看镜像检测以及如何更新你的仓库,请运行以下命令:
$ watch flux get images all --all-namespaces --kubeconfig /etc/rancher/k3s/k3s.yaml -
要检查你的应用程序部署情况,请运行以下命令:
$ kubectl get deploy -n production -
要检查你的 Pods,请运行以下命令:
$ kubectl get pods -n production
现在你已经掌握了这些关键命令,用于排查你的 Flux 系统问题。
在下一节中,我们将探索 Flux 监控仪表板。
安装 Flux 监控仪表板
Flux 本身不包含用于管理的图形用户界面,但它集成了一些有用的仪表板,利用 Prometheus 和 Grafana 来可视化你的部署状态。这些仪表板需要在每个集群上安装。要安装此功能,请按照以下步骤进行:
-
配置包含监控堆栈定义的 Git 仓库进行安装。该配置将每 30 分钟监听一次更改。代码如下所示:
$ flux create source git monitoring \ --interval=30m \ --kubeconfig /etc/rancher/k3s/k3s.yaml \ --url=https://github.com/fluxcd/flux2 \ --branch=main -
安装
kube-prometheus-stack,它将用于为你的仪表板配置 Prometheus。这个堆栈将安装在monitoring命名空间中。代码如下所示:$ flux create kustomization monitoring-stack \ --interval=1h \ --kubeconfig /etc/rancher/k3s/k3s.yaml \ --prune=true \ --source=monitoring \ --path="./manifests/monitoring/kube-prometheus-stack" \ --health-check="Deployment/kube-prometheus-stack-operator.monitoring" \ --health-check="Deployment/kube-prometheus-stack-grafana.monitoring" \ --health-check-timeout="5m0s" -
安装 Grafana 并配置你的 Flux 仪表板,将数据存储在 Prometheus 中,并在 Grafana 中通过预配置的仪表板进行可视化。代码如下所示:
$ flux create kustomization monitoring-config \ --interval=1h \ --kubeconfig /etc/rancher/k3s/k3s.yaml \ --prune=true \ --source=monitoring \ --path="./manifests/monitoring/monitoring-config" -
使用下列命令访问仪表板:
$ kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana --address 0.0.0.0 3000:80
这将打开你的仪表板的3000端口。记住,你需要访问的 IP 地址是你访问该仪表板的节点的 IP 地址。
使用以下 URL 访问仪表板:http://<NODE_IP_ADDRESS>:3000/d/flux-control-plane。
NODE_IP_ADDRESS 是你运行此步骤中命令的集群节点的 IP 地址。
-
要访问仪表板,请使用以下凭据:
-
用户名:
admin -
密码:
prom-operator
-
登录页面将如下所示:

图 7.11 – Grafana 登录表单
登录后,你将被重定向到之前提到的仪表板 URL。
- 一旦仪表板打开,它将显示如下:

图 7.12 – Grafana Flux 控制平面仪表板
重要说明
记住,你可以自定义此仪表板并创建自己的仪表板。有关此仪表板的更多信息,请访问以下链接:fluxcd.io/docs/guides/monitoring。
你的 Grafana 仪表板现在已成功安装,你可以查看部署的状态。现在,是时候在下一部分学习如何卸载 Flux 了。
卸载 Flux
一旦你不再需要 Flux 安装,可以运行以下命令:
$ flux uninstall -s --namespace=flux-system --kubeconfig /etc/rancher/k3s/k3s.yaml
这将从你的 Kubernetes 集群中卸载 Flux。现在,是时候结束本章了。
总结
在本章中,我们学习了 GitOps 的工作原理,以及如何使用 GitHub Actions 和 Flux 实现 GitOps。Flux 可以帮助你自动化边缘环境中的部署,使用单一的 Git 仓库。为此,我们学习了 Flux 如何使用 HelmRelease 对象和镜像更新功能,在边缘更新应用程序,进而实现 GitOps。Flux 可以在不使用外部方式暴露 Kubernetes 集群 API 的情况下管理你的应用集群,这与 Argo CD 等工具的做法不同。这样可以节省成本,并为使用 ARM 设备的边缘环境提供更有效的工具。另一方面,Argo CD 不支持 ARM,需要通过公共负载均衡器或互联网上的虚拟机暴露 Kubernetes API,才能将集群连接到 Argo CD。在下一章中,我们将学习如何使用 Linkerd 为你的应用程序添加基本的可观察性和流量分配。
问题
这里有一些问题来验证你新学到的知识:
-
GitHub Actions 如何帮助我在边缘实现 GitOps?
-
我如何使用 Flux 实现 GitOps?
-
Flux 还有哪些其他功能可以帮助我实现 GitOps?
-
如何使用 Flux 排查我的 Helm 发布问题?
-
如何将这个简单的用例应用到我的边缘计算场景中?
-
我如何为 GitOps 构建我的仓库结构?
进一步阅读
你可以参考以下文献,获取本章所涉及主题的更多信息:
-
什么是 GitOps?:
www.gitops.tech -
开发领导者比较持续交付、持续部署与持续集成:
stackify.com/continuous-delivery-vs-continuous-deployment-vs-continuous-integration -
GitHub 快速入门:
docs.github.com/en/get-started/quickstart -
使用 GitHub Actions 构建容器镜像:
github.com/docker/build-push-action -
Docker Hub:
hub.docker.com -
在 GitHub Actions 上创建密钥:
docs.github.com/en/actions/security-guides/encrypted-secrets -
日期和时间互联网标准:
datatracker.ietf.org/doc/html/rfc3339 -
使用 GitHub Pages 创建公共 Helm Chart:
medium.com/@mattiaperi/create-a-public-helm-chart-repository-with-github-pages-49b180dbb417 -
创建 Helm Chart 仓库:
harness.io/blog/helm-chart-repo -
如何构建你的 Flux 仓库:
fluxcd.io/docs/guides/repository-structure -
Flux 文档:
fluxcd.io/docs -
Flux Helm 发布:
fluxcd.io/docs/guides/helmreleases -
Flux、Kustomize 和 Helm 示例:
github.com/fluxcd/flux2-kustomize-helm-example
第八章:使用 Linkerd 进行可观察性和流量分流
在使用容器开发微服务或应用程序时,可观察性非常重要,因为它提供了对复杂系统的洞察。监控机制、分析和可观察性可以让你了解应用程序在生产环境中作为一个系统如何运行。在生产环境中,可观察性提供日志、指标和追踪,以便了解服务如何相互交互以提供功能。服务网格通常用于在服务中实现可观察性。服务网格是一种强大的工具,它帮助你实现可观察性及其他功能,如重试或超时管理,而无需修改应用程序。本章讨论了黄金指标,即用于理解系统的常用指标,如何使用 Linkerd 为带有入口控制器的应用程序实现可观察性,以及如何使用示例应用程序实现流量路由。
在本章中,我们将涵盖以下主要内容:
-
可观察性、监控和分析
-
服务网格和 Linkerd 简介
-
使用 Linkerd 实现可观察性和流量分流
-
使用 Linkerd 测试可观察性和流量分流
-
卸载 Linkerd
-
使用服务网格实现的思路
技术要求
在本章中,要实现 Linkerd 的可观察性,你需要以下内容:
-
使用 ARM 设备的单节点或多节点 K3s 集群,安装了 MetalLB,并且可以选择避免将 Traefik 作为默认的入口控制器进行安装。
-
已在本地机器上配置的 Kubectl,以避免使用
--kubeconfig参数。 -
安装 Helm 命令。
-
如果你想通过使用
kubectl apply来运行 YAML 配置,而不是从书中复制代码,请克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch8中的代码仓库。查看ch8目录中的yaml目录,获取 YAML 示例。
我们将安装 Linkerd,以便在此集群上实现可观察性和流量分流。那么,让我们从基础理论开始,理解可观察性的好处以及如何实现它。
可观察性、监控和分析
首先,让我们熟悉可观察性这一概念。Peter Waterhouse 在他的The New Stack文章中提到,“可观察性是衡量如何通过了解系统的外部输出,推断系统内部状态的能力。”他还提到,可观察性更像是系统的一个属性,而不是你实际做的事情。
在这个背景下,有两个概念是密切相关的:监控和可观察性。在 Steve Waterworth 的文章中(可在dzone.com查看),他通过一句话提到这种关系,“如果你是可观察的,我就能监控你。”
这意味着,当系统数据得到管理时,就实现了可观察性。另一方面,监控是收集和显示这些数据的实际任务。最后,分析发生在使用监控工具收集数据之后,且可以手动或自动进行。
这种关系通过权力金字塔表示:

图 8.1 – 权力金字塔
权力金字塔表示了分析和监控是实现可观察性的基础。它们共同作用,能提供了解系统状态的能力;这就是我们所说的可观察性。服务网格通过度量反映系统状态的指标来赋予系统可观察性。这些指标被称为黄金指标。让我们在接下来的部分中探讨黄金指标。
黄金指标
黄金指标首次出现在谷歌的《站点可靠性工程》一书中,并被定义为监控服务所需的最小指标。这也是权力金字塔在监控和可观察性讨论中占据一席之地的原因。这些指标还被定义为一种模型,作为围绕应用程序构建监控的基础。
根据 Linkerd 服务网格词汇网页,黄金指标也叫黄金信号;这些是应用健康的核心指标。这些指标是基于延迟、流量量、错误率和饱和度来定义的。通过这些指标,您可以判断应用程序的健康状况,最终在您的应用程序和系统中建立可观察性的特性。黄金指标是监控服务和构建可观察系统的基础。
让我们在接下来的部分中探讨,服务网格是如何实现这些黄金指标,以便为您的系统带来可观察性。
服务网格简介与 Linkerd
George Mirando 在他的书《服务网格》中说,服务网格“是一个专门的基础设施层,用于处理服务间通信,以便使其可见、可管理和可控制。它的架构的具体细节在不同的实现中有所不同,但一般来说,每个服务网格都实现为一系列互联的网络代理,旨在更好地管理服务流量。”一般来说,我们可以采纳这样的观点,即服务网格是通过这些互联的网络代理构建的,提供可管理、稳定和受控的服务间通信。
现在,让我们看看如何实现这一点,首先从下面的图示开始解释:

图 8.2 – 带侧车容器的服务网格实现
Sidecar 是一种在仅有单个节点的分布式系统中使用的设计模式。在 Kubernetes 中部署使用多个容器的应用程序时,通常会使用这种模式。在这个上下文中,sidecar 模式由两个容器组成;第一个容器包含应用程序容器(即核心容器),第二个 sidecar 容器是一个代理,为您的应用程序提供可靠网络功能,且这两个容器都位于一个 Pod 内(Pod 是 Kubernetes 中对一组容器的抽象)。这个 Pod 位于数据平面内,数据平面包含所有通过代理互联的服务。为了说明这一点,让我们看看以下图示:

图 8.3 – 服务网格控制平面与数据平面
这些代理向控制平面询问如何处理传入的流量,例如,阻止或加密流量。控制平面还会评估并决定在代理中执行的纠正措施,例如在超时发生时进行重试或重定向。控制平面包含应用于每个服务的规则,这些规则会贯穿整个网格。收集数据以提供黄金指标使得服务具有可观察性。一些服务网格还提供一个基本的 UI 来管理所有这些服务网格功能。
服务网格的需求存在是因为对分布式系统的错误假设,例如以下内容:
-
网络可靠。
-
延迟为零。
-
带宽是无限的。
-
网络是安全的。
-
拓扑结构不变。
-
只有一个管理员。
-
传输成本为零。
-
网络是同质的。
服务网格的存在是为了应对所有关于分布式系统的错误假设,帮助管理应用程序代码中的逻辑,并为应用程序创建可靠的网络。一般来说,服务网格通过仅仅注入代理作为 sidecar,而不修改应用程序代码,来提供这种可靠性。
最后,服务网格与可观察性之间的关系在于,当代理拦截网络流量时,这些代理可以生成黄金指标,提供一个图形化仪表盘来可视化您的应用程序状态;换句话说,创建系统的可观察性属性。
Linkerd 服务网格
Linkerd 是一个设计用于在 Kubernetes 上运行的服务。它为部署在 Kubernetes 上的应用程序提供调试、可观察性、可靠性和安全性,而无需修改应用程序的源代码。因此,Linkerd 不仅提供可观察性,还提供更多功能,诸如:
-
HTTP、HTTP/2 和 gRPC 代理
-
重试和超时
-
遥测和监控
-
负载均衡
-
授权策略
-
自动代理注入
-
分布式追踪
-
故障注入
-
流量拆分
-
服务配置文件
-
多集群通信
Linkerd 也是一款完全开源的软件,属于 Cloud Native Computing Foundation(CNCF)的毕业项目之一。Linkerd 由 Buoyant 开发。
正如我们在服务网格介绍中探讨的那样,Linkerd 通过数据平面和控制平面工作,并且具有 Linkerd CLI 来管理其安装。它还配有一个 UI,用于查看展示注入服务的黄金指标的各种图形。
为了使用 Linkerd,首先,你需要通过 Linkerd CLI 将你的应用程序注入 Linkerd 代理中,然后 Linkerd 就可以开始收集指标并使你的应用程序能够通过数据平面与其他注入的服务进行通信;当然,Linkerd 还将准备好使用所有功能来配置你的应用程序,例如流量分配。
Linkerd 设计之初便注重速度和低资源消耗,相比于其他服务网格(如 Istio),其易用性更强。Istio 包括一整套工具,不仅实现了服务网格功能,还提供了追踪和入口控制器功能,这对于一些解决方案来说可能过于复杂。而 Linkerd 简化了复杂性,旨在作为一个模块化的服务网格软件,可以与当前的技术解决方案栈集成,为系统添加可观测性层。Linkerd 满足边缘计算需求,支持 ARM 架构、低资源消耗,并且易于使用。这样,在考虑基于 Envoy 的其他解决方案(如 Istio)之前,Linkerd 可能是一个值得关注的选项。
需要特别提到的是,由于服务网格通过代理工作,一些入口控制器或云原生代理在选择完整的服务网格解决方案(如 Traefik、Emissary 和 Contour)之前,可能会更符合你的需求。在选择服务网格或云原生代理时,一些重要的考虑因素包括安全性和速率限制实现。你可以在 进一步阅读 部分查找一些对比这些解决方案的文章。但现在,是时候理解如何在接下来的章节中实现可观测性和流量分配了。
使用 Linkerd 实现可观测性和流量分配
为了说明我们如何使用 Linkerd 实现可观测性和流量分配,让我们来看看以下的图示:

图 8.4 – 使用 Linkerd 进行流量分配
首先,你需要在 Kubernetes 集群中安装 Linkerd。对于这个小场景,我们将使用两个部署。第一个部署是一个简单的 API 部署,它返回消息 Meshed application app1 with Linkerd,第二个部署始终返回错误代码 500。
所有流量将由客户端(在我们这个例子中,是一个向应用程序端点发送请求的循环)发送,客户端是由您的 ingress 控制器服务创建的负载均衡器,并由 ingress 定义使用。每次 ingress 对象检测到流量时,流量将被 50% 分配到 API 部署,50% 分配到故障部署。这将模拟 50% 请求出错和 50% 流量没有错误的错误率。
必须注入 ingress、应用程序和模拟错误的故障部署。这样,这些服务将使用注入在每个部署中的 Linkerd 代理进行相互通信。
当流量在各服务之间传输时,它会生成 Linkerd 仪表板可以通过 Grafana 和 Linkerd 在其 UI 中实现的其他报告来可视化的黄金指标。
现在,我们已经准备好在下一节中开始安装 Linkerd。
在集群中安装 Linkerd
那么,让我们从在集群中安装 Linkerd 开始。为此,您需要按照以下步骤进行:
-
首先,通过运行以下命令安装 Linkerd CLI:
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
如果您使用的是 macOS,可以通过 brew 命令安装 Linkerd CLI:
$ brew install linkerd
-
将 Linkerd 安装目录添加到您的路径中:
$ echo "export PATH=\$PATH:/home/ubuntu/.linkerd2/bin" >> ~/.bashrc
运行以下命令加载新路径,而不是重新登录以加载新路径:
$ source ~/.bashrc
-
要检查集群是否符合安装 Linkerd 的要求,请运行以下命令:
$ linkerd check --pre -
接下来,运行以下命令安装 Linkerd:
$ linkerd install | kubectl apply -f - -
现在,通过运行以下命令安装 Linkerd 仪表板:
$ linkerd viz install | kubectl apply -f -
此命令将在安装 Linkerd 后,等待 Linkerd 仪表板的安装。
-
要检查安装是否成功,请运行以下命令:
$ linkerd check -
一旦一切运行起来,要打开 Linkerd 仪表板,请运行以下命令:
$ linkerd viz dashboard --address 0.0.0.0
上述命令将会在您的设备中暴露 Linkerd 仪表板。要运行此命令,我们假设该命令是在设备内部运行的,因此您需要运行以下命令来解析 URL http://web.linkerd-viz.svc.cluster.local:50750,并将其指向您的设备:
$ IP_CLUSTER=<YOUR_IP_CLUSTER>
$ sudo echo $IP_CLUSTER" WEB.linkerd-viz.svc.cluster.local" >> /etc/hosts
IP_CLUSTER 是您的集群的 IP 地址。
现在,访问以下 URL 打开仪表板:http://web.linkerd-viz.svc.cluster.local:50750。
现在,是时候安装 NGINX ingress 控制器,以便在此实现中使用。我们将在下一节中详细介绍。
安装并注入 NGINX ingress 控制器
在这种情况下,我们将使用 NGINX ingress 控制器,通过 Helm 安装它,按照给定的步骤进行:
-
创建
nginx-ingress命名空间:$ kubectl create ns nginx-ingress -
添加 NGINX ingress 控制器 Helm 图表并更新 Helm 中配置的仓库:
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx $ helm repo update -
安装 NGINX ingress 控制器:
$ helm install nginx-ingress ingress-nginx/ingress-nginx -n nginx-ingress -
现在,要注入 NGINX ingress 控制器 Pod,请运行以下命令:
$ kubectl get -n nginx-ingress deploy nginx-ingress-ingress-nginx-controller -o yaml \ | linkerd inject - \ | kubectl apply -f -
您的 ingress 控制器现在已经准备好安装并注入。让我们在下一节中创建所需的应用程序。
创建演示应用程序和故障 Pod
现在,让我们创建我们的示例应用程序和故障 Pod,以实验流量分流功能,并获取一些故障流量来模拟错误请求。为此,请按照以下步骤操作:
-
为你的 Pods 创建
myapps命名空间:$ kubectl create ns myapps -
通过运行以下命令创建示例应用程序
app1:$ cat <<EOF | linkerd inject - | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: app1 name: app1 namespace: myapps spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - image: czdev/app1demo name: app1demo env: - name: MESSAGE value: "Meshed application app1 with Linkerd" - name: PORT value: "5000" EOF
重要提示
linkerd inject 命令将在部署或 Pod 的 annotations 部分插入 linkerd.io/inject: enabled 标签。Linkerd 使用此标签将服务与 Linkerd 代理进行注入。你也可以在 YAML 定义中手动添加此标签,以便更好地使用声明式定义来管理你的 Pods 和部署。要自定义 app1demo 的代码,请查看链接 github.com/sergioarmgpl/containers/tree/main/app1demo。
-
为了创建我们故障的 Pod,我们将使用 NGINX 作为 Web 服务器,并通过自定义配置返回带有
500代码错误的请求,以便 Linkerd 检测并将该请求计为错误。为此,让我们运行以下命令创建配置:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: error-injector namespace: myapps data: nginx.conf: |- events {} http { server { listen 5000; location / { return 500; } } } EOF -
现在,让我们创建一个在访问 Pod 的
/路径时返回500错误的部署,并监听5000端口:$ cat <<EOF | linkerd inject - | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: error-injector namespace: myapps labels: app: error-injector spec: selector: matchLabels: app: error-injector replicas: 1 template: metadata: labels: app: error-injector spec: containers: - name: nginx image: nginx:alpine volumeMounts: - name: nginx-config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf volumes: - name: nginx-config configMap: name: error-injector EOF -
现在我们的应用程序已经部署完成,让我们为这些应用程序配置服务。我们从
error-injector服务开始:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: error-injector namespace: myapps spec: ports: - name: service port: 5000 selector: app: error-injector EOF -
现在,通过运行以下命令为你的应用程序创建服务:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: app1 namespace: myapps spec: ports: - name: service port: 5000 selector: app: app1 EOF -
现在,让我们使用
app1服务和另一个error-injector服务的一半流量,因此我们期望成功率为 50%:$ cat <<EOF | kubectl apply -f - apiVersion: split.smi-spec.io/v1alpha1 kind: TrafficSplit metadata: name: error-split namespace: myapps spec: service: app1 backends: - service: app1 weight: 500m - service: error-injector weight: 500m EOF -
最后,让我们创建 ingress 规则,使用流量分流将流量发送到此应用程序:
$ cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress namespace: myapps annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/service-upstream: "true" spec: ingressClassName: nginx rules: - http: paths: - path: / pathType: Prefix backend: service: name: app1 port: number: 5000 EOF
重要提示
根据你使用的 Kubernetes 版本,你需要使用适合你 ingress 控制器定义的语法,v1beta1 或 v1。更多信息,请查看 kubernetes.io/docs/concepts/services-networking/ingress,并根据不同的 Kubernetes 版本进行更改。
现在,我们已经准备好测试使用 Linkerd 配置的可观察性和流量分流。让我们在下一部分探索这一点。
使用 Linkerd 测试可观察性和流量分流
现在,是时候测试可观察性了。要开始探索仪表盘并查看可观察性,请按照以下步骤进行:
-
通过运行以下命令打开仪表盘:
$ linkerd viz dashboard
这将自动打开仪表盘,URL 为 http://localhost:50750。
仪表盘将显示如下截图:

图 8.5 – Linkerd 仪表盘
为了加载正确的信息,在左侧边栏的组合框中选择 MYAPPS 命名空间,然后点击 Deployments 图标以加载 HTTP Metrics 和 TCP Metrics 信息。
要查看与之前仪表盘类似的信息,请执行以下命令以开始将流量发送到我们的部署:
$ ENDPOINT=$(kubectl get svc nginx-ingress-ingress-nginx-controller --output jsonpath='{.status.loadBalancer.ingress[0].ip}' -n nginx-ingress)
$ while true; do curl http://$ENDPOINT;echo " "; done
第一个命令将负载均衡器的 IP 地址分配给您的 NGINX Ingress 控制器,它作为端点来暴露使用 Ingress 定义的服务。然后,在命令发送流量时,它还会显示每个请求的结果,显示类似以下的消息:
Host:app1-555485df49-rjf4vMeshed application app1 with Linkerd
或者,显示以下输出错误:
<html>
<head><title>500 Internal Server Error</title></head>
<body>
<center><h1>500 Internal Server Error</h1></center>
<hr><center>nginx/1.21.6</center>
</body>
</html>
这是一个消息的频率为 50% 和错误的频率为 50%,平均而言。
- 如果您点击橙色的 Grafana 图标(例如,在 HTTP 指标 部分),您将看到类似以下的 Grafana 图表:

图 8.6 – Grafana Linkerd HTTP 指标图
在这个图表中,您可以看到应用程序 app1 部署的黄金指标和成功率,每秒请求数(RPS)以及每个请求的延迟;这些指标代表了您的应用程序的黄金指标,为您的系统和应用程序提供了基本的可观察性功能。
- 如果在选择 myapps 命名空间时点击 流量拆分,您将看到类似这样的流量拆分表示:

图 8.7 – Linkerd 流量拆分仪表盘
在此仪表盘中,您将实时看到流量拆分配置如何将 50% 的流量发送到 app1 Kubernetes 服务,并将 50% 的流量发送到错误注入器。红色代表失败的请求(返回 500 请求错误代码的请求),而绿色代表来自 app1 服务的有效流量,返回 200 请求代码。这通常会为您提供应用程序的实时状态,这是实现可观察性目标的一部分。
这个基本实现模拟了使用服务网格的应用程序故障请求。您还可以使用相同的实现将流量在应用程序之间拆分,或实施诸如蓝绿部署之类的高级部署策略。这是一个简单的用例,旨在在您的应用程序中实现可观察性,并展示服务网格的流量管理功能。现在,让我们来看看一些有用的命令,如果您希望使用 CLI 来使用 Linkerd。
使用 Linkerd 的 CLI
在某些情况下,如果没有 UI 可用(可能出于安全原因),使用 Linkerd CLI 可能会很有用。因此,下面我们将探讨四个基本的命令行选项:routes、top、tap 和 edges:
-
routes显示当前其他应用程序或客户端用于访问您的应用程序的路由。以我们之前的场景为例,您可以使用以下命令显示myapps命名空间中app1的路由:$ linkerd viz routes deployment/app1 --namespace myapps -
top显示你的应用程序的流量和路径。以下命令将显示入口控制器如何将流量转发到你的应用程序,显示访问/路径的计数器,并显示请求的成功率:$ linkerd viz top deployment/app1 --namespace myapps -
tap实时显示app1请求的信息;为此,你需要运行以下命令:$ linkerd viz tap deployment/app1 --namespace myapps -
edges显示一个表格,展示你的应用程序如何与集群中其他注入的应用程序连接,以及每个连接的源和目的地。为此,你需要运行以下命令来查看app1:$ linkerd viz edges po -n myapps
通过这个,你大致了解了如何使用 CLI 来操作 Linkerd。现在,让我们进入下一节,学习如何卸载 Linkerd。
卸载 Linkerd
如果你正在评估 Linkerd 或在集群中进行一些管理工作,例如,卸载 Linkerd 可能会很有用。为此,请按照下列步骤操作:
-
卸载 Linkerd 的附加功能支持(称为viz)如下:
$ linkerd viz uninstall | kubectl delete -f - -
卸载 Linkerd 控制平面。这将卸载其余的核心 Linkerd 组件。为此,运行以下命令:
$ linkerd uninstall | kubectl delete -f -
现在,Linkerd 已从你的集群中卸载。为了结束这一章,让我们进入最后一节,探讨一些有关你可以使用 Linkerd 的有用想法。
使用服务网格时可以实施的想法
为了结束这一章,以下是一些关于如何在边缘使用服务网格的优势的想法。这些想法不仅限于边缘,也可以应用于常规基础设施:
-
实施速率限制:你可以使用服务网格在你的应用程序中配置速率限制,从而管理接受多少输入流量。实现这一功能有一些很棒的项目,包括 Linkerd 和基于 Envoy 的服务网格,如 Istio 和 Ambassador。
-
流量拆分:你可以使用服务网格的这一功能来实现蓝绿部署和金丝雀部署;例如,Argo Rollouts 的实现可以使用 Linkerd 来执行这种部署策略。你还可以使用服务网格进行一些混沌工程测试。
-
安全策略:你可以使用服务网格来限制流量并加密端到端流量。这对于增强服务的安全性可能非常有用。
-
多集群连接:使用服务网格,你可以在无需复杂配置的情况下连接多个集群。Kuma 是一个面向微服务和服务网格的控制平面,能够帮助你连接多个集群,它建立在 Envoy 之上。你也可以使用 Linkerd 和其他基于 Envoy 的服务网格来实现同样的功能。
-
基于网络的扩展:你可以使用服务网格生成的 Prometheus 指标来生成警报或扩展你的服务。你还可以实现机器学习模型来进行一些智能扩展。你可以将它们与像 基于 Kubernetes 的事件驱动自动扩展(KEDA)这样的项目结合使用,KEDA 从 API 中读取信息来扩展你的服务。
这些是你在使用服务网格时可以探索的一些思路。现在,是时候结束本章了。
总结
在本章中,我们学习了如何实现可观察性,以及如何使用服务网格来设置流量拆分。我们专注于使用 Linkerd 实现这一场景,运行一个显示消息的示例应用程序,并使用流量拆分。当应用程序接收到流量时,我们展示了如何探索可以用来获取系统实时状态的不同图形。我们还学习了如何在未安装 CLI 的情况下使用 Linkerd。本章以一些实施思路作为结束,探讨了使用服务网格时可以探索的内容以及这将如何影响你的系统。所有这些内容构成了使用 Linkerd 服务网格在系统中实现可观察性和基本流量拆分的基础。在下一章中,我们将学习如何使用 Knative 实现无服务器函数和简单的事件驱动管道。
问题
这里有几个问题可以验证你新获得的知识:
-
服务网格如何帮助你实现可观察性?
-
服务网格为系统提供了哪些功能?
-
服务网格是如何在内部工作的?
-
Linkerd 为实施可观察性的用户提供了什么?
-
Linkerd 如何与其他服务网格进行比较?
-
服务网格的常见使用案例有哪些?
深入阅读
你可以参考以下资源,获取本章所涵盖主题的更多信息:
-
设计分布式系统 书籍,Brendan Burns 编著:
learning.oreilly.com/library/view/designing-distributed-systems/9781491983638 -
黄金信号 - 从基本原理监控:
www.squadcast.com/blog/golden-signals-monitoring-from-first-principles -
gRPC 官方网站:
grpc.io -
服务网格接口:
smi-spec.io -
Linkerd 术语表和有用的术语:
linkerd.io/service-mesh-glossary -
服务网格快速入门与比较:
servicemesh.es -
可观察性 vs. 监控:
dzone.com/articles/observability-vs-monitoring -
监控与可观察性——它们的区别是什么,为什么重要?:
thenewstack.io/monitoring-and-observability-whats-the-difference-and-why-does-it-matter -
云原生应用中的 API 健康与性能的 4 个黄金信号:
blog.netsil.com/the-4-golden-signals-of-api-health-and-performance-in-cloud-native-applications-a6e87526e74 -
Linkerd 文档:
linkerd.io/docs -
服务网格 与 边缘计算的考虑因素:
sunkur.medium.com/service-mesh-edge-computing-considerations-84126754d17a
第九章:使用 Knative 和 Cloud Events 实现边缘无服务器和事件驱动架构
无服务器架构降低了大规模运行分布式系统的成本。这个使用案例在边缘计算中尤为有用,因为边缘计算需要大量专用硬件和计算资源。本章将介绍如何使用 Knative 实现基于无服务器技术的 API。还将展示如何使用 Knative 简化事件驱动架构和无服务器函数来降低成本和复杂性,帮助构建系统。在本章中,我们将解释 Knative 如何使用 Cloud Events 来调用事件,并且阐述无服务器架构如何帮助开发事件驱动的应用程序。
本章将涵盖以下主要内容:
-
使用 Knative 和 Cloud Events 实现边缘计算中的无服务器架构
-
使用 Knative Serving 实现无服务器函数
-
使用 Knative 进行流量切分实现无服务器 API
-
在 Knative 中使用声明式文件
-
使用 Knative Eventing 中的序列实现事件和事件驱动的流水线
技术要求
本章需要以下内容:
-
一个使用 ARM 设备的单节点或多节点 K3s 集群,并安装了 MetalLB,且避免将 Traefik 安装为默认的入口控制器。
-
配置好 kubectl 以便在本地机器上使用,避免使用
--kubeconfig参数。 -
如果你想通过
kubectl apply运行 YAML 配置而不是从书中复制代码,请克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch9中的代码。查看ch9目录中的 Python 和 YAML 配置代码。
我们将安装 Knative 来实现简单的无服务器 API 和事件驱动流水线的使用案例。让我们理解无服务器架构是什么,以及它们如何在边缘计算环境中提供帮助。
使用 Knative 和 Cloud Events 实现边缘计算中的无服务器架构
边缘计算是一种在数据源附近处理信息的范式。这可以提高应用程序的响应时间。当数据被访问时,也能节省带宽,因为数据不再从云端获取,而是直接从源头附近访问。但其中一个问题是,服务总是保持运行状态。无服务器架构可以帮助减少成本,在服务不使用时将其缩减规模,从而减少与传统始终运行服务方式相比的额外成本。
Ben Ellerby 在他的一篇 Medium 文章《为什么无服务器将促进边缘计算革命》中提到,无服务器让我们能够构建应用程序和服务,而不必考虑底层的服务器。这意味着更多地关注应用程序本身,而不是基础设施的管理。因此,近年来无服务器技术和云服务的普及度不断增加。无服务器云服务只会在您使用服务时收取执行时间的费用。您通常可以找到作为小型代码函数的无服务器服务。无服务器技术使得事件驱动架构得以蓬勃发展,因为它们实现新功能的简单性和低成本。根据 solace.com/ 网站,事件驱动架构是一种软件设计模式,在该模式下,解耦的应用程序可以通过事件代理(现代消息导向中间件)异步地发布和订阅事件。
在构建新系统时,评估的关键因素之一是实现成本。这将在选择无服务器技术时成为常见场景。在本地环境中实现的无服务器技术可以利用资源的临时使用来执行无服务器函数。Knative 实现了无服务器函数和事件,可用于实现事件驱动的应用程序。此外,像 Cloud Events 这样的事件规范可以帮助标准化服务的通信并定义事件:

图 9.1 – Knative 架构
Knative 诞生于 Google,并作为开源项目交给了社区。Knative 由两部分组成:Serving 和 Eventing。通过 Knative Serving,您可以在 Kubernetes 中创建无服务器函数。Knative Serving 实现了网络、自动扩展和版本跟踪等功能。该抽象使用户能够更多地专注于业务逻辑,而不是基础设施管理。另一方面,Knative Eventing 使用户能够实现事件驱动架构,并调用通过 Serving 功能创建的函数。您可以根据用例配置事件,使用不同的源和代理类型来管理事件。在选择适合您场景的源和代理后,您可以触发函数的顺序或简单调用。
Cloud Events 与 Knative 一起工作,为事件提供标准结构,并有统一的方式声明和调用事件。Cloud Events 遵循用于实现事件的事件规范。这个结构已经被多个开源项目采用,如 OpenFaaS、Tekton、Argo Events、Falco、Google Cloud Eventarc 等等。Cloud Events SDK 支持多种编程语言,如 Python 和 Go。这个 SDK 将帮助你通过定义 ID、云事件规范的版本、类型、源和内容类型等来描述云事件。
Knative 和 Cloud Events 提供了一种在边缘、低资源设备上实现无服务器功能和事件驱动架构的方法,并且提供了一种轻量级的实现,可以在边缘计算场景中节省成本。
重要提示
获取更多关于 Knative 的信息,你可以访问其官方文档:knative.dev/docs。关于 Cloud Events,你可以访问其官方网站:cloudevents.io,或者查看其规范 1.0,这是我们示例中使用的版本:github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md。
使用 Knative Serving 实现无服务器功能
为了开始构建我们的简单无服务器和事件驱动用例,我们需要安装 Knative,包括 Serving、Eventing、频道和代理。在这种情况下,我们将使用基本选项,使用内存中的频道和 Knative Eventing Sugar Controller,该控制器会根据集群或命名空间中的标签创建 Knative 资源。所以,让我们在下一节开始安装 Knative Serving。
安装 Knative Serving
在本节中,我们将开始安装 Knative Serving,后续将用于实现无服务器功能。让我们按照以下步骤安装 Knative Serving:
-
使用以下命令安装 Knative CLI:
$ brew install kn
要升级当前的 Knative 二进制文件,请运行以下命令:
$ brew upgrade kn
-
安装 Knative Serving CRD,以安装 Serving 组件:
$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-crds.yaml $ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-core.yaml
提示
要了解更多关于自定义资源定义(CRDs)的信息,你可以查看这个链接:docs.openshift.com/aro/3/dev_guide/creating_crd_objects.html。你也可以通过下一个链接查看 Kubernetes 官方网站上的 CRD 文档:kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources。
-
现在安装 Contour ingress 控制器,它将作为 Knative 的默认控制器使用(该组件支持 ARM):
$ kubectl apply -f https://github.com/knative/net-contour/releases/download/knative-v1.2.0/contour.yaml -
使用之前的 ingress 运行以下命令,安装 Knative 的网络组件以启用其他功能:
$ kubectl apply -f https://github.com/knative/net-contour/releases/download/knative-v1.2.0/net-contour.yaml -
然后将 Contour 设置为 Knative 使用的默认 ingress 控制器:
$ kubectl patch configmap/config-network \ --namespace knative-serving \ --type merge \ --patch '{"data":{"ingress-class":"contour.ingress.networking.knative.dev"}}' -
获取 Contour 入口控制器创建的 IP 作为您应用程序的端点。在此情况下,我们将把此 IP 称为
EXTERNAL_IP:$ EXTERNAL_IP="$(kubectl get svc envoy -n contour-external -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')" -
设置 Knative 用来暴露您的无服务器应用程序的域:
$ KNATIVE_DOMAIN="$EXTERNAL_IP.nip.io" $ kubectl patch configmap/config-domain \ --namespace knative-serving \ --type merge \ --patch '{"data":{"'$KNATIVE_DOMAIN'":""}}' -
现在设置 水平 Pod 自动扩展器(HPA)功能以运行 Knative Serving:
$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-hpa.yaml -
最后,进行 Knative 组件运行的简单故障排除:
$ kubectl get pods -n knative-serving
这将返回您 Knative Serving 安装中 pod 的状态。这些 pod 在几分钟后应该会处于就绪状态。
重要提示
若要卸载组件,您可以使用kubectl delete而不是kubectl apply。
现在 Knative Serving 已经安装并准备好使用了。那么,让我们继续在下一部分创建一个简单的无服务器函数,使用 Knative Serving。
创建一个简单的无服务器函数
现在是时候使用 Knative Serving 了。在本节中,我们将使用 Python 和 Flask 运行一个示例 API。代码将如下所示:
from flask import Flask
from flask import jsonify
import os
import socket
app = Flask(__name__)
host = socket.gethostname()
msg = os.environ['MESSAGE']
@app.route('/')
def index():
return jsonify({"host":host,"msg":msg})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
每次调用该函数时,它将返回带有容器 ID 的变量 host 和 msg,该 msg 为 MESSAGE 环境变量的值。此 API 将使用端口 5000。这个 Python 程序已经打包在容器中,并作为 sergioarmgpl/app2demo 在 Docker Hub 上构建并发布。
重要提示
您可以在 GitHub 仓库中探索如何构建和自定义此代码:github.com/sergioarmgpl/containers。
现在,为了通过 Knative 将这个 API 部署为无服务器函数,请按照以下步骤操作:
-
使用以下命令创建您的函数:
$ kn service create api \ --image sergioarmgpl/app2demo \ --port 5000 \ --env MESSAGE="Knative demo v1" \ --revision-name=v1
此命令将容器中暴露的端口 5000 重定向到 Knative 生成的 HTTP 端点。它还接收带有 Knative demo 值的 MESSAGE 参数,并将此函数的修订版设置为 v1。运行此命令后,您将获得如下输出:
Service 'api' created to latest revision 'api-v1' is available at URL:
http://api.default.192.168.0.54.nip.io
在输出的末尾,您将找到函数的端点。在这个输出中,我们假设分配给 Contour 入口控制器的 IP 地址是 192.168.0.54,这与分配给 EXTERNAL_IP 变量的值相同。Knative 会在默认命名空间中为此函数创建必要的 pod。有关如何获取分配给 Contour 入口的 IP,请参阅 安装 Knative Serving 部分。
-
现在,使用 安装 Knative Serving 部分中定义的
EXTERNAL_IP变量访问您的函数,运行以下命令:$ curl http://api.default.$EXTERNAL_IP.nip.io
该命令将在终端返回类似以下内容的 JSON 输出:
{
"host": "api-v1-deployment-84f568857d-cxv9z",
"msg": "Knative demo v1"
}
-
要监控为您的函数创建的 pod,请运行此命令:
$ watch kubectl get pods -
在您的函数不活跃 2 分钟后,为运行您的函数而创建的 pod 将被缩减。如果您执行
watch kubectl get pods,您将看到类似以下的输出:NAME READY STATUS api-v1 2/2 Running api-v1 2/2 Terminating api-v1 1/2 Terminating api-v1 0/2 Terminating -
打开另一个终端并执行
watch kubectl get pods,然后再次调用该函数。函数的 pods 将被扩展,你将看到类似以下的输出:NAME READY STATUS api-v1 0/2 Pending api-v1 0/2 ContainerCreating api-v1 1/2 Running api-v1 2/2 Running
通过零扩展功能,当你的函数在 2 分钟内没有活动时,你可以减少云基础设施的成本。
重要提示
watch命令可能未在你的操作系统上安装。你可以在 Linux 上使用yum或apt命令进行安装,或者在 macOS 上使用brew命令进行安装。
-
使用以下命令检查默认命名空间中创建的服务:
$ kn service list
或者,运行以下命令以检查特定命名空间中可用的函数:
$ kn service list -n <YOUR_NAMESPACE>
-
要检查当前的修订版,运行以下命令:
$ kn revisions list -
(可选) 如果你不想为你的函数创建公共端点,可以使用
--cluster-local标志来为kn命令创建私有端点。要创建具有私有端点的相同函数,请使用以下命令:$ kn service create api --cluster-local \ --image sergioarmgpl/app2demo \ --port 5000 \ --env MESSAGE="Knative demo v1" \ --revision-name=v1
在输出的末尾,你将看到类似以下内容:
Service 'api' created to latest revision 'api-v1' is available at URL:
http://api.default.svc.cluster.local
此端点将是 Knative 为你创建的 URL 服务,它与 Kubernetes 中使用的相同服务对象。
-
(可选) 要访问此端点,你必须在集群内调用它。为此,创建一个包含
curl的客户端容器。运行以下命令:$ kubectl run curl -it --rm --image=curlimages/curl:7.81.0 /bin/sh
一旦 pod 创建完成,你必须运行以下命令来访问该函数:
$ curl http://api.default.svc.cluster.local
输出将如下所示:
{
"host": "api-v1-deployment-776c896776-vxhhk",
"msg": "Knative demo v1"
}
-
要删除本节中创建的无服务器函数,运行以下命令:
$ kn service delete hello
现在,你已经知道如何使用 Knative Serving 创建无服务器函数来实现一个简单的 API,并使用零扩展功能节省成本。接下来是时候在下一节中使用 Knative Serving 实现流量拆分功能了。
使用 Knative 实现带有流量拆分的无服务器 API
Knative 具有流量拆分功能,可以将流量分配到服务中的两个或多个版本,但使用代理来实现此功能。默认情况下,它使用 Istio。对于此实现,我们使用 Contour,这是一个基于 Envoy 的代理,比 Istio 消耗更少的资源。Istio 和 Contour 都使用 Envoy,这是一个第 7 层代理,用于实现服务网格功能,如流量拆分。流量拆分可以用于实现部署策略,如金丝雀发布和蓝绿部署,也可以用于模拟故障流量,进行一些基本的混沌工程场景。在本节中,我们将为之前在创建简单的无服务器函数部分中创建的 API 函数实现流量拆分。在该部分中,我们创建了一个名为api的函数,并将修订名定为v1。现在,我们将用另一个名为v2的修订版来更新此函数。此修订版仅更改了调用该函数时显示的MESSAGE值。对于此示例,我们将按 50%的比例将流量拆分到v1修订版和v2修订版。
要实现此场景,请按照以下步骤进行操作:
-
使用新修订版本
v2更新当前的api函数,其中MESSAGE变量的值为Knative demo v2,本次运行为:$ kn service update api \ --env MESSAGE="Knative demo v2" \ --revision-name=v2
此命令的输出将如下所示:
Service hello created to latest revision 'api-v2' is available at URL: http://api.default.192.168.0.54.nip.io
-
让我们通过以下命令检查我们的
api函数的修订版本:$ kn revisions list
使用此命令,您将看到所有流量将由v2修订版本处理。输出将如下所示:
NAME SERVICE TRAFFIC
api-v2 api 100%
api-v1 api
重要提示
出于学习目的,我们省略了输出的TAGS、GENERATION、AGE、CONDITIONS、READY和REASON字段。我们假设分配给 Contour Ingress 控制器的 IP 地址是192.168.0.54,与分配给EXTERNAL_IP变量的值相同。
-
将流量拆分设置为
v1版本的 50%,v2版本的 50%:$ kn service update api \ --traffic api-v1=50 \ --traffic @latest=50
预期的输出将如下所示:
Service 'api' with latest revision 'api-v2' (unchanged) is available at URL:
http://api.default.192.168.0.54.nip.io
您还可以使用@latest选项的api-v2替代方案。您还可以使用自定义版本和不同的流量分配率。
-
通过运行以下命令来设置流量拆分并检查流量是如何在
api函数中分布的:$ kn revisions list
输出将如下所示:
NAME SERVICE TRAFFIC
api-v2 api 50%
api-v1 api 50%
流量按每个修订版本分为 50%。
-
让我们使用一个简单的
BASH循环脚本向我们的函数发送流量,您可以通过运行以下命令并按Ctrl + C来停止:$ while true; do curl http://api.default.$EXTERNAL_IP.nip.io;echo "";sleep 0.3; done
此命令将持续调用每 0.3 秒拆分为两个版本的函数。默认情况下,将运行最新可用的修订版本。在此情况下,修订版本v2将用于响应。等待几秒钟后,v1被供应,并且输出开始显示流量按每个修订版本 50%拆分。输出将类似于:
{
"host": "api-v1-deployment-85f6f977b5-hcgdz",
"msg": "Knative demo v1"
}
{
"host": "api-v1-deployment-85f6f977b5-hcgdz",
"msg": "Knative demo v2"
}
使用Ctrl + C停止BASH循环。
-
如果您想检查此流量拆分的 pod,请运行以下命令:
$ kubectl get pods -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase
输出将如下所示:
NAME STATUS
api-v1-deployment-85f6f977b5-jhss5 Running
api-v2-deployment-b97859489-mtvjm Running
在此输出中,有两个正在运行的 pod —— 一个用于修订版本v1,另一个用于v2。这些 pod 是按需创建的。默认情况下,如果没有超过空闲时间以调用它,则这些修订版本中的一个将运行。开始收到请求后,另一个修订版本将扩展以开始将流量分为这些 pod 的 50%。
-
最后,您可以删除正在运行的所有修订版本的 API 函数:
$ kn service delete api
现在您已经学会了如何在 Knative 中使用流量拆分和修订版本。现在让我们深入了解 Knative,学习如何使用声明性文件在下一节中创建服务。
在 Knative 中使用声明性文件
创建环境时的一个好习惯是为您的应用程序创建声明性定义。Knative 支持使用--target标志实现此目的。例如,如果您想将前面的示例更改为 YAML 文件,可以使用此标志。要执行此操作,请运行以下命令:
$ kn service create api --cluster-local \
--image sergioarmgpl/app2demo \
--port 5000 \
--env MESSAGE="Knative demo v1" \
--revision-name=v1 --target=api.yaml
该命令将输出一个没有公共端点的 API 功能定义的 YAML 文件。api.yaml 文件中的输出将如下所示:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
labels:
networking.knative.dev/visibility: cluster-local
name: api
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/max-scale: "5"
containerConcurrency: 2
name: api-v1
spec:
containers:
- env:
- name: MESSAGE
value: "Knative demo v1"
image: sergioarmgpl/app2demo
name: ""
ports:
- containerPort: 5000
在 annotations 部分,你可以配置 Knative 提供的不同功能;例如,自动缩放、速率限制、并发等。在此案例中,我们使用了 autoscaling.knative.dev/max-scale 来设置函数部署的最大副本数,并使用 containerConcurrency 来设置每个副本的并发请求数。
另一个示例是如何定义流量分割的 YAML。基于我们在 使用 Knative 进行流量分割实现无服务器 API 部分中的流量分割示例,要生成等效的 YAML 配置,使用以下命令:
$ kn service update api \
--traffic api-v1=50 \
--traffic @latest=50 --target=api.yaml
输出将如下所示:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
labels:
networking.knative.dev/visibility: cluster-local
name: api
namespace: default
spec:
traffic:
- latestRevision: true
percent: 50
- latestRevision: false
percent: 50
revisionName: api-v1
template:
metadata:
annotations:
autoscaling.knative.dev/max-scale: "5"
containerConcurrency: "2"
name: api-v1
spec:
containers:
- env:
- name: MESSAGE
value: "Knative demo v1"
image: sergioarmgpl/app2demo
name: ""
ports:
- containerPort: 5000
这是一个理想的功能和最佳实践。为了对创建函数和其他 Knative 对象进行声明性定义,你可以浏览 Knative 的官方文档,查找声明性定义的示例。现在是时候进入下一部分,安装 Knative Eventing 的另一个功能了。
使用 Knative Eventing 实现事件和事件驱动管道
Knative 提供了 Eventing 组件来实现事件驱动架构。我们将使用 Knative 探索一个简单的 Eventing 管道,利用轻量级的内存通道组件来实现两个简单的事件,调用一个服务并展示一条消息。在第二部分,我们将实现一个简单的序列,依次调用两个服务器,展示自定义消息。那么,让我们从第一部分开始,实现简单的事件。
安装 Knative Eventing
在创建我们的事件之前,我们需要安装所有 Knative 组件。我们将使用内存通道来管理我们的事件,这是 Knative 中最简单且最轻量的通道,同时还需要使用 Sugar Controller 来通过标签在命名空间中提供 Knative Eventing 资源。按照以下步骤安装 Knative Eventing:
-
安装 Knative Eventing CRD:
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.2.0/eventing-crds.yaml -
通过运行以下命令安装 Knative Eventing 核心组件:
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.2.0/eventing-core.yaml -
现在通过运行以下命令安装内存通道组件:
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.2.0/in-memory-channel.yaml -
现在安装 MT 通道代理,它是一个轻量级且简单的实现,使用内存通道:
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.2.0/mt-channel-broker.yaml -
最后,安装 Knative Eventing Sugar Controller,它会响应特殊的标签和注释,并生成 Eventing 资源:
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.2.0/eventing-sugar-controller.yaml -
运行以下命令,检查所有组件是否处于
READY状态:$ kubectl get pods -n knative-eventing -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase
你将看到类似以下的输出:
NAME STATUS
mt-broker-filter-574dc4457f-pjs7z Running
imc-dispatcher-7fcb4b5d8c-qxrq2 Running
mt-broker-controller-8d979648f-6st56 Running
sugar-controller-6dd4c4bc5f-76kqc Running
mt-broker-ingress-5ddd6f8b5d-h94z5 Running
eventing-webhook-5968f79978-5nhlc Running
eventing-controller-58875c5478-n8xzl Running
imc-controller-86cd7b7857-hpcpq Running
现在你已经安装了所有必要的组件来使用 Knative 实现一个简单的事件驱动管道。让我们进入下一部分,学习如何实现事件。
实现一个简单的事件
现在是时候实现一些基本事件了。这个场景包括创建两个服务并通过它们的属性类型进行调用。首先,让我们探索在 Docker Hub 中名为sergioarmgpl/app3demo的容器内的代码。使用的代码是:
from flask import Flask, request
from cloudevents.http import from_http
app = Flask(__name__)
@app.route("/", methods=["POST"])
def route():
event = from_http(request.headers, request.get_data())
app.logger.warning(event)
return "", 204
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0',port=5000)
这段代码接收调用并使用 Cloud Events 库转换请求的数据,以使用 Flask 中实现的app.logger.warning函数输出事件。因此,每次在/路由路径中调用应用程序时,它都会在日志中显示调用容器的请求信息,采用 Cloud Events 结构格式。在这种情况下,我们不会返回任何数据作为响应。它只是返回 HTTP 状态响应代码204,表示请求成功。根据需要,您还可以自定义此代码,以适应您的需求。
现在,我们必须使用 YAML 定义创建两个服务。第一个服务将命名为api-demo,第二个服务为api-demo2。每次调用代理时,这些服务都会被调用,发送它们的云事件属性。当属性类型设置为event.show时,将调用api-demo服务,而当代理调用时,属性类型设置为event.show.2时,将调用api-demo2服务。两个服务都配置为监听端口5000并将请求转发到端口80,以便与 Knative Eventing 正常工作。
要开始实现第一个场景,请按照以下步骤操作:
-
创建并注入将创建事件的
event-demo命名空间:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: event-demo labels: eventing.knative.dev/injection: enabled EOF -
创建用于此实现的默认代理:
$ cat <<EOF | kubectl apply -f - apiVersion: eventing.knative.dev/v1 kind: Broker metadata: name: default namespace: event-demo annotations: eventing.knative.dev/broker.class: MTChannelBasedBroker EOF -
部署将处理事件的容器:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: api-demo name: api-demo namespace: event-demo spec: replicas: 1 selector: matchLabels: app: api-demo template: metadata: labels: app: api-demo spec: containers: - image: sergioarmgpl/app3demo name: app3demo imagePullPolicy: Always EOF -
为此
api-demo部署创建服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: api-demo name: api-demo namespace: event-demo spec: ports: - port: 80 protocol: TCP targetPort: 5000 selector: app: api-demo type: ClusterIP EOF -
创建一个由服务消费的触发器:
$ cat <<EOF | kubectl apply -f - apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: api-demo namespace: event-demo spec: broker: default filter: attributes: type: event.show subscriber: ref: apiVersion: v1 kind: Service name: api-demo EOF -
在
event-demo命名空间中创建一个 pod 来调用代理。该代理将调用我们的 pod,显示消息Simple Event using Knative。要创建此 pod,请运行:$ kubectl run -n event-demo curl -it --rm --image=curlimages/curl:7.81.0 /bin/sh -
在此 pod 内,运行
curl命令发送请求到代理。代理将采用先前实现的云事件参数并将其发送到您的 pod。要调用代理,请运行:$ curl -v "broker-ingress.knative-eventing.svc.cluster.local/event-demo/default" \ -X POST \ -H "Ce-Id: call-api-demo" \ -H "Ce-specversion: 1.0" \ -H "Ce-Type: event.show" \ -H "Ce-Source: test-send" \ -H "Content-Type: application/json" \ -d '{"msg":"Simple Event using Knative."}'
输出将如下所示:
* Connected to broker-ingress.knative-eventing.svc.cluster.local (10.43.130.39) port 80 (#0)
> POST /event-demo/default HTTP/1.1
> Host: broker-ingress.knative-eventing.svc.cluster.local
> User-Agent: curl/7.81.0-DEV
> Accept: */*
> Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f79
> Ce-specversion: 0.3
> Ce-Type: dev.knative.myevents.api-demo
> Ce-Source: dev.knative.myevents/api-demo-source
> Content-Type: application/json
> Content-Length: 37
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 202 Accepted
< Allow: POST, OPTIONS
< Date: Thu, 24 Feb 2022 05:30:13 GMT
< Content-Length: 0
<
* Connection #0 to host broker-ingress.knative-eventing.svc.cluster.local left intact
-
要退出,请在 pod 内运行下一个命令:
$ exit -
现在通过运行以下命令检查 pod 的日志:
$ kubectl -n event-demo logs -l app=api-demo --tail=50
或者,如果您想实时查看日志,当您调用代理并触发您的 pod 时,运行以下命令:
$ kubectl -n event-demo logs -f -l app=api-demo
输出将如下所示:
* Serving Flask app 'index' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on all addresses.
WARNING: This is a development server. Do not use it in a production deployment.
* Running on http://10.42.0.42:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 110-221-376
[2022-02-27 06:02:02,107] WARNING in index: {'attributes': {'specversion': '1.0', 'id': 'call-api-demo', 'source': 'test-send', 'type': 'event.show', 'datacontenttype': 'application/json', 'knativearrivaltime': '2022-02-27T06:02:02.069191004Z', 'time': '2022-02-27T06:02:02.107288+00:00'}, 'data': {'msg': 'Simple Event using Knative.'}}
如您所见,pod 收到了msg值Simple Event using Knative.,并且它已打印在 pod 的日志中。这意味着当您调用代理时,触发器会通过先前创建的服务调用暴露的 pod。
比如,假设您想使用相同的镜像创建另一个事件。这次,我们将其命名为api-demo2,用于第二个服务。创建下一个 YAML 定义:
-
要创建
api-demo2部署,请运行以下命令:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: api-demo2 name: api-demo2 namespace: event-demo spec: replicas: 1 selector: matchLabels: app: api-demo2 template: metadata: labels: app: api-demo2 spec: containers: - image: sergioarmgpl/app3demo name: app4 imagePullPolicy: Always EOF -
为此
api-demo2部署创建服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: api-demo2 name: api-demo2 namespace: event-demo spec: ports: - port: 80 protocol: TCP targetPort: 5000 selector: app: api-demo2 type: ClusterIP EOF -
创建一个触发器来启动
api-demo2,我们将调用api-demo2服务的属性类型event.show.2,该服务指向api-demo2部署:$ cat <<EOF | kubectl apply -f - apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: api-demo2 namespace: event-demo spec: broker: default filter: attributes: type: event.show.2 subscriber: ref: apiVersion: v1 kind: Service name: api-demo2 EOF -
在之前创建的
curlpod 中,运行以下命令:$ curl -v "broker-ingress.knative-eventing.svc.cluster.local/event-demo/default" \ -X POST \ -H "Ce-Id: call-api-demo2" \ -H "Ce-specversion: 1.0" \ -H "Ce-Type: event.show.2" \ -H "Ce-Source: test-send" \ -H "Content-Type: application/json" \ -d '{"msg":"Simple Event using Knative."}' -
使用以下命令检查新
api-demo2部署中的日志:$ kubectl -n event-demo logs -l app=api-demo2 --tail=50 -
日志将显示如下:
* Serving Flask app 'index' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on all addresses. WARNING: This is a development server. Do not use it in a production deployment. * Running on http://10.42.0.43:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 602-982-734 [2022-02-27 06:16:07,689] WARNING in index: {'attributes': {'specversion': '1.0', 'id': 'call-api-demo2', 'source': 'test-send', 'type': 'event.show.2', 'datacontenttype': 'application/json', 'knativearrivaltime': '2022-02-27T06:16:07.654229185Z', 'time': '2022-02-27T06:16:07.688895+00:00'}, 'data': {'msg': 'Simple Event using Knative2.'}}
现在,您已经使用 Knative Eventing 创建了两个基本事件。这可以帮助您实现简单且轻量级的事件驱动架构。现在,是时候探索如何使用 Knative Eventing 的 Sequence 功能,使用事件驱动架构创建和运行简单的管道了。
使用序列实现事件驱动管道
事件驱动架构的另一个常见用例是触发一系列步骤按顺序执行以自动化工作流。在这种情况下,您可以使用 Knative 的 Sequence 对象。在此示例中,我们将创建一个由两个步骤组成的序列。每个步骤都会打印MESSAGE变量,该变量包含当前运行步骤的编号。这个序列将通过触发器调用。我们将使用curl命令调用触发器。这是一个使用事件驱动架构的简单示例管道。让我们通过以下步骤开始:
-
使用
eventing.knative.dev/injection: enabled标签创建sequence-demo命名空间。当 Knative Eventing 检测到此标签时,它将创建默认的 Knative 代理。这得益于之前安装的 Knative Sugar Controller。因此,让我们通过运行以下命令创建命名空间:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: sequence-demo labels: eventing.knative.dev/injection: enabled EOF -
通过运行以下命令,使用 Knative 服务定义文件创建
step1:$ cat <<EOF | kubectl apply -f - apiVersion: serving.knative.dev/v1 kind: Service metadata: name: step1 namespace: sequence-demo spec: template: spec: containers: - image: sergioarmgpl/app4demo ports: - containerPort: 5000 env: - name: MESSAGE value: "step1" EOF -
现在通过运行以下命令创建
step2:$ cat <<EOF | kubectl apply -f - apiVersion: serving.knative.dev/v1 kind: Service metadata: name: step2 namespace: sequence-demo spec: template: spec: containers: - image: sergioarmgpl/app4demo ports: - containerPort: 5000 env: - name: MESSAGE value: "step2" EOF
重要提示
我们在服务定义中使用containerPort参数,定义一个自定义端口,用于容器监听与 Knative Eventing 的通信。默认情况下,Knative 使用端口80来监听服务。
-
让我们创建名为
sequence-demo的序列对象,使用内存通道进行消息传递,作为一个小型管道运行这些步骤:$ cat <<EOF | kubectl apply -f - apiVersion: flows.knative.dev/v1 kind: Sequence metadata: name: sequence namespace: sequence-demo spec: channelTemplate: apiVersion: messaging.knative.dev/v1 kind: InMemoryChannel steps: - ref: apiVersion: serving.knative.dev/v1 kind: Service name: step1 - ref: apiVersion: serving.knative.dev/v1 kind: Service name: step2 EOF -
创建我们将要使用的触发器。我们将定义一个属性来调用它。在这种情况下,每次我们调用带有
type属性且值为event.call.sequence的事件时,它将调用我们的序列:$ cat <<EOF | kubectl apply -f - apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: sequence-trigger namespace: sequence-demo spec: broker: default filter: attributes: type: event.call.sequence subscriber: ref: apiVersion: flows.knative.dev/v1 kind: Sequence name: sequence EOF -
现在,让我们在
sequence-demo命名空间中创建一个curlpod,通过我们的代理的端点调用我们的序列:$ kubectl run -n sequence-demo curl -it --rm --image=curlimages/curl:7.81.0 /bin/sh -
在 pod 中运行以下
curl命令:$ curl -v "broker-ingress.knative-eventing.svc.cluster.local/sequence-demo/default" \ -X POST \ -H "Ce-Id: call-sequence-demo" \ -H "Ce-specversion: 1.0" \ -H "Ce-Type: event.call.sequence" \ -H "Ce-Source: test-sequence" \ -H "Content-Type: application/json" \ -d '{"SOME_VARIABLE":"Simple Sequence using Knative."}'
这将显示如下输出:
* Trying 10.43.130.39:80...
* Connected to broker-ingress.knative-eventing.svc.cluster.local (10.43.130.39) port 80 (#0)
> POST /sequence-demo/default HTTP/1.1
> Host: broker-ingress.knative-eventing.svc.cluster.local
> User-Agent: curl/7.81.0-DEV
> Accept: */*
> Ce-Id: call-sequence-demo
> Ce-specversion: 1.0
> Ce-Type: event.call.sequense
> Ce-Source: test-sequence
> Content-Type: application/json
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 202 Accepted
< Allow: POST, OPTIONS
< Date: Mon, 28 Feb 2022 01:00:50 GMT
< Content-Length: 0
<
* Connection #0 to host broker-ingress.knative-eventing.svc.cluster.local left intact
-
退出 pod 并通过运行以下命令检查step 1的输出:
$ kubectl logs deploy/step1-00001-deployment -n sequence-demo user-container
您将看到如下输出:
[2022-02-28 01:06:54,364] WARNING in index: b'{"SOME_VARIABLE":"Simple Sequence using Knative."}'
[2022-02-28 01:06:54,365] WARNING in index: step1
这将接收由curl命令发送的SOME_VARIABLE变量,该变量可用于自定义您的序列。
-
现在通过运行以下命令检查step 2的输出:
$ kubectl logs deploy/step2-00001-deployment -n sequence-demo user-container The output will look like: [2022-02-28 01:07:02,623] WARNING in index: b'{\n "ENV_VAR": "step1"\n}\n' [2022-02-28 01:07:02,624] WARNING in index: step2
这将显示由上一步发送的 ENV_VAR 值以及当前环境变量,表示当前正在运行的步骤——在这种情况下是 步骤 2。
- 空闲几分钟后,命名空间中的步骤部署将会缩减,并在每次调用时重新扩展。
我们已经完成了使用 Knative 的无服务器和事件驱动管道基础知识的学习,是时候结束本章了。
总结
在本章中,我们学习了如何使用 Knative Serving 实现公共无服务器和内部无服务器函数,并使用流量分配功能。我们还学习了如何使用 Knative Eventing 实现简单事件和事件序列,构建小型事件驱动架构,并通过 Cloud Events Python SDK 实现 API 事件调用的集成和标准化。在下一章中,我们将学习如何使用 K3s 在边缘使用数据库,为边缘系统添加更多功能。
问题
这里有一些问题来验证你新学到的知识:
-
无服务器架构的使用场景是什么?
-
什么是无服务器函数?
-
无服务器技术的优势是什么?
-
我如何使用 Knative 实现一个无服务器函数?
-
我如何使用 Knative 实现一个事件?
-
我如何使用 Knative 实现事件驱动管道?
-
Cloud Events 如何帮助你实现事件?
进一步阅读
你可以参考以下资源获取更多关于本章内容的信息:
-
为什么无服务器将推动边缘计算革命:
medium.com/serverless-transformation/why-serverless-will-enable-the-edge-computing-revolution-4f52f3f8a7b0 -
什么是边缘无服务器:
www.stackpath.com/edge-academy/what-is-edge-serverless -
AI/ML、边缘计算和无服务器计算的年度优先事项:
www.redhat.com/en/blog/aiml-edge-and-serverless-computing-top-priority-list -
在 Raspberry Pi 上运行 Knative:
github.com/csantanapr/knative-pi -
使用 YAML 安装 Knative Serving:
knative.dev/docs/install/serving/install-serving-with-yaml/#install-a-networking-layer -
Cloud Events 网站:
cloudevents.io -
Cloud Events SDK:
github.com/cloudevents/sdk-python -
CloudEvents – 版本 1.0.2:
github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md -
使用 Knative Eventing 的 Hello World Python 示例:
github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-python -
将事件发送到代理:
knative.dev/docs/eventing/getting-started/#sending-events-to-the-broker -
使用 Sequence 配合 Broker 和 Trigger:
knative.dev/docs/eventing/flows/sequence/sequence-with-broker-trigger
第十章:边缘系统中的 SQL 和 NoSQL 数据库
在创建边缘系统时,一个关键任务是存储数据。为此,您需要考虑您拥有的资源、设备使用的处理器以及您要存储的数据类型。CAP 定理指出,分布式数据存储只能提供以下三项保证中的两项:一致性、可用性和分区容错性。因此,这个定理可以帮助您根据系统需求决定哪种类型的数据库最为合适。在本章中,我们将学习如何使用 K3s 和 ARM 设备部署不同类型的数据库,以便在边缘系统上运行。这些示例包括使用 ConfigMaps 和 Secrets 部署数据库等不同技术。
在本章中,我们将涵盖以下主要内容:
-
SQL 和 NoSQL 数据库的 CAP 定理
-
创建一个卷以持久化您的数据
-
使用 MySQL 和 MariaDB SQL 数据库
-
使用 Redis 键值对 NoSQL 数据库
-
使用 MongoDB 文档导向的 NoSQL 数据库
-
使用 PostgreSQL 对象关系型 SQL 数据库
-
使用 Neo4j 图形 NoSQL 数据库
技术要求
要在本章中部署数据库,您需要以下内容:
-
一个单节点或多节点的 K3s 集群,使用 ARM 设备并安装了 MetalLB 和 Longhorn 存储。如果您使用的是 Raspberry Pi 设备,至少需要 4 GB 的 RAM 和 4B 型号。每个节点必须安装 Ubuntu ARM64 操作系统,以支持 ARMv8 架构,这对于本章中的某些部署是必要的。
-
kubectl已配置为在本地机器上使用,以避免使用--kubeconfig参数。 -
如果您希望使用
kubectl apply运行 YAML 配置,而不是从书中复制代码,可以克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch10仓库。查看ch10目录中的yaml目录,了解其中的 YAML 示例。
通过这些,您可以部署本章中解释的数据库。因此,让我们首先学习 CAP 定理,以选择适合您特定用例的正确数据库。
SQL 和 NoSQL 数据库的 CAP 定理
CAP 定理由 Eric Brewer 于 1999 年定义,并于 2000 年在第十九届 ACM 分布式计算原理年会(PODC)上提出。该定理指出,分布式数据存储只能提供以下两项保证:
-
一致性:这意味着在读取信息时,数据存储返回的是最新写入的数据,或者在失败时返回错误。这指的是常规的 SQL 数据库,它们使用原子操作来保证数据的写入。如果没有,系统会自动回滚到先前的数据状态。
-
可用性:这意味着所有的读取操作都包含数据,但可能不是最新的数据。
-
分区容错性:这是分布式系统中最理想的特性。它意味着数据被分布在多个节点中,有助于减少数据库的停机时间。这意味着,如果一个节点宕机,只有一小部分数据会无法访问:

图 10.1 – CAP 定理图示
该定理通常作为设计强大的分布式系统时的数据参考点。在 CAP 定理图示(图 10.1)中,以三角形形式展示,你可以看到不同的侧面,以及每个侧面如何与其他侧面相互关联。让我们探索这些侧面并举例说明数据库:
-
CA:在这一侧,我们可以分类具有一致性和可用性的数据库。这里,我们可以找到 SQLite,它是一个非常简单的数据库。MySQL 和 PostgreSQL 是非常流行的开源数据库。SQL Server 是微软的专有数据库,Neo4j 是一个图数据库。每一个这些数据库都试图保证一致性和可用性。这些保证可以在关系数据库管理系统(RDBMS)为基础的数据库中找到。但正如我们所提到的,Neo4j 被归类在三角形的这一侧。这个侧面的一个重要特性是,如果网络宕机,数据库将会失败。
-
CP:在这一侧,你可以找到提供一致性和分区容错性的数据库。这意味着像 Mongo 和 Redis 这样的数据库使用算法来写入数据,以保证数据的一致性。例如,MongoDB 使用读写算法来写入数据库。Redis 使用类似的算法来写入数据。说到分区容错性,MongoDB 可以将信息分布到多个节点,这赋予了 MongoDB 分区数据的能力。这就是分片,提供了分区容错性特性给 MongoDB。其他基于 Bigtable 的数据库工作原理类似。这些基于 Bigtable 的数据库通常从云端的分布式信息桶中读取数据。CP 中的问题是,当一个节点或数据源宕机时,一些数据可能会变得不可用。
-
AP:在这一侧,数据库寻求可用性和分区容错性。这里,我们可以找到像 Cassandra、CouchDB、Riak、DynamoDB 和基于 Cassandra 的数据库。例如,Cassandra 具有高可用性,使用无主技术来扩展服务器,但它不保证数据的一致性。这是一些 NoSQL 数据库常见的问题。
在决定哪个数据库适合你之前,让我们先了解一下什么是关系型数据库和非关系型数据库。关系型数据库是结构化数据的数据库。这意味着数据被组织成表格、行和列。这些表格之间有关系和依赖。关系型数据库使用结构化查询语言(SQL)来管理信息。关系型数据库也叫做SQL 数据库,它们还使用 ACID 操作。ACID 代表原子性、一致性、隔离性和持久性;这些数据特性保证了在错误和故障发生时数据的完整性。一些示例包括 MySQL、PostgreSQL 和 SQL Server。
非关系型数据库是没有结构化的。它不使用表格、行和列的数据模式,而是使用针对存储数据类型特定需求优化的存储模型。这些数据类型可能是 JSON 文档、键值对等。这些数据库也被称为NoSQL 数据库。这些数据库不使用 ACID 操作,它们更关注数据的可用性和分区容忍性。一些示例包括 MongoDB、Redis、Neo4j 和 Cassandra。在选择合适的数据库时,你可以考虑以下几个问题:
-
我的系统需要哪种一致性、分区容忍性和可用性的保障?根据这些,哪种数据库最适合我的系统需求?
-
我的数据库是否需要支持 SQL 语言来查询信息?
-
我需要一个支持 SQL 语言的数据库吗?
-
我的数据是没有结构的 JSON 文档,还是需要一些像表格那样结构化的数据?
-
我存储的数据类型是什么?我需要 SQL 数据库还是 NoSQL 数据库?
-
我需要一致性、可用性还是分区容忍性?这些组件中,哪一个对我的系统最重要?
-
我的数据库将使用多少资源?我的系统预期能够处理多少个并发连接?
-
我是否需要复制信息、实现速率限制或在数据库中实现其他特定功能?
-
我的数据库在写入和读取数据时速度有多快?
-
我如何在数据库上进行复制或扩展?
这些和其他问题在选择合适的数据库时可能非常重要。因此,本章将重点介绍如何通过 CAP 定理快速入门,并给出一些如何部署 SQL 和 NoSQL 数据库的示例,这些数据库在 CAP 定理的描述中有所提及。这些 SQL 和 NoSQL 数据库将使用容器在 K3s 集群的边缘进行部署。
重要提示
在进一步阅读部分,你可以找到一些链接,了解更多关于 SQL 和 NoSQL 数据库的内容、章中提到的数据库的官方网页链接以及评估哪个数据库最适合你的用例的补充链接。你可以使用的一个补充定理是 PACELC 定理,它关注的是在数据复制时,延迟和一致性之间的权衡。
现在,在部署你选择的数据库之前,让我们继续创建一个持久化数据的存储卷。
创建一个持久化数据的存储卷
在我们开始部署数据库之前,让我们先创建一个存储数据的卷。为此,我们有两个选择。一个是使用服务器内部的目录。这意味着为了避免数据丢失,Pods 必须部署在与卷第一次创建的节点相同的节点上。如果你不想依赖 Pods 所运行的节点,可以选择第二个选项,即使用存储驱动程序。如果你选择这个方案,我们将使用 Longhorn 作为存储驱动。现在,让我们先使用本地目录创建存储。为此,请按照以下步骤操作:
-
在节点上创建
/mnt/data目录以存储数据:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolume metadata: name: db-pv-volume labels: type: local spec: storageClassName: manual capacity: storage: 5Gi accessModes: - ReadWriteOnce hostPath: path: "/mnt/data" EOF -
创建一个使用 5 GB 存储空间的 PersistentVolumeClaim:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim spec: storageClassName: manual accessModes: - ReadWriteOnce resources: requests: storage: 5Gi EOF
如果你想使用 Longhorn 作为存储,请按照以下步骤进行操作:
-
使用 Longhorn 创建一个 5 GB 存储空间的 PersistentVolumeClaim:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 5Gi EOF
这是一个关键步骤,用于持久化数据并避免数据丢失。在接下来的章节中,我们将开始部署我们的数据库,首先是基本配置,然后加入更复杂的配置,例如使用 ConfigMaps 和 Secrets 来进行更接近生产环境的部署。但首先,让我们从 MySQL 和 MariaDB 开始,它们是互联网上非常流行的数据库。
使用 MySQL 和 MariaDB SQL 数据库
MySQL 是一个关系型数据库,使用 SQL 语言读取和写入信息。它是互联网上最常用的数据库之一。MariaDB 是 MySQL 的一个分支,本示例中使用的版本与 MySQL 完全兼容。它是一个非常快速的 SQL 数据库,且易于使用。在简要介绍了 MySQL 后,让我们按照以下步骤开始部署该数据库:
-
创建 MySQL 部署时,创建一个名为
db-pv-claim的 PersistentVolumeClaim:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: containers: - image: mysql:8.0.28-oracle name: mysql env: - name: MYSQL_ROOT_PASSWORD value: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: db-pv-claim EOF
重要说明
如果不使用 MySQL,你可以选择使用 MariaDB,它与 MySQL 5.6 版本完全兼容。为此,将 mysql:8.0.28-oracle 镜像改为 arm64v8/mariadb:latest,并将 MYSQL_ROOT_PASSWORD 变量改为 MARIADB_ROOT_PASSWORD。你还可以在 hub.docker.com 上查找其他 MySQL 和 MariaDB 镜像版本。对于此部署,密码为 password。部署使用的镜像都是为 ARM 设备设计的。如果你选择重新安装 MySQL 并使用本地存储,你需要使用 rm -R /mnt/data 命令删除 /mnt/data 目录中的内容,以避免错误。
-
现在,让我们创建一个服务来通过服务访问 MySQL:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 selector: app: mysql clusterIP: None EOF -
为了测试你的 MySQL 部署是否有效,你可以通过运行以下命令来访问部署的 Pod:
$ kubectl exec -it $(kubectl get pods -l app=mysql --output=jsonpath={..metadata.name}) -- bash
在 Pod 内运行以下命令以连接到数据库:
$ mysql -h localhost -uroot -ppassword
现在,提示符将更改为 mysql>。让我们创建一个简单的数据库 EXAMPLE,其中包含 VALUE_TABLE 表,并插入和列出一些记录。为此,运行以下命令,你将看到类似这样的输出:
mysql> CREATE DATABASE EXAMPLE;
Query OK, 1 row affected (0.02 sec)
mysql> USE EXAMPLE;
Database changed
mysql> CREATE TABLE VALUE_TABLE (ID INT PRIMARY KEY NOT NULL,VALUE INT NOT NULL);
Query OK, 0 rows affected (0.10 sec)
mysql> INSERT INTO VALUE_TABLE (ID,VALUE) VALUES (1,123);
Query OK, 1 row affected (0.03 sec)
mysql> SELECT * FROM VALUE_TABLE;
+----+-------+
| ID | VALUE |
+----+-------+
| 1 | 123 |
+----+-------+
1 row in set (0.00 sec)
-
最后,使用以下命令删除表和数据库:
mysql> DROP TABLE VALUE_TABLE; Query OK, 0 rows affected (0.07 sec) mysql> DROP DATABASE EXAMPLE; Query OK, 0 rows affected (0.05 sec) mysql> EXIT Bye
现在你已经学会了如何使用 MySQL 进行基本部署和示例。接下来让我们学习 Redis 的工作原理。
使用 Redis 键值 NoSQL 数据库
现在是时候使用 Redis 作为我们的键值数据库了。Redis 是一个很好的键值数据库,它不会消耗太多资源。所有数据都存储在内存中。它有一些非常有趣的数据类型,如哈希键、列表和集合。它还实现了发布-订阅和流功能,用来实现通信通道和简单的代理功能。对于我们的 Redis 部署,我们将使用自定义配置来设置 Redis 的密码,并使用存储卷来防止数据丢失。要在集群中使用 Redis,请按照以下步骤操作:
-
创建
K3s123-和/data目录来存储 Redis 数据:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: redis-configmap data: redis-config: | dir /data requirepass YOUR_PASSWORD EOF -
使用之前的 ConfigMap
redis-configmap创建 Redis 部署,并将其挂载为redis.conf文件。我们还使用名为db-pv-claim的 PersistentVolumeClaim,并为部署设置一些资源限制,设置 CPU 和内存。我们通过运行以下命令来创建部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: run: redis name: redis spec: replicas: 1 selector: matchLabels: run: redis template: metadata: labels: run: redis spec: containers: - name: redis image: arm64v8/redis:6.2 command: - redis-server - /redisconf/redis.conf ports: - containerPort: 6379 resources: limits: cpu: "0.2" memory: "128Mi" volumeMounts: - mountPath: "/data" name: redis-storage - mountPath: /redisconf name: config volumes: - name: config configMap: name: redis-configmap items: - key: redis-config path: redis.conf - name: redis-storage persistentVolumeClaim: claimName: db-pv-claim EOF -
现在创建
redis,指向我们redis部署中的端口6379:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: run: redis name: redis spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: run: redis type: ClusterIP EOF
该服务在集群内部创建了一个名为 redis 的 DNS 记录,指向我们的 redis 部署。其他集群中的部署可以访问该 DNS 记录。
-
让我们访问 Redis pod,测试一些基本命令来存储值到我们的数据库。为此,运行以下命令:
$ kubectl exec -it $(kubectl get pods -l run=redis --output=jsonpath={..metadata.name}) -- redis-cli
提示符将如下所示:127.0.0.1:6379>。
-
现在,使用
AUTH命令认证 Redis 数据库,然后使用set和get命令创建a键并赋值为1。最后,使用exit命令退出。这个简单的测试将如下所示:127.0.0.1:6379> AUTH YOUR_PASSWORD OK 127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> get a "1" 127.0.0.1:6379> exit
这样,你就把 a 键存储了值 1。现在你已经使用 Redis 存储了简单的值。运行 exit 后,你将退出 Redis pod。
现在你已经学会了如何部署一个简单的 Redis 部署,接下来是时候在下一节部署 MongoDB 了。
使用 MongoDB 文档导向的 NoSQL 数据库
MongoDB 是一个面向文档的 NoSQL 数据库。它将数据存储为 JSON 文档,并实现了分片技术以将数据分布到各个节点,同时使用 MapReduce 技术进行数据聚合。它易于使用,并且在单节点场景下占用较少的资源。对于我们的 MongoDB 部署,我们将使用 ConfigMap 来存储自定义配置。在这个案例中,我们的 MongoDB 配置设置为在网络上暴露其端口,但为了简化部署,我们暂时没有使用 Secrets。在 使用 PostgreSQL 对象关系型 SQL 数据库 部分,我们将探讨使用 Secrets,但在此之前,让我们按照接下来的步骤部署 MongoDB:
-
部署你的自定义配置以允许客户端连接到 MongoDB:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: mongo-configmap data: mongod-conf: | dbpath=/var/lib/mongodb logpath=/var/log/mongodb/mongodb.log logappend=true bind_ip = 0.0.0.0 port = 27017 journal=true auth = true EOF
这会让 MongoDB 在网络上监听 27017 端口。
-
使用名为
mongo-configmap的 ConfigMap、PersistentVolumeClaim 以及设置初始 root 用户、用户和连接 MongoDB 所需密码的MONGO_INITDB_ROOT_USERNAME、MONGO_INITDB_ROOT_PASSWORD和MONGO_INITDB_DATABASE变量来创建部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: mongo name: mongo spec: replicas: 1 selector: matchLabels: app: mongo template: metadata: labels: app: mongo spec: containers: - image: arm64v8/mongo:4.4 name: mongo env: - name: MONGO_INITDB_ROOT_USERNAME value: "admin" - name: MONGO_INITDB_ROOT_PASSWORD value: "YOUR_PASSWORD" - name: MONGO_INITDB_DATABASE value: "mydatabase" ports: - containerPort: 27017 resources: limits: cpu: "0.5" memory: "200Mi" volumeMounts: - mountPath: "/data/db" name: mongo-storage - mountPath: /mongoconf name: config volumes: - name: config configMap: name: mongo-configmap items: - key: mongod-conf path: mongod.conf - name: mongo-storage persistentVolumeClaim: claimName: db-pv-claim EOF
重要提示
请注意,如果你想使用 MongoDB 版本大于 5.0,你需要一台支持 ARMv8.2-A 或更高版本的设备。这个原因使得我们在这个示例中使用了 MongoDB 4.4。MongoDB 4.4 支持在 ARMv8 处理器(如 Raspberry Pi)上运行。
-
现在创建一个服务,将你的 MongoDB 部署暴露为一个在集群内可访问的服务(MongoDB 使用
27017端口连接):$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: mongo name: mongo spec: ports: - port: 27017 protocol: TCP targetPort: 27017 selector: app: mongo type: ClusterIP EOF -
访问包含 MongoDB 的 pod 来测试你是否能够写入一些数据:
$ kubectl exec -it $(kubectl get pods -l app=mongo --output=jsonpath={..metadata.name}) -- mongo -uadmin -pYOUR_PASSWORD
一旦进入 pod,切换到 mydatabase,并使用 db.mycollection.insert 向 mycollection 集合中插入 {"a":1} 文档。然后,使用 db.mycollection.find 列出插入的文档。最后,执行 exit 来结束 Mongo 会话。此操作的命令和输出将如下所示:
> use mydatabase
switched to db mydatabase
> db.mycollection.insert({"a":1})
WriteResult({ "nInserted" : 1 })
> db.mycollection.find()
{ "_id" : ObjectId("622c498199789d3b03b20c45"), "a" : 1 }
> exit
Bye
这些是一些用于快速开始使用 MongoDB 的基本命令。
现在你已经知道如何在 K3s 中部署一个简单的 MongoDB 数据库,接下来让我们进入下一部分,学习如何使用 Postgres。
使用 PostgreSQL 对象关系型 SQL 数据库
PostgreSQL 是一种对象关系型数据库,以其可靠性、功能强大性和性能出名。它使用 SQL 来查询数据。它还常用于存储文件或存储用于创建机器学习模型的数据。因此,让我们学习如何以一种非常简单的方式部署 PostgreSQL。为此,请按照以下步骤操作:
-
在这个示例中,让我们使用 Kubernetes Secrets,并将密码设置为
YOUR_PASSWORD,以展示如何隐藏敏感信息如密码。为此,让我们使用以下命令生成密码的 Base64 编码:$ echo "YOUR_PASSWORD"| tr -d "\n" | base64
输出将如下所示:
WU9VUl9QQVNTV09SRA==
-
使用之前的输出,通过 YAML 文件创建您的 Secret 对象。您可以使用以下命令创建具有此值的
db-passwordSecret:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: db-password data: password: WU9VUl9QQVNTV09SRA== EOF -
现在使用以下命令创建 Postgres 部署:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: postgres name: postgres spec: replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - image: arm64v8/postgres:14.2 name: postgres env: - name: PGDATA value: "/var/lib/postgresql/data/pgdata" - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: db-password key: password ports: - containerPort: 5432 resources: limits: cpu: "0.5" memory: "200Mi" volumeMounts: - mountPath: "/var/lib/postgresql/data" name: postgres-storage volumes: - name: postgres-storage persistentVolumeClaim: claimName: db-pv-claim EOF -
现在通过运行以下命令创建
postgres服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: postgres name: postgres spec: ports: - port: 5432 protocol: TCP targetPort: 5432 selector: app: postgres type: ClusterIP EOF -
访问包含 Postgres 的 pod,测试是否能够写入一些数据。为此,请运行以下命令:
$ kubectl exec -it $(kubectl get pods -l app=postgres --output=jsonpath={..metadata.name}) -- bash -c "PGPASSWORD='YOUR_PASSWORD' psql -h postgres -U postgres" -
提示符将显示为
postgres=#。接下来,您将看到一些示例命令及其输出。这些命令将用于测试我们的数据库是否正常工作。
首先,创建包含 ID 和 VALUE 字段的 VALUE_TABLE 表:
postgres=# CREATE TABLE VALUE_TABLE (ID INT PRIMARY KEY NOT NULL,VALUE INT NOT NULL);
CREATE TABLE
然后插入一条记录,ID=1 和 VALUE=123:
postgres=# INSERT INTO VALUE_TABLE (ID,VALUE) VALUES (1,123);
INSERT 0 1
显示这些值:
postgres=# SELECT * FROM VALUE_TABLE;
id | value
----+-------
1 | 123
(1 row)
删除表:
postgres=# DROP TABLE VALUE_TABLE;
DROP TABLE
退出 Postgres:
postgres=# exit
现在,您已经学会了如何安装并运行基本命令,通过 Postgres 存储您的数据,让我们继续学习 Neo4j 这款图形 NoSQL 数据库,下一节将介绍它。
使用 Neo4j 图形 NoSQL 数据库
Neo4j 是一款图形数据库,可用于存储对象之间的关系。Neo4j 使用 Cypher 查询语言(CQL),它相当于关系数据库中的 SQL。Neo4j 还通过节点、关系、属性和标签以可视化方式表示数据。它支持 ACID 操作和原生图形存储与处理,具有出色的可扩展性和企业支持。由于其存储数据的方式,它可以用于 IoT 应用程序,以查询数据之间的关系。现在,让我们按照以下步骤安装 Neo4j:
-
创建 Neo4j 的部署:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: neo4j name: neo4j spec: replicas: 1 selector: matchLabels: app: neo4j template: metadata: labels: app: neo4j spec: containers: - image: arm64v8/neo4j name: neo4j env: - name: NEO4J_AUTH value: none ports: - containerPort: 7474 name: http - containerPort: 7687 name: bolt - containerPort: 7473 name: https volumeMounts: - name: neo4j-data mountPath: "/var/lib/neo4j/data" volumes: - name: neo4j-data persistentVolumeClaim: claimName: db-pv-claim EOF
在此部署中,我们使用了 NEO4J_AUTH 变量,并将其值设置为 none,以使用非认证方法,只是为了简化此示例。您还可以通过修改此配置,探索如何使用密钥和其他选项。
重要提示
如果删除 NEO4J_AUTH 变量,Neo4j 默认将用户名和密码设置为 neo4j。然后,登录后会弹出对话框要求您更改此密码。
-
创建服务以暴露 Neo4j 使用的
bolt、http和https端口:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: neo4j name: neo4j spec: ports: - name: https port: 7473 protocol: TCP targetPort: 7473 - name: http port: 7474 protocol: TCP targetPort: 7474 - name: bolt port: 7687 protocol: TCP targetPort: 7687 selector: app: neo4j type: ClusterIP EOF -
在连接到 Neo4j 浏览器之前,暴露
http和bolt端口。为此,请在不同的终端中运行以下命令:$ kubectl port-forward service/neo4j 7474:7474 $ kubectl port-forward service/neo4j 7687:7687 -
在浏览器中打开
http://localhost:7474页面,选择 认证类型:无认证,然后点击 连接 按钮:

图 10.2 – Neo4j 登录页面
然后,您将看到 Neo4j 用户界面:

图 10.3 – Neo4j 主页
-
在浏览器顶部的 Neo4j 终端(显示为
neo4j$)中运行一个简单示例。为此,添加接下来的命令并通过点击蓝色三角按钮运行:CREATE (IronMan:Hero{name: "Tony Stark"}) CREATE (Thanos:Villainous {name: "Thanos"}) CREATE (Thanos)-[r:ENEMY_OF]->(IronMan) RETURN IronMan, Thanos
您将看到 Neo4j 如何可视化 Marvel 角色之间的关系:

图 10.4 – Neo4j 图形可视化
现在您已经通过这个基本示例了解了如何使用 Neo4j,接下来让我们回顾一下本章的总结,看看我们学到了什么。
总结
在本章中,我们学习了如何使用 CAP 定理来选择合适的数据库来存储数据。这个定理帮助我们在设计边缘分布式系统中的分布式数据存储时,考虑到一些重要的保证。在本章中,我们还学习了不同的关系型和非关系型数据库。我们获得了有关如何设置和部署各种数据库范式(如关系型数据库、键值数据库、文档导向数据库和图数据库)的实践知识。在下一章中,我们将重点介绍时间序列数据库 Prometheus,它以值和时间的形式存储数据,并可用于实现边缘设备的有用监控仪表板。
问题
以下是一些问题,帮助您验证新获得的知识:
-
如何通过 CAP 定理根据您的使用案例决定使用哪种数据库?
-
如何在 K3s 中部署 MySQL?
-
如何在 K3s 中部署 Redis?
-
如何在 K3s 中部署 MongoDB?
-
如何在 K3s 中部署 PostgreSQL?
-
如何在 K3s 中部署 Neo4j?
-
如何使用 PersistentVolumeClaims 在 K3s 中部署数据库?
-
如何使用 ConfigMaps 和 Secrets 在 K3s 中部署数据库?
进一步阅读
您可以参考以下资料,了解本章涵盖的更多主题:
-
数据库与 SQLite 快速概述:
medium.com/aiadventures/databases-and-quick-overview-of-sqlite-5b7d4f8f6174 -
数据库的 CAP 定理:一致性、可用性与分区容错性:
www.bmc.com/blogs/cap-theorem -
非关系数据与 NoSQL:
aloa.co/blog/relational-vs-non-relational-database-pros-cons -
CAP 定理:
devopedia.org/cap-theorem -
系统设计基础:什么是 CAP 定理?:
www.educative.io/blog/what-is-cap-theorem -
事务的 ACID 属性是什么?它们在数据工程中为何如此重要?:
www.keboola.com/blog/acid-transactions -
SQL 与 NoSQL 数据库:有什么区别?:
www.bmc.com/blogs/sql-vs-nosql -
从传统关系数据库到 NoSQL 数据库:大数据时代的数据库新纪元:
www.researchgate.net/publication/324922396_TRADITIONAL_RDBMS_TO_NOSQL_DATABASE_NEW_ERA_OF_DATABASES_FOR_BIG_DATA -
MySQL 客户端 K8s:
gist.github.com/vishnuhd/b8686197f855c00fa734bc5f1fedf078 -
运行单实例有状态应用程序:
kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application -
MySQL 8 管理员指南:
www.packtpub.com/product/mysql-8-administrator-s-guide/9781788395199 -
使用 ConfigMap 配置 Redis:
kubernetes.io/docs/tutorials/configuration/configure-redis-using-configmap -
Redis Essentials:
www.packtpub.com/product/redis-essentials/9781784392451 -
Kubernetes secrets:
kubernetes.io/fr/docs/concepts/configuration/secret -
七天内掌握七种 NoSQL 数据库:
www.packtpub.com/product/seven-nosql-databases-in-a-week/9781787288867 -
如何使用 Kubernetes 部署 PostgreSQL:
www.sumologic.com/blog/kubernetes-deploy-postgres -
PostgreSQL 14 管理手册:
www.packtpub.com/product/postgresql-14-administration-cookbook/9781803248974 -
物联网和数据:强大的连接:
neo4j.com/news/internet-things-data-powerful-connection -
为什么不使用 SQLite:
stackoverflow.com/questions/66950385/how-to-use-sqlite3-database-with-django-on-kuberenets-pod -
使用 Python、Neo4j、Gephi 和 Linkurious.js 创建图形应用程序:
linkurious.com/blog/creating-a-graph-application-with-python-neo4j-gephi-and-linkurious-js
第三部分:实际边缘计算使用案例
在这一部分,您将学习如何在不同的使用情景下使用 k3s 和 k3OS,探索构建边缘计算系统的互补软件和最佳实践。
本书的这一部分包括以下章节:
-
第十一章,使用 Prometheus 和 Grafana 监控边缘
-
第十二章,使用 LoRa 远程通信边缘设备
-
第十三章,使用 GPS、NoSQL 和 K3s 集群的地理定位应用
-
第十四章,使用 Python 和 K3s 集群进行计算机视觉
-
第十五章,设计你自己的边缘计算系统
第十一章:使用 Prometheus 和 Grafana 监控边缘设备
边缘计算的一个应用场景是监控获取温度、湿度、速度、噪声等数据的设备。对于这种类型的用例,监控至关重要。本章展示了一个简单的用例,说明如何可视化来自边缘设备传感器的数据。本章提供了一个完整的示例,展示了如何在边缘计算系统的不同层次间分发和处理数据。该用例以 Prometheus 和 Grafana 为主要组件,来可视化和存储来自传感器的数据,并使用 Mosquitto(MQTT 消息代理)与 Redis 结合,实现高可用队列来处理边缘数据。
在本章中,我们将涵盖以下主要内容:
-
监控边缘环境
-
部署 Redis 以持久化 Mosquitto 传感器数据
-
安装 Mosquitto 以处理传感器数据
-
处理 Mosquitto 主题
-
安装 Prometheus,一个时间序列数据库
-
部署 Prometheus 的自定义 exporter
-
配置 DHT11 传感器,以发送湿度和温度数据
-
安装 Grafana 以创建仪表盘
技术要求
为了部署本章中的数据库,你需要以下内容:
-
一个单节点或多节点的 K3s 集群,使用带有 MetalLB 和 Longhorn 存储的 ARM 设备。如果你使用 Raspberry Pi 设备,你需要至少 4GB 的 RAM,并且至少是 4B 型号。每个节点必须运行 Ubuntu ARM64 操作系统,以支持 ARMv8 处理器。这种处理器类型是某些部署运行所必需的,因为它们使用 ARM64 容器镜像。
-
一个托管在你的公共云提供商(AWS、Azure 或 GCP)或私人云中的 Kubernetes 集群。
-
配备 2GB 或 4GB 的 Raspberry Pi 4B 作为边缘设备。
-
连接到你的边缘设备的 Keyes DHT11 传感器或类似设备,用于读取温度和湿度。
-
配置好
kubectl,以便在你的本地机器上使用 Kubernetes 云集群和边缘集群,避免使用--kubeconfig参数。 -
如果你想通过使用
kubectl apply运行 YAML 配置,而不是从书中复制代码,请克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch11仓库。查看code目录获取 Python 源代码,查看yaml目录获取位于ch11目录中的 YAML 配置文件。
这样,你就可以部署 Prometheus 和 Grafana,开始监控边缘环境中的传感器数据。
监控边缘环境
在开始构建我们的监控系统之前,让我们描述一下边缘计算不同层次的系统架构。为此,我们来看看以下图表:

图 11.1 – 使用边缘设备进行监控
该图分为不同的层次。让我们描述一下我们想要实现的这个用例的不同组件:
-
send.py。该文件准备传感器读取数据,并将信息发送到 Mosquitto 经纪人中的队列。 -
sensor1。每次该进程检测到新数据时,这些数据会被发送到云层中的名为sensor1的 Redis 队列。目的是让名为process的部署处理以适合在云层中显示的格式的数据。通过这样做,你是在边缘附近处理数据;这是边缘计算的目标。 -
近端边缘:这是连接边缘设备与 K3s 集群以处理数据的家庭路由器。它也是将数据发送到云层公共 Redis 集群的网关。
-
service1负责从sensor1Redis 队列中读取数据,并将其导出到 Prometheus。Prometheus 调用service1服务端点以获取数据。因此,每次 Prometheus 调用app1端点时,service1会返回存储在 Redis 中的数据,格式适合 Prometheus 消耗。最后,当数据存储在 Prometheus 中时,数据会在 Grafana 仪表板上实时可视化。
如你所见,这个小型用例包括了不同边缘计算层次之间的完整交互。这个用例旨在作为基础代码,可以根据你的系统需求进行扩展。现在,让我们开始实现我们的用例,从部署 Redis 来持久化 Mosquitto 传感器数据开始。
部署 Redis 来持久化 Mosquitto 传感器数据
为了安装我们的 Redis 以持久化 Mosquitto 天气数据,我们将使用带有持久化功能的 Redis,并使用单一的消息列表。要在集群中部署这个 Redis 设置,请按照以下步骤操作:
-
使用节点中的
/mnt/data目录创建 PersistentVolume,以持久化 Redis 数据:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolume metadata: name: db-pv-volume labels: type: local spec: storageClassName: manual capacity: storage: 5Gi accessModes: - ReadWriteOnce hostPath: path: "/mnt/data" EOF -
使用 5 GB 或更多存储空间创建一个 PersistentVolumeClaim,具体取决于你正在处理的传感器数量和数据量:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim spec: storageClassName: manual accessModes: - ReadWriteOnce resources: requests: storage: 5Gi EOF
重要提示
如果你的系统中安装了 Longhorn,可以使用 longhorn 类。有关更多信息,请参见 第五章,K3s 家庭实验室用于边缘计算实验。
-
现在,让我们创建一个 ConfigMap,使用自定义配置,其中包括密码
YOUR_PASSWORD和用于存储 Redis 数据的/data目录:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: redis-configmap namespace: monitoring data: redis-config: | dir /data requirepass YOUR_PASSWORD EOF -
使用之前创建的 ConfigMap
redis-configmap创建 Redis 部署。该 ConfigMap 被挂载为一个卷,其内容可通过redis.conf文件访问。它还使用一个名为db-pv-claim的 PersistentVolumeClaim,并使用 CPU 和内存的资源限制。我们通过运行以下命令来创建这个部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: run: redis name: redis namespace: monitoring spec: replicas: 1 selector: matchLabels: run: redis template: metadata: labels: run: redis spec: containers: - name: redis image: redis:6.2 command: - redis-server - /redisconf/redis.conf ports: - containerPort: 6379 resources: limits: cpu: "0.2" memory: "128Mi" volumeMounts: - mountPath: "/data" name: redis-storage - mountPath: /redisconf name: config volumes: - name: config configMap: name: redis-configmap items: - key: redis-config path: redis.conf - name: redis-storage persistentVolumeClaim: claimName: db-pv-claim EOF
重要提示
如果你计划在 ARM 节点上部署 Redis,可以使用 arm64v8/redis:6.2 镜像,而不是 redis:6.2。
-
现在创建
redis服务,在配置中设置端口6379:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: run: redis name: redis namespace: monitoring spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: run: redis type: ClusterIP EOF
该服务将被 Prometheus 的导出器 service1 使用。
-
现在创建一个名为
redis-lb的LoadBalancer服务,创建一个公共负载均衡器,供process服务使用,以存储从远端边缘到云层的数据:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: run: redis name: redis-lb namespace: monitoring spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: run: redis type: LoadBalancer EOF
这将创建一个外部 IP 用于访问 Redis。
-
要获取前一个
LoadBalancer服务生成的公共 IP,请运行以下命令:$ EXTERNAL_IP="$(kubectl get svc redis-lb -n monitoring -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
此 IP 将被部署进程使用。
现在我们的 Redis 已经准备好在远程边缘使用。让我们安装 Mosquitto,将来自 Mosquitto 的传感器数据发送到 sensor1 主题。
安装 Mosquitto 以处理传感器数据
Mosquitto 是一个开源代理,实施 MQTT 协议,而且它也非常轻量。它被设计用来与低功耗传感器和设备一起使用。这使得 Mosquitto 非常适合边缘计算和物联网应用。Mosquitto 为边缘设备提供了轻量级的通信通道,并使用发布/订阅模式来发送和读取消息,但它并不持久化。我们将稍后使用 Redis 来为数据队列提供缺失的临时持久性。现在,让我们继续在远程边缘的边缘集群中安装 Mosquitto。请记住,这个单节点集群使用的是 ARM 设备。要部署 Mosquitto,请遵循以下步骤:
-
创建一个 ConfigMap,用于监听所有可用的网络接口:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: mosquitto-configmap data: mosquitto-config: | listener 1883 0.0.0.0 allow_anonymous true EOF -
现在为 Mosquitto 创建一个部署,将端口设置为
1883用于 MQTT 协议,9001用于 HTTP 请求。该部署将使用之前创建的mosquitto-configmap:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: mosquitto name: mosquitto spec: replicas: 1 selector: matchLabels: app: mosquitto template: metadata: labels: app: mosquitto spec: containers: - name: mosquitto image: arm64v8/eclipse-mosquitto:2.0.14 ports: - containerPort: 1883 name: mqtt - containerPort: 9001 name: http resources: limits: cpu: "0.2" memory: "128Mi" volumeMounts: - mountPath: /mosquitto/config name: config volumes: - name: config configMap: name: mosquitto-configmap items: - key: mosquitto-config path: mosquitto.conf EOF
你可以自定义该部署所使用的 RAM 和 CPU 的数量。
-
现在创建一个
ClusterIP服务来暴露 Mosquitto,以便集群内的其他服务可以连接到 Mosquitto 以读取消息:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: mosquitto name: mosquitto spec: ports: - name: mqtt port: 1883 protocol: TCP targetPort: 1883 - name: http port: 9001 protocol: TCP targetPort: 9001 selector: app: mosquitto type: ClusterIP EOF -
现在创建一个 LoadBalancer 服务来暴露 Mosquitto,以便边缘设备可以连接到 Mosquitto,发布带有天气指标的消息。在此示例中,我们的设备将发布到
sensor1主题:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: mosquitto name: mosquitto-lb spec: ports: - name: mqtt port: 1883 protocol: TCP targetPort: 1883 - name: http port: 9001 protocol: TCP targetPort: 9001 selector: app: mosquitto type: LoadBalancer EOF
现在,让我们部署 process 服务,将 Mosquitto 主题中存储的所有天气数据发送到云层中的 Redis 数据库。
处理 Mosquitto 主题
我们需要使用 mqttsubs 容器镜像来部署名为 process 的部署,该镜像将 Mosquitto 中发布的数据发送到云层中的公共或私有 Redis 实例。让我们来看看这个容器镜像中的代码:
import paho.mqtt.client as mqtt
import os
import redis
import sys
mqhost = os.environ['MOSQUITTO_HOST']
rhost = os.environ['REDIS_HOST']
rauth = os.environ['REDIS_AUTH']
stopic = os.environ['SENSOR_TOPIC']
def on_connect(client, userdata, flags, rc):
client.subscribe(stopic)
def on_message(client, userdata, msg):
r = redis.StrictRedis(host=rhost,\
port=6379,db=0,password=rauth,\
decode_responses=True)
r.rpush(stopic,msg.payload)
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(mqhost, 1883, 60)
client.loop_forever()
注意
你可以在 github.com/sergioarmgpl/containers/tree/main/mqttsubs/src 找到 mqttsubs 的源代码。
通过这段代码,我们获取连接 Redis 所需的值,以及我们将要使用的主题名称。这个值将用于将传感器数据推送到 Redis 列表中。最后,MOSQUITTO_HOST 是该服务将要监听的地方。这个脚本的基本功能是开始监听名为 sensor1 的 SENSOR_TOPIC 主题,当消息到达时,它会将其插入到云层中的同名 Redis 列表中,以便临时持久化信息。Redis 使用端口 6379,是公共的,但需要密码。Mosquitto 部署在远端边缘。这就是该服务的工作原理。
要开始部署我们的 process 部署,按照以下步骤操作:
-
创建一个 Secret 来存储连接 Redis 的密码。Redis 将用于存储来自我们 Mosquitto 部署的所有信息:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: db-password data: password: WU9VUl9QQVNTV09SRA== EOF
密码的值对应于使用 base64 编码的下一条命令的输出:
$ echo "YOUR_PASSWORD" | tr -d '\n' | base64
-
创建
process部署,该部署接收来自 Mosquitto 主题的数据,并将其发送到位于云层的 Redis 服务。为此,运行以下命令:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: process name: process spec: replicas: 1 selector: matchLabels: app: process template: metadata: labels: app: process spec: containers: - image: sergioarmgpl/mqttsubs imagePullPolicy: Always name: mqttsubs env: - name: MOSQUITTO_HOST value: "mosquitto" - name: REDIS_HOST value: "192.168.0.242" - name: REDIS_AUTH valueFrom: secretKeyRef: name: db-password key: password - name: SENSOR_TOPIC value: "sensor1" EOF
使用的变量如下:
-
MOSQUITTO_HOST: 这是 Mosquitto 部署正在监听的主机名。
-
REDIS_HOST: 这是分配给 LoadBalancer 服务的 IP 地址,用于在云中公开 Redis。
-
db-password秘密值,用于设置连接 Redis 的密码。 -
SENSOR_TOPIC: 该变量设置要监听的 Mosquitto 主题,以便从传感器获取数据。
如果你使用的是私有云,你可能会使用类似 192.168.0.242 的 IP 地址。例如,你可以通过阅读 部署 Redis 来持久化 Mosquitto 传感器数据 部分来获取这个 IP 地址。然后,将 REDIS_HOST 的 IP 地址改为这个值。
我们已经完成了这一部分并理解了数据是如何处理的。接下来,让我们继续部署 Prometheus 服务,用于存储来自临时 Redis 列表的传感器数据。
安装 Prometheus,这是一个时间序列数据库。
Prometheus 是一个时间序列数据库,可以用来存储天气数据。它是开源的,适用于边缘设备。可以在 ARM 设备上部署,并且非常灵活,适用于管理指标和警报。在这个使用场景中,我们选择使用 Prometheus 是因为它的灵活性以及它对存储和可视化指标的支持。但我们稍后将使用 Grafana 来可视化数据。现在,让我们按照以下步骤在我们的 Kubernetes 云集群中安装 Prometheus:
-
创建
monitoring命名空间,该命名空间将用于安装 Prometheus 和 Grafana:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: monitoring EOF -
创建一个 ConfigMap,其中包含 Prometheus 的静态配置。在此案例中,我们将创建两个向 Prometheus 插入数据的服务:一个存储计数器和天气数据。第一个服务叫做
service1,第二个叫做service2。每个服务使用端口5555。我们称这个 ConfigMap 为prometheus-server-conf。要创建它,请运行以下命令:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: prometheus-server-conf labels: name: prometheus-server-conf namespace: monitoring data: prometheus.yml: |- global: scrape_interval: 5s evaluation_interval: 5s external_labels: monitor: 'codelab-monitor' scrape_configs: - job_name: 'MonitoringJob1' scrape_interval: 5s static_configs: - targets: ['service1:5555'] EOF
目标是以 Prometheus 能够读取的格式导出数据的服务。在这种情况下,我们使用两个服务。service1 导出来自 sensor1 的数据;这些数据由 Redis 收集并转换,以供 Prometheus 使用。在此用例中,我们只使用 service1,但你可以根据需要创建任意多的服务。
-
现在创建 Prometheus 的部署,使用之前的 ConfigMap 配置 Prometheus,在创建时进行配置,运行以下命令:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: prometheus-deployment namespace: monitoring labels: app: prometheus-server spec: replicas: 1 selector: matchLabels: app: prometheus-server template: metadata: labels: app: prometheus-server spec: containers: - name: prometheus image: prom/prometheus:v2.34.0 args: - "--storage.tsdb.retention.time=12h" - "--config.file=/etc/prom/prometheus.yml" - "--storage.tsdb.path=/prometheus/" ports: - containerPort: 9090 resources: requests: cpu: 500m memory: 500M limits: cpu: 1 memory: 1Gi volumeMounts: - name: prometheus-config-volume mountPath: /etc/prom/ - name: prometheus-storage-volume mountPath: /prometheus/ volumes: - name: prometheus-config-volume configMap: defaultMode: 420 name: prometheus-server-conf - name: prometheus-storage-volume emptyDir: {} EOF
此部署监听端口 9090。该端口用于连接到 Prometheus。
重要说明
你可以使用相同的 YAML 文件在使用云提供商(如 GCP、AWS 或 Azure)部署的 Kubernetes 集群中部署 Prometheus。
-
现在创建一个 ClusterIP 服务,将端口
9090重定向到 Prometheus 的端口8080:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: prometheus-server name: prometheus-service namespace: monitoring spec: ports: - port: 8080 protocol: TCP targetPort: 9090 selector: app: prometheus-server type: ClusterIP EOF -
让我们通过使用
port-forward来访问 Prometheus 的 UI。为此,运行以下命令:$ kubectl port-forward svc/prometheus-service 8080 -n monitoring --address 0.0.0.0 -
访问 http://localhost:8080;你将看到以下页面:

图 11.2 – Prometheus 主页面
- 现在转到 状态 | 目标 菜单:

图 11.3 – 状态菜单
你将看到以下页面:

图 11.4 – Prometheus 与目标服务关闭
在此页面上,你会看到监控任务当前处于 关闭 状态,因为服务尚未创建。在集群中创建这些监控服务后,状态将会变为 开启,并显示为绿色。
现在 Prometheus 部署已准备好。让我们在云层中安装我们自定义的导出器,将 Redis 列表中的临时传感器数据导出到 Prometheus。
为 Prometheus 部署自定义导出器
配置所有组件后,你需要部署 Prometheus 调用的导出器来从 Redis 获取数据;这个服务将被命名为 service1。记住,Redis 被用来持久化来自远端 Mosquitto 主题的临时数据。在部署此服务之前,让我们了解 exporter 容器的源代码:
from flask import Response, Flask, request, jsonify
import prometheus_client
from prometheus_client import Gauge
import redis
import os
import sys
import json
t = Gauge('weather_metric1', 'temperature')
h = Gauge('weather_metric2', 'humidity')
rhost = os.environ['REDIS_HOST']
rauth = os.environ['REDIS_AUTH']
stopic = os.environ['SENSOR_TOPIC']
r = redis.StrictRedis(host=rhost,\
port=6379,db=0,password=rauth,\
decode_responses=True)
@app.route("/metrics")
def metrics():
data = r.lpop(stopic)
values = json.loads(str(data).replace("\'","\""))
t.set(int(values["temperature"]))
h.set(int(values["humidity"]))
res = []
res.append(prometheus_client.generate_latest(t))
res.append(prometheus_client.generate_latest(h))
print({"processed":"done"},file=sys.stderr)
return Response(res, mimetype="text/plain")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5555, debug=True)
在这段使用 Python 编写的代码中,首先我们设置了 REDIS_HOST 和 REDIS_AUTH 变量来连接 Redis,并将 SENSOR_TOPIC 设置为 Redis 中存储传感器数据的列表名称。因此,每次 Prometheus 调用 /metrics 路径时,它会提取并返回 Redis 列表中由 SENSOR_TOPIC 指定的一个元素,并以 Prometheus 可读取的格式返回响应。为此,代码使用了 prometheus_client 库,并通过 Gauge 指标类型设置了两个指标,Gauge 表示简单值。在这段代码中,我们使用了两个指标:第一个名为 weather_metric1,它包含温度值,第二个名为 weather_metric2,它包含湿度数据。一旦数据被存储在 Prometheus 中,它将返回 JSON 响应 {"processed":"done"};之后,您可以在 Prometheus 中访问这些信息。或者,您也可以将 Prometheus 连接到 Grafana,以实时创建新图表来展示这些数据。
重要提示
您可以在 github.com/sergioarmgpl/containers/tree/main/exporter/src 找到导出器的源代码。
现在让我们按照以下步骤部署导出器:
-
通过创建
service1部署来创建导出器:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: service1 name: service1 namespace: monitoring spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 annotations: prometheus.io/scrape: "true" prometheus.io/path: /metrics prometheus.io/port: "5555" spec: containers: - image: sergioarmgpl/exporter name: exporter env: - name: REDIS_HOST value: "redis" - name: REDIS_AUTH value: "YOUR_PASSWORD" - name: SENSOR_TOPIC value: "sensor1" EOF
您可以使用机密信息来代替 YAML 文件中明文密码的使用。
-
现在创建
service1服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: service1 name: service1 namespace: monitoring spec: ports: - port: 5555 protocol: TCP targetPort: 5555 selector: app: service1 type: ClusterIP EOF
如果您返回到 Prometheus 的目标页面,service1 将显示为运行中并且是绿色的。
现在导出器已经运行。接下来是配置边缘设备中的 Python 脚本,以获取来自 DHT11 传感器的数据并将其发送到 Mosquitto 主题。我们将在下一节中探索这一部分。
配置 DHT11 传感器以发送湿度和温度天气数据
在开始使用带有 DHT11 传感器的边缘设备发送数据之前,您需要按照以下步骤进行配置:
-
在您的 Raspberry Pi 上安装至少 Ubuntu 20.04 LTS。您可以查看 第二章,《K3s 安装与配置》,以及 第五章,《K3s 边缘计算实验的家庭实验室》了解更多信息。
-
配置您的 DHT11 传感器将数据发送到 Raspberry Pi。对于这个用例,我们将使用来自 Keystudio Raspberry Pi 4B 完整 RFID 启动包的 DHT11 Keyes 传感器。这是一个常见的传感器,您也可以在其他品牌中找到。该传感器可以测量温度和湿度。它通常有三根引脚,分别是 G = 地线,V = VCC 和 S = 信号。连接方法是将 G 连接到 Raspberry Pi 的地线引脚,将 V 连接到 3V3 引脚,用于为传感器提供 3 伏电压。S 用于信号传输,通过 GPIO 引脚将信息发送到 Raspberry Pi。在这种配置中,您可以使用任何空闲的 GPIO 引脚;本例中我们使用的是 GPIO22 引脚:

图 11.5 – DHT11 Keyes 温湿度传感器
-
现在,通过运行以下命令在你的边缘设备上安装我们需要的系统和 Python 库,以运行传感器代码:
-
如果你的 Linux 发行版中没有安装
python3,可以使用以下命令安装:$ sudo apt-get install python3 -y -
然后继续安装所需的库:
$ sudo apt-get install libgpiod2 git -y $ sudo python3 sensor.py $ sudo pip3 install adafruit-circuitpython-dht $ sudo pip3 install psutil $ sudo apt-get install i2c-tools
-
-
克隆代码库:
$ git clone https://github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes $ cd Edge-Computing-Systems-with-Kubernetes/ch11/code -
运行以下命令:
$ sudo python3 send.py
重要提示
在你的边缘设备上,只有在用例的所有组件都部署完成后,才运行 send.py 代码。
现在,你开始从边缘设备发送数据了。但在 send.py 代码中发生了什么呢?我们来看看:
import time
import board
import adafruit_dht
import psutil
import paho.mqtt.client as mqtt
import sys
for proc in psutil.process_iter():
if proc.name() == 'libgpiod_pulsein'
or proc.name() == 'libgpiod_pulsei':
proc.kill()
sensor = adafruit_dht.DHT11(board.D22)
mqhost="192.168.0.243"
client = mqtt.Client()
client.connect(mqhost, 1883, 60)
client.loop_start()
def main():
while True:
t = sensor.temperature
h = sensor.humidity
client.publish("sensor1",\
str({"t":int(t),"h":int(h)}))
time.sleep(2)
try:
main()
except KeyboardInterrupt:
pass
finally:
sensor.exit()
在这段代码中,首先验证树莓派是否能够从 GPIO 引脚读取数据。然后,通过使用 Adafruit 库,我们设置树莓派的 GPIO22 引脚来读取传感器的数据。之后,我们使用 Mosquitto 主机的 IP 地址配置 Mosquitto 服务,该服务监听负载均衡器的 IP 地址。最后,我们启动一个循环来读取 sensor 变量的数据。该数据被发送到 Mosquitto 的 sensor1 主题中。循环每 2 秒钟发送一次数据。
如果你按 Ctrl + C,代码会停止并执行 sensor.exit() 来关闭传感器并清理传感器状态。最后,你开始发送数据。此时,所有数据通过远端的 Mosquitto 传输,并传送到云层中的 Redis 和 Prometheus。唯一缺少的是 Grafana 来可视化这些数据。为此,我们继续下一部分。
安装 Grafana 以创建仪表板
Grafana 是一个网页应用程序,可以用来可视化来自不同数据源的数据;它还可以基于你可视化的数据创建警报。在我们的用例中,Grafana 将用于可视化来自 Prometheus 的数据。让我们记住,Prometheus 正在监听 service1,以获取来自远端 Mosquitto 的数据。要部署 Grafana,请按照以下步骤操作:
-
首先,创建一个 ConfigMap 来配置你的 Grafana 部署:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: grafana-datasources namespace: monitoring data: prometheus.yaml: |- { "apiVersion": 1, "datasources": [ { "access":"proxy", "editable": true, "name": "prometheus", "orgId": 1, "type": "prometheus", "url": "http://prometheus-service.monitoring.svc:8080", "version": 1 } ] } EOF
这将是你在 grafana 部署中配置的默认数据源。
-
让我们通过运行以下命令来创建
grafana部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: grafana namespace: monitoring spec: replicas: 1 selector: matchLabels: app: grafana template: metadata: name: grafana labels: app: grafana spec: containers: - name: grafana image: grafana/grafana:8.4.4 ports: - name: grafana containerPort: 3000 resources: limits: memory: "1Gi" cpu: "1000m" requests: memory: 500M cpu: "500m" volumeMounts: - mountPath: /var/lib/grafana name: grafana-storage - mountPath: /etc/grafana/provisioning/datasources name: grafana-datasources readOnly: false volumes: - name: grafana-storage emptyDir: {} - name: grafana-datasources configMap: defaultMode: 420 name: grafana-datasources EOF -
创建服务:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: grafana name: grafana namespace: monitoring spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: grafana type: ClusterIP EOF -
通过运行以下命令打开 Grafana UI:
$ kubectl port-forward svc/grafana 3000 -n monitoring --address 0.0.0.0 -
打开 URL
http://localhost:3000。当登录页面出现时,使用用户名admin和密码admin,然后点击 Log in 按钮:

图 11.6 – Grafana 登录
- 登录后,你将看到 Grafana 的主页面:
![图 11.7 – Grafana 主页面]()
图 11.7 – Grafana 主页面
- 点击 Configuration | Data sources:

图 11.8 – Grafana 配置菜单
- 然后,检查 Prometheus 数据源是否存在:

图 11.9 – Grafana 数据源
由于我们的 ConfigMap 配置,我们的默认数据源将是prometheus-service.monitoring.svc:8080。
- 现在使用+图标创建一个新文件夹或仪表盘。我们先创建一个文件夹:

图 11.10 – Grafana 创建菜单
- 现在在打开的对话框中填写
Dashboard Sensors,以此名称创建文件夹,然后点击创建按钮:

图 11.11 – Grafana 新仪表盘文件夹对话框
如果需要,你可以使用这个文件夹来保存你的仪表盘和警报。
- 如同图 11.10所示,按照和文件夹相同的步骤操作,但这次点击仪表盘。你将看到图 11.12页面。点击添加新面板按钮:

图 11.12 – Grafana 添加面板页面
- 在下图中,你将看到配置新仪表盘的设置:

图 11.13 – Grafana 新仪表盘/编辑面板页面
在这里,你可以通过设置查询的主要部分来配置此面板。在此情况下,你需要写weather_metric1或weather_metric2。其中,weather_metric1获取温度,weather_metric2获取湿度。
- 设置时间范围以可视化数据。然后,点击应用时间范围:

图 11.14 – Grafana 绝对时间范围对话框
- 在下一个对话框中,将刷新时间设置为 5 秒,查询选项:

图 11.15 – 设置实时数据值
另外,你可以点击刷新图标:

图 11.16 – Grafana 设置刷新时间
- 然后,点击
Dashboard sensors或Temperature Sensor1:

图 11.17 – 保存新仪表盘
- 你也可以通过点击应用按钮来应用更改,而不是点击保存按钮:

图 11.18 – 应用更改到新仪表盘
- 现在你将看到你的仪表盘:

图 11.19 – Grafana 温度传感器 1 仪表盘
- 你可以通过点击搜索仪表盘图标查看你创建的仪表盘:

图 11.20 – 搜索仪表盘
现在你可以开始可视化你的边缘设备生成的数据,如图 11.18所示。你可以根据需要自定义所有参数,以显示信息。你还可以修改代码,添加任意数量的传感器。我们现在已经完成了这一章。让我们快速总结一下我们学到的内容。
总结
在本章中,我们学习了监控如何帮助我们可视化边缘的数据,特别是如何可视化来自传感器的数据,以及如何构建一个基本的使用案例场景以扩展到生产环境。为了构建这个系统,我们使用 Prometheus 作为我们的时间序列数据库,Mosquitto 作为存储传感器数据的基本方式,Redis 作为临时队列以防止传感器数据的丢失。我们还实践了如何构建一个边缘计算系统,使用其从最远边缘到云层的不同层次。这展示了时间序列数据库在管理传感器数据中的重要性,以及 Grafana 等工具如何帮助可视化数据。这个场景还可以扩展到农业、海洋和海上监测、动物种群等领域。在下一章,我们将继续一个类似的场景,但应用于 GPS 和远程传感器数据读取。
问题
以下是一些问题,用于验证你新学到的知识:
-
我如何设置边缘设备来捕获传感器数据?
-
我如何使用 Prometheus 存储传感器数据?
-
我如何使用 Grafana 创建自定义图表来可视化传感器数据?
-
我如何设计一个持久化系统,使用 Mosquitto 和 Redis 管理传感器数据?
-
我如何使用 Python 处理并发送传感器数据?
深入阅读
你可以参考以下文献,获取本章所涵盖主题的更多信息:
-
Mosquitto 官方网站:
mosquitto.org -
Prometheus Python 客户端:
github.com/prometheus/client_python -
如何在 Kubernetes 集群上设置 Prometheus 监控
devopscube.com/setup-prometheus-monitoring-on-kubernetes -
如何在 Kubernetes 上设置 Grafana:
devopscube.com/setup-grafana-kubernetes -
开始使用 Prometheus:
prometheus.io/docs/prometheus/latest/getting_started -
使用 Prometheus 和 Grafana 进行物联网监控:
cloud.google.com/community/tutorials/cloud-iot-prometheus-monitoring -
设置 Prometheus Alertmanager 与 Slack、PagerDuty 和 Gmail 的分步指南:
grafana.com/blog/2020/02/25/step-by-step-guide-to-setting-up-prometheus-alertmanager-with-slack-pagerduty-and-gmail
第十二章:使用 LoRa 在长距离上与边缘设备通信
长距离(LoRa)是一种无线协议,您可以使用它通过低功耗设备在长距离上传输和接收数据。您可以将这些边缘设备与太阳能电池板或其他能源源一起使用。有时,您的边缘设备使用电池,并且没有像我们常在家中找到的那样连接到共同的电源。当您构建边缘系统时,您可以使用带有传感器的边缘设备,您需要配置这些传感器。您可以使用原型硬件平台,例如 Arduino,或设备如 ESP32 微控制器或 Raspberry Pi。这些设备支持 LoRa 模块,能够为您的设备提供通信功能,这对发送和接收设备之间的数据至关重要。在本章中,我们将探索如何利用 LoRa 无线协议从远距离发送或接收数据。我们将继续扩展上章中监控边缘设备的选项,但现在使用的是 LoRa 无线协议。
在本章中,我们将涵盖以下主要主题:
-
LoRa 无线协议与边缘计算
-
部署 MySQL 以存储传感器数据
-
部署服务以将传感器数据存储到 MySQL 数据库中
-
编程 ESP32 微控制器以发送传感器数据
-
编程 ESP32 微控制器以接收传感器数据
-
使用 MySQL 和 Grafana 可视化 ESP32 微控制器的数据
技术要求
在本章中部署我们的数据库,您将需要以下内容:
-
一个单节点或多节点的 K3s 集群,可以使用已安装 MetalLB 和 Longhorn 存储的 ARM 设备。这个示例将在 4 GB RAM 的 Raspberry Pi 4B 上测试,并使用 Ubuntu 20.04 或更高版本的 ARM 64 位系统。
-
托管在您的公共云提供商(AWS、Azure、GCP)或私有云中的 Kubernetes 集群。
-
2 个安装了 LoRa 模块的 ESP32 微控制器。我们使用的是 Heltec ESP32 + Lora v2 模型,一个用来发送数据,另一个用来接收数据。
-
在 Mac 上安装 Arduino IDE。您也可以使用 Windows 系统,因为它的配置与 Mac 类似,并且在与硬件配合使用时更稳定。
-
一条 USB 2.0 A 型公头到 Micro B 型线,用于编程您的 ESP32 设备。
-
一个 Keyes DHT11 传感器或类似设备,连接到您的边缘设备以读取温度和湿度。
-
已配置
kubectl以在您的本地机器上使用,用于您的 Kubernetes 云集群或 K3s 集群,从而避免使用--kubeconfig参数。 -
如果您希望使用
kubectl apply运行 YAML 配置文件,而不是从本书中复制代码,请克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch12中的 GitHub 仓库。查看code目录中 Heltec 设备的 Arduino 源代码和yaml目录中的 YAML 配置文件。这些文件位于ch12目录中。
现在,让我们了解一下使用 LoRa 设备、Prometheus 和 Grafana 的场景是如何工作的。
LoRa 无线协议与边缘计算
LoRa 是一种用于长距离的无线调制技术,结合 LoRaWAN,它定义了一种网络协议,可以用于设备间的互联。LoRaWAN 还是一种网络架构,采用起始-起始拓扑结构,其中网关在边缘设备之间转发消息。LoRa 使用三种常见频率:433、868 和 915。433 有时用于户外,868 用于欧洲,915 用于美洲。LoRaWAN 有网关设备,可以将 LoRa 网络连接到互联网。LoRa 设计为低功耗,这也是 LoRa 被用于物联网应用,以便在长距离间互联设备的原因。
正如我们所知道的,边缘计算的目标是处理接近数据源的数据。因此,LoRa 使我们能够实现边缘计算,并且在不消耗大量能量的情况下,实现远距离设备互联。LoRa 的一些应用场景包括农业、建筑物、供应链、物流、地理定位应用等。支持 LoRa 的一些常见设备是 Heltec ESP32 设备,这些设备设计上注重低功耗。在本章中,我们将重点配置支持 LoRa 的 Heltec ESP32 设备。
为了开始实现我们在小型边缘上使用 LoRa 与远端边缘 Kubernetes 集群进行交互的用例,让我们先来看看以下的图示:

图 12.1 – 使用 ESP32 设备和 LoRaWAN 进行监控
该图分为不同的层级。你可以看到数据如何从远端边缘(实现了 LoRa 通信)流向云层。首先,让我们描述一下我们要实现的这个用例中的不同组件:
-
小型边缘:在这一层,我们会找到 Heltec ESP32 设备,你可以将其归类为发送或接收数据的设备。这个设备发送数据,读取 DHT11 传感器的数据,并通过 LoRa 协议将信息以 JSON 格式发送出去。其他设备读取信息,通过 LoRa 发送,并通过局域网中的 POST 请求将数据发送到远端边缘的集群。你可以根据需要添加任意数量的设备来发送数据。
-
远端边缘:在这里,你会找到一个使用 ARM 设备的单节点或多节点 K3s 集群。这个集群提供度量服务,接收来自小型边缘的 JSON 格式数据。一旦数据被接收,度量服务会将这些数据写入云层中的 MySQL 部署,该 MySQL 部署位于云提供商提供的 Kubernetes 集群中。这个云提供商可以是 Amazon、GCP、Azure 等等。
-
近端边缘:这一层包含连接本地网络和互联网的本地路由器。请记住,远端边缘的集群作为网关,负责将 LoRa 网络中的数据发送到互联网。
-
云层:在这里,您可以找到包含 MySQL 和 Grafana 的 Kubernetes 集群。MySQL 存储来自本地传感器的数据,而 Grafana 使用 MySQL 来创建使用传感器数据的仪表板。
总结来说,所有的传感器数据来自 ESP32 设备,其中一些设备配备了传感器。这些设备使用 LoRa 协议发送和接收数据。当接收设备收到信息时,它会将其转换为 JSON 格式,然后发送到位于远程边缘的 Kubernetes 服务。此服务接收信息后,会将其转发到云层并存储在 MySQL 数据库中。MySQL 被 Grafana 用来实时展示边缘收集的传感器数据。现在,让我们部署我们的 MySQL 数据库来存储数据。
部署 MySQL 以存储传感器数据
在使用 LoRa 从设备存储数据之前,必须部署数据库。为此,我们将使用 MySQL。MySQL 是一个非常流行的数据库,您可以用它来存储来自传感器的数据。使用 MySQL 的主要优势在于它有良好的文档支持,您可以在互联网上找到许多示例。对于我们的部署,我们将使用 PersistentVolumeClaim 和 mysql:8.0.28-oracle 镜像。即使您决定将 MySQL 部署到云端或本地边缘,也必须使用 LoadBalancer 服务,以便为存储所有传感器数据的服务提供端点。为了简化实现,我们的 MySQL 数据库将在默认命名空间中部署。要部署我们的 MySQL 数据库,请按照以下步骤操作:
-
创建一个具有 5 GB 存储空间的
PersistentVolumeClaim:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi EOF
重要提示
不要忘记使用 ConfigMaps 和 Secrets 进行更安全和高级的配置。您可以查看 第十章,边缘的 SQL 和 NoSQL 数据库,了解更多详细信息。
-
现在,让我们部署我们的 MySQL 数据库。我们的部署将使用之前的
PersistentVolumeClaim,即db-pv-claim,进行此次运行:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: containers: - image: mysql:8.0.28-oracle name: mysql env: - name: MYSQL_DATABASE value: sensor_data - name: MYSQL_USER value: lora_mysql - name: MYSQL_PASSWORD value: lora123- - name: MYSQL_ROOT_PASSWORD value: lora123- ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: db-pv-claim EOF
在此部署中,我们使用了一些环境变量:
-
MYSQL_DATABASE:创建一个初始数据库 -
MYSQL_USER:为在MYSQL_DATABASE中定义的数据库创建一个超级管理员用户。 -
MYSQL_PASSWORD:为在MYSQL_USER变量中定义的用户设置密码。 -
MYSQL_ROOT_PASSWORD:为 root 用户设置密码
重要提示
如果您使用的是多节点集群,请使用 nodeSelector 选项,以防止与已提供的 PersistentVolumeClaim 发生问题。
-
现在,我们需要一个
ClusterIP服务。这将在 Grafana 中用作配置 MySQL 数据库的数据源:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null name: mysql spec: ports: - port: 3306 protocol: TCP targetPort: 3306 selector: app: mysql type: ClusterIP status: loadBalancer: {} EOF -
我们还需要一个
LoadBalancer服务来暴露 MySQL。此服务将用于将 MySQL 暴露给外部世界。可以通过互联网或使用本地网络中的 IP 地址进行暴露。无论哪种方式,配置的负载均衡器 IP 地址将用于 ESP32 设备中。这些 ESP32 设备将通过我们的指标服务向此端点发送信息,最终将传感器数据存储到 MySQL 中。让我们创建这个LoadBalancer服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null name: mysql-lb spec: ports: - port: 3306 protocol: TCP targetPort: 3306 selector: app: mysql type: LoadBalancer status: loadBalancer: {} EOF
现在 MySQL 正在运行,我们需要创建一个表来存储传感器数据。为此,请按照以下步骤操作:
-
创建一个 MySQL CLI 客户端,运行一些命令来创建存储数据的表格:
$ kubectl run client -it --rm --image=mysql:8.0.28-oracle -- bash
进入后,运行以下命令。系统会要求你输入密码。请使用 lora123- 作为密码:
$ mysql -u lora_mysql -h mysql -p
提示符将更改为类似mysql>的形式。
-
创建指标表并包含
device、temperature_c、temperature_f、humidity和time字段:use sensor_data; CREATE TABLE metric (device INT NOT NULL,temperature_c DECIMAL(4,2),temperature_f DECIMAL(4,2) NOT NULL,humidity DECIMAL(4,2) NOT NULL, time DATETIME NOT NULL);
首先,我们必须使用 use 命令选择 sensor_data 数据库,然后使用 CREATE TABLE 命令创建表格 metric。我们配置每个字段都必须有值。我们使用 DECIMAL(4,2),这意味着 4-2 = 2 个整数位和 2 位小数。我们使用 now() MySQL 函数的格式来存储数据,格式为 MONTH/DAY/YEAR HOUR:MINUTE:SECOND。
下面是对每个字段包含内容的简要说明:
-
device:表示发送传感器数据的 ESP32 Lora 设备编号。这个值应该是大于 0 的数字。 -
temperature_c:这是以摄氏度为单位测量的温度。 -
temperature_f:这是以华氏度为单位测量的温度。 -
humidity:环境湿度,按百分比表示。
-
使用 MySQL 中的
quit命令退出 MySQL 客户端。 -
使用
exit命令退出客户端。退出后,Pod 将被删除。 -
获取在
LoadBalancer服务中配置的 MySQL IP 地址。为此,请运行以下命令:$ MYSQL_IP="$(kubectl get svc mysql-lb -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')" $ echo $MYSQL_IP
echo 命令将显示你的 MySQL IP 地址。
现在我们的 MySQL 数据库已经部署并准备好使用,接下来让我们在远端边缘部署我们的指标应用程序,以便将数据存储到这个 MySQL 数据库中。
部署一个服务将传感器数据存储到 MySQL 数据库中。
对于此场景,我们需要部署一个服务将数据存储到先前部署的 MySQL 中。我们将其命名为 metrics。metrics 服务包含以下代码:
from flask import Flask, request
import mysql.connector
import os
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'It works'
def insert(data):
conn = mysql.connector.connect(
host=os.environ['HOST'],
user=os.environ['MYSQL_USER'],
password=os.environ['MYSQL_PASSWORD'],
database=os.environ['MYSQL_DATABASE']
)
cursor = conn.cursor()
sql = "INSERT INTO metric "+\
"(device,temperature_c,"+\
"temperature_f,humidity,time) "+\
"VALUES (%s,%s,%s,%s,now());"
val = (data["d"],data["t"],data["t_f"],data["h"])
cursor.execute(sql,val)
conn.commit()
cursor.close()
conn.close()
@app.route('/device',methods = ['POST'])
def device():
data = request.json
print(data)
#Process data in some way
t_farenheit = float(data["t"])*(9/5)+32
data["t_f"] = t_farenheit
insert(data)
return "processed"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)
该代码有两个端点:
-
/:这只是一个测试 URL。 -
/device:该端点从 POST 请求中获取数据,并通过调用insert函数将其写入 MySQL。
它还使用 insert(data) 函数将数据插入到部署在云层中的 MySQL 中。这个函数接收来自 LoRaWAN 的数据,并重新计算温度(华氏度)。一旦存储,数据将返回处理结果。
该脚本还使用以下环境变量:
-
HOST:定义 MySQL 监听的 IP 地址。 -
MYSQL_USER:用于连接数据库的用户名 -
MYSQL_PASSWORD:用于连接数据库的密码 -
MYSQL_DATABASE:metrics将用于存储数据的数据库名称。
我们在 Kubernetes 中的部署必须设置这些变量才能正常工作,否则会出现错误。
重要提示
你可以在 github.com/sergioarmgpl/containers/tree/main/metric 查看如何基于此示例构建容器的代码。
现在我们已经看过 metrics 服务的代码,让我们将 metrics 部署到设备上,开始将传感器数据存储到数据库中。为此,按照以下步骤操作:
-
部署我们的
metrics应用程序,使其处于运行状态:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: metrics name: metrics spec: replicas: 1 selector: matchLabels: app: metrics strategy: {} template: metadata: creationTimestamp: null labels: app: metrics spec: containers: - image: sergioarmgpl/metric name: metric env: - name: HOST value: "192.168.0.240" - name: MYSQL_USER value: "lora_mysql" - name: MYSQL_PASSWORD value: "lora123-" - name: MYSQL_DATABASE value: "sensor_data" resources: {} status: {} EOF
在此部署中,我们使用以下环境变量值:
-
HOST:这是为我们的 MySQL 创建的LoadBalancer服务的 IP 地址,也就是192.168.0.240。这是上一步中返回的 IP 地址。 -
MYSQL_USER:MySQL 用户名。在此情况下,用户名为lora_mysql。 -
MYSQL_PASSWORD:lora_mysql的密码。在此情况下,密码为lora123-。 -
MYSQL_DATABASE:用于存储传感器数据的 MySQL 数据库名称。在此情况下,名称为sensor_data。
重要提示
你可以根据需要自定义所有这些值。记住,你可以使用 ConfigMaps 或 Secrets 来保护你的部署。我们仅为简化实现而使用硬编码值。关于此类配置,请查看 第十章,边缘的 SQL 和 NoSQL 数据库。
-
现在,让我们为
metrics部署创建LoadBalancer服务。已分配的 IP 地址将被硬编码到我们 ESP32 设备的代码中。创建服务时,运行以下代码:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null name: metrics spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: metrics type: LoadBalancer status: loadBalancer: {} EOF -
要获取
metrics服务的已分配 IP 地址,运行以下命令:$ METRICS_IP="$(kubectl get svc metrics -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')" $ echo METRICS_IP
echo 命令将显示我们 metrics 应用程序的 IP 地址。记下这个值,因为它将在编程我们的 ESP32 设备时使用。假设此值为 192.168.0.241。
现在我们已经将 metrics 服务部署到远端边缘,接下来让我们配置 ESP32 设备,以便它们能够发送和接收数据。
编程 ESP32 微控制器以发送传感器数据
ESP32 是一款低成本、低功耗的微控制器芯片,是 ESP8266 微控制器的继任者。在本章中,我们将使用 Heltec ESP32 + LoRa,它是一个 ESP32 微控制器,带有使用 LoRa 无线协议的能力。此微控制器还可以使用集成的 SX1276 芯片通过 LoRa 无线协议发送和接收数据,能够在 Heltec 仪表板上进行操作。
在配置设备之前,我们需要做以下事情:
-
将 DHT11 传感器连接到 Heltec ESP32 + LoRa 设备。
-
安装 USB 到 UART 桥接驱动程序。
-
安装并配置 Arduino IDE 来编程 Heltec ESP32 + LoRa 设备。
-
刷写 Heltec ESP32 + LoRa 设备。
现在,让我们通过将 DHT11 传感器连接到我们的 Heltec ESP32 + LoRa 设备开始。
配置 Heltec ESP32 + LoRa 以读取 DHT11 传感器数据
Heltec 设备通常用于物联网,ESP32 是物联网和 LoRa 实现中非常流行的设备。你可以在heltec-automation-docs.readthedocs.io/en/latest找到 Heltec 设备的官方文档。对于我们的 LoRa 实现,我们将使用以下图示:

图 12.2 – Heltec ESP32 读取 DHT11 数据的示意图
要将你的 Heltec ESP32 与 DHT11 传感器连接,请按照以下步骤操作。这是发送设备:
-
连接你的 LoRa 天线。如果天线未连接,这可能会影响传输范围。
-
将电源连接到 USB 接口。你必须使用 USB 2.0 A 型公头转 Micro B 型线缆。
-
将你的
PIN 17通过导线连接到 DHT11 传感器的S输入端。 -
将 GND(地)连接到 DHT11 传感器的
G输入端。 -
将其中一个 3V3 电压输出连接到 DHT11 传感器的
V输入端。
对于你的接收设备,只需按照以下步骤操作:
-
连接你的 LoRa 天线。
-
将电源连接到 USB 接口,作为你的发送设备。
重要提示
记住,你需要使用一个能提供 3.5 或 5 伏电压的电源或电池。想了解更多信息,请查看heltec-automation-docs.readthedocs.io/en/latest/esp32/index.html。
这样,你的发送和接收设备就可以准备好上传代码了。现在,让我们安装所有需要的软件下载代码到设备中。
安装 USB 到 UART 桥接驱动程序
安装通用的 SiLabs CP210X 驱动程序时,该驱动程序将安装对 USB 到 UART 桥的支持,并识别你计算机上的设备。通过这种方式,你的 Heltec 设备可以连接到计算机,并使用串行通信与 Arduino IDE 进行交互。
要安装此驱动程序,你可以访问以下链接以获取详细的安装说明:heltec-automation-docs.readthedocs.io/en/latest/general/establish_serial_connection.html。在该页面,你可以找到有关如何在 Windows 和 Mac 上安装驱动程序的最新文档。
这些说明包括下载各种驱动程序:
-
对于 Windows:
www.silabs.com/documents/public/software/CP210x_Windows_Drivers.zip -
对于 Mac:
www.silabs.com/documents/public/software/Mac_OSX_VCP_Driver.zip
你必须根据你的系统按照向导安装驱动程序。一旦你安装了驱动程序,就可以将设备连接到系统,并检查是否成功检测到设备。对于 Mac,您可以执行以下命令:
$ ls /dev | grep cu | grep 'usbserial\|UART'
你将看到类似以下的输出:
cu.SLAB_USBtoUART
cu.usbserial-0001
这意味着你的设备已被检测到。可能会遇到一些常见问题,导致设备无法识别。这通常是因为你使用的电缆存在问题;尝试找到一根在你电脑上能正常识别设备的电缆。
对于 Windows 系统,你会看到你的设备出现在硬件管理器的端口部分。然后,设备会像下面这样显示:
- Silicon Labs CP210X USB 转 UART 桥接器(COM3)
这意味着你的 Heltec 设备已经成功检测到。
现在,既然系统已经识别到设备,就可以安装 Arduino IDE 来上传代码到你的 Heltec 设备了。
安装 Arduino IDE
Arduino IDE 是一款可以用来向开发板上传代码的软件。在这种情况下,我们使用的是 Heltec 设计的开发板,即我们所称的 Heltec 设备。要开始使用 Arduino IDE,请按照以下步骤操作:
- 通过访问
www.arduino.cc/en/software下载 Arduino IDE。选择你所使用的操作系统,支持 Windows、Linux 或 Mac。本章节我们将介绍 Mac 操作系统,使用的是 Arduino 1.8.19 版本。
重要说明
你也可以参考 Heltec 的官方网站,里面解释了如何为 Windows 和 Mac 安装 Heltec 驱动程序和 Arduino IDE。快速入门链接为heltec-automation-docs.readthedocs.io/en/latest/esp32/quick_start.html。
-
通过点击桌面上的图标或 Mac 上的启动板,打开 Arduino IDE。
-
点击文件 | 首选项,然后将
github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.5/package_heltec_esp32_index.json粘贴到附加板管理器 URL字段中。然后,点击确定。新的 ESP32 开发板将会加载到 Arduino IDE 中:

图 12.3 – 配置首选项以使用 Heltec ESP32 设备
- 现在,前往工具 | 开发板 | 开发板管理器:

图 12.4 – 板管理器菜单
- 在弹出的对话框中搜索
heltec,然后点击安装以进行安装:

图 12.5 – 在板管理器中搜索 heltec
你将看到类似以下的内容:

图 12.6 – 通过板管理器安装的 Heltec 开发板
- 现在,通过进入工具 | 板 | Heltec ESP32 Arduino并选择WiFi LoRa 32(V2)来选择开发板:

图 12.7 – 设置 WiFi LoRa 32(V2)为默认开发板
- 现在,通过进入工具 | 管理库,搜索 Adafruit 的DHT 传感器库,并选择Heltec ESP32 Dev-Boards。确保安装它。
这样,我们的 Arduino IDE 就准备好了。现在,让我们了解一些配置,以防出现错误。
使用 Heltec ESP32 + LoRa 时的 Arduino IDE 故障排除
macOS 可能会遇到一些挑战,这取决于你的 Mac 版本,但你可以解决这些问题。
其中一个问题是 esptool Python 库。为了解决它,按照以下步骤操作:
-
将当前的
esptool.py文件复制到tools文件夹中。命令将如下所示:$ cp /Users/<YOUR_USER>/Library/Arduino15/packages/Heltec-esp32/hardware/esp32/<X.X.X> /tools/esptool.py /Users/ <YOUR_USER> /Library/Arduino15/packages/Heltec-esp32/tools/esptool_py/<X.X.X>/ -
更改
esptool.py文件的权限:$ chmod +x esptool.py
运行esptool.py文件:
$ ./esptool.py
-
有时,如果上一个命令返回错误,你需要安装串行库。为此,你有几个选项。一个是从头开始安装库,方法是访问
github.com/pyserial/pyserial/releases。在这种情况下,我们使用的是 3.4 版本。为此运行以下命令:$ wget https://github.com/pyserial/pyserial/archive/refs/tags/v3.4.zip $ sudo python setup.py install -
如果
./esptool.py命令返回错误,再次尝试。 -
根据你电脑上安装的 Python 版本,你可以尝试这个其他的解决方案:
$ sudo pip install pyserial or $ sudo pip3 install pyserial -
最后的一个解决方案是使用
easy_install来安装 pyserial 库。为此,运行以下命令:$ sudo easy_install pyserial
重要提示
macOS Monterrey 默认删除 Python 2.7,因此你必须安装该 Python 版本。你也可以使用 Python 3,但你需要使用open /Applications/Arduino.app命令打开 Arduino IDE。你可以通过观看以下视频,找到更详细的解决方案:www.youtube.com/watch?v=zkyoghpT8_U。
另一个需要修复的问题是,Heltec 安装了自己的 Wi-Fi 库,这取决于你的 Arduino 版本。因此,当你尝试编译和上传程序时,有时会看到一些错误。为避免这些错误,你有两个选项:
-
通过进入工具 | 管理库卸载默认的 Arduino Wi-Fi 库。然后,找到 Arduino 的 Wi-Fi 库并将其卸载。
-
使用以下命令删除或重命名默认的 Arduino Wi-Fi 库文件夹:
$ cd /Applications/Arduino.app/Contents/Java/libraries $ mv libraries/WiFi
重要提示
在 Windows 上,安装过程很顺利,因此你不需要修复这种问题。
现在,是时候使用 Arduino IDE 将代码上传到设备上了。
上传代码到 ESP32 微控制器以发送传感器数据
现在,让我们将代码上传到 Heltec 设备。首先从发送设备开始。该设备将从 DHT11 传感器捕获数据,并通过 LoRa 无线协议将其发送到接收设备。我们可以通过点击文件 | 新建来创建一个新文件。默认情况下,你将看到类似以下内容的内容:
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
现在,用以下代码替换它:
#include "heltec.h"
#define BAND 915E6
#include "DHT.h"
#define DHTPIN 17
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
#define DEVICE 1
#define DELAY 3000
void setup()
{
Heltec.begin(false,true,true,true,BAND);
Serial.begin(9600);
LoRa.setSyncWord(0xF3);
Serial.println("LoRa started");
dht.begin();
}
void sendTH()
{
String values = "";
LoRa.beginPacket();
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println(F("Failed to get data from sensor"));
return;
}
String hS = (String)h;
String tS = (String)t;
String dS = (String)DEVICE;
values = "{\"t\":"+tS+",\"h\":"+hS+",\"d\":"+dS+"}";
Serial.println(values);
LoRa.print(values);
LoRa.endPacket();
}
void loop()
{
delay(DELAY);
sendTH();
}
让我们更详细地看一下前面的代码:
-
heltec.h:我们导入该库以使用 ESP32 + LoRa 设备。使用这个库,你可以使用 Wi-Fi 和 LoRa 无线协议。 -
DHT.h:我们导入该库以读取 DHT11 传感器的数据。 -
BAND:我们设置用于连接设备的频段。对于欧洲,必须使用868E6,而对于美国,则必须使用915E6。 -
DHTPIN:这是一个常量值,用来设置我们 ESP32 设备中用于读取数据的引脚。在这个例子中,我们使用的是 17 号引脚。请记住,使用的引脚必须支持数字信息。 -
DHTTYPE:定义传感器类型。我们使用的库支持 DHT11 和 DHT12 传感器。 -
DEVICE:这是发送数据的设备编号。每次将代码上传到设备时,必须更改此值,以便通过编号标识每个设备。 -
DELAY:这是等待发送下一个传感器数据的时间。 -
setup():此函数为 Heltec 设备做初始配置,并使用0xF3设置 LoRa 的网络 ID——即我们设备的网络 ID。该值必须设置在0和0xFF之间。 -
sendTH():此函数使用 LoRa Wi-Fi 协议捕获并发送传感器数据,数据格式为{"t":26.2,"h":35.5,"d":1},其中t是摄氏度温度,h是百分比湿度,d是设备编号。 -
loop():此函数以循环的形式运行,并调用sendTH()来捕获并发送传感器数据,通过 LoRa 进行通信。
重要提示
你可以在github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch12/code/arduino找到发送和接收设备的源代码。
总结一下,首先,我们设置所有常量值,以配置如何读取 DHT11 传感器的数据并通过 LoRa 发送数据。然后,调用setup()来准备读取传感器数据并进行 LoRa 的初步配置。最后,loop()以循环方式运行,调用sendTH(),进行数据发送与接收。一旦你将这段代码上传到设备,只需打开设备即可开始发送数据。要停止发送数据,你需要关闭设备电源。
注意包含Serial.println命令的行。此命令通过串口打印信息。当设备通过 USB 端口连接到你的笔记本电脑时,你可以通过在窗口内打开Serial.println的输出,使用 Arduino 排查设备上的问题:

图 12.8 – 使用 Heltec ESP32 设备在 Arduino 中进行监控
重要说明
要将设备连接到 macOS,您需要一个 USB A 到 USB C 的适配器。如果您需要至少 5 伏的电源,您还可以使用电池银行,而不必将设备连接到笔记本电脑或计算机。
现在您的 Heltec 发送设备已经正常工作,您可以开始上传接收设备的代码了。
为 ESP32 微控制器编程,以接收传感器数据
现在,我们必须配置我们的 Heltec ESP32 设备,以接收传感器数据,并通过无线网络向远程边缘集群发送请求。首先,我们必须通过点击 文件 | 新建 创建另一个文件,并用以下代码替换默认内容:
#include "heltec.h"
#include "WiFi.h"
#include <HTTPClient.h>
#define BAND 915E6
#define METRICS_IP "192.168.0.241"
void setup()
{
Heltec.begin(false, true, true, true, BAND);
Serial.begin(9600);
LoRa.setSyncWord(0xF3);
Serial.println("LoRa started");
WIFISetUp();
}
void WIFISetUp(void)
{
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA);
WiFi.setAutoConnect(true);
WiFi.begin("NET_NAME","PASSWORD");
delay(100);
byte count = 0;
while(WiFi.status() != WL_CONNECTED && count < 10)
{
count ++;
delay(500);
Serial.println("Connecting...");
}
if(WiFi.status() == WL_CONNECTED)
Serial.println("Connected OK");
else
Serial.println("Failed");
}
void callURL(String data)
{
String postData = data;
Serial.println("Sending: " + postData);
WiFiClient client;
HTTPClient http;
http.begin(client, "http://"+((String)METRICS_IP)+":3000/device");
http.addHeader("Content-Type","application/json");
int httpResponseCode = http.POST(postData);
Serial.println("HTTP Response code xyz: "+(String)httpResponseCode);
http.end();
}
void loop()
{
onReceive(LoRa.parsePacket());
}
void onReceive(int packetSize)
{
String incoming = "";
if (packetSize == 0) return;
while (LoRa.available())
incoming += (char)LoRa.read();
Serial.println("Received: " + incoming);
callURL(incoming);
}
让我们稍微了解一下代码:
-
Heltec.h,WiFi.h,HTTPClient.h:这些是我们用于接收器的库。Heltec.h用于通过 LoRa 发送数据,WiFi.h是 Heltec 的 Wi-Fi 库,用于连接到无线网络,HTTPClient.h用于将传感器数据发送到我们的远程边缘服务器。 -
BAND:在这里,我们设置与发送设备相同的频段。 -
METRICS_IP:这是您集群中metrics服务的 IP 地址。要获取此值,请转到 部署用于存储传感器数据的 MySQL 数据库 部分。在上传代码到设备之前,请替换此值。 -
setup():在这里,我们配置 Heltec 设备以接收来自setSyncWord定义的相同 LoRa 网络的数据。它还配置 Wi-Fi 连接。 -
WIFISetUp(void):在这里,我们配置 Wi-Fi 连接。为此,您必须将NET_NAME替换为您的网络名称,并将PASSWORD替换为访问连接所需的密码。 -
callURL(String data):这个函数调用您集群中的metrics服务。访问它的 URL 类似于http://METRICS_IP:3000/device,但是此函数会自动使用METRICS_IP常量的值生成该 URL。 -
onReceive(int packetSize):此函数接收通过 LoRa 协议发送到已配置网络的信息,然后使用 HTTP POST 请求将这些信息发送到远程边缘集群中的metrics服务。 -
loop():此函数作为循环运行,并调用onReceive(int packetSize),该函数接收包含传感器数据的 LoRa 数据包。然后,它将这些数据发送到远程边缘集群中的metrics端点。
总结一下,首先,我们配置设备,使其能够连接到相同的 LoRa 网络。我们还必须配置 Wi-Fi,以便访问远程边缘服务器。loop() 会不断检查是否收到数据,并将其发送到远程边缘集群中的 metrics 服务器。
现在,上传代码到你的设备并打开它,正如我们在 编程 ESP32 微控制器以发送传感器数据 部分中所做的那样。至此,我们的设备已配置完毕,现在让我们进入最后一步,配置 Grafana 以便在仪表板中展示所有数据,使用存储在 MySQL 中的传感器数据。
使用 MySQL 和 Grafana 可视化 ESP32 微控制器数据
现在,让我们完成一个实时温湿度系统的实现。为此,我们将使用 Grafana 来创建报告,MySQL 作为我们提供数据的源。你可以在云端的 Kubernetes 或使用边缘集群可以访问的私有云中部署这些软件。在本节中,我们假设使用的是云中的 Kubernetes。要开始创建报告,按照以下步骤操作:
-
创建必要的命名空间 monitoring:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: creationTimestamp: null name: monitoring spec: {} status: {} EOF -
创建一个 ConfigMap,以创建包含我们 MySQL 连接的默认数据源:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: grafana-datasources namespace: monitoring labels: grafana_datasource: "true" data: datasource.yaml: |- apiVersion: 1 datasources: - name: sensor_data type: mysql url: mysql.default.svc access: proxy database: sensor_data user: lora_mysql secureJsonData: password: lora123- isDefault: true EOF
这将是你在grafana部署中配置的默认数据源。
重要提示
你可以使用 Secret 对象来保护敏感数据,但我们使用 ConfigMap 来简化这个例子。
-
部署 Grafana,以便它可以通过运行以下代码使用先前的 ConfigMap:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: grafana namespace: monitoring spec: replicas: 1 selector: matchLabels: app: grafana template: metadata: name: grafana labels: app: grafana spec: containers: - name: grafana image: grafana/grafana:8.4.4 ports: - name: grafana containerPort: 3000 resources: limits: memory: "1Gi" cpu: "1000m" requests: memory: 500M cpu: "500m" volumeMounts: - mountPath: /var/lib/grafana name: grafana-storage - mountPath: /etc/grafana/provisioning/datasources name: grafana-datasources readOnly: false volumes: - name: grafana-storage emptyDir: {} - name: grafana-datasources configMap: defaultMode: 420 name: grafana-datasources EOF -
让我们创建一个服务来访问 Grafana:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: grafana name: grafana namespace: monitoring spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: grafana type: ClusterIP EOF -
使用
port-forward转发之前的 Grafana 服务。这将帮助我们本地连接到 Grafana:$ kubectl port-forward svc/grafana 3000 -n monitoring --address 0.0.0.0 -
访问
http://localhost:3000。当登录页面出现时,使用用户名admin和密码admin,然后点击 登录 按钮。之后,你将被要求输入新的凭据:

图 12.9 – Grafana 登录
- 登录后,你可以通过访问 配置 | 数据源 来检查默认数据源是否已设置为 MySQL:

图 12.10 – Grafana 配置菜单
你将看到类似以下的内容:

图 12.11 – sensor_data 默认数据源
- 现在,点击 创建 | 仪表板:

图 12.12 – 创建仪表板
然后,点击 添加一个新面板:

图 12.13 – Grafana – 添加面板页面
-
然后,使用以下查询和值配置新仪表板:
SELECT UNIX_TIMESTAMP(time) AS "time", temperature_c AS "Temperature(Celcius)", temperature_f AS "Temperature(Farenheit)", humidity AS "Humidity(%)" FROM metric WHERE $__timeFilter(time) and device=1 ORDER BY time
你必须点击铅笔图标来编辑默认查询。然后,将之前的查询复制到此处:

图 12.14 – 编辑默认的 MySQL 查询
我们在这里将 标题 设置为 设备 1 传感器数据。新仪表板将如下所示:

图 12.15 – 新的仪表板/编辑面板窗口
修改查询后,如果数据可用,你将看到一个包含三条线的图表——一条表示摄氏温度,另一条表示华氏温度,还有一条表示湿度百分比。记得设置仪表盘以可视化正确范围的数据——例如,显示过去 5 分钟的数据——并每隔 5 秒刷新一次仪表盘。
- 之后,点击保存按钮保存你的仪表盘。
重要提示
查看第十一章,使用 Prometheus 和 Grafana 监控边缘,获取更多有关如何自定义仪表盘的细节。
- 最后,你的仪表盘将如下所示:

图 12.16 – 在 Grafana 中的 ESP32 监控仪表盘
此时,你正在可视化来自 Heltec 设备的传输和接收数据。这些设备与远程边缘集群进行交互。如果你选择在云端部署 Grafana 和 MySQL,那么此场景也在与云层交互。所有这些组件相互作用,完成各自的任务。记住,这只是一个简单的实现,你可以根据自己的需求进行扩展。现在,让我们总结一下本章所学内容。
总结
在本章中,我们探讨了如何通过使用 LoRa 设备来实现一个边缘计算系统,以发送和接收传感器数据。最后,我们使用 MySQL 和 Grafana 实现了一个仪表盘。通过这种方式,LoRa 无线协议代表了一种实现低成本系统的方式,这些系统需要在接近边缘的地方传输信息。因此,LoRa 是边缘设备和物联网应用中常用的传输协议选择。在下一章中,我们将使用 GPS 模块扩展通信范围,并利用数据库实现地理位置应用。
问题
以下是一些问题,用来验证你新的知识:
-
使用 LoRa 无线协议的用途和优势是什么?
-
什么是 LoRaWAN?
-
我如何使用 Heltec ESP32 + LoRa 设备发送传感器数据?
-
我如何使用 Arduino IDE 为 ESP32 设备编程?
-
我如何创建一个简单的网关,将来自 LoRaWAN 的数据发送到局域网?
-
我如何使用 MySQL 和 Grafana 创建报告?
深入阅读
请参阅以下参考资料,获取更多有关本章所涉及主题的信息:
-
LoRaWAN 规范是什么?:
lora-alliance.org/about-lorawan -
什么是 LoRa?:
www.semtech.com/lora/what-is-lora -
LoRaWAN 频率计划:
www.thethingsnetwork.org/docs/lorawan/frequency-plans。 -
CP210x USB 到 UART 桥接 VCP 驱动程序:
www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers -
Arduino 下载软件:
www.arduino.cc/en/software -
HttpClient 文档和示例:
github.com/amcewen/HttpClient -
在 MacOS Big Sur 及更新版本上,使用 Arduino IDE 和 Platform I/O 运行 ESP32/Arduino 的所有修复方法:
www.youtube.com/watch?v=zkyoghpT8_U -
ESP32 + LoRa Heltec 文档:
heltec-automation-docs.readthedocs.io/en/latest/esp32/index.html -
Heltec Automation 文档页面:
heltec-automation-docs.readthedocs.io/en/latest -
Heltec ESP32 LoRaWAN 库和示例:
github.com/HelTecAutomation/ESP32_LoRaWAN -
WIFI LoRa 32(V2) 引脚图:
resource.heltec.cn/download/WiFi_LoRa_32/WIFI_LoRa_32_V2.pdf -
Grafana 配置:
github.com/grafana/grafana/blob/main/docs/sources/administration/provisioning/index.md
第十三章:使用 GPS、NoSQL 和 K3s 集群的地理定位应用
边缘计算的一个日益增长的应用场景是实施一个用于跟踪货物和物流的系统。有时,这种跟踪涉及监控和获取一些可以用来优化包裹配送时间、减少油耗等的指标。您可以用来实现这一目标的一个重要技术是全球定位系统(GPS)。GPS 可以帮助您在物体实时移动时获取其坐标。结合 Kubernetes 在边缘的应用,形成了强大的技术组合,用于创建地理定位系统,也称为地理跟踪系统。
在本章中,我们将涵盖以下主要内容:
-
理解 GPS 如何在地理跟踪系统中被使用
-
使用 Redis 存储 GPS 坐标数据
-
使用 MongoDB 存储设备的跟踪数据
-
创建服务以使用 GPS 实时监控您的设备
-
配置您的 Raspberry Pi 以使用 GPS 跟踪您的设备
-
使用 Leaflet 库实时可视化您的设备
-
部署一个实时地图和报告应用程序来跟踪您的设备
技术要求
在本章中部署我们的数据库,您需要以下内容:
-
使用 ARM 设备的单节点 K3s 集群。在本例中,我们将使用 8 GB 的 Raspberry Pi 4B 型号。我们将使用没有桌面环境的 Raspberry Pi OS lite(64 位)操作系统。
-
多个 VK-162 G-Mouse USB GPS dongle 导航模块,用于您的边缘 Raspberry 设备
-
一个电池组和一根 USB 2.0 A-Male 到 USB C 的数据线。您也可以通过汽车的 USB 充电端口为 Raspberry Pi 供电。
-
一个托管在您的公共云服务提供商(AWS、Azure 或 GCP)或私有云中的 Kubernetes 集群
-
编程基础知识,尤其是 Python 和 JavaScript。
-
如果您希望通过
kubectl apply运行 YAML 配置而不是从书中复制代码,请克隆github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch13这个仓库。查看code目录中的python目录以及ch13目录中的yaml目录,以获取 YAML 配置。
通过这些步骤,您可以开始使用边缘计算实现您的地理定位系统。让我们从第一部分开始了解 GPS 是如何工作的。
理解 GPS 如何在地理跟踪系统中被使用
对于本章,我们的目标是构建一个地理定位系统,也称为地理跟踪系统。这意味着我们将构建一个从车辆获取 GPS 坐标或位置的系统。在我们的使用案例中,我们假设我们的车辆将用于配送包裹。我们的车辆将配备树莓派设备和 GPS 模块。这些硬件将收集 GPS 坐标,使用纬度和经度将其发送到云端。然后,我们的应用程序将显示所有车辆的实时位置,并生成报告以显示车辆在日期范围内的行驶路线。总体而言,这就是我们的地理跟踪系统将具备的主要功能:
-
一个实时地图,显示所有送货车辆的位置
-
显示每辆车附近配送站点的地图
-
显示车辆在日期范围内配送路线的报告
该地理跟踪系统通过以下图示表示:

图 13.1 – 一个地理定位应用的边缘图
现在,让我们通过描述边缘计算层来解释这个地理跟踪系统是如何实现的:
-
云层:在这里,我们将会在我们选择的云服务商上安装 Kubernetes 集群。在这个集群中,我们将安装三个主要应用程序。GPS 服务器将接收来自车辆的请求。它还会将收集到的位置保存在 Redis 中,以供实时地图使用,并将日志保存在 MongoDB 中,用于显示配送路线的报告。最后,我们将有一个前端应用程序,包含用于显示实时地图和报告的 Web 应用。
-
近端:这一层表示所有将通过 LTE 网络从远端移动到近端的信息。这意味着所有的 GPS 信息将通过互联网发送到最终目的地——云层。
-
远端:在这里,我们将找到配备了树莓派的车辆;该设备将使用 GPS 模块和互联网连接发送 GPS 坐标。我们的树莓派将安装 K3s。在这个 K3s 单节点集群中,我们将找到 GPS 读取器。这个应用程序将读取 GPS 信息并将其发送到云端。然而,您还可以包含其他应用程序以增加更多功能——例如,显示一个带有 GPS 信息或其他处理过的数据(如速度)的 OLED 屏幕,通过 GPS 坐标来计算速度。因此,这部分代表了边缘的本地处理。
要将树莓派连接到互联网,您可以使用 5G 或 4G LTE 模块,或者使用已经包含此类模块的智能手机。为了简化示例,我们将使用智能手机的接入点与树莓派设备共享互联网。
- Tiny edge:在这里,我们将找到边缘设备用于获取 GPS 坐标的 GPS 模块。我们的 GPS 模块将使用 全球导航卫星系统 (GNSS),这是一个全球卫星系统,能够提供 GPS 坐标。这将是我们实现中的主要数据。你还可以使用集成了 GPS 的 LTE 5G/4G 模块来加快 GPS 模块初始化速度,从而更快速地获取 GPS 坐标,但与 VK-162 G-Mouse USB 模块相比,这可能会更昂贵。在这种情况下,我们将使用 VK-162 模块来简化实现并降低该原型实现的成本。
总结来说,我们位于远端的车辆将从微型边缘设备的 GPS 模块读取信息。在读取信息并进行一些处理后,信息将通过近端设备发送到云层。一旦接收到所有信息,这些数据将存储在 Redis 和 Mongo 中,并通过前端应用展示实时地图和报告。
使用 Redis 存储 GPS 坐标数据
正如我们在 第十章 中解释的,边缘的 SQL 和 NoSQL 数据库,Redis 是一个键值数据库,在使用资源时非常轻量。Redis 专门使用 RAM 内存来存储数据,但在使用快照配置时可以持久化数据,这基本上是将数据存储在磁盘上。Redis 还可以存储地理位置数据,存储 GPS 坐标和包含纬度和经度值的元组。Redis 通过字段的纬度、经度和名称来存储这些信息。Redis 还通过 GEOADD 和 GEOSEARCH 命令来实现我们的地理定位应用程序。但是首先,让我们在云端安装 Redis,以存储一些地理位置信息。为此,请按照以下步骤操作:
-
首先,让我们为 Redis 创建一个 PersistentVolumeClaim 来持久化数据:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim-1 spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi EOF -
现在,创建一个 ConfigMap 来配置 Redis 使用认证密码:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: redis-configmap data: redis-config: | dir /data requirepass YOUR_PASSWORD EOF -
使用之前的
redis-configmap创建 Redis 的部署,并将其挂载为redis.conf文件。我们还使用db-pv-claim-1,并为部署设置了一些资源限制,包括 CPU 和内存。让我们通过运行以下命令来创建该部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: redis name: redis spec: replicas: 1 selector: matchLabels: app: redis strategy: {} template: metadata: creationTimestamp: null labels: app: redis spec: containers: - name: redis image: redis:6.2 command: - redis-server - /redisconf/redis.conf ports: - containerPort: 6379 resources: limits: cpu: "0.2" memory: "128Mi" volumeMounts: - mountPath: "/data" name: redis-storage - mountPath: /redisconf name: config volumes: - name: config configMap: name: redis-configmap items: - key: redis-config path: redis.conf - name: redis-storage persistentVolumeClaim: claimName: db-pv-claim-1 status: {} EOF
这次,我们不打算使用 ARM 64 位的镜像。
-
现在,通过打开端口
6379来创建 Redis 服务:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: redis name: redis spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: app: redis type: ClusterIP EOF
现在,我们已经安装了 Redis。接下来,我们来安装 Mongo,用于存储带有这些数据的日志信息。
使用 MongoDB 存储设备的追踪数据
MongoDB 是一个文档导向的 NoSQL 数据库,使用 JSON 格式存储信息。它也具备存储位置数据的能力。在这个用例中,我们将使用 MongoDB 来存储我们的地理位置数据;这意味着存储 GPS 在设备上捕获的所有坐标(纬度和经度)以供后续报告。MongoDB 可以对地理位置数据进行一些特殊处理,但在此用例中,我们将仅用它来存储 JSON 格式的数据。要在云中安装 MongoDB,请按照以下步骤操作:
-
为 MongoDB 创建PersistentVolumeClaim以持久化数据:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim-2 spec: accessModes: - ReadWriteOnce #storageClassName: your_driver resources: requests: storage: 5Gi EOF
重要提示
如果你安装了 Longhorn 或其他存储驱动,或者使用了云服务提供商提供的存储类,可以更改存储类。只需通过移除#字符取消注释storageClassName行。
-
部署你的自定义配置,以便客户端能够连接到 MongoDB:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: mongo-configmap data: mongod-conf: | dbpath=/var/lib/mongodb logpath=/var/log/mongodb/mongodb.log logappend=true bind_ip = 0.0.0.0 port = 27017 journal=true auth = true EOF
这将通过网络暴露 MongoDB 监听端口27017。
-
使用
mongo-configmap创建部署,我们的MONGO_INITDB_ROOT_USERNAME、MONGO_INITDB_ROOT_PASSWORD和MONGO_INITDB_DATABASE变量设置初始的根用户名、额外的用户及其连接密码,用于连接 MongoDB:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: mongo name: mongo spec: replicas: 1 selector: matchLabels: app: mongo template: metadata: labels: app: mongo spec: containers: - name: mongo image: mongo:4.4 env: - name: MONGO_INITDB_ROOT_USERNAME value: "admin" - name: MONGO_INITDB_ROOT_PASSWORD value: "YOUR_PASSWORD" - name: MONGO_INITDB_DATABASE value: "mydatabase" ports: - containerPort: 27017 resources: limits: cpu: "0.2" memory: "200Mi" volumeMounts: - mountPath: "/data/db" name: mongo-storage - mountPath: /mongoconf name: config volumes: - name: config configMap: name: mongo-configmap items: - key: mongod-conf path: mongod.conf - name: mongo-storage persistentVolumeClaim: claimName: db-pv-claim-2 EOF
重要提示
我们直接使用一些值来配置部署,以简化示例。但最佳实践是使用机密来保护敏感数据。你可以查看第十章,边缘的 SQL 和 NoSQL 数据库,获取更多示例。我们也使用的是 4.4 版本,如果你想在 ARM 设备上安装 MongoDB,请参考此版本。
-
现在,创建服务,将你的 MongoDB 部署暴露为集群内可访问的服务(MongoDB 使用端口
27017连接):$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: mongo name: mongo spec: ports: - port: 27017 protocol: TCP targetPort: 27017 selector: app: mongo type: ClusterIP EOF
现在,你的 MongoDB 数据库已经安装完成。那么,让我们在下一部分部署我们的 GPS 服务器应用程序,存储数据到 Redis 和 MongoDB。
创建服务以通过 GPS 实时监控你的设备
在我们的用例中,我们将部署一个服务,将来自边缘设备的数据经过处理后发送到云端。这个用例的目标是为多个运送包裹的车辆提供全球地理定位系统,实时显示它们的位置。为此,我们将创建一个gps-server部署,将所有的坐标存储在 Redis 和 Mongo 中。我们将使用 Python 的 Flask 库来创建这个服务。接下来,让我们探索以下包含 Python 的伪代码的主要部分:
<imported libraries>
<app_initialization>
<CORS configuration>
def redisCon():
<return Redis connection object>
@app.route("/client/<cid>/position", methods=["POST"])
def setPosition(cid):
<Call redisCon>
<Store of data in a Redis hash data type using
the fields cid,lat,lng
in the hash key named client:{cid}:position>
<set the expiration of the key>
<call the tracking-server in /client/{cid}/position
to store the position in Mongo>
return {"client_id":cid,"setPosition":"done"}
@app.route("/clients/positions/unit/<unit>/r/<radius>"
,methods=["GET"])
def getPositions(unit,radius):
<Call redisCon>
<Search for client:*:position keys>
<Search the near geospacial index for
the current position>
<Add the position to data Array>
<Returns the near positions for each unit in JSON>
return jsonify({"clients":data})
@app.route("/client/<cid>/stops", methods=["POST"])
def setStops(cid):
<Call redisCon>
<GET json values stops to set>
<Store the stops in the key client:{cid}:stops >
return jsonify({"setStops":"done"})
<App initialization in port 3000>
让我们专注于以下功能:
-
redisCon:此功能设置 Redis 连接。此应用将使用使用 Redis 存储 GPS 坐标数据部分中创建的 Redis 服务。
-
/client/<cid>/positionURL,函数将获取<cid>值,表示向此服务发送信息的连接客户端——在此案例中是我们的配送车辆。每次接收到信息时,它都会存储在 Redis 中以client:{cid}:position形式命名的键中,并存储纬度为lat变量,经度为lng,客户端 ID 或车辆编号为cid。它还会设置一个过期时间为 180 秒或 3 分钟。在调用跟踪服务器将该坐标存储到 MongoDB 后,它返回以下 JSON 响应:{"client_id":cid,"setPosition":"done"}。 -
/clients/positions/unit/<unit>/r/<radius>URL,函数连接到 Redis 并获取所有键,其形式为client:<cid>:position,包含每辆车的当前 GPS 位置。然后,通过使用 Redis 命令geosearch获取靠近该位置的停靠点。返回的 JSON 将如下所示:{"clients":[{"cid":1,"lat":0.0,"lng":0.0,"near":["stop1"]}]}。 -
/client/<cid>/stopsURL,函数将获取<cid>并将所有位置作为地理空间索引存储在以client:{cid}:stops形式命名的键中。在此键中,每个位置将与通过curl发送的 JSON 数据中的名称一起存储。这些停靠点默认存储 10 小时,因为停靠点必须在工作日内完成。这些停靠点将靠近具有cid编号的车辆。
在理解代码后,我们将在下一节部署我们的 GPS 服务器应用程序。
部署 gps-server 来存储 GPS 坐标
gps-server 应用程序将接收来自您的边缘设备的 GPS 坐标。为此,我们需要部署它并通过负载均衡器公开。要部署 gps-server 应用程序,请按照以下步骤操作:
-
创建 GPS 服务器的部署:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: gps-server name: gps-server spec: replicas: 1 selector: matchLabels: app: gps-server strategy: {} template: metadata: creationTimestamp: null labels: app: gps-server spec: containers: - image: sergioarmgpl/gps_server name: gps-server imagePullPolicy: Always env: - name: REDIS_HOST value: "redis" - name: REDIS_AUTH value: "YOUR_PASSWORD" - name: ENDPOINT value: "http://tracking-server:3000" resources: {} status: {} EOF
此部署使用以下变量:
-
REDIS_HOST:这是 Redis 服务的名称;此变量可以根据您的需要进行自定义。
-
REDIS_AUTH:这是连接 Redis 服务的密码。
-
tracking-server– 在这种情况下,URL 匹配内部的tracking-server服务,端口为3000。
重要提示
要查看代码并创建自己的容器,请参考此链接:github.com/sergioarmgpl/containers/tree/main/gps-server/src。
-
将服务创建为 LoadBalancer;此 IP 地址将在我们的 GPS 读取器服务中用于每个单位或卡车:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: gps-server name: gps-server-lb spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: gps-server type: LoadBalancer status: loadBalancer: {} EOF -
使用以下命令获取我们
gps-server部署的负载均衡器 IP:$ GPS_SERVER_IP="$(kubectl get svc gps-server-lb -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
您可以通过运行以下命令查看 GPS_SERVER_IP 环境变量的值:
$ echo $GPS_SERVER_IP
请注意,在负载均衡器的 IP 地址配置完成后需要一些时间。您可以通过运行以下命令检查服务的状态:
$ kubectl get svc gps-server-lb
等待 EXTERNAL_IP 配置完成。同时,注意 $GPS_SERVER_IP 的值,这将用于配置每个边缘设备上的 gps-reader 应用程序。
现在,你可以为第一个车辆设置停靠点,该车辆用值 1 表示。为此,请按照以下步骤操作:
-
使用 curl 存储停靠点:
$ curl -X POST -H "Accept: application/json" \ -H "Content-Type: application/json" \ --data '{ "stops":[ {"name":"stop1","lat":1.633518,"lng": -90.591706}, {"name":"stop2","lat":2.631566,"lng": -91.591529}, {"name":"stop3","lat":3.635043,"lng": -92.589982} ] }' http://$GPS_SERVER_IP:3000/client/1/stops -
这将返回以下内容:
{"setStops":"done"}
现在,我们已经部署了 gps-server 应用程序,并通过负载均衡器对外暴露。接下来,让我们部署我们的 tracking-server,即存储接收到的 GPS 位置日志的服务器。
创建一个服务来记录 GPS 位置并实现设备的实时追踪
我们的 tracking-server 应用程序将负责记录每辆车接收到的所有坐标。该信息将用于在所需的时间范围内,通过 frontend 应用程序显示车辆的路线。在部署 tracking-server 之前,让我们先了解一下这个应用程序的代码:
<Imported libraries>
<Application initialization>
<CORS configuration>
def mongoCon():
<return Mongo connection with tracking collection set>
@app.route("/client/<cid>/position", methods=["POST"])
def storePosition(cid):
<Get the position JSON values to store it
in the tracking collection>
<Get current time and store it using UTC>
<Call MongoCon function>
<Store data in the format:
{"cid":XX,"lat":XX,"lng":XX,"ts":XXXXXXX,"dtxt":XXXXXX}
Inside the tracking collection in the database
called mydatabase>
<return JSON {"client_id":cid,"positionStored":"done"}>
@app.route("/client/<cid>/positions/s/<sdate>/e/<edate>"
,methods=["GET"])
def getPositions(cid,sdate,edate):
<Get the start date to query in the format
dd-mm-yy-HH:MM:SS and convert it into UTC>
<get the end date to query in the format
dd-mm-yy-HH:MM:SS and convert it into UTC>
<Call MongoCon function>
<Query the tracking collection to get the
tracking data for a unit
or truck between the time range>
<Return the positions in an array called data>
return jsonify({"tracking":data})
<App initialization in port 3000>
在此代码中,我们可以找到以下功能:
-
tracking值。 -
在
/client/<cid>/positionURL 中发送POST请求,函数将以{"cid":1,"lat":0.0,"lng":0.0,"ts":166666666,"dtxt":"01-01-22-23:59:59"}格式存储接收到的 GPS 位置。cid表示客户端 ID 或车辆编号,lat和lng用于存储 GPS 位置,ts表示接收到坐标时生成的时间戳,dtxt是文本格式的日期,以减少从时间戳格式到 UNIX 日期格式的转换时间。将数据存储到数据库后,mydatabase会返回以下 JSON:{"client_id":cid,"positionStored":"done"}。 -
在 URL 中使用
GET请求,格式为/client/<cid>/positions/s/<sdate>/e/<edate>,它会返回一个 JSON 响应,包含在开始日期sdate和结束日期edate之间的所有 GPS 位置。为此,tracking-server会连接到 Mongo 并返回在此时间范围内执行此查询的结果。信息将以以下格式返回:{ "tracking":[ {"lat":0.0,"lng":0.0,"ts":166666666 ,"dtxt":"01-01-22-23:59:59"} ] }
现在我们知道了 tracking-server 应用程序的工作原理。接下来,让我们在下一节中部署这个应用程序。
部署 tracking-server 来存储来自 GPS 坐标的日志,以便用于车辆路线报告
我们的 tracking-server 将在我们的 frontend 应用程序中使用,以显示车辆在指定时间范围内的路线。让我们通过以下步骤部署我们的应用程序:
-
通过运行以下命令部署
tracking-server:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: tracking-server name: tracking-server spec: replicas: 1 selector: matchLabels: app: tracking-server strategy: {} template: metadata: creationTimestamp: null labels: app: tracking-server spec: containers: - image: sergioarmgpl/tracking_server name: tracking-server imagePullPolicy: Always env: - name: MONGO_URI value: "mongodb://admin:YOUR_PASSWORD@mongo/mydatabase?authSource=admin" - name: MONGO_DB value: "mydatabase" - name: TIMEZONE value: "America/Guatemala" resources: {} status: {} EOF
本次部署使用以下环境变量:
-
mongodb://USER:PASWORD@HOST/DATABASE?authSource=admin格式。你可以自定义这些凭证,并将MONGO_URI存储为密钥。 -
MONGO_DB:这是在 MongoDB 中创建的数据库,用于存储 tracking 集合。
-
tracking集合的 MongoDB。请注意,我们的 Python 代码使用了pytz库和 ISO 3166 国家名称约定。有关如何正确设置国家时区的更多信息,请查阅 进一步阅读 部分。在此案例中,我们将国家设置为America/Guatemala。
重要提示
你可以在以下页面了解更多关于 URI 的信息:www.mongodb.com/docs/manual/reference/connection-string。记住,我们为了简化示例而使用了硬编码值,但最好使用机密信息。查看第十章,边缘上的 SQL 和 NoSQL 数据库,了解更多细节。要查看代码并创建你自己的 tracking-server 版本,请参考以下链接:github.com/sergioarmgpl/containers/tree/main/tracking-server/src。
-
创建一个 ClusterIP 服务以便在
gps-server内部调用tracking-server:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: tracking-server name: tracking-server spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: tracking-server type: ClusterIP status: loadBalancer: {} EOF -
创建一个负载均衡器服务,以便在我们的查看应用程序中调用
tracking-server,该应用程序可以通过互联网访问:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: tracking-server name: tracking-server-lb spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: tracking-server type: LoadBalancer status: loadBalancer: {} EOF -
使用以下命令获取我们
tracking-server部署的负载均衡器 IP:$ TRACKING_SERVER_IP="$(kubectl get svc tracking-server-lb -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
你可以通过运行以下命令查看 TRACKING_SERVER_IP 环境变量的值:
$ echo $TRACKING_SERVER_IP
tracking-server 应用程序已部署。现在,让我们配置设备以运行我们的阅读器应用程序。
配置你的 Raspberry Pi 以使用 GPS 跟踪你的设备
在你的 Raspberry Pi 上使用 GPS 模块之前,你需要按照以下步骤操作:
-
在你的设备上安装 Raspberry Pi OS Lite(64 位);你可以查看第二章,K3s 安装与配置,了解更多细节。
-
登录到你的设备并设置一个初始用户名和密码。
-
使用以下命令运行
raspi-config:$ sudo raspi-config
你将看到如下屏幕:

图 13.2 – raspi-config 主菜单
-
要配置无线网络,请进入系统选项 | 无线局域网菜单。
-
你会看到一个选择你 Raspberry Pi 使用的国家的消息,然后点击确定。
-
之后,无线局域网国家的消息将出现。选择你的国家并点击确定。
-
请输入 SSID消息将出现。点击确定并按回车键。
-
现在,请输入密码消息将出现。点击确定并按回车键。
-
返回主菜单后,选择完成并按回车键退出。
-
启用 SSH,选择接口选项 | SSH菜单。这将显示是否启用 SSH 服务器?消息。选择是并按回车键。之后,SSH 服务器已启用消息将出现。
-
要检查你的 Raspberry Pi 的 IP,请运行以下命令:
$ ifconfig -a
输出将如下所示:

图 13.3 – ifconfig 输出
请注意 wlan0 网络接口旁边的 inet 字样的 IP 地址;这将是你 Raspberry Pi 的 IP 地址。
-
使用前面找到的 IP 地址通过 SSH 登录到您的设备:
$ ssh YOUR_USER@RASPBERRY_IP -
将以下内核参数添加到
/boot/cmdline.txt文件中以启用容器功能;请记住,您需要 root 权限才能修改此文件:cgroup_memory=1 cgroup_enable=memory -
将您的 VK-162 G-Mouse GPS 模块连接到树莓派的一个 USB 端口;几秒钟后,
/dev/ttyACM0设备将准备好使用。 -
重启您的设备以应用这些更改:
$ sudo shutdown -r now -
(可选) 如果您想配置其他功能,请登录到设备并运行以下命令:
$ sudo raspi-config -
(可选) 激活
raspi-config。 -
(可选) 然后,弹出对话框会显示 是否启用 ARM I2C 接口?。选择 是 并按 Enter。
-
(可选) 在前一个对话框之后,将显示 ARM I2C 接口已启用 信息。按 Enter 选择 确定 按钮。
-
(可选) 返回主菜单后,选择 完成 并按 Enter 退出。
-
最后,让我们通过运行以下命令来安装 K3s:
$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644" sh -s - -
运行以下命令以查看您的 K3s 单节点集群是否正在运行:
$ kubectl get nodes
这将显示类似以下内容:

图 13.4 – kubectl 获取节点输出
重要提示
您可以通过以下链接了解更多关于如何使用 raspi-config 的信息:geek-university.com/raspi-config。
现在,您的树莓派已经安装了 Raspberry Pi OS Lite,并且可以与您的 GPS 模块一起使用。在下一部分中,我们将开始部署 GPS 读取应用程序。
理解 GPS 读取器代码以发送 GPS 坐标
现在,剩下的部分是安装 gps-reader 到您 Raspberry 上安装的 K3s 单节点集群中。这个应用程序将作为容器使用 Kubernetes Pods 运行。然而,在安装 gps-reader 应用程序之前,让我们先理解一下代码:
<Imported libraries>
while True:
<Set serial Device /dev/ttyACM0 with baud rate 9600>
ser=serial.Serial(device, baudrate=9600, timeout=0.5)
<Set the PynMEA2 reader>
<Read data from the device>
<Read for GRPMC lines>
<Extract latitude, longitude>
<Call /client/{cid}/position from GPS Server
To store the position in Redis>
<If cannot read data show
"No GPS data to send">
上述代码包含一个无限循环,每半秒读取一次 /dev/ttyACM0 设备的输出。我们的 VK-162 G-Mouse GPS 模块使用 GRPMC 字段通过 PynMEA2 库获取纬度和经度坐标。一旦库提取到坐标,它会调用 GPS 服务器端点将当前的 GPS 位置存储到 Redis 中,并记录到 MongoDB。
请注意,GPS 模块在开始接收 GPS 坐标后会有一点延迟。在 GPS 模块开始接收坐标之前,可能需要几分钟时间。
要查看设备正在做什么,请运行 cat /dev/ttyACM0 命令。如果模块尚未接收坐标,它将显示类似以下内容:
$GPRMC,052326.00,V,,,,,,,,,,N*7D
$GPVTG,,,,,,,,,N*30
$GPGGA,052326.00,,,,,0,00,99.99,,,,,,*66
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
$GPTXT,01,01,01,NMEA unknown msg*58
$GPTXT,01,01,01,NMEA unknown msg*58
$GPGSV,1,1,02,01,,,30,22,,,36*7C
$GPGLL,,,,,052326.00,V,N*4A
重要提示
当模块尚未接收到坐标时,GPRMC 或 GPGLL 字段会有空值。这些缺失的值包含 GPS 模块获得的纬度和经度。
当设备开始接收数据时,您将看到类似以下内容:
$GPRMC,054003.00,A,1437.91511,N,09035.52679,W,0.077,,020622,,,D*6D
$GPVTG,,T,,M,0.077,N,0.142,K,D*21
$GPGGA,054003.00,1437.91511,N,09035.52679,W,2,06,2.54,1668.7,M,-4.9,M,,0000*68
$GPGSA,A,3,22,01,48,31,32,21,,,,,,,3.95,2.54,3.02*02
$GPTXT,01,01,01,NMEA unknown msg*58
$GPGSV,4,1,13,01,18,301,33,10,49,124,11,16,20,189,12,21,29,276,24*74
$GPGSV,4,2,13,22,39,008,32,23,18,135,,25,19,052,11,26,47,169,09*79
$GPGSV,4,3,13,27,02,204,18,31,64,342,30,32,35,037,29,46,43,252,*7A
$GPGSV,4,4,13,48,47,250,30*40
$GPGLL,1437.91511,N,09035.52679,W,054003.00,A,D*70
GPGLL行包含我们要查找的所有关于纬度和经度的信息。
重要说明
根据设备配置,GPRMC行可能包含海拔数据。在之前的输出中,海拔信息未配置,因此该行不会包含这些信息,但设备可以配置以获取海拔信息。
现在,我们知道应用程序如何从 GPS 模块读取信息。让我们使用已安装 K3s 的设备来部署我们的应用程序。
部署 gps-reader 将 GPS 坐标发送到云端
使用 K3s 的一个优势是,如果你的应用程序比较复杂,你可以将应用程序分模块或微服务部署,并且可以更新这些模块而不影响其他模块。在这种情况下,我们只使用了一个模块,称为gps-reader。这个应用程序通过 Pod 从设备读取 GPS 模块。在这个案例中,我们使用的配置允许我们仅用必要的权限读取设备上的/dev文件夹,访问/dev/ttyACM0设备,GPS 模块在此设备上显示 GPS 坐标。该设备可能会变化,具体取决于你使用的 GPS 模块。
要在你的设备上创建读卡器,请执行以下步骤:
-
创建
gps-readerPod 以开始从你的模块读取 GPS 坐标:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: gps-reader spec: containers: - image: sergioarmgpl/gps_reader name: gpsreader imagePullPolicy: Always env: - name: DEVICE value: "/dev/ttyACM0" - name: CLIENT_ID value: "1" - name: ENDPOINT value: "http://<GPS_SERVER_IP>:3000" securityContext: privileged: true capabilities: add: ["SYS_ADMIN"] volumeMounts: - mountPath: /dev name: dev-volume volumes: - name: dev-volume hostPath: path: /dev type: Directory EOF
此 Pod 将使用以下环境变量:
-
DEVICE:此项包含 GPS 模块监听的虚拟设备。根据你使用的 GPS 模块,可能会有所不同。请参阅进一步阅读部分获取更多信息。
-
CLIENT_ID:这是该读卡器在系统中代表的车辆编号——在此案例中为 1,表示第一辆车。
-
GPS_SERVER_IP变量在部署 gps-server 以存储 GPS 坐标部分中。
重要说明
要查看代码并创建你自己的gps-reader容器,请参考以下链接:github.com/sergioarmgpl/containers/tree/main/gps-reader/src。如果你想使用 OLED 屏幕显示信息,请参考以下链接:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/blob/main/ch13/code/python/oled.py。该代码使用树莓派 4B 的 keyestudio Complete RFID Starter 套件中的 OLED 屏幕。
-
你可以通过运行以下命令检查你的设备是否正在读取信息:
$ kubectl logs pod/gps-reader -f
如果你不确定是否可以访问你的设备,可以通过查看前端并检查设备是否出现在地图上来测试。
输出将如下所示:
<Response [200]>
{'lat': 11.6318615, 'lng': -80.59205166666666, 'cid': '1'}
-
按Ctrl + C取消操作。
-
输入
exit并按Enter退出你的树莓派。
现在,我们已经启动了所有后端服务并接收数据,但我们需要将这些信息可视化。接下来,进入下一部分,部署frontend应用程序。
使用 Open Street Maps 实时可视化您的设备
我们的应用程序有两个部分,一个是实时可视化车辆的 GPS 坐标及其附近的停靠点,另一个是显示车辆在时间范围内的历史轨迹。因此,让我们首先了解实时显示设备的地理跟踪地图的代码。
理解地理跟踪地图可视化代码
让我们从一张显示所有车辆及其坐标和附近停靠点的地图开始。我们使用 HTML、JavaScript、jQuery 和 Leaflet 库来创建这张地图。让我们来看一下地图的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<Load Javascript libraries>
<Load page styles>
<body>
<div id='map'></div>
<script>
<Load Map in an initial GPS position>
var marker
var markers = []
var osm = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
<Set Open Street Map Initial
Configuration using Leaflet>
});
osm.addTo(map);
setInterval(() => {
$.getJSON("URL",
function(pos) {
<Delete current markers>
<Get current positions for each unit or truck>
<For each position set a marker
in the map calling
the function markPosition>
});
}, 5000);
function markPosition(cid,lat,lng,near)
{
<Create a maker in the map with
Latitude, Longitude, Unit number and near destinies>
}
</script>
</body>
</html>
我们的页面加载了一些 JavaScript 库和 CSS 样式。之后,它加载了一个初始的 GPS 位置来显示地图。此地图被加载到<div id='map'></div>代码中。
该代码中的重要函数如下:
-
gps-server用于获取所有的 GPS 坐标。为此,setInterval函数调用http://GPS_SERVER_IP:3000/clients/positions/unit/km/r/0.1网址,该网址返回每个车辆的当前位置和其周围 0.1 公里范围内的停靠点。为此,每隔 5 秒调用markPosition函数,并传送客户端 ID 或车辆编号(cid)、纬度(lat)、经度(lng)以及包含停靠点名称的near变量。此函数将在地图上创建一个标记对象。 -
markPosition:该函数在地图上创建一个 Leaflet 标记对象,并在地图上弹出窗口。调用此函数时,还会重置地图。
该应用程序基本上加载所有必要的库,并调用setInterval函数,每 5 秒刷新一次地图,通过调用markPosition函数。设置初始 GPS 位置以居中地图是非常重要的;这个位置可以在用于部署frontend应用程序的 YAML 文件中进行自定义。地图初始化后,5 秒后将显示所有跟踪的对象:

图 13.5 – 显示两台使用 GPS 的设备的地图
如果连接的设备没有向地图发送数据,它将显示一张空白地图;在这种情况下,图 13.5显示了两台连接并发送数据的设备。现在,假设我们使用的是设备或车辆编号为 2——在此例中,表示为第二个CID(客户端 ID)。如果点击蓝色标记,它将显示当前的纬度和经度坐标,以及跟踪车辆的附近目的地或停靠点。在这种情况下,我们设置了两个停靠点,galeno_encinal和la_torre_encinal,它们距离跟踪车辆当前位置 0.1 公里。如果点击蓝色标记,你将看到类似的内容:

图 13.6 – 点击蓝色标记时显示的附近目的地
此信息每 5 秒计算一次,实时更新您被跟踪车辆的最近位置。您可以根据需要定制代码;这只是一个构建使用 GPS 的地理跟踪系统的快速入门示例。让我们看看我们的车辆路线报告如何工作,以显示从跟踪车辆收集的数据。
理解车辆路线报告
此应用程序创建一个蓝线,显示存储在 MongoDB 中的跟踪日志。这代表了车辆在特定日期或时间范围内的路线。在我们深入研究其工作原理之前,让我们首先探讨一下本页的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<Load Javascript libraries>
<Load page styles>
<body>
<form>
<input id="cid" name="cid"></input>
<input id="sdate" name="sdate"></input>
<input id="edate" name="edate"></input>
<button onclick="loadMap()"></button>
</form>
<div id='map'></div>
<script>
<Load Map in an initial GPS position>
var tiles = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
<Set Open Street Map Initial
Configuration using Leaflet>
}).addTo(map);
function onEachFeature(feature, layer) {
<Set a popup with the line visualizing the route
of the vehicle>
}
var trip;
function loadMap(){
$.getJSON(<DYNAMIC_URL>, function(pos) {
var coordinates = [];
<Creating an array with the coordinates
between the time range>
this.trip = {
<The array with the coordinates and fields
to visualize in the map>
};
var tripLayer = L.geoJSON(this.trip, {
<Get the trip data and visualize it
into the map>
}).addTo(map);
});
}
</script>
</body>
</html>
让我们分析下一个代码段:
-
trip: 这个变量包含所有坐标,用于在地图上绘制车辆在时间范围内覆盖的路线。
-
form: 这是一个用于生成动态调用的 HTML 表单,以获取所选时间范围内所有 GPS 位置的工具。
-
tracking-server并获取所有 GPS 位置。此 URL 的结构如下:http://TRACKING_SERVER_IP:3000/client/2/positions/s/25-05-22-04:39:58/e/25-05-22-04:40:00。 -
onEachFeature: 这是一个创建包含车辆 GPS 位置的线的函数。
-
LoadMap: 这是一个在点击表单加载按钮后调用的函数,用于显示车辆在时间范围内覆盖的路线。
总的来说,当单击显示路线历史按钮时,会生成此报告页面,显示地图上车辆的路线如下:

图 13.7 – 车辆路线报告
我们的 tracking-server 服务配置为在不同国家使用本地化时间和 UTC 时存储和查询车辆跟踪信息的日志。这是一个基本的车辆路线报告的实现,您可以根据需要进行定制。
重要提示
要了解更多关于 UTC 时间的信息,请查看以下链接:www.timeanddate.com/time/aboututc.html。
此地图的另一个功能是,当您单击地图时,它可以显示一些信息。在这种情况下,我们显示了一个示例消息,但您可以根据需要进行定制,例如显示车辆在特定位置的时间:

图 13.8 – 点击地图时车辆路线信息
要重置报告,您需要重新加载页面。通过这样做,我们准备部署包含实时地图和此报告的前端应用程序,最终可以访问最终应用程序。为此,让我们继续下一节。
部署实时地图和报告应用程序来跟踪您的设备。
现在我们已经准备好所有东西,所以我们需要部署包含实时地图和报告页面的前端应用。为此,我们使用一个简单的 Flask 应用,结合 Python 和模板;以下是代码:
<imported libraries>
<app_initialization>
<CORS configuration>
@app.route("/")
def map():
return render_template(<Render map.html
Using environment variables)
@app.route("/report")
def report():
return render_template(<Render report.html
using environment variables>)
<Starting the application on port 3000>
该应用呈现 map.html 页面,加载 Leaflet 库以显示地图,使用初始的纬度和经度变量。它还设置了 gps-server 的端点,该端点在此静态页面中被调用。要部署此应用,请按以下步骤操作:
-
通过运行以下命令创建部署:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: frontend name: frontend spec: replicas: 1 selector: matchLabels: app: frontend strategy: {} template: metadata: creationTimestamp: null labels: app: frontend spec: containers: - image: sergioarmgpl/frontend name: tracking-server imagePullPolicy: Always env: - name: LATITUDE value: "<YOUR_LATITUDE_COORDINATE>" - name: LONGITUDE value: "<YOUR_LONGITUDE_COORDINATE>" - name: GPS_SERVER value: "<YOUR_GPS_SERVER_IP>" - name: TRACKING_SERVER value: "<YOUR_TRACKING_SERVER_IP>" resources: {} status: {} EOF
此部署包含以下环境变量:
-
纬度: 用于定位地图中心的初始 GPS 纬度坐标
-
经度: 用于定位地图中心的初始 GPS 经度坐标
-
gps-server应用。 -
tracking-server应用。
通过这些变量,你可以配置初始加载的 GPS 坐标以定位地图中心,并设置页面调用的端点,显示 frontend 应用的报告路线实时地图。
重要说明
要查看代码并创建你自己的 frontend 容器,请参阅以下链接:github.com/sergioarmgpl/containers/tree/main/frontend/src。要获取加载时地图中心的初始 GPS 坐标,请访问此网站:www.gps-coordinates.net。
-
为你的应用创建负载均衡器服务:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: frontend name: frontend-lb spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: frontend type: LoadBalancer status: loadBalancer: {} EOF -
使用以下命令获取
frontend部署的负载均衡器 IP:$ FRONTEND_IP="$(kubectl get svc frontend-lb -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
你可以通过运行以下命令查看 FRONTEND_IP 环境变量的值:
$ echo $FRONTEND_IP
重要说明
我们使用了 LoadBalancer 服务类型来简化实现,但一种更便宜的解决方案是使用 Ingress 定义来暴露应用程序。你可以通过以下链接获取更多信息:kubernetes.io/docs/concepts/services-networking/ingress。
-
现在,在浏览器中访问
http://<FRONTEND_IP>:3000,以下是 URL 的重要端点:-
http://<FRONTEND_IP>:3000 -
http://<FRONTEND_IP>:3000/report
-
-
现在,在你的车辆中打开 Raspberry Pi 设备,并等待直到设备开始发送 GPS 坐标。别忘了为每个设备设置停靠点。几秒钟或几分钟后,你的地图将开始实时显示设备的位置。
-
通过在装备了 Raspberry Pi 设备的车辆中驾驶并记录一些数据,随后测试你的报告。
现在,我们的简单地理定位系统已经准备就绪并开始运行。在完成本章内容后,重要的是要提到,这只是一个基本示例,你可以根据需求进行扩展。现在,是时候回顾我们所学到的内容了。
概要
在本章中,我们学习了如何利用 MongoDB 和 Redis 存储和查询 GPS 坐标,以构建一个基础的地理定位系统。我们还学习了如何将 GPS 模块集成到边缘设备中,并将信息发送到云端,最终在地图上实时可视化车辆的移动,展示圆形区域内的附近停靠点,从而模拟一个基础的跟踪配送系统。这展示了如何使用地理定位实现一个简单的应用案例,以及边缘设备在实时移动中如何在地理定位系统中互动。在下一章中,我们将学习如何使用机器学习和计算机视觉创建一个小型智能交通项目。
问题
下面是一些问题,用于验证你新的知识:
-
如何使用 GPS 技术创建一个地理定位系统?
-
如何使用 Redis 存储 GPS 坐标并对这些数据进行查询?
-
如何使用 MongoDB 存储地理定位系统的日志?
-
如何设计一个实时应用,展示移动车辆的 GPS 位置?
-
如何使用边缘计算和 K3s 创建一个分布式系统来跟踪车辆?
进一步阅读
你可以参考以下资料,了解本章涵盖的主题:
-
VK-162 G-Mouse GPS 模块:
www.amazon.com/Navigation-External-Receiver-Raspberry-Geekstory/dp/B078Y52FGQ -
Redis 空间索引命令:
redis.io/commands/?group=geo -
哈希表基础:
www.hackerearth.com/practice/data-structures/hash-tables/basics-of-hash-tables/tutorial -
在 Windows、Mac 和 Linux 上查找 Arduino 端口:
www.mathworks.com/help/supportpkg/arduinoio/ug/find-arduino-port-on-windows-mac-and-linux.html -
raspi-config:
geek-university.com/raspi-config -
GPS – NMEA 语句信息:
aprs.gids.nl/nmea -
Leaflet – 一个开源的 JavaScript 库,用于移动友好的互动地图:
leafletjs.com -
GPS 坐标:
www.gps-coordinates.net -
Epoch 和 Unix 时间戳转换工具:
www.epochconverter.com -
pytz 时区库:
pypi.org/project/pytz
第十四章:使用 Python 和 K3s 集群的计算机视觉
人工智能(AI)通常用于替代人类每天进行的活动。在大多数情况下,它可以赋予系统智能,使其能够在没有人工干预的情况下自主运行。计算机视觉(CV)是人工智能的一个子类别,专注于检测视频和图像中的物体。计算机视觉通常用于检测城市中的交通流量。本章重点介绍构建一个基本的智能交通系统,该系统能够在车辆行驶时检测到诸如汽车、卡车和行人等物体。为此,系统使用了 OpenCV、TensorFlow 和 scikit-learn Python 库以及一个相机,在 Raspberry Pi 上执行边缘计算的计算机视觉。该系统还在本地为司机显示包含检测物体的地图,并实现了一个公共地图,用于全局检测物体的可视化。这个公共地图可以作为实时交通状态图,供市政部门使用。
本章将涵盖以下主要主题:
-
计算机视觉和智能交通系统
-
使用 Redis 存储临时物体 全球定位系统(GPS)位置
-
部署计算机视觉服务以使用 OpenCV、TensorFlow Lite 和 scikit-learn 检测汽车障碍物
-
部署边缘应用以基于计算机视觉可视化警告
-
部署智能交通系统的全局可视化器
技术要求
在本章中部署计算机视觉系统,你将需要以下内容:
-
在你的公共云提供商(Amazon Web Services(AWS)、Azure、Google Cloud Platform(GCP))中托管的 Kubernetes 集群。
-
一台带有 8GB 微型 Secure Digital(SD)卡和小型 液晶显示器(LCD)屏幕的 Raspberry Pi 4B,用于汽车中。
-
一款 Logitech C922 PRO 网络摄像头,推荐使用,因为其质量优良并支持 Linux 系统。
-
多个 VK-162 G-Mouse USB GPS 导航模块,用于你的边缘 Raspberry 设备。
-
人工智能的基本知识。
-
配置
kubectl以便在本地机器上使用你的 Kubernetes 云集群,避免使用--kubeconfig参数。 -
克隆
github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch14仓库,如果你想运行kubectl apply而不是从书中复制代码。查看code目录中的python目录,以及ch14目录中的yaml目录,里面包含了 YAML 配置文件。
有了这个,你可以部署 Prometheus 和 Grafana 来开始在边缘环境中进行实验性监控。
计算机视觉和智能交通系统
人工智能(AI)是计算机科学的一个领域,旨在通过数学、统计学、语言学、计算机科学及其他科学模拟人类智能。人工智能也可以定义为理性代理的研究,如下图所示:
![图 14.1 – 代理]
图 14.1 – 代理
以图 14.1为参考,代理接收来自环境的感知。这些感知由传感器捕捉,信息经过处理后,通过效应器执行动作。这些动作由安装在代理内部的内部规则决定。动作可能包括使用效应器,如手臂、腿或车轮等。
这些内部规则可以通过不同的机器学习(ML)范式来实现,例如监督学习(SL)、无监督学习(UL)和强化学习(RL)。
机器学习(ML)是一种利用历史数据作为输入进行预测的人工智能技术。计算机视觉是应用于图像和视频分析的机器学习子集,使用预测来实现分析。在我们这一章中,我们将根据代理通过摄像头捕获的信息进行预测,并据此做出决策,同时将计算机视觉应用于创建智能交通系统。让我们来看一下以下的图示,展示了我们的系统如何在边缘使用计算机视觉来创建智能交通系统:

图 14.2 – 使用计算机视觉的智能交通系统
智能交通系统通常被市政府用来提高安全性,并以具有成本效益的方式改善街道上的交通流量。我们的系统可以在两种模式下使用。静态模式使用位于城市静止位置的摄像头,而动态模式则使用移动中的汽车扫描交通情况。我们将使用动态模式。现在,让我们通过边缘计算系统的各层来解释我们的系统,如下所示:
-
云层:在这一层,我们将使用一个名为Traffic Manager的应用程序编程接口(API),它将所有在边缘检测到的对象存储在 Redis 实例中。存储的数据将包含对象的类型——如汽车、卡车和行人——这代表我们系统中的一级警告,以及 GPS 坐标。这意味着驾驶员将通过其他驾驶员提前被警告到已检测到的对象。我们的 API 将存储这些对象的 GPS 位置,这些对象可能成为车辆的障碍。该层还将包括一个名为Traffic Map Public的前端应用程序,显示地图上的检测对象。市政府可以使用这个应用程序来监控全市的交通状况。
-
近边缘:这一层使用第四代(4G)/第五代(5G)长期演进(LTE)移动网络来将信息发送到互联网。这一层将把在边缘收集的信息传送到云层。
-
远端边缘:我们的远端边缘设备配有一台树莓派,它将处理由摄像头捕捉到的信息。该设备已安装了 K3s,作为一个单节点集群来管理系统所使用的所有服务。K3s 可以为系统带来自动化。K3s 可以轻松地更新和维护系统,并且可以扩展系统以使用更多节点。这些额外的节点可以用于添加多个摄像头,以从多个角度进行物体检测。运行在集群中的计算机视觉应用程序由两个显示屏和两个 API 组成。一个显示屏运行在 K3s 之外,但与 Python 脚本在同一设备上,它是负责捕捉视频的服务。该服务由一个 Python 程序组成,使用 OpenCV 和预编译的 TensorFlow Lite 模型进行物体检测。计算机视觉的过程就在这里发生。系统使用连接到设备的小型 LCD 触摸屏。另一个显示屏是一个前端应用程序,运行在浏览器中;它展示了地图上的检测物体,不仅显示这些本地检测到的物体,还显示在 500 米半径内所有车辆检测到的物体。检测到的物体将通过推理 API 进行分类,按照其对驾驶员的警告级别进行分类。这些警告分为三个级别:级别 1 和 2 表示警告,级别 3 则可能被驾驶员忽视作为障碍物。推理 API 包含一个预编译的决策树进行分类。GPS 队列API 管理所有 GPS 坐标,并定期将代表警告的检测物体信息发送到云端,供其他驾驶员查看。整个应用程序使用显示屏、交通地图、推理和 GPS 队列组件来处理和可视化检测到的物体。GPS 队列服务基于第五章中创建的 GPS 服务,K3s Homelab 用于边缘计算实验,并进行了一些修改。需要注意的是,您可以通过使用外部设备来加速物体检测,这些设备加速神经网络(NN)处理。您可以考虑的一些设备包括 Google 的 Coral USB 加速器、Rock Pi 神经计算棒Universal Serial Bus(USB)和 NVIDIA Jetson Nano。这些设备通过将处理任务委派给专用的处理单元(有时称为图形处理单元(GPU)或张量处理单元(TPU))来加速 OpenCV 的神经网络处理。OpenCV 库使用 TensorFlow Lite 模型,因此使用这些设备可以提高 TensorFlow Lite 可用的 GPU 数量,从而增加每秒帧数(FPS),这有助于加速视频分析,TensorFlow Lite 设计用于在边缘设备上运行以加速您的视频分析。有关更多信息,请查看进一步阅读部分。
-
Tiny edge:在这里,我们可以找到一个 LCD 屏幕,用于实时显示所有检测到的物体和警告信息。你还可以在这里找到 VK-162 G-Mouse GPS 模块。
总结一下这个工作流程,我们的车辆首先使用相机捕捉图像;然后,使用 OpenCV 捕捉视频帧或图像,并通过 TensorFlow Lite 进行分类,再通过推理 API 按照警告级别为驾驶员分类。这些信息会在 LCD 屏幕和浏览器中本地显示。发送到云端的 GPS 坐标数据会在云中的公共 Web 前端应用程序中显示。现在,让我们开始构建一个基本的智能交通系统,用于警示驾驶员。
使用 Redis 存储临时对象的 GPS 位置
我们将使用 Redis 存储通过计算机视觉检测到的所有物体的 GPS 坐标。这是一个基本的配置,用于部署 Redis 来实现这一目的。这个 Redis 实例必须部署在云端。正如我们在 第十三章 中解释的那样,使用 GPS、NoSQL 和 K3s 集群的地理定位应用程序,我们将使用地理空间索引来表示我们的数据。不同之处在于,我们将实现使用 traffic 的临时数据存储,它存储由其他驾驶员检测到的所有交通物体。通过这种方式,我们实现了一种垃圾回收功能,用于在交通高峰时段删除旧的检测物体。原因是,检测到的物体只在特定时间内相关,之后必须删除。那么,让我们按照以下步骤安装我们的 Redis 部署:
-
为 Redis 创建一个 PersistentVolumeClaim,以持久化我们的数据,如下所示:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-pv-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi EOF -
现在,创建一个 ConfigMap 来配置 Redis 使用认证密码,如下所示:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: redis-configmap data: redis-config: | dir /data requirepass YOUR_PASSWORD EOF -
使用前面的
redis-configmapdb-pv-claim-1PersistentVolumeClaim 创建 Redis 部署,并设置一些资源限制,使用以下命令:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: redis name: redis spec: replicas: 1 selector: matchLabels: app: redis strategy: {} template: metadata: creationTimestamp: null labels: app: redis spec: containers: - name: redis image: redis:6.2 command: - redis-server - /redisconf/redis.conf ports: - containerPort: 6379 resources: limits: cpu: "0.2" memory: "128Mi" volumeMounts: - mountPath: "/data" name: redis-storage - mountPath: /redisconf name: config volumes: - name: config configMap: name: redis-configmap items: - key: redis-config path: redis.conf - name: redis-storage persistentVolumeClaim: claimName: db-pv-claim-1 status: {} EOF -
现在,为 Redis 创建一个服务,开放端口
6379,如下所示:$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: redis name: redis spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: app: redis type: ClusterIP EOF
现在我们已经安装了 Redis,接下来让我们在下一节中部署我们的计算机视觉服务到边缘设备。
部署计算机视觉服务来检测车辆障碍物,使用 OpenCV、TensorFlow Lite 和 scikit-learn
在本节中,我们将探讨如何配置运行在边缘的物体检测系统及其所有组件。本节还展示了如何配置运行在云端的公共 Web 应用程序,该应用程序存储并显示关于所有在边缘检测到的物体的信息。让我们首先从配置 Raspberry Pi 设备开始,下一节中将详细讲解。
准备你的 Raspberry Pi 来运行计算机视觉应用
在安装软件之前,我们必须准备好设备来运行它。为此,让我们按照以下步骤开始配置我们的 Raspberry Pi 4B:
-
使用 Debian Bullseye 安装 Raspbian Pi OS(32 位),并至少从 2022-04-04 发布。本文中运行 TensorFlow Lite 模型的代码必须在 ARMv7 设备上运行,以支持 Coral USB 加速器设备和 LCD 屏幕。ARM64 目前不受支持。
-
根据你的网络摄像头,你可能需要安装驱动程序。在本例中,我们使用的是 Logitech C922 PRO 网络摄像头,Raspbian 会自动识别它。
-
连接并配置你的 GPS 模块。在本例中,我们的 VK-162 G-Mouse 模块也会被 Raspbian 自动识别。
-
配置网络以使用无线连接,并安装运行应用所需的所有必要软件包。之后,你可以重新配置无线连接,以连接到智能手机中的接入点,但你需要删除
/etc/wpa_supplicant/wpa_supplicant.conf文件中的先前连接。 -
安装你的 LCD 屏幕的驱动程序。在本例中,我们使用的是 Miuzei 高清多媒体接口(HDMI)。这将水平翻转屏幕并激活触摸功能(当所有配置完成后,这是最后一步)。你可以查看该代码库:
github.com/goodtft/LCD-show.git,并且可以使用任何 LCD 屏幕。 -
在安装 K3s 之前,记得在
/boot/cmdline.txt文件中启用 CGROUPS,然后在行末添加以下标志:cgroup_memory=1 cgroup_enable=memory
重要提示
如需有关 CGROUPS 的更多信息,请访问此链接:man7.org/linux/man-pages/man7/cgroups.7.html
-
获取当前的
ifconfig,然后查看wlan0接口,如下所示:$ ifconfig wlan0 -
通过运行以下命令安装 K3s:
$ MASTER_IP=YOUR_PRIVATE_IP $ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644" sh -s - -
现在,你可以通过运行以下命令来测试一切是否正常:
$ kubectl get nodes
这将返回你当前唯一运行的节点。
现在,我们的边缘设备已准备好运行服务,执行边缘计算中的计算机视觉任务。接下来,让我们进入下一节。
部署推理服务以检测物体
在此场景中使用 inference 服务进行预测,并分类判断物体是否对驾驶员构成障碍。我们使用下表来完成此操作:

例如,n 字段中 id 为 1 的汽车表示警告级别 1,因此所有 warning_level 为 1 或 2 的物体将被记录为可能会阻碍交通或对驾驶员造成危险的潜在物体。如果物体的分类值为 1000,则该物体不代表任何危险,因此不会被记录。
此服务的源代码由两个文件组成:index.py和create_model.py。index.py文件包含一个基本的 API,通过调用模型来使用/predict路径进行预测。它包含加载预编译的机器学习(ML)模型的基本代码。create_model.py文件包含用于训练并生成将被此 API 使用的模型的代码,该模型使用index.py。代码如下所示:
import pandas as pd
from sklearn import tree
from joblib import dump
df = pd.read_csv("safety_rules.csv",sep=',', header='infer', encoding='latin-1')
df = df.drop(['object'], axis=1)
df.head()
feature_cols = ["n"]
X = df.loc[:, feature_cols]
y = df.warning_level
clf = tree.DecisionTreeRegressor()
model = clf.fit(X, y)
dump(clf, 'safety_rules.model')
在这里,我们读取了我们的safety_rules.csv drop。在 AI 中,必须将文本表示为数值。我们的对象列在 n 列中有一个数字表示,因此该列对象可以忽略。 从 CSV 文件加载的数据表示为一个 Pandas DataFrame,它在 scikit-learn 中作为生成决策树的数据源。决策树是一种 ML 算法,可以使用分类数据,通过树形数据结构进行预测。因此,它是使用机器学习进行预测的最简单方法之一。在 DataFrame 加载之后,scikit-learn 会执行其训练过程,生成一个safety_rules.model模型,该模型可以在 API 中用于后续的预测。每次构建容器时,模型都会通过在此 API 的Dockerfile中调用create_model.py文件来更新。现在,API 的服务代码将如下所示:
<Import Flask and Scikit Learn libraries>
def loadModel():
<Load the model safety_rules.model>
<Assign the loaded model to the variable clf>
@app.route('/predict', methods=["POST"])
def predict():
<Use clf variable to call the prediction method>
<Return the prediction using JSON format>
<Inference service initialization on port 3000 by default>
通过调用/predict的safety_rules.csv文件,你可以通过在文件中添加新的值并使用新模型重新生成容器,来为你的图像分类添加更多的值。
重要提示
要检查代码并更新模型,请查看下一个链接:github.com/sergioarmgpl/containers/tree/main/inference/src。
现在,让我们通过以下步骤,在我们的高级精简指令集计算机(ARM)设备上部署我们的inference服务:
-
为
inferenceAPI 创建一个部署,步骤如下:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: inference name: inference spec: replicas: 1 selector: matchLabels: app: inference strategy: {} template: metadata: creationTimestamp: null labels: app: inference spec: containers: - image: sergioarmgpl/inference name: inference imagePullPolicy: Always resources: {} status: {} EOF -
让我们像这样进行端口转发以运行服务:
$ kubectl port-forward --address 0.0.0.0 deploy/inference 3000:3000 -
现在,让我们调用
inferenceAPI 来获取一些预测。我们使用一个被检测并分类为other且编号为 6 的对象,它将根据预测表返回一个警告级别为 3 的结果。代码如下所示:$ curl --header "Content-Type: application/json" \ --request POST --data '{"data":[6]}' \ http://localhost:3000/predict
这将返回以下输出:
{
"prediction": 3.0
}
我们的推理服务现在正在运行,准备在设备内被调用以分类检测到的图像。接下来,让我们继续在下一部分部署gps-queue服务。
部署 gps-queue 服务以存储 GPS 坐标
gps-queue 服务由多个容器组成,每个容器专注于特定任务。首先,初始化一个名为init-gps-queue的init容器,它会在/tmp/gps文件中添加一个初始值-1。该文件存储生成的最后一个 GPS 坐标。然后,gps-queue容器负责从我们的 GPS 模块读取 GPS 坐标,因此它需要获得对主机/dev文件夹的访问权限。一旦读取到 GPS 坐标,它将被存储在/tmp/gps中。之后,sync-traffic-events容器每 30 秒默认调用一次gps-api容器,通过http://localhost:3000/traffic端点,将检测到的对象及其警告分类和 GPS 坐标发送到http://<TRAFFIC_MANAGER_IP>:5000公共端点,该端点会暂时存储这些信息,以便在traffic-map-public服务中显示,这个服务对外开放,展示其他车辆检测到的对象。在部署我们的服务之前,让我们先探索一下gps-queue容器的代码,如下所示:
<Import necessary Python libraries to read the GPS module>
<cid variable to set a unique client id for these coordinates>
<device variable to set where the GPS module will be read in /dev>
<ser variable to configure the serial communication with the GPS module>
<Initializing the device to read information>
while True:
<Read the Coordinate and store it into /tmp/gps>
这段代码配置了 GPS 模块,并将坐标存储在/tmp/gps文件中,该文件由gps-queue和gps-api容器共享。它使用cid变量将每个 GPS 坐标与一个唯一的客户端标识符(ID)关联起来,该标识符可以用于自定义,以便创建你自己的系统。信息将以以下格式存储:
{'lat': <LATITUDE_VALUE>,'lng':<LONGITUDE_VALUE>,'cid':<CLIENT_ID>}
现在,让我们探索一下gps-api容器中的代码,如下所示:
<Import the necessary Python libraries to run this code>
<Set traffic_events variable to accumulate detected objects for a time period>
<Flask and CORS configuration>
@app.route("/gps", methods=["GET"])
def getGPSCoordinate():
<Read coordinate form /tmp/gps>
<Return the GPS coordinate as JSON as
{'lat': <LATITUDE_VALUE>,'lng':<LONGITUDE_VALUE>
,'cid':<CLIENT_ID>}
>
@app.route("/traffic/event", methods=["POST"])
def registerTrafficEvent():
<Read last GPS coordinate from /tmp/gps>
<Get object type and warning classification
from the computer vision service>
<Generate the Timestamp value for the new detected object>
<Assign to a variable the warning, Latitude, Longitude
and timestamp information for the object>
<Add this information to the traffic_events array
to store it temporary the value>
<Return the object ide and that the request was processed>
@app.route("/traffic", methods=["GET"])
def syncTrafficEvents():
<Filter similar objects stored in the
traffic_events array>
<Send the filtered array using JSON format to the
endpoint http://<TRAFFIC_MANAGER:5000>/traffic/1
to store this information and get it locally and
public by calling the endpoint
http://<TRAFFIC_MANAGER:5000>/traffic>
<Return that the information syncTrafficEvents
was processed>
<GPS Queue service initialization on port 3000 by default>
作为说明,API 的/gps路径返回存储在/tmp/gps中的最后一个 GPS 坐标值,/traffic/event路径接收从运行detect.py程序的边缘设备检测到的对象。这个过程每秒发生一次。然后,信息被暂时存储在traffic_events数组中。在 Pod 内,sync-traffic-events容器每 30 秒调用一次运行在gps-api容器中的 API 的/traffic端点,过滤traffic_events数组,只保留唯一检测到的对象,因为边缘程序每帧视频分析最多只能检测到八个对象。一旦数组被过滤,它将被发送到http://<TRAFFIC_MANAGER:5000>/traffic/1。随后,这些信息会被http://<TRAFFIC_MANAGER:5000>/traffic网址请求,该网址使用 Leaflet 库在地图上展示来自所有设备的全球检测对象。
要部署此服务,请执行以下步骤:
-
创建一个 GPS 队列的部署,如下所示:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: labels: app: gps-queue name: gps-queue spec: replicas: 1 selector: matchLabels: app: gps-queue template: metadata: labels: app: gps-queue spec: initContainers: - image: busybox:1.34 name: init-gps-queue command: ['sh', '-c', "echo '-1' >> /tmp/gps"] securityContext: runAsUser: 1 volumeMounts: - name: tmp mountPath: /tmp containers: - image: sergioarmgpl/gps_queue name: gps-queue imagePullPolicy: Always env: - name: DEVICE value: "/dev/ttyACM0" securityContext: privileged: true capabilities: add: ["SYS_ADMIN"] volumeMounts: - mountPath: /dev name: dev-volume - name: tmp mountPath: /tmp - image: sergioarmgpl/gps_api name: gps-api ports: - containerPort: 3000 imagePullPolicy: Always env: - name: ENDPOINT value: "http://<TRAFFIC_MANAGER_IP>:5000" securityContext: runAsUser: 1 volumeMounts: - name: tmp mountPath: /tmp - image: curlimages/curl name: sync-traffic-events env: - name: URL value: "http://localhost:3000/traffic" - name: DELAY value: "30" command: [ "sh", "-c"] args: - while :; do curl ${URL}; sleep ${DELAY}; done; volumes: - name: dev-volume hostPath: path: /dev type: Directory - name: tmp emptyDir: {} status: {}
重要提示
要检查代码并创建自己的容器,可以查看以下链接:
github.com/sergioarmgpl/containers/tree/main/gps-api/src 和 github.com/sergioarmgpl/containers/tree/main/gps-queue/src
让我们关注一下这个部署在其容器中使用的变量。这里将详细解释这些变量:
-
gps-queue:DEVICE:配置检测到 GPS 模块的设备。对于 VK-162 G-Mouse 模块,默认值是/dev/ttyACM0。
-
gps-api:ENDPOINT:配置所有带有 GPS 坐标和警告的检测对象存储的公共端点。这是存储坐标的公共服务。默认情况下,这个地址是http://<TRAFFIC_MANAGER_IP>:5000。
-
sync-traffic-events:-
URL:包含周期性调用的本地 URL,用于发送所有检测到的对象信息。这将调用在gps-api容器中配置的 API。默认值是http://localhost:3000/traffic。 -
DELAY:配置发送最后检测到的对象及其信息的延迟时间。默认值是 30,表示以秒为单位的时间。
-
这些值可以用于定制处理检测到的对象及其 GPS 坐标的服务行为。
-
如果你想测试此服务的端点,可以在你的边缘设备内运行
port-forward来通过curl命令访问 API,方法如下:$ kubectl port-forward --address 0.0.0.0 deploy/gps-queue 3001:3000
例如,你可以执行以下命令:
$ curl http://localhost:3001/gps
它将返回类似以下内容:
{'lat': <LATITUDE_VALUE>,'lng':<LONGITUDE_VALUE>
,'cid':<CLIENT_ID>}
我们现在已经部署了 gps-queue 服务,并且它已经准备好使用。接下来是部署我们的本地 Web 应用程序,它将在边缘设备上显示通过摄像头检测到的对象。为此,我们需要解决本地 traffic-map 应用程序中的 traffic-manager 公共 API 问题。CORS 是一种机制,它允许或限制从当前域外的域请求网页上的资源。在此场景中,它被称为本地 Web 应用程序的公共 API。因此,让我们进入下一部分,创建一个简单的代理来解决这个问题。
部署 traffic-manager 来存储 GPS 坐标
traffic-manager 服务接收带有 GPS 坐标和警告级别分类的检测对象。这个 API 在云端运行,并且在边缘设备移动和检测对象时被周期性调用。此服务由两个容器组成:一个提供 API 用于收集检测到的对象,另一个负责自动过期检测到的对象和全局交通信息。因为交通状况在一天之内不断变化,你可以配置这些值以适应你的场景。让我们首先探索 traffic-manager 容器中 API 的代码,如下所示:
<Import the necessary Python libraries to run this code>
<Flask and CORS configuration>
<Set time to expire the traffic and objects by setting the values of the variables ttl_trf, ttl_obj>
def redisCon():
<Set and return the Redis connection>
@app.route("/traffic/1", methods=["POST"])
def setBulkTrafficObjects():
<Get the Redis connection calling redisCon()>
<Get detected objects from the POST request>
<Omit to store similar detected objects in a
5 meters radius>
<Set a hash value to store type and warning
level for each object>
<Set expiring time for each hash stored>
<Return that the operation was successful {"setTrafficObject":"done"}>
@app.route("/traffic/unit/<unit>/r/<radius>"+
"/lat/<lat>/lng/<lng>", methods=["GET"])
def getTrafficObjects(unit,radius,lat,lng):
<Get the Redis connection calling redisCon()>
<Get the objects detected and its metadata
from the previous stored hash
in the radius configured in the request>
<Return that the operation was successful and
the objects found
in the next format:
{"getTrafficObjects":"done",
"objects":data
}>
<Service initialization on port 3000 by default>
该容器有两个端点,路径为/traffic/1。此服务通过创建一个哈希键object:<object-id>:data来存储检测到的边缘对象,其中存储了对象类型和警告级别,在traffic地理空间集合中存储了 GPS 坐标。为traffic键设置或更新过期时间,对于新的object:<object-id>:data哈希键,也设置过期时间。调用/traffic/unit/<unit>/r/<radius>/lat/<lat>/lng/<lng>路径时,返回请求中定义的半径范围内检测到的附近对象。这是一个公共服务,所有边缘设备会定期访问该服务,以发送在移动过程中检测到的对象更新。现在,让我们继续探索autoexpire容器的代码,具体如下:
<Import all the necessary libraries>
<Set Redis connection in an r variable>
while True:
<Get all the objects inside the traffic sorted set>
<Check if each member of the set has its hash value>
<If not remove the member of the sorted set>
<Wait until the configured delay ends to
Update the set again>
该容器基本上会检查交通地理空间集合中的每个成员是否在object:<object-id>:data哈希键中有可用的元数据。如果没有,这意味着该对象已超出在交通中保持相关性的最大时间,这意味着它已经过期,接着这段代码会从排序集中过滤掉该成员。此过程会在等待由DELAY变量配置的秒数后定期进行。
要部署traffic-manager服务,请按以下步骤操作:
-
创建 GPS 服务器的部署,如下所示:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: traffic-manager name: traffic-manager spec: replicas: 1 selector: matchLabels: app: traffic-manager strategy: {} template: metadata: creationTimestamp: null labels: app: traffic-manager spec: containers: - image: sergioarmgpl/autoexpire name: autoexpire imagePullPolicy: Always env: - name: REDIS_HOST value: "redis" - name: REDIS_AUTH value: "YOUR_PASSWORD" - name: DELAY value: "30" - image: sergioarmgpl/traffic_manager name: traffic-manager imagePullPolicy: Always env: - name: REDIS_HOST value: "redis" - name: REDIS_AUTH value: "YOUR_PASSWORD" - name: TTL_TRAFFIC value: "900" - name: TTL_OBJECT value: "180" resources: {} status: {} EOF
此部署使用以下变量:
-
REDIS_HOST:这是 Redis 服务的名称。此变量可以根据需要进行自定义。 -
REDIS_AUTH:这是连接 Redis 服务的密码。 -
TTL_TRAFFIC:这是tracking-server服务的 URL。在此情况下,URL 与内部的tracking-server服务的端口3000匹配。 -
TTL_OBJECT:这是tracking-server服务的 URL。在此情况下,URL 与内部的tracking-server服务的端口3000匹配。 -
DELAY:这是等待检查流量地理空间排序集中的成员是否已过期的时间。
通过配置这些变量,您可以自定义此部署的行为。
重要说明
要查看代码并创建自己的容器,您可以查看以下链接:
github.com/sergioarmgpl/containers/tree/main/traffic-manager/src 和 github.com/sergioarmgpl/containers/tree/main/autoexpire/src
-
现在,让我们为此部署创建一个作为LoadBalancer的服务。此 IP 地址将在我们的边缘设备中使用,以便在云中传播此信息,使所有使用该智能交通系统的司机都可以访问。代码示例如下:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: traffic-manager name: traffic-manager-lb spec: ports: - port: 5000 protocol: TCP targetPort: 3000 selector: app: traffic-manager type: LoadBalancer EOF -
使用以下命令获取
traffic-manager部署的负载均衡器 IP 地址:$ TRAFFIC_MANAGER_IP="$(kubectl get svc traffic-manager-lb -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
通过运行以下命令,可以查看TRAFFIC_MANAGER_IP环境变量的值:
$ echo $TRAFFIC_MANAGER_IP
请注意,在负载均衡器的 IP 地址配置完成后需要一些时间。你可以通过运行以下命令来检查服务的状态:
$ kubectl get svc traffic-manager-lb
等待直到EXTERNAL_IP环境变量配置完成。
另外,请注意,$TRAFFIC_MANAGER_IP的值将用于在边缘设备中配置proxy服务。
-
(可选) 如果你想测试这个 API 以手动插入一个对象,请运行以下命令:
$ curl -X POST -H "Accept: application/json" \ -H "Content-Type: application/json" \ --data '{ "object":"person", "warning":1, "position":{"lat":1.633518,"lng": -90.591706} }' http://$TRAFFIC_MANAGER_IP:3000/traffic/1
这将返回以下输出:
{
"setTrafficObject": "done"
}
-
(可选) 要获取 0.1 公里半径内的所有检测对象,请运行以下命令:
$ curl -X GET -H "Accept: application/json" \ http://$TRAFFIC_MANAGER_IP:3000/traffic/objects/unit/km/r/0.1/lat/1.633518/lng/-90.5917
这将返回以下输出:
{
"getTrafficObjects": [
"person"
]
}
现在,我们的traffic-manager API 已经在云端运行。接下来,我们将使用代理在边缘设备中使用这个 API,以避免在调用 API 时遇到 CORS 限制,具体操作将在下一节中说明。
部署一个简单的代理以绕过 CORS
proxy服务用于绕过 CORS 限制,这种限制通常出现在本地网站尝试使用公共 API 地址调用公共 API 时。使用代理将请求转发到这个公共站点可能是解决这个问题的一个简单方法。另一种方法是修改 API 调用中的请求头,添加必要的头信息来绕过 CORS 限制。在这种情况下,我们将使用基于 Flask 构建的代理,将所有本地的GET请求转发到traffic-manager API,这个 API 是部署在云中的公共 API,可以通过互联网访问。在部署proxy服务之前,我们先来简要查看一下代码,如下所示:
from flask import Flask,request,redirect,Response
import os
import requests
app = Flask(__name__)
url = os.environ['URL']
@app.route('/<path:path>',methods=['GET'])
def proxy(path):
global url
r = requests.get(f'{url}/{path}')
excluded_headers = ['content-encoding'
, 'content-length', 'transfer-encoding'
, 'connection']
headers = [(name, value) for (name, value) in
r.raw.headers.items() if name.lower() not in
excluded_headers]
response = Response(r.content, r.status_code, headers)
return response
if __name__ == '__main__':
app.run(debug = False,port=5000)
这段代码基本上会接收所有GET请求并将请求以及所有重要的头信息转发到环境变量中定义的 URL。这个 API 可以通过5000端口访问。现在,接下来我们将部署这个简单的代理,将来自本地proxy服务的所有调用转发出去,执行以下步骤:
-
创建一个 GPS 服务器的部署,如下所示:
$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: proxy name: proxy spec: replicas: 1 selector: matchLabels: app: proxy strategy: {} template: metadata: creationTimestamp: null labels: app: proxy spec: containers: - image: sergioarmgpl/proxy name: proxy imagePullPolicy: Always env: - name: URL value: "http://<TRAFFIC_MANAGER_IP>:5000" resources: {} status: {} EOF
这个部署使用了以下变量:
URL:这个变量包含代理将在端口5000上接收到的所有GET请求重定向到的 URL。这个 URL 将是traffic-manager的公共 IP 地址,格式为http://<TRAFFIC_MANAGER_IP>:5000。
重要说明
要查看代码并创建你自己的容器,可以查看以下链接:github.com/sergioarmgpl/containers/tree/main/proxy/src。这个小型代理是一个自定义实现,你可以使用 Python 以外的语言来实现它,以便在实现中拥有完全的控制权。你也可以使用其他解决方案,比如使用带有proxy_pass配置的 NGINX 等。
-
你可以通过运行如下命令来测试代理:
$ curl http://localhost:5000/<REMOTE_PATH>
这里,远程路径可以是/traffic,这是一个 URL,Traffic Manager服务会返回所有由驾驶员在全球范围内检测到的对象。
现在我们的代理服务已经启动,让我们在接下来的部分中部署交通地图网页应用程序,以展示代表驾驶员警告的检测物体。
部署边缘应用程序以可视化基于计算机视觉的警告
我们的可视化应用程序由两个部分组成:第一部分是一个网页应用程序,显示所有驾驶员使用智能交通系统的数据,第二部分是一个桌面应用程序,实时显示检测到的物体。因此,接下来我们将开始安装我们的网页应用程序,以可视化不同驾驶员检测到的物体。
安装交通地图应用程序以可视化驾驶员检测到的物体
我们现在已经设置了必要的 API 来可视化我们的设备检测到的内容。接下来,我们需要继续部署网页应用程序,将这些物体在地图上进行可视化。这时,我们的交通地图应用程序就派上用场了。不过在部署之前,让我们先看看代码,代码如下:
<imported libraries>
<app_initialization>
<CORS configuration>
@app.route("/")
def map():
return render_template(<Render map.html
Using environment variables
GPS_QUEUE,TRAFFIC_MANAGER,
LATITUDE and LONGITUDE>)
<Starting the web application on port 3000>
这类似于在第十三章中使用的前一个网页应用地图,基于 GPS、NoSQL 和 K3s 集群的地理定位应用,但这个地图调用了 GPS 队列服务来获取正在边缘设备上运行的当前 GPS 坐标,并从 proxy 服务的公共端点获取数据,以防止 CORS 访问限制。它还具有在每次页面加载时将地图居中的选项。
网页部分使用 map.html 文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<Load Javascript libraries>
<Load page styles>
<body>
<div id='map'></div>
<script>
<Load Map in an initial GPS position>
var marker
var markers = []
var osm = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
<Set Open Street Map Initial
Configuration using Leaflet>
});
osm.addTo(map);
setInterval(() => {
$.getJSON("http://{{ GPS_QUEUE }}:3001/gps",
function(gps) {
<Delete current markers>
<Get current position of your device
and show it in the map>
$.getJSON(
"http://{{ TRAFFIC_MANAGER }}:5000"+
"/traffic/unit/km/r/0.5/lat/<LATITUDE>"
"/lng/<LONGITUDE>", function(pos) {
<This gets all the detected objects
in a radius of 0.5 km>
<For each object returned show it in
the map using
markPosition(object,lat,lng,o_type,warning)
function>
});
});
}, 5000);
<Configure the icons to visualize if an object is a
person, car or a truck>
function markPosition(object,lat,lng,o_type,warning)
{
<Create a maker with the appropriate Icon showing
the object name, latitude, longitude, type of object
and warning level>
}
</script>
</body>
</html>
这段代码基本上会将地图居中,显示初始的经纬度坐标,显示设备的当前位置(以蓝色地球图标表示),并显示检测到的物体的图标,展示物体名称、GPS 坐标、物体类型和警告级别。它应该类似于以下效果:

图 14.3 – 驾驶员当前位置
这会实时显示驾驶员当前位置,而车辆正在移动。另一个可能的可视化效果是展示检测到的物体在地图上的位置。这些信息是通过 proxy 服务请求的,用于可视化其他驾驶员检测到的所有物体。这可以代表一种增强现实(AR),类似于 Waze 应用中的实现。其可视化效果如下所示:

图 14.4 – 检测到的物体当前位置和警告信息
如果你点击检测到的物体,它将显示当前的 GPS 坐标、物体类型和警告信息。这个默认实现中包含了几个物体。实现包括汽车、卡车和行人检测,作为可能的障碍物和潜在的驾驶员警告。你可以在地图上看到以下图标:

图 14.5 – 交通地图中显示的汽车、卡车和人物图标
默认情况下,我们的 Web 应用程序每 5 秒更新一次在 0.5 公里半径内的物体。这些值可以根据您的需求进行定制。现在,让我们通过执行以下命令来部署我们的交通地图 Web 应用程序:
-
通过运行以下命令创建
traffic-map部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: traffic-map name: traffic-map spec: replicas: 1 selector: matchLabels: app: traffic-map strategy: {} template: metadata: creationTimestamp: null labels: app: traffic-map spec: containers: - image: sergioarmgpl/traffic_map name: traffic-map imagePullPolicy: Always env: - name: LATITUDE value: "<YOUR_LATITUDE_COORDINATE>" - name: LONGITUDE value: "<YOUR_LONGITUDE_COORDINATE>" - name: GPS_QUEUE value: "localhost" #<GPS_QUEUE_IP> - name: TRAFFIC_MANAGER value: "<TRAFFIC_MANAGER_IP>" resources: {} status: {} EOF
此部署包含以下环境变量:
-
LATITUDE:初始 GPS 纬度坐标,用于居中您的地图。 -
LONGITUDE:初始 GPS 经度坐标,用于居中您的地图。 -
GPS_QUEUE:gps-queue服务的 IP 地址端点。在本地运行时,默认设置为localhost。 -
TRAFFIC_MANAGER:proxy服务的 IP 地址端点,我们可以使用localhost来调用它,从而避免 CORS 限制。
重要提示
要查看代码并创建您自己的 traffic_map 容器,您可以查看以下链接:
github.com/sergioarmgpl/containers/tree/main/traffic-map/src
我们现在已经在边缘设备上部署了 交通地图 Web 应用程序。接下来,我们将运行边缘设备上的物体检测系统,执行我们的计算机视觉任务,如下节所述。
使用 OpenCV、TensorFlow Lite 和 scikit-learn 进行计算机视觉的物体检测
执行计算机视觉服务的代码包含在 detect.py 文件中。该文件将在我们的边缘设备上运行。在准备设备运行此程序之前,我们来探索一下这个文件中的代码,具体如下:
<Imported libraries to run OpenCV in TensorFlow Lite>
#Array to map detected objects
obj_values = {"car":1,"cat":2,"person":3
,"dog":4,"semaphore":5,"truck":6,"other":1000}
def run():
<Initialize Video Capture for the camera>
<Set screen size to capture>
<Initialize the object detection model>
#Array to store detected objects
items = []
while Camera is Opened:
detection_result = detector.detect(input_tensor)
items.clear()
<store detected objects in the items arrays>
<Show the FPS evaluated>
<Count objects detected per type of object>
<Get the classification of each object calling
/predict endpoint from the gps-api>
if the warning count of the group <= 2:
<A real warning is detected
we push this information calling
/traffic/event and warning is incremented>
if warning:
<show unique objects found
warning is set to zero>
else:
<show No warnings>
if <ESC key is pressed>:
<break the cycle>
<Set cv2 window size to show the capture>
<Close the Camera Capture>
<Destroy all windows>
def main():
<Parse parameters to run the program>
<Call run() function to start analyzing video capture>
if __name__ == '__main__':
<call the main() function of the program>
这段代码启动视频捕捉,然后将图像以 TensorFlow Lite 可以分析的格式发送。TensorFlow Lite 会检测物体的位置并使用物体名称对其进行分类。该程序将使用 efficientdet_lite0_edgetpu_metadata.tflite 模型。在本例中,我们关注的物体有汽车、人物、狗、信号灯和卡车。这些物体代表了驾驶员的障碍,并表示一定的警告级别。如果检测到的物体与这些物体不同,则会被分类为 other 并作为警告忽略。如果您想添加更多物体到列表中,只需修改 obj_values 数组并添加新值,如以下示例所示:
obj_values = {"car":1,..,"other_object":7,..."other":1000}
在此程序的每次循环中,检测到的对象按组进行计数并存储在items数组中。然后,如果其中某个组检测到多个对象,并且该组是obj_values数组中已识别的对象之一,则该组中的检测到的对象将被计为潜在的障碍物,代表着对驾驶员的警告。为了计算警告级别,脚本会调用inference API,然后,如果检测到警告,它会通过之前安装的proxy服务使用http://localhost:5000/traffic/event URL 调用traffic-map服务。每次调用代理时,请求将被发送到部署在云中的traffic-manager服务的公共端点。然后,在对象分析之后,items数组会被清空,并且通过 OpenCV 以蓝色框显示总结检测到的对象。它看起来像这样:

图 14.6 – 物体检测屏幕
该输出还显示了检测到的对象,用红色矩形标记,并显示检测到的对象名称。在左上角,您将看到分析的 FPS 数。我们的警告框将显示两种类型的消息:要么是找到的对象组(例如,发现了人和车),要么是没有检测到任何对象——这将显示没有警告消息。如果按下Esc键,服务将关闭。要在您的边缘设备上安装物体检测服务,请执行以下步骤:
-
将您的边缘设备连接到您可以访问的网络。
-
登录到您的边缘设备,如下所示:
$ ssh your_user@<EDGE_DEVICE_IP>
您可以通过运行以下命令获取设备的 IP 地址:
$ ifconfig wlan0
您可以通过将设备连接到 HDMI 屏幕,并连接键盘和鼠标来运行它。
-
通过运行以下代码克隆仓库:
$ git clone https://github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes $ cd Edge-Computing-Systems-with-Kubernetes/ch14/code/python/object_detection -
安装缺失的依赖项以运行 OpenCV 和摄像头,如下所示:
$ /bin/bash install_deps.sh -
配置设备以运行物体检测程序,如下所示:
$ /bin/bash setup.sh -
运行脚本安装桌面快捷方式,如下所示:
$ /bin/bash install_shortcuts.sh
重要提示
查看调用run.sh脚本的.desktop扩展名的文件,以及启动检测应用程序和本地 Web 流量地图应用程序的.desktop扩展名的文件。这些文件位于ch14/code/python/object_detection目录下。
-
通过点击新的Detector桌面快捷方式测试安装。
-
通过点击
http://localhost:5000测试本地流量地图应用程序。 -
将您的无线网络重新配置为使用智能手机的接入点连接,并通过删除
network {}条目重置您的/etc/wpa_supplicant/wpa_supplicant.conf配置文件,以使用智能手机的互联网连接。
重要提示
欲了解更多信息,您可以查看下一个链接:
wiki.archlinux.org/title/wpa_supplicant
-
现在,你可以配置触摸屏了。在此,我们使用的是 Miuzei LCD 4.0 英寸 HDMI 显示器,它可以翻转屏幕。为此,执行以下命令:
$ sudo rm –rf LCD-show $ git clone https://github.com/goodtft/LCD-show.git $ chmod –R 755 LCD-show $ cd LCD-show $ sudo ./MPI4008-show -
现在,通过运行以下命令重启你的设备:
$ sudo restart -
现在,访问检测快捷方式以启动检测物体的服务。
重要提示
你可以通过在ch14/code/python/object_detection/run.sh文件中取消注释--enableEdgeTPU标志来加速视频帧分析。我们的检测代码基于官方 TensorFlow 示例,该示例使用了 Coral USB 加速器设备。该设备是一个 TPU,是专门用于处理神经网络信息的单元。Coral 设备的配置超出了本书的范围。如需更多信息,请查阅进一步阅读部分中的 Coral USB 加速器链接。
- 通过点击交通快捷方式启动交通地图应用。如果检测到物体,它们将在 30 秒后显示在 Web 应用中。
最后一步是部署一个公共交通地图应用来可视化一个半径区域内的所有交通。为此,我们将在下一部分部署最后一个服务——交通地图公共版。
部署一个智能交通系统的全球可视化器
交通地图公共版服务是交通地图的静态版本,仅显示半径 5 公里内的检测物体。该服务部署在云端,因此你可以期望它与交通地图服务提供相同的可视化效果,唯一的不同是它不会显示你的实时 GPS 位置,因为它是静态的。需要考虑的 GPS 位置可以是你希望监控的城市中心的 GPS 坐标。一般来说,这种 Web 可视化适合作为市政的静态报告。代码与交通地图Web 应用相同,但省略了 GPS 位置的连续更新。要部署此服务,请运行以下命令:
-
通过运行以下命令创建一个
traffic-map部署:$ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: traffic-map-public name: traffic-map-public spec: replicas: 1 selector: matchLabels: app: traffic-map-public strategy: {} template: metadata: creationTimestamp: null labels: app: traffic-map-public spec: containers: - image: sergioarmgpl/traffic_map_public name: traffic-map-public imagePullPolicy: Always env: - name: LATITUDE value: "<YOUR_LATITUDE_COORDINATE>" - name: LONGITUDE value: "<YOUR_LONGITUDE_COORDINATE>" - name: TRAFFIC_MANAGER value: "<TRAFFIC_MANAGER_IP>" resources: {} status: {} EOF
这个部署有以下环境变量:
-
LATITUDE:初始 GPS 纬度坐标,用于将地图居中。 -
LONGITUDE:初始 GPS 经度坐标,用于将地图居中。 -
GPS_QUEUE:gps-queue服务的 IP 地址端点。在此情况下,因为是本地运行,所以默认设置为localhost。 -
TRAFFIC_MANAGER:你的localhost的 IP 地址端点,用于防止 CORS 限制。
重要提示
要检查代码并创建你自己的traffic-map-public容器,可以查看以下链接:
github.com/sergioarmgpl/containers/tree/main/traffic-map-public/src
-
现在,让我们为这个部署创建一个作为负载均衡器的服务。这个 IP 地址将是访问 Traffic Map 公共 Web 应用的端点。代码示例如下:
$ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: traffic-map-public name: traffic-map-public-lb spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app: traffic-map-public type: LoadBalancer status: loadBalancer: {} EOF
重要提示
若要排查部署问题,可以使用$ kubectl logs pod/<POD> -f <CONTAINER_NAME>命令。这将展示一些有用的输出,帮助排查服务问题。
-
使用以下命令获取
traffic-map-public部署的负载均衡器 IP:$ TRAFFIC_MAP_PUBLIC="$(kubectl get svc traffic-map-public -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')"
您可以通过运行以下命令查看TRAFFIC_MAP_PUBLIC环境变量的值:
$ echo $TRAFFIC_MAP_PUBLIC
请注意,负载均衡器的 IP 地址配置后需要一些时间。您可以通过运行以下命令检查服务的状态:
$ kubectl get svc traffic-map-public-lb
等待EXTERNAL_IP环境变量配置完毕。
- 通过
http://<TRAFFIC_MAP_PUBLIC>:3000访问 Traffic Map 公共应用。
现在一切都在运行,试着向系统填充数据,并通过您的边缘设备驾驶汽车以捕捉物体。然后,您将在几秒钟内看到系统中的物体。请查看进一步阅读部分,那里有大量可以探索的资料,帮助您构建自己的系统。但现在,是时候总结我们学到的内容了。让我们进入总结部分。
总结
在本章中,我们学习了如何使用 AI 分析由摄像头拍摄的视频,检测潜在的障碍物。这一功能在边缘计算环境下,通过 K3s 和 Raspberry Pi 实现。通过这种方法,我们创建了一个解耦的系统,能够通过容器轻松升级。我们还学到了如何将这种系统应用于现实场景中,监控交通行为以提高驾驶安全性。通过这一实现,我们还了解了如何将这种系统分布在边缘和云端,处理并展示本地信息,提升驾驶体验。在最后一章中,我们将介绍一种简单的方法,通过一个名为边缘计算设计系统画布的图表,帮助您快速组织和设计自己的边缘计算系统。
问题
这里有几个问题,帮助验证您新学到的知识:
-
人工智能、机器学习和计算机视觉如何相互关联,以设计智能交通系统?
-
TensorFlow Lite 和 scikit-learn 是如何协作检测物体并进行预测的?
-
计算机视觉在边缘设备上是如何工作的?
-
如何在边缘和云端之间分布数据?
-
如何使用 Python 构建计算机视觉系统?
-
如何使用 K3s 设计实时检测物体的分布式系统?
进一步阅读
您可以参考以下资料,获取本章涉及主题的更多信息:
-
什么是人工智能(AI)?:
www.techtarget.com/searchenterpriseai/definition/AI-Artificial-Intelligence -
人工智能中的智能体:
www.geeksforgeeks.org/agents-artificial-intelligence和www.educba.com/agents-in-artificial-intelligence -
智能交通管理:优化城市基础设施支出:
www.digi.com/blog/post/smart-traffic-management-optimizing-spend -
带自定义图标的标记:
leafletjs.com/examples/custom-icons -
MLOps 使用 Argo 和 K3s:
github.com/sergioarmgpl/mlops-argo-k3s -
YOLO 和 Tiny-YOLO 在 Raspberry Pi 和 Movidius NCS 上的物体检测:
pyimagesearch.com/2020/01/27/yolo-and-tiny-yolo-object-detection-on-the-raspberry-pi-and-movidius-ncs -
TensorFlow Lite 示例应用:
www.tensorflow.org/lite/examples -
TensorFlow Hub:
tfhub.dev -
获取 TensorFlow Lite 的模型:
www.tensorflow.org/lite/models -
运输和物流领域的边缘分析:案例研究:
www.skillsire.com/read-blog/174_edge-analytics-in-transportation-and-logistics-space-a-case-study.html -
在 Raspberry Pi 上设置 TensorFlow 物体检测 API 的教程:
github.com/EdjeElectronics/TensorFlow-Object-Detection-on-the-Raspberry-Pi -
TensorFlow-Lite-物体检测在 Android 和 Raspberry Pi 上的应用:
github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi -
TensorFlow Lite Python 物体检测示例与 Raspberry Pi:
github.com/tensorflow/examples/tree/master/lite/examples/object_detection/raspberry_pi -
Python 项目 – 实时人体检测与计数:
data-flair.training/blogs/python-project-real-time-human-detection-counting -
Coral USB 加速器:
coral.ai/products/accelerator -
Edge TPU 简单相机示例:
github.com/google-coral/examples-camera -
使用 NGINX 作为反向代理:
www.linode.com/docs/guides/use-nginx-reverse-proxy -
Movidius 在 Mac OS 上:
github.com/acharroux/Movidius-On-MacOS -
NCS-Pi-Stream:
github.com/HanYangZhao/NCS-Pi-Stream -
Intel® 神经计算棒 2(Intel® NCS2):
www.intel.com/content/www/us/en/developer/tools/neural-compute-stick/overview.html -
深度学习下的深度监控 – 智能视频监控项目:
data-flair.training/blogs/deep-surveillance-with-deep-learning-intelligent-video-surveillance-project -
道路车道线检测 – Python 中的计算机视觉项目:
data-flair.training/blogs/road-lane-line-detection -
Raspberry Pi 和 Movidius NCS 人脸识别:
pyimagesearch.com/2020/01/06/raspberry-pi-and-movidius-ncs-face-recognition -
在 Raspberry Pi 上使用 OpenVINO、OpenCV 和 Movidius NCS:
pyimagesearch.com/2019/04/08/openvino-opencv-and-movidius-ncs-on-the-raspberry-pi -
使用神经计算棒和 OpenVINO 加速低功耗设备上的预测:
towardsdatascience.com/speed-up-predictions-on-low-power-devices-using-neural-compute-stick-and-openvino-98f3ae9dcf41 -
使用 Movidius NCS 进行深度学习(第四部分):在 Rock64 上安装 NCSDK:
www.youtube.com/watch?v=AXzIYk7-lr8 -
基于字形的视频可视化在智能城市中进行监控的 Google Map 上:
jivp-eurasipjournals.springeropen.com/articles/10.1186/s13640-017-0175-4 -
车辆内外观:基于计算机视觉的增强车辆安全性:
escholarship.org/content/qt2g6313r2/qt2g6313r2_noSplash_81ae2290f201a6b25e8eecc8a1142845.pdf?t=lnpgaj -
为 Raspberry Pi 安装触摸屏和触摸校准程序:
www.gechic.com/en/raspberry-pi-install-touch-monitor-and-touch-calibrator-driver -
旋转树莓派 4 触摸屏显示器:
www.interelectronix.com/rotating-raspberry-pi-4-touch-monitor.html
第十五章:设计您自己的边缘计算系统
有时候,项目的成功并不在于技术——而在于设计和执行的方式。边缘计算系统可以从一个小型初创想法开始,因此您可以使用它参考精益画布商业计划模板来做出系统构建的初步草稿。但如果我们有一些类似的模板,专门适应于边缘计算呢?这就是边缘计算系统设计画布可以帮助您的地方。这个图表的理念是为您提供一个工具,帮助您创建构建边缘计算系统所需的所有内容的初步草稿,您可以将本书的章节视为构建您自己的模块。在本章中,我们将探索您可以用来托管服务的云服务提供商,一些需要考虑的最佳实践,您可以用来构建边缘计算系统的软件,以及其他一些使用案例,如果本书没有覆盖到的,可以用来创建系统的情况。
在本章中,我们将涵盖以下主要主题:
-
使用边缘计算系统设计画布
-
使用云服务提供商的托管服务
-
用于您项目的现有硬件
-
探索适用于您的系统的补充软件
-
建议构建您的边缘计算系统
-
探索额外的边缘计算使用案例
技术要求
想了解更多详情,请访问这个 GitHub 资源:github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/tree/main/ch15
使用边缘计算系统设计画布
边缘计算系统设计画布基于精益画布商业计划模板,旨在提供一个工具,帮助人们通过填写一张纸来创建和组织他们的边缘计算系统,从而开始构建他们的系统。让我们来看看我们的画布模板所涵盖的不同领域:

图 15.1 – 边缘计算系统设计画布
我们的模板涵盖了在设计系统时可以考虑的 14 个领域。首先,您需要定义系统的目的和要实现的功能。然后,在填写模板时,您可以并行注释挑战、人员和成本。之后,您可以定义是否在系统中使用自动化。在这个类别中,我们可以讨论 CI/CD 流水线和版本控制。接下来,您需要定义如何管理数据,使用何种格式,并决定实施哪些安全实践。最后两个部分包括您将在哪些部分运行边缘计算、使用哪些设备和传感器。最后,您还需要定义系统哪些部分将在云端运行、边缘与云端之间如何通信、从边缘收集哪些指标,以及哪些指标会在边缘或云端持续存在。
现在,让我们探索一些问题,您可以用来填写这张纸。目的是在不超过 10 分钟的时间内完成,类似于精益画布的工作方式。
目的
正如您所知,您需要定义为什么要构建这个系统——也就是这个系统存在的主要原因。您可以和团队讨论以下问题:
-
系统的目的是什么?
-
它将执行什么操作?
-
它将解决什么问题?
功能
在这里,您可以列出系统的五个主要功能。您可以从系统属性的功能角度描述它们,例如可用性、可靠性等。您可以提出以下问题:
-
系统的主要功能是什么?
-
这些功能为客户带来了哪些功能?
-
这个系统的独特功能是什么?
挑战
在这里,您需要识别出系统构建中的关键路径和潜在阻碍因素。您可以提出以下问题:
-
在边缘或云端运行软件面临哪些挑战?
-
当边缘设备与云端进行通信时,系统会面临哪些挑战?
您可以通过其他问题来补充,评估构建系统过程中其他技术领域的复杂度。
人员
在这里,您需要评估系统中正在工作的人员,并为未来的招聘定义基本技能。您可以提出以下问题:
-
构建系统所需的技能有哪些?
-
建立该系统需要多少人?
-
项目将如何管理?是按季度、学期、两周冲刺还是其他方式?
成本
在这里,您需要计算购买硬件、运行第三方服务等的可能成本。您可以提出以下问题:
-
我的设备成本是多少?
-
我的传感器成本是多少?
-
我的云服务商成本是多少?
-
我的硬件供应商是谁?
-
我需要考虑哪些额外的成本?
自动化
在这里,你需要评估自动化流程和代码版本管理。这是你可以填写与 CI/CD 流水线、数据流水线、GitOps、测试等相关内容的地方。你可以提出以下问题:
-
哪些流程将被自动化?
-
代码版本管理将如何实施?
-
你需要 CI/CD 或 GitOps 吗?
-
软件测试将如何实施?
数据
在这里,你需要定义如何管理数据。这包括数据格式、数据库、数据摄取、存储等。你可以提出以下问题来定义如何在系统中管理数据:
-
系统是否使用 NoSQL 数据库?
-
系统是否使用 SQL 数据库?
-
系统将使用哪种类型的数据(JSON、CSV 等)?
-
我的数据库需要哪些特性?这包括高可用性、持久性、并发性、分区容错性等。你可以使用 CAP 定理来选择最适合你需求的数据库。
安全性
在这里,你可以评估数据和服务的安全性。本书没有特别涵盖这个话题,但你可以提出以下问题来评估系统中某些最基本的安全方面:
-
系统中将实施哪些安全策略?
-
系统中需要在哪些地方使用数据加密?
-
系统认证如何在系统中工作?
边缘
在本节中,你需要列出并决定哪些设备将在边缘运行。在这里,你可以找到 ARM 设备和边缘集群。你需要决定将在边缘设备上运行哪些技术。你可以提出以下问题来评估这一点:
-
边缘设备上将运行什么?
-
哪些软件将在你的设备上运行?
-
系统是否需要在边缘运行单节点或多节点集群?
-
软件是使用虚拟机、容器、二进制文件还是其他方式运行?
设备
本节内容与列出系统中可能使用的设备及其附加硬件相关。你可以提出以下问题,收集可能使用的设备初步列表:
-
你的设备将使用什么类型的处理器?ARM 还是 x86_64?
-
我的设备需要使用哪些附加硬件?
-
设备将如何供电?使用电池还是直流电?
-
设备将如何管理本地时间?
-
你的设备将有多少内存用于固件和数据存储?
传感器
本节的目标是列出可能的传感器以及如何从中获取数据。然后,你必须将这些信息转化为衡量环境的指标或变量。你可以使用以下问题来分析与传感器相关的内容:
-
你打算使用哪些传感器?
-
传感器将测量什么?
-
传感器需要电源吗?它们需要什么类型的电源?
-
传感器将如何校准?
云
本节旨在评估系统中哪些部分需要在云中运行,如果有必要,评估你将使用哪些托管服务,以及是否有第三方服务可以帮助减少和简化系统实现的时间。为了评估这一点,你可以提出以下问题:
-
哪个云服务提供商最适合你的系统需求?
-
系统需要哪些托管服务?
-
是否有任何第三方服务对系统的使用至关重要?
通信
本节是与边缘和云层通信的结果。这里将定义各层之间如何通信、将使用哪些协议、通信是否是实时的,以及你的设备是否会使用特定协议进行相互通信。为了填充这一部分内容,你可以提出以下问题:
-
边缘设备将如何将数据传输到云端?
-
将使用什么类型的通信来与边缘设备和云端通信?这可能是套接字、REST API、gRPC 等。
-
系统是否使用 Lora、Wi-Fi、蓝牙、Sigfox 或其他协议与边缘设备或云端设备通信?
-
通信是同步还是异步,以便存储数据?
指标
你在边缘的传感器生成的数据将被转化为在仪表盘中显示的指标。然而,这些指标需要被定义。本节的目标是定义要使用的指标并将其可视化。这些指标是通过边缘收集的数据创建的。为了定义这些指标,你可以提出以下问题:
-
系统将收集什么类型的指标?黄金指标、天气指标,还是其他类型的?
-
系统将生成并使用哪些指标?这些可以包括延迟、温度、速度等。
-
系统将如何可视化收集到的数据?
-
系统是否会使用仪表盘软件(如 Grafana 或类似工具)来可视化数据?
请使用本书的不同章节作为构建块来创建你的系统。你可以使用github.com/PacktPublishing/Edge-Computing-Systems-with-Kubernetes/blob/main/ch15/docs/EdgeComputingSystemDesignCanvas.pdf中准备好的模板来设计和打印你的边缘计算系统。现在,是时候查看前三大云服务提供商的相关托管服务了。
使用云服务提供商的托管服务
选择合适的云服务提供商非常重要。市场上有多个云服务提供商,但前三名是亚马逊网络服务(AWS)、谷歌云和 Azure。我们来看看你可以在这些云提供商上使用的不同托管服务:
-
AWS:你可以使用虚拟机(EC2 和 Graviton 2 ARM 实例)来测试将在边缘运行的软件。Fargate 是一个可以用来在容器中部署应用的服务,它提供了多个选项。例如,你可以自动扩展该服务,类似于 Kubernetes 的做法。Elastic Kubernetes Service(EKS)是 AWS 的 Kubernetes 管理服务。EKS 是一个非常强大的解决方案,但与其他服务相比,你需要为某些任务(如扩展解决方案)执行更多手动操作。谈到数据库,你可以使用 Aurora 作为 MySQL 或 Postgres 实例。你还可以使用基于 Redis 或 Elastic Cache 的其他管理服务。对于文件存储,你可以使用 S3 服务。最后,对于完整的无服务器解决方案,你可以部署 Lambda 函数,它会按需运行小段代码。AWS 还有一个 IoT 平台,用于连接在边缘运行的设备。AWS 提供了一些认证设备来与其平台配合使用。官方网站是
aws.amazon.com。 -
Google Cloud:这个云服务提供商包括与 AWS EC2 实例相同的虚拟机。该服务是计算引擎服务的一部分,称为 VM 实例。Google Cloud 还通过 Tau 实例类型提供 ARM 实例。它提供了 Cloud Run 服务,用于运行容器,以及 Google Kubernetes Engine(GKE),这是一个 Kubernetes 管理服务,比 EKS 更容易管理,且稳定性更强。对于数据库,你可以使用 Memory Store,这是一个自管理的 Redis 服务,还有 Cloud SQL,它类似于 AWS Aurora。然而,在数据库方面,Google 提供的预构建选项比 AWS 少,尽管其功能非常相似。它可以作为 MySQL、Postgres 和 SQL Server 运行。它有自己管理存储的方式,使用云存储,并通过使用桶来存储信息,类似于 S3。最后,它也提供了与 AWS Lambda 类似的无服务器能力,叫做 Cloud Functions。Google Cloud 的一个主要特点是它的服务定义与开源项目兼容。例如,Flask 与 Cloud Functions 兼容,Cloud Run 在某些方面与 Knative 兼容。使用 Google Cloud 的一个主要优势是它与开源项目的兼容性。Google Cloud 也有自己的 IoT 解决方案,类似于 AWS,但它还支持一些开源硬件和设备,如 Coral USB 加速器或 Coral 开发板。官方网站可以在
cloud.google.com找到。 -
Azure:这感觉像是 AWS 和 Google Cloud 的结合,提供类似的工具。它有虚拟机服务,还支持使用 Dpsv5 和 Epsv5 实例的 ARM 处理器。它还拥有Azure Kubernetes Service(AKS),这是 Azure 的托管 Kubernetes 服务。AKS 有一些被禁用的功能,配置起来有点复杂,即使使用正确的配置,它也感觉不如 AWS 和 Google Cloud 成熟。它的成本也略高,但这取决于系统所需集群节点的数量和规模。与 AWS 和 Google Kubernetes 托管服务相比,AKS 相对不那么成熟。Azure 还拥有 Azure 容器实例,用于运行容器,就像 AWS Fargate 和 Google Cloud Run 一样。对于数据库,它提供 Azure Cosmos DB,提供类似于 AWS Aurora 或 Google Cloud SQL 的数据库托管服务。该数据库支持 Cassandra、SQL Server、MongoDB 和 Gremlin,类似于 Neo4J。Cosmos DB 更像是 Aurora 和 Cloud SQL 的 NoSQL 版本。它默认还提供企业级 Redis 服务。谈到无服务器函数,它提供 Azure Functions,支持 Python 和 TypeScript 等语言,以及微软拥有的专有语言,如 C#和 PowerShell 脚本。在物联网(IoT)方面,Azure 提供了更多的设备连接选项,并且有大量经过认证的硬件设计可以与 Azure 一起运行。这个平台看起来经常在创新。官方官网可以在
azure.microsoft.com找到。
对于这种特定类型的书籍,您还可以考虑Civo云,它提供托管的 K3s 服务,您可以用来玩转 K3s。官方网站可以在www.civo.com找到。
重要提示
查看每个云服务提供商的官方网站,了解他们当前服务的更新信息。
所有这些信息只是对这些云服务提供商所提供内容的简要介绍,因此并未涵盖每个云服务提供商的所有功能。也许您在想,哪个是最好的云服务提供商。答案取决于您对某些解决方案的偏好,因为这可能与当前的服务合同、先前的软件采用等因素有关。在某些情况下,一些云服务提供商的服务比其他提供商更好,您的团队需要花时间进行评估。服务的成本可能会根据每个云服务提供商上服务的规模而变化。为了帮助您做出决定,您可以思考以下问题:
-
提供商的托管服务价格是否合理,能够替代系统计划使用的自托管服务?
-
托管服务的学习曲线是否值得,是否会影响项目的截止日期?
-
云服务提供商是否包含了大部分需要在系统中实现的服务,而无需使用其他云服务提供商?
-
云服务提供商是否提供了支持和完善的文档以使用他们的服务?
-
选择的云服务提供商是否允许你在无需对应用程序源代码做太多修改的情况下继续运行应用程序?
这些是你可以向项目团队提问的一些问题,它们可以作为评估云服务提供商的良好起点。现在,让我们来探索一些可以用于边缘计算系统的硬件。
你的项目现有硬件
有许多硬件可以用于边缘计算项目。让我们来看看可以用于你项目的一些硬件列表。以下列表包括微型计算机,如树莓派(Raspberry Pi)和微控制器,如 Arduino:
-
Coral Dev Board:这是由 Google 设计的板卡,使用 Coral Accelerator 来运行机器学习应用。它体积适中,能够提供处理能力以运行机器学习应用。欲了解更多信息,请访问
coral.ai/products/dev-board。 -
Rock Pi:这款设备类似于树莓派,但配备了 Mali GPU,可用于处理机器学习应用。它还提供了其他版本的板卡,可以用于边缘计算。欲了解更多信息,请访问
rockpi.org。 -
Pine64:这是一个社区平台,创建了配备 ARM 处理器的板卡。它还推出了类似树莓派的产品,可用于边缘计算。欲了解更多信息,请访问
www.pine64.org。 -
ESP32:这是一款常用的微控制器,你可以编程使其在边缘读取传感器信息。很多分销商提供了集成传感器的各种版本的 ESP32。欲了解更多信息,请访问
heltec.org/proudct_center/esp-arduino。 -
MicroPython:这款板卡设计用于运行 Python。它具有许多功能,可以快速原型化一个用于在边缘捕捉数据的设备。欲了解更多信息,请访问
micropython.org。 -
NVIDIA Jetson Nano:这款设备由 NVIDIA 设计,配备了强大的 GPU,具有大量计算能力,适合运行高强度任务,包括机器学习。欲了解更多信息,请访问
developer.nvidia.com/embedded/jetson-nano-developer-kit。
请注意,某些设备可以仅用于原型设计解决方案,但不建议在生产环境中运行它们。请参阅进一步阅读部分以了解其他设备。现在,让我们探索一些你可以在边缘使用的补充软件,帮助你构建系统。
探索构建系统的补充软件
如果本书中的一些示例不适合你的系统需求,仍然有其他软件可以使用。以下是一些示例:
-
Crossplane:这用于通过 Kubernetes 来部署基础设施。Crossplane 可以为你提供进行这项工作的抽象。欲了解更多信息,请访问
crossplane.io。 -
Thanos:这是一个 Prometheus 集群,你可以用它来扩展你的 Prometheus 服务。欲了解更多信息,请访问
thanos.io。 -
Argo:这是一个完整的生态系统,可以用于实现 GitOps、工作流和事件管理。它是一个功能强大的软件。Argo 还可以在 ARM 设备上运行。欲了解更多信息,请访问
argoproj.github.io。 -
Containerd:如果 K3s 对于你的解决方案来说过大,你可以考虑使用容器。Containerd 可以在不增加额外服务的情况下为你提供这一抽象。欲了解更多信息,请访问
containerd.io。 -
Rancher:这是一个 Kubernetes 发行版,你可以用它来管理边缘上的所有集群,从而在一个地方通过单一的仪表板应用程序来管理和监控所有集群。欲了解更多信息,请访问
rancher.com。 -
KubeSphere:这与 Rancher 类似,但采用了不同的方法,使其更加适合开发者;而 Rancher 则更适合运维人员。欲了解更多信息,请访问
kubesphere.io。 -
OpenEBS:这是 Longhorn 的替代方案,提供了非常好的存储支持和选项。欲了解更多信息,请访问
openebs.io。 -
KubeEdge:这是 K3s 的一个修改版,用于将你的节点分布在云端和边缘。它还支持 MQTT 协议。欲了解更多信息,请访问
kubeedge.io。 -
Akri:这是一个 Kubernetes 资源接口,可以轻松地将你在小型边缘设备上的设备(如摄像头或 USB 设备)作为 Kubernetes 集群中的资源暴露出来。欲了解更多信息,请访问
docs.akri.sh。
你还可以浏览 CNCF 的已毕业、孵化和沙箱项目,访问www.cncf.io,以及浏览landscape.cncf.io上的生态系统,探索更多可以加入你项目的选项。现在,让我们继续讨论一些创建系统时的有用建议。
构建边缘计算系统的建议
这是你在设计边缘计算系统时可以考虑的一些建议:
-
在设计系统时请务必花时间。你可以在纸上进行设计,这将在构建系统时为你节省大量时间。
-
衡量构建系统的进度。没有衡量,就没有压力。你可以使用 Scrum 和 Kanban 敏捷方法来管理项目进度。规划是非常重要的。
-
在决定使用哪种技术、云服务提供商或第三方之后,投入时间制作概念验证(POC)。这对建立系统时保持持续的流程至关重要。
-
投资于文档工作。这是唯一一种在某人离职时不会丢失知识的方法。
-
对项目的代码进行版本控制。这是一种健康的最佳实践,可以确保你在项目中不会丢失重要的代码。
-
使用加密。评估系统中可能存储敏感数据的位置,并进行加密。
-
一般情况下,使用秘密。在本书中,有许多例子为了简化内容而没有使用秘密。然而,在现实场景中,使用秘密是必须的。
-
要像黑客一样思考。有时,你必须考虑最坏的情况,思考人们如何窃取你的信息。
-
投资有经验的专业人士,但也不要忽视新人。在为项目招聘人员时,关注团队中某些成员可以为你提供的经验,但也要记住,最年轻的人才可能会有创新的想法。
至此,你已经获得了一些构建边缘计算系统的建议。接下来,让我们探索更多边缘计算的用例。
探索额外的边缘计算用例
为了完成本书,以下是一些可以使用边缘计算技术探索和实施的用例:
-
医疗健康:在这个系统中,数据可以在本地处理或分析。有时,这些信息可以通过人工智能进行处理。这个系统可以集成本地传感器并在边缘处理信息。
-
工业 4.0:这与边缘计算和物联网在制造过程中的应用相关,你可以使用 ARM 设备在边缘处理信息,以减少系统互联和数据处理时的延迟。
-
自动驾驶汽车:随着电动汽车市场的兴起,这个行业正在不断发展。这个用例利用摄像头、增强现实和计算机视觉,以实现汽车自主驾驶的目标。
-
游戏:这个用例侧重于将处理任务分配到云端和高端用户设备(如游戏主机)之间,以减少视频游戏的延迟。
-
安全:在安全用例中,可以使用摄像头来监控并检测人们的危险行为或防止盗窃。这类系统通常使用物体检测和人工智能来实现这个目的。
-
农业:这可以应用于智能农场或花园,用于监控植物并执行如浇水等操作。这个使用案例与物联网技术和长距离协议(如 LoRa)有一定联系。
-
智慧城市:这个使用案例有很多应用场景,例如智能交通,它包括对交通和安全的监控,从而改善城市中的交通流量。
-
物流:这个使用案例可以提高包裹的配送时间,优化配送路线和燃料消耗等。对于市场上的公司来说,这可能带来竞争优势。
还有许多其他使用案例可以探索。请查看深入阅读部分了解更多信息。现在,让我们总结一下我们在本章中学到的内容。
总结
在本章中,我们学习了设计和实施边缘计算系统的一些补充内容。首先,我们介绍了边缘计算系统设计画布,并提出了一些可以帮助快速开始设计系统的有用问题。接着,我们探索了可与边缘计算系统配合使用的顶级云服务提供商的托管服务及一些相关硬件。最后,我们看了一些可以作为推荐使用的补充软件和其他待探索的使用案例。通过所有这些信息,你可以有条理地组织并快速开始构建一个边缘计算系统。这些内容对整理你在边缘计算系统中的所有思路可能会有所帮助。感谢你阅读这本书——希望你喜欢它。
问题
以下是一些问题,用于验证你新获得的知识:
-
如何使用边缘计算系统设计画布来设计一个边缘计算系统?
-
你可以使用哪些云服务提供商来补充你的系统?
-
你可以使用哪些补充软件或硬件来构建你的系统?
-
还可以使用哪些其他的使用案例来实现边缘计算?
深入阅读
请参考以下资料,以获取本章中涉及的主题的更多信息:
-
云原生计算基金会:
www.cncf.io -
Azure 认证设备目录:
devicecatalog.azure.com -
Azure IoT 开发者工具包:
microsoft.github.io/azure-iot-developer-kit -
Adafruit:
www.adafruit.com -
M5stack 电子商店:
shop.m5stack.com/collections -
EMQX,物联网中最具可扩展性的 MQTT 代理:
www.emqx.io -
Seeed Studio IoT 商店:
www.seeedstudio.com -
12 个现实生活中的边缘计算使用案例:
www.scitechsociety.com/12-real-life-edge-computing-use-cases -
交通和物流领域的边缘分析:案例研究:
www.skillsire.com/read-blog/174_edge-analytics-in-transportation-and-logistics-space-a-case-study.html -
Kubernetes 如何塑造汽车的未来:
thechief.io/c/editorial/how-kubernetes-is-shaping-the-future-of-cars -
零售、仓储和物流的边缘使用案例:
stlpartners.com/articles/edge-computing/edge-use-cases-for-retail-warehousing-and-logistics -
推动创新的边缘计算使用案例:
www.section.io/blog/edge-compute-use-cases -
物联网与边缘计算:有什么区别:
developer.ibm.com/articles/iot-vs-edge-computing



浙公网安备 33010602011771号