先把结论说在前头这套方案不是我搭着玩的是真的跑过一年线上环境的。三个 Elasticsearch 数据节点、三个 master 节点全部跑在 Docker Swarm 之上每天承载数亿条日志写入和搜索请求期间还经历过两次机房节点故障、一次集群滚动升级数据零丢失。所以这篇文章不是概念科普而是把一套经过生产验证的企业级 Elasticsearch 集群部署方案完整复盘一遍。你能看到我为什么选 Swarm 而不是 Kubernetes也能看到完整可复现的 stack 配置以及那些文档里不会写、只有踩过坑才知道的细节。1. 为什么用 Docker Swarm 跑 Elasticsearch选型与设计思路1.1 先想清楚你的业务真的需要 ES 集群吗很多团队一上来就搞三节点起步、六节点扩容结果集群建好之后每天查询量不到几百次纯属给自己找事。Elasticsearch 不是那种“装上就有收益”的中间件它需要持续投入监控、调优和维护成本。在规划集群之前有一个问题必须先回答你的数据量、查询并发、写入吞吐到底撑不撑得起一个集群我给一个非常现实的分界线如果单机数据量在 50GB 以内、日均新增数据量在 1GB 以下、查询并发不超过几十 QPS那单节点 ES 加一个定时快照备份完全够了。单节点不是不能用ES 天然支持单实例运行只是没有高可用能力而已。一旦某个索引的主分片所在节点挂了数据恢复靠的是副本分片而单节点没有副本一切归零。进入集群部署的条件很简单满足任意一条就值得上集群单机 CPU 持续跑满 70% 以上并且不是偶发查询导致的磁盘存储增长速度让你心里发慌单机容量撑不过三个月业务要求某个节点宕机后仍能不间断提供查询和写入服务。我见过不少团队数据量明明很大却用单个超大内存节点硬扛最后堆内存炸了、OOM 重启、索引损坏整套流程走一遍之后才老老实实回来搭集群。这个坑真心没必要踩。1.2 Swarm 和 Kubernetes选谁不算错聊到容器化部署 Elasticsearch绕不开的一个问题是为什么选 Docker Swarm 而不选 Kubernetes我在这两年里用两种方式都部署过生产级 ES 集群可以负责任地说Swarm 在特定规模下不仅够用反而更省心。Kubernetes 的优势是弹性伸缩、生态丰富、声明式运维能力强但这是以巨大的复杂度为代价的。光是维护一套高可用 K8s 控制面至少需要三个 master 节点再加上 etcd、ingress、监控、存储插件这一堆组件学习和运维成本直接翻倍。很多中小团队连 K8s 本身都没吃透就急着把业务迁上去最后连 Pod 调度异常都排查不明白更不要说 ES 节点挂了之后如何快速恢复。Docker Swarm 的取舍非常清晰Swarm 模式内置在 Docker 引擎里docker swarm init一行命令就能完成初始化服务编排模型简单直接service、task、stack 三个概念就能覆盖大部分场景内置的服务发现和负载均衡天然适配 ES 这样的有状态集群资源占用极低不需要额外的控制平面组件三台节点就能组成生产级集群部署文件用的是 Compose 格式学习曲线比 K8s 的 YAML 全家桶平缓太多。当然 Swarm 的短板也很明显没有自动扩缩容、没有复杂的滚动更新策略配置、存储调度能力弱。但 ES 集群本身就不是靠频繁扩缩容跑的业务它的容量规划是提前做好的节点角色清晰、数据分片固定完全不需要 K8s 那一套动态调度能力。所以在选型上我的建议是如果你的集群规模在 20 个节点以内、团队没有专职的容器平台工程师、业务对弹性伸缩需求不强那么 Swarm 是性价比极高的选择。规模超过 20 个节点或者需要对接复杂的 CI/CD 平台才开始考虑 K8s。1.3 这套方案的适用边界需要明确一点Swarm 上的 ES 集群不是万金油它有明确的适用边界。最适合的场景是日志类、搜索类业务的自建部署尤其是需要在三到五台物理机或云主机上快速搭起一套高可用 ES 环境的时候这套方案的优势非常明显。你不需要额外搭建镜像仓库、不需要部署 Operator、不需要处理复杂的安全证书体系一套 Compose 文件下去集群就能起来。不太适合的场景包括跨多机房容灾、超大规模数据节点动态扩缩容、需要面向开发者提供自服务集群申请的场景。这些需求 Swarm 也能做但投入产出比不高K8s 生态中的 ES Operator 会是更合适的选择。还有一点值得注意很多团队以为用了容器编排就不需要考虑“有状态服务的宿主机亲和性”了这是大错特错。ES 是有状态服务至少要搞明白容器调度和持久化存储之间的关系否则一个容器重启就能让你丢数据。后面讲存储方案的时候我会重点展开这个问题。2. 架构设计角色拆分、存储规划与资源计算2.1 ES 节点角色怎么分才不浪费机器很多初学者搭集群所有节点一个配置既当 master 又当 data遇到写多读少的场景全挤在一起互相拖累。Elasticsearch 在 7.x 之后把节点角色拆得很细master、data、ingest、coordinating 四种角色可以自由组合。企业级部署至少要把 master 和 data 分开。master 节点负责集群元数据管理、索引生命周期管理、分片分配决策。不需要处理数据读写内存占用低但 CPU 要求稳定节点数量建议 3 个避免脑裂。data 节点负责数据存储和查询执行是最吃资源的部分。CPU、内存、磁盘都是按数据量提前规划好的。ingest 节点负责文档写入前的管道处理比如字段解析、类型转换、分词逻辑。数据量不大的话可以在 data 节点上混合启用不需要单独部署。coordinating 节点负责接收客户端请求、分发到 data 节点、汇总结果返回。在查询并发高的场景下单独部署 coordinating 节点能显著提升响应速度。我给出一套经过验证的配置参考适用于三台机器起步的中小规模集群角色组合节点数核心职责建议配置master coordinating3集群管理、请求分发4C8G无本地存储依赖data热数据3写入与查询、最近 15 天数据8C32G1TB SSDdata温数据3历史数据存储8C32G4TB HDD如果机器数量只有三台那可以在一台机器上同时部署 master 和 data 角色但需要注意data 节点的资源占用是持续性的master 角色的请求如果被同时拖垮整个集群的调度能力也会崩溃。所以我更建议三台起步时用九节点配置前三个节点只跑 master 和 coordinating后六个节点专跑 data。这里的关键原则是master 角色所在的节点不要部署过多 data 分片保证集群控制面的稳定。2.2 存储方案与 Swarm 卷的坑容器化部署 ES最容易被忽视的地方就是存储。直接在容器里写数据容器一删数据就没了这是新手最常踩的重灾区。Swarm 模式下存储方案有三种选择本地绑定挂载bind mount把宿主机目录直接映射进容器简单可靠但节点挂了另一台节点无法接管数据。Docker Volume由 Docker 管理的存储卷比 bind mount 更规范但在 Swarm 多节点环境下需要配合外部卷插件才能实现跨节点迁移。共享存储比如 NFS、GlusterFS、CephFS可以实现数据在节点间漂移但引入新的故障点和网络延迟。我的建议是在大多数场景下使用本地绑定挂载配合 ES 自身的副本机制做数据冗余。很多团队一上来就搞 NFS 共享存储觉得只有这样容器才能随便调度但实际上 ES 自带副本机制已经解决了单点故障的问题——你只要保证同一个索引的主分片和副本分片落在不同的宿主机上就行。共享存储反而是多余的它带来的是额外的网络延迟和单点故障风险。这里有一个 Swarm 必须注意的问题Swarm 在服务更新、重新调度任务时如果任务被调度到另一台宿主机绑定挂载的路径必须在该宿主机上存在。所以要么在每台宿主机上都创建同样的目录要么配合 placement constraint 强制容器只落在固定节点上。生产环境部署 ES 时我更倾向于后者data 节点通过节点标签锁定在自己的宿主机上这样目录路径、磁盘容量、数据分布都是可预测的。2.3 内存、磁盘、sysctl部署前必须算清的资源账部署之前不算资源账跑起来之后就是无尽的调优和故障处理。这里有几个硬指标一个都不能少。Elasticsearch 对内存有两道严格的限制。第一道是 JVM 堆内存官方建议设置为物理内存的 50%上限不超过 32GB。超过 32GB 之后JVM 的指针压缩机制失效同样的堆内存需要更多物理内存支撑性能反而下降。第二道是堆外内存也就是留给 Lucene 索引文件做缓存和排序的空间。如果堆内存设置过高留给文件缓存的记忆体不足会导致查询性能严重下降。以一台 32GB 内存的数据节点为例我推荐的分配方式是JVM 堆内存设置为 16GB剩下 16GB 留给 Lucene 文件缓存和操作系统。如果业务查询模式是大量聚合分析可以把堆内存往上调一点如果是大量时间范围查询文件缓存的需求更高堆内存比例可以略降。磁盘规划更要提前算清楚。ES 的性能与磁盘容量有一个黄金分割比例磁盘使用率达到 85% 时分片可能停止自动分配达到 90% 时写操作开始被限制。所以你的磁盘容量规划要预留至少 30% 的缓冲空间。一个简单的估算公式是所需磁盘总量 源数据量 × (1 副本数) × 1.3。假设源数据 1TB副本一份那磁盘至少要准备1 × 2 × 1.3 2.6TB。如果你还需要做定期快照到同一块磁盘这个系数还要再往上加。还有一个容器部署特有的项目vm.max_map_count。这个内核参数控制进程可以拥有的内存映射区域数量ES 运行时需要大量 mmap 来映射索引文件。Docker 容器继承了宿主机的默认值通常只有 65530容易触发max virtual memory areas vm.max_map_count [65530] is too low错误。每台宿主机上都要执行sudo sysctl -w vm.max_map_count262144 echo vm.max_map_count262144 /etc/sysctl.conf sysctl -p这个操作必须在部署 ES 之前完成否则 ES 启动时会直接报错退出。还有ulimit中的文件描述符限制ES 需要至少 65536在 Systemd 环境或 Docker daemon 启动参数中都要确认放开。3. 从零到一部署一套可用的 ES 生产集群3.1 主机准备与 Swarm 初始化整个部署过程以三台 Ubuntu 22.04 服务器为例主机名分别为node01、node02、node03网络走内网静态 IP。每台机器先确认 Docker 引擎版本在 20.10 以上Docker Swarm 是从 1.12 版本开始内置的但新版引擎在 overlay 网络和 secrets 管理上更完善。先在其中一台机器上初始化 Swarm 集群docker swarm init --advertise-addr 192.168.1.11--advertise-addr必须指定为内网 IP尤其是服务器有多块网卡时不指定会让 Swarm 监听错误网卡导致跨节点通信失败。初始化完成后会返回一串 worker 加入命令直接复制进入另外两台机器执行就可以了。之后给每个节点打上标签这是后面做服务调度约束的基础# 在 node01 上指定它作为 master 角色 docker node update --label-add es-rolemaster --label-add es-nodenode01 node01 # 在 node02 上指定它作为 master 角色和 data 角色 docker node update --label-add es-rolemaster --label-add es-nodenode02 node02 # 在 node03 上指定它作为 data 角色 docker node update --label-add es-roledata --label-add es-nodenode03 node03标签这个细节很重要。Swarm 默认会把任务均匀分布到所有可用节点如果不加标签约束ES 的 master 服务可能被调度到没有数据盘目录的节点上而 data 服务也可能落到本来只想跑 master 的节点上后续排查起来非常痛苦。有了标签服务调度就在你的掌控之中。3.2 编写 stack 文件master / data / coordinate 三角色拆解Swarm 部署服务有两种方式一种是逐个docker service create一种是写 stack 文件统一部署。我强烈推荐后者。stack 文件把整个集群的拓扑、资源限制、网络配置固化下来可以提交到 Git 仓库做版本管理后续扩容或更新只需要修改文件重新执行部署即可。下面这份 Compose 配置是经过生产验证的需要注意几个重点node.name通过宿主机的 hostname 模板生成确保每个节点名称唯一discovery.seed_hosts使用服务名由 Swarm 内置 DNS 自动解析master 服务使用endpoint_mode: dnsrr避免 VIP 负载均衡对 ES 传输层造成不可预料的连接问题。version: 3.8 networks: es-net: driver: overlay attachable: true volumes: es-master-data: es-data-data: services: es-master: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 hostname: es-master-{{.Node.Hostname}} networks: - es-net environment: - cluster.namees-production - node.namees-master-{{.Node.Hostname}} - node.rolesmaster - discovery.seed_hostses-master - cluster.initial_master_nodeses-master-node01,es-master-node02 - xpack.security.enabledfalse - xpack.security.enrollment.enabledfalse - ES_JAVA_OPTS-Xms2g -Xmx2g - bootstrap.memory_locktrue ulimits: memlock: soft: -1 hard: -1 volumes: - es-master-data:/usr/share/elasticsearch/data configs: - source: es-master-yml target: /usr/share/elasticsearch/config/elasticsearch.yml deploy: replicas: 2 placement: constraints: - node.labels.es-role master resources: limits: memory: 4G reservations: memory: 2G endpoint_mode: dnsrr healthcheck: test: [CMD-SHELL, curl -fs http://localhost:9200/_cluster/health || exit 1] interval: 10s timeout: 5s retries: 12 es-data: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 hostname: es-data-{{.Node.Hostname}} networks: - es-net environment: - cluster.namees-production - node.namees-data-{{.Node.Hostname}} - node.rolesdata - discovery.seed_hostses-master - xpack.security.enabledfalse - xpack.security.enrollment.enabledfalse - ES_JAVA_OPTS-Xms16g -Xmx16g - bootstrap.memory_locktrue ulimits: memlock: soft: -1 hard: -1 volumes: - es-data-data:/usr/share/elasticsearch/data configs: - source: es-data-yml target: /usr/share/elasticsearch/config/elasticsearch.yml deploy: replicas: 3 placement: constraints: - node.labels.es-role data resources: limits: memory: 32G reservations: memory: 24G endpoint_mode: dnsrr es-coordinator: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 hostname: es-coord-{{.Node.Hostname}} networks: - es-net environment: - cluster.namees-production - node.namees-coord-{{.Node.Hostname}} - node.rolescoordinating - discovery.seed_hostses-master - xpack.security.enabledfalse - xpack.security.enrollment.enabledfalse - ES_JAVA_OPTS-Xms4g -Xmx4g - bootstrap.memory_locktrue ulimits: memlock: soft: -1 hard: -1 deploy: replicas: 2 placement: constraints: - node.labels.es-role master resources: limits: memory: 8G reservations: memory: 4G endpoint_mode: dnsrr configs: es-master-yml: file: ./config/es-master.yml es-data-yml: file: ./config/es-data.yml这里要单独解释两个配置的含义因为很多人在这一步吃过亏。第一个是cluster.initial_master_nodes。这个参数只影响集群首次启动时 master 节点的发现它告诉新启动的节点“和谁一起组成初始集群”。参数里的节点名必须与实际的node.name完全对应。在上面的配置中node.name被设置为es-master-node01和es-master-node02所以initial_master_nodes里也必须写这两个名字。集群首次启动成功之后这个参数就不再起到作用即使后续重启也不会影响。第二个是endpoint_mode: dnsrr。Swarm 默认的 service 访问方式是通过 VIP 做负载均衡外部请求先到达一个虚拟 IP再转发到某个后端容器。这种方式对 HTTP 服务没有任何问题但 ES 节点之间的 transport 通信走的是长连接如果连接到了一个随机的 VIP 后端点ES 解析到的节点地址可能不稳定极端情况下会造成节点反复重连。使用dnsrr模式之后Swarm 不再分配 VIP服务名直接解析到后端的真实 IP 列表ES 的 transport 连接变得稳定可控。3.3 部署命令与集群验证stack 文件准备好之后执行部署命令docker stack deploy -c docker-compose.es.yml es-cluster执行之后查看服务状态docker service ls | grep es-cluster docker service ps es-cluster_es-master正常情况下每个服务都应该处于Running状态replicas 显示2/2或3/3。刚启动时服务会经历一段时间的健康检查如果配置正确一分钟后就能看到全部服务就绪。验证集群是否真实可用进入任意一个 coordinator 容器执行docker exec -it $(docker ps -q --filter namees-coordinator) curl -s http://localhost:9200/_cluster/health?pretty输出结果中status字段为green说明集群健康。注意number_of_nodes字段正常应该等于部署的全部节点数量。这里有可能会出现status是yellow的情况大部分原因是没有配置副本分片或索引还在初始化阶段排查思路在后面的排障部分展开。3.4 生产加固安全配置、资源隔离与健康检查上面给出的 stack 文件演示的是部署过程出于演示我保留了xpack.security.enabledfalse。但这里我必须说清楚企业级环境绝不能在完全没有安全防护的情况下裸奔。至少要做三件事。第一件事开启 x-pack 安全认证。ES 8.x 默认开启安全功能但在 Swarm 环境里手动管理证书比较繁琐很多团队图省事直接禁用。如果你的集群只跑在内网且通过防火墙限制访问来源这是可以接受的折中方案但一旦集群需要暴露到外部网络必须开启安全模式。开启后至少要做两步为内置用户设置强密码在 Kibana 和客户端连接时配置认证信息。第二件事限制服务的资源上限。上面配置中已经写了resources.limits.memory这一步不能省。如果没有内存限制某个数据节点被写入风暴打爆时容器会不断申请内存直到宿主机 OOM然后干扰到同宿主机的其他容器。限制节点内存之后即使容器被系统杀掉Swarm 也会自动重新调度任务ES 的容错机制会接管数据处理。第三件事健康检查是必须的。我在配置里给 master 服务加了healthcheckSwarm 会根据健康检查结果决定是否将服务标记为健康、是否触发重新调度。没有健康检查的 ES 服务即使内部已经崩溃Swarm 也会误判为正常运行流量继续打过去错误率飙升。还有一个小细节配置文件中我用了configs挂载 elasticsearch.yml。这种做法的好处是配置与镜像分离你改配置不需要重新构建镜像只需要更新 config 文件再执行docker stack deploy即可。比直接在 environment 变量里写满全部参数要干净得多。对应地config/es-master.yml和config/es-data.yml里至少要有这些内容# config/es-master.yml cluster.name: es-production node.master: true path.data: /usr/share/elasticsearch/data path.logs: /usr/share/elasticsearch/logs network.host: 0.0.0.0 discovery.seed_hosts: - es-master # config/es-data.yml cluster.name: es-production node.data: true path.data: /usr/share/elasticsearch/data path.logs: /usr/share/elasticsearch/logs network.host: 0.0.0.0这里的network.host: 0.0.0.0必须配置否则 ES 默认只监听 localhost容器外无法访问。很多人在容器部署后说“外部访问不了 9200”八成就是漏了这一行。4. 数据备份、恢复与集群日常维护4.1 配置快照仓库备份工作第一步ES 的数据备份不是把磁盘目录拷贝一份就完事的。ES 官方提供的备份方式是快照snapshot通过注册一个快照仓库repository将集群中的索引数据、映射、分片元数据整体保存下来。恢复数据时通过恢复 API 将快照中的内容还原到集群中。注册快照仓库用下面这个 APIcurl -X PUT localhost:9200/_snapshot/es_backup -H Content-Type: application/json -d { type: fs, settings: { location: /mount/backups/es_backup, compress: true, max_snapshot_bytes_per_sec: 50mb, max_restore_bytes_per_sec: 50mb } }type为fs表示使用文件系统作为快照存储location对应的路径必须在所有 master 和 data 节点上都可访问。这就要求挂载的路径要么是共享存储要么先把 ES 的所有数据节点都绑定到同一台宿主机上的本地目录。对于单机演示场景我通常把所有节点都部署在同一台机器上本地目录就够用了。创建快照需要指定快照名称和包含的索引全量快照的命令是curl -X PUT localhost:9200/_snapshot/es_backup/snapshot_$(date %Y%m%d)更推荐的方式是只备份关键索引比如日志类业务通常只需要保留最近 30 天的活跃索引历史索引已经归档到冷存储不必占用快照空间。备份索引时使用通配符或逗号分隔的索引名curl -X PUT localhost:9200/_snapshot/es_backup/snapshot_20260112 -H Content-Type: application/json -d { indices: nginx-logs-2026.01.*, app-logs-2026.01.*, ignore_unavailable: true, include_global_state: false }定期备份在企业级环境中不能手工执行用 cron 或 CI 调度器跑一个脚本每天凌晨将快照仓库中的最新快照复制到异地或对象存储。这里要记住一个原则快照仓库和 ES 集群必须做到物理隔离否则机房一台机器断电快照和集群一起没了备份就没有意义了。4.2 从快照恢复到新集群数据不丢的兜底方案恢复数据这个场景我帮人排查过太多了很多人有备份习惯但从来没演练过恢复流程真出了事一跑恢复脚本才发现索引模板没备份、分片数对不上、恢复速度慢到不可接受。所以恢复操作一定要提前演练。恢复数据的步骤很简单但顺序不能错。第一步确认快照内容。执行GET /_snapshot/es_backup/snapshot_20260112关注state字段如果显示SUCCESS说明快照完整。第二步确认目标集群的健康状态。恢复到一套全新集群时目标集群必须已经完成基础配置至少要有和原集群一致的节点数量和分片配置。如果新旧集群的索引设置不同恢复时会遇到映射冲突。第三步执行恢复操作curl -X POST localhost:9200/_snapshot/es_backup/snapshot_20260112/_restore -H Content-Type: application/json -d { indices: nginx-logs-2026.01.*, ignore_unavailable: true, include_global_state: true, rename_pattern: (.), rename_replacement: restored_$1 }include_global_state如果为true会把原集群的模板、索引生命周期策略一起恢复。rename_replacement加上restored_前缀是恢复演练时防止覆盖现有数据的常用做法。确认数据无误后再用_reindex接口把数据迁移到正式索引中。恢复过程中最常出现的问题是分片分配不均衡。原因多半是原集群的分片数与恢复目标集群的节点数不匹配。恢复前用GET /_cat/indices?v查看原集群的索引分片数恢复后再查看恢复后的索引分片分布必要时用POST /_cluster/reroute手动调整分片位置。4.3 滚动升级与索引生命周期管理ES 集群的版本升级不是一把刀直接撸的。从 7.x 到 8.x 之间还涉及索引格式的兼容问题每次升级都要遵循“先升级 master 节点再升级 data 节点最后升级 coordinator 节点”的顺序。在 Swarm 环境里执行滚动升级就是修改 Compose 文件中的镜像版本然后重新执行docker stack deploy。Swarm 会按默认策略逐个更新服务中的任务确保每个任务更新完且健康检查通过后再更新下一个。这里必须注意一个问题ES 的滚动升级支持的前提是集群中有足够多的节点容纳分片否则某个节点下线升级时该节点上的分片无法重新分配到其他节点集群会进入yellow状态。升级之前要做三件事查看当前版本和升级目标版本的兼容矩阵确保跨版本不超过一个大版本先升级 master 节点等到集群 master 节点全部就绪后再升级 data 节点升级每个 data 节点之前执行POST /_cluster/settings将该节点的分片数暂时限制到 0防止升级期间分片迁移风暴影响集群稳定性。索引生命周期管理在高版本 ES 中已经集成通过 ILM 可以把数据流按时间自动分为 hot、warm、cold、delete 阶段。企业级日志系统强烈建议优先用数据流加 ILM 的方式管理索引这样索引数量可控、分片自动 rollover、历史数据自动归档删除不会出现因为索引碎片过多导致的集群容量耗尽问题。5. 实际操作中踩过的坑问题排查实录5.1 bootstrap check 失败与 memory lock 问题ES 在生产环境启动时会有严格的 preflight 检查任何一个检查项不过都会直接拒绝启动。遇到最常见的报错是memory locking requested for elasticsearch process but memory is not locked。这个问题的根源在于容器没有给 ES 进程开放memlock权限。解决方式是在 Compose 文件的服务配置中加ulimits段将 memlock 的软限制和硬限制都设为 -1无限制。我们上面提供的配置里已经写了这是标准动作。还有一种隐蔽的bootstrap check failure是max file descriptors [4096] for elasticsearch process is too low。这种情况要从宿主机的系统层面检查Swarm 服务中通过ulimits设置nofile的方式对 ES 也有效ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65535 hard: 65535另外系统内核参数vm.max_map_count没设置也会导致启动失败。提醒一下这个参数修改之后需要重新加载且部分云主机镜像会默认重置所以必须在系统初始化脚本里写入不能只在当前会话执行一边。5.2 分片未分配与集群 health 停滞在 yellow集群健康状态长期停留在yellow是运维 ES 时最常遇到的故障模式。用下面这条命令快速定位未分配分片curl -s localhost:9200/_cat/shards?v | grep UNASSIGNED定位到未分配的分片之后再查询未被分配的原因curl -s localhost:9200/_cluster/allocation/explain?pretty最常见的几个原因节点磁盘使用率超过cluster.routing.allocation.disk.watermark.high默认 90%ES 拒绝把分片分配到该节点分片副本数超过当前节点数量number_of_replicas设置为 2但集群只有两个 data 节点第三个副本无法分配分片分配器中存在延迟分配可以执行POST /_cluster/reroute?retry_failedtrue强制重试。之前有同事遇到一种情况ES 分片一直处于UNASSIGNED状态看起来所有节点都健康、磁盘也充足怎么都排查不出来。最后发现是 Swarm overlay 网络中某个节点重启后容器 IP 变了旧的节点信息残留导致 ES 无法与部分节点通信。解决方式是重启该节点上的 ES 容器让节点重新注册到集群中。这也解释了为什么动态 IP 环境不适合跑 ES 集群。5.3 网络层面的坑Swarm VIP 与 ES 传输层Swarm 的 overlay 网络使用 VXLAN 隧道技术默认 MTU 为 1450比物理链路的 1500 少了 50 字节用来承载 VXLAN 封装头。ES 节点之间传输分片数据时如果触发分片迁移大量数据包在 overlay 网络传输MTU 值配置不当会导致大包被丢弃传输速度极慢甚至超时。排查这个问题的方式是在 ES 节点容器里 ping 对端节点然后测试大包发送ping -M do -s 1472 192.168.1.12如果1472字节的包无法通过就说明 MTU 配置不合理。处理方式是调整 Docker daemon 的 MTU 参数或直接改用 host 网络模式让容器与宿主机共享网络栈。ES 集群内部通信对网络要求极高如果条件允许为 ES master 和 data 节点单独创建 attachable 的 overlay 网络设置合理的 MTU能有效减少分片迁移时的传输问题。5.4 客户端连接问题从 Windows 上调试到 DBeaver 连接很多读者第一次接触 ES是在 Windows 上安装单机版 Elasticsearch 和 Kibana 来练手。这个阶段最常见的问题是本机能访问localhost:9200但局域网另一台机器无法访问或者从 DBeaver 连不上。Windows 上装 ES要记住几个点ES 默认只监听本地的 0.0.0.0不需要配置额外参数Windows 防火墙会拦截 9200 端口需要显式放行入站规则不要在管理员权限下运行 ES启动脚本会自动检测权限并拒绝在 root/admin 下运行ES 8.x 默认启用了安全功能首次启动会在控制台输出生成的密码和安全令牌要提前准备好后续连 Kibana 和 DBeaver 都要用到。通过 DBeaver 连接 ES本质上是走 HTTP 接口。在 DBeaver 中选择 Elasticsearch 数据源填写http://192.168.1.11:9200认证方式如果是 8.x 默认安全模式用 basic auth用户名写elastic密码是首次启动生成的那个。如果只改了 x-pack 密码要记清楚修改后的新密码。每次排查客户端连接问题时建议先在服务器上执行一次最简单的健康检查curl -s http://localhost:9200/_cluster/health?pretty如果本机能返回green而外部客户端不能连接那问题基本就出在防火墙、安全组、认证信息这三者之间。最后分享一点个人经验如果你准备照着这套方案在自己的环境里跑一遍我给三点额外建议。一点建议是快照备份不要只做一个地址至少要做到本地副本加异地对象存储双份。我遇到过本地机房蓄电池故障导致整个机柜断电快照仓库和集群同时不可用的情况当时如果没有异地备份数据就真的找不回来了。第二点建议是定期做故障演练。每季度挑一个模拟的数据节点容器杀一次看集群能不能在分片重新分配后回到green状态每半年做一次完整恢复演练把备份数据恢复到测试集群验证备份脚本还能不能正常跑通。这些演练做下来比看十篇文档都管用。第三点是关于机器数量的选择。ES 官方推荐至少三个 master 节点但很多小团队只有两台机器于是就把 master 节点部署成两个。两个 master 节点在故障时极易发生脑裂判断依据是必须在多数派存活时才能选举主节点。用两台机器跑 ES性价比远不如把 master 和 data 合并在同一台节点上但至少保证有三个节点的拓扑。如果预算真的抠到只能上两台机器我宁可牺牲一个数据节点的容量也要保证三个 master 角色存在这是集群数据安全的底线。这个系列的下一篇我打算拆一下 Elasticsearch 数据节点上分片数量的预估方法和索引模板调优因为这是和“大数据集群部署策略”直接相关的话题。到时候我们接着聊分片与节点之间的资源匹配那才是让 ES 集群性能稳定下来的关键一环。