EKS Fargateのtelemetryデータをopentelemetry-collectorで取得する
tl;dr
- EKS Fargateで動かすapplication containerのtelemetryデータ(metrics, logs, traces)をsidecarのopentelemetry-collector(以下sidecar otel-col)にて取得した
- metrics
- sidecar otel-colにてnode/proxy apiを利用しkubeletstat receiverで取得
- 動的に決まるnode名をDownward APIで取得
- sidecar otel-colにてnode/proxy apiを利用しkubeletstat receiverで取得
- logs
- teeコマンドにて標準出力とemptyDirをマウントした先にファイル出力
- sidecar otel-colにて同一emptyDirをマウントしてfilelog receiverで取得
- ファイル出力によるdiskfullを防ぐためlogrotate containerもsidecarとして追加
- logsに付与したいpod名、namespace名等をDownward APIで取得
- traces
- sidecar otel-colにてotlp receiverでデータを待ち受け
- application containerはsidecar otel-colにtracesを投げる
- metrics
はじめに
皆さんk8s上のシステムのtelemetryデータはどのように取得していますでしょうか。
DatadogのようなSaaSを使ったり、各クラウドベンダが提供するサービス(AWS/Amazon EKSだとContainer Insightsなど)を利用したり様々な方法があるかと思っています。
弊社ではAmazon EKSや閉域オンプレミス上のk8sクラスタなど、複数のk8sクラスタを運用する機会があり、
telemetryデータの取得方法をできるだけ統一したくopentelemetry-collector(以降 otel-col)の利用へと切り替えています。
オンプレミスでの対応を先に行い、その後EKSへと同様の設定で展開しようとしたところでFargateでのtelemetryデータの取得に詰まってしまったため、Fargate用の対応を行いました。
どういった問題が発生したのか、どう解決したのかについて纏めます。
前提
- otel-colはopentelemetry-collector operatorでdeploy
- versionは0.102.0を使用
- 以下設定等についてはconfigや必要な部分を抜粋して記載
- オブザーバビリティバックエンドについては言及しない
- telemetryデータの取得について注力します!
オンプレミスk8sでのtelemetryデータの取得
オンプレミスでのtelemetryデータの取得についてmetrics, logs, tracesそれぞれの方式をconfigを交えて記載します。
metrics
otel-colのkubeletstat receiverを使い、DaemonSetでnodeごとに取得しました。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
spec:
mode: daemonset
env:
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
config: |
receivers:
kubeletstats:
auth_type: serviceAccount
endpoint: "https://${NODE_IP}:10250"
DaemonSetが動くNodeのkubelet apiを叩いてmetrics情報を取得しています。
/stats/summary
や/pods
apiを使っています。
logs
otel-colのfilelog receiverを使い、DaemonSetでnodeごとに取得しました。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
spec:
mode: daemonset
volumes:
- name: varlog
hostPath:
path: /var/log
volumeMounts:
- mountPath: /var/log
name: varlog
readOnly: true
config: |
receivers:
filelog:
include: /var/log/pods/*/*/*.log
exclude: /var/log/pods/monitoring_*/*/*.log
include_file_path: true
operators:
- type: regex_parser
id: parser-containerd
regex: '^(?P<time>[^ ^Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$'
output: extract_metadata_from_filepath
timestamp:
parse_from: attributes.time
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
- type: regex_parser
id: extract_metadata_from_filepath
regex: '^.*\/(?P<namespace>[^_]+)_(?P<pod_name>[^_]+)_(?P<uid>[a-f0-9\-]{36})\/(?P<container_name>[^\._]+)\/(?P<restart_count>\d+)\.log$'
parse_from: attributes["log.file.path"]
- type: move
from: attributes.namespace
to: resource["k8s.namespace.name"]
- type: move
from: attributes.pod_name
to: resource["k8s.pod.name"]
exampleを参考に作成しているため、filelog receiver設定の詳細はこちらを見ていただければです。
Node(ホスト)側に出力されるログをマウントしてそこを読んでいます。
そして、include_file_path: true
を設定してlog.file.path
attributeにファイルパスを設定してもらい、operatorsというログ処理の機構の中でそのディレクトリ構造からnamespaceやpod名といった後でログを見る際に必要な情報を抽出してresourceに設定することをしています。
また、criが出力するtimeを見て、timestampに設定してます。
(podごとにlog formatが異なっても、criが設定するformatで必要な情報を取得できます。)
余談ですが、直近(2024/05)の更新で、
operatorsにcontainer operatorという、上記で行っているcriごとによく行われるparse対応を纏めて行ってくれるoperatorが追加されたのでそちらを使うとoperator部分が簡素にかけそうです。
まだ試せていないのですが、試して置き換えたいと思います。
上記では17行あったcontainerdに沿ったparse対応処理が以下の通り、3行くらいでかけてしまいます。
(default設定を明示的に書いている項目があったり、過不足している行があったりするので正確ではないですが、個人的にはそのくらいのインパクトでした。)
- type: container
format: containerd
add_metadata_from_filepath: true
これでcontainerd形式のファイルを読み込み、時刻、body、stdout/errの抽出とfile pathからpod名やnamespace名などの抽出を行ってくれるようです。
これからfilelog receiverを使い始める方はこのcontainer operator
を使うのが良さそうです。
traces
otel-colのotlp receiverを使い、sidecarでapplication podごとに取得しました。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
spec:
mode: sidecar
config: |
receivers:
otlp:
protocols:
grpc:
endpoint: "127.0.0.1:4317"
sidecarではgrpcにて待ち受けて、otlpのtracesをapplicationが投げてくるのを待つだけです。
EKSへの適用/Fargateでの問題
オンプレでは問題なく動き、いざ上記設定をEKSへ適用/展開しようとしました。
するとEKS on EC2は問題なく動くのですが、
EKS FargateはDaemonSetが使えない[1]ため上記の方法ではmetrics, logの取得ができませんでした。
tracesの取得はsidecarで行っているため変わらず可能です。
Fargateは1pod = 1nodeのため、単純にsidecarにすれば同じ方式でできるのでは?とも思いましたが、以下の点があり何か別の手を検討する必要がありました。
メンテナンスなどを考慮してできるだけ同じreceiverを使いたい思いがありその方向で色々と試してみたところ、妥協した部分はありますが、以下の方法で解決できました。
EKS Fargateでのtelemetryデータの取得
tracesはオンプレミスk8sでのtelemetryデータの取得と同様のため、
metrics, logsについてFargate用に修正した方式を記載します。
metrics
nodes/proxy apiを利用することにしました。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
spec:
mode: sidecar
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
config: |
receivers:
kubeletstats:
auth_type: serviceAccount
endpoint: "https://kubernetes.default.svc:443/api/v1/nodes/${env:NODE_NAME}/proxy"
podから直接kubelet apiを叩け無いため、api-serverを経由して叩こうという作戦です。
その際に、ノード名が必要になるためDownward APIで取得しています。
nodes/proxy apiを使うために権限を付与する必要があります。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
rules:
- apiGroups: [""]
resources:
- nodes/proxy
verbs: ["get"]
かなりすっきりkubeletstatsでfargate podのmetricsを取ることができました。
蛇足ですが、1Podあたり1ServiceAccount(SA)になるため、メインのapplication contianerとsidecar container共通で利用するSAに権限付与する必要があるのが少し気持ち悪いと思いました。
この件については議論[4]があるようですね。
logs
criがnodeに配置するログファイルを読むことは諦めて、自前でログファイルに出力する方式にしました。
kubectl logsコマンドは引き続き利用できると嬉しいため標準出力にも出力します。
そこでteeコマンドを使いました。
otel-colのmanifestより先にfargateで動かすアプリmanifest(Deployment)のイメージを記載します。
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: application
command:
- /bin/bash
args:
- -c
- exec_file | tee -a /var/log/fargate/application.log
volumeMounts:
- mountPath: /var/log/fargate
name: fargate-app-logs
- name: logrotate
volumeMounts:
- mountPath: /var/log/fargate
name: fargate-app-logs
volumes:
- emptyDir: {}
name: fargate-app-logs
妥協した点なのですが、標準エラー出力については取得できていません。
原因をわかっていないのですが、標準エラー出力も取得を試みると、
標準出力と標準エラー出力の出力順序がおかしくなるケースが有り、そこの時系列が乱れると調査がしにくくなると判断し、かつ標準エラーの場合は別の検知が可能であったためです。
こちらも蛇足ですが、このようなコマンドを試していました。
exec_file 1> >(tee stdout.log >&1 ) 2> >(tee stderr.log >&2)
また、自前で作成したログファイルはもちろんkubeletによるrotateやディレクトリ構造からのnamespace, pod名の判別ができなくなる[5]ためそれらに対応する必要があります。
実際にdiskfullになるとどうなるか試してみたところprocess(pod)は落ちないものの、自前のログファイルや(恐らく)criが作成するログファイルへの出力ができなくなり、まったくログが見れないpodが出来上がりました。(kubectl logsも追加のログがまったくない)
これを避けるために、さらにsidecarでlogrotateを実行しています。
こちら[6]を参考にさせていただきました。
長くなりましたが以上がアプリでのログファイル作成の部分で以降がote-colでのログファイル読み取りの話です。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
spec:
mode: sidecar
volumeMounts:
- name: fargate-app-logs
mountPath: /var/log/fargate
readOnly: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
config: |
receivers:
filelog:
include: /var/log/fargate/application.log
include_file_path: false
resource:
k8s.container.name: fargate-application
k8s.namespace.name: ${env:POD_NAMESPACE}
k8s.pod.name: ${env:POD_NAME}
k8s.pod.uid: ${env:POD_UID}
operators:
- type: json_parser
timestamp:
parse_from: attributes.timestamp
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
いくつかポイントがあるので箇条書きで並べると
- アプリが作成するログを読むためにvolumeMountをする
- sidecarからvolumeMountするのが初だったのでvolumes宣言がないけど、volumeMountを書くのに違和感があった
- criによりpod名、namespaceといった情報はDownward APIから取得する
- criが付与してくれていたtimestamp情報はアプリのログから取得する
- sidecarなのでログの形式はわかっている
という感じです。
こちらは色々手が入りましたが、無事filelog receiverでfargate podのログを取得することができました。
纏め
EKS Fargateの制約/特性上、onpreと同様にDaemonSetでmetrics/logsを取得することはできませんでしたが、
- metrics
- nodes/proxyを利用して無理やりapi-server経由でkubeletのstats apiを叩く
- logs
- 自前でログファイルに出力する
- logrotateを一緒に動かす
という対応をしてsidecarで動かすことでなんとかFargateでもtelemetryデータの取得ができるようになりました。
また、同じreceiver(kubeletstats, filelog)を使うことができたため、後段の処理は同じものを使うことができます。
ただ、logsについて標準エラー出力の扱いで妥協した部分があるため、その点は意識する必要があります。
なにか良い方法が見つかれば追記できればと思います。
最後まで見ていただきましてありがとうございます!!
気になる点などございましたら教えていただければとても嬉しいです!
ありがとうございました。
-
DaemonSets aren’t currently supported. from https://docs.aws.amazon.com/prescriptive-guidance/latest/implementing-logging-monitoring-cloudwatch/kubernetes-eks-metrics.html ↩︎
-
HostPath persistent volumes aren't supported. from: https://docs.aws.amazon.com/prescriptive-guidance/latest/implementing-logging-monitoring-cloudwatch/kubernetes-eks-metrics.html ↩︎
-
kubeletはログのローテーションとログディレクトリ構造の管理を担当します。 from: https://kubernetes.io/ja/docs/concepts/cluster-administration/logging/ ↩︎
Discussion