OpenTSDB 容器化之路

一. 何为 OpenTSDB?

官方表述是:基于 HBase 的分布式、可伸缩的时间序列数据库。

主要用途,做监控系统;譬如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储、查询。

OpenTSDB 是对外通信的无状态的服务器,在收到监控数据后,将数据写入到 HBase。

二.YMM-TSDB v1 架构

如下图,部署了多个 OpenTSDB 节点组成集群,上层使用阿里云 SLB 封装了一个写 IP:Port 和一个读 IP:Port。

所有业务都使用写 IP:Port 向 OpenTSDB 写入数据,而读 IP:Port 主要用在 Grafana 里为绘制图表取数,同时也在少量报表 JOB 中使用。

这种架构可以满足监控的基本需求,但是缺点也非常明显:

  • 阿里云 SLB 有坑

    当某个业务有大量数据的写或大范围的读时,通过 SLB 转到后端某个 OpenTSDB 进程上,可能会引发该 OpenTSDB 进程的 OOM,如果 Client 源单一,阿里云 SLB 会继续把请求发向出问题的 OpenTSDB 进程,于是出现读写失败问题,此现象在 Grafana 上尤其明显,因为 Grafana 只部署在一台 ECS 上,即 Client 源单一。

  • 读写不分离

    由于读和写是都通过阿里云 SLB 指向同一套 OpenTSDB 集群的,当这些 OpenTSDB 进程出现请求量异常时,无法明确是由写造成的还是读造成的,特别是聚合操作的读请求,不会有很大的网络 OUT 流量,但会引发 OpenTSDB 进程向 HBase 读取很多数据进行计算,使得网络 IN 流量突增,让我们误以为是读请求。

  • 业务不做分离

    多个业务使用相同的写 IP:Port 和读 IP:Port 访问 OpenTSDB,当某个 OpenTSDB 进程出故障时,无法定位到是哪个业务引发的,同时由于 OpenTSDB 进程失损,还会对其他业务的读写造成影响,激起连锁反应。

    以上三点原因,让我们很难维护好 OpenTSDB 集群,每当 OpenTSDB 进程出现问题时,都可能重启,而无法定位到问题源予以彻底解决。

三.YMM-TSDB v2 架构 – 容器

为了解决上述问题,首先想到的就是拆分 OpenTSDB 集群,并做业务分离。即让每个业务使用各自的 OpenTSDB 集群,可是还不够,还要做读写分离,这就变成每个业务要两套 OpenTSDB 集群。

于是我们面临以下问题:

  1. 没有那么多 ECS 部署 OpenTSDB 进程,一台 ECS 部署多个 OpenTSDB 进程,又有资源竞争问题,还要配置端口。
  2. 即使部署了这么多 OpenTSDB 进程,也没法有效管理,每个业务 2 读 2 写至少 4 个 OpenTSDB 进程,5 个就得是 20 个 OpenTSDB 进程。
  3. 阿里云 SLB 有坑,如何让业务程序访问 OpenTSDB 呢,将多个 IP:Port 提供给业务吗,业务代码是否支持呢,已上线的业务还得改代码。
  4. 高可用是空白,还没有任何保障机制,当一个 OpenTSDB 进程出问题时,另一个 OpenTSDB 进程顶上,不影响到业务。
  5. 负载水平扩展,能不能做到在多个 OpenTSDB 进程间平摊业务访问量,比如一个特别大的业务,我们可以用多个 OpenTSDB 进程吃下。
  6. 自愈功能,我们不想去手动重启 OpenTSDB 进程,能不能检测到它挂了,就自动重启,I’m back!

如何破?容器化!

如果我们将 OpenTSDB 进程容器化后 :

1. 不再需要那么多机器。一个容器就是一个 OpenTSDB 进程,端口也不用变,CPU 和 Mem 完全独立,能够通过 K8S Configmap 进行配置,我们默认配置为 1Core 4GB Mem。

2. 部署便捷快速。制定出 OpenTSDB 的 Docker 镜像后,通过 K8S Deployment 方式能迅速建起数套 OpenTSDB 容器集群,并且 K8S 命令就可以完成对每套容器集群的管理。

3. 每套容器集群包含多个容器。我们默认为 4 个,这些容器又分部在多个 K8S 节点上,我们使用 K8S Service 在多个容器上提供 HA,使用阿里云 SLB 在多个 K8S Node 间实现 HA。

4. 每套容器集群中的多个容器可以实现负载分摊。K8S Deployment 可以实现容器数量动态增加,负载水平扩展问题完美解决。

5.K8S 容器自带自愈功能。当检测到 OpenTSDB 进程无响应时,可自动重启容器,其他存活容器可继续提供服务,不影响业务。

于是我们有如下新的架构图:

  • 将写入拆分成四套容器:opsmonitor(运维监控脚本)、opsfalcon(falcon 基础指标监控)、onlineservice(除大数算法外的所有线上服务监控数据)、bdalg(大数据 & 算法专用)。

  • 将读取拆分成两套容器:grafana_read(grafana 读,不包含大数算法)、bdalg_read(大数算法读)。

四、必要的 K8S 概

1.Kubernetes 简介

Kubernetes 是 Google 基于 Borg 开源的容器编排调度引擎,作为 CNCF(Cloud Native Computing Foundation)最重要的组件之一,它的目标不仅仅是一个编排系统,而是提供一个规范,可以让你来描述集群的架构,定义服务的最终状态,Kubernetes 可以帮你将系统自动地达到和维持在这个状态。作为云原生应用的基石,Kubernetes 相当于一个云操作系统,其重要性不言而喻。

2. 使用资源类型说明:

  • Deployment

    Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的 ReplicationController 来方便的管理应用。

  • Configmap

    许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。这些配置信息需要与 docker image 解耦,你总不能每修改一个配置就重做一个 image 吧?ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象。

  • Service

    Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector (查看下面了解,为什么可能需要没有 selector 的 Service )实现的。

  • PodDisruptionBudge

    应用程序所有者可以为每个应用程序创建一个 PodDisruptionBudget 对象(PDB)。PDB 将限制在同一时间自愿中断的复制应用程序中宕机的 Pod 的数量。例如,基于定额的应用程序希望确保运行的副本数量永远不会低于仲裁所需的数量。Web 前端可能希望确保提供负载的副本的数量永远不会低于总数的某个百分比。

  • Taint 和 Toleration

    Taint(污点)和 Toleration(容忍)可以作用于 node 和 pod 上,其目的是优化 pod 在集群间的调度,这跟节点亲和性类似,只不过它们作用的方式相反,具有 taint 的 node 和 pod 是互斥关系,而具有节点亲和性关系的 node 和 pod 是相吸的。另外还有可以给 node 节点设置 label,通过给 pod 设置 nodeSelector 将 pod 调度到具有匹配标签的节点上。Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod 上,则表示这些 pod 可以(但不要求)被调度到具有相应 taint 的节点上。

  • nodeLabel

    Label 是附着到 object 上(例如 Pod)的键值对。可以在创建 object 的时候指定,也可以在 object 创建后随时指定。Labels 的值对系统本身并没有什么含义,只是对用户才有意义。Kubernetes 最终将对 labels 最终索引和反向索引用来优化查询和 watch,在 UI 和命令行中会对它们排序。不要在 label 中使用大型、非标识的结构化数据,记录这样的数据应该用 annotation。

3.YMM-TSDB v2 容器内部架构

五、简单介绍下实现方式

1. 第一阶段,通过 Taint(污点)、Toleration(容忍)、nodeSelector、nodeLabel、externalTrafficPolicy 等 k8s 特性,将中间件节点与线上其他业务进行隔离。

a. Taint 和 Toleration,创建驱逐策略,将业务应用驱逐出该节点,集群内新生成 pod 不会调度到该节点,配合 yaml 中 tolerations,实现该 opentsdb pod 不被该节点驱逐,命令举例如下:

复制代码

$ kubectl taint nodes prod.op.k8s.mdnode-00.hzdedicated::NoSchedule-
$ kubectl taint nodes prod.op.k8s.mdnode-00.hzdedicated=opentsdb:NoSchedule

b. 节点标签,创建节点标签,标示节点,用于筛选节点,配合 yaml 中 nodeSelector,实现分配 pod 到指定节点,命令举例如下:

复制代码

$ kubectllabelnodes prod.op.k8s.mdnode-00.hzopentsdb=true

c. Deployment yaml 配置,配合上面驱逐策略、节点标签,最终实现只有 yaml 中配置以下策略的应用能够在该节点启动,其他应用将不被调度到该节点,配置举例如下:

复制代码

tolerations:
-key:"dedicated"
operator:"Equal"
value:"opentsdb"
effect:"NoSchedule"nodeSelector:
opentsdb:"true"

2. 第二阶段,容器化 opentsdb,使用 Deployment 定义 yaml,通过 env 标签自定义 HeapSize 大小,利用探针对 opentsdb 使用 httpGet 进行健康检查。

a. 健康检查,用于检测 opentsdb 是否正常运行,配置举例如下:

复制代码

livenessProbe:
httpGet:
path:/
port:4242
initialDelaySeconds:600
periodSeconds:
failureThreshold:3

b. 环境变量,用于往 image 中传递参数,实现自定义 heap size,配置举例如下:

复制代码

env:
-name:HBASE_HOME
value:"/opt/hbase/bin/"
-name:JVMXMX
value:"-Xmx4096m"

c. 滚动升级,滚动升级中,将先启动新 pod 再销毁老 pod,配置举例如下:

复制代码

strategy:
rollingUpdate:
maxSurge:10%
maxUnavailable:0
type:RollingUpdate

3. 第三阶段,加入监控 sidecar,将原有 opentsdb 监控脚本进行容器化,跟随 opentsdb 主进程启动,通过 localhost 访问 opentsdb 进程,传入监控数据。

a. 容器化监控脚本,容器化原有 opentsdb 监控脚本,配置举例如下:

复制代码

#FROM python:2.7.15-alpine3.6
COPYrequirements.txt /requirements.txt
RUNpip install -r /requirements.txt
COPYsrc /app
WORKDIR/app
CMD["sh","/app/start.sh"]

b. Deployment yaml 添加 sidecar,实现监控 sidecar,监控进程跟随主进程启动,与主进程共享 namespace,使用 localhost 直接访问主进程,配置举例如下:

复制代码

-name:monitoring
image:harbor.ymmoa.com/monitoring/opentsdb-monitoring:v0.0.4
imagePullPolicy:Always
command:["sh","/app/start.sh"]

六、上线后遇到的问题

1.Service 类型配置不当导致 opentsdb crash

解决方法:起初 service 配置使用 NodePort 默认选项,进行压测 pod 状态一直正常,准备在 node 上面加一层 SLB 作为负载均衡,但是通过 SLB 压 opentsdb 就出现 pod 随机 CRASH 的现象。随后,调整 Service 参数 externalTrafficPolicy: Local,故障随后接触,通过 SLB 可以正常查询数据。

参数说明:service.spec.externalTrafficPolicy 的值为 Local ,请求就只会被代理到本地 endpoints 而不会被转发到其它节点。这样就保留了最初的源 IP 地址。如果没有本地 endpoints,发送到这个节点的数据包将会被丢弃。这样在应用到数据包的任何包处理规则下,你都能依赖这个正确的 source-ip 使数据包通过并到达 endpoint。

2.TSDB 向后端 HBase 发送 compaction 请求,导致 HBase 执行该操作时跑满网卡

解决方法:对 HBase 配置了 hbase.hstore.compaction.throughput.lower.bound 和 hbase.hstore.compaction.throughput.higher.bound 参数后,限制 HBase 执行 compacte 操作时可用的带宽,以避免跑满网卡而影响正常的读写操作

参数说明:hbase.hstore.compaction.throughput.lower.bound, 合并占用吞吐量下限;hbase.hstore.compaction.throughput.higher.bound,合并占用吞吐量上限。

七、全新监控图表

K8S 节点监控

TSDB 容器监控

TSDB 监控

作者: 满帮集团技术保障部冷振国、朱慧君、王杰