ARTICLE DETAIL

资讯详情

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

Docker Swarm部署Elasticsearch集群:从方案选型到生产级运维实践

Docker Swarm部署Elasticsearch集群:从方案选型到生产级运维实践 先交代一个背景这篇文章不是讲“怎么把ES装进Docker跑起来”而是讲怎么把ES集群真正跑在Docker Swarm上并且能扛住生产流量。我见过太多团队用docker run 起单机ES做测试然后直接被线上数据量和查询压力打穿。我也见过另一类团队明明只有三五台机器却非要引入K8s来编排ES结果运维复杂度比重建集群还高。Docker Swarm在云原生时代确实显得“不够酷”但如果你跟我一样长期维护一套中小规模的企业搜索或日志分析平台你会发现在节点数不多、团队没有专职K8s运维的情况下Swarm反而是更合适的调度器。这篇文章我会把从镜像选型、配置文件编写、状态持久化、集群初始化到扩容缩容、快照恢复的完整过程都捋一遍并且把我在实际部署中踩过的坑一并放上来。这篇内容适合谁给那些正在用Swarm管理企业应用又想把Elasticsearch集群纳入统一编排的运维工程师也适合被分配了“用Docker部署一套ES集群”任务却不知道从何下手的后端开发。整套操作下来大概一个多小时就能完成初始化上线但我建议你预留半天来做参数调优和故障演练。1. 方案选型与整体设计思路1.1 为什么用Docker Swarm而不是K8s或裸机部署先聊选型。Elasticsearch本身是分布式系统它假设节点之间通过TCP互相发现然后各自维护集群状态。这个特性决定了它既可以跑在裸机也可以跑在容器里关键看你怎么管理它的生命周期。裸机部署ES最大的问题是环境一致性。ES对JVM版本、系统参数比如vm.max_map_count、文件句柄限制都很敏感稍有偏差两个节点可能行为不一致排查起来非常痛苦。而且裸机部署没有“服务恢复”的概念机器宕机之后要人工介入重新拉起。K8s部署ES是当前互联网大厂的主流方案但它对运维能力要求较高至少要维护一个etcd集群还要理解StatefulSet、PV/PVC、Headless Service、Operator等一系列概念。如果公司本身没有K8s平台为了ES单独搭一套K8s性价比非常低。Docker Swarm介于两者之间。它不需要额外的状态存储基于Raft协议内嵌自带服务发现和健康检查支持service副本漂移最关键的是它用一个docker-compose.yml就能描述整套集群拓扑维护成本极低。从我实际维护的经验看Swarm在几十个节点的规模下稳定性没有问题。ES集群本身的横向扩展瓶颈通常出现在存储和内存上而不是调度器。只要你的ES节点数不超过几十个Swarm完全撑得住。1.2 一套生产集群到底需要多少资源在设计之前先把资源规划搞清楚。Elasticsearch的资源消耗大户有两个JVM堆内存和文件句柄。堆内存不是越大越好ES官方建议是系统内存的一半左右上限32GB。这是因为JVM在32GB以内可以使用压缩指针超过之后对象指针膨胀性能反而下降。一个常见的生产配置是单节点分配16GB堆内存总内存32GB剩余内存留给OS page cache因为ES的Lucene底层要大量使用文件缓存。如果堆内存设得过高留给page cache的空间不足检索性能和段合并速度都会明显变差。节点角色上小集群可以只分两类master-eligible node和data node。但如果你的数据量达到几十TB查询复杂度高了以后建议再拆出独立的ingest node做数据预处理避免写入管道占用data node的CPU。如果在Swarm里管理多角色的ES节点我建议每个角色的service分开定义这样扩缩容可以独立操作不会相互影响。2. 部署前的准备与镜像选型2.1 系统参数调整vm.max_map_count和文件句柄不管用什么方式部署ES有两项OS级别的配置必须提前确认。第一个是vm.max_map_count。Elasticsearch的Lucene会创建大量内存映射文件默认的65530经常不够用。如果是生产环境我建议直接设为262144。临时设置用sysctl -w永久生效需要写入/etc/sysctl.conf。# 临时生效 sysctl -w vm.max_map_count262144 # 永久生效 echo vm.max_map_count262144 /etc/sysctl.conf sysctl -p第二个是文件句柄和线程数限制。容器环境里虽然可以通过ulimit控制但Swarm服务默认的LimitNOFILE不一定覆盖到位。我这里直接建议在Swarm服务的deploy配置里显式写清楚。2.2 镜像版本与基础配置镜像版本我强烈建议不要追新。Elasticsearch的大版本升级有不少坑特别是7.x到8.x默认开启了SSL认证集群配置参数也改了不少。这篇文章里用的是7.17.x这是7.x的最后一个大版本线稳定性和生态兼容性都很好且支持很多老版本才有的配置方式。这里提供一个选择逻辑如果你的业务代码里使用了TransportClient那版本不能高于6.8如果你用的是REST Client或者Java High Level REST Client7.17完全没问题如果你要上8.x那需要额外处理证书和TLS配置。从稳定优先的角度考虑我建议生产环境先用7.17.x跑通整个链路。另外一个容易被忽略的细节是镜像用户权限。ES官方镜像默认以elasticsearch用户运行但挂载数据目录后宿主机目录的所有者往往不对导致容器写不进去。解决方式是在启动前手动chown或者挂载时指定uid/gid。3. Swarm服务定义与集群配置3.1 目录结构与配置文件我习惯先把整个部署目录准备好再初始化Swarm和部署服务。目录结构如下/opt/es-cluster/ ├── config/ │ ├── master-1/ │ │ └── elasticsearch.yml │ ├── master-2/ │ │ └── elasticsearch.yml │ ├──>cluster.name: es-prod-cluster node.name: master-1 node.roles: [master] network.host: 0.0.0.0 discovery.seed_hosts: master-1,master-2,master-3 cluster.initial_master_nodes: master-1,master-2,master-3 xpack.security.enabled: false xpack.monitoring.enabled: true这里我把xpack.security显式关闭了因为在内网环境里可以通过网络策略限制访问。如果你的ES要暴露到非可信网络一定要开启安全认证后续版本默认强制开启。3.3 docker-compose.yml完整解析Swarm部署使用的docker-compose.yml和单机Docker Compose略有不同多了一个顶级关键字deploy这个段落只对Swarm生效。下面是我线上在用的配置hostname需要按实际机器调整version: 3.8 networks: esnet: driver: overlay attachable: true volumes: es-master-1: driver: local es-master-2: driver: local es-master-3: driver: local es-data-1: driver: local es-data-2: driver: local services: master-1: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.20 hostname: master-1 volumes: - ./config/master-1/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml - es-master-1:/usr/share/elasticsearch/data environment: - ES_JAVA_OPTS-Xms16g -Xmx16g networks: - esnet deploy: replicas: 1 placement: constraints: - node.hostname node1 resources: limits: memory: 32g reservations: memory: 24g restart_policy: condition: any master-2: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.20 hostname: master-2 volumes: - ./config/master-2/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml - es-master-2:/usr/share/elasticsearch/data environment: - ES_JAVA_OPTS-Xms16g -Xmx16g networks: - esnet deploy: replicas: 1 placement: constraints: - node.hostname node2 resources: limits: memory: 32g reservations: memory: 24g restart_policy: condition: any master-3: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.20 hostname: master-3 volumes: - ./config/master-3/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml - es-master-3:/usr/share/elasticsearch/data environment: - ES_JAVA_OPTS-Xms16g -Xmx16g networks: - esnet deploy: replicas: 1 placement: constraints: - node.hostname node3 resources: limits: memory: 32g reservations: memory: 24g restart_policy: condition: any >docker swarm init --advertise-addr 192.168.1.10把manager节点的token拷下来在需要加入的worker节点上执行docker swarm join。如果是多manager架构额外执行docker swarm join --manager。初始化之后在主管理节点上执行部署命令docker stack deploy -c docker-compose.yml es-cluster这个命令会把整套服务部署到整个Swarm集群。查看服务状态docker service ls docker service ps es-cluster_master-1ES容器的启动时间比较慢因为JVM启动和集群发现需要时间。我遇到过刚启动就去查集群状态结果报connection refused的情况这是正常的等3-5分钟再看。4.2 验证集群健康状态等到全部容器状态都是Running之后进入任意一个master容器执行docker exec -it $(docker ps -q --filter namees-cluster_master-1) curl -s http://localhost:9200/_cluster/health?pretty正常情况下你会看到类似下面的输出{ cluster_name : es-prod-cluster, status : green, number_of_nodes : 5, number_of_data_nodes : 2, active_primary_shards : 0, active_shards : 0, unassigned_shards : 0 }status字段有三个取值green表示所有主分片和副本分片都可用yellow表示主分片可用但副本分片有缺失通常是因为副本策略或者资源不足red表示有主分片不可用这时候数据是部分丢失的需要立刻排查。4.3 动态扩容与缩容最让我觉得Swarm比K8s简单的一个场景就是扩容。假设你需要新增两个数据节点只需要在docker-compose.yml里再添加两个service然后重新执行docker stack deploy。对不对不需要手动修改磁盘、不需要设置PVC、不需要预写PV。因为都基于同样的镜像和配置模板新增服务到部署完成也就是几秒钟的事情。但这里有个数据安全层面的关键点ES默认的自动分片均衡会把现有数据重新分布到新节点上这个过程CPU和IO开销非常大。在业务低峰期再扩容可以避免集群在扩节点时性能抖动。缩容更谨慎一些。如果你从Swarm服务里直接删除一个data节点ES的shard会先重新分配如果这种分配正在发生时节点宕了数据会处于red状态。建议先手动把节点从ES集群中排除——通过PUT _cluster/settings设置cluster.routing.allocation.exclude._name为要缩容的节点名等分片迁移完成后再停用Swarm服务。5. 常见故障与排查实战5.1 集群状态变YELLOW分片始终无法分配这是我遇到频率最高的一个问题。现象是集群能正常访问但status一直是yellow。用curl查看curl -s http://localhost:9200/_cat/shards?vhindex,shard,prirep,state,node如果unassigned的shard属于一个新建的索引优先怀疑可用节点数不够。简单说如果你只有一台data节点索引的副本分片永远无法分配因为同一分片的主副本不能落在同一节点上。解决方案也有两条路要么增加data节点数量要么把索引的副本数调成0。如果unassigned的shard在节点重启之后一直没恢复就得查更细的原因curl -s http://localhost:9200/_cluster/allocation/explain?pretty这个接口会直接告诉你为什么无法分配。最常见的原因是磁盘水位限制ES会拒绝把分片分配到磁盘使用率超过85%的节点上。5.2 磁盘水位阈值导致的写入失败ES有一个磁盘保护机制叫做cluster.routing.allocation.disk.watermark。默认情况下当节点磁盘使用率超过85%新分片不再分配超过90%系统会尝试把分片迁移走超过95%该节点的分片会被置为只读。生产环境里我比较推荐手工设定更保守的值比如curl -X PUT localhost:9200/_cluster/settings?pretty -H Content-Type: application/json -d { persistent: { cluster.routing.allocation.disk.threshold_enabled: true, cluster.routing.allocation.disk.watermark.flood_stage: 92%, cluster.routing.allocation.disk.watermark.high: 85%, cluster.routing.allocation.disk.watermark.low: 75% } }注意watermark.flood_stage一旦触发索引会变成只读。这时候即使之后磁盘释放了也要手动把索引置回可写状态否则业务侧会一直报错curl -X PUT localhost:9200/_all/_settings?expand_wildcardsall -H Content-Type: application/json -d { index.blocks.read_only_allow_delete: null }5.3 数据恢复从快照仓库恢复数据恢复这件事有条件的话一定要做一次演练。ES支持注册快照仓库把数据备份到NFS或者对象存储里。注册一个NFS仓库的配置如下curl -X PUT localhost:9200/_snapshot/es_backup?pretty -H Content-Type: application/json -d { type: fs, settings: { location: /mnt/es-backup, compress: true, max_snapshot_bytes_per_sec: 500mb, max_restore_bytes_per_sec: 500mb } }执行快照可以指定索引也可以全量。我建议全量快照配合每周周期执行增量由系统自动处理。恢复数据时如果原集群还在可以直接curl -X POST localhost:9200/_snapshot/es_backup/snapshot_20260112/_restore?pretty -H Content-Type: application/json -d { indices: *, ignore_unavailable: true, include_global_state: false }include_global_state这里我特意设成false这样不会把快照里保存的别名、索引模板整体覆盖到现有集群避免造成配置污染。如果是全新集群恢复直接把仓库注册好、快照列表拉出来然后执行恢复操作即可。5.4 跨主机网络性能问题overlay网络走的是VXLAN隧道相比宿主机网络有额外的封装开销。这一问题在大量小报文场景下尤其明显。ES集群节点之间的内部通信全都是TCP小报文如果吞吐量不对劲先确认一下是不是流量都走了overlay。我的处理方式是把ES集群单独放到Swarm的同一个overlay网络上同时将宿主机网卡mtu调大到1500以上。实测大部分性能问题都能通过这个方式缓解。如果还是不够可以考虑把ES剥离容器网络改用host网络模式。host模式在docker-compose里的写法是network_mode: host。但注意Swarm模式下network_mode: host的生效有版本限制——只有Docker 20.10.9及之后版本才支持。而且改用host模式后discovery.seed_hosts要写成宿主机IP这也会让整个配置的移植性变差。建议先用默认overlay跑通确认性能瓶颈确实在网络之后再做调整。6. 快照备份与Windows环境下的数据导入6.1 一个被忽略的场景从Windows本地数据导入集群本来这部分不在计划内但很多同事接手ES之后都问过一个问题说工作机是Windows之前单机版ES里有一批数据怎么导到集群里来。这个场景在企业里其实很常见因为开发测试阶段大家习惯在Windows上用ES跑一堆数据到了上线阶段要迁移到Linux集群。在Windows上启动Elasticsearch非常简单解压官方压缩包后执行bin/elasticsearch.bat即可。这种单机实例的数据目录是data目录。迁移数据最稳妥的方式不是直接拷贝Lucene文件因为索引版本之间可能存在差异跨版本直接拷贝容易导致段文件无法读取。我比较推荐的做法是在Windows本地把数据导出为ndjson格式然后在集群上重放# 在Windows单机ES上导出 curl -s http://localhost:9200/myindex/_search?scroll5msize1000 -H Content-Type: application/json -d { query: {match_all: {}} }拿到_scroll_id之后循环调用scroll接口拉数据用jq或脚本把hits的_source转成ndjson保存到文件。然后在集群上通过bulk接口批量导入curl -s -X POST http://localhost:9200/myindex/_bulk?pretty -H Content-Type: application/x-ndjson --data-binary data.json如果数据量大bulk一条条发太慢了Python的elasticsearch.helpers.bulk是个更高效的选择。6.2 生产快照恢复演练建议快照恢复这件事我强烈建议至少每季度演练一次。因为恢复过程中很多坑不在“能不能恢复”而在“恢复多久恢复完”。比如从NFS拉数据受限于网络带宽一个500GB的索引可能要跑几个小时。等到真正出故障时才发现恢复时长不可接受那损失就大了。另外要注意恢复操作会阻塞当前集群部分索引的读写因为ES需要把恢复的分片先放到节点上再进入active状态。最低影响方案是先扩容一个临时data节点把恢复目标指向这个临时节点恢复完成后让ES自动把分片均衡到其他节点再下线这个临时节点。这样可以把恢复压力隔离在集群侧边缘不会剧烈影响主节点的稳定性。7. 最终实践经验与维护建议部署这套集群的过程中我最大的体会是ES集群的稳定运行更多取决于运维规范而不是初始部署多顺利。很多人把部署当作终点其实只是起点。后续你会遇到日志膨胀、冷热分离、索引生命周期管理等一堆问题。我手头这套Swarm ES集群已经稳定运行了差不多两年期间经历过两次Swarm节点重启、一次数据节点硬件更换都没有丢数据。秘诀不外乎几点。第一任何变更前先打快照。ES快照仓库的注册成本很低一次配置好之后后续定期快照几乎没有感知。一旦操作失误直接回滚数据比调参回滚快得多。第二监控体系一定要跟上。ES的Heap使用率、磁盘使用率、分片分布情况、查询QPS和延迟曲线这些都要纳入监控。我一般用cAdvisor和Prometheus抓容器指标再配合ES自身的_cat接口做巡检脚本。特别留意Heap使用率如果持续超过85%需要检查是否存在大聚合查询或者分片过多的问题而不是只靠堆内存。第三变更窗口要克制。ES最怕的不是变更而是变更得太频繁。Swarm stack重新部署时如果改动了内存限制或镜像版本可能引发全部节点滚动重启直接影响这段时间的查询。所以非紧急配置修改凑批处理。最后再分享一个小技巧。如果你担心某个ES节点被Swarm误调度到别的机器上导致数据丢失除了配置placement constraint外可以在节点Docker daemon上设置engine label比如node.labels.esdatatrue然后在服务部署的placement.constraints里引用node.labels.esdatatrue。这样以后新增节点时只要不打这个标签就不会被ES服务意外占用。Docker Swarm或许不是这个时代讨论度最高的编排方案但在企业内网资源有限、团队规模不大的场景下它确实能用最简单的方式帮你把ES集群管起来。这套配置跑通之后日常维护的重心就可以放到数据分片、索引性能和业务查询这些真正重要的事情上去了。
返回列表