Skip to content

国内源部署高可用 ELK(ECK Operator)手册

  • 日期:2026-08-05
  • 集群:uat1(RKE2 v1.35.6,API 192.168.122.31:6443,Rancher 纳管)
  • 方案:ECK(Elastic Cloud on Kubernetes)Operator 管理完整 ELK:3 节点 Elasticsearch + Kibana + Logstash
  • 存储:NFS 192.168.122.1:/mnt/xfs/rancher/es(专用 provisioner + StorageClass nfs-elasticsearch)
  • 镜像源:国内代理 elastic.m.daocloud.io → 同步至内网 Harbor 192.168.122.156:30000/elastic

1. 架构与版本

text
┌─ elastic-system ─────────────────────────┐
│ elastic-operator-0 (ECK Operator,1 副本) │  监听全集群 ES/Kibana CR
└──────────────────────────────────────────┘
                │ reconcile
┌─ elasticsearch ──────────────────────────┐
│ elasticsearch-es-master-data-0/1/2       │  3 节点,全角色(master+data+ingest)
│ 反亲和强制分散到不同 K8s 节点            │
│ PVC ×3 → nfs-elasticsearch → NFS 服务器  │
│ svc/elasticsearch-es-http:9200 (HTTPS)   │
│ kibana-kb ×1 → Traefik /kibana 对外      │
│ logstash-ls ×1 (http:8080 → ES)          │
│ elasticsearch-nfs-provisioner ×1         │
└──────────────────────────────────────────┘
组件版本镜像(Harbor 内网地址)
ECK Operator3.5.0192.168.122.156:30000/elastic/eck-operator:3.5.0
Elasticsearch9.4.4192.168.122.156:30000/elastic/elasticsearch:9.4.4
Kibana9.4.4192.168.122.156:30000/elastic/kibana:9.4.4
Logstash9.4.4192.168.122.156:30000/elastic/logstash:9.4.4
nfs-subdir-external-provisionerchart 4.0.18 / app v4.0.2192.168.122.156:30000/sig-storage/nfs-subdir-external-provisioner:v4.0.2
ECK helm chart3.5.0https://helm.elastic.co(国内可直连)

为什么选 ECK 而不是 elastic helm chart / StatefulSet 手写:ECK 是 Elastic 官方推荐的 K8s 部署方式,自动处理 TLS 证书、节点发现、滚动升级、扩缩容时的分片迁移与安全投票配置,HA 集群生命周期管理成本最低。

2. 前置条件

  • kubectl 能操作集群:export KUBECONFIG=~/.kube/config-122.31;
  • 一台能上公网的机器(本文档机):装 skopeo,能连 Harbor;
  • Harbor 管理员账号(项目需手工创建一次);
  • 集群节点 containerd 已配置 mirror "*" → http://192.168.122.156:30000,所有镜像必须先进 Harbor,否则拉取失败。

3. 部署步骤

全流程已固化为 deploy.sh,下文为逐步说明。

3.1 国内镜像源说明

地址用途
DaoCloud elastic 代理(本文使用)elastic.m.daocloud.io代理 docker.elastic.co,拉 ECK/ES/Kibana 镜像
DaoCloud dockerhub 代理docker.m.daocloud.io拉 docker.io 镜像
helm 官方 repohttps://helm.elastic.coECK chart,国内实测可直连

验证镜像可拉:

bash
skopeo inspect --tls-verify=false docker://elastic.m.daocloud.io/eck/eck-operator:3.5.0 --format '{{.Digest}}'
skopeo inspect --tls-verify=false docker://elastic.m.daocloud.io/elasticsearch/elasticsearch:9.4.4 --format '{{.Digest}}'

3.2 同步镜像到内网 Harbor

bash
# ① 创建项目(一次性)
curl -s -X POST -u 'admin:xxx' -H 'Content-Type: application/json' \
  -d '{"project_name":"elastic","metadata":{"public":"true"}}' \
  'http://192.168.122.156:30000/api/v2.0/projects'

# ② 登录并同步
skopeo login --tls-verify=false -u admin -p '<HARBOR密码>' 192.168.122.156:30000

skopeo copy --src-tls-verify=false --dest-tls-verify=false \
  docker://elastic.m.daocloud.io/eck/eck-operator:3.5.0 \
  docker://192.168.122.156:30000/elastic/eck-operator:3.5.0

skopeo copy --src-tls-verify=false --dest-tls-verify=false \
  docker://elastic.m.daocloud.io/elasticsearch/elasticsearch:9.4.4 \
  docker://192.168.122.156:30000/elastic/elasticsearch:9.4.4   # 约 890MB,内网几分钟

# Kibana / Logstash 同样同步
skopeo copy --src-tls-verify=false --dest-tls-verify=false \
  docker://elastic.m.daocloud.io/kibana/kibana:9.4.4 \
  docker://192.168.122.156:30000/elastic/kibana:9.4.4
skopeo copy --src-tls-verify=false --dest-tls-verify=false \
  docker://elastic.m.daocloud.io/logstash/logstash:9.4.4 \
  docker://192.168.122.156:30000/elastic/logstash:9.4.4

3.3 安装 ECK Operator

bash
# chart 国内可直连下载;如失败也可先 helm pull 再传
curl -skLO 'https://helm.elastic.co/helm/eck-operator/eck-operator-3.5.0.tgz'

kubectl create namespace elastic-system
helm install elastic-operator ./eck-operator-3.5.0.tgz -n elastic-system \
  --set image.repository=192.168.122.156:30000/elastic/eck-operator

kubectl -n elastic-system rollout status sts/elastic-operator
kubectl get crd elasticsearches.elasticsearch.k8s.elastic.co   # CRD 就绪

3.4 准备 NFS 存储(专用目录 /mnt/xfs/rancher/es)

集群惯例:每个中间件一个独立 NFS provisioner(参考 mysql/redis),ES 同理。

bash
# ① 在任一能挂载 NFS 的机器上创建目录(一次性)
mount -t nfs 192.168.122.1:/mnt/xfs/rancher /tmp/nfs-mnt
mkdir -p /tmp/nfs-mnt/es && umount /tmp/nfs-mnt

# ② helm 安装 ES 专用 provisioner(chart 本地缓存或官方 repo)
helm install elasticsearch-nfs-provisioner \
  nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  -n elasticsearch --create-namespace \
  --set nfs.server=192.168.122.1 \
  --set nfs.path=/mnt/xfs/rancher/es \
  --set image.repository=192.168.122.156:30000/sig-storage/nfs-subdir-external-provisioner \
  --set image.tag=v4.0.2 \
  --set storageClass.name=nfs-elasticsearch \
  --set storageClass.provisionerName=cluster.local/elasticsearch-nfs-provisioner \
  --set storageClass.archiveOnDelete=false

kubectl get sc nfs-elasticsearch

3.5 部署 3 节点 HA Elasticsearch

完整清单见 elasticsearch-cluster.yaml,核心点:

yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: elasticsearch
  namespace: elasticsearch
spec:
  version: 9.4.4
  image: 192.168.122.156:30000/elastic/elasticsearch:9.4.4   # 关键:指向 Harbor
  nodeSets:
  - name: master-data
    count: 3
    config:
      node.store.allow_mmap: false        # NFS 存储必须关 mmap
    podTemplate:
      spec:
        affinity:                          # 一机一拍,真 HA
          podAntiAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  elasticsearch.k8s.elastic.co/cluster-name: elasticsearch
              topologyKey: kubernetes.io/hostname
        containers:
        - name: elasticsearch
          resources:                       # ECK 自动按 limits 50% 配堆(2Gi→1G heap)
            requests: {memory: 2Gi, cpu: 500m}
            limits:   {memory: 2Gi, cpu: "2"}
    volumeClaimTemplates:
    - metadata: {name: elasticsearch-data}
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: nfs-elasticsearch
        resources: {requests: {storage: 20Gi}}
bash
kubectl apply -f elasticsearch-cluster.yaml
kubectl -n elasticsearch get elasticsearch -w   # 等 PHASE=Ready HEALTH=green

ECK 默认 Basic license(免费),安全(HTTPS + elastic 账号)开箱即用;vm.max_map_count 由 ECK initContainer 自动设置,无需人工调内核。

3.6 部署 Kibana(可视化)

清单 kibana.yaml,关键点:elasticsearchRef 指向 ES 集群自动注入连接配置;集群内经 Traefik 对外,关 Kibana 自身 TLS、server.basePath=/kibana 使其工作在路径前缀下:

bash
kubectl apply -f kibana.yaml   # 含 Kibana CR + Traefik IngressRoute
kubectl -n elasticsearch get kibana -w   # 等 HEALTH=green

IngressRoute 必须带 kubernetes.io/ingress.class: traefik 注解(本集群 traefik 开了 ingressClass 过滤)。

3.7 部署 Logstash(采集管道)

清单 logstash.yaml,关键点:elasticsearchRefs 自动注入 ECK_ES_HOSTS/USER/PASSWORD/SSL_CERTIFICATE_AUTHORITY 环境变量,pipeline 里直接引用;示例管道为 HTTP 输入(8080)→ 写 logstash-* 索引:

bash
kubectl apply -f logstash.yaml
kubectl -n elasticsearch get logstash -w   # 等 HEALTH=green

4. 验证

bash
# ① ECK 层
kubectl -n elasticsearch get elasticsearch
# NAME            HEALTH   NODES   VERSION   PHASE   AGE
# elasticsearch   green    3       9.4.4     Ready   9m

# ② 取 elastic 密码(ECK 自动生成)
PASS=$(kubectl -n elasticsearch get secret elasticsearch-es-elastic-user \
  -o jsonpath='{.data.elastic}' | base64 -d)

# ③ 集群健康与节点角色
kubectl -n elasticsearch port-forward svc/elasticsearch-es-http 19200:9200 &
curl -sk -u "elastic:$PASS" 'https://127.0.0.1:19200/_cluster/health?pretty'
# status: green, number_of_nodes: 3, unassigned_shards: 0
curl -sk -u "elastic:$PASS" 'https://127.0.0.1:19200/_cat/nodes?v&h=name,node.role,master,heap.max'
# 3 个节点均为 cdfhilmrstw 全角色,heap 1gb

# ④ 读写测试(主+副本 2/2 成功即 HA 生效)
curl -sk -u "elastic:$PASS" -X POST 'https://127.0.0.1:19200/eck-test/_doc/1' \
  -H 'Content-Type: application/json' -d '{"msg":"hello eck"}'
# {"result":"created","_shards":{"total":2,"successful":2,"failed":0}}

# ⑤ 数据落盘确认(NFS 服务器侧可见 3 个 PVC 目录)
ls /mnt/xfs/rancher/es/
# elasticsearch-elasticsearch-data-elasticsearch-es-master-data-{0,1,2}-pvc-*

# ⑥ Pod 分散性
kubectl -n elasticsearch get pods -o wide | grep master-data   # 3 个不同 K8s 节点

# ⑦ Kibana:经 Traefik 访问(浏览器打开 http://192.168.122.31/kibana/)
curl -sk -o /dev/null -w '%{http_code}\n' 'http://192.168.122.31/kibana/api/status'   # 200

# ⑧ Logstash 管道端到端:发一条日志 → ES 出现 logstash-* 索引
kubectl -n elasticsearch port-forward svc/logstash-ls-http-input 18080:8080 &
curl -X POST 'http://127.0.0.1:18080/' -H 'Content-Type: application/json' \
  -d '{"message":"elk pipeline test","level":"info"}'
sleep 15
curl -sk -u "elastic:$PASS" "https://127.0.0.1:19200/_cat/indices/logstash-*?v&h=index,docs.count,health"
# logstash-2026.08.05   3   green

5. 访问方式

场景地址
集群内应用https://elasticsearch-es-http.elasticsearch.svc:9200(HTTPS,自签 CA)
集群内跳过证书校验curl -sk(UAT 可接受;正规做法挂 elasticsearch-es-http-certs-public secret 里的 ca.crt)
集群外临时kubectl -n elasticsearch port-forward svc/elasticsearch-es-http 19200:9200
Kibana(公网)https://ai-ear.cn:9003/(自动 302 到 /kibana/,账号同 elastic)
Kibana(内网)http://<任一节点IP>/kibana/(如 http://192.168.122.31/kibana/)
Logstash HTTP 输入集群内 logstash-ls-http-input.elasticsearch.svc:8080 POST JSON/普通文本
账号elastic / secret elasticsearch-es-elastic-user

需要经 Traefik 对外暴露时,参考《Traefik(RKE2 内置)HTTP/HTTPS 配置、原理与高并发生产落地手册》建 IngressRoute,注意后端是 HTTPS 需配 ServersTransport(scheme: https + insecureSkipVerify 或挂 CA)。

6. Kibana 公网暴露(ai-ear.cn:9003)

链路(全部复用既有设施,集群侧零改动):

text
浏览器 https://ai-ear.cn:9003/
  → 公网 nginx(8.153.84.140,TLS 终结,已有 server 块) → 127.0.0.1:19003
  → frps → frpc(既有 host-tcp-9003 映射) → 文档机 127.0.0.1:9003
  → tcp-forward.py(用户态转发,~/kibana-forward/) → 192.168.122.31:80(Traefik)
  → IngressRoute(Host=ai-ear.cn) → Kibana

集群侧配置(kibana.yaml):

  • Kibana server.basePath=/kibana + 不配 publicBaseUrl(相对链接,内外网入口通吃);
  • 原有 PathPrefix(/kibana) 路由不带 Host 限制,天然同时服务内网 IP 与公网域名;
  • 新增 kibana-public-root IngressRoute:Host(ai-ear.cn) && Path(/)redirectRegex middleware 302 到 /kibana/;
  • IngressRoute 必须带 kubernetes.io/ingress.class: traefik 注解。

转发进程:无 root 权限改不了 frpc,故用 ~/kibana-forward/tcp-forward.py(nohup 常驻)监听 127.0.0.1:9003 转发到节点 80。重启后需手工拉起;有 sudo 时可改 /etc/frp/frpc.tomlhost-tcp-9003localIP/localPort 直接改为 192.168.122.31/80systemctl restart frpc,即可去掉转发进程。

⚠️ 暴露公网后任何能访问 ai-ear.cn:9003 的人都能看到 Kibana 登录页,务必保管好 elastic 密码;如需收紧可在公网 nginx 该 server 块加 IP 白名单。

7. 生产注意事项

  1. NFS 仅适合 UAT/测试:Elastic 官方不推荐 ES 数据目录放 NFS(延迟与一致性风险),本文 node.store.allow_mmap=false 是 NFS 下的必要缓解。生产环境改用本地 SSD(local-path-provisioner / longhorn / TopoLVM),并拆分专用 master(3 小节点)与 data 节点;
  2. 堆内存:ECK 按容器 limits 的 50% 自动配堆,调内存只改 resources,不要手设 ES_JAVA_OPTS;
  3. 升级:改 CR 的 spec.version(如 9.4.4→9.5.x)并同步镜像到 Harbor,ECK 自动滚动升级(逐个节点,先迁分片);
  4. 扩缩容:改 count 即可;缩容时 ECK 自动迁移分片并维护投票配置,勿直接删 StatefulSet;
  5. 备份:生产配 snapshot 仓库(S3/MinIO 或 NFS path.repo),ECK 支持通过 keystore 注入仓库凭据;
  6. PDB:ECK 默认建 PodDisruptionBudget(maxUnavailable=1),节点维护 drain 时集群不中断。

8. 常见问题

现象原因与处理
Pod ImagePullBackOff,镜像名是 docker.elastic.co集群 mirror "*" 强制走 Harbor,镜像未同步或 CR 没改 spec.image
ECK status 显示 yellow 但 _cluster/health 是 greenECK 状态聚合有滞后,等 1~2 个 reconcile 周期;持续 yellow 看 operator 日志
ES 起不来,日志报 mmap/store 相关 I/O 错NFS 存储未加 node.store.allow_mmap: false
Pod 一直 Pending:pod has unbound immediate PersistentVolumeClaimsprovisioner 未就绪或 NFS 目录不存在,查 elasticsearch-nfs-provisioner Pod 日志
helm 拉 chart 失败(github EOF)用本机缓存 ~/.cache/helm/repository/nfs-subdir-external-provisioner-4.0.18.tgz,ECK chart 直连 helm.elastic.co 下载

9. 卸载

bash
kubectl -n elasticsearch delete kibana kibana
kubectl -n elasticsearch delete logstash logstash
kubectl -n elasticsearch delete ingressroute kibana
kubectl -n elasticsearch delete elasticsearch elasticsearch   # ECK 级联删 StatefulSet/SVC/PVC(PVC 随 SC Delete 策略清 NFS 目录)
helm uninstall elasticsearch-nfs-provisioner -n elasticsearch
kubectl delete sc nfs-elasticsearch
helm uninstall elastic-operator -n elastic-system
kubectl delete ns elasticsearch elastic-system
kubectl get crd | grep elastic.co | awk '{print $1}' | xargs kubectl delete crd   # 彻底清 CRD