Datadog Agent(DaemonSet)の起動ができないパターンに対処した話
前提条件
本記事では以下のバージョンを前提としています。
- EKS(EC2) 1.28
- Cluster Autoscaler 1.28.2
- Horizontal Pod Autoscaler
- Datadog
- Helm マニフェスト 3.33.1
- Datadog Agent 7.46.0
Datadog Agent の起動台数が足りないアラートが発生
Datadog モニターから以下のアラートが届きました。
DaemonSet は全ての Node に1台ずつ起動するという特性があるので、 このモニターでは Node の数だけ DaemonSet が起動しているかどうかを監視しています。
監視内容は以下のクエリで、要約すると「直近40分間で理想とする DaemonSet Pod の起動数を満たせているかどうか」を監視しています。
min(last_40m):min:kubernetes_state.daemonset.desired{env:production} by {kube_daemon_set,kube_cluster_name} - max:kubernetes_state.daemonset.ready{env:production} by {kube_daemon_set,kube_cluster_name} > 0``
この時のアラートでは Node30台に対して Datadog Agent Pod が 29台しか起動していない状態でした。
ただ、このアラートは以前からサーバー縮小(Nodeの最小台数を減らす)などの作業で一時的に発生するケースが稀にありました。
また、数分後にはアラート状態から復旧していたことや、直近で Horizontal Pod Autoscaler の設定を変更して Node数が変動しやすくなっていたという経緯もあって一旦は深追いせずにクローズしました。
アラート再発・調査
数日後、同様のアラートが再発しました。
しかも、今度は1時間以上アラート状態が継続しており、一時的な Node数の増減だけが原因ではないと判断し調査することにしました。
以下のコマンドで全ての Pod を確認したところ、Pending 状態になっている Datadog Agent Pod を発見しました。
kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
~略~
monitoring datadog-5w697 0/3 Pending 0 25m
Pending 状態の Pod の詳細を kubectl describe
で確認したところ、 CPU の空き容量不足が原因で Pod のスケジューリングに失敗しているようでした。
kubectl describe po datadog-5w697 -n monitoring
~略~
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 3m19s (x29 over 13m) default-scheduler 0/32 nodes are available: 1 Insufficient cpu. preemption: 0/32 nodes are available: 32 No preemption victims found for incoming pod..
CPU の空き容量不足の実態を確かめるためにスケジューリングに失敗している Node の状態を確認したところ、予約済み含めて空き CPU 容量 9% になっていました。
Datadog Agent の Requests 設定は 全体の10%(200m) となっているので、空き CPU 容量 9% では収まりきらず起動できないということみたいです。
どの Pod が占拠しているのか確認するために kubectl describe node
で詳細確認したところ、 Puma(Railsを動かすためのアプリケーションサーバー)が 15% × 4台 = 60% と、多めに枠を取っていることがわかりました。
$ kubectl describe node ip-10-0-221-239.ap-northeast-1.compute.internal の結果の一部
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system aws-node-z5nst 50m (2%) 0 (0%) 0 (0%) 0 (0%) 72m
kube-system kube-proxy-bplll 100m (5%) 0 (0%) 0 (0%) 0 (0%) 72m
logging fluent-bit-dsdf4 1m (0%) 0 (0%) 60Mi (0%) 0 (0%) 72m
socialplus socialplus-puma-659fd5fd7f-gn59q 300m (15%) 0 (0%) 450M (6%) 0 (0%) 73m
socialplus socialplus-puma-659fd5fd7f-h244z 300m (15%) 0 (0%) 450M (6%) 0 (0%) 73m
socialplus socialplus-puma-659fd5fd7f-w5bjf 300m (15%) 0 (0%) 450M (6%) 0 (0%) 73m
socialplus socialplus-puma-659fd5fd7f-wgkdf 300m (15%) 0 (0%) 450M (6%) 0 (0%) 73m
socialplus socialplus-sidekiq-low-67c8959c4f-89vj9 200m (10%) 0 (0%) 750Mi (10%) 0 (0%) 73m
socialplus socialplus-sidekiq-replicadb-7f78568fcd-mrsrm 200m (10%) 0 (0%) 1Gi (14%) 0 (0%) 73m
一時対応
Node の CPU 使用率の問題で Datadog Agent Pod が起動しないことがわかったので、手っ取り早くアラート状態を解除するために一番容量を食っている Puma の再起動をすることにしました。
Puma 再起動後、 Node 毎の Puma の配置が入れ替わったおかげで全ての Datadog Agent Pod が起動できるようになりました。
恒久対策をするまでの間、1~2週間に1回くらいの頻度で同じ状況が発生しており、どうやら Cluster Autoscaler による Node のスケールアウト/スケールインのタイミングにランダムで同じ状況が再現しているようでした。
DaemonSet なのに起動に失敗する謎
本来 DaemonSet は、全ての利用可能な Node に1台ずつ Pod を起動することを保証する挙動をするはずです。
DaemonSet の仕様について調べてみると、DaemonSet Pod に以下の Toleration を自動的に追加すると書いてあり、スケジューリング先の Node の Status が悪くてもスケジューリングを優先するような設定になっていることがわかります。
Toleration key | Effect | Details |
---|---|---|
node.kubernetes.io/not-ready | NoExecute | 健康でないNodeや、Podを受け入れる準備ができていないNodeにDaemonSet Podをスケジュールできるように設定します。そのようなNode上で動作しているDaemonSet Podは退避されることがありません。 |
node.kubernetes.io/unreachable | NoExecute | Nodeコントローラーから到達できないNodeにDaemonSet Podをスケジュールできるように設定します。このようなNode上で動作しているDaemonSet Podは、退避されません。 |
node.kubernetes.io/disk-pressure | NoSchedule | ディスク不足問題のあるNodeにDaemonSet Podをスケジュールできるように設定します。 |
node.kubernetes.io/memory-pressure | NoSchedule | メモリー不足問題のあるNodeにDaemonSet Podをスケジュールできるように設定します。 |
node.kubernetes.io/pid-pressure | NoSchedule | 処理負荷に問題のあるNodeにDaemonSet Podをスケジュールできるように設定します。 |
node.kubernetes.io/unschedulable | NoSchedule | スケジューリング不可能なNodeにDaemonSet Podをスケジュールできるように設定します。 |
node.kubernetes.io/network-unavailable | NoSchedule | ホストネットワークを要求するDaemonSet Podにのみ追加できます、つまりspec.hostNetwork: trueと設定されているPodです。このようなDaemonSet Podは、ネットワークが利用できないNodeにスケジュールできるように設定します。 |
結局、 DaemonSet なのに他の Pod より後からスケジューリングされ、Node の空き CPU が足りず起動に失敗する原因についてはわかりませんでした。
個人的な予想としては、
- Horizontal Pod Autoscaler が Puma Pod をスケールアウト
- Pod が増えたことによって、要求 Node 数が増加
- Cluster Autoscaler が Node をスケールアウト
- スケジューリング待ちだった Pod が一気に新しい Node に流れ込む
- 運が悪いと DaemonSet が後から起動することになる
みたいなことが裏で起こっていたのではないかと考えています(もし知っている方がいれば教えていただきたいです)。
恒久対策
恒久対策として以下のような案がありました。
- 優先起動処理を実装
- PriorityClass を設定すれば実現できそう
- Kubernetes の Resource Requests と Resource Limits を調整する
- これ以上 Requests を下げると Pod の稼働に影響するので下げられない
- リソース拡張
- インスタンスタイプの変更(t3.large -> t3.xlarge)
- コスト増だし、これで解決する確証がない
-
最小Node数をあらかじめ増やす- 試したけど再発したので意味がなかった
- インスタンスタイプの変更(t3.large -> t3.xlarge)
この中でも確実に効果がありそうで手軽にできる PriorityClass の設定を行うことにしました。
PriorityClass の実装
PriorityClass の実装をするにあたり、事前にPodの優先度とプリエンプションを読んで理解しておくことをお勧めします。
まず、既存の構成で PriorityClass が実装されていないか以下のコマンドで確認しました。
$ kubectl get priorityclasses.scheduling.k8s.io
NAME VALUE GLOBAL-DEFAULT AGE
system-cluster-critical 2000000000 false 316d
system-node-critical 2000001000 false 316d
system-cluster-critical
と system-node-critical
が実装されていますが、これらは最初から kubernetes に用意されている PriorityClass で、退去されるとクラスターの動作に関わる重要な Pod を保護するための優先度クラスです。
デフォルトで用意されている PriorityClass を Datadog Agent Pod に紐づけるだけでも優先起動させるという要件は満たせそうなのですが、 Datadog Agent Pod が Node の存続に関わるほど優先度が高いかと言われるとそうでもなさそうなので別途 Datadog Agent のための PriorityClass を作成することにしました。
以下は PriorityClass を新しく設定するために設定した Helm の values.yaml です。
# Datadog Agent のための優先度設定を作成する
priorityClassCreate: true
priorityClassName: datadog-agent-priority
# リソース不足などで Datadog Agent を起動できない場合他の優先度の低い Pod の Preempt を認める
priorityPreemptionPolicyValue: PreemptLowerPriority
# デフォルトの priority が 0 なので、 10 で十分
priorityClassValue: 10
コメントに書いてある通り、PriorityClass がない Pod(Pumaなど)の優先度はデフォルトで0なので、優先度10で十分優先されることになります。
また、 priorityPreemptionPolicyValue
を PreemptLowerPriority
に設定することで、より優先度の低い Pod を Preempt(差し替え)することを許可しています。
preemptionPolicy
の他の設定についてはこちらをご覧ください。
上記の設定により、Puma などがリソースを占拠している状況でも優先度の低い Pod と差し替えることで Datadog Agent Pod の起動を実現できるようになりました。
その後、設定から2ヶ月経ちましたが、DaemonSet の起動数が足りないアラートは一度も起きていないです。
まとめ
- DaemonSet はスケジューリングを最優先するが、絶対ではない
- PriorityClass を適切に設定することで詳細な優先起動設定が可能
Discussion