1. 先把场景聊清楚离线装 ES 到底难在哪离线装 ElasticSearch 单机版这件事很多人第一反应是“拉个镜像跑起来不就完了”真到内网里动手往往卡在第二十分钟——镜像拉不下来、Docker 装不上、容器起来三秒就退出、日志里一行max virtual memory areas vm.max_map_count [65530] is too low看得人一头雾水。这篇文章聊的就是在 CentOS 7 这种偏老但存量巨大的系统上用 Docker 把 ElasticSearch 单机版离线装起来的一整套流程包括物料怎么准备、参数怎么算、坑怎么绕。适合完全断网的生产内网环境也适合只有一台跳板机能通外网的半隔离环境读者不需要是 ES 专家但最好对 Linux 和 docker 命令有个基本手感。我把整个链路拆成四段物料搬运、引擎落地、镜像落地、容器编排。这四段每一段都有独立的失败模式混在一起排查就是灾难。所以下面会严格按顺序讲前一段没验收通过不要急着进下一段这是我自己吃过亏之后定下的规矩。单机版听起来简单但“单机”不等于“随便”一旦你打算把它当生产日志库或者检索服务用堆内存、磁盘水位、快照策略这些东西一个都躲不掉后面会逐一交代。1.1 三类典型现场与选型逻辑第一类是纯物理隔离的内网机器连 U 盘拷贝都要走审批这种情况所有 rpm 包、镜像 tar 包、插件 zip 必须一次性备齐中途发现缺东西就得重新走流程所以物料清单要按“宁可多备”的原则做。第二类是只有一台跳板机能访问外网其他机器全封闭这种可以在一台中转机上把东西准备好再用 scp 或者内网文件服务分发相对轻松但要注意中转机的 Docker 版本和 CPU 架构必须和目标机一致。第三类是准生产环境外网能通但限速严重拉一个 1.2G 的 ES 镜像要几十分钟这种情况用离线搬运反而比在线拉取快得多。选 Docker 而不是直接跑 rpm 或者 tar.gz 二进制包理由其实很实在ES 对 JDK 版本、系统参数、目录权限都有要求用二进制包部署意味着你要自己管 JDK、自己写 systemd 单元、自己处理升级回滚用容器则把这些东西封装进镜像里升级就是换一个 tar 包重新 load回滚就是把旧容器删了用旧镜像再起一个。代价是要多学一层 Docker 的网络和存储但对单机场景来说这层成本可控。我不想把 Docker 说成银弹它只是把这个特定场景下的运维复杂度转移到了一个更标准化的接口上。还有一个容易被忽略的选型点ES 主版本。7.x 和 8.x 在安全策略上的差异非常大8.x 默认开启 TLS 和账号密码直接用 curl 访问会返回一堆证书错误很多人第一次装 8.x 会被这个吓到。如果你的应用客户端比较老、或者只是想快速搭一个能用的检索服务7.17 这个长期维护版本反而更省心如果是新项目8.x 更合适但要多做一步安全配置。下面所有命令我都以 8.11 为主涉及 7.x 差异的地方会明确标出来。1.2 单机版的能力边界discovery.type: single-node这个配置项一加ES 就进入单节点模式不做主节点选举、不尝试发现其他节点、不要求集群法定人数。带来的好处是启动快、配置少一台机器就能跑出 green 状态的集群健康值。但边界也很清楚没有副本分片节点挂了这个索引就不可用数据量受单机磁盘和内存限制索引重建、快照恢复都只能在本机操作。所以单机版适合什么日志量在每天几十 GB 以内、允许短暂中断的内部检索服务、开发测试环境、边缘节点上的本地缓存索引。不适合什么核心交易数据的主存储、要求 99.99% 可用性的对外服务、单索引超过几亿文档还要高频聚合的场景。我见过有团队把单机 ES 当订单查询主库用后来磁盘满了写不进去整个下单链路跟着挂这种教训不用重复。明确边界之后再看配置思路就清楚了单机的优化方向不是高可用而是“用有限的资源撑住当前的量并且别因为一个参数没设对就崩”。堆内存给多少、分片设几个、刷新间隔调不调这些决策都要围绕这个目标来。后面讲参数计算的时候会反复回到这个前提。1.3 这套方案的资源与取舍清单给一份我常用的最小资源基线CPU 4 核起步内存 8G 起步其中一半给 ES 堆数据盘用 SSD 或者至少是独立于系统盘的机械盘容量按预估数据量的 2 倍准备Lucene 段合并期间会有临时空间占用。这三项里最容易出问题的是磁盘——把数据目录放在根分区索引写到一半根分区满了整台机器都会出问题后面会专门讲怎么把 Docker 的数据根目录挪走。取舍清单列一下方便你对着自己的环境判断用宿主机网络还是端口映射单机场景端口映射更好管但如果你要做性能压测宿主机网络少一层转发关不关 xpack 安全内网单机、上游有网关兜底的情况下可以关省去证书和密码的麻烦对外暴露则必须开堆给 4G 还是 8G低于 4G 在数据量上来后会频繁 GC高于物理内存一半会挤占文件缓存Lucene 的读取性能反而下降要不要装中文分词插件中文内容检索几乎必装英文为主的场景可以跳过少一个版本对齐的负担。2. 离线物料清单与宿主机基线物料准备这一环决定了后面顺不顺。我见过太多人装到一半发现缺container-selinux又跑出去找包。这里给一份可以直接照着抄的清单按“必须”和“视情况”分开你按自己环境裁剪。所有文件建议统一放在一个目录里并生成一份 SHA256 校验文件搬运过程中损坏一个字节都可能导致docker load报错而报错信息往往看不出是文件损坏。宿主机基线检查同样不能省。CentOS 7 的默认内核是 3.10虽然 ES 8 能跑但有几个内核参数的默认值明显偏小尤其是vm.max_map_count的默认 65530ES 启动时的自检会直接判定不合格。另外 CentOS 7 已经停止常规维护很多默认 yum 源不可用离线安装反而是更靠谱的路径——反正也不指望在线装。2.1 需要提前搬进内网的全部文件下面这张表是我实际用过的清单按类别分组类别文件说明是否必须容器引擎docker-ce、docker-ce-cli、containerd.io 及依赖 rpm建议整包下载含依赖必须引擎插件docker-compose-plugin、docker-buildx-plugin用 compose 编排时需要视情况系统依赖container-selinux、libcgroup、fuse-overlayfs、slirp4netns部分需从 base 源取必须镜像elasticsearch 镜像 tar.gz大小约 600M–1.3G必须插件analysis-ik 对应版本 zip版本必须与 ES 完全一致视情况工具jq、curl、net-tools 的 rpm排查问题时很顺手建议配置elasticsearch.yml、jvm 参数文件提前写好现场只改 IP必须校验上述所有文件的 sha256 清单防搬运损坏建议下载 rpm 依赖的推荐做法是在一台能上网的同类系统上执行yumdownloader --resolve --destdir/tmp/rpms docker-ce docker-ce-cli containerd.io--resolve会把依赖树一起拉下来。如果系统自带的 yumdownloader 版本太老解析不全可以换成 repotrack 之类的工具或者干脆在一台干净的 CentOS 7 上yum install --downloadonly --downloaddir/tmp/rpms整个装一遍装完 rpm 包就都在目录里了这个方法笨但最可靠。注意下载 rpm 的机器和目标机器的架构必须一致都是 x86_64如果中间有 ARM 服务器镜像和 rpm 都得单独准备一份混用会在docker load或者rpm -ivh阶段报架构不匹配。2.2 CentOS 7 的基线检查进机器先跑几条命令确认底子cat /etc/centos-release uname -r free -g df -hT getenforce systemctl status firewalld重点看三件事。第一根分区和数据分区是不是 xfs这关系到 Docker 存储驱动的选择第二SELinux 是不是 enforcingenforcing 状态下容器挂载宿主机目录经常出现权限拒绝前期排查阶段建议临时设为 permissivesetenforce 0确认能跑通后再决定要不要保留第三防火墙状态9200 端口要不要对外开放取决于你的访问来源。内存要特别留意free -g里的 available 值而不是 total。CentOS 7 上常见的情况是 total 16G 但 available 只有 10G因为跑了一堆监控 agent。堆内存的算法后面会讲但基线是给 ES 的堆不要超过 available 的一半。磁盘检查用df -hT如果/var/lib/docker所在分区剩余空间不到 50G强烈建议先改 Docker 数据根目录再装否则镜像加上数据很快就撑爆。改法在 3.3 节详细说。2.3 内核参数与资源限制的一次性调优这几个参数是 ES 官方文档里明确要求的在宿主机层面一次性配好写进文件保证重启后依然生效# /etc/sysctl.d/99-elasticsearch.conf vm.max_map_count 262144 vm.swappiness 1 fs.file-max 655360 net.core.somaxconn 32768vm.max_map_count是 Lucene 内存映射文件数量的上限默认 65530 在索引稍微大一点的时候就会不够ES 启动自检也会卡这一项。vm.swappiness调到 1 是让系统尽量不用 swapES 被换出到磁盘会导致查询延迟飙升但完全设 0 在内存紧张时可能触发 OOM Killer1 是个折中。执行sysctl --system让它生效然后用sysctl vm.max_map_count验证。文件描述符限制在/etc/security/limits.d/99-elasticsearch.conf里加* soft nofile 65536 * hard nofile 65536 * soft memlock unlimited * hard memlock unlimited这里有个必须说清楚的点容器内的 ulimit 并不直接继承宿主机的 limits.conf它由 dockerd 的默认值决定。也就是说你改了 limits.conf容器里ulimit -n可能还是 1048576 或者别的值。真正起作用的是启动容器时的--ulimit参数或者在/etc/docker/daemon.json里配default-ulimits。这一条我在早期部署时完全没意识到改了半天 limits.conf 发现没效果白折腾半小时。3. Docker 引擎的离线落地Docker 装不上后面全是空谈。离线装 Docker 的难点不在安装命令本身而在依赖包的完整性。CentOS 7 上container-selinux这个包最容易缺缺了之后docker-ce安装会报依赖错误而错误信息只告诉你要装 container-selinux不告诉你它自己还有依赖。另一个容易忽略的事情是版本选择。CentOS 7 的内核 3.10 对较新的 Docker 版本支持有限我一般用 24.x 或者 20.10.x 这两个区间的版本太新的版本在 CentOS 7 上可能因为缺少某些内核特性而告警。当然如果你的环境允许Docker 20.10.24 这个版本在 CentOS 7 上被我验证过很多次跑 ES 8 完全没问题。3.1 安装包与依赖的完整抓取整包抓取之后把 rpm 全部拷到内网目录比如/opt/offline/rpms/docker/然后先看一眼都有些什么ls -1 /opt/offline/rpms/docker/ | sort rpm -qp --requires docker-ce-*.rpm | head -40第二条命令能列出 docker-ce 的直接依赖你可以对照着看哪些包已经在系统里了rpm -q 包名哪些还需要单独准备。这一步花五分钟能省掉后面反复试错的半小时。如果实在凑不齐依赖有个兜底办法是用rpm -ivh --nodeps强行安装但这属于给自己埋雷——缺的依赖往往是运行时真正需要的强行装完可能在启动时报找不到库。我更推荐的做法是在同类系统上先完整装一遍把/var/cache/yum或者下载目录整个打包带走这样拿到的是经过验证的完整集合。3.2 安装、启动与 daemon 配置进入 rpm 目录执行cd /opt/offline/rpms/docker/ yum localinstall -y ./*.rpm # 如果 yum 因为源不可用而报错改用 # rpm -ivh --replacepkgs ./*.rpm systemctl enable --now docker systemctl status docker --no-pager docker versiondocker version能同时输出 Client 和 Server 两段信息才算真正跑通只有 Client 说明 dockerd 没起来去看journalctl -u docker -n 50。接着配 daemon.json。这个文件在离线环境下有几个关键项数据根目录、日志轮转、默认 ulimit。日志轮转特别重要Docker 默认的 json-file 日志驱动不做限制一个跑几个月的容器日志文件能涨到几十 G把磁盘吃光{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, default-ulimits: { nofile: { Name: nofile, Soft: 65536, Hard: 65536 }, memlock: { Name: memlock, Soft: -1, Hard: -1 } }, live-restore: true }live-restore让 dockerd 重启时容器继续运行做维护时很有用。改完执行systemctl daemon-reload systemctl restart docker。注意>docker pull docker.elastic.co/elasticsearch/elasticsearch:8.11.3 docker images | grep elasticsearch docker save docker.elastic.co/elasticsearch/elasticsearch:8.11.3 | gzip -c es-8.11.3.tar.gz sha256sum es-8.11.3.tar.gz es-8.11.3.tar.gz.sha256 ls -lh es-8.11.3.tar.gz用管道 gzip 压缩体积能从 1.2G 降到 500M 左右走 U 盘或者内网传输都省事。docker save支持一次导出多个镜像如果你还要装 Kibana 或者别的组件可以一条命令搞定docker save -o bundle.tar image1 image2或者用 gzip 管道一起压。导出前建议先docker inspect看一下镜像的 Entrypoint 和环境变量心里有个数尤其 8.x 版本的默认配置和你手写的 yml 会合并弄清楚哪个优先。这一步不是必须的但能省掉后面“为什么我配的不生效”的困惑。4.2 内网侧导入与校验拷到内网机器上第一件事是对校验和cd /opt/offline/images/ sha256sum -c es-8.11.3.tar.gz.sha256输出OK再继续。然后导入gunzip -c es-8.11.3.tar.gz | docker load docker images | grep elasticsearch docker inspect docker.elastic.co/elasticsearch/elasticsearch:8.11.3 | grep -A3 Entrypointdocker load的输出会告诉你加载了哪个 tag。如果 tag 是none说明导出时用的是镜像 ID 而不是完整名字这时候用docker tag补一下就行但更稳妥的做法是重新导出。如果导入过程报no space left on device说明/var/lib/docker所在分区不够回到 3.2 节改>mkdir -p /data/es/{config,data,logs,plugins,snapshots} cp -a /data/es/config /data/es/config.bak # 改配置前先留一手属主设置是第一个坑。ES 官方镜像里的运行用户是elasticsearchUID 1000GID 0也就是 root 组。所以宿主机上挂载进去的目录属主必须是 1000:0否则容器内进程写不进去日志里会出现AccessDeniedException: /usr/share/elasticsearch/data/nodes这样的报错chown -R 1000:0 /data/es/data /data/es/logs /data/es/plugins /data/es/snapshots chmod -R 775 /data/es/data /data/es/logs注意 config 目录不需要给 1000 权限因为挂载进去是只读的。plugins 目录必须可写安装插件时要往里解压。如果你在启用了 SELinux 的机器上还需要加:z或:Z挂载选项或者干脆setenforce 0做临时验证。SELinux 导致的权限问题在日志里表现为Permission denied但ls -l看权限明明是对的这种“权限看着对却写不进去”的情况八成就是 SELinux。5.2 elasticsearch.yml 逐项配置单机版的最小可用配置大概是这样写在/data/es/config/elasticsearch.ymlcluster.name: es-single-prod node.name: node-1 path.data: /usr/share/elasticsearch/data path.logs: /usr/share/elasticsearch/logs path.repo: [/usr/share/elasticsearch/snapshots] network.host: 0.0.0.0 http.port: 9200 discovery.type: single-node bootstrap.memory_lock: true action.destructive_requires_name: true逐项说。cluster.name是个标识单机也用得上将来扩成集群时不用改。network.host: 0.0.0.0是让 ES 监听所有网卡默认只监听回环地址容器外就访问不到这是新手最常踩的坑之一。discovery.type: single-node是单机模式的关键加了它就不做集群发现。bootstrap.memory_lock: true是锁住内存防止被换出但它要求容器的 memlock 无限制配置不匹配会直接导致启动失败这一点后面 7.1 节会细说。action.destructive_requires_name: true是防止DELETE /_all这种误操作生产环境必开。8.x 版本还需要补一段安全相关配置。如果内网环境、上游有网关可以整个关掉xpack.security.enabled: false xpack.security.enrollment.enabled: false xpack.security.http.ssl.enabled: false xpack.security.transport.ssl.enabled: false如果是 7.x默认就没开安全上面这几行不需要写写了反而可能报未知配置。判断方式很简单看镜像 tag 的主版本号。关于挂载方式我强烈建议挂整个 config 目录而不是单个文件但前置动作是先把镜像内的默认配置拷出来docker run --rm docker.elastic.co/elasticsearch/elasticsearch:8.11.3 \ cat /usr/share/elasticsearch/config/jvm.options /data/es/config/jvm.options docker run --rm docker.elastic.co/elasticsearch/elasticsearch:8.11.3 \ cat /usr/share/elasticsearch/config/log4j2.properties /data/es/config/log4j2.properties为什么不用单文件挂载因为单文件挂载在宿主机上编辑时某些编辑器会采用“写临时文件再改名”的方式保存改完之后容器的挂载点指向的还是旧 inode配置看起来改了但没生效。这个坑很隐蔽用cat确认容器内文件内容才能发现。挂目录就没有这个问题。5.3 堆内存的算法堆内存的设置规则官方给了两条硬约束不超过物理内存的 50%不超过 31GB。第一条是为了给 Lucene 的文件缓存留空间ES 的查询性能很大程度上依赖操作系统的 page cache堆给太大反而拖慢读取。第二条是 JVM 压缩指针的边界超过 32GB 之后对象指针从 4 字节变 8 字节实际可用内存反而下降。我常用的对照表物理内存建议堆大小说明8G3G–4G小数据量够用留足缓存16G6G–8G最常见的生产配置32G12G–16G注意不要超过一半64G26G–31G逼近压缩指针边界谨慎128G31G再多给堆没意义加机器具体怎么设在/data/es/config/jvm.options.d/heap.options里写两行注意jvm.options.d目录需要手动创建里面的文件会覆盖jvm.options中的同名参数-Xms8g -Xmx8gXms 和 Xmx 必须相等否则 JVM 会在运行期反复扩缩堆带来不必要的 GC 波动。这一点在容器环境里尤其重要因为容器感知的内存限制和 JVM 看到的可能不一致。更规范的做法是用ES_JAVA_OPTS环境变量传进去但要注意它和配置文件冲突时的优先级。我的经验是只在一个地方设别两边都写否则排查起来很痛苦。容器方式下用环境变量更直观改完重启容器就行不用进目录改文件。5.4 启动命令逐条解释docker run -d \ --name es-single \ --restartalways \ -p 9200:9200 -p 9300:9300 \ -e ES_JAVA_OPTS-Xms8g -Xmx8g \ -e discovery.typesingle-node \ --ulimit memlock-1:-1 \ --ulimit nofile65536:65536 \ -v /data/es/config:/usr/share/elasticsearch/config \ -v /data/es/data:/usr/share/elasticsearch/data \ -v /data/es/logs:/usr/share/elasticsearch/logs \ -v /data/es/plugins:/usr/share/elasticsearch/plugins \ -v /data/es/snapshots:/usr/share/elasticsearch/snapshots \ -v /etc/localtime:/etc/localtime:ro \ docker.elastic.co/elasticsearch/elasticsearch:8.11.3--restartalways保证机器重启或者容器异常退出后自动拉起单机场景必备。-p 9200:9200是 HTTP 接口9300是节点间通信端口单机其实用不上 9300但如果将来要扩集群先留着不亏。--ulimit memlock-1:-1配合 yml 里的bootstrap.memory_lock: true两者必须成对出现。--ulimit nofile是文件描述符容器默认值通常够但显式写出来更保险。-v /etc/localtime:ro让容器时间和宿主机一致否则日志时间戳和你的排查时间对不上会很误导。如果机器上只跑 ES 一个服务可以用--network host替代端口映射省掉一层 NAT性能略好代价是端口直接暴露在宿主机上要自己用防火墙管。用 host 网络时容器内的network.host配置依然要写0.0.0.0别写成127.0.0.1。启动后别急着访问先看日志docker logs -f es-single正常启动会看到current.healthGREEN之类的输出整个过程大概 20 到 60 秒取决于磁盘速度和分片数。如果 30 秒内进程就退出了直接跳到第 7 节。6. 上线验证与日常运维容器起来不等于服务可用验证要分层做。我一般分四层进程层看容器状态端口层看监听接口层看 HTTP 响应集群层看健康值。四层都过了才算真正跑通。很多人只做第一层docker ps显示 Up 就以为成了结果应用连不上回头再查发现是防火墙。日常运维这块单机版最需要关注的是磁盘水位和快照。ES 有个默认的水位线机制磁盘使用超过 85% 后新索引的分片不再分配超过 90% 后索引变成只读这时候写入全部失败。单机场景没有副本可以迁移一旦触发只读只能删数据或者扩容。所以监控磁盘是必须的别等出事。6.1 健康检查的四个层次第一层容器状态docker ps --filter namees-single --format {{.Status}} docker inspect --format{{.State.Health.Status}} es-single第二层端口监听ss -lntp | grep -E 9200|9300第三层接口响应。8.x 开了安全的话要带认证# 关闭安全的情况 curl -s http://127.0.0.1:9200 | jq . # 开启安全的情况 curl -s -u elastic:你的密码 http://127.0.0.1:9200 | jq .第四层集群健康curl -s http://127.0.0.1:9200/_cluster/health?pretty单机模式下status应该是greennumber_of_nodes是 1。如果显示yellow说明有副本分片分配不出去——单机没有第二个节点任何带副本的索引都会 yellow。解决办法是把索引的副本数设为 0PUT /索引名/_settings {index:{number_of_replicas:0}}。这是单机版的常见“假故障”不是真的有问题。顺手做一次写入测试确认链路通curl -X POST http://127.0.0.1:9200/test-idx/_doc?refreshtrue \ -H Content-Type: application/json \ -d {msg:hello offline es,ts:2024-01-01} curl http://127.0.0.1:9200/test-idx/_search?pretty6.2 安全配置的两种走法第一种是关闭 xpack 安全适合内网、前面有网关或者负载均衡做认证的场景。改 yml 里那四行重启容器即可。代价是所有请求裸奔任何能访问 9200 的人都能删索引。第二种是开启8.x 默认就是开的。首次启动时日志里会打印elastic用户的初始密码或者用命令重置docker exec -it es-single bin/elasticsearch-reset-password -u elastic重置后把密码记下来然后给应用建一个专用账号别用 elastic 超级用户跑业务docker exec -it es-single bin/elasticsearch-users useradd app_user -p 强密码 -r app_role再配一个角色限制索引权限这套流程在 8.x 里都能用命令行完成不需要额外装工具。7.x 要开启安全需要改更多配置包括生成证书步骤繁琐得多如果只是内网自用7.x 直接不开更省事。注意开启安全后Kibana 或者客户端的连接方式要同步改成 HTTPS 加认证容器内的证书校验经常出问题测试阶段可以用-k跳过校验生产环境要把 CA 证书导出来配上。6.3 离线安装中文分词插件中文检索不装分词器基本没法用默认的标准分词器会把一个词拆成单字召回率虚高、准确率极低。离线安装 analysis-ik 的步骤在和 ES 同版本的联网机器上下载对应 zip拷到内网解压到 plugins 目录下的独立子目录。mkdir -p /data/es/plugins/ik unzip -o elasticsearch-analysis-ik-8.11.3.zip -d /data/es/plugins/ik ls /data/es/plugins/ik | head chown -R 1000:0 /data/es/plugins/ik chmod -R 755 /data/es/plugins/ik docker restart es-single判断安装成功解压后目录里应该直接有plugin-descriptor.properties文件而不是多套一层文件夹。如果解压出来是ik/elasticsearch-analysis-ik-8.11.3/plugin-descriptor.properties这种结构插件加载会失败需要把内层文件挪到外层。重启后验证curl -s http://127.0.0.1:9200/_cat/plugins?v能看到analysis-ik就成功了。然后用 IK 分词器测一下curl -X POST http://127.0.0.1:9200/_analyze -H Content-Type: application/json -d {analyzer:ik_max_word,text:离线安装搜索引擎单机版}输出里能看到合理切分的词就对了。ik_max_word是细粒度适合建索引查询时用ik_smart粗粒度召回更准。这两个名字在 mapping 里要写对写错了 ES 会用默认分词器静默兜底你不会收到报错但搜索结果很差。6.4 快照仓库与数据保护单机版的“高可用”只能靠快照。ES 的 fs 类型快照仓库要求路径必须在path.repo里声明前面配置里已经写了/usr/share/elasticsearch/snapshots容器启动后注册仓库curl -X PUT http://127.0.0.1:9200/_snapshot/backup_repo \ -H Content-Type: application/json -d {type:fs,settings:{location:/usr/share/elasticsearch/snapshots,compress:true}}然后手动打一个快照验证curl -X PUT http://127.0.0.1:9200/_snapshot/backup_repo/snap-001?wait_for_completiontrue curl -s http://127.0.0.1:9200/_snapshot/backup_repo/_all?pretty快照目录在宿主机上就是/data/es/snapshots可以用 rsync 同步到另一台机器或者备份服务器。这个组合在离线环境里特别实用——内网没有对象存储fs 仓库加 rsync 是最省事的方案。有两点要提醒。第一快照不能跨大版本恢复7.x 的快照恢复不到 8.x升级前要做好迁移方案通常用 reindex 或者导出导入。第二快照期间会占用额外的磁盘空间仓库所在分区要留足余量快照本身是增量存储的删旧快照的清理策略要提前定好。7. 排障实录那些年踩过的坑排障这件事经验比理论重要。ES 的启动失败信息大多藏在标准输出的前二十行docker logs默认会把所有内容刷出来关键信息往往被淹没。我的习惯是先docker logs es-single 21 | head -50看开头因为 bootstrap check 的报错都在最前面——ES 的启动流程是先做自检自检不过就直接退出后面的日志根本没有。另一个通用技巧是容器退出后不要急着docker rm容器还在的时候可以用docker inspect --format{{.State.ExitCode}}看退出码用docker logs看完整日志。删了就什么都没了。养成“先看后删”的习惯。7.1 启动即退出的五类报错第一类max virtual memory areas vm.max_map_count [65530] is too low。这是宿主机内核参数没调回到 2.3 节执行sysctl -w vm.max_map_count262144并且写进/etc/sysctl.d/持久化。容器内改不了这个参数因为它是宿主机内核参数宿主机sysctl是唯一的解法。第二类max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]。容器内的 nofile 限制不够加--ulimit nofile65536:65536或者在 daemon.json 里配 default-ulimits。第三类Unable to lock JVM Memory: error12, reasonCannot allocate memory。这是bootstrap.memory_lock: true但 memlock 没有放开加--ulimit memlock-1:-1同时确认宿主机的 limits.conf 里 memlock 是 unlimited。如果实在搞不定把 yml 里的bootstrap.memory_lock改成 false先跑起来但要注意这样堆可能被换出。第四类java.lang.IllegalArgumentException: Plugin [analysis-ik] was built for Elasticsearch version 8.10.0 but version 8.11.3 is running。插件版本不匹配换版本一致的插件包别想着改插件里的版本号蒙混过关。第五类AccessDeniedException: /usr/share/elasticsearch/data/nodes。目录属主不对chown -R 1000:0 /data/es/data配合 SELinux 处理。7.2 配置与权限类问题配置类问题的典型表现是“明明改了却不生效”。前面提过单文件挂载的 inode 问题验证方法是在容器内cat一下配置文件看内容是不是你改的那份。如果内容对但行为不对就要怀疑参数优先级命令行-E参数 环境变量ES_JAVA_OPTSjvm.options.djvm.optionselasticsearch.yml里同名参数以最后加载的为准。还有一种情况是容器内的配置目录被挂载成只读:roES 启动时想写一些运行时文件会失败。我的做法是 config 目录不加:ro因为 ES 确实需要在 config 目录里写一些东西比如 8.x 的证书文件。网络类问题看两个地方network.host是不是0.0.0.0防火墙有没有放行。CentOS 7 的 firewalld 放行命令firewall-cmd --permanent --add-port9200/tcp firewall-cmd --reload firewall-cmd --list-ports如果用了-p端口映射但访问不通还要确认 Docker 有没有改 iptables 规则导致和 firewalld 冲突这在 CentOS 7 上偶有发生重启 dockerd 通常能解决。7.3 性能与资源类问题查询变慢、写入变慢、频繁 GC这三类现象经常一起出现。排查顺序先看 GC 日志再看磁盘 IO最后看分片数和段数量。GC 日志可以通过在jvm.options.d里加-Xlog:gc*:file/usr/share/elasticsearch/logs/gc.log:time,uptime:filecount5,filesize50m打开8.x 的写法7.x 用-XlogGC。如果看到 full GC 频繁且回收后内存下降不多说明堆不够或者有内存泄漏前者加堆后者看是不是聚合查询写得太重。磁盘 IO 用iostat -x 1看%util接近 100% 说明磁盘是瓶颈考虑换 SSD 或者减少写入频率。写入侧的优化手段包括调大refresh_interval默认 1s日志类场景改成 30s 能显著降低段合并压力、用批量写入 API、控制单次 bulk 的大小在 5–15MB。分片数是个容易被忽略的点。单机版的合理分片数是“每个分片 10–50GB总数不超过 CPU 核数的 2 倍”。见过有人给一个每天 1GB 的索引设 10 个分片结果每个分片才几十兆段合并和查询开销全浪费在协调上了。索引模板里提前设好分片数别等数据进来再改改分片要 reindex。7.4 速查表现象大概率原因处理动作容器起来 3 秒退出vm.max_map_count 太小宿主机 sysctl 调到 262144日志报文件描述符不足容器 ulimit 不够加--ulimit nofile65536:65536爆内存锁定失败memlock 未放开加--ulimit memlock-1:-1或关 memory_lock数据目录写入被拒属主不是 1000:0chown -R 1000:0外部访问 9200 不通network.host 是回环改为 0.0.0.0 并放行防火墙集群状态 yellow单机有副本分片副本数设为 0索引突然只读磁盘水位超过 90%清理数据或扩容PUT _all/_settings解只读中文搜不到未装 IK 或 mapping 写错装插件并在 mapping 指定 ik_max_word插件加载失败版本不匹配或目录层级错换同版本包确认 descriptor 在目录顶层docker load 报错包损坏或磁盘满校验 sha256检查>
阅读完成 · 觉得有帮助?