Elasticsearch 启动反复重启排查实录:从“内存不足”到“vm.max\_map\_count 过小”

发布于:2025-08-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

这是一篇按实际故障排查顺序整理的笔记,覆盖症状 → 定位 → 根因 → 解决 → 验证 → 最佳实践,同时给出 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 -h
                  total        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]
    

二、问题现象

  1. 节点 es2 启动过程中反复退出重启。
  2. 初期日志显示 JVM 需一次性映射 ~12GB 内存 失败(errno=12),同时机器 无 swap
  3. 随后日志显示通过一段初始化后 Bootstrap Checks 失败,因 vm.max_map_count 过小(65530 < 262144)。

三、快速结论

  • 阶段一根因:JVM 初始堆或直接内存等配置偏大 + 无 swap/连续内存不足 → mmap 失败。
  • 阶段二根因:系统参数 vm.max_map_count 低于 Elasticsearch 启动要求 → Bootstrap Checks 拦截 → 进程退出 → 容器/服务反复重启。

两个问题互相独立:先解决内存分配问题能让 ES 走到后续引导检查;再把内核参数调到位才能稳定起来。


四、定位思路与要点

1)从日志抓关键线索

  • 看到 mmap failed ... 12884901888 byteserrno=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/--memorymem_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

七、最佳实践与避坑指南

  1. 合理的堆内存Xms=Xmx,一般为物理内存的一半以内,上限 30~31GB

  2. 预留额外内存:堆外(直接内存、页缓存、线程栈、Lucene 结构)也吃内存;容器限制需预留余量。

  3. Swap 合理有益:线上不等于禁止 swap;适度 swap + 低 vm.swappiness 可以提升稳定性,避免突发 OOM。

  4. vm.max_map_count 与映射:索引分段与 mmap 文件较多,262144 是官方最低建议,节点高并发场景可适当再高一些。

  5. 透明大页(THP):延迟敏感可以考虑关闭以减少抖动:

    echo never > /sys/kernel/mm/transparent_hugepage/enabled
    echo never > /sys/kernel/mm/transparent_hugepage/defrag
    # 开机加入 rc.local 或 systemd unit
    
  6. 观察 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; then
  echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
fi
sysctl -p >/dev/null

# === 2. 若无 swap 则创建 8G swap ===
if [ "$(swapon --noheadings | wc -l)" -eq 0 ]; then
  echo "[+] create 8G swap at /swapfile"
  dd if=/dev/zero of=/swapfile bs=1G count=8 status=progress
  chmod 600 /swapfile
  mkswap /swapfile
  swapon /swapfile
  if ! grep -q '^/swapfile' /etc/fstab; then
    echo '/swapfile none swap sw 0 0' >> /etc/fstab
  fi
fi

# === 3. 降低 vm.swappiness(可选) ===
if ! grep -q '^vm.swappiness=' /etc/sysctl.conf; then
  echo 'vm.swappiness=10' >> /etc/sysctl.conf
  sysctl -w vm.swappiness=10 >/dev/null
fi

# === 4. 调整 JVM 堆(若>8g则改为4g) ===
JVM_OPTS_FILE="/etc/elasticsearch/jvm.options"
if [ -f "$JVM_OPTS_FILE" ]; then
  XMX=$(grep -E '^\-Xmx' "$JVM_OPTS_FILE" | head -n1 | sed 's/[^0-9]//g' || true)
  if [ -n "$XMX" ] && [ "$XMX" -gt 8000 ]; then
    echo "[+] 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 || true

echo "[DONE]"
EOF

chmod +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; then
  echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
fi
sysctl -p >/dev/null

# === 2. 若无 swap 则创建 8G swap ===
if [ "$(swapon --noheadings | wc -l)" -eq 0 ]; then
  echo "[+] create 8G swap at /swapfile"
  dd if=/dev/zero of=/swapfile bs=1G count=8 status=progress
  chmod 600 /swapfile
  mkswap /swapfile
  swapon /swapfile
  if ! grep -q '^/swapfile' /etc/fstab; then
    echo '/swapfile none swap sw 0 0' >> /etc/fstab
  fi
fi

# === 3. 重建容器,指定合理堆与 sysctl ===
CN=${1:-es}
if docker ps -a --format '{{.Names}}' | grep -qx "$CN"; then
  echo "[+] recreate container: $CN"
  IMAGE=$(docker inspect -f '{{.Config.Image}}' "$CN")
  docker stop "$CN" || true
  docker rm "$CN" || true
  docker 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"
else
  echo "[i] container $CN not found, skip recreate"
fi

# === 4. 健康检查 ===
sleep 5
docker logs "$CN" --tail=200 || true

echo "[DONE]"
EOF

chmod +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


网站公告

今日签到

点亮在社区的每一天
去签到