Golang 实战 ELK 日志系统全流程教程(三):Elasticsearch 和 Kibana 环境搭建

前两篇把 Go 侧日志怎么打、日志字段大概怎么设计捋了一遍以后,接下来就绕不开 ELK 里最先要跑起来的两个东西:

Elasticsearch:存日志、查日志
Kibana:看日志、筛日志、做一点简单分析

我一开始搭这个环境时犯过一个挺典型的错误:上来就想把 Elasticsearch、Kibana、Logstash、Filebeat 全部塞进一个 compose 文件里。

看起来很完整,实际一旦启动失败,排查会很痛苦。到底是 ES 没起来?还是 Kibana 连不上?还是 Logstash pipeline 写错?还是 Beat 采集路径不对?日志一堆,问题反而看不清。

所以这篇先只做一件事:把 Elasticsearch 和 Kibana 稳稳跑起来,并且手动写入一条 Go 应用日志,确认 Kibana 能查到。

ELK 日志系统最小闭环图

先说版本:不要用 latest

Elastic 这套东西最怕“版本随缘”。

我写这篇时是 2026-05-28,Elastic 官方 Docker 文档当前示例已经是 9.4.1。但真实项目里我不建议写 latest,原因很简单:今天能跑,不代表下周同一份 compose 还能用同样的方式跑。

这里统一固定版本:

Elasticsearch:9.4.1
Kibana:9.4.1

Kibana 和 Elasticsearch 尽量保持同一个大版本、同一个小版本。它们本来就是一套栈里的组件,版本错开以后,经常不是直接报一个很好懂的错误,而是在启动、认证、API 兼容上给你一点小惊喜。

这篇是本地开发环境,所以我会先关闭安全认证:

xpack.security.enabled=false

这不是生产建议。生产环境要启用认证、TLS、账号权限、审计等配置。但学习 Go 日志链路时,第一阶段先把“日志能写进去、Kibana 能查出来”跑通,认知负担会小很多。

目录准备

我习惯把 ELK 环境单独放一个目录:

mkdir go-elk-demo
cd go-elk-demo

目录最后大概长这样:

go-elk-demo
├── .env
└── docker-compose.yml

写 .env

.env 里只放版本号:

STACK_VERSION=9.4.1

这样后面升级版本时,不用在 compose 文件里到处改。

写 docker-compose.yml

下面这个 compose 是我本地学习日志系统时比较常用的最小版本:

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    container_name: go-elk-elasticsearch
    environment:
      - node.name=es01
      - cluster.name=go-elk-demo
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:9200 >/dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12

  kibana:
    image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
    container_name: go-elk-kibana
    environment:
      - SERVER_NAME=kibana
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      elasticsearch:
        condition: service_healthy

volumes:
  es-data:

几个配置我单独说一下,不然很容易照抄完了不知道为什么这么写。

discovery.type=single-node 是告诉 Elasticsearch:我现在就是单节点开发环境,不要再等别的节点加入集群。

ES_JAVA_OPTS=-Xms1g -Xmx1g 是限制 JVM 堆内存。ES 很吃内存,本地机器如果还开着 IDE、浏览器、数据库,别一上来就给太大。

xpack.security.enabled=false 是为了本地教程先绕过账号密码和证书。等后面日志链路通了,再单独补安全配置更清楚。

ELASTICSEARCH_HOSTS=http://elasticsearch:9200 这里的 elasticsearch 不是本机域名,而是 Docker Compose 内部服务名。Kibana 在容器网络里访问 ES,要走服务名,不是 localhost

这个地方我以前就写错过。Kibana 容器里的 localhost 指的是 Kibana 自己,不是宿主机,也不是 Elasticsearch 容器。

Docker Compose 网络访问示意图

启动环境

执行:

docker compose up -d

看容器状态:

docker compose ps

正常情况下会看到两个容器都在运行:

go-elk-elasticsearch   running
go-elk-kibana          running

docker compose ps 运行结果截图

如果 Kibana 还在启动,不要急。Kibana 第一次起来会比较慢,尤其是机器内存不太宽裕的时候。

可以看日志:

docker logs -f go-elk-elasticsearch
docker logs -f go-elk-kibana

验证 Elasticsearch

访问:

curl http://localhost:9200

Windows PowerShell 里建议写成:

curl.exe http://localhost:9200

因为 PowerShell 里的 curl 很多时候是 Invoke-WebRequest 的别名,输出格式和 Linux/macOS 下不一样。

正常会返回类似这样的 JSON:

{
  "name": "es01",
  "cluster_name": "go-elk-demo",
  "version": {
    "number": "9.4.1"
  },
  "tagline": "You Know, for Search"
}

这一步很关键。不要 Elasticsearch 还没验证,就直接去开 Kibana。否则 Kibana 页面打不开时,你不知道是 Kibana 自己的问题,还是后面的 ES 根本没活。

curl http://localhost:9200 返回结果截图

验证 Kibana

浏览器打开:

http://localhost:5601

因为我们本地关闭了安全认证,所以这里不需要输入 elastic 用户密码。

如果页面一直转圈,先看 Kibana 日志:

docker logs -f go-elk-kibana

我比较关注这几类信息:

Kibana is now available
Unable to retrieve version information from Elasticsearch nodes
connect ECONNREFUSED

第一种说明 Kibana 基本好了。

后两种大概率是 Kibana 连不上 Elasticsearch。优先检查 ELASTICSEARCH_HOSTS,再检查 ES 容器健康状态。

手动写入一条 Go 应用日志

环境起来以后,不要急着接 Go 代码。先手动写一条日志进去。

curl -X POST "http://localhost:9200/go-app-log-000001/_doc" \
  -H "Content-Type: application/json" \
  -d '{
    "@timestamp": "2026-05-28T10:30:00+08:00",
    "service_name": "order-api",
    "env": "dev",
    "level": "INFO",
    "trace_id": "trace-20260528-0001",
    "span_id": "span-001",
    "message": "create order success",
    "method": "POST",
    "path": "/api/orders",
    "status": 200,
    "cost_ms": 37,
    "user_id": 10001
  }'

PowerShell 可以这样写,少一点转义折磨:

$body = @{
  "@timestamp" = "2026-05-28T10:30:00+08:00"
  service_name = "order-api"
  env = "dev"
  level = "INFO"
  trace_id = "trace-20260528-0001"
  span_id = "span-001"
  message = "create order success"
  method = "POST"
  path = "/api/orders"
  status = 200
  cost_ms = 37
  user_id = 10001
} | ConvertTo-Json

Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:9200/go-app-log-000001/_doc" `
  -ContentType "application/json" `
  -Body $body

返回里看到 result: created,说明写入成功。

再查一下:

curl "http://localhost:9200/go-app-log-000001/_search?pretty"

如果能看到刚才那条日志,Elasticsearch 这一侧就没问题。

Elasticsearch 查询结果截图

在 Kibana 里创建 Data View

接下来去 Kibana 看这条日志。

打开 Kibana 后,进入:

Stack Management -> Data Views -> Create data view

填:

Name: go-app-log
Index pattern: go-app-log-*
Timestamp field: @timestamp

保存后,进入:

Discover

选择 go-app-log 这个 Data View,把时间范围调大一点,比如 Last 7 days 或者手动覆盖到日志时间。

Kibana 创建 Data View 成功截图

这里有个小坑:如果你插入的 @timestamp 是一个很早或者很晚的时间,而 Kibana 默认只看最近 15 分钟,你会以为日志没写进去。

我第一次用 Kibana 查日志时就被这个时间范围坑过。ES 里 _search 明明查得到,Kibana 里就是空。后来才发现不是索引问题,也不是 Data View 问题,就是右上角时间范围太窄。

Kibana Discover 页面截图

先别急着上 Logstash

到这里,我们已经完成了一个最小闭环:

Elasticsearch 已启动
Kibana 已启动
能手动写入日志
能在 Kibana 查到日志

这个闭环看起来简单,但它很重要。

因为后面接 Go 服务、Filebeat、Logstash 时,只要日志没出现,就可以分段排查:

Go 程序有没有输出日志文件?
Filebeat 有没有采集到?
Logstash 有没有收到?
Logstash 有没有写入 Elasticsearch?
Elasticsearch 里有没有索引?
Kibana 的 Data View 和时间范围对不对?

如果一上来就把所有组件混在一起,排查路径会变成一锅粥。

本地常见坑

1. Elasticsearch 容器启动后立刻退出

先看日志:

docker logs go-elk-elasticsearch

Linux 机器上比较常见的是 vm.max_map_count 太小。可以临时设置:

sudo sysctl -w vm.max_map_count=262144

如果是 Docker Desktop,通常要看 Docker Desktop 给 Linux VM 分配的资源。ES 内存不够时,表现也可能是启动慢、卡住、容器被杀。

2. Kibana 报连不上 Elasticsearch

重点看这个配置:

ELASTICSEARCH_HOSTS=http://elasticsearch:9200

在 compose 网络里,Kibana 访问 ES 用服务名 elasticsearch

不要写:

ELASTICSEARCH_HOSTS=http://localhost:9200

除非你非常清楚自己在做什么。容器里的 localhost 不是宿主机的 localhost

3. Kibana Discover 里看不到日志

按这个顺序查:

curl "http://localhost:9200/_cat/indices?v"
curl "http://localhost:9200/go-app-log-000001/_search?pretty"

如果 ES 能查到,Kibana 看不到,优先检查:

Data View 是否匹配 go-app-log-*
时间字段是不是 @timestamp
右上角时间范围是否覆盖日志时间

很多时候问题不在写入,而在 Kibana 默认时间范围。

4. 端口被占用

92005601 都是常见端口。

如果启动时报端口占用,可以先看本机端口:

netstat -ano | findstr :9200
netstat -ano | findstr :5601

或者直接改 compose 里的宿主机端口:

ports:
  - "19200:9200"

这样宿主机访问就是:

http://localhost:19200

容器内部还是 9200,不要把 Kibana 的 ELASTICSEARCH_HOSTS 也跟着改乱。

停止和清理

停止:

docker compose down

如果想把数据卷也删掉,重新来一遍:

docker compose down -v

注意,-v 会删除 Elasticsearch 里的数据。学习环境无所谓,项目环境别手滑。

这一篇先到这里

这一篇没有接 Go 代码,也没有上 Logstash。

我更想先把前面弄稳:Elasticsearch 能存,Kibana 能看,索引和时间字段能对上。后面真正接 Go 日志时,我们就不用再怀疑底层环境是不是坏的,可以把精力放到日志格式、trace_id、采集链路和字段映射上。

下一篇就可以开始做 Go 应用日志输出,然后考虑到底是:

Go -> 日志文件 -> Filebeat -> Elasticsearch

还是:

Go -> 日志文件 -> Filebeat -> Logstash -> Elasticsearch

我个人更倾向先从 Filebeat 直写 ES 开始,链路短,排错快。等字段清洗、脱敏、复杂路由需求出现,再把 Logstash 加进来。

参考资料

更多推荐