it.todo("should be a (web)log");

役に立(つ/たない)技術情報やポエム

OpenTelemetry Collectorでコンテナのログをファイルから取る

呑気にOSをアップデートしたら今までjournalに出ていたコンテナのログが出なくなってしまった。 今までコンテナのログもjournaldから取ってOpenTelemetry Collectorで集約していたが、これを機にファイルから取る方法を試すことにした。

今回はPodmanを使っている*1が、後述するように使う道具の全てがDocker用なのでDockerでも同じようにできるはず。

Podmanのログファイル

Podmanでコンテナを起動する際、log-driverk8s-fileを指定すると、コンテナIDごとに分かれたディレクトリにログファイルが作られる。 ログファイルの位置は$ podman inspect (コンテナ名) --format {{.HostConfig.LogConfig.Path}}で確認できる。 このログファイルはcontainerdのログと同じ形式になっている*2ので、containerdのログファイルを読む仕組みを流用できる。

また、この格納方法から、Podmanのコンテナの生成と破棄を検知して、ファイルからログを読み出す設定をコンテナIDに同期させればうまくログを収集できそうということがわかる。

Receiver Creator

OpenTelemetry Collectorには以下のようなコンポーネントが存在する。

  • Receiver: テレメトリを収集する
  • Processor: テレメトリを加工する
  • Exporter: テレメトリを出力する
  • Provider: 設定ファイルに介入して情報を展開する
  • Extension: その他全て

これらのコンポーネント(主にReceiver/Processor/Exporter)を組み合わせてパイプラインを組むのが作法だが、基本的にこれらのコンポーネントは静的に定義され、Collectorが起動している間に変化することはない。 しかしReceiver Creatorは特殊なコンポーネントで、Observerという一種のExtensionからコンテナなどの情報を取得し、そこからテンプレートを使って動的にReceiverを増減させることができる*3

Docker Observer

Docker Observerは実際にDockerを監視してコンテナの状態を検知する。PodmanはDockerの互換APIを実装している*4ため、Docker用のObserverをそのまま使うことができる。

File Log Receiver

OpenTelemetry Collectorでファイルからログを読み出して加工するためにはFile Log Receiverを使う。 File Log ReceiverはOperatorを使って取得したログをパースしたり加工したりできる。 コンテナログの場合、type: containerというOperatorを使えばいい感じにパースしてくれる。

レシピ

最終形(ログ取得に関わる部分)としてはこんな感じ。他にも書きたいことは色々ある*5が、今日は疲れたのでここまで。

extensions:
  docker_observer/podman:
    endpoint: unix:///var/run/podman/podman.sock
    excluded_images:
      - (Collector自身のイメージ名を入れて自分を除外するようにしておく)

receivers:
  receiver_creator/podman:
    watch_observers: [docker_observer/podman]
    resource_attributes:
      container: # type: container のときの設定
        container.id: "`container_id`"
        container.name: "`name`"
        container.image.name: "`image`"
    receivers:
      filelog:
        rule: type == "container"
        config:
          include:
            - "/path/to/containers/`container_id`/userdata/*.log"
          include_file_path: true # ログストリームの連続性の判定に必要
          operators:
            - type: container
              add_metadata_from_filepath: false

service:
  extensions:
    - docker_observer/podman
  pipelines:
    logs/podman:
      receivers: [receiver_creator/podman]
      processors: ...
      exporters: ...

おまけ: OpenTelemetry Collectorで構造化ログをいい感じにパースする設定

JSONやlogfmtを区別せずに放りこみたいし、severityやトレース周りをフィールドから読んで埋めてほしいということはよくある。

- type: router
  routes:
    - output: json_parser
      expr: hasPrefix(body, '{"')
    - output: key_value_parser
      expr: body matches '^[a-zA-Z0-9]{1,16}='
  default: end
- type: json_parser
  parse_from: body
  parse_to: body
  output: structured
- type: key_value_parser
  parse_from: body
  parse_to: body
  output: structured

- id: structured
  type: noop
# Severity
- type: copy
  if: body.level != nil
  from: body.level
  to: attributes.level
- type: severity_parser
  if: attributes.level != nil
  parse_from: attributes.level
  on_error: send_quiet
# Traces
- type: move
  if: body.traceID != nil
  from: body.traceID
  to: body.trace_id
- type: move
  if: body.spanID != nil
  from: body.spanID
  to: body.span_id
- type: trace_parser
- type: remove
  if: body.trace_id != nil
  field: body.trace_id
- type: remove
  if: body.span_id != nil
  field: body.span_id

- id: end
  type: noop

*1:Podmanが好きで使っているかと聞かれればあまりそうではなく、使ってるVyOSが自由にプロセスを動かせる手段をコンテナしか提供していなくて、それがPodmanで……

*2:正確にはCRI-Oの形式になっていて、微妙に違う

*3:Observerの概念はここでしか使われていないのが残念。OpenTelemetry Collectorにおけるサービスディスカバリの抽象として働いているはずで、うまくやればK8s Attribute Processorとか置き換えられそうな気がする。

*4:イベントの取得やコンテナの一覧などは実装されているがメトリックなど一部は互換性がない

*5:Observerがコンテナの生成を検知するのは非同期なのでログは本当は頭から読む必要があるとか、それをするとCollectorを再起動する度に大量のログが流れるのでどこまで読んだかを記録するためにstorage extensionが必要とか……