1. 摘掉 9200 的证书之前,先把 ECK 那三层 TLS 分清楚
Kubernetes 上用 ECK(Elastic Cloud on Kubernetes)部署 Elasticsearch 7.17,跑内网测试环境,客户端一堆:Java 服务、Logstash、Filebeat、还有几个同事本地 curl。每次接一个新客户端,第一件事就是问「CA 证书在哪」「为什么连上去报 PKIX path building failed」。折腾两轮之后,大家的共识就变成一句话:能不能把 TLS 关了,就用账号密码连?
这个诉求本身没毛病,但动手之前得先知道你要关的是哪一层。ECK 从 1.0 开始就把 TLS 当默认配置塞进去了,而且不是一层,是三层,混在一起改很容易把集群搞成 CrashLoopBackOff 还找不到原因。
1.1 HTTP 层、transport 层、operator 管理面,各管各的
第一层是HTTP 层,也就是 9200 端口,你 curl、Kibana、Logstash 连的都是它。这一层的证书由 ECK 自动生成一对自签证书,放在<集群名>-es-http-certs-public这类 Secret 里,同时挂进 Pod 的/usr/share/elasticsearch/config/http-certs/。客户端要跳过校验,要么关校验,要么把 CA 塞进 truststore。这层是可以干净关掉的,CRD 里有对应字段,后面详细说。
第二层是transport 层,9300 端口,节点之间互相通信、选主、分片复制都走它。这层的证书由 operator 生成在<集群名>-es-transport-certs里。注意,这一层跟你的客户端完全没有关系——外部客户端永远不会连 9300。很多人以为「关 TLS」要把 9300 也关掉,其实纯属多此一举,而且 ECK 的 CRD 里根本没给你留关闭 transport TLS 的开关。
第三层是operator 自己的管理面,包括它跟 API Server 之间的 validating webhook 证书。这层跟 Elasticsearch 集群的 TLS 是两码事,你改 CR 里的http.tls字段不会影响到它。
所以真正要动的是第一层。我在实际项目里踩过一次坑:某个同学看到报错说证书问题,直接在nodeSets[].config里手写了xpack.security.transport.ssl.enabled: false,想着「一次性全关了省事」。结果 operator 重建 StatefulSet 的时候,把它自己生成的 transport 配置又合并回去,最终 Pod 里的elasticsearch.yml出现了两组互相矛盾的配置,ES 起不来,日志里刷的是节点间握手失败。这个坑后面第 5 节还会展开。
1.2 关掉 9200 的 TLS 之后,谁受益谁遭殃
先把账算清楚。受益的一方很明确:所有走 REST 接口的外部客户端,包括 Java 的 RestHighLevelClient、Python 的 elasticsearch-py、Logstash 的 elasticsearch output、Filebeat 的 output.elasticsearch、curl、Kibana。它们不用再加载 CA,不用配ssl.certificate_authorities,连接串从https://改成http://就完事。
遭殃的一方也要说清楚。明文传输意味着同 VLAN 内任何人抓包就能拿到你的 Basic Auth 头,也就是拿到了账号密码。这不是危言耸听,内网横向移动的第一步就是抓明文凭据。所以这个方案只适合两种场景:一是纯测试/开发集群,里面没有真实业务数据;二是集群前面有明确的网络隔离,比如只有特定的几个 Pod 能访问到 9200,且这个网段本身是可信的。
我个人的判断标准很粗暴:只要集群里有真实用户数据或者生产流量,就不会关 HTTP TLS。测试环境怎么折腾都行,生产环境关 TLS 省下的那点配置成本,远不够后面出事之后的补救成本。
1.3 一个容易被忽略的连带影响:Kibana 的地址协议
ECK 管 Kibana 的时候,是通过 association 机制自动注入elasticsearch.hosts的,你不允许在 Kibana 的spec.config里手写这个字段——写了会被 operator 的校验拦住。那么它注入的是http://还是https://?这取决于 operator 读到的 ES CR 状态。
在 ECK 2.x 里,operator 会判断 ES 的 HTTP TLS 是否启用,然后选择对应的 scheme。也就是说,你把 ES 的http.tls.selfSignedCertificate.disabled设成true之后,Kibana 的 association 会自动跟着变成 http。这一点实测是好的,但前提是你的 operator 版本足够新、且 ES 集群状态已经 Reconciliation 完成。如果改完 CR 立刻看 Kibana 的日志,可能会看到短暂的连接失败,等 operator 走完一轮就好。
2. operator 版本先钉死:能认 7.17 的只有 ECK 2.x 这条线
这一节的内容是我觉得整篇文章里最值钱的:版本选错,后面全是白工。
2.1 兼容矩阵:ECK 3.x 不认 7.17
ECK 的版本策略跟 Elasticsearch 大版本是绑定的。2.x 这条线覆盖了 Elasticsearch 7.17 一直到 8.x 的相当一部分版本,而 3.0 之后官方把 7.x 的支持移除了,只面向 8.16 以上和 9.x。
这意味着什么?如果你图省事,直接kubectl apply了官网首页给的「最新版」operator.yaml,很可能装上去的是 3.x。这时候你提交一个version: 7.17.25的 Elasticsearch CR,operator 会拒绝,或者在 status 里挂一个 unsupported version 的 condition,Pod 根本不会被创建。
所以第一步不是写 CR,是把 operator 的版本号写进你的部署脚本里,当成一个常量管理。我一般会这么干:
# 锁死 operator 版本,别用 latest ECK_VERSION="2.16.0" OPERATOR_NS="elastic-system" kubectl create namespace ${OPERATOR_NS} --dry-run=client -o yaml | kubectl apply -f - # 先装 CRD,再装 operator,顺序不能反 kubectl apply -f https://download.elastic.co/downloads/eck/${ECK_VERSION}/crds.yaml kubectl apply -f https://download.elastic.co/downloads/eck/${ECK_VERSION}/operator.yaml装完确认一下:
kubectl -n elastic-system get pods kubectl -n elastic-system logs -f statefulset.apps/elastic-operator --tail=50正常情况下你会看到一个elastic-operator-0处于 Running,日志里没有持续刷 error。如果 Pod 一直起不来,八成是 RBAC 或者 webhook 证书的问题,看日志里的failed to create webhook之类的关键字。
2.2 CRD 和 operator 的顺序,以及 webhook 那点事
上面脚本里我特意把crds.yaml和operator.yaml分成两条命令,这是有原因的。ECK 的 CRD 文件比较大,而且注册了 conversion webhook 和 validating webhook。如果你先 apply operator 再 apply CRD,operator 启动时找不到 CRD,会进入一个半死不活的状态,虽然最后可能自己恢复,但中间那几分钟的日志噪音很烦人。
另一个跟 webhook 相关的坑:删了 operator 但没删 CRD 和 webhook 配置。下次重装的时候,API Server 会去调一个已经不存在的 webhook service,导致所有 Elasticsearch CR 的创建/更新请求超时失败,报错类似failed calling webhook "elastic-es-validation-v1.k8s.elastic.co"。遇到这个,检查一下:
kubectl get validatingwebhookconfigurations | grep elastic kubectl get mutatingwebhookconfigurations | grep elastic该删的删掉,再重装。
2.3 7.17 的小版本号怎么选
spec.version这一栏填的是完整的 Elasticsearch 版本号,比如7.17.25。这里有个细节:ECK 会拿这个版本号去拉镜像docker.elastic.co/elasticsearch/elasticsearch:7.17.25,如果你的集群拉不到外网镜像,需要提前做镜像同步,并且在 CR 里通过spec.image覆盖成你私有仓库的地址。
spec: version: 7.17.25 image: your-registry.local/elasticsearch:7.17.25注意version字段还是得填,operator 靠它做兼容性判断和配置生成,image只是覆盖实际拉取的地址。这两个字段的含义不一样,别只填一个。
至于具体选哪个 7.17 补丁号,原则是选当前 7.17 分支上最新的补丁版本,因为 7.17 是长期维护分支,补丁版本主要是修 bug 和安全问题。写这篇文章的时候我用的示例是 7.17.25,你实际部署前建议看一眼官方 release notes 确认一下最新的补丁号,别直接用我这里的数字。
还有一个隐性约束:operator 升级要谨慎。你锁了 2.16.0,跑了半年,某天想升到 2.17,这个动作本身通常没问题,但如果新版本对 ES 7.17 的支持发生变化(比如某个字段被废弃),可能会触发预期外的滚动重启。我的建议是:只要集群稳定,operator 别乱动,把它当成基础设施的一部分冻结起来。
3. 一份能直接 apply 的非 TLS 清单,逐字段讲清楚为什么这么写
到这一步,环境准备好了,operator 跑起来了,可以写 CR 了。我把完整清单列出来,然后一个字段一个字段解释。
3.1 命名空间、StorageClass 与前置检查
先把命名空间建好,然后确认 StorageClass 可用。这一步听起来废话,但我见过太多次 Pod 卡在 Pending,最后发现是 PVC 绑不上——集群里压根没有默认 StorageClass,或者默认的那个是WaitForFirstConsumer模式而节点亲和性又配错了。
kubectl create namespace es-demo # 确认有没有默认 StorageClass kubectl get storageclass # 输出里 DEFAULT 列有 * 的那个就是默认的如果没有默认的,你在volumeClaimTemplates里必须显式写storageClassName: xxx,不然 PVC 永远 Pending,ES Pod 也永远 Pending,而kubectl get elasticsearch的 HEALTH 列会一直显示空。
3.2 Elasticsearch CR 的完整写法
apiVersion: elasticsearch.k8s.elastic.co/v1 kind: Elasticsearch metadata: name: es-plain namespace: es-demo spec: version: 7.17.25 # 关键:关闭 HTTP 层自签证书 http: tls: selfSignedCertificate: disabled: true nodeSets: - name: default count: 3 config: # 显式声明,方便排查时一眼看到 xpack.security.enabled: true # 3 节点小集群,全角色够用 node.roles: ["master", "data", "ingest"] # 如果没法改宿主机 sysctl,用这个开关兜底 node.store.allow_mmap: false podTemplate: spec: initContainers: - name: sysctl securityContext: privileged: true runAsUser: 0 command: - sh - -c - | sysctl -w vm.max_map_count=262144 containers: - name: elasticsearch resources: requests: cpu: "2" memory: 4Gi limits: memory: 4Gi env: - name: ES_JAVA_OPTS value: "-Xms2g -Xmx2g" volumeClaimTemplates: - metadata: name: elasticsearch-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 50Gi storageClassName: standard3.3 selfSignedCertificate.disabled 到底做了什么
这是整份清单里最关键的一行:
spec: http: tls: selfSignedCertificate: disabled: true它的语义是:不要为 HTTP 层自动生成自签证书。operator 在 reconcile 的时候会检查 HTTP TLS 的状态,判断逻辑大致是「有没有用户提供的证书 Secret?没有的话,自签是不是被显式禁用了?」如果两个答案都是「没有」和「是」,operator 就会在生成的elasticsearch.yml里写入xpack.security.http.ssl.enabled: false,同时把 readiness probe 的 scheme 从 HTTPS 改成 HTTP,Service 也不再挂证书文件。
这里有个非常容易踩的坑:不要同时在nodeSets[].config里手写xpack.security.http.ssl.enabled。operator 生成的配置和用户配置合并的时候,同名 key 会产生冲突,表现是 Pod 起不来或者配置被覆盖成非预期值。我在第 5 节会教你一招:直接 exec 进 Pod 看最终的elasticsearch.yml,这是判断「配置到底生效没有」的唯一可靠手段。
另外,如果你之前给这个集群配过spec.http.tls.certificate(用户自备证书),要一并删掉。因为certificate和selfSignedCertificate.disabled是互斥的,只要certificate.secretName有值,operator 就认为你要用 TLS,disabled: true会被忽略。判断依据还是那句:看 Pod 里的配置文件,别猜。
3.4 transport 层怎么办:认了,别硬刚
前面说过,ECK 的 CRD 里没有 transport TLS 的关闭开关。我试过往nodeSets[].config里塞xpack.security.transport.ssl.enabled: false,operator 在组装配置的时候会把它自己生成的那份合进来,最终结果要么是你的值被覆盖,要么是两份配置并存导致节点间握手失败,Pod 直接 CrashLoop。
所以我的做法是:transport 层保持 ECK 默认的 TLS,一点都不动。反正 9300 只在集群内部通信,客户端感知不到,安全上也没有额外负担。你真正要摘掉的是 9200 的证书,这就够了。
如果你确实因为某些极端原因(比如要做流量镜像调试)必须让 9300 也明文,那就得绕开 operator 的配置管理,比如自己维护 StatefulSet 而不用 ECK,或者用 initContainer 在启动前改配置文件。这两种做法都超出了「正常使用 ECK」的范围,维护成本很高,我不建议。
3.5 vm.max_map_count 的三种解法,选哪个
ES 对vm.max_map_count的要求是 262144,低于这个值启动时会直接报错退出,日志里是max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]。这个错误很显眼,但新手第一次看到容易懵。
三种解法,我列个表对比:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 宿主机预配置 | 在每台 node 上写/etc/sysctl.d/99-es.conf | 一次配置永久生效,性能最好 | 需要 node 权限,托管 K8s 可能改不了 |
| initContainer | 特权容器启动前 sysctl -w | 随 Pod 走,不依赖宿主机 | 需要 privileged,部分集群的安全策略会拦 |
| 关闭 mmap | config 里设node.store.allow_mmap: false | 零权限要求,最省事 | 性能下降,官方不推荐生产用 |
我的选择是:能改宿主机就改宿主机,改不了就用 initContainer,两个都不行才用node.store.allow_mmap: false。上面那份清单里我把 initContainer 和 allow_mmap 都写上了,实际用的时候二选一——如果你用了 initContainer 且它生效了,allow_mmap: false就可以删掉,让 ES 用 mmap 获得更好的性能。
3.6 资源与 JVM 堆的配比关系
ES_JAVA_OPTS里的-Xms2g -Xmx2g和容器的memory: 4Gi之间的关系是:堆内存占容器内存的 50%,剩下 50% 留给 Lucene 的堆外内存(文件系统缓存、段合并的临时缓冲等)。这个 50% 不是随便定的,是 Elastic 官方推荐的配比。
两个红线:一是堆大小不要超过物理内存的 50%,超了会 OOMKilled,而且是被内核杀的,日志里看不到 Java 的 OOM 堆栈,只有Exit Code: 137,很容易误判成应用 bug;二是堆不要超过 31g,超过这个值 JVM 会关闭指针压缩,实际可用内存反而变少,属于负优化。
小集群 3 节点,每节点 2g 堆,总共 6g 堆内存,跑测试环境和中小规模数据完全够。如果数据量上来了,优先加节点数而不是无脑加堆。
3.7 apply 之后五分钟内该看什么
kubectl apply -f es-plain.yaml # 持续观察 kubectl -n es-demo get elasticsearch es-plain -w关注HEALTH和PHASE两列。正常演进是:空 →ProcessorsPending→ApplyingChanges→Ready,HEALTH 最后变成green(单节点或数据少时)或者yellow(有副本未分配,多节点初始状态常见)。yellow不算故障,等分片分配完会变绿。
同时看 Pod:
kubectl -n es-demo get pods -l elasticsearch.k8s.elastic.co/cluster-name=es-plain kubectl -n es-demo logs es-plain-es-default-0 -f日志里看到Security is enabled和HttpSslEnabled(或者http ssl disabled相关的行),基本就稳了。
4. 账号密码这条链路:secret 在哪、用户怎么加、客户端怎么连
TLS 关掉了,认证还得留着。ES 的账号密码体系在 ECK 下有一套自动化的东西,搞清楚 Secret 的分布,后面加用户、改密码都有章法。
4.1 operator 自动生成的那些 Secret
集群 Ready 之后,kubectl -n es-demo get secret会看到一堆名字带前缀的 Secret,我按用途归类:
| Secret 名称 | 内容 | 用途 |
|---|---|---|
es-plain-es-elastic-user | key 为elastic的超级用户密码 | 你的日常管理入口 |
es-plain-es-internal-users | kibana_system、beats_system等内部账号 | operator 给 Kibana/Beats 自动注入用 |
es-plain-es-transport-certs | transport 层证书 | 节点间通信,别动 |
es-plain-es-http-certs-public | HTTP 层证书 | 关掉 TLS 后这个通常不再生成 |
取 elastic 密码:
kubectl -n es-demo get secret es-plain-es-elastic-user \ -o jsonpath='{.data.elastic}' | base64 -d && echo这里有个细节:不要-o yaml去看,输出的是 base64。用jsonpath加base64 -d一步到位,&& echo是为了补一个换行,不然密码会粘在你的 shell 提示符前面。
密码是 operator 随机生成的,长字符串。测试环境图省事,可以直接改:
kubectl -n es-demo patch secret es-plain-es-elastic-user \ --type merge \ -p '{"data":{"elastic":"'$(echo -n 'MyP@ssw0rd123' | base64)'"}}'改完等几十秒,operator 会把新密码同步到 ES 的用户体系里。这个机制是 ECK 的一个便利点:Secret 是密码的唯一真相来源,改了 Secret 就等于改了 ES 密码,不需要你去调_security/user/_password接口。
注意:生产环境不要手工改这个 Secret,让 operator 自己生成随机密码,然后通过你的密钥管理系统去取。手改的弱密码是安全审计的高频问题。
4.2 建业务账号:走 API 还是走 file realm
ECK 2.x 提供了spec.auth.fileRealm这个字段,可以在 CR 里声明用户和角色,operator 会生成对应的 file realm 配置和 Secret。写法大概是这样:
spec: auth: fileRealm: - secretName: es-app-users对应的 Secret 里放user:hashed_password格式的内容。这个方案的优点是声明式,跟 GitOps 流程契合;缺点是需要你先生成 bcrypt hash,而且每次加用户都得改 CR 触发 reconcile。
我个人的做法是:测试环境用 API 直接建,生产环境如果上了 GitOps 再考虑 file realm。API 建用户很直接,而且能顺便建角色:
ES_PASS=$(kubectl -n es-demo get secret es-plain-es-elastic-user -o jsonpath='{.data.elastic}' | base64 -d) kubectl -n es-demo port-forward svc/es-plain-es-http 9200:9200 & # 建角色 curl -s -u "elastic:${ES_PASS}" -X POST "http://localhost:9200/_security/role/app_writer" \ -H 'Content-Type: application/json' \ -d '{ "cluster": ["monitor"], "indices": [ { "names": ["logs-*"], "privileges": ["create_index", "index", "write", "read", "view_index_metadata"] } ] }' # 建用户并绑定角色 curl -s -u "elastic:${ES_PASS}" -X POST "http://localhost:9200/_security/user/app_writer" \ -H 'Content-Type: application/json' \ -d '{ "password": "AppWr1ter@2024", "roles": ["app_writer"], "full_name": "log writer service" }'注意权限收窄的原则:给业务方单独的账号和角色,不要图省事全用 elastic。一旦出问题,你能从访问日志里定位到具体是哪个服务在干什么。角色里的indices.names用通配符logs-*而不是*,防止某个服务误操作把系统索引删了。
验证一下:
curl -s -u "app_writer:AppWr1ter@2024" "http://localhost:9200/_cat/indices?v" curl -s -u "app_writer:AppWr1ter@2024" -X PUT "http://localhost:9200/logs-test-000001/_doc/1" \ -H 'Content-Type: application/json' -d '{"msg":"hello"}'第二条能成功写入,说明角色配置正确。
4.3 三种客户端的连接串写法
curl / Postman最简单,http://es-plain-es-http.es-demo.svc.cluster.local:9200加-u user:pass。如果是集群外访问,先kubectl port-forward或者把 Service 改成 NodePort/LoadBalancer。
Java 客户端(7.17 用 RestHighLevelClient):
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials("app_writer", "AppWr1ter@2024") ); RestClientBuilder builder = RestClient.builder( new HttpHost("es-plain-es-http.es-demo.svc.cluster.local", 9200, "http")) .setHttpClientConfigCallback( httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); RestHighLevelClient client = new RestHighLevelClient(builder);关键在两处:HttpHost的第三个参数是"http"不是"https";凭据走CredentialsProvider而不是手动拼 header,这样连接池的每个请求都会带上认证信息。我见过有人在拦截器里手动加Authorization,结果连接池复用时偶尔丢头,排查了很久。
Logstash 输出插件:
output { elasticsearch { hosts => ["http://es-plain-es-http.es-demo.svc.cluster.local:9200"] user => "app_writer" password => "${ES_PASSWORD}" index => "logs-%{+YYYY.MM.dd}" } }密码用环境变量注入,别硬编码在配置文件里。Logstash 7.17 的输出插件对 http 地址支持得很好,不需要额外配置。
Filebeat的filebeat.yml:
output.elasticsearch: hosts: ["http://es-plain-es-http.es-demo.svc.cluster.local:9200"] username: "app_writer" password: "${ES_PASSWORD}"Beats 在 7.x 里对 http 协议是兼容的。但注意如果你用的是 8.x 的 Beats 连 7.17 的 ES,版本兼容性要单独确认,跨大版本通常不推荐。
5. 起来之后才开始疼:探针、Kibana 与滚动重启
集群绿了不代表活干完了,后面几个问题会在你「觉得已经搞定」的时候冒出来。
5.1 Kibana 到底会不会自动跟着变成 http
理论上会,实际上要验证。部署 Kibana 的 CR:
apiVersion: kibana.k8s.elastic.co/v1 kind: Kibana metadata: name: kb-plain namespace: es-demo spec: version: 7.17.25 count: 1 elasticsearchRef: name: es-plain http: tls: selfSignedCertificate: disabled: true podTemplate: spec: containers: - name: kibana resources: requests: cpu: 500m memory: 1Gi limits: memory: 1GiKibana 自己也要关 HTTP TLS,不然浏览器访问 5601 还是得吃自签证书警告,一边关一边不关很别扭。
部署之后,验证它连 ES 用的是不是 http:
POD=$(kubectl -n es-demo get pod -l kibana.k8s.elastic.co/name=kb-plain -o jsonpath='{.items[0].metadata.name}') kubectl -n es-demo exec ${POD} -- cat /usr/share/kibana/config/kibana.yml | grep -A3 elasticsearch如果看到hosts: ["http://es-plain-es-http.es-demo.svc:9200"],说明 association 正确识别了非 TLS 状态。如果看到https://,那就是 operator 版本或者 reconcile 状态有问题,最直接的排查路径是:
kubectl -n es-demo describe kibana kb-plain看 Events 和 Status 里的 association 相关信息。ECK 的 association 出错时会在 status 里留下明确的 message,比如association failed或证书相关的错误。
还有一种情况:Kibana 起来了,浏览器能打开登录页,但登录报Unable to authenticate。这通常是kibana_system用户的密码同步问题。glass 的排查手段是看 Kibana Pod 日志里的security_exception详细信息,然后确认es-plain-es-internal-users这个 Secret 里kibana_system的密码跟 ES 里的一致。ECK 正常情况下会自动同步,如果不同步,重启一次 Kibana 的 StatefulSet 通常能解决。
5.2 从带 TLS 的旧集群改成非 TLS,路径怎么走
这是最容易被低估的操作。改 CR 本身就是改几行 YAML 的事,但 operator 会触发 StatefulSet 滚动重启,每个 Pod 依次重建。如果你的集群里有数据,这个过程会触发分片重分配,短则几分钟,长则几十分钟(取决于数据量)。
我的操作顺序是这样的:
- 先确认集群是绿的,有未分配分片的话先处理掉,带病滚动风险高。
- 备份。测试环境可以不做,但如果是重要的测试数据,用 snapshot 存一份。ECK 支持 snapshot,用
snapshotRepository配置 S3 或者本地 PVC 都行。 - 改 CR 里的
http.tls.selfSignedCertificate.disabled: true,kubectl apply。 - 盯着
kubectl get pods -w看滚动顺序,ECK 一般是一个一个重建,不会全杀。如果发现两个 Pod 同时 Terminating,说明updateStrategy配置有问题。 - 滚动完之后验证:新 Pod 里的
elasticsearch.yml里有xpack.security.http.ssl.enabled: false,curl http://...:9200能通。
有一个非常隐蔽的坑:如果你之前的客户端走 https,改完之后它们会全部连不上,报错是Unrecognized SSL message, plaintext connection?。这个错误的含义是「客户端发的是 TLS 握手包,但服务端在说 HTTP 明文」。反过来,如果客户端用 http 连一个还在 TLS 的服务端,报错是received plaintext http traffic on an https channel。这两个报错信息我建议你记下来,方向判断非常快。
所以切换窗口期内,客户端和集群的配置要同步改。要么先改客户端配http://,要么接受一段时间的连接失败。测试环境无所谓,生产环境必须做灰度。
5.3 排查链路实录:Pod 起不来 / 401 / 连接被拒
这一节我把三个高频故障的排查顺序完整写出来,你可以照着走一遍。
故障一:ES Pod 一直 CrashLoopBackOff
kubectl -n es-demo logs es-plain-es-default-0 --previous先看倒数第二次的日志。三类典型输出:
max virtual memory areas vm.max_map_count [65530] is too low→ 回到 3.5 节,处理 sysctl。java.nio.file.AccessDeniedException或者 data 目录权限问题 → PVC 挂载的 fsGroup 不对。ECK 默认给 Pod 设了fsGroup: 1000,某些 NFS 或者 hostPath 的 StorageClass 不遵守这个语义,需要手动在podTemplate.spec.securityContext里补fsGroup。failed to load plugin或者配置解析错误 → 去 exec 看/usr/share/elasticsearch/config/elasticsearch.yml,大概率是你手写的 config 跟 operator 生成的冲突了。
故障二:curl 返回 401
curl -u "elastic:$ES_PASS" http://localhost:9200401 只有三种原因:密码错、用户名错、没带认证头。先确认 Secret 里取出来的密码没有多余的空格或者换行(base64 -d之后不加echo容易在命令替换时出问题)。再确认你连的是对的 Service: