ES 7.17 三节点 Docker 集群接入 IK 自定义词典:一次从丢插件到热更新的完整排障

AI 生成声明:本文内容由 AI 生成或经 AI 辅助完成,请注意甄别。

背景

一套 3 节点的 Elasticsearch 7.17 集群跑在 Docker 里,节点分布在三台内网机器上,容器用 --net=host 起、数据目录挂载在宿主机。现在需要给 IK 分词器导入业务自定义词典,让"图计算引擎""向量数据库"这类专有名词不被切碎。

最初的直觉方案很朴素:改 IK 的 IKAnalyzer.cfg.xml,把词典文件填进 ext_dict,重启就完事。但容器是 docker stop && docker rm 重建式管理的,改在容器里的任何东西都会随容器删除而丢失------于是整个问题变成"如何把 IK 的插件与配置真正持久化到宿主机"。

真正做下来才发现,坑远不止这一层。

一、先搞清楚 7.x 的 IK 目录结构

这是整个过程中最关键的一条认知,也是后面所有问题的根源:

内容 位置
插件本体(jar 包、plugin-descriptor.properties) /usr/share/elasticsearch/plugins/analysis-ik/
配置文件(IKAnalyzer.cfg.xml、main.dic、stopword.dic) /usr/share/elasticsearch/config/analysis-ik/

在 IK 6.x 时代两者都在 plugins/analysis-ik/config/ 下,7.x 之后配置目录搬到了 ES 的 config/analysis-ik/。网上大量教程还是 6.x 的路径,照抄会直接撞墙。

所以持久化必须挂两个目录,缺一不可:

bash 复制代码
-v /data/elasticsearch/plugins/analysis-ik:/usr/share/elasticsearch/plugins/analysis-ik \
-v /data/elasticsearch/config/analysis-ik:/usr/share/elasticsearch/config/analysis-ik \

只挂配置、不挂插件,容器一重建插件就丢,_cat/plugins 立刻变空------这个现象后面会反复出现。

二、两种自定义词典方案

方案 A:本地词典 ext_dict

把词典文件放进配置目录,在 IKAnalyzer.cfg.xml 的 <properties> 里声明:

xml 复制代码
<entry key="ext_dict">custom.dic</entry>

多个词典用分号分隔。词典格式要求很硬:每行一个词,UTF-8 无 BOM ,可选 词#词性 标注词性。缺点是每次改词典都要重启节点,词典迭代频繁时体验很差。

方案 B:远程词典 remote_ext_dict(推荐)

把词典放到一个 HTTP 静态服务上,配置指向 URL:

xml 复制代码
<entry key="remote_ext_dict">http://192.168.1.13:8080/custom.dic</entry>

IK 会按 Last-Modified 头判断文件有没有变,默认约 60 秒轮询一次,变了就重新加载。只有第一次改配置需要重启,之后改词典内容全程免重启。

远程词典服务用 python3 -m http.server 就够,再用 systemd 托管保证开机自启:

ini 复制代码
[Unit]
Description=IK Dictionary HTTP Server
Wants=network-online.target
After=network-online.target

[Service]
User=deploy
Group=deploy
ExecStart=/usr/bin/python3 -m http.server 8080 --directory /data/ik-dict
Restart=always
RestartSec=5
WorkingDirectory=/data/ik-dict

[Install]
WantedBy=multi-user.target
bash 复制代码
sudo systemctl daemon-reload
sudo systemctl enable --now ik-dict-server
curl -I http://192.168.1.13:8080/custom.dic

返回 200 且带 Last-Modified 才算正常------python3 的 http.server 自带这个头,换成某些简易服务端可能没有,那就热更新失效。

服务装在哪台机器都行,只要三个节点都能访问到 8080 端口。建议同时配两个 URL(用分号分隔)指向不同机器上的同一份词典,避免单点:

xml 复制代码
<entry key="remote_ext_dict">http://192.168.1.13:8080/custom.dic;http://192.168.1.11:8080/custom.dic</entry>

三、踩坑实录

下面这些报错按发生顺序排列,基本覆盖了"docker 化 ES + 插件持久化"会遇到的所有典型问题。

坑 1:docker cp ... :/plugins/analysis-ik/config/. 报找不到文件

复制代码
Error response from daemon: Could not find the file
/usr/share/elasticsearch/plugins/analysis-ik/config/. in container es-node-1

第一反应是 docker cp 对结尾 /. 的兼容问题,但实际原因是 7.x 的 config 根本不在 plugins/ 下。先确认目录结构,别靠猜:

bash 复制代码
docker exec es-node-1 ls -la /usr/share/elasticsearch/plugins/analysis-ik/
docker exec es-node-1 ls -la /usr/share/elasticsearch/config/analysis-ik/

第一条只看到 jar 包、第二条才有 IKAnalyzer.cfg.xml,说明路径用错了,正确的复制源是 /usr/share/elasticsearch/config/analysis-ik/.。

坑 2:重建容器后插件消失,_cat/plugins 为空

复制代码
name component version

空表。原因就是只挂了 config/analysis-ik,没挂 plugins/analysis-ik,插件 jar 躺在容器可写层里,docker rm 之后灰飞烟灭。

修复要先在运行中的容器里重装插件,再把整个插件目录复制出来持久化:

bash 复制代码
docker exec -it es-node-1 /bin/bash -c \
  "./bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/7.17.0"

sudo mkdir -p /data/elasticsearch/plugins
sudo docker cp es-node-1:/usr/share/elasticsearch/plugins/analysis-ik /data/elasticsearch/plugins/
sudo chown -R 1000:1000 /data/elasticsearch/plugins

官方源下载慢的话可以换成镜像站的 release zip(elasticsearch-plugin install <zip-url> 形式同样可用)。

坑 3:/data/elasticsearch 整目录 chown 把 ES 搞崩了

在目标机上为了能 scp 写入,顺手执行了:

bash 复制代码
sudo chown -R deploy:deploy /data/elasticsearch

结果节点进入 Restarting 循环:

复制代码
ElasticsearchException[failed to bind service];
nested: AccessDeniedException[.../data/nodes/0/indices/.../_state/.es_temp_file]

ES 容器内进程以 uid 1000 运行,data 目录属主被改成普通用户后写不进去,节点直接启动失败。数据目录千万不能跟着改属主:

bash 复制代码
sudo chown -R 1000:1000 /data/elasticsearch/data
docker restart es-node-2

正确的目录权限分工是:

  • data、plugins:1000:1000,容器内进程要读写;
  • 需要给普通用户同步的目录:保持属主不变,只补读权限 sudo chmod -R a+rX。

坑 4:scp 报 Permission denied

复制代码
scp: local opendir "/data/elasticsearch/config/analysis-ik": Permission denied

目录属主是 1000:1000、权限 700 左右,普通用户 deploy 读不了本地源目录。不用改属主,加读权限即可:

bash 复制代码
sudo chmod -R a+rX /data/elasticsearch/config/analysis-ik
sudo chmod -R a+rX /data/elasticsearch/plugins

a+rX 里的 X 只对目录生效,不会给普通文件加执行位,是这里最合适的写法。

坑 5:scp 报 stat remote: No such file or directory

不是本地问题,是目标机上 /data/elasticsearch/ 还没建。先远程把目录建好并授权:

bash 复制代码
ssh -t deploy@192.168.1.12 "sudo mkdir -p /data/elasticsearch/config && sudo chown -R deploy:deploy /data/elasticsearch"

坑 6:SSH 远程执行 sudo 报 a terminal is required

复制代码
sudo: a terminal is required to read the password; either use the -S option ...

非交互式 SSH 没有分配终端,sudo 无法提示密码。加上 -t 强制分配伪终端即可:

bash 复制代码
ssh -t deploy@192.168.1.12 "sudo mkdir -p /data/elasticsearch/config && sudo chown -R deploy:deploy /data/elasticsearch"

或者一劳永逸配置该用户的 sudo 免密(内网运维机可以接受):

bash 复制代码
ssh -t deploy@192.168.1.12 "echo 'deploy ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/deploy && sudo chmod 440 /etc/sudoers.d/deploy"

坑 7:三台节点插件不一致

_cat/plugins 只列出两台,第三台缺失。用 docker inspect 看挂载就能一眼定位:

bash 复制代码
docker inspect es-node-3 --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'

结果少了 plugins/analysis-ik 那一行------重建时用的是旧命令。补上挂载重建即可。三台节点的插件与配置必须完全一致,否则同一段文本在不同节点分词结果不同,检索会出现莫名其妙的不一致。

坑 8:集群 red、大量分片未分配

滚动重启 + 权限事故之后,unassigned_shards 一度涨到 63,status: red。处理顺序是:

bash 复制代码
# 1. 先看能不能直接重试(节点重启导致的未分配,通常这步就够)
curl -s -m 10 -u "elastic:${ES_PWD}" -X POST "http://localhost:9200/_cluster/reroute?retry_failed=true?pretty"

# 2. 重试无效,查具体原因
curl -s -m 10 -u "elastic:${ES_PWD}" "http://localhost:9200/_cluster/allocation/explain?pretty"

# 3. 同时看磁盘水位
curl -s -m 10 -u "elastic:${ES_PWD}" "http://localhost:9200/_cat/allocation?v"

reason 字段的常见取值:NODE_LEFT / CLUSTER_RECOVERED 属暂时状态,重试后自动恢复;ALLOCATION_FAILED 要看详细报错;如果看到 DISK_WATERMARK 则是磁盘水位,只能先清盘或临时调高阈值(恢复后务必调回):

bash 复制代码
curl -s -m 10 -u "elastic:${ES_PWD}" -X PUT "http://localhost:9200/_cluster/settings" \
  -H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "92%",
    "cluster.routing.allocation.disk.watermark.high": "95%",
    "cluster.routing.allocation.disk.watermark.flood_stage": "98%"
  }
}'

另外,curl 卡住不代表集群挂了------16g 堆的节点重启加 60 多个分片恢复,1~3 分钟内连不上是正常的。排查时一律加 -m 10 超时,避免在终端里干等。

四、怎么验证自定义词典真的生效了

按三层查,从配置到效果逐层排除:

第一层:配置是否指向词典

bash 复制代码
cat /data/elasticsearch/config/analysis-ik/IKAnalyzer.cfg.xml

三台的 remote_ext_dict 内容应完全一致。

第二层:词典服务是否可用

bash 复制代码
systemctl status ik-dict-server
curl -I http://192.168.1.13:8080/custom.dic

第三层:分词效果

bash 复制代码
curl -s -m 10 -u "elastic:${ES_PWD}" "http://localhost:9200/_analyze?pretty" \
  -H 'Content-Type: application/json' -d'
{
  "analyzer": "ik_max_word",
  "text": "知识图谱推理"
}'

判断标准:tokens 里出现完整的"知识图谱推理"即生效;被切成"知识/图谱/推理"则没加载。再拿一个词典里没有的词做对照,两个结果一对比就清楚。

两个容易忽略的点:

  • 分析器必须是 ik_max_word 或 ik_smart,用 standard 测永远不会命中自定义词;
  • 刚改完词典要等约 60 秒轮询周期,立刻测还是旧结果。

节点日志里也能看到加载记录,连不上词典服务时会打 download error:

bash 复制代码
docker logs es-node-1 2>&1 | grep -iE "dict|ik" | head -20

五、日常维护

加词:一条命令,一分钟全集群生效。

bash 复制代码
echo "新词条" | sudo tee -a /data/ik-dict/custom.dic

批量加词用 heredoc 更省事。带词性写 词条#nz。

删词 :这是 IK 的已知行为------热更新只增不减,已加载进内存的词不会因为从词典里删掉而消失。想真正删掉,要么重启节点全量加载,要么把词典文件改名(让 IK 重新拉取),推荐后者:

bash 复制代码
sudo mv /data/ik-dict/custom.dic /data/ik-dict/custom.dic.old
# 重新写入一份不含待删词的新 custom.dic

改词性同理,只对新增生效。

备份:词典虽然是纯文本,但丢了就是线上分词立刻跑偏,建议定期留一份:

bash 复制代码
sudo cp /data/ik-dict/custom.dic /data/ik-dict/backup/custom.dic.$(date +%Y%m%d)

词典体积:IK 启动时把词典全量读进每个节点的内存,几万词以上会明显拖慢启动并占用堆,别把整个行业词库一股脑塞进去。

本地 + 远程同时配置 :ext_dict 保证节点启动时基础词一定可用(哪怕词典服务暂时挂了),remote_ext_dict 负责日常热更新 ------ 两者并存是最稳的组合。

六、最后的检查清单

给容器化 ES 配 IK 自定义词典,收尾时确认这几条:

  1. 插件目录、配置目录两个都挂载,缺一会导致重建即失联;
  2. 节点采用 1000:1000 属主,同步用 chmod a+rX 而不是改属主,数据目录尤其不能动;
  3. 三台节点的插件版本、IKAnalyzer.cfg.xml 内容完全一致;
  4. 词典文件 UTF-8 无 BOM,每行一词;
  5. 远程词典服务有 Last-Modified,三节点网络可达 8080;
  6. 用 _analyze + ik_max_word 验证,而不是只看文件在不在;
  7. 重建节点逐台进行,每台起来并加回集群后再动下一台,避免分片恢复压力叠加。

整套配好之后,日常维护确实只剩下"一条 echo + 等 60 秒",但前期把目录、权限、挂载这三件事理顺,才是真正省时间的地方。

注:文中 IP、主机名、账号、密码、集群名、索引与路径等均已做匿名化/占位符处理,示例账号密码一律用 ${ES_PWD} 环境变量代替。

相关推荐
风翼靓崽17 分钟前
jenkins搭建与简单使用
运维·jenkins
小白的码BUG之路1 小时前
Jenkins -- gitee+docker+harbor+k8s 部署项目
docker·gitee·jenkins
AlfredZhao1 小时前
Docker Compose 平滑切换环境变量文件,容器配置不残留
docker
新*15 小时前
Elasticsearch 7.17 的 SQL 到底能查到什么?一次踩坑记录
后端·sql·elasticsearch·查询
duansamve17 小时前
Python3+Flask+PostgreSQL18+Vue3项目生产环境用docker如何部署?
python·docker·flask
Z21-X20 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
进击的荆棘1 天前
Linux系统——线程同步与互斥之线程池和安全
linux·运维·服务器·安全·单例模式·线程
瑞雪兆丰年兮1 天前
JavaWeb-day11-SpringBootWeb案例(文件上传)
java·运维·服务器·javaweb·oss
那明眸似水1 天前
云原生项目三:Docker秒级启动NextCloud个人网盘存储办公系统
linux·运维·docker·云原生·容器