
讲个真实感受在用 ECK 之前我在 K8S 里部署 Elastcsearch 集群一直是手动拼 StatefulSet 加 ConfigMap节点扩容要改一堆文件证书自己生成、自己挂载滚动升级更是提心吊胆生怕一个节点起不来整个集群就黄了。后来接触到 ECKElastic Cloud on Kubernetes相当于把整个 ES 集群生命周期管理全都交给了 Operator你只需要写一份 YAML 声明“我要一个 3 节点 ES 集群”剩下的事它自动处理。这篇文章我会从安装方式、YAML 字段拆解、实操流程到问题排查完整走一遍给正在研究 K8S ECK 的朋友一份能直接照着操作的参考。1. ECK 是什么为什么值得在 K8S 里折腾1.1 一个真实场景切入假设你现在要在一个已有的 K8S 集群里部署一套 Elasticsearch 集群按传统思路你要做这些事情手写 Service、StatefulSet、ConfigMap、Secret自己处理 PVC 和存储类然后写一堆初始化脚本去设置vm.max_map_count内核参数还要手动生成 TLS 证书并挂载到每个 Pod最后通过环境变量把节点间通讯地址配好。这一套下来普通运维至少需要半天而且容易出现低级错误比如节点发现配置写错、证书挂载路径不一致、内存限制和 JVM 堆设置冲突。ECK 解决的就是这个痛点。它是在 K8S 里以 Operator 模式运行的 Elastic 官方控制器你把 ES 集群的期望状态写成一个ElasticsearchCRD 资源比如指定版本、节点数量、存储大小、资源配额ECK 会自动帮你创建 StatefulSet、Service、ConfigMap、Secret并且实时监控集群状态节点挂了自动重建版本升级只需要改一个字段。1.2 ECK 的核心价值点第一个价值是声明式管理。K8S 本身就是声明式体系ECK 把 ES 集群的管理方式完全对齐到这个体系里你不再关心“如何创建某个 Pod”“如何更新某个配置”只关心“集群最终应该是什么样”。第二个价值是生命周期自动化。ECK 不只负责部署还负责后续的滚动升级、节点替换、证书轮换。比如 ES 从 8.13.4 升级到 8.14.0你只需要修改 YAML 里的 version 字段ECK 就会自动按“先启动新节点、迁移数据、再下线旧节点”的方式完成滚动升级这比手工操作 Safe 很多。第三个价值是安全默认开启。ECK 从 8.x 开始默认启用 Elasticsearch 安全特性自动生成自签 CA 和节点证书自动创建默认用户 elastic 的密码你不需要额外配置 xpack.security 相关参数。2. 环境准备与版本选择2.1 版本对应关系与前置要求ECK 2.x 版本要求 K8S 版本在 1.21 以上建议使用 1.25 以上的稳定版本。这里有一个容易混淆的点ECK Operator 的版本和 Elasticsearch 的版本不是同一个概念。ECK 2.12.1 可以管理 Elasticsearch 8.13.x可以管理 7.17.x也可以管理 6.8.x版本适配比较宽容但不代表所有版本的组合都经过官方完整测试生产环境建议去官方兼容性矩阵核对一下。我这次演示使用的是 ECK 2.12.1 搭配 Elasticsearch 8.13.4K8S 版本是 1.28。如果你用的是国内云厂商的托管 K8S 服务注意确认容器运行时是否为 containerd以及存储类是否可用这会影响后续 PVC 创建是否成功。2.2 需要准备好的关键参数在开始部署之前有几项基础信息需要确认。第一是存储类名称。ECK 创建的 ES 节点默认会申请 PVCPVC 的 storageClassName 如果没有在 YAML 里指定会用 K8S 集群的默认存储类。如果集群里没有默认存储类或者你想用特定的高性能云盘必须在 YAML 里显式指定。第二是资源配额。ES 是内存大户JVM 堆默认占容器内存的一半这意味着如果你给一个 ES Pod 设置 memory limit 为 8GiJVM 堆大小约为 4Gi。我建议在 YAML 里显式配置 ES 的ES_JAVA_OPTS比如-Xms4g -Xmx4g避免 JVM 自动探测不准确导致内存超卖。第三是内核参数。ES 需要vm.max_map_count至少为 262144很多 K8S 节点默认值是 65530。ECK 在启动 ES Pod 时会自动创建一个 initContainer 尝试修改这个参数但如果你用的是托管 K8S 或容器运行时权限受限这个修改会失败。官方推荐的替代方案是在 ES 配置里设置node.store.allow_mmap: false。注意如果你在 YAML 里不设置node.store.allow_mmap: false且 K8S 节点不允许修改vm.max_map_countES Pod 启动时很可能会报max virtual memory areas vm.max_map_count [65530] is too low错误直接 CrashLoopBackOff。这个坑我踩过好几次建议提前处理。3. 核心 YAML 逐段拆解3.1 安装 Operator 的 YAML安装 ECK Operator 有两种常见方式一种是直接用 kubectl apply 官方提供的 YAML 文件另一种是通过 Helm 安装。先看第一种这也是最符合本文主题“使用 YAML 安装”的方式。kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/crds.yaml kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/operator.yaml第一条命令安装所有 CRDCustomResourceDefinition包括 Elasticsearch、Kibana、ApmServer、Enterprisesearch、Beats、Agent 等资源的定义。第二条命令创建 operator 关联的 Namespace、ServiceAccount、ClusterRole、ClusterRoleBinding、Deployment。如果你用 Helm方式是这样的helm repo add elastic https://helm.elastic.co helm install elastic-operator elastic/eck-operator -n elastic-system --create-namespace两种方式本质相同Helm 更便于版本管理和参数覆盖kubectl apply 更直观适合学习 YAML 结构。Operator 本身需要较宽的 K8S 权限因为它要创建和管理多个 Namespace 下的 StatefulSet、Service、PVC、Secret 等资源。3.2 Elasticsearch YAML 详拆Operator 安装完成后核心操作就是编写 Elasticsearch 资源 YAML。下面是一套可以直接复用的配置我加了详细注释apiVersion: elasticsearch.k8s.elastic.co/v1 kind: Elasticsearch metadata: name: es-prod namespace: elastic-system spec: version: 8.13.4 # 快速启动模式适合测试环境生产环境不要开 # quickstart: true http: tls: selfSignedCertificate: disabled: false nodeSets: - name: master count: 3 config: node.roles: [master] node.store.allow_mmap: false volumeClaimTemplates: - metadata: name: elasticsearch-data spec: storageClassName: csi-disk accessModes: - ReadWriteOnce resources: requests: storage: 50Gi podTemplate: spec: containers: - name: elasticsearch resources: requests: memory: 8Gi cpu: 2 limits: memory: 8Gi env: - name: ES_JAVA_OPTS value: -Xms4g -Xmx4g - name: data count: 3 config: node.roles: [data, ingest] node.store.allow_mmap: false volumeClaimTemplates: - metadata: name: elasticsearch-data spec: storageClassName: csi-disk accessModes: - ReadWriteOnce resources: requests: storage: 200Gi podTemplate: spec: containers: - name: elasticsearch resources: requests: memory: 16Gi cpu: 4 limits: memory: 16Gi env: - name: ES_JAVA_OPTS value: -Xms8g -Xmx8g这份 YAML 我拆开讲一下关键字段。spec.version是必须的ECK 会根据这个版本自动拉取对应的 Elasticsearch 镜像。注意标签必须是完整的 ES 版本号不能只写到 8.13否则 operator 无法确定具体镜像。spec.nodeSets是节点组配置每个 nodeSet 对应一个状态集。我分成 master 组和 data 组这是一种比较经典的角色分离方式。master 节点负责集群元数据管理不存数据data 节点负责数据存储和检索。如果你的集群规模不大也可以把所有角色放一个 nodeSet 里。node.roles控制节点角色支持 master、data、ingest、ml、remote_cluster_client 等。注意早版本的 ES 有node.master: true这种配置方式8.x 之后推荐使用node.roles列表方式。node.store.allow_mmap: false前面提过是为了规避vm.max_map_count限制。如果你的节点已经设置了 262144 或以上可以不写这个配置保持默认的 trueES 性能会更好一点。volumeClaimTemplates定义 PVC 模板。这里的 metadata.name 不能随意写它代表 ES 数据目录的挂载名必须是elasticsearch-data这是 ECK 默认的数据卷名称。如果你改成别的名字Pod 启动后 ES 容器找不到数据目录会因为权限或路径错误启动失败。storageClassName 一定要提前确认集群里是否存在否则 PVC 会一直处于 Pending 状态。podTemplate是标准 Pod 模板你可以像写普通 Deployment 一样设置 resources、env、nodeSelector、affinity、tolerations 等字段。其中 ES_JAVA_OPTS 环境变量很重要我建议必须显式设置-Xms和-Xmx并且这两个值一般都设置为容器内存 limit 的一半。比如 limit 是 8GiJVM 堆就设 4g。这样做的原因是 ES 的堆外内存还需要存储 Lucene 索引缓存、网络通信缓冲等如果堆设得过大会因为缺少堆外内存而发生 OOM。3.3 Kibana YAML 详拆ES 集群部署后通常需要 Kibana 做可视化界面。对应 YAML 如下apiVersion: kibana.k8s.elastic.co/v1 kind: Kibana metadata: name: kibana-prod namespace: elastic-system spec: version: 8.13.4 count: 1 elasticsearchRef: name: es-prod http: tls: selfSignedCertificate: disabled: false podTemplate: spec: containers: - name: kibana resources: requests: memory: 1Gi cpu: 1 limits: memory: 2Gi关键字段是elasticsearchRefECK 会通过配置中的 name 去同 Namespace 下寻找对应的 Elasticsearch 资源自动把 Kibana 和 ES 集群关联起来。你不用手动配置 Kibana 的 elasticsearch.hosts 地址operator 会生成内部访问地址并注入环境变量。http.tls.selfSignedCertificate.disabled这里设置为 false意思是启用 ECK 自动生成的自签证书。如果你有自己的 CA也可以把 disabled 设为 true然后通过http.tls.certificateRef挂载自己的证书。4. 实操全流程从部署到访问4.1 安装 Operator 并验证状态先执行两条 kubectl apply 命令安装 operator。kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/crds.yaml kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/operator.yaml执行完成后等待 operator Pod 启动kubectl get pods -n elastic-system正常情况下会看到一个名为elastic-operator-controller-manager-xxx的 Pod 处于 Running 状态。如果一直 Pending大概率是节点资源不足或者镜像拉取失败可以用kubectl describe pod -n elastic-system pod-name查看具体原因。Operator 启动后它会监听 CRD 资源的变化。你可以用以下命令查看是否已经注册了 ES 相关的 CRDkubectl get crd | grep elastic输出会包含elasticsearch.k8s.elastic.co、kibana.k8s.elastic.co、apmserver.apm.k8s.elastic.co等。4.2 部署 Elasticsearch 集群把上面那段 Elasticsearch YAML 保存为es-prod.yaml然后执行kubectl apply -f es-prod.yaml查看 ES 集群状态kubectl get elasticsearch -n elastic-system输出里的 PHASE 字段会从 Empty 变为 Ready。如果需要查看更详细的状态可以用kubectl describe elasticsearch es-prod -n elastic-systemEvents 部分会显示 operator 的整个处理过程。等待 Pod 创建完成kubectl get pods -n elastic-system -l elasticsearch.k8s.elastic.co/cluster-namees-prod你会看到类似es-prod-master-0、es-prod-master-1、es-prod-master-2的 Pod每个 Pod 的命名规则是cluster-name-nodeSet-name-序号。这里有个观察点ECK 创建的 ES Pod 数量可能不会一次性全部出现。Operator 在启动一个新集群时会先启动 master 节点等 master 集群形成再启动 data 节点。所以如果你看到只有 master Pod 在运行data Pod 还没创建不要慌这是正常流程。4.3 部署 Kibana 并完成访问保存 Kibana YAML 为kibana-prod.yaml执行kubectl apply -f kibana-prod.yaml等待 Pod 就绪kubectl get pods -n elastic-system -l kibana.k8s.elastic.co/cluster-namekibana-prod访问 Kibana 前需要先拿到两个关键信息用户密码和 CA 证书。获取 elastic 用户密码kubectl get secret es-prod-es-elastic-user -n elastic-system -o go-template{{.data.elastic | base64decode}}注意 secret 的名称是cluster-name-es-elastic-user这是 ECK 固定生成的如果 cluster 名是 es-prodsecret 就是es-prod-es-elastic-user。获取 CA 证书kubectl get secret es-prod-es-http-certs-public -n elastic-system -o go-template{{index .data tls.crt | base64decode}} ca.crt如果你只是本地测试也可以直接把 Kibana 和 ES 的 http 服务通过 kubectl port-forward 暴露出来kubectl port-forward -n elastic-system svc/kibana-prod-kb-http 5601:5601然后浏览器访问https://localhost:5601。因为证书是自签的浏览器会提示不安全你可以在高级选项里选择继续访问或者把上面的 ca.crt 导入系统信任列表。4.4 配置持久化存储的注意点生产环境下持久化存储是最容易出问题的地方。ECK 创建的 PVC 名称格式是cluster-name-nodeSet-name-pod-name比如es-prod-data-es-prod-data-0。如果创建后 PVC 一直 Pending检查存储类是否存在以及云厂商是否配置了对应的存储驱动。kubectl get pvc -n elastic-system kubectl describe pvc pvc-name -n elastic-system如果是自建 K8S没有默认存储类你需要在 K8S 里先创建一个 StorageClass。以本地存储为例可以这样apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer然后把 ES YAML 里的storageClassName改成 local-storage。注意 no-provisioner 存储类不会动态创建 PV你需要预先准备好 PV或者使用支持动态供应的存储插件比如 csi-hostpath-driver。4.5 快速启动与快速关闭ECK 有一个很实用的功能叫做快速启动和快速关闭对应 Elasticsearch CRD 里的spec.quickstart字段。把它设置为 trueECK 会跳过一些生产环境的稳健性检查比如不要求配置持久化存储、不要求节点必须分配在不同可用区部署速度会快很多适合开发测试环境。对应的关闭方式是在 ES 集群不需要跑的时候执行kubectl delete elasticsearch es-prod -n elastic-system但注意这个操作会删除 PVC如果你的存储类使用了 Delete 回收策略数据会一起被删掉。如果只是想停服务又保留数据可以先把节点数改为 0或者直接 scale statefulset但 ECK 的声明式模型不推荐这么干你改了 operator 的期望状态它很快会帮你改回来。正确的暂停方式是给 Elasticsearch 资源加注解elasticsearch.k8s.elastic.co/pause: true。5. 常见问题与排查实录5.1 问题速查表现象可能原因排查方向ES Pod 一直 Pending节点资源不足或调度约束不满足查看 pod describe检查 CPU、内存 requests、nodeSelector、taint/tolerationPVC 一直 Pending存储类不存在或存储驱动异常查看 pvc describe确认 storageClassName 是否有效ES Pod CrashLoopBackOffvm.max_map_count 不足或 JVM 堆配置不合理kubectl logs 查看 ES 启动日志设置 allow_mmapfalse 或调整 ES_JAVA_OPTS集群 PHASE 一直红/黄分片未分配、磁盘不足、节点掉了kubectl get elasticsearch 查看状态进入 ES 容器执行 _cluster/health访问时证书错误ECK 自动生成的自签证书浏览器不信任获取 CA 证书导入信任树或者配置自定义证书Kubectl 查看 CRD 资源时权限不足当前 ServiceAccount 缺少对应资源权限检查 K8S RBAC为只读用户添加 ES/Kibana CRD 的 get/list 权限5.2 几个典型的排查过程场景一vm.max_map_count 不足导致 CrashLoopBackOffES Pod 状态变为 CrashLoopBackOff查看日志时会看到类似max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]的报错。如果你无法在节点层面修改内核参数最直接的解决办法是在 CRD 的 config 中加上node.store.allow_mmap: false然后在 ES 容器里确认生效kubectl exec -it es-prod-master-0 -n elastic-system -- curl -s https://localhost:9200/_nodes/process?pretty -u elastic:$(密码) -k | grep -A 3 mlockall场景二ES 集群一直显示绿色但数据节点没创建如果只有 master 节点运行data 节点始终没有 Pod原因通常是 operator 在等待 master 集群健康后再创建 data 节点。你可以使用kubectl get elasticsearch查看状态如果 PHASE 是 Ready但 data 节点还是 0 个就检查 data nodeSet 的存储类是否填写错误。场景三Kibana 页面打不开先确认 Kibana Pod 是否 Ready然后用 port-forward 提供服务。如果访问时报 502 或连接拒绝检查 Kibana 是否成功连接到 ES。可以查看 Kibana Pod 日志kubectl logs kibana-prod-kb-0 -n elastic-system常见错误是savedobjects-service无法连接 ES这通常是因为 elasticsearchRef 指向的 Elasticsearch 资源名称写错或者 ES 集群还没完全 Ready。场景四只读用户如何看 ECK 资源有朋友问过“K8S 只开只读权限的用户怎么查看 ES 集群状态”。这里要区分两层权限K8S RBAC 和 ES 自身的 RBAC。如果只是让某个人查看 ECK 管理的 ES 集群资源、日志、Pod 状态在 K8S 里创建一个 Role只授予 get/list/watch 权限即可不需要 admin。5.3 关于 CPU 限制和性能的提示如果你在 podTemplate 里给 ES 设置了过小的 CPU limitCPU 使用率达到上限后会出现 CPU Throttling导致 ES 的查询延迟变高、GC 停顿变长。ES 对 CPU 非常敏感尤其是 master 节点如果因为 CPU 限流导致集群选举超时会出现节点间通信异常。我的建议是 master 节点不要设置 CPU limit或者设置一个高于实际需求的 limitdata 节点可以设置 limit但要充分评估业务峰值。5.4 关于内存和 JVM 的补充我再强调一遍内存配置。ES 容器内存需求分为 JVM 堆和堆外内存两部分。JVM 堆用于索引、查询、聚合等核心操作大小建议为容器内存的 50% 左右堆外内存用于 Lucene 索引文件缓存、网络发送缓冲、压缩和解压等。如果你把 8Gi 的 limits 全部给了 JVM 堆系统报 OOM 几乎是必然的。一个合理的组合是resources: limits: memory: 16Gi requests: memory: 16Gi env: - name: ES_JAVA_OPTS value: -Xms8g -Xmx8g5.5 升级与回滚的实战经验ECK 升级 ES 集群版本非常简单修改 YAML 里的 version 字段然后 apply 即可。但我强烈建议在升级前做一次快照备份ECK 本身支持自动创建快照仓库也可以用传统方式调用_snapshotAPI 手动备份。升级过程中如果新版本启动失败ECK 会自动保留旧版本节点不会立刻删除旧 Pod。你可以通过以下命令查看升级进度kubectl get pods -n elastic-system -l elasticsearch.k8s.elastic.co/cluster-namees-prod -o wide如果升级失败需要回滚把 version 字段改回原版本重新 apply。注意回滚时 ES 集群需要重新执行数据迁移耗时取决于数据量可能要几分钟到几小时期间集群状态会是黄色。5.6 离线环境安装 ECK 的补充如果 K8S 集群无法访问外网需要提前把镜像下载到内网仓库。ECK operator 镜像地址是docker.elastic.co/eck/eck-operator:2.12.1ES 镜像地址是docker.elastic.co/elasticsearch/elasticsearch:8.13.4Kibana 镜像地址是docker.elastic.co/kibana/kibana:8.13.4。先在能联网的机器上 docker pull然后 docker tag 和 docker push 到内网 Harbor 或其他仓库。同时安装 operator 的 YAML 文件也要提前下载到本地再 kubectl apply 本地文件。离线环境下Elasticsearch YAML 里可以通过spec.image字段指定私有镜像仓库地址覆盖默认镜像。例如spec: version: 8.13.4 image: registry.internal.com/elastic/elasticsearch:8.13.4写在最后的一点体会ECK 这套方案我用下来最深的感受是它把 ES 集群从“需要运维盯着的宠物”变成了“可以随时重建的牲口”但对 YAML 编写者的要求并没有降低反而提高了。因为你写的不再是一个 Pod 的配置而是整个集群的期望状态任何一个字段的失误都会被 operator 放大成集群级别的故障。我个人的建议是所有 CRD 配置先在小环境里验证一遍尤其是存储类名称、JVM 参数、节点角色这三个地方生产环境最容易在这三个地方踩坑。另外虽然 ECK 自带很多自动化能力但 ES 的运维知识还是不能丢比如分片数量规划、索引生命周期管理、快照备份策略这些仍然需要你手动去设计和维护。