生产业务日志集中采集与检索平台建设部署笔记
唯一主线:只读盘点 → 变更保护 → 隔离日志平台 → TLS/最小权限 → ES/Kibana → Logstash → Filebeat 灰度 → 逐节点放开 → 网站真实后台验收 → 快照恢复 → 安全回滚。任何节点异常、双采或未批准变更均停止在当前步骤。
项目时间:2024.08—2025.03。
部署线路图
主线:已有 Kubernetes 与业务只读盘点 → 变更保护 → 隔离日志命名空间与三节点 Elasticsearch → TLS 与 Kibana HTTPS 入口 → Logstash 解析与索引路由 → Filebeat DaemonSet 灰度采集 → 逐节点放开 → Kibana 网页检索验收 → 快照备份、隔离恢复与安全回滚。任何节点异常、双采或未批准变更均停止在当前步骤。
0. 固定参数
| 参数 | 固定值 |
|---|---|
| 集群 / 业务 | prod-cluster / business-prod |
| 日志平台 | observability / prod-logs / prod-kibana |
| ES/存储 | 8.14.3 / fast-ssd(三节点各 500Gi)/ cephfs-snapshots(快照 PVC 2Ti) |
| TLS/入口 | internal-ca / nginx / kibana.ops.internal |
| 日志 | logstash-beats.observability.svc:5044 / logs-k8s-prod-* / 30 天 |
| 灰度/恢复 | wk01 / prod-logs-cephfs / restore-logs-k8s-prod-* |
1. 接入前只读初始化盘点
执行机器:生产发布机;使用只读 kubeconfig。 前置确认:本节禁止 apply、delete、restart。
# 确认目标生产集群。
kubectl config current-context # 输出当前 kubeconfig 上下文,必须是 prod-cluster。
# 检查节点和业务。
kubectl get nodes -o wide # 输出节点 Ready 状态、内部 IP 和 Kubernetes 版本。
kubectl -n business-prod get pod -o wide # 输出业务 Pod 状态、所在节点和 Pod IP。
# 查已有采集器,防止双采。
kubectl get daemonset,deploy -A | grep -Ei 'filebeat|fluent|vector|elastic|logstash' || true # 查已有日志组件;无匹配时不让只读盘点失败。
# 确认存储、入口、证书和快照能力。
kubectl get storageclass # 列出存储类,后续必须存在 fast-ssd。
kubectl get storageclass cephfs-snapshots # 确认快照共享存储类存在;三台 ES Pod 共同挂载此类创建的 RWX PVC。
kubectl get ingressclass # 列出入口类,后续必须存在 nginx。
kubectl -n cert-manager get pod # 确认证书控制器 Pod 全部 Running。
kubectl get clusterissuer internal-ca # 确认内部 CA 签发器存在且 Ready。
真实输出样例:节点 STATUS 均为 Ready;业务 Pod 为 Running;采集器命令列出名称和 Namespace。 通过标准:负责人确认已有采集器迁移窗口、索引前缀、输出地址和灰度节点 wk01。 失败处理:Context 错、节点异常、未知采集器、业务窗口外时停止;禁止先部署再排查。
2. 隔离部署 ES 与 Kibana
执行机器:生产发布机。 前置确认:fast-ssd、internal-ca、nginx 已由集群平台验收。
# 安装固定 ECK Operator。
helm repo add elastic https://helm.elastic.co # 注册 Elastic 官方 Helm 仓库。
helm repo update # 拉取仓库索引,确保能解析固定 Chart 版本。
kubectl create namespace elastic-system --dry-run=client -o yaml | kubectl apply -f - # 幂等创建仅运行 ECK Operator 的命名空间。
helm upgrade --install eck-operator elastic/eck-operator --version 2.13.0 --namespace elastic-system --wait --timeout 5m # 安装固定版本并等待 Operator 就绪。
# 创建隔离日志 Namespace。
kubectl create namespace observability --dry-run=client -o yaml | kubectl apply -f - # 幂等创建日志平台隔离命名空间。
kubectl -n elastic-system get pod # 确认 ECK Operator 为 1/1 Running。
真实输出样例:eck-operator-... 为 1/1 Running。 通过标准:Operator Ready 后才创建 Elasticsearch。 失败处理:ImagePullBackOff 查镜像策略;Pending 查资源/污点。
2.1 创建唯一的采集清单(证书、Logstash、RBAC、Filebeat)
执行机器:生产发布机。本节只生成并引用本文完整给出的 20-log-collect.yaml 与 21-logstash-filebeat.yaml;不依赖任何未在文中定义的 YAML 文件。
# 创建唯一的采集链路清单文件;此文件包含后续所有对象。
tee 20-log-collect.yaml <<'EOF' # 把下方固定配置写入当前目录的唯一采集入口清单。
apiVersion: cert-manager.io/v1 # 使用 cert-manager Certificate API。
kind: Certificate # 声明要申请一张 TLS 证书。
metadata: # 定义证书对象元数据。
name: logstash-beats # 证书对象固定名称。
namespace: observability # 证书和目标 Secret 均位于日志平台命名空间。
spec: # 定义证书签发规格。
secretName: logstash-beats-tls # 签发成功后把证书和私钥写入这个 Secret。
dnsNames: # 证书 SAN 列出 Filebeat 实际连接的两个 Service 域名。
- logstash-beats.observability.svc # 集群内短域名。
- logstash-beats.observability.svc.cluster.local # 集群内完整域名。
issuerRef: # 指定签发证书的集群级 CA。
name: internal-ca # 使用已验收的内部 CA 名称。
kind: ClusterIssuer # internal-ca 是集群级而不是命名空间级签发器。
--- # 分隔下一个 Kubernetes 对象。
apiVersion: v1 # 使用核心 Kubernetes API。
kind: Service # 声明供 Filebeat 访问的稳定服务入口。
metadata: # 定义服务对象元数据。
name: logstash-beats # Service 名称必须与证书 DNS 完全一致。
namespace: observability # 服务位于日志平台命名空间。
spec: # 定义服务转发规则。
selector: # 只选择带 app=logstash 标签的 Logstash Pod。
app: logstash # 与后续 Deployment Pod 标签一一对应。
ports: # 定义 Service 对外暴露的端口列表。
- name: beats # 命名端口供探针与排错识别。
port: 5044 # Filebeat 连接的 Service TCP 端口。
targetPort: 5044 # 转发到 Logstash 容器 Beats 输入端口。
EOF # 结束 here-document,完成 YAML 文件写入。
# 服务端校验并创建证书和 Service。
kubectl apply --dry-run=server -f 20-log-collect.yaml # 由 API Server 校验 YAML、CRD 和准入策略,不落盘。
kubectl apply -f 20-log-collect.yaml # 创建 Certificate 与 Service。
kubectl -n observability wait --for=condition=Ready certificate/logstash-beats --timeout=180s # 最多等待 180 秒,证书 Ready 才能继续。
真实输出样例:Certificate READY 为 True,Service logstash-beats 已存在。 通过标准:TLS Secret 含 tls.crt、tls.key、ca.crt,且 DNS 与 Service 完全一致。 失败处理:Certificate 未 Ready 时查 Issuer/Certificate Events,不能关闭 TLS。
# 文件:10-elastic-stack.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prod-logs-snapshot-rwx
namespace: observability
spec:
accessModes: [ ReadWriteMany ]
storageClassName: cephfs-snapshots
resources:
requests:
storage: 2Ti
---
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: prod-logs
namespace: observability
spec:
version: 8.14.3
nodeSets:
- name: master-data
count: 3
config:
node.roles: [ master, data_hot, ingest ]
path.repo: [ /mnt/es-snapshots ]
podTemplate:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: elasticsearch.k8s.elastic.co/cluster-name
operator: In
values: [ prod-logs ]
topologyKey: kubernetes.io/hostname
containers:
- name: elasticsearch
volumeMounts:
- name: es-snapshot-rwx
mountPath: /mnt/es-snapshots
resources:
requests: { cpu: "2", memory: 4Gi }
limits: { cpu: "4", memory: 8Gi }
volumes:
- name: es-snapshot-rwx
persistentVolumeClaim:
claimName: prod-logs-snapshot-rwx
volumeClaimTemplates:
- metadata: { name: elasticsearch-data }
spec:
accessModes: [ ReadWriteOnce ]
storageClassName: fast-ssd
resources:
requests: { storage: 500Gi }
---
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
name: prod-kibana
namespace: observability
spec:
version: 8.14.3
count: 1
elasticsearchRef: { name: prod-logs }
配置详解(10-elastic-stack.yaml):文件先创建 prod-logs-snapshot-rwx:它使用固定 cephfs-snapshots、ReadWriteMany 与 2Ti 容量,是三台 ES 共同可见的快照路径。其余对象使用 ECK 的 Elasticsearch/Kibana 自定义资源;两处 metadata.name 固定资源名称,namespace: observability 保证日志平台隔离。ES 的 version: 8.14.3 必须与 Kibana 同版本;nodeSets.name: master-data 是节点组名,count: 3 表示三节点高可用;node.roles 同时启用选主、热数据和 ingest,适合本手册唯一的日志集群。path.repo 只允许使用 /mnt/es-snapshots;该目录由共享快照 PVC 挂载,后续只在这里注册快照仓库。podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution 强制同集群 ES Pod 不落到同一主机,labelSelector 只匹配 prod-logs 集群的 Pod,topologyKey: kubernetes.io/hostname 以节点为反亲和维度。resources.requests 是调度保底资源,limits 是单 Pod 上限;volumeClaimTemplates 为每个 ES Pod 创建独立 PVC,accessModes: ReadWriteOnce 只允许一个节点挂载,storageClassName: fast-ssd 使用已验收 SSD 存储,storage: 500Gi 是每节点固定容量。Kibana 的 count: 1 表示单副本 Web 查询层,elasticsearchRef.name: prod-logs 让 ECK 自动注入 ES 访问配置;此处不暴露 ES 服务到集群外。
配置作用:创建隔离的三节点 Elasticsearch 数据面和一台只供查询的 Kibana。 生效结果:ECK 自动创建 ES HTTP CA、内部 Service、PVC、StatefulSet 类工作负载与 Kibana 到 ES 的受控连接。 注意事项:任何 count、磁盘容量或 ES 版本变更都必须单独走容量和变更评审;不能删除 elasticsearch-data PVC 来处理启动失败。
# 校验、部署并观察。
kubectl apply --dry-run=server -f 10-elastic-stack.yaml # 提交前由 API Server 校验 ECK 对象与 PVC 规格。
kubectl apply -f 10-elastic-stack.yaml # 创建三节点 ES 和单副本 Kibana。
kubectl -n observability wait --for=jsonpath='{.status.health}'=green elasticsearch/prod-logs kibana/prod-kibana --timeout=15m # ES 与 Kibana 都达到 green 前禁止部署 Logstash 与 Filebeat。
kubectl -n observability get elasticsearch,kibana,pod,pvc # 输出 ES/Kibana 健康、三个 Pod 和四个 PVC 的最终状态。
真实输出样例:三个 ES Pod 1/1 Running,PVC 为 Bound,HEALTH 为 green。 通过标准:Elasticsearch 与 Kibana 均为 green,三个 ES Pod 为 1/1 Running,三个数据 PVC 和一个快照 PVC 均为 Bound;否则不得进入第 3 节。 失败处理:查 ECK Events、资源、StorageClass;禁止删除 PVC。
3. TLS Logstash 与 Filebeat 灰度
执行机器:生产发布机和 ES/PKI 管理员。 前置确认:logstash_writer 仅有 logs-k8s-prod-* 写权限;禁止 elastic 超级账号。
阶段通过标准:Logstash 写入账号、Logstash TLS Secret、Filebeat CA Secret 均存在且不输出敏感值;未满足任一项时禁止创建 DaemonSet。 阶段失败处理:写入权限不足由 ES 管理员调整最小权限;证书未签发则检查 Issuer 和 Certificate Event;禁止改用 elastic 超级账号或关闭 TLS。
# 交互创建凭据;密码不进入 YAML 或 Git。
kubectl -n observability create secret generic logstash-es-credentials --from-literal=username=logstash_writer --from-literal=password --dry-run=client -o yaml | kubectl apply -f - # 读取组件最近日志,定位采集、TLS 或输出异常。
# 从签发后的证书创建 Filebeat 只读 CA;不要复制私钥。
kubectl -n observability get secret logstash-beats-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > ./logstash-beats-ca.crt # 仅导出 CA 公钥到临时文件,绝不导出私钥。
kubectl -n observability create secret generic logstash-beats-ca --from-file=ca.crt=./logstash-beats-ca.crt --dry-run=client -o yaml | kubectl apply -f - # 创建供 Filebeat 校验服务端证书的只读 CA Secret。
rm -f ./logstash-beats-ca.crt # 删除本地临时 CA 文件,避免残留在发布机。
# 文件:21-logstash-filebeat.yaml;以下完整清单保存为此文件。
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: logstash
namespace: observability
---
apiVersion: v1
kind: ConfigMap
metadata:
name: logstash-pipeline
namespace: observability
data:
logstash.conf: |-
input { beats { port => 5044 ssl_enabled => true ssl_certificate => "/usr/share/logstash/config/beats/tls.crt" ssl_key => "/usr/share/logstash/config/beats/tls.key" } }
filter { mutate { add_field => { "log_pipeline" => "prod-elk" } } }
output { elasticsearch { hosts => [ "https://prod-logs-es-http:9200" ] user => "${ES_USERNAME}" password => "${ES_PASSWORD}" ssl_certificate_authorities => [ "/usr/share/logstash/config/es-ca/ca.crt" ] index => "logs-k8s-prod-%{+YYYY.MM.dd}" } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: logstash
namespace: observability
spec:
replicas: 2
selector: { matchLabels: { app: logstash } }
template:
metadata: { labels: { app: logstash } }
spec:
serviceAccountName: logstash
containers:
- name: logstash
image: docker.elastic.co/logstash/logstash:8.14.3
ports: [ { name: beats, containerPort: 5044 } ]
env:
- name: ES_USERNAME
valueFrom: { secretKeyRef: { name: logstash-es-credentials, key: username } }
- name: ES_PASSWORD
valueFrom: { secretKeyRef: { name: logstash-es-credentials, key: password } }
resources: { requests: { cpu: 500m, memory: 1Gi }, limits: { cpu: "1", memory: 2Gi } }
readinessProbe: { tcpSocket: { port: beats }, initialDelaySeconds: 20, periodSeconds: 10 }
volumeMounts:
- { name: pipeline, mountPath: /usr/share/logstash/pipeline/logstash.conf, subPath: logstash.conf, readOnly: true }
- { name: beats-tls, mountPath: /usr/share/logstash/config/beats, readOnly: true }
- { name: es-ca, mountPath: /usr/share/logstash/config/es-ca, readOnly: true }
volumes:
- { name: pipeline, configMap: { name: logstash-pipeline } }
- { name: beats-tls, secret: { secretName: logstash-beats-tls } }
- { name: es-ca, secret: { secretName: prod-logs-es-http-ca-internal } }
---
apiVersion: v1
kind: ServiceAccount
metadata: { name: filebeat, namespace: observability }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: filebeat-metadata-reader }
rules:
- apiGroups: [""]
resources: ["pods", "namespaces", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: { name: filebeat-metadata-reader }
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: filebeat-metadata-reader }
subjects: [ { kind: ServiceAccount, name: filebeat, namespace: observability } ]
---
apiVersion: v1
kind: ConfigMap
metadata: { name: filebeat-config, namespace: observability }
data:
filebeat.yml: |-
filebeat.inputs:
- type: filestream
id: container-logs
paths: ["/var/log/containers/*.log"]
parsers: [ { container: ~ } ]
processors:
- add_kubernetes_metadata: { host: ${NODE_NAME} }
output.logstash:
hosts: ["logstash-beats.observability.svc:5044"]
ssl: { certificate_authorities: ["/usr/share/filebeat/certs/ca.crt"], verification_mode: full }
---
apiVersion: apps/v1
kind: DaemonSet
metadata: { name: filebeat, namespace: observability }
spec:
selector: { matchLabels: { app: filebeat } }
template:
metadata: { labels: { app: filebeat } }
spec:
serviceAccountName: filebeat
nodeSelector: { logs.ops.internal/filebeat: enabled }
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat:8.14.3
args: ["-c", "/usr/share/filebeat/filebeat.yml", "-e"]
env: [ { name: NODE_NAME, valueFrom: { fieldRef: { fieldPath: spec.nodeName } } } ]
resources: { requests: { cpu: 100m, memory: 128Mi }, limits: { cpu: 500m, memory: 512Mi } }
volumeMounts:
- { name: config, mountPath: /usr/share/filebeat/filebeat.yml, subPath: filebeat.yml, readOnly: true }
- { name: containers, mountPath: /var/log/containers, readOnly: true }
- { name: logstash-ca, mountPath: /usr/share/filebeat/certs, readOnly: true }
volumes:
- { name: config, configMap: { name: filebeat-config } }
- { name: containers, hostPath: { path: /var/log/containers, type: Directory } }
- { name: logstash-ca, secret: { secretName: logstash-beats-ca } }
配置详解(21-logstash-filebeat.yaml):Logstash 使用 ServiceAccount: logstash、双副本 Deployment 和固定 8.14.3 镜像;其 ConfigMap.logstash.conf 中 beats input 只监听 5044 并要求 TLS,ssl_certificate/ssl_key 来自 logstash-beats-tls,elasticsearch output 仅访问集群内部 prod-logs-es-http:9200,凭据来自 logstash-es-credentials,ES CA 来自 ECK 自动创建的 prod-logs-es-http-ca-internal。readinessProbe.tcpSocket 只在 5044 可监听后放入 Service Endpoint。Filebeat 使用独立 ServiceAccount;ClusterRole 仅允许读取 pods、namespaces、nodes 的元数据,ClusterRoleBinding 只绑定该账号。filestream 只读 /var/log/containers/*.log,container parser 解析 CRI 容器日志,add_kubernetes_metadata 通过 ${NODE_NAME} 关联本节点 Pod;其唯一输出是带完整证书校验的 logstash-beats.observability.svc:5044。DaemonSet 的 nodeSelector: logs.ops.internal/filebeat: enabled 保证未打标签的 Worker 不采集;三个 volume 分别挂载配置、只读宿主机容器日志和 CA。
配置作用:构成唯一链路 Filebeat → TLS Logstash → HTTPS Elasticsearch,并以节点标签控制灰度范围。 生效结果:每个被批准的 Worker 最多运行一个 Filebeat;Logstash 只有证书和最小 ES 写权限都齐全时才接收并写入当天索引。 注意事项:prod-logs-es-http-ca-internal 是 ECK 按 ES 名称生成的 Secret;若 ES 名称改动,必须同时改此引用。禁止为排错将 verification_mode 改成 none、将 Filebeat 改为特权容器,或把 ES/Logstash 改成 NodePort。
# 校验并部署完整采集清单;此时尚未给业务节点打采集标签。
kubectl apply --dry-run=server -f 20-log-collect.yaml -f 21-logstash-filebeat.yaml # 校验全部对象、Secret 引用、RBAC 和准入策略。
kubectl apply -f 20-log-collect.yaml -f 21-logstash-filebeat.yaml # 创建 Logstash、Filebeat、RBAC 与配置对象。
kubectl -n observability rollout status deployment/logstash --timeout=180s # 最多等待 180 秒,确认两个 Logstash 副本就绪。
# 只将负责人批准的 wk01 纳入灰度;确认后再按同一标签方式逐节点扩大。
kubectl label node wk01 logs.ops.internal/filebeat=enabled # 只给已批准的 wk01 打灰度采集标签。
kubectl -n observability rollout status daemonset/filebeat --timeout=180s # 等待 wk01 上的唯一 Filebeat Pod 就绪。
kubectl auth can-i list pods --as=system:serviceaccount:observability:filebeat -A # 验证 Filebeat 能读取 Pod 元数据,预期 yes。
kubectl auth can-i '*' '*' --as=system:serviceaccount:observability:filebeat -A # 验证账号没有通配高权限,预期 no。
真实输出样例:Logstash 有两个 Endpoint;wk01 仅一个 Filebeat;权限结果为 yes、no。 通过标准:TLS 没有报错,Kibana 可见灰度日志且无双采。 失败处理:无日志查 hostPath、CA、Service Endpoint;发现双采立即删除 Filebeat,不动业务。
3.1 灰度通过后逐节点放开与回退
执行机器:生产发布机。只有变更单记录了灰度结果、业务负责人确认无双采、日志平台负责人确认索引增长后才执行;每次只增加一个已批准 Worker。
# 先记录当前节点和 Filebeat 分布,作为扩容前基线。
kubectl get nodes -l logs.ops.internal/filebeat=enabled -o wide # 记录当前已采集节点清单与 IP,作为变更前基线。
kubectl -n observability get pod -l app=filebeat -o wide # 记录现有 Filebeat Pod 与对应节点。
# 例:批准 wk02 后才执行;确认无异常后再以同样方式处理 wk03。
kubectl label node wk02 logs.ops.internal/filebeat=enabled # 仅在 wk01 验收通过后纳入 wk02。
kubectl -n observability rollout status daemonset/filebeat --timeout=180s # 等待 wk02 的 Filebeat 就绪。
kubectl -n observability get daemonset filebeat # 核对 DESIRED、CURRENT、READY 与已标记节点数相同。
kubectl -n observability logs daemonset/filebeat --tail=80 # 查看最近 80 行发送日志,排除 TLS 和 output 错误。
# 回退只移除本手册新增采集器的节点标签;不会删除业务 Pod、PVC 或已有采集器。
kubectl label node wk02 logs.ops.internal/filebeat- # 回滚仅移除 wk02 的采集标签,使其 Filebeat Pod 被 DaemonSet 回收。
kubectl -n observability get pod -l app=filebeat -o wide # 确认 wk02 Filebeat 消失且其他灰度节点未受影响。
真实输出样例:扩容后 DaemonSet 的 DESIRED/CURRENT/READY 与已打标签节点数相同;Filebeat 日志持续出现 publish 成功且没有 TLS error。 通过标准:每增加一个节点,Discover 中该节点新日志出现一次且索引写入速率没有翻倍;回退后该节点 Filebeat Pod 消失,业务 Pod 数量不变。 失败处理:发现重复日志、业务抖动、TLS/权限错误或 ES 写入失败,立即移除刚增加节点的标签并保留 Filebeat/Logstash 日志、事件和时间范围供复盘。
4. 网页验收、备份、恢复、故障排查和回滚
执行机器:浏览器在授权运维电脑;快照在 ES 管理员终端。
4.1 仅暴露 Kibana 的 HTTPS 入口
执行机器:生产发布机。Elasticsearch 9200 和 Logstash 5044 始终只保留集群内部访问;入口复用第 0 节已经验收的 nginx IngressClass 和 internal-ca。
# 文件:22-kibana-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: prod-kibana
namespace: observability
spec:
ingressClassName: nginx
tls:
- hosts: ["kibana.ops.internal"]
secretName: kibana-example-com-tls
rules:
- host: kibana.ops.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: prod-kibana-kb-http
port: { number: 5601 }
配置详解(22-kibana-ingress.yaml):ingressClassName: nginx 指定已验收的 Nginx Ingress Controller;TLS 的 hosts、规则 host 和证书 SAN 都必须为 kibana.ops.internal,secretName 引用同名 HTTPS 证书。path: / 加 Prefix 覆盖 Kibana 所有路由,后端只转发到 ECK 创建的内部 Service prod-kibana-kb-http:5601。
配置作用:仅向授权用户暴露 Kibana 的 HTTPS 查询入口。 生效结果:ES 9200 与 Logstash 5044 始终无外部入口;浏览器流量经 Ingress 到 Kibana Ready Endpoint。 注意事项:证书错误优先检查 DNS、SAN 与 Secret,禁止用长期 NodePort 替代 HTTPS 入口。
# 先确认 ECK Service 和证书 Secret,再校验并创建入口。
kubectl -n observability get service prod-kibana-kb-http # 确认 ECK Kibana 内部 Service 存在。
kubectl -n observability get secret kibana-example-com-tls # 确认 HTTPS 证书 Secret 已存在。
kubectl apply --dry-run=server -f 22-kibana-ingress.yaml # 用 API Server 校验 IngressClass、Service 与字段。
kubectl apply -f 22-kibana-ingress.yaml # 创建唯一的 Kibana HTTPS 入口。
kubectl -n observability get ingress prod-kibana # 输出 Ingress 地址、host 与 TLS 配置。
kubectl -n observability get endpoints prod-kibana-kb-http # 确认后端有 Ready Kibana Endpoint。
真实输出样例:Ingress 有地址,prod-kibana-kb-http 有 Ready Endpoint,浏览器访问 https://kibana.ops.internal 不出现证书错误。 通过标准:外部仅能访问 Kibana HTTPS;kubectl get svc -n observability 中 ES 与 Logstash 均没有 LoadBalancer/NodePort。 失败处理:404 查 IngressClass、host 和 path;502 查 Kibana Endpoint;证书错误查 TLS Secret 和域名;禁止为了临时访问改成长期 NodePort。

截图只对应 Kibana Discover 的页面操作;截图中的课程 HTTP/NodePort 地址不能作为本生产 HTTPS 入口的验收证据,生产入口只按本节的 https://kibana.ops.internal 与证书检查结果验收。
网页操作:打开 https://kibana.ops.internal → Stack Management → Data Views → logs-k8s-prod-* → @timestamp → Discover → 最近 15 分钟 → 筛选 kubernetes.namespace: business-prod。 真实输出样例:Discover 命中数大于 0,包含 timestamp、namespace、pod、message。 通过标准:TLS 正常、可登录、只返回业务 Namespace 日志;公网无 ES 9200/Logstash 5044。 失败处理:按应用→Filebeat→Logstash→ES→Data View 排查,禁止重启全部组件。

下两张截图只对应 Management 与 Dev Tools 的只读查询操作;其中课程环境的地址或端口不用于证明生产网络入口。
网页操作(只读验证):在左侧 Management → Dev Tools 打开 Console,先执行 GET /_cluster/health、GET /_cat/indices/logs-k8s-prod-*?v;只读 API 返回正常后,才进入快照维护窗口。截图来自你的网站课程后台,不是示意图。

# 终端 A:临时访问 ES API;该命令保持前台运行,窗口关闭即撤销本地端口。
kubectl -n observability port-forward service/prod-logs-es-http 9200:9200 # 读取组件最近日志,定位采集、TLS 或输出异常。
# 终端 B:导出 CA,密码仅存在当前会话,先只读确认健康和已批准的快照仓库。
kubectl -n observability get secret prod-logs-es-http-ca-internal -o jsonpath='{.data.ca\.crt}' | base64 -d > ./prod-logs-ca.crt # 读取组件最近日志,定位采集、TLS 或输出异常。
read -s -p 'elastic password: ' ELASTIC_PASSWORD; echo # 静默读取当前会话密码,不回显且不写入命令历史。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" https://127.0.0.1:9200/_cluster/health?pretty # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs' -H 'Content-Type: application/json' -d '{"type":"fs","settings":{"location":"/mnt/es-snapshots","compress":true}}' # 在三台 ES 共用且受 path.repo 限制的目录注册唯一快照仓库。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" https://127.0.0.1:9200/_snapshot/prod-logs-cephfs?pretty # 读取刚注册的仓库,确认 location 固定为 /mnt/es-snapshots。
# 仓库存在且健康为 green 后创建快照并恢复到隔离前缀;绝不覆盖生产索引。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs/logs-k8s-prod-20260728?wait_for_completion=true' -H 'Content-Type: application/json' -d '{"indices":"logs-k8s-prod-*","include_global_state":false}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X POST 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs/logs-k8s-prod-20260728/_restore' -H 'Content-Type: application/json' -d '{"indices":"logs-k8s-prod-*","rename_pattern":"logs-k8s-prod-(.+)","rename_replacement":"restore-logs-k8s-prod-$1","include_global_state":false}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" 'https://127.0.0.1:9200/_cat/indices/restore-logs-k8s-prod-*?v' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
rm -f ./prod-logs-ca.crt # 清理当前发布机的临时文件或敏感会话变量。
unset ELASTIC_PASSWORD # 清理当前发布机的临时文件或敏感会话变量。
真实输出样例:健康接口为 green,仓库 JSON 可读,快照状态 SUCCESS,恢复索引以 restore-logs-k8s-prod- 开头。 通过标准:仓库由平台团队预先批准;恢复索引可查询,生产索引和业务 Pod 未变化。 失败处理:仓库不存在、健康非 green、快照非 SUCCESS 或恢复冲突时停止,禁止临时创建本地仓库、删除生产索引、PVC 或 Namespace。