把 Elasticsearch 跑在容器里,单节点通常只需要一条 docker run 命令,但要让三个节点组成集群,情况就完全不同了。节点之间如何互相发现、数据目录如何持久化、JVM 堆内存如何限制,这些都会直接影响到集群能否正常选出主节点以及能否在高负载下稳定运行。本文用 Docker Compose 编排一个三节点 Elasticsearch 集群,并逐步说明每个配置项的作用。

一、容器环境下的节点发现机制
Elasticsearch 从 7.x 开始逐步用基于种子主机的发现机制替代了旧的 Zen Discovery。在容器环境中,节点的 IP 地址会随着容器重建而变化,因此必须使用可解析的服务名作为种子地址。Docker Compose 默认会为每个服务创建 DNS 记录,服务名就可以直接作为主机名使用。比如设置 discovery.seed_hosts=es01,es02,es03 后,每个节点启动时会向这三个地址发起握手,从而找到集群中的其他成员。
除了种子主机,还需要指定哪些节点有资格成为主节点。对于三节点集群,通常把所有节点都设置成主节点候选,这样任意一个节点故障后,剩下的两个节点仍然可以完成主节点选举。对应的配置项是 cluster.initial_master_nodes=es01,es02,es03。这个参数只在集群第一次启动时生效,之后可以保留但不会影响已经存在的集群状态。
如果只设置了种子主机而没有设置初始主节点,集群可能在启动时出现主节点未发现的异常。反过来,如果三个节点同时启动但网络没有就绪,也可能导致选主超时。因此生产环境建议给节点设置重试次数和超时时间,例如 discovery.zen.minimum_master_nodes 在 7.x 之前至关重要,新版本已经由内置的多数派策略自动处理。
二、用 Docker Compose 编排三节点集群
下面是一份可以直接运行的三节点配置。每个节点使用相同的镜像,但需要不同的服务名和端口映射。为了模拟真实环境,这里只暴露 es01 的 9200 端口给宿主机,另外两个节点只在内网中通信,这样可以减少端口冲突。
version: '3.8'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata01:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- esnet
es02:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: es02
environment:
- node.name=es02
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata02:/usr/share/elasticsearch/data
networks:
- esnet
es03:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: es03
environment:
- node.name=es03
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata03:/usr/share/elasticsearch/data
networks:
- esnet
volumes:
esdata01:
driver: local
esdata02:
driver: local
esdata03:
driver: local
networks:
esnet:
driver: bridge
这段配置中,bootstrap.memory_lock=true 用来锁定 JVM 内存,防止操作系统把 Elasticsearch 进程交换到磁盘。ES_JAVA_OPTS 指定堆内存初始值和最大值,两者必须一致,否则运行时调整堆大小会触发频繁的垃圾回收。容器默认启动用户是 elasticsearch,挂载的数据目录权限需要确保该用户可写,否则会报权限错误。
注意这里关闭了安全认证,只适合本地开发。生产环境应开启 xpack.security.enabled=true 并配置证书。三个节点共享同一个 cluster.name,这是它们属于同一集群的基本条件。如果某个节点的集群名不一致,它要么自己组成单节点集群,要么直接拒绝加入现有集群。
三、宿主机参数调整与数据持久化
Elasticsearch 在 Linux 上对虚拟内存映射数量有要求,通常需要执行 sysctl -w vm.max_map_count=262144。如果没有调整,容器日志里会出现 max virtual memory areas vm.max_map_count [65530] is too low 的错误。这个错误在 Docker Desktop for Mac/Windows 中也可能出现,但调整位置不同:需要进入 Docker 虚拟机执行命令,或者直接在宿主机配置文件中写入 vm.max_map_count=262144 然后重启。
数据持久化是另一个重点。容器本身是无状态的,如果数据保存在容器可写层,删除容器后索引数据就永久丢失。上面的配置通过命名卷 esdata01、esdata02、esdata03 把数据目录映射到 Docker 的卷存储中。这样做的好处是即使容器重建,数据仍然存在。但命名卷默认存储在 Docker 管理的目录下,生产环境更推荐使用 bind mount 把数据目录指向独立磁盘,例如 /data/es01:/usr/share/elasticsearch/data。
另外还需要关注文件描述符限制和线程数限制。ulimits.memlock=-1 只是解锁内存锁定,文件句柄默认可能只有 1024,高并发查询时会不够用。可以在服务定义中增加 ulimits.nofile 并设置 soft 和 hard 都为 65536。如果使用 systemd 管理 Docker,还需要修改 /etc/systemd/system/docker.service 中的 LimitNOFILE。这些细节容易被忽略,但往往是大集群运行一段时间后突然出现“Too many open files”的根源。
四、验证集群状态与常见故障排查
启动集群后,可以通过 curl -s http://localhost:9200/_cluster/health?pretty 查看健康状态。刚启动时可能显示 status: red 或 yellow,这是因为主分片还没有完成分配。对于三节点集群,只要三个节点全部加入,且索引至少有 0 个副本,状态最终会变为 green。也可以用 curl -s http://localhost:9200/_cat/nodes?v 列出节点信息。
如果发现某个节点没有加入集群,第一步是检查该节点的日志,通常会出现 ConnectTimeoutException 或 failed to resolve host。前者说明种子主机配置错误,后者说明服务名无法解析。可以在宿主机上执行 docker network inspect esnet 查看容器 IP 是否在同一个网段,再进入容器内部执行 ping es01 验证 DNS 解析。
另一种常见问题是堆内存不足。默认 ES_JAVA_OPTS 通常会根据容器内存自动推断,但 Docker 容器看到的可能是宿主机的全部内存,导致 Elasticsearch 设置了过大的堆。建议显式指定 -Xms 和 -Xmx,并保持不超过宿主机物理内存的一半。对于 4GB 内存的开发机,每个节点给 512m 到 1g 比较合适。内存锁定开启后,如果容器内存限制小于 JVM 堆大小,容器会直接 OOM,可通过 docker stats 观察内存使用情况。
如果需要启用安全认证,可以在第一次启动后进入 es01 容器执行 bin/elasticsearch-setup-passwords interactive 设置内置用户密码,然后更新其他节点的安全配置并重启。整个过程要保证所有节点使用相同的证书和密钥,否则节点之间无法建立加密通信。对于开发环境,可以先通过环境变量关闭安全认证,等业务逻辑验证完成后再逐步开启。
Elasticsearch容器集群Docker Compose修改时间:2026-10-05 06:53:53