写了 Dockerfile 却没生效:ES + Kibana 的 compose 配置里,藏着三个反直觉的点
大多数人第一次用 compose 搭 Elasticsearch,都是复制一份 docker-compose.yml、up -d、浏览器能打开 9200,然后就算完成了。但这套跑通的动作里,至少有三件事和直觉相反:装了 IK 的 Dockerfile 可能压根没被执行;注释写着"等服务就绪",代码实际只等了容器启动;把 localhost 写进 Kibana 的配置,通常连不上。
这篇不谈 ES 的完整概念,只谈这三个点,外加一个绕不开的判断题:ES 到底快在哪。配置来自一个本地实验项目,命令运行未验证。
三个反直觉,先给结论
| 你以为 | 实际 |
|---|---|
| 目录里有 Dockerfile,镜像就是按它构建的 | compose 写的是 image:,Dockerfile 处于未接线状态 |
depends_on 表示"等 ES 启动完成" |
它只保证容器启动顺序,不管服务是否就绪 |
容器里访问 localhost:9200 就是宿主机 |
容器有独立网络栈,localhost 指向容器自己 |
这三条都不是配置写错,而是语义理解偏差。下面逐条拆。
快的原因不在"换了个数据库"
先解决一个更根本的判断题,否则后面全是背命令。
MySQL 查文本慢,不是它"不够快",而是查询方式决定它用不上索引。LIKE '%关键词%' 前面带通配符,B+ 树没法从最左前缀定位,只能逐行取出文本做匹配。项目笔记里对这个场景的判断很直接:"mysql 扛不住,性能很差"。
ES 换的是数据组织方向:
text
正排:database → table → row → column → 文本
倒排:词 → 文档 ID 列表
一个词对应一串文档编号,查询时不必碰原文。所以严格说,ES 不是"更快的 MySQL",而是为"找包含某词的文档"这件事重新设计了存储结构。理解到这一层,你就不会再问"为什么不直接给 content 加个索引"。
倒排表的前提是分词。中文没空格,ES 内置分词器对中文基本按字切,"人工智能"会碎成四个字,因此需要 IK。这也解释了项目里那个 Dockerfile 为什么要单独存在。
逐段读配置:哪些行真的在起作用
yaml
services:
es:
image: elasticsearch:8.17.0
container_name: es-dev
ports: ["9200:9200"]
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/es:/usr/share/elasticsearch/data
ports 是"宿主机:容器"。写成 9200:9200 你才能用 localhost:9200 访问;只写 9200 会随机分配宿主机端口,地址就不固定了。
discovery.type=single-node 是单机开发的必要项。不写它,ES 会尝试组集群并反复找不到节点,日志里全是警告。
ES_JAVA_OPTS 卡死 512m 是保命配置。ES 是 JVM 应用,容器不限制内存时默认堆会相当激进,笔记本上跑很容易整机卡顿。
volumes 里那串 ${DOCKER_VOLUME_DIRECTORY:-.} 是 shell 的默认值语法:变量有值用变量的,没有就取当前目录。数据落在宿主机,容器删了索引还在。
再看 Kibana 唯一重要的那行:
yaml
environment:
- ELASTICSEARCH_HOSTS=http://es:9200
es 是 compose 网络里的服务名,同时也是 DNS 记录。写成 localhost 时,Kibana 是在请求它自己的 9200 端口------那个端口什么都没监听,于是报 Unable to connect to Elasticsearch。这个错误看起来像 ES 没起来,实际上是你把地址指错了人。
至于 depends_on: es,它保证的是"先创建 es 容器,再创建 kibana 容器"。ES 从容器启动到能响应 HTTP 请求往往要几十秒,Kibana 完全可能先失败一次再重试。配置里的注释写"等待 ES 启动完成后再启动 Kibana",是意图 而不是实现 。要真的等到就绪,得给 es 加 healthcheck,再写 condition: service_healthy。
Dockerfile 从"存在"到"生效"只差一行
项目里的 Dockerfile 是标准的定制镜像写法:
dockerfile
FROM elasticsearch:8.17.0
RUN elasticsearch-plugin install --batch \
https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.17.0.zip
FROM 定底座,RUN 在构建期装插件,--batch 免去交互确认。逻辑没问题。
问题在 compose 那一侧:image: elasticsearch:8.17.0 的语义是"直接用官方镜像",compose 不会顺便去执行旁边的 Dockerfile。这也是很多人"文件都写了但还是没 IK"的真正原因------不是插件装失败,是构建根本没发生。
改成构建模式即可:
yaml
es:
build: ./elasticsearch
# image 行删除
启动时加 --build:
bash
docker compose up -d --build
不加这个参数,compose 会继续复用已有镜像,改了 Dockerfile 也看不出来。
顺带一提,文件顶部的 version: '3.8' 在 Compose V2 里已经废弃,只会在启动时输出一条 obsolete 警告,删掉即可,不影响功能。
一份可以照着打勾的自检清单
-
docker ps能看到es-dev与kibana-dev两个容器在运行 -
localhost:9200返回节点信息 JSON -
localhost:5601能打开 Kibana 页面 -
docker exec es-dev bin/elasticsearch-plugin list输出里有analysis-ik -
_analyze用ik_max_word与standard对比,"人工智能"的切分结果不同 - 改完 Dockerfile 后启动命令带了
--build - Kibana 的
ELASTICSEARCH_HOSTS用的是服务名而不是localhost
任何一项打不了勾,基本都能在上面的三条反直觉里找到原因。以上检查运行未验证,请以你机器上的实际输出为准。
面试官问的其实不是命令
如果面试被问到"ES 为什么快",背"倒排索引"四个字只能算及格线。往下还有一层:倒排表为什么能省时间?因为查询不再触碰原文,只在词表和文档 ID 列表上工作;词表有序可二分,ID 列表压缩后顺序读友好。再往下,就是分片与副本:分片解决单机容量上限,副本解决可用性与读吞吐。
而 depends_on 和 localhost 这两个点,考的是你有没有真的在容器里排查过问题。这两类错误在本地开发中出现频率极高,且报错信息都具有误导性。
最后留一个可迁移的判断:配置文件里注释写的是作者的意图,不是运行时的行为。下次读任何 compose 或 K8s 配置,先把注释遮住读一遍字段语义,再回头看注释对不对------你会发现不少"注释骗人"的地方,而那些地方,往往就是线上事故的入口。