電気ひつじ牧場

技術メモと日常のあれこれ

vectorでelasticsearchにEchoのログを収集してみる

この記事について

golangのechoが吐き出すログをvectorで収集し、opendistro版 Elasticsearchに収集、可視化する方法についてメモ

vectorについて

vector.dev

Rust製のデータ収集、変換ツールです。この分野で有名なものにtd-agent(fluentd)がありますが、vectorは高パフォーマンス、軽量を売りにしています。

f:id:cha-shu00:20210117184632p:plain
https://vector.dev/docs/about/what-is-vector/ より引用

opendistro Elasticsearchについて

opendistro.github.io

OSSのElasticsearchにいろいろなプラグインを同包したものです。elastic版Elasticsearchとは異なり、elastic社が有料プランで提供しているアラートなどの機能を無料で使うことができます。

elasticsearch + kibanaの構築

docker-composeを利用して構築します。無駄にクラスタを組んでますが1ノードでも問題なく動きます。

docker-compose.yml

version: "3"
services:
  server:
    build: ./server
    ports:
      - 8888:8888
  odfe-node1:
    image: amazon/opendistro-for-elasticsearch:1.12.0
    container_name: odfe-node1
    environment:
      - cluster.name=odfe-cluster
      - node.name=odfe-node1
      - discovery.seed_hosts=odfe-node1,odfe-node2
      - cluster.initial_master_nodes=odfe-node1,odfe-node2
      - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
      - opendistro_security.disabled=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
        hard: 65536
    volumes:
      - odfe-data1:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
      - 9600:9600 # required for Performance Analyzer
    networks:
      - odfe-net

  odfe-node2:
    image: amazon/opendistro-for-elasticsearch:1.12.0
    container_name: odfe-node2
    environment:
      - cluster.name=odfe-cluster
      - node.name=odfe-node2
      - discovery.seed_hosts=odfe-node1,odfe-node2
      - cluster.initial_master_nodes=odfe-node1,odfe-node2
      - bootstrap.memory_lock=true
      - opendistro_security.disabled=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - odfe-data2:/usr/share/elasticsearch/data
    networks:
      - odfe-net

  kibana:
    build: ./kibana
    container_name: odfe-kibana
    ports:
      - 5601:5601
    environment:
      ELASTICSEARCH_URL: http://odfe-node1:9200
      ELASTICSEARCH_HOSTS: http://odfe-node1:9200
    networks:
      - odfe-net

volumes:
  odfe-data1:
  odfe-data2:

networks:
  odfe-net:

elasticsearchのセキュリティ機能をオフにしたので、kibanaのセキュリティプラグインも削除します。

kibana/Dockerfile

FROM amazon/opendistro-for-elasticsearch-kibana:1.12.0
RUN /usr/share/kibana/bin/kibana-plugin remove opendistroSecurityKibana
COPY --chown=kibana:kibana kibana.yml /usr/share/kibana/config/

kibana/kibama.yml

server.name: kibana
server.host: "0"

./serverにはechoアプリケーションが入ります。開発環境なのでfreshでオートビルドされるようにしてあります。

./server/Dockerfile

FROM golang:1.15.6-alpine3.12
WORKDIR /go/src/app
RUN apk add --no-cache \
    alpine-sdk \
    git \
    && go get github.com/pilu/fresh
CMD [ "fresh" ]

ビルドしていい感じにkibanaにログインできたらOK

vectorの作成

こちらもDockerで立てます。ネットワークはアプリケーションがあるdefaultとelasticsearchがあるodfe-netの両方に属している必要があります。

docker-compose.yml

  vector-server:
    image: timberio/vector:0.11.X-alpine
    volumes:
      - ./vector/vector.toml:/etc/vector/vector.toml:ro
    depends_on:
      - odfe-node1
    networks:
      - default
      - odfe-net

vectorでは次の3つを指定します。

  • sources: データの発生元
  • transforms: sourcesで受け取ったデータの変換規則(オプション)
  • sinks: 最終的なデータの受け渡し先

sourcesではファイルやOSのメトリックの他、サーバを立ててそこに投げられるデータを扱うことも可能です。今回はsocket sourceを利用してechoアプリケーションのログをUDPで受けるようにします。

https://vector.dev/docs/reference/sources/socket/#outputにあるように、socket sourceで受けたデータはmessageフィールドにコンテンツが格納されるので、transformsでそれを抽出し、elasticsearch sinkに渡します。

./vector/vector.toml

[sources.origin]
  type = "socket" # required
  address = "0.0.0.0:50000" # required, required when mode = `tcp` or `udp`
  mode = "udp" # required

[transforms.extract_message]
  type = "json_parser" # required
  inputs = ["origin"] # required
  field = "message" # optional, default

[sinks.elastic]
  type = "elasticsearch" # required
  inputs = ["extract_message"] # required
  endpoint = "http://odfe-node1:9200" # required
  index = "echo-log-%F" # optional, default

アプリケーションからのログ送信

echoにはロギング用のミドルウェアが備わっているのでそれを使い、UDPのエンドポイントとstdoutに同時に出力するようにしています。 $VECTOR_SERVERには先ほどvector.tomlsocket sourceで指定したアドレスがセットされるようにする必要があります(省略)。

main.go

e := echo.New()
api := e.Group("/api")

var output io.Writer = os.Stdout
raddr, err := net.ResolveUDPAddr("udp", os.Getenv("VECTOR_SERVER"))
if err == nil {
    conn, err := net.DialUDP("udp", nil, raddr)
    if err == nil {
        output = io.MultiWriter(output, conn)
    }
}
api.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
    Output: output,
}))

ここまでできれば、アプリケーションのログがvectorを通してelasticsearchに蓄積されるようになります。

$ curl localhost:9200/_cat/indices?v
health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   echo-log-2021-01-17             i8CpjKbuSAGEX3J5Ez6dmg   1   1          5            0     27.5kb         13.7kb

kibanaでの確認と可視化

ログインし、右のメニューからStack Management > Index Patterns > Newを選択しecho-log-*でインデックスパターンを作成します。これでVisualizationが利用できます。

Visualizeタブからグラフを作成しました。BucketsがX軸、MetricsがY軸になります。X軸方向にはタイムスタンプによる時間、Y軸にはログのカウントを取っています。ログのフィールドからHTTPのステータスコードやレスポンスタイムを可視化することも可能です。

f:id:cha-shu00:20210117194501p:plain

終わりに

golang echo + vector + elasticsearch + kibanaによるログ可視化について記述しました。ログが収集できたので、この値を元にアラートを飛ばすことも可能です(これが無料でできるのがopendistro版ESの良いところ)。次回はアラートについて書きたいと思います。