1. 先搞明白:这个集群到底要解决什么问题
我最初接触ELK的时候,对Elasticsearch集群的理解特别肤浅:以为把ES多装几台,启动起来就算集群了。结果在测试环境里把三台机器都配成了默认角色,数据全挤在一台机器的磁盘上,剩下的节点闲得没事干,查询一进来全部撞在同一个节点上,整个集群没撑过半天就卡死了。后来老老实实把节点角色分开,才明白集群搭建这件事,真正的难点不是“把服务拉起来”,而是“让每一台机器知道自己该干什么”。
ELK这套组合里,Elasticsearch负责数据的存储和检索,Logstash负责采集和清洗,Kibana负责展示。绝大部分业务会把ES作为核心引擎来用,一旦数据量上来,单节点在写入吞吐、查询并发、磁盘容量上都会被快速击穿。所以集群搭建不是“为了多一台机器”,而是为了三件事:高可用、水平扩容、分摊读写压力。这篇文章就围绕ES集群的规划、配置、启动和排障展开,适合刚接触ELK或者准备把ES从单机迁到多机的同学参考。
1.1 三种最典型的“必须上集群”场景
第一种是日志系统。这是ELK最经典的场景,每天产生几十GB甚至上TB的访问日志、业务日志、错误日志。单节点ES即便能写进去,索引一多,分片一多,节点很快会因为堆内存压力或者磁盘IO瓶颈变成亚健康状态。这种情况上集群,首要目的是把分片散到多台机器上,避免单机磁盘被打满。
第二种是业务搜索或数据分析。比如电商的商品搜索、运营后台的明细查询、用户行为的聚合分析。这类场景的特点是查询复杂、并发不低、对延迟敏感。单节点CPU和内存都有限,无法同时扛住大量聚合查询和写入。集群可以把查询请求分散到多个数据节点并行处理,再通过协调节点汇总结果,响应时间会明显下降。
第三种是服务连续性要求高的业务。如果ES挂了会导致核心功能不可用,那就不能接受单点故障。集群配合副本分片,能在某个节点宕机后由副本继续提供查询服务,主分片也会在其他节点重新分配。虽然切换过程会有短暂延迟,但比整库不可用强得多。
反过来,如果你的数据量只有几GB、每天写入量很少、也没有高可用要求,就不要盲目搭集群。集群会引入网络开销、分片协调、脑裂风险、运维复杂度。单节点ES加一份副本分片,已经能覆盖很多中小项目的需求。过度设计同样是坑。
1.2 节点角色:主节点、数据节点、协调节点
ES的集群能力很大程度上依赖节点角色划分。角色划分合理,集群能稳定跑很久;划分不合理,再多的机器也可能被打到崩溃。
ES中常见的三种角色是master、data、ingest,以及通过node.roles配置的coordinating。很多人只知道前几个,却忽略了默认情况下每个节点都可以当协调节点使用。协调节点负责接收客户端请求、分发到数据节点、汇总结果,本身不存数据,也不参与主节点选举。
主节点负责集群元数据管理、索引创建、分片分配、节点上下线等操作。数据节点负责实际存储数据和处理写入查询。生产环境我一般建议把主节点和数据节点分开,尤其是写入量大的时候。如果主节点同时又是数据节点,一旦数据节点的堆内存GC出现长时间停顿,主节点可能会被认为失联,触发重新选举,集群稳定性会大打折扣。
协调节点可以作为一层路由入口。当数据节点非常多时,可以在前面部署两个纯协调节点,让所有读写请求都先打到协调节点上,避免每个业务方直接跟一堆数据节点建立一堆连接。协调节点不需要很高配置,但连接数和CPU得够用。
1.3 版本选择:7.x还是8.x
版本选择影响后面的所有配置。如果是全新项目,我推荐直接用8.x。8.x的默认安全特性更强,自带TLS加密和账号认证,不再需要像7.x那样手动装安全插件、调整一堆认证参数。而且8.x对集群发现和引导配置做了简化,没有7.x早期那么多玄学问题。
如果是已有的ELK系统,或者依赖了一些老版本的Logstash插件、分词器,建议继续保持7.x,然后规划升级。7.x中我比较推荐7.10之后的版本,越往后无论是内存使用还是分片分配策略都更成熟。不要混用大版本,尤其不要一台7.x一台8.x,ES要求集群内的节点版本保持一致,跨大版本即使能加入集群,也会出现兼容性问题。
另外一个容易忽略的点是ELK全家桶的版本对齐。ES、Logstash、Kibana、Beats之间有大版本兼容性要求,比如ES用8.6,Kibana最好也是8.x的最新小版本。不要ES升到8.x了,Kibana还停在7.x,控制台大概率连不上ES。
2. 环境准备与基础配置
2.1 三台机器怎么规划
搭建集群至少准备三台机器。如果只是测试,可以用配置较低的虚拟机,但最好也把角色模拟出来。生产环境我比较常用的一个规划是:三台数据节点同时兼任主节点,或者五台机器里分出两台主节点、三台数据节点。
先给一个三台机器且没有独立协调节点的方案:三台都配置node.roles: [master, data]。这样的好处是集群至少有三个master候选节点,出现单点故障时还能保留多数派。缺点是数据写入会同时影响master角色。适合日志量不大、查询压力不高的场景。
如果数据量较大,我更推荐四台到五台的方案:三台数据节点加两台仅master节点,或者三台数据节点加两台协调节点。数据节点只负责数据,master节点只维护元数据,协调节点只处理路由。这样哪怕某一类节点出现压力,也不会拖垮其他角色。
硬件的粗略标准可以参考:数据节点内存建议64GB起步,其中JVM堆内存给31GB左右,剩余留给OS页缓存;主节点和协调节点16GB到32GB就够。磁盘按每日新增数据量乘以保留天数,再乘一个1.5的冗余系数来估算。需要保存30天、每天新增10GB数据,那基本要准备450GB以上的存储,再额外留一些滚动刷新和分段合并的临时空间。
2.2 操作系统参数调整
ES对Linux系统参数有一些硬性要求,不调整很难正常启动,或者启动后隐患很多。第一处是文件描述符限制。ES需要打开大量文件句柄来管理磁盘上的分段文件,默认的1024或4096完全不够。编辑/etc/security/limits.conf,加入:
es_user soft nofile 65535 es_user hard nofile 65535 es_user soft nproc 65535 es_user hard nproc 65535es_user是后面创建运行ES的专用系统用户,千万不能用root跑ES。修改完要重新登录或者重启终端才能生效,可以用ulimit -n检查。
第二处是虚拟内存区域数量。ES底层依赖Lucene,在写入和合并时会创建大量内存映射区域,系统默认的vm.max_map_count只有65530,在数据量大时极易触发"max virtual memory areas vm.max_map_count [65530] too low"错误。执行:
sysctl -w vm.max_map_count=262144 echo "vm.max_map_count=262144" >> /etc/sysctl.conf第三处是交换分区。ES启动时如果系统内存不够,内核会把ES的部分内存换到swap,GC时性能下降会很严重。建议将vm.swappiness调低,例如设为10,并且把ES的堆内存锁定到物理内存中。锁定内存需要在elasticsearch.yml里设置bootstrap.memory_lock: true,同时检查ulimit -l unlimited是否允许锁定。
2.3 安装JDK与Elasticsearch包
ES 7.x及以上版本内部会检测自己的Java运行环境。8.x版本自带JDK,不需要额外安装,这一点反而省了不少事。但如果你的服务器上有一个旧版本JDK,并且设置了JAVA_HOME,ES可能会优先使用你指定的JDK,这时要确认版本兼容性。我遇到过一台机器上装了Java 8,结果ES 8.x启动直接报UnsupportedJavaVersionError,最后把JAVA_HOME临时清掉,让它用自己的内置JDK解决。
安装包建议从官方仓库下载tar.gz包,版本跨度尽量小。把包放到/opt下解压:
tar -zxvf elasticsearch-8.6.0-linux-x86_64.tar.gz -C /opt cd /opt mv elasticsearch-8.6.0 elasticsearch chown -R es_user:es_user /opt/elasticsearchES解压后的目录权限要严格控制。因为ES有执行脚本、写日志、写数据的需求,如果目录属主不对,启动时会报权限错误。之后切到es_user用户操作,后续所有配置和启动命令都在这个用户下执行。
3. 把核心配置写对:elasticsearch.yml逐项拆解
3.1 全局配置项:集群名、节点名、网络绑定
config/elasticsearch.yml是ES最核心的配置文件。第一个要改的是集群名称:
cluster.name: es-prod-cluster node.name: node-1同一集群内所有节点的cluster.name必须完全一致。如果有一个节点手滑写错,它启动后会尝试去发现另一个集群,永远加不进来。节点名也必须唯一。我习惯把节点名和实际机器用途对上,比如node-1-master、node-1-data,日志里能看到问题节点是哪个。
网络绑定是一个需要谨慎的选项:
network.host: 0.0.0.00.0.0.0表示监听所有网卡,这样外网也能访问HTTP端口和传输端口。如果服务器没有防火墙,很容易被人扫描到并执行危险操作。建议明确绑定到内网IP:network.host: 192.168.10.11。或者分两项来配置:http.host绑到需要的网卡,transport.host绑到内网网卡。没有特殊需求就简化成一个内网地址,别用公网地址。
还要注意HTTP端口和传输端口。HTTP端口默认9200,用于REST接口;传输端口默认9300,用于节点间的数据通信。集群发现走的是传输端口,防火墙里要把所有节点之间的9300端口放通,仅放通9200是不足以组成集群的。
3.2 节点角色与数据路径
在8.x版本里,角色通过node.roles配置。如果这一项不写,默认该节点同时是master、data、ingest,这在小集群里问题不大,但大集群要明确指定。
一个纯master节点的配置:
node.roles: [master]一个数据节点配置:
node.roles: [data, ingest]一个协调节点配置:
node.roles: []node.roles: []表示该节点不担任任何专职角色,只作为协调节点。我第一次看到这个配置的时候还以为是写错了,后来才理解空数组就是“我只负责路由和汇总,别的都不干”。
数据路径和日志路径也很重要。默认就在解压目录下,但生产上通常数据目录要放到独立磁盘:
path.data: /data/elasticsearch path.logs: /var/log/elasticsearch创建这两个目录并授权给es_user。为什么数据目录要单独规划?因为ES的数据文件很大,而且会频繁写入,如果和系统盘混在一起,系统日志写满、ES数据写满,很容易互相拖累。独立挂载的磁盘会让故障范围可控。
3.3 集群发现与初始主节点
这一块是很多人组建集群失败的重灾区。ES需要知道有哪些节点可以加入集群,以及首次启动时由谁来完成集群初始化。在7.x和8.x中,核心配置是discovery.seed_hosts和cluster.initial_master_nodes。
假设三台机器IP是192.168.10.11、192.168.10.12、192.168.10.13,每台机器都要写:
discovery.seed_hosts: - 192.168.10.11 - 192.168.10.12 - 192.168.10.13 cluster.initial_master_nodes: - node-1 - node-2 - node-3seed_hosts相当于一个“初始联系人名单”。任何节点启动后,会先去名单里的地址联系,谁先启动谁就在等别人,后启动的节点通过这个机制发现已经存在的节点,然后加入集群。initial_master_nodes只用于集群首次引导,指定哪些节点有资格竞争集群的第一个“主节点”。这个配置只在第一次启动时起作用,之后节点重启不会覆盖集群已有的元数据。
集群形成后,再新增节点时不需要修改initial_master_nodes,只需要把新节点的IP加进现有节点的seed_hosts即可。这一点我经常看到有人搞错,每台机器都留着不同的initial_master_nodes,再重启后集群元数据就开始乱套。
3.4 设置JVM堆内存
JVM堆内存配置在config/jvm.options里,常见写法是:
-Xms31g -Xmx31g最好把初始堆和最大堆设置成一样大,避免JVM动态扩容导致性能抖动。堆内存的经典建议是物理内存的一半左右,但不要超过32GB。为什么是32GB?因为JVM对象指针在32GB以下可以开启压缩指针,超过32GB性能反而可能下降。当然,堆内存不是越大越好,堆太大时GC停顿时间会非常长,反而影响ES的实时响应和节点心跳。
如果机器内存是64GB,给堆31GB,剩下的给文件缓存,这个比例在大多数场景下比“64GB全给堆”效果好得多。文件缓存对搜索和写入提速非常明显,ES查询时大量分段数据都是从文件缓存里读的,堆内存反而主要用于索引结构、聚合结果和请求处理。
数据节点和主节点的堆大小可以不同。数据节点处理查询和写入,堆可以给足;纯主节点不存数据,堆给4GB到8GB就够,太大了反而浪费。协调节点看并发和聚合复杂度,一般8GB到16GB。
4. 集群启动与验证
4.1 首次启动前的检查清单
我每次拉新集群之前都会过一个检查清单,避免启动后反复出错。
第一,所有节点的cluster.name是否一致,节点名是否冲突。第二,节点角色是否按规划写清楚。第三,path.data目录是否存在且属主是es_user,path.logs同理。第四,系统参数是否改好,用sysctl vm.max_map_count确认。第五,防火墙是否放通了9300端口,以及9200端口。第六,三台机器的时间是否同步,ES对节点间时钟偏差很敏感,没装NTP的话后面各种奇怪的超时会找上门。
检查完这些再启动,能省掉至少一半的报错。特别是时钟同步,很多看起来像网络问题的故障,最后都是时区不对或时钟偏差过大导致的。
4.2 按顺序启动节点并观察日志
启动命令没有太多花样,切到es_user用户,进入ES安装目录:
bin/elasticsearch -d -p /var/run/elasticsearch.pid-d表示后台运行,-p把进程ID写到文件,方便后续用kill $(cat ...)来停止。不要用root启动,ES会直接报can not run elasticsearch as root。
启动顺序上,我建议先启动配置了cluster.initial_master_nodes的其中一台,让它完成集群初始化。观察日志是最直接的判断手段:
tail -f /var/log/elasticsearch/es-prod-cluster.log看到类似"master not discovered yet, this node has not previously joined a bootstrapped cluster"的日志,说明节点在等待主节点或者发现不了种子节点。如果这是第一台启动的节点,这个日志会持续一段时间,直到它自己成为主节点。如果等了很久还没成为主节点,就要回去看cluster.initial_master_nodes里的节点名是否和node.name完全一致,以及seed_hosts里的地址是否正确。
第二台节点启动后,日志里会出现成功加入集群的信息。第三台启动后,再检查_cluster/health,确保状态是green。
4.3 集群健康检查的几种方式
启动完成后,用REST接口检查是最快的:
curl -s http://192.168.10.11:9200/_cluster/health?pretty返回结果里重点看status和number_of_nodes。
status有三种状态。green说明所有主分片和副本分片都分配成功,集群完全健康。yellow说明部分副本分片没有可用副本,但主分片都在,集群可读可写,只是高可用打折扣。red说明有主分片未分配,部分数据会丢失或者索引不可用。刚搭建好集群如果只有单份分片且没有副本,状态可能是yellow,这不是集群故障,只是缺少副本。
如果发现number_of_nodes比预期少,就用以下命令看每个节点的状态和角色:
curl -s http://192.168.10.11:9200/_cat/nodes?v再结合_cat/shards看分片分布是否平衡:
curl -s http://192.168.10.11:9200/_cat/shards?v如果某个节点的分片数明显偏多,可能是角色配置或者磁盘水位设置导致的。集群自动会将分片均匀分布,但如果你手动改过分片分配策略,就得人工检查。
4.4 新版本启用了安全后的处理
8.x版本启动时默认开启了安全特性,第一次启动会自动生成elastic用户的密码,同时自动生成节点间的TLS证书。如果你是用tar包手动搭建,第一次启动后终端会打印一段包含密码的信息,一定要保存下来。如果没保存到,可以执行:
bin/elasticsearch-reset-password -u elastic重建密码。节点之间如果要加入集群,需要通过证书进行TLS通信。自动生成的证书默认只包含当前节点的证书,多节点集群需要把其他节点的证书纳入信任范围,或者统一生成集群证书。
如果只是内网测试环境,不想被安全配置折腾,可以在集群启动前显式关闭安全:
xpack.security.enabled: false这样节点间不需要TLS也能互相通信。但在生产环境我不建议关。ES的安全认证配合反向代理使用,是保护数据的底线。关掉安全等于把数据裸奔在网上,尤其是9200端口如果暴露到外网,很容易被攻击。
5. 从踩坑到稳定:常见问题与维护建议
5.1 常见启动报错与排查速查
我整理过一张踩坑表,每次排查都从这里面找方向。
| 现象 | 典型原因 | 解决办法 |
|---|---|---|
| max file descriptors [4096] too low | ulimit未生效 | 修改limits.conf,重新登录,确认es_user的nofile值 |
| max virtual memory areas vm.max_map_count [65530] too low | 系统vm.max_map_count过小 | sysctl -w vm.max_map_count=262144,写进sysctl.conf |
| master not discovered yet | 集群发现配置不对或等待初始化 | 检查seed_hosts地址和initial_master_nodes节点名 |
| failed to send join request | 防火墙隔离或transport端口不通 | 检查9300端口放通,节点间ping或telnet测试 |
| unassigned shards | 磁盘水位过高或副本配置问题 | 查看_cat/allocation和_cat/shards,执行reroute或放宽水位 |
| authentication exception | 安全认证配置不一致 | 检查用户名密码,节点间信任证书是否统一 |
| no persistent, no transient | 节点角色为空且数据路径无元数据 | 检查node.roles是否误配,或清空path.data重新引导 |
一个经常被忽略的问题是“磁盘水位”。ES默认当磁盘使用率达到85%时,不再分配新分片;到90%时,会尝试把分片迁移到其他节点;到95%时,当前节点会强制变为只读。所以如果你看到某个节点的分片无法分配,先看磁盘空间,不要急着调分片数量。
5.2 集群扩容和缩容时的注意点
集群不是一次性搭完就完事,后续扩容是必然的。新增节点时,在现有节点的discovery.seed_hosts里加入新节点IP,然后新节点配置里也把旧节点的IP写进seed_hosts,启动后它会自动加入。加入后ES会自动重新平衡分片,你不需要手动把分片挪过去,但要注意观察分片迁移过程,避免大量数据迁移占用磁盘IO。
缩容时,不能直接kill节点。正确做法是先执行:
curl -X PUT http://192.168.10.11:9200/_cluster/settings -H 'Content-Type: application/json' -d '{ "transient": { "cluster.routing.allocation.exclude._name": "node-4" } }'这句的作用是让集群把node-4上的分片慢慢迁移到其他节点,迁移完成后再下线那台机器。如果直接关机,ES会把分片标为未分配,然后重新分配副本,虽然也能恢复,但会有短暂的集群状态波动,而且如果同时下多台,甚至会丢数据。
5.3 日常维护的几个好习惯
集群稳定运行靠的不只是搭建那一下,更多是日常维护。至少要把这几项做起来。
第一,监控JVM堆内存使用率。堆使用率如果持续超过85%,频繁触发GC,说明索引结构、聚合或缓存配置有问题。第二,监控磁盘使用率,自动告警要设在80%之前。第三,定期清理旧索引。日志类的索引一般按天滚动,建议用索引生命周期管理策略,自动把几天前的索引从热节点转到冷节点或者删除。
我还有一个习惯:每天检查一下_cluster/allocation/explain,看是否存在未分配分片。哪怕集群状态是green,也要定期看,因为分片重分配可能随时发生。
最后,备份这件事一定要做。脱离集群环境,用快照方式把关键索引备份到对象存储或者另一台机器。ES集群再稳,也怕机房断电、磁盘损坏、人为误操作。定期快照是最后的保险丝。
最后再分享一个小习惯
每次启动完ES集群,我都会在节点上留一个简单的端口检测脚本,定时检查9200和9300是否监听,并记录日志。看似多此一举,但很多问题其实是你早上到工位之后发现集群挂了,而挂掉的时间点、当时的日志、当时的系统负载,这些信息如果没留下记录,排查起来全靠猜。把这些基础工作做好,集群的维护成本会低很多。