主题
国内源部署高可用 Elasticsearch(ECK Operator)手册
- 日期:2026-08-05
- 集群:uat1(RKE2 v1.35.6,API
192.168.122.31:6443,Rancher 纳管) - 方案:ECK(Elastic Cloud on Kubernetes)Operator 管理 3 节点 Elasticsearch 集群
- 存储: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) │
│ 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 |
| 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:<HARBOR密码>' -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,内网几分钟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-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 自动设置,无需人工调内核。
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 节点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 |
| 账号 | elastic / secret elasticsearch-es-elastic-user |
需要经 Traefik 对外暴露时,参考《Traefik(RKE2 内置)HTTP/HTTPS 配置、原理与高并发生产落地手册》建 IngressRoute,注意后端是 HTTPS 需配 ServersTransport(
scheme: https+insecureSkipVerify或挂 CA)。
6. 生产注意事项
- 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 时集群不中断。
7. 常见问题
| 现象 | 原因与处理 |
|---|---|
| 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 下载 |
8. 卸载
bash
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