🔭

Kubernetes内のアプリログをSplunkに送る

2023/05/01に公開

はじめに

Kubernetesはロギングもホストとは諸々違います。
ただ、一つ変わらないことはSplunkに送ればなんでもできるということです。
ということでKubernetesのコンテナログの送信方法をまとめました。

使用するエージェント

Splunk OpenTelemetry Collector for Kubernetes(以下、Splunk Otel)を使います。
これはOpenTelemetry CollectorをKubernetes環境に使いやすくチューニング、機能追加したものです。
Helm Chartが用意されており様々な設定を自動化できます。可能な設定について詳細はこちら

HEC準備

Splunk OtelではSplunkへはHECで送信するため、先に準備しておきます。

  1. ヘッダーメニューから 設定 > データ入力
  2. HTTPイベントコレクターの「新規追加」をクリック
    適当に設定
    名前:任意(「Otel」など)
    ソースタイプ:自動
    インデックス:そのまま
  3. 払い出されたトークン値をメモしておく

Kubernetesのログ出力

代表的な以下方法について見てきます。

  1. 標準出力
  2. emptyDirへの出力

1. 標準出力

一般的に推奨される方法がこちらです。標準出力または標準エラー出力にログを出力します。
Splunk Otelはデフォルト設定で標準出力のログを取得することができます。

テスト用Pod

テスト用のPodを立ち上げて確かめてみましょう。busyboxを使い標準出力および標準エラー出力に5秒ごとにログを無限に吐きだします。

標準出力テスト用のPod
apiVersion: v1
kind: Namespace
metadata:
  name: std-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: std-logger
  namespace: std-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: std-logger
  template:
    metadata:
      labels:
        app: std-logger
    spec:
      containers:
      - name: busybox
        image: busybox
        command:
          - "/bin/sh"
          - "-c"
          - |
            while true; do
              echo "$(date '+%Y-%m-%d %H:%M:%S') - Standard Out Log!"
              echo "$(date '+%Y-%m-%d %H:%M:%S') - Standard Error Log!" >&2
              sleep 5;
            done

標準出力、標準エラー出力を確認してみましょう。

kubectl logs -l app=std-logger -n std-app
2023-04-30 13:09:59 - Standard Out Log!
2023-04-30 13:09:59 - Standard Error Log!
2023-04-30 13:10:04 - Standard Out Log!
2023-04-30 13:10:04 - Standard Error Log!

Splunk Otelの設定

まずは設定用のyamlを用意します。splunk_log.yamlとでもしておきます。

splunk_log.yaml
clusterName: <クラスター名>
logsEngine: otel

splunkPlatform:
  logsEnabled: true
  token: "<HECトークン>"
  endpoint: "https://<ホスト名>:<HECポート>/services/collector"
  index: "<index名>" #デフォルトはmain。変更したい場合は設定する。
  insecureSkipVerify: true #Splunk Entepriseに送る場合にオレオレ証明書を使っている場合。

簡単に解説します。
cluseterName:ログにメタデータとして埋め込むための任意のクラスター名
logsEngineotelを指定することでfilelog receiverを使う(デフォルトはfluentd)
splunkPlatform:HEC送信のための設定。URLなどの詳細はこちら

次にHelmを使いクラスターにデプロイします。

Splunk Otelのデプロイ
helm repo add splunk-otel-collector-chart https://signalfx.github.io/splunk-otel-collector-chart

helm install my-splunk-otel-collector splunk-otel-collector-chart/splunk-otel-collector -f ./splunk_log.yaml

Splunk OpenTelemetry Collector is installed and configured to send data to Splunk Platform endpoint "https://<ホスト名>:<HECポート>/services/collector".とメッセージが出ればPod作成成功です。

Otel Collectorがノードにデプロイされ、filelog receiverによりPodの標準出力、標準エラー出力の値がSplunkに送られ始めます。
それぞれの結果を見てみましょう。

標準出力

標準エラー出力

Splunk Otelのデフォルト設定によりメッセージ以外にも様々なメタデータを付与してくれています。sourcetypeはコンテナ名が使われるようです。
Kubernetesで標準出力と標準エラー出力は一つのファイルに出力されるので若干使いにくいのですが、さすがSplunk、きちんとフィールドで拾ってくれています。

補足:Splunk Otelで何をしているのか

生成されたOtelのコンフィグを確認すると以下の設定が確認できます。

Splunk Otelで生成されたコンフィグ(抜粋)
receivers:
  filelog:
    include:
    - /var/log/pods/*/*/*.log

service:
  pipelines:
    logs:
      exporters:
      - splunk_hec/platform_logs
      processors:
      - memory_limiter
      - k8sattributes
      - filter/logs
      - batch
      - resource
      - resource/logs
      - resourcedetection
      receivers:
      - filelog
      - fluentforward
      - otlp

Podの標準出力ログはノードの/var/log/pods/配下に出力されます。そのため、コンフィグでターゲットのパスを/var/log/pods/*/*/*.logとしています。
(念のため、Splunk OtelはDaemonSetを使うので全ノードのログを取れます)
また、Pipelineのprocessorsで大量の定義を入れてくれて、これがメタデータの付与などを担っていることも分かります。助かりますね。

2. emptyDirへの出力

場合によっては標準出力ではなくemptyDirのログファイルに出力するケースもあります。
この場合、一般的な方法はsidercarでデプロイしてファイル取得かと思いますが、今のところSplunk Otelはsidecarによるデプロイはできません。
じゃあ代わりにどうするかというと、emptyDirはノードのディレクトリからも参照できますので、Splunk Otelからも直接参照する手を使います。
参考:
https://github.com/signalfx/splunk-otel-collector-chart/issues/307

テスト用Pod

まずはemptyDirに出力するテスト用のPodを立ち上げます。

emptyDirテスト用のPod
apiVersion: v1
kind: Namespace
metadata:
  name: emptydir-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: emptydir-logger-app
  namespace: emptydir-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: emptydir-logger-app
  template:
    metadata:
      labels:
        app: emptydir-logger-app
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo \"$(date '+%Y-%m-%d %H:%M:%S') - EmptyDir Sample Log!\" >> /logs/app.log; sleep 5; done"]
        volumeMounts:
        - name: logs-volume
          mountPath: /logs
      volumes:
      - name: logs-volume
        emptyDir: {}

emptyDirを使い/logsをマウントし/logs/app.logにログを吐きだします。

emptyDirはどこにあるのか

デフォルトでは/var/lib/kubelet/pods/配下にPodごとのディレクトリが切られています。このディレクトリの位置はkubeletの設定により変更することもできますので、使っているk8s環境で変更されていないか念のため確認しておきましょう。

emptyDirとファイルは以下のようなパスに存在しています。
/var/lib/kubelet/pods/<PODのUID>/volumes/kubernetes.io~empty-dir/<Volume名>/<ファイル名>

さて、実際に吐き出されたログファイルはノードにSSHでアクセスすることで確認できます。
(普通はkubectl exec -it [テストpod名] -- cat /logs/app.logで見れますが、今回はパスも確認したいのでSSHします)
今回はminikubeを使っていますので、minikubeの場合はminikube sshでアクセスできます。
lsしてみます。

emptyDirの中身を確認
sudo su
ls /var/lib/kubelet/pods/

すると、以下のようにPodのUIDの名前でディレクトリができていることが分かります。

197cd0de602d7cb722d0bd2daf878121      5175bba984ed52052d891b5a45b584b6      ebbe12ad-4db5-43a1-b2fe-492f113ecf0b  

UIDはkubectl get pods <POD名> -o jsonpath='{.metadata.uid}'で確認できます。

今回のテストPodで出力したファイルを確認してみます。

emptyDirの中身を確認(続き)
cat /var/lib/kubelet/pods/ebbe12ad-4db5-43a1-b2fe-492f113ecf0b/volumes/kubernetes.io~empty-dir/logs-volume/app.log
2023-04-30 13:31:07 - EmptyDir Sample Log!
2023-04-30 13:31:12 - EmptyDir Sample Log!

Splunk Otelの設定

それではSplunk Otelでログ取得します。
Helm Chartでマウントポイントを追加できますので以下のようにします。

splunk_log.yaml
clusterName: <クラスター名>
logsEngine: otel

splunkPlatform:
  logsEnabled: true
  token: "<HECトークン>"
  endpoint: "https://<ホスト名>:<HECポート>/services/collector"
  index: "<index名>" #デフォルトはmain。変更したい場合は設定する。
  insecureSkipVerify: true #Splunk Entepriseに送る場合にオレオレ証明書を使っている場合。
  
#以降追加
agent:
  extraVolumes:
    - name: emptydirlogs
      hostPath:
        path: /var/lib/kubelet/pods/
  extraVolumeMounts:
    - name: emptydirlogs
      mountPath: /tmp/emptyDirLogs
      readOnly: true
      
logsCollection:
  extraFileLogs:
    filelog/sample-logger:
      include: [/tmp/emptyDirLogs/*/volumes/kubernetes.io~empty-dir/logs-volume/app.log]
      start_at: beginning
      include_file_path: true
      include_file_name: false
      resource:
        host.name: 'EXPR(env("K8S_NODE_NAME"))'
        com.splunk.sourcetype: myLogOnEmptyDir
        com.splunk.source: /logs/app.log

簡単に解説します。
agent.extraVolumesextraVolumeMountsで/tmp/emptyDirLogsにマウントしています。
logsCollection.extraFileLogs.filelog/<任意の定義名>filelog receiverの設定を直接書けます。特にsourcetypeは指定すべきです(Splunk側でのフィールド抽出定義のため)。

Splunk Otel先ほど既にデプロイ済みなので今度はupgradeしてみます。

Splunk Otelのupgrade
helm upgrade my-splunk-otel-collector splunk-otel-collector-chart/splunk-otel-collector -f ./splunk_log.yaml

これによりemptyDirの中身をSplunkに送り始めます。
それでは結果を見てみましょう。

emptyDir

いいですね。問題なく取れています。

まとめ

Splunk OpenTelemetry Collector for Kubernetesを使えばKubernetes内のアプリログを簡単にSplunkに送信することができました。便利ですね。

今回はログでしたが、トレース、メトリクスを取得する方法はこちらにまとめています。

https://qiita.com/symmr/items/20f58522d7c1bcb8eb91

Discussion