Linux: Prometheus

exporter

告警规则

prometheus 安装


多云采集

名称 平台 采集方式 是不需要打通内网
外部 promethes ali 接收端  
外部 grafana ali 展示  
EKS aws 集群内部署一套 Prometheus(agent 模式) → 否。出网可访问 ali prom
    remote_write 推到外部 Prometheus  
RDS 基础指标 aws 走公网调CloudWatch API 否
ElastiCache(Redis)基础指标 aws 走公网调CloudWatch API 否
Amazon MQ 的基础指标 aws 走公网调CloudWatch API 否
RDS/Redis/MQ 的引擎深度指标 aws EKS 里跑 mysqld/redis/rabbitmq-exporter 否
(慢查询、keyspace、队列深度等)      
内网端口连通性探测 aws blackbox-exporter 部署到 EKS 里 否

即:推(remote_write)+ CloudWatch API 拉,完全不用 VPN。

架构

┌─────────────────────── 阿里云 ECS 10.14.0.219 / 公网 IP       ────────────────────────┐
│  Prometheus v3.13.3  (HTTPS 9090, basic auth, remote-write receiver)                 │
│  Grafana 13.0.8 / Alertmanager / node-exporter / cadvisor / blackbox-exporter        │
└───────────────────────────────────▲──────────────────────────────────────────────────┘
                                    │  remote_write (HTTPS + Basic Auth, 出公网)
                                    │
┌───────────────────────────────────┴──── AWS VPC 172.31.0.0/16 ───────────────────────┐
│  EKS                                                                                 │
│   ├─ kube-prometheus-stack (本地 12h 保留,负责所有集群内抓取 + 统一外推)                  │
│   │    ├─ kubelet / cAdvisor / kube-state-metrics / node-exporter / apiserver        │
│   │    ├─ YACE  ──(公网 CloudWatch API)──▶ AWS/RDS, AWS/ElastiCache, AWS/AmazonMQ     │
│   │    ├─ mysqld_exporter / redis_exporter / rabbitmq_exporter (内网直连,深度指标)    │
│   │    └─ blackbox-exporter (内网端口探活)                                            │
└──────────────────────────────────────────────────────────────────────────────────────┘

外部 prometheus 相关

开启 remote-write 接收 + 鉴权 + TLS

  • 建议走 私有 CA → 签发服务端证书 两级结构,而不是直接 req -x509 一把梭:这样 AWS 侧只需要信任一个 ca.crt,以后换 IP / 加域名重签服务端证书,客户端不用动。
  • Go(Prometheus / Grafana 都是 Go)自 1.15 起完全忽略 CN,只认 SAN。所以 SAN 里必须补上 DNS:prometheus(compose 里 Grafana 是用 https://prometheus:9090 连的)和 IP:127.0.0.1,否则容器内互访会报 x509: certificate is valid for …, not prometheus。

生成证书

mkdir -p /opt/docker/monitor/prometheus/certs
cd /opt/docker/monitor/prometheus/certs

# 1 SAN 扩展文件

cat > fd.ext <<'EOF'
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = DNS:*.xxx.com, DNS:xxx.com, DNS:prometheus, DNS:localhost, IP:<内网IP>, IP:<你的IP>, IP:127.0.0.1
EOF


# 2 私有 CA(100 年)

openssl genrsa -out ca.key 4096

openssl req -x509 -new -nodes -sha256 -days 36500 \
  -key ca.key -out ca.crt \
  -subj "/C=CN/ST=hangzhou/L=hangzhou/O=ops:prometheus/CN=ops-internal-ca" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"

# CA 的 CN 不要和服务端证书的 CN 重名,否则部分工具链路校验会犯迷糊,这里用 ops-internal-ca。

# 3 服务端私钥 + CSR(用你给的 subj)

openssl genrsa -out tls.key 2048

openssl req -new -sha256 -key tls.key -out tls.csr \
  -subj "/C=CN/ST=hangzhou/L=hangzhou/O=ops:prometheus/CN=prometheus"

# 4 用 CA 签发

openssl x509 -req -sha256 -days 36500 \
  -in tls.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -extfile fd.ext \
  -out tls.crt

# 5 校验
# 看 SAN 有没有全进去
openssl x509 -in tls.crt -noout -text | grep -A2 'Subject Alternative Name'
openssl x509 -in tls.crt -noout -subject -issuer -dates
# 验签链
openssl verify -CAfile ca.crt tls.crt          # 应输出 tls.crt: OK
# 确认公私钥配对(两个 md5 必须一致)
openssl x509 -in tls.crt -noout -pubkey | openssl md5
openssl rsa  -in tls.key -pubout       | openssl md5

# 6 权限
chmod 600 ca.key tls.key
chmod 644 ca.crt tls.crt
rm -f tls.csr          # ca.key 请离线备份,别留在容器能读到的地方之外也别丢

#容器里 Prometheus 以 user: '0' 跑,root 能读 600 的 key,没问题。

grafana 模板

https://grafana.com/grafana/dashboards/

安装外部 promethues

prometheus compose

web.yml TLS + Basic Auth

mkdir monitor/prometheus/rulues -pv
cd monitor

# Prometheus 的 web.yml 只支持 bcrypt
# 生成密码-方法1
apt install apache2-utils -y
htpasswd -nBC 10 remote_writer   → 取冒号后面那串,注意 $ 在 yaml 单引号里是安全的
# 生成密码-方法2
docker run --rm httpd:alpine htpasswd -nBC 10 remote_writer
#生成密码-方法3
# 生成密码
cat p.py <<EOF
import getpass
import bcrypt

password = getpass.getpass("password: ")
hashed_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())

print(hashed_password.decode())
EOF
python3 p.py # 输入密码

# 生成配置
cat > prometheus/web.yml <<\EOF
# https://prometheus.io/docs/prometheus/latest/configuration/https/
tls_server_config:
  cert_file: /etc/prometheus/certs/tls.crt
  key_file:  /etc/prometheus/certs/tls.key
  min_version: TLS12

basic_auth_users:
  admin: $2b$12$Bq/xxx
  remote_write: $2y$10$PX4xxxxx
EOF
# prometheus/alertmanager.yml
cat <<\EOF> prometheus/alertmanager.yml 
route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 20m
  receiver: 'default-receiver'

receivers:
- name: default-receiver
EOF

# 黑盒监控
cat > prometheus/blackbox.yml <<EOF
modules:
  http_2xx:
    prober: http
    timeout: 5s
    http:
      preferred_ip_protocol: "ip4"
      valid_status_codes: [200, 401, 301, 302]
      method: GET
  http_post_2xx:
    http:
      method: POST
      preferred_ip_protocol: ip4
    prober: http
  tcp_connect:
    prober: tcp
    timeout: 5s
  icmp:
    prober: icmp
    timeout: 5s
EOF
# prometheus/prometheus.yml
cat > prometheus/prometheus.yml  <<\EOF
global:
  scrape_interval: 30s
  scrape_timeout: 10s
  evaluation_interval: 30s
  external_labels:
    cluster: aliyun

storage:
  tsdb:
    out_of_order_time_window: 30m   # 跨云网络抖动/重传时,乱序样本不被丢弃

alerting:
  alertmanagers:
  - follow_redirects: true
    scheme: http
    timeout: 10s
    api_version: v2
    static_configs:
    - targets:
      - alertmanager:9093

rule_files:
- /etc/prometheus-rules/*.yml

#https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/

#remote_write:
#  - url: '<Your Prometheus remote_write endpoint>'
#    basic_auth:
#      username: '<Your Grafana Username>'
#      password: '<Your Grafana API key>'


scrape_configs:
- job_name: 'prometheus'
  scrape_interval: 1m
  scheme: https
  static_configs:
  - targets: ['localhost:9090']
  tls_config:
    ca_file: /etc/prometheus/certs/ca.crt
    server_name: localhost
  basic_auth:
    username: admin
    password: xxx

- job_name: 'cadvisor'
  scrape_interval: 5s
  static_configs:
  - targets: ['cadvisor:8080']

- job_name: 'node'
  static_configs:
  - targets: ['node-exporter:9100']

- job_name: 'blackbox-exporter'
  static_configs:
  - targets: ['blackbox-exporter:9115']
- job_name: domain-url
  scrape_interval: 30s
  scrape_timeout: 20s
  metrics_path: /probe
  params:
    modelue: [http_2xx]
  static_configs:
  # 内网不互通
  #- targets:
  #    - http://argocd.xxx.com/login
  #  labels:
  #    bussiness: ops
  #    serverty: info
  - targets:
      - https://devgw.xxx.com/api/
    labels:
      bussiness: pt-dev
      service: web
      serverty: info
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: blackbox-exporter:9115
  - source_labels: [instance]
    regex: https?://(.*)
    replacement: "$1"
    target_label: target

- job_name: 'mysql'
  static_configs:
  - targets: ['mysqld_exporter:9104']

- job_name: 'redis'
  static_configs:
  - targets: ['redis_exporter:9121']
EOF

docker-compose.yml

cat > docker-compose.yml <<EOF
networks:
  monitor_net:
    name: monitor_net
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.222.0/24

services:
  prometheus:
    image: prom/prometheus:v3.13.3
    container_name: prometheus
    hostname: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./prometheus/web.yml:/etc/prometheus/web.yml
      - ./prometheus/certs:/etc/prometheus/certs:ro
      - ./prometheus/rules:/etc/prometheus-rules
      - ./prometheus/data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--web.config.file=/etc/prometheus/web.yml'
      - '--web.enable-remote-write-receiver'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--web.enable-lifecycle'
      - '--storage.tsdb.retention.time=60d'
    user: '0'
    ports:
      - 9090:9090
    networks:
      - monitor_net

  alertmanager:
    hostname: alertmanager
    container_name: alertmanager
    image: prom/alertmanager:v0.34.0
    volumes:
      - ./prometheus/alertmanager.yml:/config/alertmanager.yml
    command:
      - "--config.file=/config/alertmanager.yml"
      - '--data.retention=240h'
    ports:
      - 9093:9093
    networks:
      - monitor_net
    restart: always

  grafana:
    container_name: grafana
    image: grafana/grafana:13.0.8
    depends_on:
      - prometheus
    user: '0'
    ports:
      - 3000:3000
    volumes:
      - ./grafana-data:/var/lib/grafana
    networks:
      - monitor_net
    restart: always


  cadvisor:
    image: ghcr.io/google/cadvisor:v0.57.0
    hostname: cadvisor
    container_name: cadvisor
    #platform: linux/aarch64
    privileged: true
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - --docker_only=true
      - --housekeeping_interval=10s
      - --store_container_labels=false
      - --disable_metrics=percpu,sched,tcp,udp,hugetlb,referenced_memory,cpu_topology,resctrl
    ports:
      - 8080:8080
    networks:
      - monitor_net
    restart: unless-stopped


  node-exporter:
    image: prom/node-exporter:latest
    hostname: node-exporter
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    ports:
      - 9100:9100
    networks:
      - monitor_net

  blackbox-exporter:
      image: quay.io/prometheus/blackbox-exporter:latest
      hostname: blackbox-exporter
      container_name: blackbox-exporter
      restart: unless-stopped
      volumes:
      - ./prometheus/blackbox.yml:/etc/blackbox_exporter/config.yml
      command:
      - --config.file=/etc/blackbox_exporter/config.yml
      ports:
      - "9115:9115"
      networks:
      - monitor_net
      # 开启 icmp ping探测
      cap_add:
        - NET_RAW

#  mysqld_exporter:
#    image: prom/mysqld-exporter:v0.15.1
#    hostname: mysqld-exporter
#    container_name: mysqld_exporter
#    ports:
#      - 9104
#    command:
#      - "--mysqld.username=root:kot2LT4jgJEo2rsnvduF"
#      - "--mysqld.address=mysql-8-slots:3306"
#      - '--tls.insecure-skip-verify'
#    restart: always
#    networks:
#      - monitor_net
#
#  redis_exporter:
#    image: oliver006/redis_exporter:v1.62.0
#    container_name: redis_exporter
#    ports:
#      - 9121
#    command:
#      - "--redis.addr=redis://redis-stack-slots:6379"
#      - "--redis.password=OzTKYlJ9KvGZAkYf9B"
#      - '--debug'
#    restart: always
#    networks:
#      - monitor_net

#  debian:
#    image: debian:unstable-slim
#    container_name: debian
#    restart: always
#    tty: true
#    #entrypoint:
#    #  - cat
#    networks:
#      - monitor_net
EOF

启动

docker compose up -d
docker compose logs --tail=50 prometheus


# 从 ECS 本机验证(带 CA 校验,不要用 -k)
curl --cacert /opt/docker/monitor/prometheus/certs/ca.crt \
     -u remote_writer:'你的密码' \
     https://10.14.0.219:9090/-/healthy

# 公网 IP 也应通过(SAN 里有)
curl --cacert ... -u ... https://<你的IP>:9090/-/healthy

如果日志里出现 tls: failed to find any PEM data in key input,说明 key 被加密了(生成时漏了 -nodes/用了 -aes256)。

各客户端怎么信任它

Prometheus 自监控 job

- job_name: 'prometheus'
  scheme: https
  tls_config:
    ca_file: /etc/prometheus/certs/ca.crt
    server_name: prometheus
  basic_auth:
    username: remote_writer
    password: '你的密码'
  static_configs:
    - targets: ['prometheus:9090']

Grafana 数据源

URL 改成 https://prometheus:9090,勾上 With CA Cert,把 ca.crt 内容粘进去,Server Name 填 prometheus。(cat prometheus/certs/ca.crt 复制即可。不想粘就勾 Skip TLS Verify)

prometheus 重新加载配置脚本

# 建一个 600 权限的密码文件
umask 077
printf 'admin:xxxpassword' > /opt/docker/monitor/.reload-cred
chmod 600 /opt/docker/monitor/.reload-cred

# 脚本内容
cat >reload-prom <<\EOF
#!/usr/bin/bash
# prometheus 重加载配置
ppath=/opt/docker/monitor
/usr/bin/curl -fsS --connect-timeout 5 -m 30 \
  -XPOST --cacert ${ppath}/prometheus/certs/tls.crt \
  -u "$(cat ${ppath}/.reload-cred)" \
  https://127.0.0.1:9090/-/reload \
  && echo "reload OK" || echo "reload FAILED"
EOF

chmod +x reload-prom
ln -sv /opt/docker/monitor/reload-prom /usr/local/bin
#这样普通用户 cat 脚本看不到密码

promehtues agent 采集 EKS 信息

EKS 指标:集群内 Prometheus agent → remote_write

部署(kube-prometheus-stack,最省事)

# 把 CA 塞进 Secret:
kubectl create ns monitoring
kubectl -n monitoring create secret generic prom-rw-ca \
  --from-file=ca.crt=./ca.crt

kubectl -n monitoring create secret generic prom-rw \
  --from-literal=username=remote_writer --from-literal=password='xxx'

cat >values-agent.yaml << EOF
grafana:        { enabled: false }
alertmanager:   { enabled: false }

prometheus:
  agentMode: true                  # 开启转发器。不本地存储、省内存
  prometheusSpec:
    retention: 12h                 # 本地只留短期,方便就近排查
    externalLabels:
      platform: kubernetes
      prometheus_agent: eks-xx
      environment: test
      cloud_provider: aws
      cloud_account: xxx
      cloud_account_id: "9xxx"
      cluster_service: eks
      cluster_name: xxx
      region: ap-southeast-1
      project: jp
    additionalScrapeConfigsSecret: # 静态配置
      enabled: true
      name: additional-scrape-configs
      key: prometheus-additional.yaml
    remoteWrite:
      - url: https://<你的IP>:9090/api/v1/write
        basicAuth:
          username: { name: prom-rw, key: username }
          password: { name: prom-rw, key: password }
        writeRelabelConfigs:
          # 丢掉高基数直方图,跨云出流量是按 GB 收费的
          - sourceLabels: [__name__]
            regex: '(apiserver_request_duration_seconds_bucket|etcd_request_duration_seconds_bucket|.*_bucket)'
            action: drop
          - sourceLabels: [__name__]
            regex: 'go_.*|process_.*'
            action: drop
        tlsConfig:
          ca:
            secret:
              name: prom-rw-ca
              key: ca.crt
          #serverName: 'xx.me'
#Prometheus 每 10s 抓一次、用 cAdvisor 自带的时间戳写入,而 cAdvisor 的数据刷新比 10s 慢且不规律,必然出现「这次拿到的 ts 比上次写进去的还旧」
#  - Prometheus 每 10s 抓一次,用的是 cAdvisor 埋在数据里的 ts;
#  - 某次抓取拿到的 ts 比上一次已经写入的还旧 → WAL appender 判定 out-of-order → 整批丢弃。
# scrape_interval 设置为 30s,减少样本量
# 解决:关掉时间戳透传、减少样本量
kubelet:
  serviceMonitor:
    honorTimestamps: false
    trackTimestampsStaleness: false
    interval: 30s
EOF

用域名就把 xx.com 解析到 <你的IP>(;不想配 DNS 就直接写 https://<你的IP>:9090/api/v1/write 并把
  serverName 去掉——SAN 里有这个 IP,一样能通过校验。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm search repo prometheus-community

helm install -n monitoring kps \
    prometheus-community/kube-prometheus-stack \
    -f values-agent.yaml

helm upgrade --install -n monitoring kps \
     prometheus-community/kube-prometheus-stack \
     -f values-agent.yaml
# helm -n monitoring uninstall prometheus


# 验证
kubectl -n monitoring get secrets prom-agent-kps-kube-prometheus-stack-prometheus \
    -o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip \
    | grep -A6 'kubelet/1'
kubectl -n monitoring logs -f --tail 20 prom-agent-kps-kube-prometheus-stack-prometheus-0

装完就自带 kubelet / cAdvisor / kube-state-metrics / node-exporter / apiserver / coredns 全套抓取规则,不用自己写。

流量预估

  • remote_write 是 snappy 压缩的 protobuf,经验值约 1.5~2 bytes/sample。
  • 10 个节点、约 15 万 series、30s 一次 ≈ 5000 samples/s ≈ 10 KB/s ≈ 25 GB/月,AWS 出公网约 $2~3/月。做了上面的 drop 后通常能砍掉一半以上。

RDS / Redis / MQ 基础采集-YACE

基础指标:YACE 拉 CloudWatch(零网络打通)

所有 AWS 托管服务的官方指标都在 CloudWatch,而 CloudWatch 是公网 endpoint,理论上 exporter 放哪都行。放进 EKS 的好处是可以用 IRSA / Pod Identity 拿临时凭证,彻底不落地 AccessKey;代价是指标要经 remote_write 才能到阿里云,链路断了 CloudWatch 指标也一起看不到(可接受)。

IAM 授权

CLUSTER=eks-dev
REGION=ap-southeast-1
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)

IAM:建一个最小权限策略:

cat > yace-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "tag:GetResources",
      "cloudwatch:ListMetrics",
      "cloudwatch:GetMetricData",
      "cloudwatch:GetMetricStatistics",
      "iam:ListAccountAliases",
      "rds:DescribeDBInstances",
      "elasticache:DescribeCacheClusters",
      "mq:ListBrokers"
    ],
    "Resource": "*"
  }]
}
EOF

aws iam create-policy --policy-name YaceCloudWatchRead \
  --policy-document file://yace-policy.json
CLUSTER=eks-jp-test
REGION=ap-southeast-1
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
方式 A:IRSA(通用,推荐)

eksctl 一条命令把 OIDC provider、IAM Role、ServiceAccount 注解全部搞定:

eksctl utils associate-iam-oidc-provider --cluster $CLUSTER --region $REGION --approve

kubectl create ns monitoring --dry-run=client -o yaml | kubectl apply -f -

eksctl create iamserviceaccount \
  --cluster $CLUSTER --region $REGION \
  --namespace monitoring --name yace \
  --attach-policy-arn arn:aws:iam::${ACCOUNT}:policy/YaceCloudWatchRead \
  --approve

不用 eksctl 时,手动建 Role,信任策略为:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::<ACCOUNT>:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/<OIDC_ID>"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.ap-southeast-1.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:monitoring:yace",
        "oidc.eks.ap-southeast-1.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
      }
    }
  }]
}

然后给 SA 打注解: eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT>:role/YaceRole 。

方式 B:EKS Pod Identity(较新,不用碰 OIDC)
aws eks create-addon --cluster-name $CLUSTER --region $REGION \
  --addon-name eks-pod-identity-agent

# Role 信任策略的 Principal 为 pods.eks.amazonaws.com
# Action 为 sts:AssumeRole + sts:TagSession
aws eks create-pod-identity-association --cluster-name $CLUSTER --region $REGION \
  --namespace monitoring --service-account yace \
  --role-arn arn:aws:iam::${ACCOUNT}:role/YaceRole

此方式 SA 无需任何注解, kubectl create sa yace -n monitoring 即可。

部署清单

# 确认镜像
helm upgrade --install yace  \
  prometheus-community/yet-another-cloudwatch-exporter \
  --timeout 600s \
  --dry-run=client \
  --namespace monitoring --create-namespace |grep image
cat >yace.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: yace-config
  namespace: monitoring
data:
  config.yml: |
    apiVersion: v1alpha1
    sts-region: ap-southeast-1
    discovery:
      exportedTagsOnMetrics:
        AWS/RDS: [Name, Env]
        AWS/ElastiCache: [Name, Env]
      jobs:
        - type: AWS/RDS
          regions: [ap-southeast-1]
          period: 300
          length: 600
          metrics:
            - { name: CPUUtilization,      statistics: [Average, Maximum] }
            - { name: FreeableMemory,      statistics: [Average, Minimum] }
            - { name: FreeStorageSpace,    statistics: [Average, Minimum] }
            - { name: DatabaseConnections, statistics: [Average, Maximum] }
            - { name: ReadLatency,         statistics: [Average, Maximum] }
            - { name: WriteLatency,        statistics: [Average, Maximum] }
            - { name: ReadIOPS,            statistics: [Average] }
            - { name: WriteIOPS,           statistics: [Average] }
            - { name: ReplicaLag,          statistics: [Average, Maximum] }
            - { name: BurstBalance,        statistics: [Minimum] }
            - { name: DiskQueueDepth,      statistics: [Average] }
            - { name: SwapUsage, statistics: [Average] }

        - type: AWS/ElastiCache
          regions: [ap-southeast-1]
          period: 300
          length: 600
          metrics:
            - { name: CPUUtilization,        statistics: [Average, Maximum] }
            - { name: EngineCPUUtilization,  statistics: [Average, Maximum] }
            - { name: DatabaseMemoryUsagePercentage, statistics: [Average, Maximum] }
            - { name: CurrConnections,       statistics: [Average, Maximum] }
            - { name: ReplicationLag,        statistics: [Average, Maximum] }
            - { name: SwapUsage,             statistics: [Average] }
            - { name: Evictions,      statistics: [Sum, Average] }
            - { name: CacheHits,      statistics: [Sum] }
            - { name: CacheMisses,    statistics: [Sum, Average] }
            - { name: FreeableMemory, statistics: [Average, Minimum] }

        # Amazon MQ(RabbitMQ / ActiveMQ)
        - type: AWS/AmazonMQ
          regions: [ap-southeast-1]
          period: 300
          length: 600
          metrics:
            - { name: CpuUtilization,    statistics: [Average, Maximum] }
            - { name: SystemCpuUtilization,       statistics: [Average] }
            - { name: RabbitMQMemUsed,            statistics: [Average] }
            - { name: RabbitMQMemLimit,           statistics: [Average] }
            - { name: RabbitMQDiskFree,           statistics: [Average] }
            - { name: RabbitMQDiskFreeLimit,      statistics: [Average] }
            - { name: RabbitMQFdUsed,             statistics: [Average] }
            - { name: MessageCount,               statistics: [Average] }
            - { name: MessageReadyCount,          statistics: [Average] }
            - { name: MessageUnacknowledgedCount, statistics: [Average] }
            - { name: PublishRate,                statistics: [Average] }
            - { name: ConfirmRate,                statistics: [Average] }
            - { name: AckRate,                    statistics: [Average] }
            - { name: ConsumerCount,              statistics: [Average] }
            - { name: ConnectionCount,            statistics: [Average] }
            - { name: ChannelCount,               statistics: [Average] }
            - { name: QueueCount,                 statistics: [Average] }
            - { name: ExchangeCount,              statistics: [Average] }

        # 若 MQ 实为 MSK,改用 AWS/Kafka(CpuUser / KafkaDataLogsDiskUsed / SumOffsetLag ...)
        # 若为 SQS,改用 AWS/SQS(ApproximateNumberOfMessagesVisible / ApproximateAgeOfOldestMessage)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: yace
  namespace: monitoring
  labels: { app: yace }
spec:
  replicas: 1          # 必须为 1:多副本会重复调 CloudWatch API,费用翻倍
  strategy: { type: Recreate }
  selector:
    matchLabels: { app: yace }
  template:
    metadata:
      labels: { app: yace }
      annotations:
        checksum/config: "REPLACE_ME"   # 改配置后改此值触发滚动,或 kubectl rollout restart
    spec:
      serviceAccountName: yace
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        fsGroup: 65534
      containers:
        - name: yace
          image: quay.io/prometheuscommunity/yet-another-cloudwatch-exporter:v0.62.1
          args:
            - -config.file=/etc/yace/config.yml
            - -listen-address=:5000
            - -scraping-interval=300      # 后台轮询 CloudWatch 的周期(秒)
          ports:
            - { name: metrics, containerPort: 5000 }
          volumeMounts:
            - { name: config, mountPath: /etc/yace, readOnly: true }
          resources:
            requests: { cpu: 50m,  memory: 128Mi }
            limits:   { cpu: 500m, memory: 512Mi }
          readinessProbe:
            httpGet: { path: /metrics, port: metrics }
            initialDelaySeconds: 15
            periodSeconds: 20
            timeoutSeconds: 10
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
      volumes:
        - name: config
          configMap: { name: yace-config }
---
apiVersion: v1
kind: Service
metadata:
  name: yace
  namespace: monitoring
  labels: { app: yace }
spec:
  selector: { app: yace }
  ports:
    - { name: metrics, port: 5000, targetPort: metrics }
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: yace
  namespace: monitoring
  labels:
    app: yace
    release: kps        # 需匹配 kube-prometheus-stack 的 release 名,否则不被发现
spec:
  selector:
    matchLabels: { app: yace }
  endpoints:
    - port: metrics
      interval: 60s
      scrapeTimeout: 30s
EOF

kubectl -n monitoring get po
kubectl -n monitoring logs deploy/yace

# 验证
kubectl -n monitoring get pod -owide |grep yace
curl -s http://172.31.88.138:5000/metrics |grep -c '^aws_'
curl -s http://172.31.88.138:5000/metrics |grep -c '^aws_rds'

深度指标:exporter 放进 EKS

redis-exporter

cat >exporter.yaml <<EOF
apiVersion: v1
data:
  sample-pwd-file.json: |-
    {
      "redis://redis6:6379": "",
      "rediss://[email protected]:6379": "passwordxx"
    }
kind: ConfigMap
metadata:
  name: redis-pwd-config
  namespace: monitoring
---
apiVersion: v1
kind: Service
metadata:
  name: redis-exporter
  namespace: monitoring
  #annotations:
  #  prometheus.io/scrape: "true"
  labels:
    app: redis-exporter
spec:
  selector:
    app: redis-exporter
  ports:
    - name: tcp-9121
      protocol: TCP
      port: 9121
      targetPort: 9121
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-exporter
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis-exporter
  template:
    metadata:
      #annotations:
      #  prometheus.io/path: "/metrics"
      #  prometheus.io/port: "9121"
      #  prometheus.io/scrape: "true"
      labels:
        app: redis-exporter
    spec:
      #nodeSelector:
      #  type: ops-prod-prometheus
      #imagePullSecrets:
      #- name: docker-secret
      containers:
      - name: redis-exporter
        image: oliver006/redis_exporter:v1.91.1
        imagePullPolicy: IfNotPresent
        env:
        - name: REDIS_PASSWORD_FILE
          value: /data/sample-pwd-file.json
        resources:
          limits:
            cpu: 300m
            memory: 300Mi
          requests:
            cpu: 10m
            memory: 10Mi
        ports:
        - containerPort: 9121
        volumeMounts:
        - mountPath: /data
          name: redis-pwd-file
      volumes:
      - configMap:
          defaultMode: 420
          items:
          - key: sample-pwd-file.json
            path: sample-pwd-file.json
          name: redis-pwd-config
        name: redis-pwd-file
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: redis-exporter
  namespace: monitoring
  labels:
    app.kubernetes.io/name: redis-exporter
    app.kubernetes.io/instance: redis-exporter
    app.kubernetes.io/version: "1.91.1"
    app.kubernetes.io/component: exporter
    release: kps
spec:
  endpoints:
    - path: /metrics
      port: tcp-9121
      #interval: 15s
  namespaceSelector:
    matchNames:
      - monitoring
  selector:
    matchLabels:
      app: redis-exporter
EOF

添加 agent 静态配置

cat >prometheus-additional.yaml <<EOF
#Redis
- job_name: 'redis-exporter-targets'
  static_configs:
  - targets:
    - rediss://[email protected]:6379
    labels:
      dbtype: "db-redis"
  metrics_path: /scrape
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: redis-exporter:9121

#EC2
- job_name: 'node-exporter'
  static_configs:
  - targets:
    - "x.x.x.x:9100"
    labels:
      business: jp-test
      type: ec2
      service: "ops-jmp"
EOF

# Update
kubectl -n monitoring create secret generic additional-scrape-configs \
        --from-file=prometheus-additional.yaml \
        --dry-run=client -oyaml|kubectl replace -f-


# helm
prometheus:
  agentMode: true                  # 开启转发器。不本地存储、省内存。不能查历史
  prometheusSpec:
    ...
    additionalScrapeConfigsSecret: # 静态配置
      enabled: true
      name: additional-scrape-configs
      key: prometheus-additional.yaml


helm upgrade --install -n monitoring kps \
     prometheus-community/kube-prometheus-stack \
     -f values-agent.yaml

PrometheusAlert全家桶

下载 app.conf docker-compose

# 下载默认配置。修改登录密码
mkdir prometheusalert/conf
cd prometheusalert/conf
wget https://raw.githubusercontent.com/feiyu563/PrometheusAlert/master/conf/app-example.conf -O app.conf


  prometheusalert:
    container_name: prometheusalert
    image: feiyu563/prometheus-alert:master
    depends_on:
      - prometheus
    user: '0'
    ports:
      - 18080:8080
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./prometheusalert/conf:/app/conf
      - ./prometheusalert/db:/app/db # 防模板丢失
      - ./prometheusalert/logs:/app/logs
    networks:
      - monitor_net
    restart: always

配置 alertmanager 路由

cat >prometheus/alertmanager.yml <<EOF
route:
  group_by: ['alertname', 'instance']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 20m
  receiver: 'default-receiver'

  routes:
  - receiver: 'eks-p1'
    group_wait: 10s
    group_interval: 30s
    repeat_interval: 30m
    match_re:
      #severity: critical
      cluster: ".*eks.*"
  - receiver: 'default-receiver'
    group_by: ['alertname']
    match_re:
      alertname: ".*"
receivers:
- name: "pt-p1"
  webhook_configs:
    # type:fs(飞书),tg(Telegram),webhook
    # tpl:模板名。内置了内置有 prometheus-dd / prometheus-wx / prometheus-fs
    # ddurl/wxurl/fsurl:覆盖 app.conf 默认值,多个用 , 分隔
    # at:@ 指定手机号,多个用 , 分隔
    # rr=true:多地址时随机取一个发,避免机器人限频
  - url: 'http://prometheusalert:8080/prometheusalert?type=fs&tpl=prometheus-fs&fsurl=https://open.larksuite.com/open-apis/bot/v2/hook/xxxx'
    send_resolved: true
- name: "default-receiver"
  webhook_configs:
  - url: 'http://prometheusalert:8080/prometheusalert?type=fs&tpl=prometheus-fs&fsurl=https://open.larksuite.com/open-apis/bot/v2/hook/xxx'
    send_resolved: true

inhibit_rules:
- source_matchers: [severity="critical"]
  target_matchers: [severity="warning"]
  equal: ['alertname', 'instance']
EOF

备选方案

方案 B:跨云 VPN,保持纯 pull

阿里云 VPN 网关 ↔ AWS Site-to-Site VPN 建 IPsec 隧道(你的 10.14.0.0/16 和 172.31.0.0/16 不冲突,可以直接互通)。打通后外部 Prometheus 就能用 kubernetes_sd_configs 直抓 Pod、直连 RDS 端口。

  • 优点:模型干净,指标零延迟,不用改集群。
  • 缺点:跨云公网 IPsec 稳定性一般,隧道断了就整片断采;VPN 网关有固定月费;抓取是持续长连接,对隧道压力比 remote_write 大。
  • 适合:你本来就需要跨云互通(比如业务侧也要访问)时顺带做。

方案 C:通过公网 apiserver 做代理抓取(小集群应急)

你的 EKS API Server 公网可达,可以让 Prometheus 通过 apiserver 的 proxy 子资源间接抓节点:

- job_name: 'eks-cadvisor-via-apiserver'
  scheme: https
  tls_config: { ca_file: /etc/prometheus/eks/ca.crt }
  bearer_token_file: /etc/prometheus/eks/token
  kubernetes_sd_configs:
    - role: node
      api_server: https://XXXXX.gr7.ap-southeast-1.eks.amazonaws.com
      tls_config: { ca_file: /etc/prometheus/eks/ca.crt }
      bearer_token_file: /etc/prometheus/eks/token
  relabel_configs:
    - action: labelmap
      regex: __meta_kubernetes_node_label_(.+)
    - target_label: __address__
      replacement: XXXXX.gr7.ap-southeast-1.eks.amazonaws.com:443
    - sourceLabels: [__meta_kubernetes_node_name]
      regex: (.+)
      target_label: __metrics_path__
      replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

需要建一个 ServiceAccount,ClusterRole 授予 nodes/proxy、nodes/metrics、services/proxy 的 get 权限,并手动创建 kubernetes.io/service-account-token 类型的 Secret 拿长期 token(1.24+ 不再自动签发)。

  • 缺点很实在:所有抓取流量都压在 apiserver 上,节点一多 apiserver CPU 会被打爆;Pod 级自定义指标也得走 service proxy,很别扭。
  • 只建议用于十几个节点以内、临时过渡。另外务必把 EKS 公网 endpoint 的 Public access source allowlist 限制成 47.86.42.227/32。