Elasticsearch 启动反复重启排查实录:从“内存不足”到“vm.max\_map\_count 过小”
这是一篇按实际故障排查顺序整理的笔记,覆盖症状 → 定位 → 根因 → 解决 → 验证 → 最佳实践,同时给出 systemd 与 Docker 两套可复制命令及一键脚本。
一、环境信息(按现场日志复盘)
-
Elasticsearch:7.15.0(Docker 镜像,bundled JDK)
-
JVM:OpenJDK 16.0.2(日志显示
JVM arguments ... -Xms16062m -Xmx16062m
) -
内核 & 架构:Linux 4.14,aarch64
-
集群/节点:cluster.name=
es-cluster
,node.name=es2
-
可用内存概览(现场输出):
free -htotal used free shared buff/cache available Mem: 47G 32G 3.6G 2.4G 11G 5.7G Swap: 0B 0B 0B
-
关键报错 1(JVM 内存申请失败):
There is insufficient memory for the Java Runtime Environment to continue. Native memory allocation (mmap) failed to map 12884901888 bytes for committing reserved memory. error='Not enough space' (errno=12)
-
关键报错 2(Bootstrap check 未通过):
bootstrap check failure [1] of [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
二、问题现象
- 节点
es2
启动过程中反复退出重启。 - 初期日志显示 JVM 需一次性映射 ~12GB 内存 失败(errno=12),同时机器 无 swap;
- 随后日志显示通过一段初始化后 Bootstrap Checks 失败,因
vm.max_map_count
过小(65530 < 262144)。
三、快速结论
- 阶段一根因:JVM 初始堆或直接内存等配置偏大 + 无 swap/连续内存不足 →
mmap
失败。 - 阶段二根因:系统参数
vm.max_map_count
低于 Elasticsearch 启动要求 → Bootstrap Checks 拦截 → 进程退出 → 容器/服务反复重启。
两个问题互相独立:先解决内存分配问题能让 ES 走到后续引导检查;再把内核参数调到位才能稳定起来。
四、定位思路与要点
1)从日志抓关键线索
- 看到
mmap failed ... 12884901888 bytes
与errno=12 Not enough space
,说明 不是磁盘空间,而是 无法获取足够大的连续虚拟内存; - 配合
free -h
发现 Swap 为 0,物理内存还有余量但不连续,JVM 预触碰(-XX:+AlwaysPreTouch
)+ G1 Region 分配需要较大连续空间; - 再看 JVM 启动参数:
-Xms16062m -Xmx16062m
,堆初始即 15.7GB;在容器或内存紧张场景容易失败; - 后续日志提示
vm.max_map_count
过小,属于 Elasticsearch 的强制启动检查。
2)为什么 available
还有 5.7G 也会失败?
- JVM 需要一整块连续虚拟内存 来 commit 预留空间;
- 内存碎片化 + 无 swap 时,内核更难为大块映射腾挪空间;
- Docker/K8s 限制(如
-m
)若存在,也会导致映射失败。
五、解决方案(按顺序执行)
建议先确保 JVM 内存设置与宿主机/容器限制匹配,再处理 内核参数,最后统一验证。
方案 A:下调 JVM 堆/直接内存(首选快速解法)
1. systemd 部署
编辑 jvm.options
(常见路径 /etc/elasticsearch/jvm.options
或安装目录下 config/jvm.options
):
# 将初始堆和最大堆设置为合理值,例如 4G(按物理内存的一半以内)
-Xms4g
-Xmx4g
经验:堆大小不超过物理内存的一半,并且不超过 30~31GB(超过大约 32GB 会失去 Compressed Oops,指针膨胀导致性能下降)。
重启服务:
systemctl restart elasticsearch
2. Docker 部署
- 使用环境变量覆盖 JVM:
# docker run 示例
docker run -d --name es \-e ES_JAVA_OPTS="-Xms4g -Xmx4g" \-p 9200:9200 -p 9300:9300 \elasticsearch:7.15.0
- docker-compose:
environment:- ES_JAVA_OPTS=-Xms4g -Xmx4g
若使用了
-m/--memory
或mem_limit
等容器内存限制,请确保 容器可用内存 > Xmx,通常至少预留 1~2GB 额外空间给非堆/直接内存/线程栈等。
方案 B:为宿主机添加 Swap(当确需较大堆或内存紧张时)
# 创建 16G 的 swap 文件(按需调整)
dd if=/dev/zero of=/swapfile bs=1G count=16
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile# 开机自启
echo '/swapfile none swap sw 0 0' >> /etc/fstab# 验证
free -h
swapon --show
性能敏感业务可适当调低
vm.swappiness
(例如 1~10),避免频繁换页:
sysctl -w vm.swappiness=10
sed -i '$a vm.swappiness=10' /etc/sysctl.conf
方案 C:通过内核参数通过 Bootstrap Checks(必做)
ES 在绑定非本地地址时会开启 Bootstrap Checks,
vm.max_map_count
必须 ≥ 262144。
1. systemd/物理机
# 临时生效(立即修复)
sysctl -w vm.max_map_count=262144# 永久生效(写入配置并加载)
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p
2. Docker / docker-compose
- 宿主机先设置(容器内设置无效):
sysctl -w vm.max_map_count=262144
- docker-compose(可选补充,便于编排记录):
sysctls:- vm.max_map_count=262144
- 重新创建或启动容器:
docker-compose down && docker-compose up -d
# 或
docker stop es && docker rm es
docker run -d --name es \--sysctl vm.max_map_count=262144 \-e ES_JAVA_OPTS="-Xms4g -Xmx4g" \-p 9200:9200 -p 9300:9300 \elasticsearch:7.15.0
六、验证与健康检查
# 1) 核心内核参数
sysctl vm.max_map_count
# 期望:vm.max_map_count = 262144# 2) 内存与 swap
free -h
swapon --show# 3) 容器资源限制(如使用 Docker)
docker inspect es | egrep -i 'Memory|NanoCpus'# 4) ES 进程与监听端口
ss -lntp | egrep '9200|9300'# 5) 集群健康(需启用 9200)
curl -s http://127.0.0.1:9200/_cluster/health?pretty# 6) 查看最近错误日志
# systemd:
journalctl -u elasticsearch -n 200 --no-pager
# Docker:
docker logs es --tail=200
七、最佳实践与避坑指南
-
合理的堆内存:
Xms=Xmx
,一般为物理内存的一半以内,上限 30~31GB。 -
预留额外内存:堆外(直接内存、页缓存、线程栈、Lucene 结构)也吃内存;容器限制需预留余量。
-
Swap 合理有益:线上不等于禁止 swap;适度 swap + 低
vm.swappiness
可以提升稳定性,避免突发 OOM。 -
vm.max_map_count
与映射:索引分段与 mmap 文件较多,262144
是官方最低建议,节点高并发场景可适当再高一些。 -
透明大页(THP):延迟敏感可以考虑关闭以减少抖动:
echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag # 开机加入 rc.local 或 systemd unit
-
观察 GC 与段合并:定期检查
logs/gc.log
与_cat/segments
,及时做 ILM/索引生命周期管理,控制段数量与内存占用。
八、一键修复脚本
两套脚本仅对常见场景做“安全默认值”处理:设置
vm.max_map_count
,若无 swap 则创建 8G,且将 JVM 堆调整为 4G(可按需修改)。
1)systemd 部署一键脚本
cat > /tmp/fix_es_systemd.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail# === 1. 调整 vm.max_map_count ===
echo "[+] set vm.max_map_count=262144"
sysctl -w vm.max_map_count=262144 >/dev/null
if ! grep -q '^vm.max_map_count=262144' /etc/sysctl.conf 2>/dev/null; thenecho 'vm.max_map_count=262144' >> /etc/sysctl.conf
fi
sysctl -p >/dev/null# === 2. 若无 swap 则创建 8G swap ===
if [ "$(swapon --noheadings | wc -l)" -eq 0 ]; thenecho "[+] create 8G swap at /swapfile"dd if=/dev/zero of=/swapfile bs=1G count=8 status=progresschmod 600 /swapfilemkswap /swapfileswapon /swapfileif ! grep -q '^/swapfile' /etc/fstab; thenecho '/swapfile none swap sw 0 0' >> /etc/fstabfi
fi# === 3. 降低 vm.swappiness(可选) ===
if ! grep -q '^vm.swappiness=' /etc/sysctl.conf; thenecho 'vm.swappiness=10' >> /etc/sysctl.confsysctl -w vm.swappiness=10 >/dev/null
fi# === 4. 调整 JVM 堆(若>8g则改为4g) ===
JVM_OPTS_FILE="/etc/elasticsearch/jvm.options"
if [ -f "$JVM_OPTS_FILE" ]; thenXMX=$(grep -E '^\-Xmx' "$JVM_OPTS_FILE" | head -n1 | sed 's/[^0-9]//g' || true)if [ -n "$XMX" ] && [ "$XMX" -gt 8000 ]; thenecho "[+] tune heap to 4g in $JVM_OPTS_FILE"sed -ri 's/^(-Xms)(.*)$/\14g/' "$JVM_OPTS_FILE"sed -ri 's/^(-Xmx)(.*)$/\14g/' "$JVM_OPTS_FILE"fi
fi# === 5. 重启服务 ===
echo "[+] restart elasticsearch"
systemctl restart elasticsearch || true
sleep 2
systemctl --no-pager -l status elasticsearch || trueecho "[DONE]"
EOFchmod +x /tmp/fix_es_systemd.sh
bash /tmp/fix_es_systemd.sh
2)Docker 部署一键脚本(容器名假设为 es
)
cat > /tmp/fix_es_docker.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail# === 1. 宿主机内核参数 ===
echo "[+] set vm.max_map_count=262144"
sysctl -w vm.max_map_count=262144 >/dev/null
if ! grep -q '^vm.max_map_count=262144' /etc/sysctl.conf 2>/dev/null; thenecho 'vm.max_map_count=262144' >> /etc/sysctl.conf
fi
sysctl -p >/dev/null# === 2. 若无 swap 则创建 8G swap ===
if [ "$(swapon --noheadings | wc -l)" -eq 0 ]; thenecho "[+] create 8G swap at /swapfile"dd if=/dev/zero of=/swapfile bs=1G count=8 status=progresschmod 600 /swapfilemkswap /swapfileswapon /swapfileif ! grep -q '^/swapfile' /etc/fstab; thenecho '/swapfile none swap sw 0 0' >> /etc/fstabfi
fi# === 3. 重建容器,指定合理堆与 sysctl ===
CN=${1:-es}
if docker ps -a --format '{{.Names}}' | grep -qx "$CN"; thenecho "[+] recreate container: $CN"IMAGE=$(docker inspect -f '{{.Config.Image}}' "$CN")docker stop "$CN" || truedocker rm "$CN" || truedocker run -d --name "$CN" \--restart=always \--sysctl vm.max_map_count=262144 \-e ES_JAVA_OPTS="-Xms4g -Xmx4g" \-p 9200:9200 -p 9300:9300 \"$IMAGE"
elseecho "[i] container $CN not found, skip recreate"
fi# === 4. 健康检查 ===
sleep 5
docker logs "$CN" --tail=200 || trueecho "[DONE]"
EOFchmod +x /tmp/fix_es_docker.sh
bash /tmp/fix_es_docker.sh es
提示:如需保留原容器的数据与配置,请补充
-v
卷挂载参数与原容器一致;生产环境建议使用docker-compose
管理。
九、最终结果
- 通过下调 JVM 堆或添加 swap,消除了 mmap 失败;
- 通过将
vm.max_map_count
提升到 262144,通过了 Bootstrap Checks; - 节点
es2
正常启动,集群恢复服务。
十、附:快速自检清单(复制即用)
# 1) JVM 参数与堆大小
jcmd $(pgrep -f Elasticsearch | head -n1) VM.flags 2>/dev/null | head -n5 || true# 2) 内存 & swap
free -h && swapon --show# 3) 内核参数
sysctl vm.max_map_count# 4) 容器限制
[ -x "$(command -v docker)" ] && docker inspect es | egrep -i 'Memory|NanoCpus' || true# 5) ES 健康
curl -s http://127.0.0.1:9200/_cluster/health?pretty || true# 6) 最近错误
journalctl -u elasticsearch -n 200 --no-pager 2>/dev/null || docker logs es --tail=200 || true