1. 为什么离线装 ES 比在线装更考验人
在完全断网或者内网隔离的 CentOS 7 机器上部署 Elasticsearch,跟平时yum install一把梭完全是两码事。在线装的时候,依赖缺了直接补,版本不对直接换,网络慢就挂个代理重试;而离线环境里,你手里只有一台不能出网的机器和一个 U 盘,任何一个依赖没提前备好,整个安装就得推倒重来。我前后在三个不同规模的内网环境里做过 ES 的离线部署,踩过的坑从 JDK 版本不匹配到 systemd 启动超时,几乎把能遇到的都遇了一遍。
这篇内容面向的是需要在无外网 CentOS 7 服务器上部署 Elasticsearch 的运维和开发人员。我会把整个流程拆成"准备什么、为什么这么准备、怎么装、装完怎么验证、出问题怎么查"这几条线,重点讲清楚那些在线安装时根本不会暴露、但离线环境下会要命的细节。读完你至少能拿到一套可以直接照着走的操作路径,以及一份我实际踩坑后总结的排查清单。
需要先明确一个前提:Elasticsearch 从 7.x 开始自带了 JDK,这给离线部署省了大事。但如果你选的是 6.x 或者更早的版本,就得自己准备 JDK,而且版本卡得很死。下面我以7.x 系列为主线来讲,同时在关键位置标注 6.x 的差异,方便你对号入座。
2. 离线包到底该准备哪些东西
2.1 主程序包的选择:tar.gz 还是 rpm
Elasticsearch 官方提供两种离线包:tar.gz和rpm。很多人第一反应是选 rpm,觉得用yum localinstall更"正规"。但在离线场景下,我强烈建议用tar.gz,原因有三个。
第一,rpm 安装会自动创建elasticsearch用户、注册 systemd 服务、设置一堆默认路径,看起来省事,但它对目录权限和 SELinux 的假设很强,一旦你的环境有定制,反而更难改。第二,rpm 的卸载和重装会牵扯到配置文件残留,调试阶段反复装卸很烦。第三,tar.gz 解压即用,所有路径你自己说了算,出问题直接删目录重来,干净利落。
tar.gz 包从哪来?在一台能出网的同版本 CentOS 7 机器上下载即可,注意要选对架构。命令很简单:
# 在有网的机器上执行,下载 7.17.x 版本(7.x 最后一个稳定系列) wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.9-linux-x86_64.tar.gz # 同时下载校验文件,离线环境也要验证完整性 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.9-linux-x86_64.tar.gz.sha512下载完务必做一次校验,U 盘拷贝过程中文件损坏是常有的事,等到解压报错再回头查就浪费时间了:
sha512sum -c elasticsearch-7.17.9-linux-x86_64.tar.gz.sha5122.2 那些容易被忽略的依赖项
ES 7.x 自带 JDK,所以不用单独装 Java,这是最大的便利。但有几个系统层面的依赖,在线装时 yum 会自动带上,离线时就得自己确认。
首先是libcurl和openssl相关的库。ES 启动时会加载本地库做安全通信,如果系统里缺libcurl.so或者版本太老,启动日志里会出现Unable to load JNA之类的报错。CentOS 7 最小化安装通常自带,但如果你用的是精简过的镜像,就得检查:
rpm -qa | grep -E "libcurl|openssl"其次是nc(netcat)和lsof,这两个不是 ES 运行必需的,但排查端口占用和网络连通性时没有它们会非常难受。离线环境建议提前把net-tools、lsof、nc这几个包的 rpm 一起备好,用yum localinstall装上。
还有一个隐藏项:文件句柄数和内存映射数。ES 对这两个系统参数有硬性要求,在线装时很多人靠ulimit -n临时改,但离线生产环境必须写进配置文件,否则重启就失效。这个后面单独讲。
2.3 版本匹配的坑:别让 JDK 拖后腿
虽然 7.x 自带 JDK,但如果你因为某些原因必须用 6.x,那 JDK 版本就是头号大坑。ES 6.x 要求 JDK 8,而且对具体小版本有要求,太新的 JDK 8u 版本反而可能因为安全策略变化导致启动失败。我遇到过用 JDK 8u302 启动 ES 6.8 报java.lang.IllegalArgumentException的情况,换成 8u252 就正常了。
所以离线准备时,JDK 的 tar.gz 也要一并下载,并且在目标机器上先解压测试,确认java -version输出正常再继续。别等到 ES 装到一半才发现 JDK 有问题。
| 组件 | 7.x 是否需要单独准备 | 6.x 是否需要单独准备 | 备注 |
|---|---|---|---|
| JDK | 否(自带) | 是(JDK 8) | 6.x 对 JDK 小版本敏感 |
| libcurl | 通常自带 | 通常自带 | 精简镜像需检查 |
| lsof/nc | 建议准备 | 建议准备 | 排查用,非运行必需 |
| systemd | 系统自带 | 系统自带 | CentOS 7 默认有 |
3. 系统层面的前置配置怎么调
3.1 内核参数的修改逻辑
ES 启动时会检查两个内核参数:vm.max_map_count和文件句柄数。前者默认值是 65530,而 ES 要求至少 262144。这个参数控制的是单个进程能拥有的内存映射区域数量,ES 的 Lucene 底层大量使用 mmap 来读写索引文件,值太小会直接导致启动失败,报错信息是max virtual memory areas vm.max_map_count [65530] is too low。
修改方式是在/etc/sysctl.conf里追加一行,然后sysctl -p生效:
echo "vm.max_map_count=262144" >> /etc/sysctl.conf sysctl -p文件句柄数则要改/etc/security/limits.conf,同时注意 CentOS 7 的 systemd 服务不受 limits.conf 完全控制,如果后面用 systemd 管理 ES,还得在 service 文件里单独声明LimitNOFILE。这一点很多人会漏,导致明明改了 limits.conf,ES 启动后ulimit -n还是 4096。
# /etc/security/limits.conf 追加 * soft nofile 65536 * hard nofile 65536 * soft nproc 4096 * hard nproc 4096注意:
*通配符对 root 用户不生效,如果你打算用 root 启动测试(生产绝对不建议),得单独写root soft nofile 65536。
3.2 专用用户与目录权限
ES 从 5.x 开始就禁止用 root 直接启动,这是硬性限制,绕不过去。所以离线环境里第一件事是建一个专用用户:
groupadd es useradd -g es -m -s /bin/bash es然后把解压后的 ES 目录整个chown给这个用户。这里有个细节:如果你把 ES 放在/opt或/usr/local下,父目录的权限也要确认,否则 es 用户可能连目录都进不去。我习惯把 ES 放在/data/elasticsearch这种独立数据盘路径下,既方便管理,也避免和系统目录混在一起。
mkdir -p /data tar -zxvf elasticsearch-7.17.9-linux-x86_64.tar.gz -C /data mv /data/elasticsearch-7.17.9 /data/elasticsearch chown -R es:es /data/elasticsearch3.3 关闭 swap 的必要性说明
ES 官方明确建议关闭 swap,原因是当 JVM 堆内存被换出到磁盘时,垃圾回收的停顿时间会急剧增加,导致节点响应变慢甚至被集群踢出。离线环境里这个操作更要提前做,因为一旦 ES 跑起来再关 swap,可能触发一次长时间的 GC。
临时关闭用swapoff -a,永久关闭要注释掉/etc/fstab里的 swap 行。改完用free -h确认 swap 那一行全是 0。
swapoff -a # 编辑 /etc/fstab,注释掉包含 swap 的行 sed -i '/swap/s/^/#/' /etc/fstab4. 配置文件里必须改对的几个参数
4.1 elasticsearch.yml 的最小改动集
解压出来的config/elasticsearch.yml默认全是注释,很多人直接启动发现只能本机访问,或者集群名不对。离线单机部署,至少要改这几项:
cluster.name: es-offline-cluster node.name: node-1 path.data: /data/elasticsearch/data path.logs: /data/elasticsearch/logs network.host: 0.0.0.0 http.port: 9200 discovery.seed_hosts: ["127.0.0.1"] cluster.initial_master_nodes: ["node-1"]这里重点说network.host。默认是localhost,只监听回环地址,外部访问不了。改成0.0.0.0后 ES 会进入"生产模式",此时它会强制检查一系列引导配置(bootstrap checks),如果前面说的vm.max_map_count没改,启动会直接失败。所以顺序很重要:先改内核参数,再改 network.host。
cluster.initial_master_nodes是 7.x 引入的,用于首次启动时选举主节点。单机部署写自己的 node.name 即可。这个配置只在集群第一次启动时生效,后续重启不需要改,但留着也没关系。
4.2 jvm.options 的堆内存设置
ES 默认的 JVM 堆是 1GB,对于稍微有点数据量的场景都不够。改堆内存要编辑config/jvm.options,找到-Xms和-Xmx两行。原则是:两者设成一样大,避免运行期动态调整带来的性能波动;不超过物理内存的 50%,且不超过 31GB(超过会失去指针压缩优化)。
# 假设机器有 16GB 内存,设 8GB 堆 -Xms8g -Xmx8g离线环境里经常遇到机器内存很大但堆设得很小的情况,白白浪费资源。我的经验是:如果这台机器只跑 ES,堆可以给到物理内存的 50%;如果还跑别的服务,给 30% 到 40% 比较稳妥。剩下的内存 ES 会用来做文件系统缓存,这对搜索性能同样关键,所以不要把内存全给堆。
4.3 一个容易翻车的细节:路径里的空格和中文
path.data和path.logs的路径里绝对不能有空格和中文。ES 启动脚本对路径的处理不够健壮,带空格的路径会导致 JVM 参数解析错乱,报错信息还特别隐晦,往往只显示Could not find or load main class。离线部署时目录名尽量用纯英文加下划线,别图省事用中文。
5. 启动方式的选择与 systemd 集成
5.1 前台启动先验证,别急着上 systemd
第一次启动强烈建议用前台方式,直接su - es然后跑bin/elasticsearch。这样所有日志直接打在终端上,有任何报错立刻能看到。如果一上来就配 systemd,启动失败后还得journalctl翻日志,效率低很多。
su - es cd /data/elasticsearch ./bin/elasticsearch看到类似publish_address {127.0.0.1:9200}和started的日志,说明启动成功。此时另开一个终端curl localhost:9200,应该返回一段包含 cluster_name 和 version 的 JSON。
5.2 写一个靠谱的 systemd unit 文件
前台验证通过后,再配 systemd 做后台常驻。在/etc/systemd/system/下建elasticsearch.service:
[Unit] Description=Elasticsearch After=network.target [Service] Type=simple User=es Group=es Environment=ES_HOME=/data/elasticsearch Environment=ES_PATH_CONF=/data/elasticsearch/config Environment=JAVA_HOME=/data/elasticsearch/jdk WorkingDirectory=/data/elasticsearch ExecStart=/data/elasticsearch/bin/elasticsearch LimitNOFILE=65536 LimitNPROC=4096 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target几个关键点:LimitNOFILE必须写,否则 limits.conf 对 systemd 服务不生效;Restart=on-failure让 ES 意外退出后自动拉起;JAVA_HOME指向自带的 JDK,避免系统里其他 Java 干扰。
配好后:
systemctl daemon-reload systemctl enable elasticsearch systemctl start elasticsearch systemctl status elasticsearch5.3 启动超时的处理
systemd 默认给服务的启动超时是 90 秒。ES 在数据量大或者磁盘慢的机器上,首次启动可能超过这个时间,导致 systemd 判定失败并杀掉进程。如果你在status里看到start operation timed out,就在[Service]段加一行TimeoutStartSec=300,给足启动时间。
6. 装完之后怎么确认真的能用
6.1 基础连通性检查
curl localhost:9200返回 JSON 只是第一步。接着要确认集群健康状态:
curl -s localhost:9200/_cluster/health?pretty单机部署下status应该是green,number_of_nodes是 1。如果是yellow,说明有副本分片没分配,单节点环境下这是正常的,因为副本不能和主分片在同一节点。可以在索引模板里把副本数设为 0,或者接受 yellow 状态。
6.2 写入和查询的冒烟测试
光看健康状态不够,实际写一条数据再查出来,才能确认索引功能正常:
# 写入 curl -X POST "localhost:9200/test-index/_doc/1" -H 'Content-Type: application/json' -d '{"name":"offline-test","value":100}' # 查询 curl -s "localhost:9200/test-index/_search?pretty"如果写入报cluster_block_exception,说明磁盘水位触发了只读锁。离线环境里磁盘空间往往比较紧张,ES 默认磁盘使用超过 95% 就会把所有索引设为只读。检查df -h确认空间,必要时清理或者调整水位线。
6.3 开机自启的验证
systemctl enable只是设置了软链接,真正验证要重启机器:
reboot # 重启后 systemctl status elasticsearch curl localhost:9200这一步在离线环境特别重要,因为很多内网机器不允许随便重启,等到真正需要重启维护时才发现自启没配好,那就被动了。
7. 离线环境特有的排错思路
7.1 日志是第一手线索
ES 的日志在logs/目录下,主日志是<cluster.name>.log。启动失败时,先看这个文件的最后几十行。常见的几类报错和处理方式我整理成表:
| 报错关键词 | 根本原因 | 处理方式 |
|---|---|---|
max_map_count too low | 内核参数未改 | 改 sysctl.conf 并生效 |
max file descriptors too low | 句柄数不足 | 改 limits.conf + systemd LimitNOFILE |
can not run elasticsearch as root | 用 root 启动 | 切换到 es 用户 |
failed to obtain node locks | 数据目录被占用 | 确认没有残留进程,检查 path.data |
java.lang.IllegalArgumentException | JDK 版本不匹配 | 6.x 换 JDK 小版本 |
Connection refused | 服务没起来或端口不对 | 查进程和端口监听 |
7.2 端口占用与防火墙
离线环境里 9200 端口被占用的概率不高,但 9300(集群通信端口)有时会和别的服务冲突。用ss -tlnp | grep 9200确认监听情况。另外 CentOS 7 默认开 firewalld,如果外部要访问,得放行端口:
firewall-cmd --permanent --add-port=9200/tcp firewall-cmd --permanent --add-port=9300/tcp firewall-cmd --reload如果内网有安全组或者硬件防火墙,这一步也要同步配置,别只改了系统防火墙就以为通了。
7.3 磁盘空间和 inode 的隐性坑
ES 对磁盘空间敏感,但很多人只看df -h的容量,忽略了 inode。ES 会创建大量小文件,inode 耗尽时同样无法写入,而df -h看起来空间还很充裕。用df -i检查 inode 使用率,超过 80% 就要警惕。
另外,path.data所在分区的挂载参数如果带了noexec,ES 启动时加载本地库会失败。检查/etc/fstab里对应分区的挂载选项,确保没有noexec。
8. 我踩过的几个真实坑和应对
第一个坑是解压后直接启动,忘了改目录属主。ES 启动时报AccessDeniedException,日志指向 data 目录。原因是 tar 包解压后属主是 root,es 用户没有写权限。解决就是chown -R es:es,但要注意如果 data 目录已经生成了文件,得先清空再改属主,否则残留文件还是 root 的。
第二个坑是systemd 启动后ulimit -n还是 4096。查了半天发现 limits.conf 改了但没在 service 文件里写LimitNOFILE。CentOS 7 的 systemd 服务不继承 login shell 的 limits 设置,这是 systemd 的设计,不是 bug。加上LimitNOFILE=65536后解决。
第三个坑是离线机器时间不同步导致集群无法组建。虽然单机部署影响不大,但如果后续要扩成多节点,节点间时间差超过一定阈值会导致握手失败。离线环境没有 NTP,建议手动校准时间或者在内网搭一个时间源。
第四个坑是用 scp 传大包时中断,文件不完整但没校验。解压时报gzip: stdin: unexpected end of file,重新传一遍才好。所以前面强调的 sha512 校验一定要做,别省这一步。
9. 后续扩展时可以怎么调整
单机跑通之后,如果数据量涨上来要扩成集群,离线环境的扩展思路和在线差不多,但有几个点要提前想好。一是新节点的 ES 包和版本必须和现有节点完全一致,离线环境里版本管理靠人工,容易搞混,建议在包名里带上版本号并归档。二是discovery.seed_hosts要改成所有 master 候选节点的地址列表,cluster.initial_master_nodes只在首次组建集群时用,扩节点时不要带这个配置。三是节点间通信端口 9300 必须互通,离线内网里这个通常没问题,但安全组策略要确认。
另外,如果后续要装 Kibana 或者 Logstash 做配套,它们的离线安装思路和 ES 一样:提前下好 tar.gz、确认依赖、配好 systemd。Kibana 的版本必须和 ES 严格一致,小版本号都不能差,这是硬性要求,离线准备时别下错。
我个人在实际操作中的体会是,离线部署最花时间的从来不是安装本身,而是前期把依赖和参数理清楚。在线装的时候这些都被 yum 和网络掩盖了,离线环境把它们全部暴露出来。把上面这些点过一遍,基本能覆盖 90% 的翻车场景。剩下那 10%,就得靠看日志和一点点耐心了。