部署与高可用
集群怎么摆
单节点能跑通之后,下一步就是把它变成集群。ES 的集群没有中心节点,靠 master 选举和状态同步维持一致,所以节点怎么分工、摆几台,直接决定了可用性和运维复杂度。
先给三条硬性原则:
- master 节点数量必须是奇数,3 个是生产最小配置。2 个 master 的集群,挂一台就失去多数派,直接无法选举。
- master 节点不要兼数据节点(小集群可以妥协)。master 要处理集群状态变更,一旦被大查询或 merge 拖住,分片分配、节点上下线全部卡住。
- 至少 3 个节点才谈得上高可用。1 主 1 副的索引要同时容忍一台机器故障,至少需要 3 个数据节点(副本不能和主分片同节点)。
一个常见的生产形态:
| 节点 | 数量 | 角色 | 配置侧重 |
|---|---|---|---|
| master | 3 | master | 2C4G 足够,要稳定、低延迟 |
| data-hot | 3+ | data_hot, data_content | 高 CPU、大内存、SSD |
| data-warm | 2+ | data_warm | 大容量 HDD,CPU 一般 |
| coordinating | 2 | 空角色 | 高 CPU、大内存,挡查询流量 |
小团队(日增日志几十 GB)不用拆这么细,3 台机器每台都兼 master, data 完全够用。等数据量和查询量上来,再按热温冷拆。
数据节点数决定了副本能放几份,也决定了单节点故障后剩余容量够不够扛。做容量规划时,按「少一台节点仍能承载全部数据」来算磁盘,否则一次硬件故障就会触发磁盘水位告警,连锁反应比故障本身更麻烦。
多节点配置
ES 的配置就一个 config/elasticsearch.yml,节点间差异主要在 node.name。下面是一个 3 节点、每节点兼 master 和 data 的最小生产配置(以第一台为例):
# 集群标识:所有节点必须一致;改名等于换了个新集群,已有的数据不会被识别
cluster.name: es-prod
# 节点名:集群内唯一,建议和主机名/容器名对应,排障时一眼能对上
node.name: es-node-1
# 角色:小集群兼 master + data;生产大集群按热温冷拆开
node.roles: [ master, data_hot, data_content ]
# 数据与日志目录,生产建议数据盘单独挂载
path.data: /data/es/data
path.logs: /var/log/elasticsearch
# 监听地址:0.0.0.0 表示所有网卡,生产建议绑定内网网卡
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
# 集群发现:列出所有 master 候选节点的传输地址
discovery.seed_hosts:
- 10.0.0.11:9300
- 10.0.0.12:9300
- 10.0.0.13:9300
# 首次引导:只写 master 候选节点的 node.name,集群成型后必须删掉
cluster.initial_master_nodes:
- es-node-1
- es-node-2
- es-node-3
# 锁定内存,防止堆被换出(配合系统 ulimit memlock)
bootstrap.memory_lock: true
# 生产必须开启认证;TLS 证书按官方安全配置生成,别裸奔 9200
xpack.security.enabled: true
几个容易踩的点:
cluster.initial_master_nodes是一次性的。它只在「集群第一次成型」时生效,集群有了状态之后就该从配置里删掉,否则节点重启时会试图重新引导,可能引发脑裂或启动失败。之后节点靠discovery.seed_hosts互相发现。discovery.seed_hosts里要列全所有 master 候选节点,不要求列数据节点。列表里的地址可以是 IP、主机名,端口默认 9300。network.host别在公网机器上写 0.0.0.0,ES 的 9200 没有认证时就是完全开放的。生产用内网地址 + 安全组/防火墙。path.data的权限要归 ES 运行用户(Docker 镜像里是 UID 1000),否则启动直接报权限错误。- 节点间时钟必须同步,建议装 chrony 或 systemd-timesyncd。时间漂移会让日志时间线错乱,也会影响基于时间的 ILM 判断。
多节点启动顺序没有严格要求,但建议先起 master 候选节点,确认集群成型(_cluster/health 能看到 number_of_nodes 达到预期)后再起数据节点,日志更干净、问题更好定位。
堆内存怎么给
JVM 堆的配置放在 config/jvm.options.d/heap.options(推荐,升级不覆盖)或 config/jvm.options 里:
-Xms16g
-Xmx16g
三条原则:
-Xms必须等于-Xmx,避免堆动态伸缩带来的 GC 抖动。- 不超过物理内存的 50%,剩下的留给 Lucene 的文件缓存——ES 的查询性能极度依赖 page cache,堆给太多反而更慢。
- 绝对值不超过 31GB。超过这个值 JVM 会失去压缩指针(compressed oops),同样的数据占用更多内存,堆越大性能反而下降。机器再大,堆也别超过 31GB,多出来的内存留给文件缓存。
分片规划
分片规划是 ES 里一次性决策里最贵的一个:主分片数建索引时定死,后期想改只能 reindex 或 shrink/split,几 TB 的索引迁移一次要好几个小时。所以宁可多花十分钟算清楚。
两条铁律
- 主分片数创建后不可变(
number_of_shards只能建索引时指定)。 - 副本数随时可改(
number_of_replicas一条 PUT 就能调,改完自动重新分配)。
每个分片多大
官方给的经验区间是 20-50GB / 分片。小于 20GB 分片数量太多,集群状态、恢复时间、查询扇出都会变差;大于 50GB 单个分片恢复慢,一台节点挂掉后重新分配要传的数据量太大。
同时还有一条硬约束:单节点的分片总数不要超过「堆内存 GB 数 × 20」。一个 16GB 堆的节点,分片数控制在 320 个以内;一个 3 节点集群,总副本加主分片不要超过 960 个。分片元数据常驻堆内存,分片数超标是 JVM 堆压力最常见的来源之一。
估算示例
假设日志每天 100GB,保留 30 天,1 个副本:
| 项目 | 计算 | 结果 |
|---|---|---|
| 总数据量 | 100GB × 30 天 | 3TB |
| 单分片目标大小 | 取 30GB | 30GB |
| 每天主分片数 | 100 ÷ 30,向上取整 | 4 个 |
| 每天总分片数 | 4 × (1 主 + 1 副) | 8 个 |
| 30 天总分片数 | 8 × 30 | 240 个 |
240 个分片,3 个数据节点平均 80 个,远低于「堆 16GB × 20」的上限,健康。如果每天只有 5GB,按这个公式算出来每天 1 个主分片,30 天 60 个分片,完全没问题——小数据量不要为了「看起来分布式」硬拆很多分片,一个分片能装下就别拆。
时间序列索引:别手工建
日志、监控这类按天/按月滚动的数据,千万别手工一个个建索引,用索引模板 + 别名 + ILM:
PUT /_index_template/logs-template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 4,
"number_of_replicas": 1,
"refresh_interval": "5s"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"service": { "type": "keyword" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
}
模板匹配 logs-*,之后写入 logs-2025.10.25 这样的索引会自动套用上面的配置。再用一个别名把整批索引收在一起,查询只认别名:
POST /_aliases
{
"actions": [
{ "add": { "index": "logs-*", "alias": "logs" } }
]
}
写入方按日期滚动(比如每天新建 logs-yyyy.MM.dd),查询方只查 logs 别名并带时间范围条件。索引的滚动、降冷、删除交给 ILM 自动完成——第 5 篇会给出完整的生命周期策略。
快照与恢复
副本解决的是「机器坏了」,快照解决的是「数据被删了、索引写坏了、要回到昨天」。这两件事不能互相替代:副本是实时的,删除操作会同步到副本;快照是时间点副本,能回到过去。
配置快照仓库
快照仓库有三种常见类型:fs(共享文件系统)、s3(对象存储)、hdfs。生产最推荐对象存储——便宜、耐用、和集群解耦。
fs 类型要求所有节点都能访问同一个共享目录(NFS 或挂载的 NAS)。先在 elasticsearch.yml 里声明允许的仓库路径,这个配置必须重启节点才生效:
path.repo: ["/data/es/backup"]
# 每个节点都要有目录且权限正确
sudo mkdir -p /data/es/backup
sudo chown -R 1000:1000 /data/es/backup
注册仓库:
PUT /_snapshot/fs_backup
{
"type": "fs",
"settings": {
"location": "/data/es/backup",
"compress": true,
"max_snapshot_bytes_per_sec": "100mb",
"max_restore_bytes_per_sec": "100mb"
}
}
S3 类型要在每个节点装 repository-s3 插件(部分发行版已内置)并配置访问凭证,然后注册:
PUT /_snapshot/s3_backup
{
"type": "s3",
"settings": {
"bucket": "es-snapshot-prod",
"region": "ap-east-1",
"base_path": "es-prod",
"compress": true
}
}
注册完验证一下,返回 200 并列出参与验证的节点才算通(不通会直接抛错):
curl -s -X POST "http://localhost:9200/_snapshot/fs_backup/_verify?pretty"
一是 path.repo 改了没重启节点,注册仓库直接报 location ... doesn't match any of the locations specified by path.repo;二是只有部分节点挂载了共享目录,快照能建但恢复时找不到分片文件;三是 NFS 的锁和权限问题导致快照卡在 IN_PROGRESS。生产上能上 S3 就上 S3,省掉这一整类问题。
创建与查看快照
PUT /_snapshot/fs_backup/snap-20251025?wait_for_completion=false
{
"indices": "logs-*,articles",
"ignore_unavailable": true,
"include_global_state": false
}
indices:要备份的索引,支持通配符;不写就是全部。wait_for_completion=false:异步执行,立刻返回任务 ID。快照耗时和索引大小成正比,几 TB 的索引不要在前台等。include_global_state:是否包含集群全局状态(模板、ILM 策略等)。只恢复业务数据时设false,避免把旧的集群配置带回生产。ignore_unavailable: true:有索引不存在时不报错,适合通配符场景。
进度和结果:
# 看快照列表和状态(SUCCESS / IN_PROGRESS / FAILED / PARTIAL)
curl -s "http://localhost:9200/_snapshot/fs_backup/_all?pretty"
# 看单个快照的分片级进度
curl -s "http://localhost:9200/_snapshot/fs_backup/snap-20251025/_status?pretty"
快照是增量的:第一次全量,之后只备份新增的段文件。所以「每天一次快照」的成本远低于每天传一份全量数据,放心做。
恢复
POST /_snapshot/fs_backup/snap-20251025/_restore?wait_for_completion=false
{
"indices": "articles",
"rename_pattern": "(.+)",
"rename_replacement": "restored_$1",
"include_global_state": false,
"index_settings": {
"index.number_of_replicas": 1
}
}
几个注意点:
- 恢复的索引名不能和现有索引冲突。要么先删/关闭目标索引,要么用
rename_pattern+rename_replacement换个名字恢复出来再对比数据。上面的写法会把articles恢复成restored_articles。 - 恢复要挑低峰期。恢复分片会大量读写磁盘、占网络带宽,和业务查询抢资源。
- 跨集群恢复(把 A 集群的快照恢复到 B 集群)要求 B 集群的节点也能访问同一个仓库,fs 类型就是挂同一个 NFS,s3 类型配同一个桶。
用 SLM 自动快照
手工建快照迟早会忘,用快照生命周期管理(SLM)让它自动跑:
PUT /_slm/policy/daily-snapshots
{
"schedule": "0 30 2 * * ?",
"name": "<daily-snap-{now/d}>",
"repository": "fs_backup",
"config": {
"indices": ["*"],
"ignore_unavailable": true,
"include_global_state": false
},
"retention": {
"expire_after": "30d",
"min_count": 5,
"max_count": 50
}
}
schedule 是 cron 表达式(6 位,含秒),上面表示每天凌晨 2:30。name 里的日期占位符会替换成当天日期。retention 保证最多留 50 份、至少留 5 份、超过 30 天自动清理。执行一次验证:
curl -s -X POST "http://localhost:9200/_slm/policy/daily-snapshots/_execute?pretty"
快照状态是 SUCCESS 只代表「数据写出去了」,不代表「能恢复」。建议每季度做一次恢复演练:把快照恢复到一个临时索引,抽样对比文档数量和关键字段。没验证过的备份,约等于没有备份。
ILM 预告
上面提到了索引模板和别名,但「索引什么时候滚动、什么时候降冷、什么时候删除」还没讲。这部分由 ILM(Index Lifecycle Management) 负责:按索引年龄和大小自动执行 rollover、forcemerge、searchable_snapshot、delete。第 5 篇会给一份热温冷归档的完整策略。
小结
- 高可用三原则:master 奇数个(生产 3 个)、master 与 data 分离、至少 3 个数据节点;副本不能和主分片同节点。
- 配置的核心是
cluster.name(全集群一致)、node.roles(分工)、discovery.seed_hosts(发现)、cluster.initial_master_nodes(仅首次引导,用完就删)。 - 堆内存
-Xms等于-Xmx、不超过物理内存 50%、不超过 31GB,其余留给 Lucene 文件缓存。 - 分片规划两条铁律:主分片数创建后不可变、副本数随时可调;单分片 20-50GB,单节点分片数不超过「堆 GB × 20」。
- 时间序列索引用索引模板 + 别名 + ILM 管理,别手工建索引。
- 快照和副本解决不同问题:副本防机器故障,快照防误删和逻辑损坏。fs 仓库要所有节点挂同一目录并重启生效,生产优先对象存储;用 SLM 自动备份,并且定期做恢复演练。
集群搭起来只是开始,接下来要回答「它现在到底健不健康」:下一篇讲监控指标、ILM 策略和 Grafana 接入。