ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Docker Compose部署Elasticsearch与离线IK分词器完整指南

基于Docker Compose部署Elasticsearch与离线IK分词器完整指南 1. 部署前必须想清楚的几个决策点1.1 为什么用docker-compose而不是直接装ES直接装Elasticsearch本身并不难解压、改配置、启动三步就能跑起来。但真正用起来之后就发现麻烦事一件接一件JDK版本不对启动报错内存配置没调好OOM升级版本时要重新配一堆参数更别提集群模式下每个节点都要重复操作一遍。docker-compose解决的是“环境一致性”和“可复制性”的问题。compose文件把镜像版本、端口映射、数据卷、环境变量、资源限制全部声明在一个文件里团队任何人拿到这份配置都能启动一个一模一样的环境。这个价值在多人协作和迁移场景下特别明显——我见过太多“在我机器上能跑”的经典事故用compose之后这类问题基本绝迹。另外docker-compose特别适合本地开发环境和测试环境。你可以在不污染宿主机的情况下快速起一个ES实例做验证用完docker-compose down一锅端干净利落。生产环境用compose的也有但更推荐Kubernetes或者其他编排平台不过那是另外的话题了。1.2 版本选择7.17.x是当前最稳的落点版本选择是部署ES第一个要做的决策也是很多人忽略的关键点。当前主流的ES版本线有7.x和8.x8.x引入了很多新特性比如安全认证默认开启、新的搜索能力等但对初学者和大部分业务场景来说7.17.x仍然是最成熟稳定的选择。原因有三点生态兼容性最好。Spring Boot 2.x集成的Elasticsearch客户端版本对应7.x很多中间件和工具链对7.x的支持最成熟踩坑成本最低。IK分词器支持完善。IK分词器对7.17.x的适配非常稳定网上资料多遇到问题容易找到解决方案。7.17是7.x的最终版本修复了大量bug相比7.0到7.16各版本要稳定得多。我实测用的是elasticsearch:7.17.0镜像。这里有个注意点docker镜像的tag和ES版本号是对应的但7.17.0和7.17.x后续小版本不一定都有独立tag拉取时要先确认镜像仓库里的可用tag列表避免写错版本号导致拉取失败。1.3 内存与系统参数这些坑不提前处理后面全炸ES是Java写的对内存敏感而且运行时需要一些操作系统级别的参数支持。直接跑docker run的话至少有两个坑是必踩的坑一JVM堆内存默认设置。ES默认堆内存是1GB如果你的机器内存够大比如16GB这个配置跑小数据量没问题但数据量上来之后频繁GC会导致查询响应变慢。我建议通过环境变量ES_JAVA_OPTS来设置堆内存比如-Xms512m -Xmx512m具体大小根据业务数据量和宿主机内存来定一般设置为物理内存的一半左右。坑二vm.max_map_count参数。ES底层依赖LuceneLucene需要大量的mmap文件映射如果操作系统默认的vm.max_map_count值通常是65530不够ES启动时会报错内容是max virtual memory areas vm.max_map_count [65530] is too low。这个参数在宿主机上执行以下命令即可解决sudo sysctl -w vm.max_map_count262144如果想永久生效需要修改/etc/sysctl.conf文件加入下面这行然后执行sysctl -pvm.max_map_count262144注意这个操作是在宿主机上执行不是在容器里。因为容器共享宿主机的内核参数。还有一个很多人遇到的是文件描述符限制。ES运行时会打开大量文件句柄Linux默认的1024限制根本不够。Docker容器默认继承宿主机的ulimit设置所以也需要在compose文件中主动调大。2. compose文件设计与每个参数的含义2.1 完整的目录结构规划动手写compose文件之前先把目录结构规划好。一个清晰的项目结构能让你后续维护时省很多心。我习惯这样组织es-deploy/ ├── docker-compose.yml ├── es-data/ # ES数据目录挂载到容器 ├── es-logs/ # ES日志目录挂载到容器 ├── es-plugins/ # ES插件目录挂载到容器后续装IK分词器 └── ik/ # IK分词器离线包存放目录es-data和es-logs是ES运行时的数据与日志目录必须挂载到宿主机否则容器一删数据全丢。es-plugins是和容器内插件目录的映射后面离线安装IK分词器时可以直接把插件文件放进去。2.2 docker-compose.yml逐行拆解与配置理由直接上一份我实际使用并验证过的完整配置version: 3.8 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0 container_name: es-node restart: always environment: - node.namees-node - cluster.namees-cluster - discovery.typesingle-node - bootstrap.memory_locktrue - ES_JAVA_OPTS-Xms512m -Xmx512m - TZAsia/Shanghai ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 hard: 65536 ports: - 9200:9200 - 9300:9300 volumes: - ./es-data:/usr/share/elasticsearch/data - ./es-logs:/usr/share/elasticsearch/logs - ./es-plugins:/usr/share/elasticsearch/plugins networks: - es-net networks: es-net: driver: bridge逐个参数说因为我发现很多人都是抄配置但根本没搞懂每个参数是干嘛的一旦出问题就完全不知道怎么排查。node.name和cluster.name前者是节点名后者是集群名。单节点模式下node.name其实不太重要但cluster.name要留意它是ES节点之间通信识别的标识。如果你后面要扩展成多节点集群所有节点的cluster.name必须一致才能组成一个集群。discovery.typesingle-node这个参数非常关键。它告诉ES“我是单节点模式”不需要做节点发现和选举。如果不设置ES会默认走集群发现流程单节点启动时经常因为主节点选举超时而启动失败。这也是很多新手第一次启动ES报错的根源。bootstrap.memory_locktrue锁定内存防止ES内存被交换到swap分区。ES是内存敏感型应用一旦发生swapGC暂停时间会急剧增加查询性能断崖式下降。配合ulimits.memlock的soft: -1, hard: -1表示不限制锁定内存大小这个配置才能生效。ES_JAVA_OPTSJVM堆内存配置。这里我设置了512m适合入门测试。生产环境建议至少2g但要保证不超过物理内存的一半因为ES的堆外内存文件缓存、网络缓冲等同样吃内存。ports9200是HTTP REST API端口给业务应用调用9300是集群节点间通信的TCP端口如果只是单机单节点9300可以不映射出去但多节点集群环境下各节点间需要通过这个端口通信。ulimits.nofile文件描述符数量上限。前面提到ES会打开大量文件句柄1024的默认值远远不够。这里设置为65536是ES官方文档推荐的最低值。volumes三个挂载点都很重要。数据目录不挂载容器重建就丢数据日志目录挂载是为了方便排查问题插件目录挂载是为IK分词器准备的。2.3 网络模式的选择考量compose默认会为服务创建一个bridge网络。把networks显式声明出来而不是用默认网络其实是为了后续扩展时更灵活——比如你后面打算在这个网络里再部署Kibana、Logstash组成一套完整的ELK技术栈用自定义网络可以保证它们之间通过服务名互相访问而不需要暴露一堆端口到宿主机。如果你有多个容器需要和ES通信建议把它们都加入同一个es-net网络然后通过服务名访问ES。比如在同一个compose文件里加一个Kibana服务配置elasticsearch.hosts时直接写http://elasticsearch:9200就能通。3. 离线IK分词器安装的完整路径3.1 为什么需要离线安装IK分词器Elasticsearch自带的standard分词器对中文的支持非常差——它会把中文按单字拆分而不是按词语切分。比如“中华人民共和国”会被拆成“中”“华”“人”“民”“共”“和”“国”这种结果对搜索来说几乎是灾难。IK分词器则能智能识别中文词语把“中华人民共和国”作为一个整体切出来搜索体验完全不一样。正常情况下安装插件通过elasticsearch-plugin install命令在线安装。但实际工作中经常遇到两类场景必须要走离线路径一是部署环境是内网隔离的宿主机访问不了外网二是容器运行时想改插件但容器没有外网权限。说白了这个“离线”需求其实是企业环境中特别常见的刚性需求。3.2 版本匹配IK分词器和ES版本必须一致这是整个IK分词器安装过程中最重要的规则没有之一。IK分词器在GitHub上发布时版本号和ES版本是绑定的。比如IK 7.17.0只对应ES 7.17.0如果你用的是ES 7.17.0但下载了IK 7.17.1加载时会直接报错。版本号必须一字不差不存在兼容一说。我实际踩过的坑一次我用ES 7.17.0手头只有一个IK 7.17.2的zip包想着“7.17.x应该差不多吧”结果ES启动时直接加载失败日志报Plugin [analysis-ik] is incompatible with version [7.17.0]只能重新下载对应版本。所以确认版本匹配的唯一方法先确定ES镜像版本号再根据这个版本号去下载IK分词器对应release版本。3.3 推荐方案把宿主机插件目录挂载进容器IK分词器离线安装有三种常见方式我列个表格对比一下方案操作方式优点缺点A. 挂载目录解压zip到宿主机目录挂载到容器插件目录无需重新构建镜像升级插件只需替换文件服务器目录结构要求一致B. Dockerfile构建在镜像基础上COPY插件文件镜像自包含可移植性好每次改插件都要重新build镜像C. 直接docker cp先启动容器再拷入插件重启容器操作简单直接容器重启后插件可能丢失不符合优雅实践我推荐方案A理由很实际插件和ES配置解耦。以后想升级IK分词器版本只需要在宿主机上替换文件、重启容器不用重新构建镜像。而且compose文件里已经配置了./es-plugins:/usr/share/elasticsearch/plugins的挂载直接利用这个设计即可。3.4 完整操作步骤第一步下载IK分词器离线包。行动正常的网络环境从IK分词器的GitHub仓库Release页面下载与ES版本匹配的zip包。比如ES 7.17.0对应的是elasticsearch-analysis-ik-7.17.0.zip。提示如果你下载服务器在国外网络不稳定可以通过镜像站下载。这里关键是文件校验下载后用sha256sum命令比对一下文件哈希值避免文件损坏导致的解压或加载失败。第二步在宿主机创建IK插件目录。按照之前规划的目录结构在es-plugins下创建一个子目录ikmkdir -p es-plugins/ik第三步解压IK插件到该目录。把zip包上传到宿主机解压到es-plugins/ik目录unzip elasticsearch-analysis-ik-7.17.0.zip -d es-plugins/ik解压完成后es-plugins/ik目录下应该包含commons-codec-1.9.jar、commons-logging-1.2.jar、httpclient-4.5.2.jar、httpcore-4.4.4.jar等jar包以及plugin-descriptor.properties和config目录。第四步修改目录权限。这是个经常被忽略的细节。容器内的ES是以uid 1000elasticsearch用户运行所以挂载进去的目录必须保证该用户有读写权限chown -R 1000:1000 es-plugins/ik如果没有这一步ES启动时读插件目录会权限不足日志显示Permission denied。第五步启动容器并验证。确保compose文件已就绪执行docker-compose up -d然后查看启动日志docker logs -f es-node正常启动时日志里会看到类似这样的输出[2024-01-15T10:23:45,123][INFO ][o.e.p.PluginsService ] [es-node] loaded plugin [analysis-ik]看到loaded plugin [analysis-ik]就说明IK分词器加载成功。4. 启动验证与常见问题排查4.1 验证ES是否正常运行容器启动后先确认ES本身是否正常响应。在宿主机上执行curl -X GET localhost:9200正常返回的JSON大致长这样{ name : es-node, cluster_name : es-cluster, cluster_uuid : xxxxx, version : { number : 7.17.0, build_flavor : default, build_type : docker, build_hash : xxxxx, build_date : 2022-03-03T21:21:22.381854Z, build_snapshot : false, lucene_version : 8.11.1, minimum_wire_compatibility_version : 6.8.0, minimum_index_compatibility_version : 6.0.0-beta1 }, tagline : You Know, for Search }返回tagline: You Know, for Search说明ES在正常服务。4.2 验证IK分词器是否真正生效ES启动正常不等于IK分词器就能用了。创建一个测试索引测试IK分词效果curl -X POST localhost:9200/_analyze -H Content-Type: application/json -d { analyzer: ik_max_word, text: 中华人民共和国国歌 }如果IK分词器加载成功返回结果会包含“中华人民共和国”、“中华人民”、“中华”、“华人”、“人民共和国”、“人民”、“共和国”、“共和”、“国歌”等词组。如果返回的是单字拆分结果说明IK分词器并没有生效需要检查插件是否加载成功。我建议测试时用ik_max_word和ik_smart两种分词器各试一次。ik_max_word是细粒度分词把文本拆得最细适合召回更多的搜索场景ik_smart是粗粒度分词拆出的词语更少但更精准适合需要精准匹配的场景。两种模式各有用途生产环境一般同时配置。4.3 常见错误与解决方案我在部署过程中以及帮同事排查时遇到的典型错误汇总如下错误一容器启动后立即退出日志里出现ERROR: [1] bootstrap checks failed [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]原因就是前面说的系统参数问题。在宿主机执行sudo sysctl -w vm.max_map_count262144然后重启容器。错误二内存锁失败日志里出现memory locking requested for elasticsearch process but memory is not locked多半是ulimits.memlock设置没生效。检查compose文件里memlock的配置是否和bootstrap.memory_lock配套。另外如果Docker本身没有启用限制能力也会导致这个配置不生效。错误三IK分词器版本不兼容日志里出现Plugin [analysis-ik] is incompatible with version [7.17.0]解决方法只有一个下载对应版本的IK分词器zip包替换后重启。错误四数据目录权限问题日志里出现Exception in thread main java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data这是因为挂载的目录权限不对ES进程无法写入数据目录。执行chown -R 1000:1000 es-data es-logs错误五端口被占用启动失败日志显示java.net.BindException: Address already in use说明宿主机9200端口已经被其他进程占用了。要么换一个宿主机端口映射比如9201:9200要么先解决端口冲突问题。4.4 容器重启后IK分词器不生效的处理这是一个比较隐蔽的问题。如果你已经把IK插件文件放到了挂载目录但重启容器后发现IK分词器没有加载大概率是插件目录结构不对。ES加载插件时会在plugins目录下扫描每个子目录读取其中的plugin-descriptor.properties文件来判断插件的名称和版本。如果你的目录结构是es-plugins/ik/elasticsearch-analysis-ik-7.17.0/里面又套了一层目录ES扫描不到最内层的插件描述文件自然加载失败。正确的目录结构是es-plugins/ik目录下直接就是插件文件包括plugin-descriptor.properties不能再多套一层。解压zip包时要注意这点很多人在这一步翻车。5. 进阶配置自定义词典与热更新5.1 自定义词典的应用场景IK分词器默认的词典覆盖了大部分常用词汇但很多垂直领域需要扩展词库才能获得更好的分词效果。比如你做的是医疗领域搜索“卡马西平”这种药名默认词典可能没有就会被拆得乱七八糟做电商搜索“戴森吹风机”同样需要自定义词条。IK分词器的config目录下有几个关键文件IKAnalyzer.cfg.xml主配置文件main.dic主词典ext.dic扩展词典stopword.dic停用词词典5.2 扩展词典配置实操在es-plugins/ik/config目录下编辑IKAnalyzer.cfg.xml?xml version1.0 encodingUTF-8? !DOCTYPE properties SYSTEM http://java.sun.com/dtd/properties.dtd properties commentIK Analyzer 扩展配置/comment entry keyext_dictext.dic/entry entry keyext_stopwordsstopword.dic/entry /properties然后在同目录下创建或编辑ext.dic文件每行一个词卡马西平 戴森吹风机 王者荣耀保存后重启容器词库即生效。5.3 词典热更新的坑与绕行方案IK分词器在7.x版本支持了词典热更新但配置起来比较复杂。比较坑的一个点是词典文件加载后是缓存在内存里的修改文件后如果不触发reload分词结果不会变化。而reload机制又和ES的节点状态有关实际使用中经常出现“改了词库文件但不生效”的困惑。我的建议是对于生产环境不要指望动态热更新改完词库直接重启容器最稳妥。毕竟ES重启本身就很快十几秒的事情比起调热更新配置花掉半天时间重启一次的成本低得多。如果你确实需要热更新IK提供了基于HTTP请求的reload接口但需要在配置中开启remote_ext_dict方式并且确保词库文件能通过HTTP访问。这套方案配置复杂度高网络隔离环境下还要额外部署HTTP服务性价比不高除非你的业务场景对词典更新频率要求特别高否则不建议一开始就上。6. 从单机到集群的平滑演进思路虽然本文主题是单机部署但很多读者后续一定会面临扩展集群的需求。有几点提前了解会有帮助6.1 单机配置如何改造成三节点集群之前compose文件里的discovery.typesingle-node是多节点集群部署时要第一个移除的配置。集群模式需要设置discovery.seed_hosts来指定其他节点的地址以及cluster.initial_master_nodes来指定初始主节点候选。一个简单粗暴的三节点集群compose写法是在一个compose文件里定义三个ES服务使用相同的cluster.name通过es-net网络互相通信。关键配置差异包括去掉discovery.typesingle-node设置discovery.seed_hosts: [es-node2, es-node3]注意这里用服务名通信设置cluster.initial_master_nodes: [es-node1, es-node2, es-node3]6.2 共享挂载目录的注意事项单机部署时我把es-plugins挂载到了宿主机目录。多节点集群时每台机器上的插件必须保持一致否则集群节点之间能力不一致数据迁移时会出现问题。建议在CI/CD流程中把插件安装做成镜像的一部分方案B而不是挂载共享目录。6.3 数据目录的备份策略单机模式下es-data里的数据就是这个节点的全部分身误删等于数据全丢。我用过比较靠谱的方案是定期用ES的快照API把索引备份到对象存储或者NAS而不是直接拷贝数据目录。因为直接拷贝正在运行中的ES数据目录大概率得到损坏的数据文件。PUT /_snapshot/my_backup { type: fs, settings: { location: /usr/share/elasticsearch/backup } }把location指向一个挂载到宿主机的外部存储目录定期执行快照创建操作数据安全性比裸拷贝高得多。回到最初的问题上docker-compose部署ES加离线IK分词器整套流程跑通之后其实非常简单步骤就那么几步。真正的价值在于理解每一步背后的原理和坑在哪里。这样下次你遇到Kibana装不上、Logstash连不上ES、分词效果不对这类后续问题时就不会无从下手了。最后想说的是我实际用这套流程部署过多次从本地开发机到隔离内网环境的服务器都跑过。遇到问题最多的不是ES本身反而是权限问题、目录结构和版本匹配这些“小细节”。所以建议各位在部署时严格按照这里列出的步骤来特别留意我标注的那些常见坑能省下不少排查时间。
返回列表