使用Go语言、Docker和Elastic Stack(Elasticsearch Logstash Kibana)开始进行BI环境搭建的实践操作
本文是弹性栈圣诞日历的第12天篇章。
最近,我在实际工作中使用 Docker + Go语言 + Elasticsearch 来开发API。不仅感受到了 Elasticsearch 的便利性,还对其在构建BI环境时的顺畅度感到印象深刻。因此,本次将以Go语言开发为例,通过实操方式介绍BI环境的搭建。我们将学习如何使用Docker运行,配置Elasticsearch集群,以及利用Logstash建立BI环境和向Slack发送通知等内容。
我也会把存储库放在这里:https://github.com/po3rin/go-elastic-starter
这次要做的东西和Stack介绍
使用Go语言 + Docker + Elastic Stack搭建一个简单的BI环境。通过API生成的日志由Logstash收集,并进行通知到Slack以及保存到Elasticsearch中。将存入Elasticsearch的数据通过Kibana可视化展示。
弹性搜索
Elasticsearch 是由 Elastic 公司开发的开源分布式 RESTful 搜索/分析引擎,具有出色的搜索速度和分析灵活性。
Kibana 凯班娜
Kibana是Elastic公司为可视化开发的工具。它可以以多种形式绘制存在于Elasticsearch数据库中的数据。
日志收集
这也是 Elastic 公司提供的开源数据收集引擎。
它可以通过实时流水线处理将不同类型的数据转换为统一的格式,并发送到任意的传输目的地。
我经常看到它被用作日志收集工具。
使用Docker创建Elasticsearch + Kibana环境。
首先,让我们追求上述的形式。在这里,我们还可以建立Kibana。
在这个部分,文件结构将采用如下所示的形式。
go-elastic-starter
├── docker-compose.yml
├── elasticsearch
│ ├── Dockerfile
│ └── config
│ ├── elasticsearch.yml
│ └── log4j2.properties
└── kibana
└── Dockerfile
让我们立即准备好启动Elasticsearch和Kibana。
启动Elasticsearch集群
这次是来自Elasticsearch的。我们将尝试三种集群配置。在Elasticsearch中,Elasticsearch通过节点作为单位来运行在名为集群的实体中。这次我们会通过配置文件传递共同的设置,并通过docker-compose.yml文件的环境变量传递节点特定的设置。
首先,创建一个elasticsearch目录,并在其中创建一个Dockerfile。
FROM docker.elastic.co/elasticsearch/elasticsearch:6.5.1
# # kuromojiをインストールする場合
# RUN elasticsearch-plugin install analysis-kuromoji
创建一个config目录,并在其中创建一个用于Elasticsearch配置的elasticsearch.yml文件。
network.host: 0.0.0.0
# bootstrap.memory_lock: true
### Cluster settings
discovery.zen.minimum_master_nodes: 2
在elasticsearch.yml文件中,可以对集群和每个节点进行配置。由于本次将使用Docker来组建Elasticsearch集群,因此可以在docker-compose.yml文件中传递每个节点的单独配置。所以在这里,我们只进行共同的配置。
这次我们会建立三个被称为“可主节点”的节点。节点有各种类型,但简单来说,这些节点是可以成为Master候选的节点。
发现.zen.minimum_master_nodes 的值推荐设置为 (节点数 / 2) + 1。例如,在使用3个可选主节点组成集群的情况下,当集群内发生网络分裂时,分裂后的节点将重新组成不同的集群。这个值表示需要多少个节点才能构建一个集群。通过这种方式,分离的一个节点将不再晋升为主节点。下面的参考资料有更详细的解释。
然后进行关于日志输出的设置。Elasticseardh使用了一个名为log4j2.properties的文件。
status = error
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
我已经完成了日志的设置。然后,在docker-compose.yml文件中添加以下内容。
version: '3.6'
services:
# ...
elasticsearch:
build: ./elasticsearch
container_name: elasticsearch
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- node.name=es01
- cluster.name=go_elastic_starter_cluster
ports:
- '9200:9200'
- '9300:9300'
volumes:
# - esdata01:/usr/share/elasticsearch/data
- ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./elasticsearch/config/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties
elasticsearch2:
build: ./elasticsearch
container_name: elasticsearch2
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- node.name=es02
- cluster.name=go_elastic_starter_cluster
- "discovery.zen.ping.unicast.hosts=elasticsearch"
volumes:
# - esdata02:/usr/share/elasticsearch/data
- ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./elasticsearch/config/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties
elasticsearch3:
build: ./elasticsearch
container_name: elasticsearch3
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- node.name=es03
- cluster.name=go_elastic_starter_cluster
- "discovery.zen.ping.unicast.hosts=elasticsearch"
volumes:
# - esdata03:/usr/share/elasticsearch/data
- ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./elasticsearch/config/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties
每个节点的配置通过环境变量传递。指定了节点名称和集群名称。(虽然在这里省略了,但是也可以在这里设置JVM堆大小。如果使用文件传递,创建一个名为 jvm.options 的文件即可)
准备工作已经完成!让我们开始启动吧。
# Elasticsearchクラスタ立ち上げ
$ docker-compose up -d
# 立ち上げ確認
$ curl "localhost:9200/_cat/health?v"
请确认节点总数为3,这样就完成了一个包含3台节点的集群!
然后我们会以这个势头启动Kibana。先把容器关掉吧。
$ docker-compose down
启动 Kibana
让我们创建一个名为kibana的文件夹,并在其中创建一个Dockerfile。
FROM docker.elastic.co/kibana/kibana:6.5.1
# kibana にプラグインを入れる際にはここに記載
然后将其添加到 docker-compose.yml 文件中!
version: '3.6'
services:
# ...
kibana:
build: ./kibana
links:
- elasticsearch
environment:
- ELASTICSEARCH_URL=http://elasticsearch:9200
ports:
- 5601:5601
这次的设置可以保持默认值就可以了。现在启动它。
$ docker-compose up -d
你可以在 http://localhost:5601 上查看 Kibana 的页面。
通过这个操作,Elasticsearch + Kibana已经成功启动了!
启动 Logstash
创建一个logstash目录,并创建Dockerfile。这次我们也要添加插件进行Slack通知。在这个部分,可以形成以下的配置。
.
├── Makefile
├── README.md
├── docker-compose.yml
├── elasticsearch
│ ├── Dockerfile
│ ├── README.md
│ └── config
│ ├── elasticsearch.yml
│ └── log4j2.properties
├── kibana
│ └── Dockerfile
└── logstash
├── Dockerfile
└── logstash.conf
FROM docker.elastic.co/logstash/logstash:6.4.3
RUN logstash-plugin install logstash-output-slack
然后我们将创建一个重要的 logstash.conf 文件。将它放在刚刚创建 Dockerfile 的同一级目录下。
在这里我们将配置输出到 elasticsearch 和 slack。
input {
tcp {
port => 5959
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
}
slack {
url => ["{{ WEB_HOOK_URL }}"]
channel => ["{{ #CHANNEL_NAME }}"]
username => ["po3rin"]
icon_emoji => [":simple_smile:"]
format => ["%{message}"]
}
}
您可以通过 Slack API 注册应用程序来获取 {{ WEB_HOOK_URL }} 和 {{ #CHANNEL_NAME }}。您可以在左侧菜单的 Incoming Webhooks 中找到这两个选项。
然后,在docker-compose.yml文件中添加有关Logstash的描述。
logstash:
build: ./logstash
volumes:
- ./logstash:/logstash_dir
command: logstash -f /logstash_dir/logstash.conf
links:
- elasticsearch
ports:
- "5959:5959"
- "9600:9600"
$ docker-compose up -d
我们现在已经成功启动了Logstash,让我们来确认一下。
curl localhost:9600
{"host":"03ad2934dd30","version":"6.4.3","http_address":"0.0.0.0:9600","id":"ccf108c4-aaf5-40ae-ac37-41866641e3de","name":"03ad2934dd30","build_date":"2018-10-31T00:19:35Z","build_sha":"17e7a50dfb0beb05f5338ee5a0e8338e68eb130b","build_snapshot":false}
好的。我们也让容器在这里先停一会儿,为下一部分做准备。
$ docker-compose down
使用Go语言编写的API服务器将日志发送到Logstash。
我会把最后的形状带到这个部分。
在本节中最终的结构如下所示。
.
├── Makefile
├── README.md
├── api_server
│ ├── Dockerfile
│ ├── go.mod
│ ├── go.sum
│ ├── logger
│ │ └── logger.go
│ ├── main.go
│ └── wait-for-logstash.sh
├── docker-compose.yml
├── elasticsearch
│ ├── Dockerfile
│ ├── README.md
│ └── config
│ ├── elasticsearch.yml
│ └── log4j2.properties
├── kibana
│ └── Dockerfile
└── logstash
├── Dockerfile
└── logstash.conf
我们来配置Go语言的开发环境。请准备Go1.11或更高版本。然后创建一个api_server目录,创建go.mod文件,并在同级目录下创建main.go文件。暂时不设置日志记录。
package main // import "api_server"
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(hello))
http.ListenAndServe(":8080", mux)
}
func hello(w http.ResponseWriter, r *http.Request) {
msg := "Hello Elastic Stack"
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, msg)
}
很简单啊,在全力开发之前,我们先搭建开发环境,使用Docker + fresh进行热重载。以下进行解释,详细信息请查看过去的文章。
使用Go v1.11 + Docker + fresh,创建并享受一个愉快的Go语言开发环境。
我将创建一个Dockerfile。
FROM golang:1.11.1
WORKDIR /api_server
COPY . .
ENV GO111MODULE=on
RUN go get github.com/pilu/fresh
当你在本地更新文件后,就可以在Docker容器内实现热重载环境。我们在docker-compose.yml中声明了启动命令fresh。接下来,让我们在docker-compose.yml中添加相关信息。
# ...
version: '3.6'
services:
go_elastic_starter:
build: ./api_server
ports:
- '8080:8080'
volumes:
- ./api_server:/go/src/go_elastic_starter/api_server
command: fresh
立刻开始吧。
$ docker-compose up -d
这样容器中就有一个服务器了。
curl localhost:8080/
Hello Elastic Stack
现在开发环境已经准备妥当了!现在让我们来设置主要的日志记录器。在 main.go 同一层级下创建 logger/logger.go 文件。
package logger
import (
"github.com/bshuster-repo/logrus-logstash-hook"
"github.com/sirupsen/logrus"
)
// Log - log client.
var Log *logrus.Logger
func init() {
logger := logrus.New()
logstashHook, err := logrustash.NewHook("tcp", "logstash:5959", "from_logstash")
if err != nil {
panic(err)
}
logger.Hooks.Add(logstashHook)
Log = logger
}
// Debug - shorthand Log.Debug
func Debug(msg string) {
Log.Debug(msg)
}
// Info - shorthand Log.Info
func Info(msg string) {
Log.Info(msg)
}
// Warn - shorthand Log.Warn
func Warn(msg string) {
Log.Warn(msg)
}
// Error - shorthand Log.Error
func Error(msg string) {
Log.Error(msg)
}
我使用的日志记录器包是sirupsen/logrus。除了基本功能外,它还提供了用于记录日志时的各种钩子。详见:https://github.com/sirupsen/logrus
本次使用的Hook是bshuster-repo/logrus-logstash-hook。它可以将日志发送到Logstash。其他可用于logrus的Hook列表可以在以下链接中找到。
https://github.com/sirupsen/logrus/wiki/Hooks
那么让我们在 main.go 中添加日志记录,以告知API已收到通知。
package main // import "api_server"
import (
"fmt"
"net/http"
"api_server/logger"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(hello))
http.ListenAndServe(":8080", mux)
}
func hello(w http.ResponseWriter, r *http.Request) {
msg := "Hello Elastic Stack"
// logging 追加 !!!!
logger.Info(msg)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, msg)
}
Go语言的实现已经完成。但还有一些工作要做。事实上,在Logstash接受请求之前,用Go语言编写的API会尝试连接到Logstash并导致错误。为了避免这种情况,我们需要编写一个脚本来等待Logstash启动。让我们在api_server目录中添加脚本。
#!/bin/bash
## Or whatever command is used for checking logstash availability
until curl 'http://logstash:9600' > /dev/null; do
echo "Waiting for logtash..."
sleep 3;
done
# Start your server
fresh
这个脚本尝试连接到 Logstash,如果没有收到200响应,将等待3秒后再次进行连接测试。
然后将它传递到 docker-compose.yml 的命令中。
# ...
go_elastic_starter:
build: ./api_server
ports:
- '8080:8080'
volumes:
- ./api_server:/api_server
links:
- logstash
# コマンドを差し替え
command: bash wait-for-logstash.sh
确认动作
我们已经做好了所有的准备。现在,让我们启动吧。
$ docker-compose up -d
我现在尝试访问 API。
curl localhost:8080/
Hello Elastic Stack
Slack会接收到有关访问API的通知。
当然,Kibana 也可以将日志数据可视化。
现在使用Go语言创建API服务器的商业智能环境已经准备就绪!
推荐的 Elasticsearch 学习方法,为今后做准备。
这是我学习的方法,学习 Elasticsearch 时,Kibana 的 Dev Tools + 官方文档非常方便。在 Kibana 的左侧菜单中有 Dev Tools,你可以直接在其中按照文档中的形式输入查询并运行。
GET /_cat/health?v
当然也可以从文档中复制curl命令,在本地进行测试!
而且,尽管在这次没有介绍,但也可以直接从Go语言连接到Elasticsearch并保存数据。在Go语言中处理Elasticsearch时,以下的包非常有用。
https://github.com/olivere/elastic
总结
多亏了Docker,我能够迅速启动Elastic Stack,太棒了。