ES transport证书p12到期监控实现(CronJob推送指标)
记录时间:2026-09-07
环境:dyck 生产 K8s(RKE,ARM64 信创节点)/ Elasticsearch 7.17 三节点集群(StatefulSetdyck-elasticsearch,tools namespace)/ VictoriaMetrics 集群(部署在 kjds,入口域名prod.vm.example.com)/ elasticsearch-exporter v1.11.0
一、背景
dyck 生产 ES 集群的节点间互信(transport 层,9300 端口)用的是 elasticsearch-certutil 自签的 p12 证书,以 Secret dyck-elastic-certificates 挂载进容器。我查了当前证书状态,2029-03-29 到期。
这个证书过期的影响比看起来严重:TLS 证书验证发生在握手阶段,存量连接过期后还活着,集群表面正常,但任何一次节点重启、网络闪断触发连接重建时握手直接失败,节点掉出集群回不来,分片堆积滚向 red。故障时刻不等于过期时刻,等爆出来排查会走弯路。
监控这条路有个前提问题:elasticsearch-exporter(我用的 v1.11.0)不采集 _ssl/certificates 这个 xpack API,标准采集方式拿不到证书剩余天数。好在 ES 原生就带这个接口,我手动调用确认输出里直接有 expiry 字段,数据源现成。
我的方案:CronJob 每天调一次这个 API,取最早过期的证书换算剩余天数,按 Prometheus 文本格式推给 VM,vmalert 按阈值评估告警。更彻底的做法是重签一份 10 年证书一次性解决,操作单我已经备好,但当前离过期还有两年半,先落监控兜底,重签排期再做。
镜像说明(重要):本方案用到的两个镜像都是我自己构建/转推进 Harbor 的,集群内不用 Docker Hub:
| 镜像 | 来源 | Harbor 地址 |
|---|---|---|
| elasticsearch-exporter | 官方镜像 pull 后重打 tag | reg-hub.example.com/library/prometheuscommunity/elasticsearch-exporter:v1.11.0 |
| curl-jq | 基于 alpine 自建(alpine 官方镜像不带 curl 和 jq,我加了一层 apk add) |
reg-hub.example.com/library/curl-jq:latest |
二、操作过程
2.1 构建并推送镜像
exporter 用官方镜像转推(注意 dyck 节点是 arm64,--platform 要对):
docker pull --platform linux/arm64 prometheuscommunity/elasticsearch-exporter:v1.11.0
docker tag prometheuscommunity/elasticsearch-exporter:v1.11.0 \
reg-hub.example.com/library/prometheuscommunity/elasticsearch-exporter:v1.11.0
docker push reg-hub.example.com/library/prometheuscommunity/elasticsearch-exporter:v1.11.0
curl-jq 没有现成的官方组合镜像(官方 curlimages/curl 实测不带 jq),我自己写了两行 Dockerfile:
FROM alpine:3.20
RUN apk add --no-cache curl jq
docker build -t reg-hub.example.com/library/curl-jq:latest .
docker push reg-hub.example.com/library/curl-jq:latest
注意:构建机的架构要和集群节点一致(我直接在 arm64 机器上 build),x86 机器跨架构 build 需要 buildx + QEMU。
2.2 部署 CronJob
完整清单在仓库 prometheus/kube-prometheus-stack/prod-xc-kube-prometheus-stack/other/elasticsearch-exporter/es-cert-expiry-check-cronjob.yaml,核心是 ConfigMap 脚本 + CronJob 两段。
脚本逻辑:带认证调 _ssl/certificates,jq 取最早过期的证书转 epoch,减当前时间得剩余天数,拼成文本格式的指标 POST 给 vminsert 的导入端点:
#!/bin/sh
set -eu
ES_PW="$(cat /run/secrets/es-pw/password)"
JSON="$(curl -sf --max-time 30 -u "elastic:${ES_PW}" \
"http://dyck-elasticsearch.tools.svc.cluster.local:9200/_ssl/certificates")"
MIN_EPOCH="$(echo "$JSON" | jq -r '[.[].expiry | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601] | min')"
[ -n "${MIN_EPOCH}" ] || { echo "ERROR: no expiry parsed from API response"; exit 1; }
NOW="$(date +%s)"
DAYS=$(( (MIN_EPOCH - NOW) / 86400 ))
METRIC="es_transport_cert_expiry_days{systemName=\"dyck\",cluster=\"dyck-prod-xc\",job=\"dyck-es-cert-expiry-check\"} ${DAYS} $(date +%s)000"
curl -sf --max-time 30 -u "${VM_USER}:${VM_PASS}" \
-X POST "http://prod.vm.example.com/insert/0/prometheus/api/v1/import/prometheus" \
--data-binary "${METRIC}"
CronJob 关键参数:
| 参数 | 值 | 说明 |
|---|---|---|
| schedule | 0 6 * * * |
每日 06:00 低峰执行,告警粒度 ±1 天 |
| 镜像 | library/curl-jq:latest |
自建镜像(见 2.1) |
| hostAliases | 10.202.17.91 → prod.vm.example.com |
集群内 DNS 解析不了这个域名,必须写 hosts |
| env 凭据 | vm-remote-write-basic-auth |
VM 推送认证,与 Prometheus remoteWrite 同源 |
| volume 挂载 | dyck-elasticsearch-password |
ES 密码经文件读入,脚本零明文 |
推送地址我用的是 prod.vm.example.com/insert/0/prometheus/api/v1/import/prometheus,这个前置条件有两个,缺一不可:
- hostAliases:集群内 DNS 解析不了公司内部域名,我在 CronJob 的 Pod spec 里写了与 dyck Prometheus 同款的 hosts 条目(
10.202.17.91是 kjds 的 ingress VIP); - tools namespace 的 Secret 副本:K8s 的
secretKeyRef不能跨 namespace 引用,monitoring 里的vm-remote-write-basic-auth不会自动共享,我在 tools 建了一份同值的。
2.3 配置 VMRule 告警
规则追加在仓库 victoriametrics/xc-kjds/alerting/rules/elasticsearch/vmrule-elasticsearch.yaml,三条:
- alert: ESTransport证书即将过期
expr: es_transport_cert_expiry_days < 30
for: 1h
labels:
severity: warning
- alert: ESTransport证书过期临近红线
expr: es_transport_cert_expiry_days < 7
for: 1h
labels:
severity: critical
- alert: ES证书检查数据中断
expr: (time() - timestamp(es_transport_cert_expiry_days{job="dyck-es-cert-expiry-check"})) > 172800
for: 1h
labels:
severity: warning
第三条是给我自己这套检查任务上的保险:指标 48 小时没更新(ES 认证轮换没同步、CronJob 被删等),告警本身先报出来,监控不静默变盲。
三、验证
手动触发一次(不用等凌晨的 schedule):
kubectl apply -f es-cert-expiry-check-cronjob.yaml
kubectl create job --from=cronjob/dyck-es-cert-expiry-check dyck-es-cert-test -n tools
kubectl logs -n tools job/dyck-es-cert-test
预期输出两行:
cert earliest expiry days remaining: 939
pushed to VM ok
vmui 查询确认序列与值:
es_transport_cert_expiry_days{job="dyck-es-cert-expiry-check"} # ≈ 939
之后每天 06:00 刷新一次,跌破 30 天微信收到 warning,7 天 critical。
四、注意事项 / 遇到的问题
这几个问题都是我实际撞到的,按踩到的顺序记:
- exporter v1.11.0 不消费
ES_URI/ES_PASSWORD环境变量。我最初用 env 方式注入地址和密码,Pod 起来了、targets 也是 UP,但/metrics里只有 go 和 process 自身指标,一个elasticsearch_*都没有,启动日志干净得只有两行 Listening。最后确认进程拿着默认值localhost:9200在空转。解决:认证地址改写在启动参数里--es.uri=http://elastic:xxx@...(k3s 那套的老写法)。代价是密码明文出现在 Pod spec,换密码时 command 和 Secret 两处要同步改。 - v1.11.0 镜像的 USER 是字母形态
nobody,和清单里的runAsNonRoot: true冲突(kubelet 要求数字 UID 才能验证非 root),Pod 起不来。解决:容器级显式runAsUser: 65534。 - 官方
curlimages/curl镜像不带 jq(我原本想当然认为带了),Job 直接jq: not found。解决:换我自建的curl-jq镜像。
五、参考资料
- ES 证书 API:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-api-ssl.html
- elasticsearch_exporter:https://github.com/prometheus-community/elasticsearch_exporter
- VM 文本导入端点:https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
- 仓库部署清单:
prometheus/kube-prometheus-stack/prod-xc-kube-prometheus-stack/other/elasticsearch-exporter/ - 告警规则:
victoriametrics/xc-kjds/alerting/rules/elasticsearch/vmrule-elasticsearch.yaml