ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker部署Elasticsearch与Kibana:从环境准备到Compose编排实战

Docker部署Elasticsearch与Kibana:从环境准备到Compose编排实战

提到搜索引擎、日志分析,很多人第一反应就是Elasticsearch,但真正动手装的时候,不少人卡在了JDK版本、内存分配、配置文件路径这些环境问题上。我前前后后在裸机和虚拟机里手动部署过多次ES和Kibana,光环境变量和配置文件就折腾了一整天。后来切到Docker部署,一次性的启动时间从半天压缩到十分钟,换机器、做测试、升级版本都省心很多。这篇记录一下从零在Docker里跑起ES和Kibana的完整过程,包括环境准备、单节点部署、Kibana接入、Docker Compose编排以及常见问题排查。新手照着操作基本能一次跑通,已经摸过一遍的人也可以从参数调优和排错心得里找点有用的东西。

1. 部署之前的思路与方案选择

1.1 ES和Kibana这对组合是干什么的

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,最常被拿来存储日志、指标和业务检索数据,借助倒排索引和聚合能力做近实时查询。Kibana则是它的可视化前端,负责把数据变成图表,也提供Dev Tools这种直接写DSL查询的入口。很多人刚开始接触时会把ES当成普通数据库来用,这个思路不能算全错,但容易走弯路。ES擅长的是全文检索和聚合统计,不是事务性存储。比如做商品搜索、日志分析平台、接口调用链追踪面板,ES加Kibana是非常成熟的组合;如果想拿它记账、做严格的关联关系查询,那后面等着的就是各种别扭。

我手头有这样一个场景:需要把系统里几十个服务的运行日志汇总起来,提供关键字搜索和错误聚合统计。在Kibana里配好索引模式之后,定位线上问题从“登录服务器翻日志”变成了“打开面板输入关键字”,效率提升完全不在一个层面。这个场景非常适合用Docker部署方案来完成,因为它涉及两个组件、一条数据链路,正好能用容器编排把复杂度封住。

1.2 单节点起步,还是直接上集群

很多人一上来就想搞三节点集群,我建议冷静一下。Elasticsearch集群的部署复杂度会随节点数明显上升,角色划分、分片副本分配、脑裂问题、跨节点通信,每一样都是单独的坑。对个人学习和中小型项目来说,单节点Docker部署完全够用。先把搜索、聚合、可视化链路跑通,后面生产环境需要再加节点也不迟。

单节点模式对应的关键参数是discovery.type=single-node。这个参数容易忽略,但非常重要。没有它,ES会默认自己处于集群环境,启动后反复寻找其他节点,最后可能因为找不到master而一直处于异常状态甚至拒绝写入。设成single-node之后,它就把自己当作唯一节点,启动流程顺畅很多。我见过太多人栽在这个参数上,容器起来了但日志全是报错,实际上就是少了这一行配置。

1.3 为什么坚持走Docker路线

Elasticsearch手动安装在Linux上并不难,真正麻烦的是环境一致性。ES对JVM版本有要求,对内核参数有要求,对内存和文件描述符有要求,稍有不对就是各种报错。Docker把这些都封装进镜像,宿主机只需要满足很少几个条件,剩下的由容器运行时统一处理。更重要的是版本切换和回滚。手动部署时从8.10升到8.11,要下载新包、迁移数据目录、改配置,万一搞坏还得回滚。Docker这边就是拉新镜像、停旧容器、用同一个数据卷启动新容器,过程清晰得多。

再加上Docker Compose一写,整个环境的状态和配置都固化在代码里,换台机器也能一键拉起。团队协作时也不会出现“我这边能跑你那边就跑不起来”的尴尬。折腾过自建ES的人应该都有体会,这套组合拳打下来,运维负担降低得非常明显。

2. 环境准备与版本选型

2.1 Docker基础环境检查

开始之前先确认Docker是否可用。终端执行:

docker -v docker compose version

如果系统里还没有Docker,先用包管理器安装。安装完成后记得把当前用户加入docker组,否则每条命令都要sudo,后续操作会比较别扭:

sudo usermod -aG docker $USER newgrp docker

第二条命令让组权限立即生效,不执行的话需要重新登录终端才有效。这个细节经常被忽略,很多人后面所有命令都加sudo,结果文件归属和目录权限被搞乱。

接着检查一个Elasticsearch最重要的内核参数——vm.max_map_count。ES运行时会创建大量内存映射区域,这个值太小会直接导致启动失败。先看当前值:

sysctl vm.max_map_count

如果输出低于262144,执行:

sudo sysctl -w vm.max_map_count=262144

不过sysctl -w只是临时修改,重启后失效。要永久生效,把它写进/etc/sysctl.conf:

echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf

这一步如果不做,ES容器偶尔能启动,但一旦索引数量上来或者内存压力变大,就会莫名死掉,排查起来非常痛苦。

2.2 镜像版本与匹配原则

Elasticsearch和Kibana的镜像都建议从官方源拉取。这里有个非常重要的版本原则:ES和Kibana的主版本必须保持一致。ES用8.10.x,Kibana就要用对应的8.10.x。我见过ES用7.x、Kibana用8.x的例子,Kibana页面会一直报版本不兼容,怎么调都调不通。

如果你所在环境拉取镜像很慢,可以提前在Docker守护进程配置里设置好镜像加速地址,然后重启Docker再拉取。ES镜像通常在600MB左右,Kibana在1GB上下,耐心等一会儿是正常的。

另外建议明确指定次版本号,比如8.10.2,而不是直接用latest。latest虽然方便,但ES和Kibana之间的版本耦合太强,用latest很容易出现ES先升级、Kibana还没跟上之类的尴尬情况。固定版本之后,环境就是可复现的,出问题也能快速对照。

2.3 网络与存储规划

我建议单独创建一个Docker网络,让ES和Kibana通过容器名互相访问,而不是通过宿主机IP加映射端口。这样即使宿主机IP变化,容器之间的连接也不受影响:

docker network create es-net

存储方面,ES的数据目录一定要挂载到宿主机或数据卷上。ES的数据包括索引、分片、集群状态信息,一旦容器被删除,如果数据只存在容器可写层里,整个索引就全没了。最稳妥的方式是使用Docker数据卷:

docker volume create es-data

后面启动ES时,把这个卷挂载到容器内的/usr/share/elasticsearch/data目录。这样容器删了重建,数据依然还在。不要贪图方便直接裸跑容器,数据丢了再后悔就来不及了。

3. Elasticsearch容器部署实操

3.1 拉取镜像并启动容器

先拉取镜像,这里以8.10.2为例:

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.10.2

然后执行启动命令:

docker run -d \ --name elasticsearch \ --network es-net \ -p 9200:9200 \ -e "discovery.type=single-node" \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ -e "xpack.security.enabled=false" \ -v es-data:/usr/share/elasticsearch/data \ --ulimit nofile=65535:65535 \ elasticsearch:8.10.2

这里有几个参数值得展开说。

先说ES_JAVA_OPTS。ES是JVM应用,堆内存大小直接影响性能和宿主机内存占用。官方推荐把-Xms和-Xmx设为一样,避免运行中发生堆扩容造成不必要的停顿。512m适合学习和轻量使用,如果你的机器内存足够,可以适当调高,但别把一大半内存都塞给它,ES还需要大量内存做文件缓存。

再看discovery.type=single-node,前面说过,这是单机运行的开关,不加的话ES会尝试找其他节点,容易出问题。还有xpack.security.enabled=false,8.x版本默认开启安全认证,对生产环境是好事,但对本地开发和教学场景来说,默认安全模式会让Kibana连接变得麻烦。我在这套教学型部署里先关掉它,先把链路跑通,后面再单独说怎么开启。

3.2 验证ES是否成功启动

容器启动后先看日志:

docker logs -f elasticsearch

正常日志里会有一行类似started的信息。ES启动比较慢,通常在十几秒到几十秒之间。不要急着判断失败,看到一堆初始化输出属于正常现象。快速验证可以用:

curl http://localhost:9200

如果输出JSON里有cluster_name和tagline之类的信息,说明ES已经在正常对外提供接口了。注意,ES默认cluster_name是docker-cluster,后面可以通过配置文件修改。

如果curl返回connection refused,先别怀疑镜像有问题,90%的情况是容器还在启动中,多等几秒再来一次。如果等了半分钟还是拒绝,再回头看日志,有没有堆栈报错。常见问题我在第6章单独整理。

3.3 数据卷与索引生命周期

容器启动后,所有索引数据都落在挂载的数据卷es-data里。可以用docker inspect确认挂载情况:

docker inspect elasticsearch | grep -A 5 Mounts

确认Mounts里的Source和Destination都正确。这里有个容易踩的坑:如果一开始没挂载卷就跑起来了,后来想补挂载,直接再启动会报端口冲突,需要先停掉并删除旧容器,再重新创建。操作前务必确认数据是否需要保留,一旦容器删了又没有卷,数据就真的找不回来了。

4. Kibana容器部署实操

4.1 启动Kibana容器

Kibana负责提供可视化界面和查询工具,启动前必须确认ES已经处于可用状态。执行:

docker pull docker.elastic.co/kibana/kibana:8.10.2

版本保持和ES一致,这是老生常谈但真的很重要。启动命令:

docker run -d \ --name kibana \ --network es-net \ -p 5601:5601 \ -e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \ -e "SERVER_NAME=kibana.local" \ kibana:8.10.2

注意--network es-net这个参数,它让Kibana和ES处于同一个Docker网络,Kibana内部才能通过elasticsearch这个容器名访问ES。如果用localhost或127.0.0.1,Kibana会尝试在自己的容器内部找ES,结果必然失败。这个网络模型是Docker下多容器协作的基础,理解后后面编排其他组件也顺理成章。

SERVER_NAME在本地访问时不填问题不大,但填上可以避免个别浏览器下的Host header校验问题。如果Kibana配置了域名访问,这里应该填对应的域名。

4.2 Kibana连接ES的多种姿势

上面这个命令用的是最简单的方式:ES不开启安全认证,Kibana直接通过http访问。这是开发环境最省事的方式。

如果ES开启了安全认证,Kibana必须提供账号信息。启动命令改成:

docker run -d \ --name kibana \ --network es-net \ -p 5601:5601 \ -e "ELASTICSEARCH_HOSTS=https://elasticsearch:9200" \ -e "ELASTICSEARCH_USERNAME=elastic" \ -e "ELASTICSEARCH_PASSWORD=你的密码" \ kibana:8.10.2

这里注意两点。第一,协议要改成https,因为ES开安全认证后默认走TLS。第二,密码通过环境变量传入,别写进容器内配置文件然后忘记清理。Kibana也支持挂载kibana.yml的方式,但环境变量方案在Docker里更直观,也更适合做配置管理。

4.3 浏览器验证与首次初始化

Kibana启动完成后,浏览器访问宿主机IP的5601端口。如果Kibana一直转圈或显示“Kibana server is not ready yet”,先别急着反复刷新,回终端看Kibana日志:

docker logs -f kibana

这个提示的本质是Kibana还没准备好,常见原因有ES还没完全健康、ES地址配置不对、安全认证信息不匹配。排查顺序建议是:先看ES是否正常,再确认ELASTICSEARCH_HOSTS在Kibana容器内是否可达,最后检查账号密码。

首次进入Kibana后,首页不一定立刻有数据。需要到Management里创建Index Pattern,告诉Kibana你的索引名称和日期字段,它才知道怎么展示数据。这个步骤很多人会漏,其实只要ES里有数据,建好索引模式后就能在Discover里看到日志流了。

5. Docker Compose一键编排方案

5.1 Compose文件的完整写法

两个docker run命令能跑通,但每次重启都要手动输入一长串参数,确实容易出错。实际项目里我更推荐用Docker Compose统一管理。先建一个目录:

mkdir ~/es-kibana && cd ~/es-kibana

创建docker-compose.yml:

services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2 container_name: elasticsearch environment: - discovery.type=single-node - ES_JAVA_OPTS=-Xms512m -Xmx512m - xpack.security.enabled=false ports: - "9200:9200" volumes: - es-data:/usr/share/elasticsearch/data networks: - es-net ulimits: nofile: soft: 65535 hard: 65535 kibana: image: docker.elastic.co/kibana/kibana:8.10.2 container_name: kibana environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 ports: - "5601:5601" networks: - es-net depends_on: - elasticsearch volumes: es-data: driver: local networks: es-net: driver: bridge

这个文件把前面所有docker run参数都翻译成了声明式配置,一眼能看清ES和Kibana的关系。depends_on表示Kibana会等ES先启动,但它只保证进程启动顺序,不保证ES内部已经完全就绪。所以启动后如果Kibana短暂显示未就绪,是正常现象,多等一会儿就好。

5.2 用Compose管理整个环境

启动:

docker compose up -d

查看状态:

docker compose ps

查看日志:

docker compose logs -f

停止但不删除容器:

docker compose stop

停止并清理容器,注意不会影响数据卷:

docker compose down

整个环境的生命周期都能用这几条命令管理,不用再逐一记住容器名。我习惯把Compose文件纳入版本管理,改了什么参数一目了然。同事交接时也不用口口相传,直接看文件就知道当前部署了什么配置。

5.3 数据备份、迁移与版本升级

Compose方案下数据落在数据卷es-data里,备份迁移有两条路线。

一条是直接操作数据卷。用tar打包卷目录,或者通过临时容器把数据复制出来。另一条更推荐的方式是用ES自身的快照接口,先把数据快照到某个目录,再在新环境里恢复。注意,ES快照恢复要求版本不能差太多,小版本升级一般没问题,跨大版本不建议直接恢复。

如果只是测试环境,最省心的方式是保留Compose文件,把数据卷一起迁移到新机器。ES的节点名、集群名这些都可以在环境变量里重配,关键是索引数据本身别丢。升级版本时,我会先把容器停掉,备份数据卷,再改镜像标签重新up。这套流程走顺之后,版本升级一两分钟就能完成。

6. 常见问题排查与避坑记录

6.1 容器反复重启,退出码137

这是遇到最多的一个问题。退出码137意味着容器被系统强杀,最常见原因是内存不足。ES的JVM堆加上文件缓存开销不小,宿主机可用内存紧张时,Docker会在资源耗尽时杀掉容器。

解决方向有两个。一是调低ES_JAVA_OPTS,比如改成-Xms256m -Xmx256m,虽然性能会降,但至少能跑起来。二是给宿主机腾内存或加内存。先看可用内存:

free -h

如果在Compose文件里还配了mem_limit,容器的可用上限会更小,需要整体权衡。我自己的经验是,测试环境最少给ES留1GB可用内存,低于这个值怎么调都会很勉强。

6.2 vm.max_map_count过低导致启动失败

ES启动日志出现max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]的报错,就是这个问题。前面给出了临时和永久两种修改方法,这里再补充一句:有些云主机可能锁定内核参数,改完需要重启Docker或重启机器才能生效。如果改了不生效,先确认是否真的写入了/etc/sysctl.conf,再执行sysctl -p让配置立即加载。

6.3 Kibana一直提示Server not ready

Kibana界面提示这个信息时,排查顺序别乱。先确认ES本身能访问,再确认Kibana容器和ES容器在同一网络,然后检查ELASTICSEARCH_HOSTS地址是否正确。我遇到过一次很隐蔽的情况:ES没有开启安全认证,但Kibana环境变量里残留了旧密码,导致每次连接都被ES拒绝。

另外,如果改了ES的端口映射,比如9200映射成了宿主机9201,Kibana里的ELASTICSEARCH_HOSTS必须用容器内部的9200端口,而不是宿主机的9201。这个内外端口不一致的问题在Docker环境里很容易绕晕人。先用docker exec -it kibana curl http://elasticsearch:9200在容器内部测一下连通性,比反复猜配置快得多。

6.4 宿主机挂载目录的权限问题

如果不用数据卷而用宿主机目录挂载,ES容器内的进程可能没有写权限,导致启动失败。ES官方镜像里运行用户是非root用户,UID是1000。所以把宿主机目录属主改成1000,或者给足写权限:

sudo chown -R 1000:1000 /your/es/data

这个细节在手动部署时几乎不会碰到,但凡是Docker挂载宿主机目录的,非常容易在这里栽跟头。Kibana同样有这个问题,如果挂载了宿主机目录作为配置目录,也需要检查属主。

6.5 日志时间与Kibana时间相差八小时

ES和Kibana容器默认时区是UTC,Kibana界面上看到的时间可能和本地差8个小时,看业务日志时特别难受。解决方法是在启动命令或Compose环境变量里设置TZ:

-e "TZ=Asia/Shanghai"

Kibana里的时间字段显示还受浏览器时区影响,页面右上角的时间模式也可以手动调整。虽然不是致命问题,但排查线上问题时时间差一两个小时,非常容易误判时序。

6.6 数据卷误删导致索引消失

这个问题的操作层面很简单,但很多人就是会踩。docker compose down不带-v参数不会删除数据卷,但docker compose down -v会把数据卷一起删掉。如果在生产环境误执行带-v的命令,数据直接蒸发。我的习惯是从不在生产环境使用-v参数,要清理之前先手动备份。

另外,普通docker rm删除容器时不会主动删卷,但带-v的docker rm -v会删除未命名的卷。ES的索引数据如果曾挂在匿名卷里,一旦被删,同样是不可逆的。

7. 安全认证的扩展配置

7.1 什么时候需要开启安全认证

如果这套环境只是本地学习,前面的xpack.security.enabled=false方案最省事。如果环境要放到内网供多人使用,或者有暴露到公网的可能,我强烈建议开启安全认证。ES的8.x版本默认安全模式,登录Kibana需要账号密码,ES自身也默认启用TLS传输加密,这能挡住很大一部分裸奔风险。

7.2 安全模式下账号密码怎么配置

在安全模式下启动ES,日志会自动生成超级用户elastic的初始密码。如果使用Compose,也可以在环境变量里显式设置:

- xpack.security.enabled=true - ELASTIC_PASSWORD=自己设置的强密码

Kibana侧再用对应账号密码去连接。对于已有数据的生产环境,建议在容器内执行密码重置命令重新设置各内置账号密码,而不是直接改动环境变量重启,因为环境变量的初始化逻辑只在第一次启动时生效。

7.3 安全模式不等于可以无视网络隔离

即使开了安全认证,也不代表ES可以无防护地暴露到公网。ES的9200端口直接暴露在公网上,数据泄露风险依然很大。我的建议是:本地学习关掉安全模式没问题;长期运行的环境,无论是否开启认证,都应该放在内网,并且通过防火墙把9200端口限制在可信网段内。

最后说几句实际的

如果总结成一句话,我用下来最深的体感是:Docker部署ES和Kibana的最大优势不在于省掉安装步骤,而在于整个环境能被描述成代码,出了问题推倒重来也比手动环境便宜得多。我第一次搭这套环境的时候,也在vm.max_map_count和内存问题上反复折腾,后来把排查经验整理成了一份自查清单,再往后搭环境基本就是十分钟的事。

最后分享一个很多人不一定知道的小操作:在Kibana的Dev Tools里可以直接写DSL查询验证数据,比如GET /_cat/indices?v就能快速看到所有索引的健康状态,不需要额外装任何客户端工具。组合键和页面布局熟悉之后,这套环境完全可以当作日常数据检索和日志分析的工作台来用。你可以先按文中步骤跑通单节点版本,再根据实际需求逐步调整内存、目录和安全策略。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进