Elasticsearch集群搭建实战:从单节点故障到三节点高可用方案

发布时间:2026/10/13 21:46:47
Elasticsearch集群搭建实战:从单节点故障到三节点高可用方案
1. 一次单节点故障让我重写ES集群部署流程去年年中我维护的一个搜索服务在晚高峰突然大面积超时排查到最后发现是Elasticsearch集群出了问题。准确说当时根本没有集群只有一个节点扛着全部分片。那个节点内存溢出后整个搜索接口直接瘫痪。后来我用三天时间紧急把架构扩到三节点过程中踩了一串配置坑才把elasticsearch集群搭建这套流程彻底理顺。这篇内容就是那次重构后的完整记录。先给不熟悉ELK的人一个定位ELK里Elasticsearch负责数据存储和检索Logstash负责采集和清洗Kibana负责可视化。而集群搭建针对的是最底层的ES所有数据可靠性、查询性能、水平扩展能力都从这一步开始。下面所有讨论默认你至少有三台机器并且对Linux基本操作有概念。这里不说太高深的调优我用一套适合大多数业务的中型集群方案覆盖原理、配置、启动、验证、排错和生产加固。刚接手ES、准备把单节点升级成多节点、或者已经搭过但没搭明白的读者都能直接照着做。1.1 单节点能跑但业务不答应从故障现场说起单节点ES在一个项目早期完全够用。数据量几百GB、查询QPS几十上百单节点都能扛得住。问题的本质不是单节点性能不够而是它把“索引数据”“主分片”“副本分片”“集群控制”这四件事全压在同一台机器上任何一个环节出问题都会连锁反应。那次的故障链条是这样的某天凌晨定时任务灌入大量数据磁盘使用率涨到85%以上后台不断报merge线程排队。白天业务高峰时查询请求和写入请求争抢内存最终触发OOM。ES的JVM进程一挂所有index都不可用当时没有副本分片可以接管连恢复的余地都没有。事后我做了个复盘清单没有master节点独立角色节点挂了直接丢集群协调能力所有索引的副本数设置为0数据没有任何本地冗余没有配置JVM堆大小ES默认1GB堆直接OOM没有快照备份只能靠原始数据重新灌库。这个教训让我明白搭建集群不是为了跑分是为了让故障发生时业务不中断。这也是下面所有设计决策的出发点。1.2 版本选型与组件搭配为什么我把基线定在7.17在做集群方案时我对比了几个版本路线最终把基线定在7.17。这不是说8.x不好而是7.17在配置方式和运维习惯上更平缓。这里有个实际原因8.x默认开启了安全认证首次启动就需要配置证书和密码对刚接触集群的人来说排查难度会加倍。ES的报错信息不像应用代码那么直观如果安全模块和节点发现模块的坑同时出现新手基本会卡死在启动阶段。7.17支持先不开启安全认证把集群正常跑起来之后再按生产要求补上xpack配置整个过程可以拆成两段独立完成。组件版本上我的原则是ES、Kibana、Logstash必须保持大版本一致最好完全一致。混搭版本会导致Kibana的堆栈监视功能报错也可能出现Mapping字段兼容性问题。如果你用Logstash做7.17的采集就找7.17.x对应版本不要自作聪明混装。2. 集群角色与分片副本机制动手前必须想清楚的三个问题配置elasticsearch.yml之前先想清楚三个问题每个节点扮演什么角色、数据如何分布、集群如何选主。很多配置看起来只是几个单词但背后是这几个问题在决定。2.1 节点角色分离master、data、ingest各自忙什么在ES集群里节点可以同时承担多种角色每种角色有不同的职责master节点负责集群元数据管理比如索引的创建删除、节点上下线的处理、分片分配决策data节点负责存储分片数据处理查询和写入请求ingest节点负责在数据写入前执行预处理管道coordinating节点是“路由层”把请求分发到对应数据节点并汇总结果。对于三台机器规模不大的集群最简单可靠的做法是三台都是master-eligible节点加data节点加ingest节点也就是混合角色。这样每台机器都有完整的数据和协调能力资源利用率最高。如果你的机器数量超过五台或者集群数据量已经上到TB级别我建议拆开独立三台master节点数据节点只保留data角色。原因是master节点的JVM堆主要用来维护集群状态当集群里有几千个索引时元数据会占据大量堆空间。一旦data节点因为查询压力导致GC频繁master节点也跟着遭殃这是很常见的生产事故源。2.2 分片与副本数据分布和容灾的基本单位一个索引的数据不是一整块存放在某台机器上而是被切分成多个分片每个分片在ES里就是一个Lucene索引。分片分布在不同的data节点上这就是水平扩展的基础。每个主分片可以配置对应数量的副本分片副本是主分片的完整拷贝作用有两个一是主分片所在节点故障时直接提升为新的主分片保证数据不丢二是分担读请求尤其是搜索类业务副本多能明显提升并发查询能力。默认配置下ES会为每个索引创建1个主分片和1个副本分片。有些教程建议把分片数设大一些比如一个索引建8个分片理由是方便扩展。我的建议是生产环境的索引在创建时就要根据数据量规划好分片数因为分片数创建后不能直接修改只能重建索引或做reindex。这里给出一个参考单分片的数据量控制在20GB到50GB之间比较合理分片过大恢复时间太长分片过小会导致太多小分片占用大量文件句柄。比如一个索引预计年增长300GB主分片设8个就很合适。至于副本在集群节点数大于1时至少设为1那次的故障教训就是副本数0导致数据无法本地接管。2.3 脑裂与quorum集群投票的最小法定人数集群不是节点越多就越稳定这里必须提脑裂。脑裂指的是网络故障把集群切成两半两边各自选出一个master节点同时对外提供服务。这时候可能会有两个节点同时写同一个索引的分片最终造成数据永久性损坏。避免脑裂的核心是投票机制只有获得超过半数的master-eligible节点投票才能当选master。集群里master-eligible节点数是3法定人数就是2也就是最少两个节点同意才算数。这个“半数以上”在7.x版本中通过配置体现。这里顺便说一个版本差异网上很多旧资料会让你配discovery.zen.minimum_master_nodes这个参数在7.x中它仍然有效建议设置为(master eligible节点数 / 2) 1。而在8.x中这个参数已经被移除集群会自动计算投票数。你如果用的是7.17配这个参数没有坏处能进一步提高容错性。3. 环境准备系统参数、用户权限与目录规划很多人第一次搭ES是直接下载、解压、然后启动结果报错一头雾水。ES对环境有一些硬性要求提前处理好后面的排错会少很多。3.1 操作系统参数max_map_count与文件描述符ES使用mmap来映射索引文件Linux默认的虚拟内存映射数量限制往往不够典型报错是max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]这个报错很多人都见过解决办法是修改/etc/sysctl.confvm.max_map_count262144然后执行sysctl -p另外需要提高进程可打开的文件描述符数量。ES进程需要打开大量文件默认的1024肯定不够。在/etc/security/limits.conf里添加es soft nofile 65536 es hard nofile 65536 es soft nproc 4096 es hard nproc 4096注意这里的es是后面要创建的运行用户。这两个参数是ES启动的基础门槛不配好的话即使勉强起来了高并发场景下也会突发句柄耗尽。还有一个经常被忽略的是swap。ES官方建议关闭swap因为内存一旦被换到磁盘JVM的响应时间会急剧上升对查询时延影响极大。可以在elasticsearch.yml里设置bootstrap.memory_lock: true再用mlockall锁定内存。配合这个参数建议在/etc/fstab里关掉swap分区或者在启动前用swapoff -a临时关闭。3.2 非root运行与目录规划从安装开始规避权限坑ES不允许以root身份直接启动在7.x版本中会直接报错can not run elasticsearch as root这不是什么安全问题而是出于安全考虑防止ES进程权限过高被漏洞利用。所以我们需要创建一个es用户useradd es mkdir -p /opt/elasticsearch mkdir -p /data/es-data mkdir -p /data/es-logs chown -R es:es /opt/elasticsearch chown -R es:es /data目录规划时我把数据目录和日志目录放在/data下和程序安装目录/opt分开。好处有两个第一数据目录单独挂盘后后续扩容只要加磁盘不用动程序第二ES恢复数据时对磁盘写入压力很大程序盘和数据盘分开能避免互相干扰。下载安装包后解压到/opt/elasticsearch目录结构应该是/opt/elasticsearch/ ├── bin/ # 启动脚本和工具 ├── config/ # 配置文件 ├── data/ # 默认数据目录(未指定时) ├── logs/ # 默认日志目录(未指定时) └── modules/ # 内置模块如果jdk环境需要单独配置7.17的安装包自带OpenJDK在JDK目录下不需要额外安装Java。如果你是直接用apt或yum装的ES它会依赖系统Java版本反而容易出现版本不一致。压缩包方式更可控。3.3 三台机器的网络规划与hosts解析在实际项目里三台机器的内网IP可能是192.168.10.11、192.168.10.12、192.168.10.13这种情况下我强烈建议配置hosts解析用主机名互相访问而不是直接写IP。原因很实际如果后续IP调整或者迁移机器用主机名的话只需要改hosts文件而用IP的话要逐个修改elasticsearch.yml。90%的节点发现问题都出在IP或主机名解析不一致上。三台机器的/etc/hosts都加上192.168.10.11 es-node-1 192.168.10.12 es-node-2 192.168.10.13 es-node-3每台机器上执行互相ping主机名确认解析正常后再继续。另外注意ES有两个端口http端口9200用于REST接口transport端口9300用于节点间通信。集群发现配置走的是9300防火墙或者安全组至少要放行这两个端口的内部访问。4. elasticsearch.yml逐项拆解每个配置背后解决什么问题配置文件是集群搭建的核心很多问题都出在参数名写错或参数值不一致。我需要先给出一份完整的三节点配置模板然后逐项说明。4.1 一个可以照抄的三节点配置模板以es-node-1为例在/opt/elasticsearch/config/elasticsearch.yml里写入cluster.name: es-prod-cluster node.name: es-node-1 node.roles: [master, data, ingest] path.data: /data/es-data path.logs: /data/es-logs network.host: 0.0.0.0 http.port: 9200 transport.port: 9300 discovery.seed_hosts: [es-node-1, es-node-2, es-node-3] cluster.initial_master_nodes: [es-node-1, es-node-2, es-node-3] discovery.zen.minimum_master_nodes: 2 gateway.recover_after_nodes: 2 bootstrap.memory_lock: true action.destructive_requires_name: truees-node-2和es-node-3的配置只改node.name字段其他完全一致。node.name分别是es-node-2和es-node-3。如果是8.x版本discovery.zen.minimum_master_nodes一整行要去掉其他参数大多通用。4.2 从cluster.name到discovery模块每个参数的含义与关系cluster.name是集群的唯一标识所有节点必须完全一致否则节点发现时会认为是不同集群互相拒绝加入。这里我说个经验集群名称取业务含义更清晰比如es-prod-cluster比默认的elasticsearch更容易在运维平台里辨识。node.name是节点在集群中的唯一标识每台机器不同。调试时会发现日志里大量使用node.name来指代节点写清楚业务和位置信息会方便很多。比如es-node-1、es-node-2这种或者es-master-01这种带角色信息的命名。node.roles指定节点角色。这里三个节点都是master、data、ingest三者的组合完全一致。如果你想让某台机器只做数据节点就把roles改成[data]但要注意必须有至少三个节点承担master角色才比较安全。network.host设置为0.0.0.0表示监听所有网卡。有人会问能不能只监听内网IP可以但要保证所有节点能通过这个IP互相访问。0.0.0.0的好处是后续容器化或网络环境变化时不受影响。discovery.seed_hosts是节点发现的种子列表。每个节点启动时从这个列表探测其他节点是否存在从而加入集群。这里我填的是主机名这些主机名在/etc/hosts里有解析所以ES能正常解析。cluster.initial_master_nodes只在集群第一次启动时需要配置它告诉每个节点哪些是候选master节点用于完成第一次选主。集群启动成功后这个参数不再严格必要但保留也无妨。新节点加入已运行的集群时并不需要这个参数只需要discovery.seed_hosts能解析到集群内任意节点。gateway.recover_after_nodes配置的目的是集群在重启后等待至少N个节点加入才开始恢复分片。比如配置2意味着最少两个节点同时在线才会触发数据恢复避免单节点先启动后在只加载了部分数据的情况下就进行分片恢复导致大量数据段重复复制。4.3 两处最容易被忽视的参数bootstrap.memory_lock与action.destructive_requires_namebootstrap.memory_lock配合系统配置里的swap关闭把JVM堆锁定在物理内存中禁止换出。如果系统swap未关闭或内存不足启动时会报错memory locking requested for elasticsearch process but memory is not locked这时候需要检查系统配置是否生效还可以在/etc/security/limits.conf里加一行es memlock unlimited。这个参数对稳定性影响很大尤其是日志类业务写入和查询都会频繁分配内存一旦触发换页节点响应会从毫秒级跳到秒级。action.destructive_requires_name是保护参数。很多团队习惯在Kibana里直接执行DELETE *这样的通配符删除操作一旦索引名写得不严谨可能把整个集群的数据瞬间清空。开启这个参数后带通配符的删除操作会被拒必须写明确的索引名。这个参数不是ES必需的但每个生产实践里我都会加一行。5. 启动三件套JVM参数、后台启动与集群健康验证配置写完后很多人会直接跑bin/elasticsearch看到一堆日志刷屏就以为起来了实际上可能只是前台进程在跑。启动这一步有几个细节直接决定成败。5.1 jvm.options堆内存大小不是越大越好ES的JVM堆内存配置在config/jvm.options里默认是1GB生产环境必须改。注意这里的堆内存不是物理内存越大越好存在两方面限制。一方面堆内存最大不要超过30GB。ES官方文档明确说在30GB附近内存指针压缩会失效之后即使增加内存也得不到性能收益。把堆设为30GB以上反而会因为GC时间过长导致节点不稳定。我的建议是物理内存的一半单机32GB内存就设16g堆。另一方面Xms和Xmx必须设置成相同的值。很多人只改Xmx不改Xms结果JVM启动时只申请了小堆随着数据增长反复扩容缩容每次都是Full GC性能被拖垮。正确的配置是在jvm.options里-Xms16g -Xmx16g修改后jvm.options这个文件还有一堆默认参数比如-XX:UseG1GC7.17新版默认使用G1垃圾回收器保持默认即可。GC线程数等参数如果不是特别懂不建议乱动。5.2 首次启动的完整命令与日志判断标准所有配置就绪后用es用户执行启动。以下命令在三台机器上都需要执行su - es cd /opt/elasticsearch ./bin/elasticsearch -d -p /data/es.pid-d表示后台运行-p指定PID文件路径方便后续用kill等方式停止。不建议用前台方式启动因为SSH断开时进程会跟着退出。启动后不要急着看9200端口先看日志。日志在/data/es-logs/目录下文件命名一般是集群名加节点名比如es-prod-cluster.log。查看最新日志tail -f /data/es-logs/es-prod-cluster.log正常情况下日志里会出现这样的标志master node changed表示节点被选为master或加入已有masterNode joined表示其他节点成功加入集群recovered [n] indices表示分片恢复完成started说明节点完成启动流程。如果日志停在某个阶段不动或者反复报错先记录报错原文再排查。很多问题在日志里写得很直白只是大家习惯不看。5.3 用curl完成集群健康三连验证启动完三台节点后别急着接业务先做三个基础健康检查。选择任意一台机器执行curl -s http://192.168.10.11:9200/_cluster/health?pretty返回的JSON里重点看这几个字段{ cluster_name : es-prod-cluster, status : green, number_of_nodes : 3, active_primary_shards : 2, active_shards : 4, unassigned_shards : 0 }status有green、yellow、red三种状态green表示所有主分片和副本分片都已分配一切正常yellow表示主分片全部正常但至少一个副本分片未分配此时集群可用但有风险red表示至少一个主分片未分配存在数据不可用。再查看节点列表curl -s http://192.168.10.11:9200/_cat/nodes?v这个命令会输出所有节点的IP、角色和堆内存使用情况。最后查看分片维度curl -s http://192.168.10.11:9200/_cat/shards?v如果status是green说明这个集群的搭建已经基本成功。三台机器都透过9300端口建立了节点通信分片也按副本规则散布到了不同节点。5.4 Kibana接入与常见端口对照集群搭建好后ELK里的Kibana负责可视化。Kibana需要和ES版本一致7.17的Kibana对应7.17.x。编辑其config/kibana.ymlserver.port: 5601 server.host: 0.0.0.0 elasticsearch.hosts: [http://192.168.10.11:9200, http://192.168.10.12:9200, http://192.168.10.13:9200]然后启动Kibana./bin/kibana浏览器访问5601端口能看到Kibana界面就可以继续配置索引模式。如果Kibana提示无法连接ES优先检查ES的9200端口是否被防火墙拦截以及kibana.yml里指定的ES地址是否能被Kibana服务器的网络访问。这里把三个服务的端口整理一下ES的http端口是9200传输层端口是9300Kibana是5601Logstash默认是9600和5044。出现连不上的问题时先确认这些端口是否从访问端到服务端全链路放开。6. 启动失败与节点掉线的完整排查链路集群搭建过程中最耗时间的永远不是写配置而是排错。我整理了三类出现频率最高的故障场景和完整的排查思路按这个思路走能省大量时间。6.1 场景一日志停在master not discovered yet表现是每个节点启动后日志反复出现类似这样的内容Master not discovered yet, this node has not joined an elasticsearch cluster这句话本身不是报错ES在等待发现master时会一直打印这条日志。但如果等了几分钟还是这样才说明有问题。排查链路我按这个顺序走检查每个节点的elasticsearch.yml中cluster.name是否一致不一致的集群无法互相发现检查discovery.seed_hosts的地址确认是主机名还是IP以及被访问端的/etc/hosts有没有对应条目在其中一个节点上用curl或telnet测试9300端口连通性telnet es-node-2 9300如果端口不通多半是防火墙或安全组未放行传输层端口确认cluster.initial_master_nodes中的节点名和node.name是否完全匹配最后查看日志开头的WARN信息ES会先打印哪些配置无效或者不推荐。这中间有一个很容易入坑的细节如果三个节点同时启动彼此之间可能因为争抢选主出现短暂的master not discovered这是正常的等待30秒到1分钟再看日志即可。如果超过三分钟还是这个状态才需要真正排查。6.2 场景二集群状态yellow或red分片分配不出去健康状态显示yellow说明主分片都正常副本分片存在未分配。最常见的原因是集群节点数小于副本数配置。比如一个索引配置了2个副本但集群只有两个数据节点第三个副本自然分配不出去。此时用这个命令找出具体原因curl -s http://192.168.10.11:9200/_cluster/allocation/explain?pretty这个接口会直接告诉你分片为什么未分配。我见过比较典型的原因有分片副本数大于数据节点数、节点磁盘空间不足、某个索引显式指定了不存在的节点属性。如果是磁盘空间不足导致需要先清理或扩容然后等待ES自动重新分配。如果是有问题索引导致可以直接删除或重建该索引。red状态比yellow严重说明存在未分配的主分片。如果red的原因是节点曾经宕机后数据还没恢复通常重启节点后会自动恢复。如果磁盘数据损坏可能要考虑从快照恢复。6.3 场景三误以为启动成功实际JVM异常退出有一种情况是启动命令执行后没有任何日志输出进程却过了一会儿就消失。用ps查看ps aux | grep elasticsearch如果进程不在查看logs目录下启动时间附近的日志。7.x版本会出现JVM未能初始化的情况比如jvm.options里写了过大的堆内存而物理内存不够。这类问题的特点是ES进程未能正确初始化日志里会有Invalid initial heap size: -Xms16g或Could not reserve enough space for 1572864KB object heap看到这类日志说明堆内存设置超出物理内存或容器限制。把-Xms和-Xmx调小后重新启动即可。另一个常见坑是系统里多个Java版本导致JAVA_HOME混乱。ES压缩包自带JDK如果你又在系统里额外安装了Java并且设置了JAVA_HOME环境变量ES会优先使用系统Java版本不兼容时启动失败。解决办法是清空JAVA_HOME或指定使用内置JDK。6.4 排查中的几个底层原则排查ES集群问题时我有几个经验原则第一日志永远是最可靠的信息源。ES的日志写得非常详细报错信息里甚至直接告诉你“increase to at least [262144]”。很多人跳过日志凭记忆搜解决方案反而绕远路。第二一次只改一个配置。我见过太多人把多个疑似问题同时改掉最后出现新问题时根本不知道是哪次改动引起的。第三用最小的版本验证集群发现。如果两个节点都无法互相发现先砍成单节点启动确认单节点本身能正常提供服务再扩展到多节点。这样能快速区分是节点自身问题还是网络问题。7. 生产环境的最后一公里安全认证、快照备份与日常巡检集群跑起来只是工作的开始。真正进入生产后必须处理三个事情安全认证、备份恢复、日常体检。7.1 开启xpack安全认证的时机与方法ES默认没有任何认证任何人能访问9200端口就能读写数据。生产环境里这个风险绝对不可接受不过很多人在搭建阶段都先不开这个思路没问题但集群稳定后应立即开启。以7.17为例在每台节点的elasticsearch.yml里添加xpack.security.enabled: true然后重启所有节点。之后在任意一个节点执行./bin/elasticsearch-setup-passwords auto这个命令会为内置用户生成随机密码包括elastic、kibana_system、logstash_system等。建议把生成的密码保存在密码管理器里。elastic是超级用户后续所有管理操作都使用它。Kibana配置需要同步修改在kibana.yml里添加elasticsearch.username: elastic elasticsearch.password: 这里填生成的密码如果不配置Kibana启动后无法连接ES。Logstash如果需要采集数据也要在output插件里配置对应的用户名密码。开启安全认证后所有curl命令都需要带认证信息curl -u elastic:密码 http://192.168.10.11:9200/_cluster/health?pretty7.2 用快照给集群上最后一道保险ES的数据恢复不是简单把文件复制回去最好用的方式是快照。快照就是定期把索引的数据和元数据备份到磁盘或对象存储。先配置快照仓库路径在每台节点的elasticsearch.yml里添加path.repo: /data/es-backup然后重启节点。创建仓库和快照通过REST接口操作PUT _snapshot/es_backup { type: fs, settings: { location: /data/es-backup } }需要注意快照路径需要各节点共享如果是单机挂载的本地磁盘只能通过原始文件备份。生产环境我建议把快照放到独立的备份盘不要和ES数据目录在同一块磁盘上。快照完成后在Kibana的Stack Management里就能看到快照列表也可以随时触发恢复。7.3 我日常巡检必看的几个接口与指标最后分享一套我自己一直在用的巡检清单每天只需要几分钟能提前发现绝大多数隐患。以下命令建议存成脚本定时执行# 健康状态与节点数 curl -u elastic:密码 http://192.168.10.11:9200/_cluster/health # 节点分片分布和堆内存 curl -u elastic:密码 -s http://192.168.10.11:9200/_cat/nodes?vhip,heap.percent,ram.percent,node.role,master # 未分配分片数 curl -u elastic:密码 -s http://192.168.10.11:9200/_cat/shards?vhindex,shard,prirep,state,unassigned.reason # 磁盘水位 curl -u elastic:密码 -s http://192.168.10.11:9200/_cat/allocation?vhshards,disk.percent,disk.used,disk.avail,node几个关键的预警指标磁盘使用率超过80%就要做扩容超过85%ES会自动停止分配新分片heap.percent长期超过75%说明节点内存压力大需要扩容或调整索引分片数unassigned_shards长期不为0必须查明原因不要放任yellow状态JVM GC频繁在日志中出现长停顿说明堆大小或GC配置不合理。如果集群出现节点掉线先用_cat/nodes确认哪些节点还活着然后对照日志和网络检查按第6节的思路排查。日常巡检花的时间永远少于故障处理花的时间这是我在那次搜索服务故障后最深刻的体会。如果你也正在折腾ES集群我建议先按三台混合角色把这个流程完整跑通不要一上来就追求大集群方案。跑通一遍之后再逐步拆角色、开安全、接Logstash每一步都验证清楚再进行下一步。集群这块跑在深处的那些“坑”基本都要自己踩过一遍才真正长记性。