Teg

从零构建企业级多云监控平台:架构设计与实战经验

2026/02/24 23:02 2 次阅读 王梓
★ 打赏
✸ ✸ ✸

从零构建企业级多云监控平台:架构设计与实战经验

三国演义开篇有云:"天下大势,分久必合,合久必分。"这句话用来形容企业监控领域,再贴切不过。

当基础设施从单体应用演进到微服务,从单一机房迁移到多云环境,监控工具也随之碎片化:CloudWatch 管 AWS 资源、Prometheus 管 Kubernetes、ELK 管日志、各业务系统各自为政。每个工具都很专业,但"分"久了,问题就来了 -- 出了故障,你得在五六个控制台之间来回切换;想看全局视图,根本没有;成本数据散落各处,月底才知道超支。

这篇文章记录我独立构建 CTMT(Centralized Monitor)监控平台的完整历程。从为什么要"合",到怎么"合",再到"合"了之后发现的问题和反思。不是为了证明自己的方案多好,而是真实地分享一个人从零到一做企业级监控的经验与教训。

一、监控孤岛:分久必合的痛点

在动手之前,先说说当时面临的现状。公司的云基础设施分布在多个 AWS 账号中:

  • 资源分散:生产环境、预生产环境、开发环境各自独立的 AWS 账号
  • 集群众多:每个环境有独立的 EKS 集群、RDS 实例、EC2 实例
  • 服务碎片化:SQS 队列、S3 存储桶、Lambda 函数散布各处
  • 流程割裂:GitLab CI/CD Pipeline 的状态需要单独查看

监控工具的现状是典型的"诸侯割据":

监控维度 使用工具 痛点
AWS 资源指标 CloudWatch Console 每个账号要单独登录,无法跨账号对比
Kubernetes 集群 kubectl + Prometheus 命令行操作,非运维人员无法使用
业务日志 CloudWatch Logs / EFK 查询复杂,缺乏统一入口
成本管理 AWS Cost Explorer 数据滞后,缺乏按产品维度的实时视图
CI/CD 状态 GitLab Web UI 需要逐个项目查看,无全局视图
证书管理 手动检查 容易遗忘,证书过期导致故障

这种"分治"状态带来的直接后果:

  • 故障排查时间长 -- 一个请求链路可能跨越三个控制台
  • 自助能力缺失 -- 非运维人员(开发、测试、管理层)无法自助查看资源状态
  • 成本失控 -- 月底才发现某个环境的 NAT Gateway 流量暴涨
  • 标签治理困难 -- 未打标签的资源越来越多,成本归属不清

所以,"合"不是为了技术炫技,而是实实在在的业务需求。

二、业界方案横向对比

在决定自研之前,我调研了业界主流的监控方案。坦白说,每个方案都有其优势,自研并不一定是最优选择。

2.1 商业方案

方案 优势 劣势 适用场景
Datadog 全栈可观测性,APM + 日志 + 指标一体化;700+ 集成;强大的告警和 AI 异常检测 价格昂贵(按主机/容器计费,大规模部署成本高);数据驻留在海外,合规风险;中国区支持有限 预算充足的中大型企业,追求开箱即用
New Relic 全栈可观测性;100GB/月免费额度;APM 能力强 超出免费额度后价格陡增;中国区网络延迟;学习曲线较陡 需要深度 APM 的团队
AWS CloudWatch 与 AWS 服务原生集成;无需额外部署;Container Insights 支持 EKS 跨账号体验差;自定义仪表盘能力弱;告警规则不够灵活;无法监控非 AWS 资源 纯 AWS 环境,监控需求简单

2.2 开源方案

方案 优势 劣势 适用场景
Grafana + Prometheus 开源免费;生态丰富;Grafana 可视化能力极强;支持多数据源 需要自行运维 Prometheus;大规模下存储和查询性能是挑战;告警配置复杂 有运维能力的团队,Kubernetes 原生监控
Zabbix 老牌开源监控;支持 Agent/SNMP/JMX 多种采集方式;模板丰富 架构偏传统,云原生支持弱;UI 老旧;大规模部署性能瓶颈 传统 IDC 环境,物理机/虚拟机监控
SigNoz / Uptrace 基于 OpenTelemetry;APM + 日志 + 指标;开源自托管 社区相对年轻;生产环境案例较少;文档不够完善 追求开源全栈可观测性的新项目

2.3 为什么选择自研

说实话,如果今天重新做这个决定,我可能会选择 Grafana + CloudWatch 数据源 + 自定义 Panel 的方案。但当时选择自研,有几个现实原因:

  • 网络限制:公司在中国区(AWS CN Region),Datadog/New Relic 等 SaaS 方案网络不稳定
  • 认证需求:需要深度集成 LDAP 企业认证,商业方案的 SSO 集成成本高
  • 定制需求:需要自定义的业务监控视图(如按产品线的成本分析、未打标签资源检测)
  • 生态限制:Grafana 当时对 AWS 中国区的支持不够完善
  • 个人成长:作为基础设施工程师,我想通过这个项目提升全栈开发能力

这些理由有合理的部分,也有主观的部分。后面的"反思"章节会详细讨论这个决策的得失。

三、整体架构设计

CTMT 采用前后端分离架构。后端基于 Django REST Framework 提供 API,前端基于 Vue.js 2 + Element UI + ECharts 实现可视化。整个系统由我一个人设计和开发。

CTMT 监控平台整体架构 前端 (Vue.js) Vue 2.5 + Element UI ECharts 数据可视化 Vue Router 路由管理 Axios HTTP 请求 后端 (Django) Django 3.2 + DRF LDAP 认证 AWS SDK (boto3) Kubernetes Python Client LDAP 认证服务 企业目录服务 单点登录 (SSO) 用户权限管理 REST API 认证请求 AWS 多账号环境 (通过 IAM Role 跨账号访问) 生产环境 EKS / RDS / EC2 SQS / S3 / Lambda 开发环境 EKS / RDS / EC2 SQS / S3 预生产环境 EKS / RDS API Gateway 预发布环境 EKS / RDS CloudFront IAM Role Assume 监控数据源 CloudWatch RDS / EC2 指标 SQS 队列深度 Lambda 调用量 NAT 流量 Kubernetes API Pod 状态/重启 容器日志 资源使用率 Service 状态 Cost Explorer 按产品线成本 按服务类型成本 成本趋势 预算告警 业务数据源 GitLab Pipeline ECR 镜像仓库 SSL 证书状态 ElastiCache 指标 数据采集

 

3.1 技术栈选择

 

层级 技术选型 选择理由
后端框架 Django 3.2 + DRF Python 生态与 AWS SDK(boto3)、K8s Client 天然契合,DRF 快速构建 REST API
前端框架 Vue.js 2.5 + Element UI 组件化开发效率高,Element UI 提供完整的管理后台组件
数据可视化 ECharts 图表类型丰富,支持大数据量渲染,中文文档完善
认证 ldap3 企业 LDAP 目录服务集成,实现统一认证
AWS 交互 boto3 + eks-token 官方 SDK,支持 Assume Role 跨账号访问
K8s 交互 kubernetes Python Client 官方客户端,支持动态 kubeconfig 加载
部署 Docker + EKS + GitLab CI 容器化部署,与现有 CI/CD 流程集成

 

3.2 部署架构

 

前后端分别容器化,部署在 EKS 集群中,通过 Ingress 暴露服务:

CTMT 部署架构 浏览器 Ingress 路由 + SSL 终止 Frontend Pod Nginx + Vue SPA Backend Pod Django REST API 外部服务 LDAP 认证服务 AWS API (多账号) Kubernetes API (多集群) CloudWatch Metrics/Logs Cost Explorer API GitLab API GitLab CI/CD Pipeline 代码提交 -> 构建 Docker 镜像 -> 推送 ECR -> 部署到 EKS

四、核心功能实现

 

4.1 LDAP 认证集成

 

企业级应用的第一道门槛是认证。接入公司的 LDAP 目录服务,实现统一登录,避免维护独立的用户体系。

 

# 认证模块核心逻辑
from ldap3 import Server, Connection, ALL
from django.conf import settings

def ldap_login(username, password):
    """LDAP 认证 - 验证用户凭证"""
    try:
        server = Server(
            settings.LDAP_SERVER,
            get_info=ALL,
            use_ssl=True,
            port=settings.LDAP_PORT
        )

        # 构造用户 DN (Distinguished Name)
        user_dn = f"{settings.LDAP_USER_ATTR}={username},{settings.LDAP_BASE_DN}"

        conn = Connection(
            server,
            user=user_dn,
            password=password,
            auto_bind=True
        )

        conn.unbind()
        return True
    except Exception as e:
        # 不记录具体错误信息,避免泄露 LDAP 结构
        return False

 

关键设计决策:

 

  • SSL 强制:生产环境必须使用 LDAPS(端口 636),确保密码传输安全
  • 信息隐藏:错误处理不暴露 LDAP 目录结构信息
  • Token 机制:认证成功后生成 JWT Token,后续请求携带 Token 验证

 

4.2 AWS 跨账号访问(IAM Assume Role)

 

监控多个 AWS 账号的资源,不能在每个账号创建 Access Key(安全隐患大)。正确做法是使用 IAM Role 的 Assume Role 机制:

跨账号访问流程 CTMT Backend (监控账号) ServiceAccount Role AWS STS AssumeRole 临时凭证(1h) 生产账号 Role 开发账号 Role 预生产账号 Role 1. 请求 2. 获取临时凭证 目标资源 CloudWatch RDS / EC2 EKS API SQS / S3 3. 访问 每个目标账号创建同名 IAM Role,信任策略只允许监控账号 Assume Role 权限遵循最小权限原则:只读 CloudWatch/RDS/EC2/EKS 等 临时凭证有效期 1 小时,自动轮换,无需管理长期密钥

# 跨账号访问核心实现
import boto3

def assume_role(account_id, role_name, session_name="ctmt"):
    """通过 STS AssumeRole 获取目标账号的临时凭证"""
    sts_client = boto3.client('sts')
    role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"

    assumed_role = sts_client.assume_role(
        RoleArn=role_arn,
        RoleSessionName=session_name
    )
    return assumed_role['Credentials']

def get_client(service_name, credentials, region='cn-north-1'):
    """使用临时凭证创建 AWS 服务客户端"""
    return boto3.client(
        service_name,
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken'],
        region_name=region
    )

def ret_client(aws_env, target):
    """根据环境名获取对应 AWS 客户端,映射关系通过配置文件管理"""
    role_name = 'monitoring-readonly'

    env_account_map = {
        'prod':    'PROD_ACCOUNT_ID',
        'dev':     'DEV_ACCOUNT_ID',
        'staging': 'STAGING_ACCOUNT_ID',
        'preprod': 'PREPROD_ACCOUNT_ID'
    }

    account_id = env_account_map.get(aws_env)
    if not account_id:
        raise ValueError(f"未知环境: {aws_env}")

    credentials = assume_role(account_id, role_name)
    return get_client(target, credentials)

 

安全要点:

 

  • 最小权限原则 -- 监控 Role 只有只读权限,无法修改任何资源
  • 临时凭证 -- 有效期 1 小时,自动过期,无需手动轮换
  • 信任策略 -- 限制只有监控账号的特定 Role 才能 Assume
  • ExternalId -- 防止"混淆代理人"攻击

 

4.3 RDS 监控实现

 

RDS 是最核心的监控对象之一。通过 CloudWatch Metrics API 批量获取多个实例的 CPU、磁盘、连接数指标。

 

# RDS 监控 - 批量指标查询
from datetime import datetime, timedelta

def generate_metric_data_queries(instances, metric_name, stat, unit, namespace):
    """构建 CloudWatch 批量查询请求,一次 API 调用获取多个实例的同一指标"""
    queries = []
    for inst in instances:
        queries.append({
            'Id': inst['name'].replace('-', '_'),
            'MetricStat': {
                'Metric': {
                    'Namespace': namespace,
                    'MetricName': metric_name,
                    'Dimensions': [{
                        'Name': 'DBInstanceIdentifier',
                        'Value': inst['name']
                    }]
                },
                'Period': 300,  # 5 分钟粒度
                'Stat': stat,
                'Unit': unit
            },
            'ReturnData': True,
        })
    return queries

def get_rds_metrics(rds_list, client):
    """
    获取 RDS 实例的核心监控指标:
    - FreeStorageSpace: 剩余磁盘空间
    - CPUUtilization: CPU 使用率
    - DatabaseConnections: 活跃连接数
    """
    result = []

    # 1. 磁盘空间
    queries = generate_metric_data_queries(
        rds_list, 'FreeStorageSpace', 'Average', 'Bytes', 'AWS/RDS'
    )
    response = client.get_metric_data(
        MetricDataQueries=queries,
        StartTime=datetime.utcnow() - timedelta(minutes=30),
        EndTime=datetime.utcnow(),
        ScanBy='TimestampAscending'
    )

    for r in response['MetricDataResults']:
        if r['Values']:
            free_gb = round(r['Values'][0] / 1024**3, 2)
            total = next(
                (i['rds_storage'] for i in rds_list if i['name'] == r['Label']), 0
            )
            result.append({
                'name': r['Label'],
                'free': free_gb,
                'used': round(total - free_gb, 2),
                'disk_percent': round((total - free_gb) / total * 100, 2) if total else 0
            })

    # 2. CPU 利用率、3. 连接数 (类似逻辑,合并到 result)
    result.sort(key=lambda x: x.get('cpu_percent', 0), reverse=True)
    return result

 

RDS 监控的关键指标与告警阈值:

 

指标 CloudWatch MetricName 告警阈值 影响
CPU 利用率 CPUUtilization > 80% 持续 5min 查询变慢,可能需要升级实例规格
磁盘空间 FreeStorageSpace 剩余 < 20% 写入失败,需要扩容或清理
连接数 DatabaseConnections > 最大连接数 80% 新连接被拒绝,检查连接池配置
读/写延迟 ReadLatency/WriteLatency > 20ms IO 瓶颈,考虑使用 Provisioned IOPS
副本延迟 ReplicaLag > 60s 读副本数据不一致,检查主库负载

 

4.4 Kubernetes 多集群监控

 

连接多个 EKS 集群是技术难点之一。每个集群需要动态获取访问 Token,构造 kubeconfig,然后通过 Kubernetes Python Client 操作。

 

# EKS 多集群连接
from kubernetes import client, config
from eks_token import get_token

def get_k8s_client(env):
    """
    动态连接到指定环境的 EKS 集群
    流程: Assume Role -> 获取集群信息 -> 获取 Token -> 构造 kubeconfig
    """
    # 1. 获取目标账号凭证
    credentials = assume_role(ENV_CONFIG[env]['account_id'], 'monitoring-readonly')

    # 2. 获取 EKS 集群 endpoint 和 CA
    eks_client = get_client('eks', credentials)
    cluster = eks_client.describe_cluster(
        name=ENV_CONFIG[env]['cluster_name']
    )['cluster']

    # 3. 获取访问 Token
    role_arn = f"arn:aws:iam::{ENV_CONFIG[env]['account_id']}:role/monitoring-readonly"
    token = get_token(ENV_CONFIG[env]['cluster_name'], role_arn)['status']['token']

    # 4. 动态构造 kubeconfig
    kubeconfig = {
        'apiVersion': 'v1',
        'clusters': [{
            'cluster': {
                'certificate-authority-data': cluster['certificateAuthority']['data'],
                'server': cluster['endpoint']
            },
            'name': 'target',
        }],
        'contexts': [{'context': {'cluster': 'target', 'user': 'monitor'}, 'name': 'ctx'}],
        'current-context': 'ctx',
        'kind': 'Config',
        'users': [{'name': 'monitor', 'user': {'token': token}}],
    }

    config.load_kube_config_from_dict(kubeconfig)
    return client.CoreV1Api()

def list_pods(env, namespace):
    """获取指定环境和命名空间的 Pod 列表"""
    v1 = get_k8s_client(env)
    pods = v1.list_namespaced_pod(namespace=namespace).items

    result = []
    for pod in pods:
        info = {
            'name': pod.metadata.name,
            'image': pod.spec.containers[0].image.split(':')[-1],
            'node': pod.spec.node_name
        }

        if pod.status.container_statuses:
            cs = pod.status.container_statuses[0]
            info['status'] = 'Running' if cs.ready else 'NotReady'
            info['restarts'] = cs.restart_count
            if cs.state.running:
                info['start_time'] = cs.state.running.started_at.strftime('%Y-%m-%d %H:%M')
        else:
            info['status'] = pod.status.reason or 'Pending'
            info['restarts'] = 0
        result.append(info)

    return result

 

4.5 成本监控与标签治理

 

云成本管理是这个平台的一大亮点。通过 Cost Explorer API 按产品线维度分析成本,同时检测未打标签的资源。

 

# 成本分析 - 按产品线维度
def get_cost_by_product(period, client):
    """获取按产品标签分组的成本数据,支持 DAILY 和 MONTHLY 两种粒度"""
    now = datetime.now()
    days = 365 if period == 'MONTHLY' else 180

    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': (now - timedelta(days=days)).strftime('%Y-%m-%d'),
            'End': now.strftime('%Y-%m-%d')
        },
        Granularity=period,
        Metrics=['BlendedCost'],
        GroupBy=[{
            'Type': 'TAG',
            'Key': 'CostCenter:Product'  # 按产品标签分组
        }]
    )

    # 转换为 ECharts 堆叠柱状图数据格式
    return {'costs': series_data, 'times': time_labels}

# 未打标签资源检测
def detect_untagged_resources(aws_env):
    """扫描指定环境中未打成本标签的资源,帮助推动标签治理"""
    client = ret_client(aws_env, 'resourcegroupstaggingapi')
    TARGET_TAG = 'CostCenter:Product'
    untagged = []

    EXCLUDED = ['cloudformation', 'ssm', 'iam', 'kms', 'backup', 'events', 'glue', 'vpc/']

    paginator = client.get_paginator('get_resources')
    for page in paginator.paginate():
        for mapping in page['ResourceTagMappingList']:
            arn = mapping['ResourceARN']
            tags = mapping.get('Tags', [])
            has_tag = any(t['Key'] == TARGET_TAG for t in tags)
            is_excluded = any(ex in arn for ex in EXCLUDED)
            if not has_tag and not is_excluded:
                untagged.append(arn)
    return untagged

 

成本监控的实际效果:

 

  • 异常发现:发现某环境 NAT Gateway 流量异常,月成本从 $200 涨到 $800,及时排查定位
  • 标签治理:未打标签资源从最初的 200+ 降到 20 以内,成本归属清晰度大幅提升
  • 成本透明:按产品线的成本趋势图让管理层能直观看到各业务线的云支出

 

五、前端实现

 

5.1 路由与页面结构

 

前端采用 Vue Router 管理路由,按监控维度组织页面层级:

 

// 路由结构(简化)
export default new Router({
  routes: [
    {
      path: '/aws',
      component: Main,
      children: [
        { path: 'rds', component: () => import('@/main/aws/Rds.vue') },
        { path: 'ec2', component: () => import('@/main/aws/Ec2.vue') },
        { path: 'sqs', component: () => import('@/main/aws/Sqs.vue') },
        { path: 'cost', component: () => import('@/main/aws/Cost.vue') },
        { path: 'tag', component: () => import('@/main/aws/Tag.vue') },
        { path: 's3', component: () => import('@/main/aws/s3.vue') },
        { path: 'elasticache', component: () => import('@/main/aws/Elasticache.vue') },
        { path: 'lambda', component: () => import('@/main/aws/Lambda.vue') },
        { path: 'nat', component: () => import('@/main/aws/Nat.vue') },
        { path: 'certificate', component: () => import('@/main/aws/Certificate.vue') },
        { path: 'ecr', component: () => import('@/main/aws/Ecr.vue') }
      ]
    },
    {
      path: '/kubernetes',
      component: Main,
      children: [
        { path: 'prod', component: () => import('@/main/kubernetes/Prod.vue') },
        { path: 'staging', component: () => import('@/main/kubernetes/Staging.vue') },
        { path: 'preprod', component: () => import('@/main/kubernetes/Preprod.vue') },
        { path: 'dev', component: () => import('@/main/kubernetes/Dev.vue') }
      ]
    },
    {
      path: '/gitlab',
      component: Main,
      children: [
        { path: 'pipeline', component: () => import('@/main/gitlab/Pipeline.vue') },
        { path: 'deployfrequency', component: () => import('@/main/gitlab/Deployfrequency.vue') },
        { path: 'failurerate', component: () => import('@/main/gitlab/Failurerate.vue') }
      ]
    },
    {
      path: '/business',
      component: Main,
      children: [
        { path: 'apigateway', component: () => import('@/main/business/Apigateway.vue') },
        { path: 'efk', component: () => import('@/main/business/Efk.vue') },
        { path: 'bill', component: () => import('@/main/business/Bill.vue') }
      ]
    }
  ]
})

 

5.2 数据可视化

 

使用 ECharts 实现多种图表,配合 Element UI 的 Table 和 Progress 组件展示监控数据:

 

// RDS 监控页面示例(简化)
export default {
  data() {
    return {
      awsEnv: 'prod',
      rdsList: [],
      detailVisible: false
    }
  },
  methods: {
    async fetchRdsData() {
      const { data } = await this.$http.post('/api/rds/list', {
        aws_env: this.awsEnv
      })
      this.rdsList = data
    },
    getProgressColor(pct) {
      if (pct < 50) return '#67c23a'   // 绿色 - 健康
      if (pct < 80) return '#e6a23c'   // 黄色 - 警告
      return '#f56c6c'                  // 红色 - 危险
    },
    async showDetail(rds) {
      this.detailVisible = true
      this.$nextTick(() => {
        // 初始化 ECharts 折线图
        // 展示 CPU/磁盘/连接数的时间序列数据
      })
    }
  },
  mounted() {
    this.fetchRdsData()
  }
}

 

前端的一个特色功能是音频告警 -- 当检测到 Pipeline 失败或 Pod CrashLoopBackOff 时,页面会播放告警音效,确保值班人员不会错过关键事件。

 

六、系统不足与诚实反思

 

写技术文章容易陷入"报喜不报忧"的陷阱。这一节我想诚实地谈谈这个系统的不足,以及如果重来一次,我会怎么做。

 

6.1 架构层面的不足

 

问题 现状 理想方案
无数据缓存层 每次请求都直接调用 AWS API,响应慢且容易触发限流 引入 Redis 缓存,对不常变化的数据设置 TTL
无异步任务队列 所有数据获取都是同步的,页面加载慢 使用 Celery + Redis 做异步任务,定时预拉取数据
无时序数据存储 不存储历史数据,无法做趋势分析 引入 InfluxDB 或 Prometheus 存储时序数据
单点部署 后端只有一个 Pod,无高可用 至少 2 副本 + HPA 自动扩缩容
无 APM 集成 监控平台自身缺乏可观测性 接入 OpenTelemetry,监控自身的请求延迟和错误率

 

6.2 代码层面的不足

 

  • 缺乏单元测试 -- 作为一个人的项目,测试覆盖率几乎为零。重构时心里没底,只能靠手动验证
  • 前端组件复用率低 -- 很多 Vue 组件之间有大量重复代码(比如各环境的 Kubernetes 页面),应该抽象为通用组件
  • 错误处理不统一 -- 后端的异常处理比较随意,没有统一的错误码和错误响应格式
  • 前端状态管理缺失 -- 没有使用 Vuex,组件间的数据传递依赖 props 和事件,复杂场景下维护困难
  • API 设计不够 RESTful -- 部分接口用 POST 传参数,实际上应该用 GET + Query Parameters

 

6.3 功能层面的不足

 

  • 告警能力弱 -- 只有前端音频告警,没有集成邮件、企业微信、PagerDuty 等通知渠道
  • 无 RBAC 权限控制 -- 所有登录用户看到的内容一样,无法按角色限制访问范围
  • 无审计日志 -- 谁在什么时间查看了什么数据,没有记录
  • 不支持自定义仪表盘 -- 页面布局是固定的,用户无法自定义关注的指标组合

 

6.4 如果重来一次

 

如果让我重新设计这个系统,架构会有很大不同:

理想架构(如果重来) Grafana 可视化 + 告警 Prometheus 指标存储 + 查询 自定义 API 业务逻辑 + 聚合 Redis 缓存 + 消息队列 CloudWatch Exporter AWS 指标 -> Prometheus 格式 Celery Workers 异步数据采集 + 定时任务 数据源: AWS API / Kubernetes API / GitLab API / LDAP 通过 Assume Role 跨账号访问(这部分设计保留) 核心变化: 用 Grafana 替代自研前端,用 Prometheus 替代实时 API 调用,保留自定义 API 处理业务逻辑

核心改进思路:

 

  • Grafana 替代自研前端 -- Grafana 的仪表盘能力远超手写的 Vue 页面,且支持用户自定义
  • Prometheus + CloudWatch Exporter -- 解决数据缓存和历史趋势问题
  • 保留自定义 API 层 -- 处理 Grafana 无法直接实现的业务逻辑(标签治理、成本分析、LDAP 集成)
  • Celery 异步任务 -- 定时预拉取数据,避免用户等待

 

但话说回来,自研的过程让我对监控体系有了深入理解,对 AWS 服务、Kubernetes API、前端可视化都有了实战经验。这些经验是用 Grafana 开箱即用所无法获得的。

 

七、与业界方案的差距

 

客观地说,CTMT 与 Datadog、Grafana Cloud 这样的成熟方案相比,差距是全方位的:

 

能力维度 CTMT Datadog Grafana Stack
数据采集 实时 API 调用,无缓存 Agent 采集,自动发现 Exporter + ServiceMonitor
可视化 固定页面布局 自定义仪表盘 + Notebook 高度可定制仪表盘
告警 仅前端音频 多渠道 + AI 异常检测 AlertManager 多渠道
APM 分布式追踪 + Profiling Tempo 分布式追踪
日志 基础查询 全文检索 + 关联分析 Loki 日志聚合
扩展性 需要改代码 700+ 开箱即用集成 丰富的插件生态
运维成本 低(自托管) 高(按量付费) 中(需要运维基础设施)
定制化 完全可控 受限于平台能力 高(开源可改)

 

CTMT 的优势在于:完全可控、深度集成企业 LDAP、针对性的成本分析和标签治理功能。但在通用监控能力上,差距确实很大。

 

八、生产环境最佳实践

 

8.1 安全加固

 

  • HTTPS 强制:所有 API 请求必须使用 HTTPS
  • CORS 配置:限制跨域访问来源
  • 输入验证:所有用户输入必须验证和清理
  • 日志脱敏:避免记录敏感信息(密码、Token)
  • 定期审计:定期审查 IAM 角色权限和访问日志

 

8.2 性能优化

 

  • 数据缓存:对不常变化的数据(如 RDS 实例列表)进行缓存
  • 批量查询:使用 CloudWatch 的 get_metric_data 批量查询多个指标
  • 分页加载:Pod 列表、日志等数据支持分页
  • 懒加载:前端路由和组件使用懒加载

 

8.3 告警策略

 

监控项 告警条件 通知方式
RDS CPU > 80% 持续 5 分钟 邮件 + 钉钉
RDS 磁盘 < 20% 剩余空间 邮件 + 钉钉
Pod CrashLoop 重启次数 > 5 邮件 + 音频告警
成本异常 日成本 > 预算 120% 邮件 + 钉钉

 

九、写在最后的一些感想

 

这个项目是我利用工作之余独立完成的,受限于个人精力和能力,有很多地方做得不够好。但也正因为是一个人摸索,踩了不少坑,也学到了不少东西。

 

一个人做项目,好处是迭代快、什么都能接触到,从后端 API 到前端页面到 CI/CD 部署,每个环节都得自己上手,逼着自己成长。坏处也很明显:没有人帮你 Code Review,代码质量全靠自觉;缺少不同视角的反馈,容易钻牛角尖;测试覆盖基本为零,重构的时候心里没底;而且一旦自己不维护了,别人接手会很痛苦。

 

十、结语

 

"天下大势,分久必合。"监控领域也是如此。从最初的监控孤岛,到统一平台,再到未来可能演进为基于 Grafana 的混合架构 -- 这个过程本身就是技术演进的缩影。

 

CTMT 不是一个完美的系统,但它解决了实际问题:让非运维人员能自助查看资源状态,让成本数据透明可追溯,让多环境的 Kubernetes 管理不再依赖命令行。作为一个人的作品,我对它有感情,也清楚它的局限。

 

如果你也在考虑构建类似的监控平台,我的建议是:先评估 Grafana + Prometheus 能否满足 80% 的需求,只对剩下 20% 的定制化需求做自研;从第一天就考虑数据缓存和异步处理;写测试,哪怕只是核心逻辑的测试;记录文档,未来的你会感谢现在的你。

 

监控的终极目标不是收集更多的数据,而是在正确的时间把正确的信息传递给正确的人。这个目标,无论用什么技术栈,都值得追求。

✸ ✸ ✸

📜 版权声明

本文作者:王梓 | 原文链接:https://www.bthlt.com/note/15142197-Teg从零构建企业级多云监控平台:架构设计与实战经验

出处:葫芦的运维日志 | 转载请注明出处并保留原文链接

📜 留言板

留言提交后需管理员审核通过才会显示