bon now

ありのままの現実を書き殴る吐き溜め。底辺SEの備忘録。
Written by bon who just a foolish IT Engineer.

Kubernetes上でシンプルなElasticsearch環境が使えるパッケージを作った

Created Date: 2019/10/07 19:47
Updated Date: 2023/11/05 03:00

作りました。もともとは仕事用だったんですが僕にスキルがなさすぎてElastic公式のKubernetes環境下のElasticsearchのデプロイがうまく機能しなかったため、 同じように困ってる人のために公開することにしました。 多分今年中には公式のKubernetesのYAMLたちが正しく使えるようになると思うので、僕としてはつなぎの意味もあります。 まぁそんなことよりもこういう使い方があるんだよってことを知ってもらえたらなと思いますので、拙いREADMEよりも少し詳しく説明したいと思います。

simple-k8s-elasticsearch

※このリポジトリが北極送りになったようです

目的

KubernetesでElasticserchを使いたかっただけです。 まずVMでElasticsearchに関わるサービス群を1つ1つ作るのが面倒でした。僕のお仕事的にはKubernetes基盤のCI環境ができればよかったので、 可観測性を確保するというミッションは正直ついででした。しかしながらシステムは1人が管理するものではないという当たり前の原則から、 エンジニア全員が自分のシステムの品質やエラーに対して向き合えることを実現するためにログ集約基盤が必要でした。

僕の監修したCI環境ではサービスに対して git push されたブランチごとに、1つのシステムが稼働するようになっています。ここでいうシステムは、複数のサービスがそれぞれ相互に関わり合って動いているモノを指します。 僕の所属する企業のサービスは若干特殊なサービス連携を要するため、特定のサービス単体の修正とデプロイでは動作確認できない部分があり、 エンジニアは自分のローカル環境やみんなで共用で使ってる検証用サーバーに複数のサービスを起動/デプロイしてシステムとして動作させるっていう面倒なことをやらなければなりませんでした。
ついでにいうとスマホとの連携も必要な改修も発生した場合、ローカル環境での検証ではどうあがいても検証できないという業界固有の難しさもありました。 (厳密に言うとできないことはないのですが、まぁまぁ面倒くさいうえにいらぬスキルを要求される)

これらの問題を解決すべく、上述の通りCIによって自動的に各エンジニアが自分たちの持つブランチ単位でシステムができあがるので、 他人のソースコードの汚染もないきれいで独立性のあるなかで自動テストや検証ができるわけです。品質も向上しテストもはかどりますよね。
要するにメルカリさんがやってる開発向けCI/CD基盤を想像してもらったほうが早いです。

で、システムを検証するときにいちいち各Podに侵入してlessやtailしてたら今までと何も変わらないので、エンジニアにはもっとクラウドネイティブな開発にも親しんでもらいたいというのと、 原因不明のエラーでPodが立ち上がらないという場合の死亡原因を突き止めるという感じでElasticsearchによるログ集約をやる必要があったのです。

構成

ようやくここからElasticsearchの話です。

僕の作ったシンプルなElasticserch on Kubernetesは以下のような感じで出来上がってます。

  • KubernetesのStatefulSet
  • ElasticsearchはSingleNode
  • Logstash/Kibana/APM Server付き
  • ログ収集はFilebeatが別途必要
  • ElasticsearchのデータはKubernetesのLocalVolumeを利用して永続化

順番に説明します。

KubernetesのStatefulSet

Kubernetesにはサービスをデプロイする方法として色々あります。Podという単位でサービスが立ち上がるのですが、 多くの場合PodをよしなにコントロールすることのできるようにDeployment/StatefulSetmのようなリソースが存在しています。 詳しくはKubernetesのWorkloadsリソース(その1)KubernetesのWorkloadsリソース(その2)を参照ください。

今回のElasticsearchはログ集約の要ですので、データが消えてもらうと困りますし、サービスが停止しても困ります。 そういう役割として最適なのがStatefulSetになります。

ElasticsearchはSingleNode

Elasticsearchは基本は3Node以上での動作を推奨しています。これはMasterNodeが2つのClientNodeを管理する感じで、ClientNodeが1つ死んでも大丈夫なような最低限のHA構成となります。 が、今回は開発向けであることと、たとえNodeが死んでもKubernetesが頑張って新しく起動してくれるので、 SingleNodeでの稼働としています。それと、Elasticsearchは最低推奨メモリが4Gとまぁまぁデカいので、リソースを最低限に抑えたいという意図もあります。

SingleNodeでの稼働については、Elasticsearch 7 からは discovery.type: single-node をelasticsearc.ymlに追加するだけです。

Logstash/Kibana/APM Server付き

俗に言うELK環境(Elasticsearch、Logstash、Kibana)に加え、Elasitc APM Serverも稼働するようにしています。 Logstashの設定にはRubyフォーマッタを指定しているので、Ruby on Railsのアプリのログ収集であればFilebeatの設定はほぼ不要ですが、以下の部分を補足します。
(logstash-configmap.yamlの全体については冒頭のGithubを参照してください)

1
2
3
4
5
6
7
8
output {
      elasticsearch {
        hosts => "http://elasticsearch.elasticsearch:9200"
        index => "%{[fields][env]}-%{+YYYYMMdd}"
        ilm_enabled => "true"
        ilm_rollover_alias => "dev"
      }
    }

まず[fields][dev]という[]で囲まれた文字列は変数です。この変数はLogstashの設定ファイルのどこにも定義がないと思います。 これはFilebeat側でこの変数を設定してLogstashへ送信すると、そのまま変数を取得できる仕組みがLogstashにあるのを利用しています。 つまりFilebeatのfieldsにて以下のように設定すると変数を渡せます。

1
2
3
fields:
      env: dev
      project: my-app

これで実際にElasticsearchに送信されるインデックス名は、my-app-dev-20191006-000001となります。 と、ここでまたもや設定した覚えのない文字列-00000X(Xは数字)が出てきました。これはYMLの ilm_enabled => "true" が影響しています。

ilm_enabled のilmはElasticsearchのIndex Lifecycle Managementの略で、それを有効にするということを指しています。デフォルトでlotate用の文字列として"-00000X"が指定されるということです。
Index Lifecycle Managementは要するにElasticsearchのインデックスの管理をよしなにやってくれる仕組みです。 Elasticsearchのインデックスにはステージという概念があります。Elasticsearch6まではこれをAPI経由だったり人力で頑張って色々設定しなければならなかったのですが、 Elasticsearch7からはKibanaの管理画面からGUIでポチポチすることが可能になりました。これがめっちゃ便利なんです。
僕の場合はElasticsearchに日々蓄積されるログを、一定期間経過後に完全に削除するという目的で使用しています。 開発向けなので、例えば3日前のログは要らないという定義を用いて、インデックスを物理削除しています。 この仕組みによって余計なインデックスの容量過多やパフォーマンス劣化を防いでいます。

謎の文字列 -000001 の答えはこのILMが生成する一意のキーです。生成される文字列については変えることもできます。詳しくは以下参照ください。
Logstash Reference(7.4) Elasticsearch output plugin

次にAPMについてです。

APMとはApplication Performance Monitoringの略で、以前だとNew RelicやDataDogが有名でしたが、Elasticも6、7あたりから本格参戦してます。
Ruby on Railsアプリの場合はGemで elastic-apmを導入することで簡単にアプリケーションのパフォーマンス情報をElasticsearchで収集し、Kibanaで可視化することができるようになります。 APMを利用するととで、パフォーマンスのボトルネックとなっているトランザクション負荷がどこで発生しているのかや、エラーが頻繁に起きる箇所などを容易に特定できます。 あるに越したことはないので開発用なら入れておいて損はありません。Elastic APM - Github
ちなみに、PHPは2019/10現在Agentが存在しませんので統計をとることができません……。

Rubyの elastic-apm の場合、 config/elastic-apm.ymlを定義することでAPMを利用できます。詳しくは以下参照ください。
APM Ruby Agent Reference(2.x)

ログ収集はFilebeatが別途必要

これは前述どおりですが、今回の構成だとLogstash単体ではログを収集できません。 各サービスにFilebeatを通してLogstashへログを送信する必要があります。

FilebeatはDaemonSetで各ネームスペースごとのPodの標準出力を集めるっていうのが正攻法なのですが、 Podが複数ログをファイルに吐き出している仕様の場合、それをうまく収集することはできません。正確にはできますが何かしらの工夫が必要になります。
そういったアプリケーションやインフラ側の工夫をしなくても収集できるようにするには、サイドカーコンテナとしてFilebeatを立ち上げることが必要になります。

言葉では難しいと思うのでRuby on Railsアプリでの例を以下に載せておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-rails-app
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      component: my-app
  template:
    metadata:
      labels:
        component: my-app
    spec:
      # Rails App
      containers:
      - name: "dev-my-app"
        image: my-rails-app:latest
        imagePullPolicy: Always
        envFrom:
          - configMapRef:
              name: "configmap-my-app"
        command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
        env:
        resources:
          limits:
            cpu: 1
          requests:
            cpu: 5m
        ports:
        - containerPort: 3000
          name: http
        volumeMounts:
        - name: log
          mountPath: /app/log
      # filebeat
      - name: "my-app-filebeats"
        image: docker.elastic.co/beats/filebeat:7.3.0
        volumeMounts:
        - name: log
          mountPath: /app/log
          readOnly: true
        - name: filebeat
          mountPath: /tmp/filebeat/
        command: ["filebeat", "-c", "/tmp/filebeat/filebeat.yml", "-e"]
      volumes:
      - name: log
        emptyDir: {}
      - name: filebeat
        configMap:
          name: "configmap-my-app"
          items:
            - key: filebeat.yml
              path: filebeat.yml

YMLからわかるようにサイドカーコンテナは Containers に単純に2つめの name/imageなどを定義するだけです。 また、実際にRailsアプリやfilebeatが利用するパラメータはすべて configmap-my-app.yaml に記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
apiVersion: v1
kind: ConfigMap
metadata:
  name: "configmap-my-app"
  labels:
    app: my-app
data:

  # elastic-apm
  APM_SERVER_URL: http://apm-server.elasticsearch:8200
  APM_SECRET_TOKEN: ""
  SERVICE_NAME: my-app

  # filebeat
  filebeat.yml: |
    filebeat.inputs:
    - type: log
      paths:
        - /app/log/*.log

      fields_under_root: true
      document_type: app-logs
      ignore_older: 24h

      multiline.pattern: '^[[:upper:], ]'
      multiline.negate: true
      multiline.match: after

    fields:
      env: dev
      project: my-app

      fields_under_root: true

    output.logstash:
      hosts: ["logstash.elasticsearch:5044"]
      index: "logstash-%{+yyyy.MM.dd}"

    #xpack.monitoring:
    #  enabled: true
    #  elasticsearch:
    #    hosts: ["http://elasticsearch.elasticsearch:9200"]
    #    #username: yyyyy
    #    #password: xxxxx

こんな感じです。APMの設定もConfigMapに外だししておくと、いちいちアプリをデプロイし直さなくてよいので便利です。

Filebeatに関してはマニュアル読んでもらったほうが早いので詳しくは省略します。 コメントアウトされているxpackについては、有料契約すると使えるようになるいろんな便利機能の1つです。 xpack.monitoring によってFilebeatの稼働統計がKibanaで閲覧できるようになります。

ElasticsearchのデータはKubernetesのLocalVolumeを利用して永続化

Elasticsearchのデータを永続化するために、永続ボリュームをKubernetesで利用しなければなりません。 本来であればローカル環境のKubernetesの場合、NFSを構築して使うほうが最適なのですが、 面倒くさがってしまって普通にローカルボリュームをmountで切って使ってしまいました!改善の余地ありです。

LocalVolumeの使い方については、以下参照ください。

以上長々と説明しました。

今後

まだやること多いです。例えば以下。

  • アラート監視を入れたい(Preacoとか)
  • 実際の使い方をREADMEに拡充

また、今回timestamp型をdatenanosに変えたこととかログを1つのインデックスに集約している理由とかを記事にできていないので、そのへんは別記事にまとめようと思います。

local_offer
folder work