使用Go语言、Docker和Elastic Stack(Elasticsearch Logstash Kibana)开始进行BI环境搭建的实践操作

cover.png

本文是弹性栈圣诞日历的第12天篇章。

最近,我在实际工作中使用 Docker + Go语言 + Elasticsearch 来开发API。不仅感受到了 Elasticsearch 的便利性,还对其在构建BI环境时的顺畅度感到印象深刻。因此,本次将以Go语言开发为例,通过实操方式介绍BI环境的搭建。我们将学习如何使用Docker运行,配置Elasticsearch集群,以及利用Logstash建立BI环境和向Slack发送通知等内容。

我也会把存储库放在这里:https://github.com/po3rin/go-elastic-starter

这次要做的东西和Stack介绍

arche.png

使用Go语言 + Docker + Elastic Stack搭建一个简单的BI环境。通过API生成的日志由Logstash收集,并进行通知到Slack以及保存到Elasticsearch中。将存入Elasticsearch的数据通过Kibana可视化展示。

弹性搜索

undefined

Elasticsearch 是由 Elastic 公司开发的开源分布式 RESTful 搜索/分析引擎,具有出色的搜索速度和分析灵活性。

Kibana 凯班娜

undefined

Kibana是Elastic公司为可视化开发的工具。它可以以多种形式绘制存在于Elasticsearch数据库中的数据。

日志收集

undefined

这也是 Elastic 公司提供的开源数据收集引擎。
它可以通过实时流水线处理将不同类型的数据转换为统一的格式,并发送到任意的传输目的地。
我经常看到它被用作日志收集工具。

使用Docker创建Elasticsearch + Kibana环境。

arche2.png

首先,让我们追求上述的形式。在这里,我们还可以建立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 的页面。

kibanaUI.png

通过这个操作,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。

我会把最后的形状带到这个部分。

arche.png

在本节中最终的结构如下所示。

.
├── 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的通知。

slack.png

当然,Kibana 也可以将日志数据可视化。

kibana_sample.png

现在使用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,太棒了。

广告
将在 10 秒后关闭
bannerAds