主题
国内源部署高可用 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 + StorageClassnfs-elasticsearch) - 镜像源:国内代理
elastic.m.daocloud.io→ 同步至内网 Harbor192.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 Operator | 3.5.0 | 192.168.122.156:30000/elastic/eck-operator:3.5.0 |
| Elasticsearch | 9.4.4 | 192.168.122.156:30000/elastic/elasticsearch:9.4.4 |
| Kibana | 9.4.4 | 192.168.122.156:30000/elastic/kibana:9.4.4 |
| Logstash | 9.4.4 | 192.168.122.156:30000/elastic/logstash:9.4.4 |
| nfs-subdir-external-provisioner | chart 4.0.18 / app v4.0.2 | 192.168.122.156:30000/sig-storage/nfs-subdir-external-provisioner:v4.0.2 |
| ECK helm chart | 3.5.0 | https://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 官方 repo | https://helm.elastic.co | ECK 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.43.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-elasticsearch3.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=greenECK 默认 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=greenIngressRoute 必须带
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=green4. 验证
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 green5. 访问方式
| 场景 | 地址 |
|---|---|
| 集群内应用 | 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-rootIngressRoute:Host(ai-ear.cn) && Path(/)经redirectRegexmiddleware 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.toml 把 host-tcp-9003 的 localIP/localPort 直接改为 192.168.122.31/80 并 systemctl restart frpc,即可去掉转发进程。
⚠️ 暴露公网后任何能访问 ai-ear.cn:9003 的人都能看到 Kibana 登录页,务必保管好 elastic 密码;如需收紧可在公网 nginx 该 server 块加 IP 白名单。
7. 生产注意事项
- NFS 仅适合 UAT/测试:Elastic 官方不推荐 ES 数据目录放 NFS(延迟与一致性风险),本文
node.store.allow_mmap=false是 NFS 下的必要缓解。生产环境改用本地 SSD(local-path-provisioner / longhorn / TopoLVM),并拆分专用 master(3 小节点)与 data 节点; - 堆内存:ECK 按容器 limits 的 50% 自动配堆,调内存只改 resources,不要手设
ES_JAVA_OPTS; - 升级:改 CR 的
spec.version(如 9.4.4→9.5.x)并同步镜像到 Harbor,ECK 自动滚动升级(逐个节点,先迁分片); - 扩缩容:改
count即可;缩容时 ECK 自动迁移分片并维护投票配置,勿直接删 StatefulSet; - 备份:生产配 snapshot 仓库(S3/MinIO 或 NFS
path.repo),ECK 支持通过 keystore 注入仓库凭据; - PDB:ECK 默认建 PodDisruptionBudget(maxUnavailable=1),节点维护 drain 时集群不中断。
8. 常见问题
| 现象 | 原因与处理 |
|---|---|
| Pod ImagePullBackOff,镜像名是 docker.elastic.co | 集群 mirror "*" 强制走 Harbor,镜像未同步或 CR 没改 spec.image |
ECK status 显示 yellow 但 _cluster/health 是 green | ECK 状态聚合有滞后,等 1~2 个 reconcile 周期;持续 yellow 看 operator 日志 |
| ES 起不来,日志报 mmap/store 相关 I/O 错 | NFS 存储未加 node.store.allow_mmap: false |
| Pod 一直 Pending:pod has unbound immediate PersistentVolumeClaims | provisioner 未就绪或 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