ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Elasticsearch 生产集群容器化:用 Docker Swarm 编排高可用 ES 的完整实践

Elasticsearch 生产集群容器化:用 Docker Swarm 编排高可用 ES 的完整实践 去年我接手了一套 Elasticsearch 集群六个节点全部裸机安装版本乱到一台 7.5 一台 7.9JVM 参数各写各的数据目录直接坐在系统盘上连个监控都没有。某个周六夜里节点宕掉因为 discovery 配置太随意剩下两台机器反复选举日志刷了好几百兆才有人发现。就是从那次事故开始我把这套 ES 全部推倒用 Docker Swarm 重新编排成标准化集群从节点角色、编排文件到备份监控一步步收敛成今天要讲的这套方案。这篇文章就是那次重构的完整记录核心是讲清楚这几件事为什么选 Swarm 而不是 K8s、主从数据节点怎么在 Swarm 里划分角色、编排文件怎么写才是生产级配置、上线后备份恢复和滚动更新怎么处理。对正在维护 ES 集群、或者准备把 ES 容器化的团队来说这篇可以当一份落地手册来用照着做至少能避开我踩过的那几个大坑。1. 抛开 K8s我为什么选 Docker Swarm 扛 ES 生产集群先说一个容易被带偏的问题。现在一提容器编排很多人默认那就上 K8s但我的观点是工具选型要结合团队维护能力和应用特性Elasticsearch 这种有状态中间件对调度器的需求根本没那么强Swarm 反而是更省心的选择。1.1 Swarm 和 K8s 的真实成本差异K8s 本身不是一个组件是一套平台。要跑起来至少需要 etcd、api-server、controller-manager、scheduler、kubelet 五个核心组件还要考虑 CNI 网络插件、CoreDNS、Ingress Controller、Dashboard以及配套的 RBAC、Helm、监控体系。这些组件每一样都有版本兼容问题光升级一次集群版本就够运维团队折腾几天。Swarm 呢它内置在 Docker 引擎里安装 Docker 之后docker swarm init一下控制面就起来了。不需要额外的存储组件不需要额外的 API Serverdocker stack deploy直接吃 Compose 文件。对一个三到六人的后端加运维团队来说Swarm 的学习成本几乎是零——团队里任何一个会写docker-compose.yml的人到了 Swarm 环境里基本可以无缝上手。再算资源账。K8s 的 control plane 组件加起来至少要 2GB 内存etcd 还要占用磁盘 IO。Swarm 的 control plane 非常轻manager 节点多跑几个容器只占几百 MB。省出来的资源放在 ES 这种吃内存的数据库上就是在给核心业务加资源。1.2 什么样的企业场景适合 SwarmSwarm 适合的场景画个像大概是这样的ES 集群规模在 3 到 15 个节点之间不需要分钟级自动扩缩容偶尔手动扩容就够了。团队没有专门的 K8s 平台组运维由后端开发兼职需要的是看得懂、能维护的工具。中间件种类不多ES、Redis、MySQL用 Compose 文件都能描述清楚。有滚动重启和故障恢复诉求但不需要 K8s 那套复杂的 Operator 机制。反过来如果公司已经有成熟的 K8s 平台ES 的 Pod 归平台组统一管理那直接用 K8s 没毛病不用为了 ES 单独维护一套 Swarm。如果 ES 集群要支持多租户、动态扩缩容、和 K8s 上的业务服务做细粒度网络互通那 Swarm 也确实不如 K8s 顺手。有些朋友跟我聊过 Doris、ClickHouse 这类 OLAP 组件问是不是部署策略可以照搬 ES 这套。我的回答是底层思路一致都是角色分离 主节点仲裁 数据节点水平扩展但具体编排方式必须按各自的节点发现协议去适配千万别混着来。ETL 链路里 ES 负责检索、Doris 负责聚合分析数据模型本来就各管一摊部署上各自保持独立集群反而是最稳的。2. 集群角色的物理划分主节点、数据节点、协调节点的 Swarm 落地ES 集群不像 Redis Cluster 那样所有节点角色一致。它有三种基础角色每种角色干的活不一样对硬件的要求也不一样。这一步不设计好后面再调就是拆东墙补西墙。2.1 节点角色的边界与误区master 节点负责集群状态管理、索引元数据维护、分片分配决策。它们不存业务数据但集群元数据变更全走这里。生产环境必须独立部署数量保持奇数3 个或 5 个。data 节点真正存放分片数据承担索引写入和查询请求的 IO 压力。这是集群的大头容量规划主要做在这一层。coordinating 节点接收客户端请求做路由分发和结果聚合本身不存数据、也不参与主节点选举。规模大了以后可以独立加一层用来隔离查询压力。ingest 节点执行数据预处理管道比如字段转换、日志切分。如果写入链路里有 ingest pipeline可以单独划一批节点出来避免预处理和搜索抢资源。很多人图省事一个节点同时打 master 和 data 标签小集群这么干没问题到了 6 节点以上就会出现一个经典症状data 节点频繁 Full GC主节点心跳超时集群反复进入 red 状态。因为 master 职责要求响应快、GC 稳定而 data 节点的 IO 起伏大两者放在同一进程里就是互相拖累。这句话我希望每个组集群的人都看进去生产环境主数据不分家事故早晚找上门。2.2 用 Swarm 标签和约束把角色钉死在指定主机上Swarm 本身没有角色概念它只有节点标签labels和调度约束placement constraints。我们把这两件事结合起来就行。假设有三台机器跑 master三台机器跑 data物理上分了两组。初始化 Swarm 之后给每一台机器打上对应标签docker node update --label-add es_rolemaster node-1 docker node update --label-add es_rolemaster node-2 docker node update --label-add es_rolemaster node-3 docker node update --label-add es_roledata node-4 docker node update --label-add es_roledata node-5 docker node update --label-add es_roledata node-6然后在 ES 服务的编排文件里通过deploy.placement.constraints把服务固定到对应主机上services: es-master: deploy: placement: constraints: - node.labels.es_role master es-data: deploy: placement: constraints: - node.labels.es_role data这样跑起来之后Swarm 的调度器只会在满足标签的主机上启动副本。千万不要用 Swarm 默认的全局调度去跑 ES否则一个节点挂了Swarm 可能把你的主节点服务重新调度到数据节点上ES 数据目录错乱这个坑踩下去相当酸爽。2.3 网络与端口规划ES 有两个核心端口9200 给 HTTP 客户端访问9300 给节点间传输层做集群通信。在 Swarm 里我们用一个 overlay 网络把集群内部通信隔离起来外部访问只暴露 9200。创建网络时建议开启加密和别名选项docker network create -d overlay \ --attachable \ --opt encrypted \ es-net需要在外部访问 9200 的节点比如 Kibana、Logstash、业务服务再单独接入这个es-net。节点之间用服务名互相解析这一步很关键ES 的 discovery 配置直接依赖它。端口规划上9300 千万不要暴露到宿主机公网。就算你在云上做了安全组也建议只暴露 92009300 只存在于 overlay 网络内部避免别人直接探测你的节点传输端口。3. Stack 编排文件逐行拆解生产级配置不该用默认值ES 官方镜像的默认配置能跑起来但上生产到处都是坑。下面是经过实际验证的一份es-stack.yml我会拆开讲清楚每一处为什么要这么写。version: 3.8 services: es-master: image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2 hostname: es-master networks: - es-net environment: - cluster.namees-prod - node.namees-master-{{.Node.Hostname}} - node.rolesmaster - discovery.seed_hostses-master,es-data1,es-data2 - cluster.initial_master_nodeses-master - xpack.security.enabledfalse - ES_JAVA_OPTS-Xms4g -Xmx4g volumes: - /data/es/master:/usr/share/elasticsearch/data deploy: mode: replicated replicas: 3 placement: constraints: - node.labels.es_role master resources: limits: memory: 8g restart_policy: condition: any delay: 5s max_attempts: 3 es-data: image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2 hostname: es-data networks: - es-net environment: - cluster.namees-prod - node.namees-data-{{.Node.Hostname}} - node.rolesdata,ingest - discovery.seed_hostses-master,es-data1,es-data2 - ES_JAVA_OPTS-Xms8g -Xmx8g volumes: - /data/es/data1:/usr/share/elasticsearch/data deploy: mode: replicated replicas: 3 placement: constraints: - node.labels.es_role data resources: limits: memory: 16g restart_policy: condition: any delay: 5s max_attempts: 3 networks: es-net: external: true3.1 镜像版本怎么选我在这套方案里用的是7.10.2。原因很现实企业里大量插件、代码、以及团队对配置体系的熟悉程度都停在 7.x而 7.10 之后官方把一些集群分片分配的核心参数改成默认值部分老版本的工具链会失灵。8.x 系列默认强制开启安全认证节点间需要配置 TLS 证书对运维体系要求又高了一个台阶从 7.x 升级到 8.x 不是改个镜像标签就完事的事建议等团队有充足预案再动。如果你确实要上 8.x请重点确认三件事安全证书如何统一分发、Kibana 版本必须对齐、以及旧的.security索引迁移策略。没有想清楚之前别上了生产再反悔。3.2 环境变量与 JVM 内存的黄金规则cluster.name必须统一集群内节点才能互相识别。node.name我用了模板变量{{.Node.Hostname}}这样每个节点拿到独立名字出现问题时一眼能从日志中定位是物理机上的哪个容器。discovery.seed_hosts填的是 overlay 网络里其他节点的服务名这比 IP 更可靠因为 Swarm 重新调度后 IP 会变。cluster.initial_master_nodes只要在集群首次启动时指定 master 节点的名字引导建立集群后这个配置的使命就结束了。注意如果三个 master 节点是同一份镜像启动的它们的node.name必须唯一否则 ES 会认为自己分裂成了两个节点。JVM 堆内存我见过最多的问题是拍脑袋配。官方建议是堆大小不要超过物理内存的一半因为 ES 的 Lucene 要用除了堆之外的系统内存做文件缓存堆最小值Xms和最大值Xmx必须设成一样防止运行中堆动态扩容引发 Full GC单节点堆建议控制在 32GB 以内超过 32GBJVM 的压缩指针失效内存利用率反而下降。数据节点上我会把总内存 16GB 的机器配 8GB 堆剩下给文件系统缓存。resources.limits.memory设置为堆的两倍左右给 Lucene 和系统开销留余量否则容器可能因为 RSS 超过限制被 OOMKilled。3.3 系统参数与数据卷权限两个最容易翻船的点ES 官方在生产环境要求vm.max_map_count至少是 262144这个系统参数是 ES 用来做 mmap 内存映射的分区数默认值 65530 对单机够用对集群远远不够。如果不开日志里会出现这样一段max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]在每台宿主机上执行sysctl -w vm.max_map_count262144 echo vm.max_map_count262144 /etc/sysctl.conf第二个容易翻船的点是数据卷权限。ES 官方镜像约定以uid:1000elasticsearch 用户访问容器内的数据目录如果你在宿主机直接mkdir /data/es/xxx默认目录 owner 是 root容器一启动就报AccessDeniedException数据节点根本起不来。这是初学 ES 容器化必踩的坑。mkdir -p /data/es/master /data/es/data1 chown -R 1000:1000 /data/es或者干脆在数据目录挂载后加一个初始化容器去chown但我更推荐宿主机层面处理好简单直接不增加编排复杂度。3.4 健康检查与优雅停止生产环境我会在每个 ES 服务里加上健康检查等集群完全就绪后再让 Swarm 认为服务健康healthcheck: test: [CMD-SHELL, curl -fsS http://localhost:9200/_cluster/health] interval: 30s timeout: 10s retries: 5 start_period: 60sstart_period非常关键。ES 冷启动时要先恢复分片这段时间可能长达一两分钟如果不给 grace periodSwarm 会反复重启容器导致分片恢复和重启互相打架。同样需要关注的还有停止顺序。ES 在容器收到 SIGTERM 后优雅关闭时会优先把本节点的分片迁移走这个过程可能需要几十秒。Swarm 里可以通过stop_grace_period告诉调度器多给点时间stop_grace_period: 2m不做这个设置的话Swarm 默认 10 秒就强制杀进程分片来不及迁移重新启动后集群恢复时间会拖长很多。4. 部署实录从 swarm init 到集群绿灯全流程以及踩过的坑理论说完了我们完整跑一遍流程。下面这个顺序是我踩过坑之后总结出来的最优路径不要随意调换。4.1 六台机器的标准操作路径第一步初始化 Swarm 集群。假设六台机器 IP 分别是 172.16.1.11 到 172.16.1.16第一台做 managerdocker swarm init --advertise-addr 172.16.1.11这一步会输出一个docker swarm join --token ...的命令其余五台机器跑这个命令加入。如果不小心把 token 丢了在 manager 节点执行docker swarm join-token worker重新获取。第二步打节点标签docker node update --label-add es_rolemaster 172.16.1.11 docker node update --label-add es_rolemaster 172.16.1.12 docker node update --label-add es_rolemaster 172.16.1.13 docker node update --label-add es_roledata 172.16.1.14 docker node update --label-add es_roledata 172.16.1.15 docker node update --label-add es_roledata 172.16.1.16注意 manager 节点默认也可以运行容器但为了不让 Swarm 自动把 ES 调度到任意 manager 上靠的就是我们前面约束里的es_role标签。什么样的标签打在你身上Swarm 就只知道往哪调度。第三步宿主机系统参数和磁盘目录处理照着 3.3 小节执行确认所有机器都做了。第四步部署 stackcd /opt/es-stack docker stack deploy -c es-stack.yml es第五步观察服务状态docker service ls docker service ps es_es-master docker service ps es_es-data当每个服务都显示3/3的时候说明三个副本都起来了。这时候开始在任意一台机器上检查集群健康curl http://任意节点IP:9200/_cluster/health?pretty期望看到status: green。如果只是yellow说明有副本分片还没分配多半是磁盘水位、权限或者节点内存的问题如果red那就是有主分片无法分配这时候就要进入排查链路了。4.2 集群红色状态的完整排查链路我先把话放这儿集群红了第一件事不是重启节点而是看分片为什么没分配。重启很容易把事情搞大因为节点加入退出的抖动会让分片分配器反复错乱。排查路径我固定用三连命令curl localhost:9200/_cat/health?v curl localhost:9200/_cat/shards?vhindex,shard,prirep,state,unassigned.reason curl localhost:9200/_cat/allocation?v第一命令看整体状态。第二命令看哪些分片 unassignedunassigned.reason会提示是NODE_ADDED、CLUSTER_RECOVERED、还是INDEX_CREATED。第三命令看数据节点磁盘使用率。真实项目里遇到最多的场景是磁盘水位问题。ES 默认把cluster.routing.allocation.disk.watermark.low设为 85%、high设为 90%、flood_stage设为 95%。一旦磁盘超过 90%ES 会自动把该节点上的分片迁移走超过 95%索引直接进入只读模式写入全挂。我之前就处理过一次线上故障一个数据节点的磁盘到了 96%集群先是 yellow然后大量写报错cluster_block_exception。当时我先清理了服务器上的日志和临时文件让磁盘降到 85% 以下然后手动解除索引只读curl -XPUT localhost:9200/_all/_settings -H Content-Type: application/json \ -d {index.blocks.read_only_allow_delete: null}注意这个命令要在所有索引上执行而且执行前确认磁盘是真的降下来了否则又会马上弹回去。另一个高频坑是节点之间网络命名空间隔离导致 discovery 失败。ES 节点在 overlay 网络中通过 9300 端口互相连接排查时先确认容器之间能否 ping 通服务名docker exec -it es-master容器ID curl -s http://es-data1:9200能通说明 overlay 网络正常不通就要检查--opt encrypted是否影响了节点间通信有的老版本 Docker 对加密 overlay 的支持有 bug。遇到这种情况临时去掉--opt encrypted重建网络基本都能恢复。4.3 Windows 开发机上连集群要留意什么很多同事习惯在 Windows 笔记本上用本地装好的 Elasticsearch 做开发一兴奋直接从 7.10 官网 zip 包启动单节点结果本地磁盘、JVM 参数、系统环境和服务器集群完全对不上联调时各种问题说不清楚。其实 Windows 上直接跑elasticsearch.bat想连 Swarm 集群跨网络和认证配置会有额外成本远不如在开发机上装个 Docker Desktop然后通过--network host或者用同一套 Compose 文件起一个 developer 配置文件直接连集群的 9200 端口或者干脆通过 Kibana 做数据验证。如果必须在 Windows 上单机跑 ES 做功能测试几个建议用和线上一致的 7.10.2 版本避免版本行为差异在config/elasticsearch.yml里关掉 xpack 安全特性否则连本地_cluster/health都要带用户名密码注意 Windows 防火墙是否拦截了 9200/9300不要在生产集群和本地单节点之间搞什么组集群版本、角色、系统参数全不一样效果参考价值极低。5. 上线后必做的三件事备份恢复、滚动更新、监控告警集群亮了绿灯很多人就以为运维结束了。真正的事故往往发生在第三个月比如误删索引、节点硬件故障、或者一次错误的配置更新把整个集群搞挂。所以这三件事上线当天就必须做好。5.1 Snapshot 快照备份到共享存储ES 的备份不是导数据这么简单正确做法是注册一个快照仓库然后定期执行快照。快照仓库要求有一个所有节点都能访问的共享文件系统企业场景常用 NFS或者 S3 对象存储。在 Swarm 环境里因为每个副本可能漂移到不同宿主机所以共享存储一定要走网络文件系统不要在本地磁盘上建目录。NFS 挂载所有数据节点后在 Kibana 里注册仓库PUT /_snapshot/es_backup { type: fs, settings: { location: /data/es_backup, compress: true } }备份三个核心点快照仓库要和数据节点放在不同的故障域别让集群数据盘挂了的同时备份盘也挂在同一台宿主机上定期执行快照策略用 Cron 任务每天凌晨执行一次完整快照保留最近三十天恢复前先检查快照状态不是备份了就万事大吉要定期做恢复演练。恢复数据的基本姿势# 查看快照列表 GET /_snapshot/es_backup/_all # 恢复指定索引 POST /_snapshot/es_backup/20260112_001/_restore { indices: nginx-log-*, rename_pattern: nginx-log-(.), rename_replacement: restored-nginx-log-$1 }这里强调一下恢复索引时建议重命名避免和线上现存索引冲突确认数据正确后再切换别名别直接把线上的原索引覆盖掉。5.2 Swarm 滚动更新策略一次动一个节点ES 集群升级最忌讳一把梭。容器化的优势在于可以精确控制滚动节奏Swarm 的update_config配合健康检查可以做一节点一节点的灰度升级。在 stack 文件里配置deploy: update_config: parallelism: 1 delay: 60s order: start-first failure_action: rollback monitor: 120sparallelism: 1同时只更新一个节点delay: 60s更新完一个等一分钟再动下一个给分片恢复留时间order: start-first新容器先起来再停旧容器减少可用副本数为零的时间窗口failure_action: rollback如果健康检查失败自动回滚到上一个版本monitor: 120s更新完成后观察两分钟再判定是否成功。更新的时候用命令指定镜像版本docker service update --image docker.elastic.co/elasticsearch/elasticsearch:7.10.2 es_es-data升级过程中密切观察集群健康状态。一个节点更新完毕后主分片的副本重新分配要时间如果直接看到yellow别急着继续推进等它变绿再让 Swarm 动下一个节点。整个过程就像多人协作分片迁移你越有耐心线上越稳定。5.3 监控告警cerebro elasticsearch-exporter Prometheus最后也最容易被忽视的是监控。我推荐组合一套轻量的监控方案Cerebro一个 Web 界面工具可以直观看到集群节点状态、分片分布、实时迁移信息。排查问题时比命令行效率高一截。启动一个 Cerebro 容器连接集群 9200 地址就行。elasticsearch-exporterPrometheus 生态的 ES 指标导出器把集群状态、分片状态、节点 JVM、磁盘空间等指标导给 Prometheus。Grafana配上 ES 监控仪表盘模板一张大屏全搞定。Prometheus 采集到的关键指标里我会重点盯着这几个指标项推荐阈值说明elasticsearch_cluster_health_statusgreen状态不是 green 要立即报警elasticsearch_cluster_shards_percentage_unassigned小于 1%未分配分片大于 0 就要关注elasticsearch_node_jvm_heap_used_percent不超过 75%JVM 堆使用率长时间超 75%GC 压力会非常大elasticsearch_node_filesystem_free_bytes不小于 20GB磁盘低于 20GB 就触发预报警elasticsearch_node_thread_pool_search_rejected持续为 0有 rejected 说明查询线程池队列爆了告警别只接一个渠道钉钉/企业微信/短信至少配两个。同时给 Cerebro 也留个入口排障时开个页面就能看到分片在哪里、谁在拖后腿比抓日志舒服太多。5.4 关于集群容量规划的一点私房心得最后想分享一个很多人没有的意识ES 集群的容量规划要按场景 增长曲线一起算而不是按磁盘总数除一下每个节点。我有一个大致公式每 1GB 原始数据在未压缩的 ES 存储里经过倒排索引、doc values、_source 原始文档等多重存储膨胀比一般在 1.2 到 1.5 倍左右然后每个索引默认要留一个副本所以实际占用是数据量的 2.4 到 3 倍。加上系统预留 20% 磁盘空间假设你每个月新增 100GB 原始日志那一个月真实占用的空间大约是 250GB 到 300GB。按这个估算再决定数据节点的数量和每块磁盘的大小会靠谱很多。至于cluster.routing.allocation.disk.watermark这几个水位参数不要随便调低去压榨磁盘空间。你把水位调低ES 就会在磁盘还很宽裕的时候开始往别的节点搬分片迁移流量反而拖垮集群性能。保留默认值是个稳妥的选择。最后说两句这套 Swarm ES 集群方案上线到现在数据节点从 3 个扩到 6 个经历了两轮滚动升级和一次真实的磁盘故障演练没有出过像样的生产事故。容器编排不是越复杂越好能把故障恢复、滚动更新、备份恢复这三件事跑顺再复杂的系统也能在一个看板里管清楚。如果你正在纠结要不要把裸机的 ES 容器化我的建议很明确先从非核心日志集群开始练手把整套流程跑熟再逐步把核心业务索引迁过去。容器化不是目的稳定可复现才是。
返回列表