使用 Telegraf 发送数据到阿里云 IoT 平台
阿里云的IoT平台本质上是一个以MQTT服务为核心的物联网平台。国内的一些商业网关设备,提供了上报数据阿里云IoT平台的支持。而使用开源方案的话,Telegraf 提供了 MQTT 输出插件,配合其他的配置,也可以数据上云。
问题定义
工业设备或者 IoT 设备的数据已经被采集到本地服务器(边缘服务器),比如在 OPCUA 服务器、Kakfa 甚至本地的 MQTT 服务器中。为了数据监控、分析和应用,需要将数据传送到阿里云 IoT 平台中。待传到 IoT 平台之后,还可以利用它的数据流转服务,编写类 Javascript 代码,将数据流转到 Lindorm 时序数据库中。本文只关注上报数据到阿里云 IoT 平台。
技术调研
Telegraf
Telegraf 提供了发送数据到 MQTT 服务器的集成,具体见其代码库中的文档《MQTT Producer Output Plugin》。
阿里云 IoT 平台
在最核心 MQTT 服务器 之外,阿里云IoT平台还包括了管理元数据的物模型、鉴权认证等。一个产品对应一个物模型,而一个产品可以实例化为多个设备。物模型的介绍见阿里云的文档《配置物模型》,其中介绍了《什么是物模型》。在此不做赘述。
鉴权包括了TLS证书、MQTT用户名密码认证。其中,MQTT用户名是由设备名字和产品键组成的,即deviceName+"&"+productKey,而每个设备由自己的密码。TLS证书见《MQTT-TLS连接通信》 中的文档。本文作者验证了使用“Global Sign R1根证书”的方法。
Telegraf 与 阿里云 IoT 集成的挑战
在真实的场景下,当使用 Telegraf 的 MQTT 输出插件与阿里云 IoT 集成时,会遇到一个问题:假如有数十个设备,那么意味着就有数十个用户名密码对。但是每一个 Telegraf 的集成,只支持填写一对 MQTT 输出插件的用户名密码。这也就意味着,针对每一个设备,都需要启动一个 Telegraf 进程。这显然带来了管理和运维上的复杂度。目前我还没有找到简化的方法。
当发送数据到阿里云时,需要构建特定格式的 JSON 格式的消息体,而不是 Influxdb 的格式。好消息是,可以使用 json_transformation 配置发送的消息体。
为了安全起见,一些企业只允许本地网络和阿里云之间通过VPN网关联通,此时阿里云IoT位于 VPC 之中。截止当前,本地的客户端无法直接访问阿里云IoT的VPC地址,还需要在 VPC 中搭建一个三层代理进行数据转发。
在早期的 Telegraf 有一个 Bug,它会在构建 MQTT 主题的时候,去掉 / 前缀,而阿里云的 MQTT 主题正好以 / 开头。有很多文章不建议 MQTT 的主题以 / 开头,因为这会产生歧义,但阿里云似乎总是不按常理出牌。在最新的版本中这个问题已经修复。
【疯狂吐槽】阿里云为什么不按常理出牌?可能是独树一帜吧!也许这样就有自己的标准了。
配置举例
使用 Nginx 做三层代理
通常 Nginx 被用于做四层代理,其实也可以作为三层代理,配置示例如下:
stream { upstream aliyun_iot_platform_1883 { server iot-xxxx-vpc.mqtt.iothub.aliyuncs.com:1883; # 服务器地址位于“查看开发设置” }
server { listen 31883; # 本地端口,自行选择 proxy_pass aliyun_iot_platform_1883; }}Telegraf 配置
Telegraf MQTT 输出插件的配置示例如下,注意修改deviceName、productKey、client_id、password(client_id 和 password 的计算方式见文档《MQTT-TLS连接通信》):
[[outputs.mqtt]]servers = ["tls://x.x.x.x:xxxx"] # 指向 VPC 中的代理username = "deviceName&productKey"client_id = "client_id"password = "password"topic = "/sys/productKey/deviceName/thing/event/property/post"protocol = "3.1.1"qos = 1tls_ca = "/etc/telegraf/aliyun/root.crt" # 指向本地的 “Global Sign R1根证书”keep_alive = 3000connection_timeout = "5s"insecure_skip_verify = truetimeout = "2s"
data_format = "json"json_timestamp_units = "1ms"# 下面的 tags.Property 是一个自定义的标签,对应的是设备的属性json_transformation = ''' {"id": $join([tags.Property, "-", $string(timestamp)]), "version":"1.0", "sys": { "ack": 0 }, "params": { tags.Property: { "value": fields.Value, "time": timestamp }}, "method": "thing.event.property.post" }'''结尾
使用 Telegraf 发送数据到阿里云 IoT 平台是可行的,但是在集成的过程中,并不是很顺利。尤其在遇到很多设备的时候,如果每个设备都运行一个 Telegraf 进程,那管理起来就是噩梦。如果不需要在阿里云上构建物模型,而只是为了转存 IoT 数据到阿里云上的 Lindorm 时序数据库作为分析,那么可以直接将数据发送到 Lindorm 时序数据。