K8S中Prometheus高可用集群实战:分片+Thanos+Raft告警

1. 为什么“高可用”在 Prometheus 场景里不是锦上添花,而是生死线

K8S 部署 Prometheus 高可用集群(最新无坑版)——这个标题里,“高可用”三个字绝不是营销话术,而是从血泪教训里抠出来的硬指标。我见过太多团队把 Prometheus 当成“能跑就行”的监控组件:单实例部署、不配持久化、不设副本、连 Service 的 topologyKey 都懒得填。结果呢?一次节点重启、一次磁盘 IO 尖峰、甚至一次误删 PVC,整个监控系统就黑屏两小时。更讽刺的是,等你手忙脚乱登录服务器查日志时,发现告警根本没发出来——因为告警服务 Alertmanager 也挂在同一个挂掉的 Pod 里。

Prometheus 的设计哲学是“拉取式 + 本地存储”,这决定了它天然存在单点风险。它的 TSDB(时间序列数据库)数据默认写在本地磁盘,WAL(Write-Ahead Log)文件只做崩溃恢复用,不提供跨实例数据同步能力。这意味着:如果你只部署一个 Prometheus 实例,它挂了,哪怕只停 30 秒,这 30 秒内的所有指标采集就永久丢失;如果它所在节点磁盘损坏,WAL 文件和 block 数据一并归零,恢复不了——Prometheus 不是数据库,它不保证数据持久性,只保证“尽力而为”。

所以,“高可用集群”在这里的真实含义是:通过多实例冗余 + 外部存储解耦 + 智能查询路由 + 独立告警通道,把监控系统的 MTBF(平均无故障时间)从“按天计”拉到“按年计”。这不是为了应付 K8S 面试题里的“如何实现高可用”,而是为了确保当生产环境的 MySQL 连接池打满、当 Istio Sidecar 内存泄漏、当某个 Deployment 因镜像拉取失败卡在 ContainerCreating 状态时,你的 SRE 团队能在黄金 5 分钟内收到精准告警,并看到完整的 CPU、内存、网络延迟、HTTP 5xx 错误率曲线,而不是对着一个灰掉的 Grafana 面板干瞪眼。

关键词“K8S”、“Prometheus”、“高可用集群”在此刻形成强绑定:K8S 提供了声明式编排、滚动更新、健康检查这些基础设施能力;Prometheus 提供了指标采集与查询引擎;而“高可用集群”则是把这两者拧成一股绳的具体工程实践。它不依赖任何外部商业产品,但要求你对 K8S 的 StatefulSet、Headless Service、PersistentVolumeClaim、TopologySpreadConstraints 有肌肉记忆般的理解。接下来的内容,就是我踩过至少 7 次坑、重装过 12 次集群后,总结出的、真正能落地的“无坑版”路径——没有 Operator 的抽象黑盒,不绕弯讲 Helm Chart 的参数魔方,只聚焦最可控、最透明、最易调试的原生 YAML 方式。

1.1 单实例 Prometheus 的“伪高可用”陷阱

很多教程会告诉你:“加个 ReplicaSet 就是高可用”。这是典型误区。我拿一个真实案例说明:某电商大促前夜,运维同学给 Prometheus 加了 3 个副本,Service 类型设为 ClusterIP,Selector 匹配所有副本。看起来很美,对吧?结果大促开始 2 小时,监控大盘突然抖动——部分 Pod 的 CPU 使用率曲线断崖式下跌,但实际业务流量暴涨。排查发现:3 个 Prometheus 实例都在疯狂抓取同一组目标(比如 kubelet、cAdvisor),但它们之间完全不知道彼此的存在。每个实例都独立执行 scrape,独立存储数据,独立计算告警规则。当其中一个实例因 GC 停顿导致抓取延迟 15 秒,它就会漏掉这 15 秒的数据;而另外两个实例可能刚好抓到了,但 Grafana 查询时默认轮询后端,你看到的是一条“拼凑”出来的、时序错乱的曲线。

更致命的是告警风暴。Alertmanager 默认以 StatefulSet 形式部署,但如果你没配置--cluster.peer参数,3 个 Alertmanager 实例就是 3 个孤岛。同一个HighPodRestartRate告警,会被 3 个实例各自触发,发 3 遍企业微信、3 遍电话。这不是高可用,这是高噪音。

提示:真正的高可用,核心是“去中心化协同”,不是“中心化复制”。Prometheus 官方明确建议:多个 Prometheus 实例应负责不同目标分片(shard),而非全量复制。这是理解后续所有架构设计的起点。

1.2 “最新无坑版”的底层逻辑:解耦、分片、仲裁

所谓“最新”,指的是适配 Kubernetes v1.26+ 和 Prometheus v2.47+ 的变更。v2.45 之后,Prometheus 引入了--web.enable-admin-api的默认禁用策略,v2.46 开始强制要求--storage.tsdb.retention.time必须显式设置,否则启动失败;K8S v1.26 则废弃了policy/v1beta1API 组,所有 PodDisruptionBudget 必须迁移到policy/v1。这些细节,任何一个遗漏,都会让你卡在CrashLoopBackOff里反复挣扎。

而“无坑”,源于对三个关键耦合点的主动解耦:

  • 存储耦合:TSDB 数据不再绑定 Pod 生命周期。我们弃用emptyDir,改用ReadWriteOnce的 PV(如 NFS 或 Ceph RBD),并通过volumeClaimTemplates让每个 StatefulSet Pod 拥有专属 PVC。这样即使 Pod 被调度到新节点,数据也能跟着卷走。

  • 查询耦合:不靠 Service 轮询,而用 Thanos Query 作为统一查询层。它能同时对接多个 Prometheus 实例(甚至跨集群),自动合并、去重、降采样,返回一条逻辑一致的时间线。这才是用户感知到的“高可用”。

  • 告警耦合:Alertmanager 必须启用集群模式。3 个实例通过--cluster.peer参数互相发现,使用 Raft 协议选举 Leader,所有告警请求先发给 Leader,由 Leader 广播给 Follower,确保同一告警只触发一次。

这三步做完,你的 Prometheus 集群才真正具备“故障自愈”能力:一个实例挂了,Thanos Query 自动剔除它;数据卷坏了,重建 Pod 后 PVC 重新挂载,WAL 恢复最近 2 小时数据;Alertmanager 一个节点宕机,Raft 30 秒内选出新 Leader,告警不中断。这才是工程师该追求的“高可用”。

2. 架构选型:为什么放弃 Operator 和 Helm,选择纯 YAML 手工编排

市面上主流的 Prometheus 部署方案有三类:Helm Chart(如 prometheus-community/kube-prometheus)、Operator(如 CoreOS Prometheus Operator)、纯 YAML 清单。我曾用 Helm 部署过 5 个集群,用 Operator 管理过 3 个生产环境,最终全部回归到手工 YAML。这不是守旧,而是被现实逼出来的最优解。

Helm 的问题在于“参数黑洞”。values.yaml里有 200+ 个可配置项,90% 你永远用不到,但剩下的 10% —— 比如prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage—— 一旦填错单位(写成10Gi而非10Gi),K8S 就报Invalid value: "10Gi": quantity must be a positive number followed by a unit,然后你得翻半天文档找单位规范。更麻烦的是升级:Helm upgrade 时,如果新 Chart 改了 CRD 结构,老数据可能无法迁移,只能删库重来。

Operator 的问题更隐蔽。它用 CustomResourceDefinition(CRD)封装了 Prometheus、Alertmanager、ServiceMonitor 等资源,表面看是“声明式”,实则引入了第二层抽象。当你发现 Prometheus 抓取超时,想查scrape_config时,得先kubectl get servicemonitor -o yaml,再kubectl get prometheus -o yaml,最后在 Operator 的日志里翻它生成的最终配置。三层跳转,效率极低。而且 Operator 本身是个 Go 程序,版本升级常伴随 Breaking Change,比如 v0.68 升级到 v0.70,spec.serviceMonitorSelector的匹配逻辑变了,所有监控全部失效。

纯 YAML 的优势,在于“所见即所得”和“最小权限”。我给你看一段真实的prometheus-statefulset.yaml片段:

apiVersion: apps/v1 kind: StatefulSet metadata: name: prometheus-main namespace: monitoring spec: serviceName: "prometheus-headless" replicas: 3 selector: matchLabels: app: prometheus component: server template: metadata: labels: app: prometheus component: server spec: containers: - name: prometheus image: quay.io/prometheus/prometheus:v2.47.2 args: - "--config.file=/etc/prometheus/config_out/prometheus.env.yaml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=15d" - "--web.enable-lifecycle" - "--web.enable-admin-api" - "--storage.tsdb.no-lockfile" # 关键!避免 NFS 锁冲突 volumeMounts: - name: config-volume mountPath: /etc/prometheus/config_out - name: storage-volume mountPath: /prometheus volumes: - name: config-volume configMap: name: prometheus-config volumeClaimTemplates: - metadata: name: storage-volume spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 50Gi

这段代码里,每一个字段你都能在prometheus --help输出里找到对应解释。--storage.tsdb.no-lockfile是为了解决 NFS 存储下文件锁不可用的问题;volumeClaimTemplates确保每个 Pod 有独立 PVC;replicas: 3直观表明副本数。没有魔法,没有隐藏逻辑,只有清晰的因果链。

注意:选择纯 YAML 并不意味着拒绝自动化。我们用 Argo CD 管理这些 YAML 清单,GitOps 流水线自动同步变更。YAML 是事实源(Source of Truth),Argo CD 是执行器(Executor)。这种组合,比 Operator 的“智能代理”更可靠,也比 Helm 的“模板引擎”更透明。

2.1 分片策略:按命名空间还是按指标类型?

Prometheus 高可用的核心是分片(Sharding),但怎么分?常见两种思路:

  • 按命名空间分片prometheus-ns-a只抓取defaultkube-system命名空间下的 Pod;prometheus-ns-b抓取prodstaging。优点是配置简单,RBAC 权限好控制;缺点是负载不均——kube-system里有 etcd、coredns、kube-proxy,指标量远超普通业务命名空间,容易让prometheus-ns-a成为瓶颈。

  • 按指标类型分片prometheus-metrics专抓container_cpu_usage_seconds_totalcontainer_memory_usage_bytes等基础指标;prometheus-logs(配合 Promtail)抓日志指标;prometheus-traces(配合 Jaeger)抓链路追踪。这需要修改scrape_configs,用relabel_configs过滤目标。例如:

- job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme] action: replace target_label: __scheme__ regex: (https?) - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+) # 关键:只保留带特定标签的 Pod - source_labels: [__meta_kubernetes_pod_label_team] action: keep regex: backend|frontend

我们最终采用混合分片:先按业务域粗分(prometheus-core抓集群组件,prometheus-apps抓业务应用),再在prometheus-apps内部用relabel_configsteam标签细分。这样既保证核心监控不被业务抖动影响,又能让各业务线自主管理自己的抓取范围。实测下来,3 个prometheus-apps实例 CPU 使用率稳定在 45%-65%,没有明显倾斜。

2.2 Thanos Query:不只是查询聚合,更是故障隔离阀

很多人把 Thanos Query 当成“多个 Prometheus 的查询网关”,这理解太浅。它的真正价值,在于将查询失败的影响范围,从“整个监控系统”缩小到“单个后端实例”

假设你有 3 个 Prometheus 实例:p1p2p3p1因磁盘满导致tsdb加载失败,p2正常,p3因网络抖动响应超时。如果 Grafana 直连p1,整个面板就白屏;如果 Grafana 连 Thanos Query,它会并发向p1p2p3发起查询,收到p2的成功响应后,立即返回数据,同时记录p1p3的错误日志。用户无感知,只是数据延迟了 200ms。

Thanos Query 的部署本身很简单,但它依赖两个关键组件:

  • Thanos Sidecar:必须和每个 Prometheus 实例部署在同一 Pod 里(sidecar容器),通过--prometheus.url=http://localhost:9090对接。Sidecar 的作用是:1)暴露/metrics给 Thanos Query;2)在 Prometheus 启动时,自动将其注册到 Thanos Query 的后端列表;3)当 Prometheus 重启时,自动通知 Query 更新状态。

  • Object Storage:Thanos Query 本身不存数据,它需要从对象存储(如 MinIO、AWS S3、阿里云 OSS)读取长期历史数据。因此,每个 Prometheus 实例的 Sidecar 必须配置--objstore.config-file=/etc/objstore/config.yaml,将block数据定期上传到对象存储。

我们的thanos-query-deployment.yaml关键片段如下:

apiVersion: apps/v1 kind: Deployment metadata: name: thanos-query namespace: monitoring spec: replicas: 2 selector: matchLabels: app: thanos-query template: metadata: labels: app: thanos-query spec: containers: - name: thanos-query image: quay.io/thanos/thanos:v0.34.1 args: - "query" - "--http-address=0.0.0.0:9090" - "--grpc-address=0.0.0.0:10901" - "--log.level=info" - "--query.replica-label=prometheus_replica" # 告诉 Thanos 哪个 label 表示副本 # 关键:指定所有 Prometheus Sidecar 的地址 - "--store=dnssrv+_grpc._tcp.thanos-store-gateway.monitoring.svc.cluster.local" # 启用对象存储查询 - "--store=dnssrv+_grpc._tcp.thanos-objstore.monitoring.svc.cluster.local"

这里dnssrv+是 DNS SRV 记录,Thanos 会自动解析thanos-store-gatewayService 下的所有 Endpoints。我们用 Headless Service 配合 StatefulSet,确保每个 Sidecar 有唯一 DNS 名(如prometheus-main-0.prometheus-headless.monitoring.svc.cluster.local),Thanos Query 就能精准定位。

3. 实操详解:从零构建 3 节点 Prometheus 高可用集群(含完整 YAML)

现在进入最硬核的部分:手把手带你写出可直接kubectl apply -f的全部 YAML。我们以 Ubuntu 24.04 + K8S v1.27 为基准环境,所有命令和配置均经实测验证。整个过程分为 5 个原子步骤,每一步都附带原理说明和避坑提示。

3.1 步骤一:创建专用命名空间与 RBAC 权限

不要把 Prometheus 塞进default命名空间。新建monitoring命名空间,并赋予最小必要权限:

kubectl create namespace monitoring

RBAC 是安全基石。Prometheus 需要读取 Pod、Service、Endpoints、Node、Namespace 等资源来发现监控目标。但绝不给cluster-admin。我们创建一个精确的ClusterRole

# monitoring-rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: [""] resources: - nodes - nodes/metrics - services - endpoints - pods - namespaces verbs: ["get", "list", "watch"] - apiGroups: ["extensions"] resources: - ingresses verbs: ["get", "list", "watch"] - apiGroups: ["networking.k8s.io"] resources: - ingresses verbs: ["get", "list", "watch"] - apiGroups: [""] resources: - configmaps verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: monitoring --- apiVersion: v1 kind: ServiceAccount metadata: name: prometheus namespace: monitoring

提示:nodes/metrics权限是给 kubelet 的 metrics-server 用的,如果你用的是 cAdvisor,这里可以去掉。configmaps权限是为了让 Prometheus 能读取prometheus-configConfigMap。执行kubectl apply -f monitoring-rbac.yaml后,prometheusServiceAccount 就拥有了“只读集群元数据”的能力,既满足需求,又杜绝越权。

3.2 步骤二:定义 Prometheus 配置(ConfigMap)

Prometheus 的灵魂是prometheus.yml。我们把它做成 ConfigMap,方便热更新。关键点在于scrape_configs的分片设计和relabel_configs的精准过滤:

# prometheus-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config namespace: monitoring data: prometheus.yml: | global: scrape_interval: 30s evaluation_interval: 30s external_labels: monitor: 'k8s-monitoring' rule_files: - "/etc/prometheus/rules/*.rules" alerting: alert_relabel_configs: - source_labels: [severity] regex: critical action: drop # 严重告警才发,避免噪音 - source_labels: [alertname] regex: Watchdog|KubeSchedulerDown action: drop # 屏蔽基础组件告警,由专门的 core-alertmanager 处理 alertmanagers: - static_configs: - targets: - "alertmanager-main.monitoring.svc.cluster.local:9093" scrape_configs: # 1. 抓取 Kubernetes 组件(etcd, kube-scheduler, kube-controller-manager) - job_name: 'kubernetes-components' scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt insecure_skip_verify: true bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token kubernetes_sd_configs: - role: endpoints relabel_configs: - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] action: keep regex: (kube-scheduler|kube-controller-manager);https - action: labelmap regex: __meta_kubernetes_service_label_(.+) - source_labels: [__meta_kubernetes_namespace] action: replace target_label: kubernetes_namespace - source_labels: [__meta_kubernetes_service_name] action: replace target_label: kubernetes_name # 2. 抓取 Node Exporter(每个节点的硬件指标) - job_name: 'node-exporter' kubernetes_sd_configs: - role: service relabel_configs: - source_labels: [__meta_kubernetes_service_name] action: keep regex: node-exporter - action: labelmap regex: __meta_kubernetes_service_label_(.+) - source_labels: [__meta_kubernetes_namespace] action: replace target_label: kubernetes_namespace - source_labels: [__meta_kubernetes_service_name] action: replace target_label: kubernetes_name # 3. 抓取业务 Pod(按 team 标签分片) - job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme] action: replace target_label: __scheme__ regex: (https?) - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+) # 关键分片:只抓取 team=backend 的 Pod - source_labels: [__meta_kubernetes_pod_label_team] action: keep regex: backend - action: labelmap regex: __meta_kubernetes_pod_label_(.+) - source_labels: [__meta_kubernetes_namespace] action: replace target_label: kubernetes_namespace - source_labels: [__meta_kubernetes_pod_name] action: replace target_label: kubernetes_pod_name

这个配置里,job_name: 'kubernetes-pods'relabel_configs最后一行regex: backend就是分片开关。部署prometheus-main-0时,你把它改成frontendprometheus-main-1改成backendprometheus-main-2改成infra。这样三个实例各司其职,互不干扰。

注意:insecure_skip_verify: true是因为 kube-scheduler 等组件的证书是自签的,K8S 内部通信允许跳过验证。生产环境若用 Let's Encrypt,这里要换成ca_file指向正确证书。

3.3 步骤三:部署 StatefulSet 与 Headless Service

StatefulSet 是有状态应用的基石。我们用它确保每个 Prometheus 实例有稳定的网络标识(DNS 名)和存储卷(PVC):

# prometheus-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: prometheus-main namespace: monitoring spec: serviceName: "prometheus-headless" replicas: 3 selector: matchLabels: app: prometheus component: server template: metadata: labels: app: prometheus component: server spec: serviceAccountName: prometheus containers: - name: prometheus image: quay.io/prometheus/prometheus:v2.47.2 args: - "--config.file=/etc/prometheus/config_out/prometheus.env.yaml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=15d" - "--web.enable-lifecycle" - "--web.enable-admin-api" - "--storage.tsdb.no-lockfile" - "--web.route-prefix=/" - "--web.external-url=http://prometheus.monitoring.svc.cluster.local" ports: - containerPort: 9090 name: http livenessProbe: httpGet: path: /-/healthy port: 9090 initialDelaySeconds: 30 timeoutSeconds: 10 readinessProbe: httpGet: path: /-/ready port: 9090 initialDelaySeconds: 30 timeoutSeconds: 10 resources: limits: cpu: 2000m memory: 4Gi requests: cpu: 1000m memory: 2Gi volumeMounts: - name: config-volume mountPath: /etc/prometheus/config_out - name: storage-volume mountPath: /prometheus - name: thanos-sidecar image: quay.io/thanos/thanos:v0.34.1 args: - "sidecar" - "--prometheus.url=http://localhost:9090" - "--grpc-address=0.0.0.0:10901" - "--http-address=0.0.0.0:10902" - "--objstore.config-file=/etc/objstore/config.yaml" ports: - containerPort: 10901 name: grpc - containerPort: 10902 name: http volumeMounts: - name: config-volume mountPath: /etc/objstore - name: storage-volume mountPath: /prometheus volumes: - name: config-volume configMap: name: prometheus-config volumeClaimTemplates: - metadata: name: storage-volume spec: accessModes: ["ReadWriteOnce"] storageClassName: "nfs-client" # 替换为你环境的 StorageClass resources: requests: storage: 50Gi --- apiVersion: v1 kind: Service metadata: name: prometheus-headless namespace: monitoring labels: app: prometheus component: server spec: clusterIP: None # Headless Service 关键! selector: app: prometheus component: server ports: - name: http port: 9090 targetPort: 9090 - name: grpc port: 10901 targetPort: 10901

执行kubectl apply -f prometheus-statefulset.yaml后,你会看到:

$ kubectl -n monitoring get pods NAME READY STATUS RESTARTS AGE prometheus-main-0 2/2 Running 0 2m prometheus-main-1 2/2 Running 0 1m50s prometheus-main-2 2/2 Running 0 1m40s

每个 Pod 有两个容器:prometheusthanos-sidecarprometheus-headlessService 的clusterIP: None是关键,它让每个 Pod 有独立 DNS 名:prometheus-main-0.prometheus-headless.monitoring.svc.cluster.local。Thanos Query 就靠这个发现后端。

提示:storageClassName: "nfs-client"需要你提前部署好 NFS Client Provisioner。Ubuntu 24.04 上,推荐用nfs-subdir-external-provisioner,它比旧版nfs-client-provisioner更稳定。如果用云厂商的托管存储(如 AWS EBS、Azure Disk),这里填对应的 StorageClass 名。

3.4 步骤四:部署 Thanos Query 与 Store Gateway

Thanos Query 是查询入口,Store Gateway 是对象存储的代理。两者必须一起部署:

# thanos-query.yaml apiVersion: v1 kind: Service metadata: name: thanos-query namespace: monitoring spec: selector: app: thanos-query ports: - name: http port: 9090 targetPort: 9090 - name: grpc port: 10901 targetPort: 10901 --- apiVersion: apps/v1 kind: Deployment metadata: name: thanos-query namespace: monitoring spec: replicas: 2 selector: matchLabels: app: thanos-query template: metadata: labels: app: thanos-query spec: containers: - name: thanos-query image: quay.io/thanos/thanos:v0.34.1 args: - "query" - "--http-address=0.0.0.0:9090" - "--grpc-address=0.0.0.0:10901" - "--log.level=info" - "--query.replica-label=prometheus_replica" - "--store=dnssrv+_grpc._tcp.thanos-store-gateway.monitoring.svc.cluster.local" - "--store=dnssrv+_grpc._tcp.thanos-objstore.monitoring.svc.cluster.local" ports: - containerPort: 9090 name: http - containerPort: 10901 name: grpc livenessProbe: httpGet: path: /-/healthy port: 9090 initialDelaySeconds: 30 timeoutSeconds: 10 readinessProbe: httpGet: path: /-/ready port: 9090 initialDelaySeconds: 30 timeoutSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: thanos-store-gateway namespace: monitoring spec: clusterIP: None selector: app: thanos-store-gateway ports: - name: grpc port: 10901 targetPort: 10901 --- apiVersion: apps/v1 kind: Deployment metadata: name: thanos-store-gateway namespace: monitoring spec: replicas: 1 selector: matchLabels: app: thanos-store-gateway template: metadata: labels: app: thanos-store-gateway spec: containers: - name: thanos-store-gateway image: quay.io/thanos/thanos:v0.34.1 args: - "store" - "--http-address=0.0.0.0:10902" - "--grpc-address=0.0.0.0:10901" - "--objstore.config-file=/etc/objstore/config.yaml" ports: - containerPort: 10901 name: grpc - containerPort: 10902 name: http volumeMounts: - name: objstore-config mountPath: /etc/objstore volumes: - name: objstore-config configMap: name: thanos-objstore-config

thanos-objstore-configConfigMap 需要你提供对象存储的访问密钥。以 MinIO 为例:

# thanos-objstore-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: thanos-objstore-config namespace: monitoring data: config.yaml: | type: S3 config: bucket: "thanos" endpoint: "minio.monitoring.svc.cluster.local:9000" insecure: true signature_version2: false region: "us-east-1" access_key: "minioadmin" secret_key: "minioadmin"

提示:insecure: true是因为 MinIO 在集群内用 HTTP 通信。生产环境务必用 HTTPS 并关闭insecurebucket: "thanos"是 MinIO 里预先创建的桶名。执行kubectl apply -f thanos-query.yaml后,thanos-query会自动发现thanos-store-gateway和所有prometheus-main-*的 Sidecar,构建起完整的查询链路。

3.5 步骤五:部署 Alertmanager 集群(3 节点 Raft)

Alertmanager 是告警的“大脑”,必须高可用。我们用 StatefulSet 部署 3 个实例,启用 Raft 集群模式:

# alertmanager.yaml apiVersion: v1 kind: ConfigMap metadata: name: alertmanager-config namespace: monitoring data: config.yml: | global: resolve_timeout: 5m smtp_smarthost: 'smtp.gmail.com:587' smtp_from: 'your-email@gmail.com' smtp_auth_username: 'your-email@gmail.com' smtp_auth_password: 'your-app-password' # Gmail 应用专用密码 route: group_by: ['alertname', 'cluster', 'service'] group_wait: 30s group_interval: 5m repeat_interval: 12h receiver: 'email' receivers: - name: 'email' email_configs: - to: 'admin@company.com' send_resolved: true inhibit_rules: - source_match: severity: 'critical' target_match: severity: 'warning' equal: ['alertname', 'cluster', 'service'] --- apiVersion: apps/v1 kind: StatefulSet metadata: name: alertmanager-main namespace: monitoring spec: serviceName: "alertmanager-headless" replicas: 3 selector: matchLabels: app: alertmanager component: server template: metadata: labels: app: alertmanager component: server spec: containers: - name: alertmanager image: quay.io/prometheus/alertmanager:v0.26.0 args: - "--config.file=/etc/alert