[k8s] 意外な原因でdnsのパフォーマンス障害になってしまった時の記録

kubernetesでのdnsトラブルは結構ありがちみたいで、関連する記事も多くあります
私もそんなdnsトラブルに遭遇し、調べ倒したけど結局自力では解決できなかった時の記録です

結論だけ置いとくと、nodelocal dnscacheを使おうというものです
https://kubernetes.io/ja/docs/tasks/administer-cluster/nodelocaldns/

# 環境
プロバイダ: aws
eks: 当時のk8sバージョンを忘れてしまったけど1.16かそれ以前か
node: セルフマネージドec2（fargateではない）

# 経緯
1年以上前のことになるのですが、ECSで稼働していた既存サービスを移行するk8sクラスタを作成し、負荷テストを行ったところ、問題が発生しました

大まかな内容は、多量のリクエストを一定の速度で流し続けると、数分経過後に遅延・エラーが多発するというもの

# 調査

状況を整理すると

- 流すリクエストの量が同じなら、エラーが出始めるタイミングもほぼ同じで、再現性がある
    - 量を増やすとエラーが出るのが早くなるし、減らすと遅く出る
    - どこかにエラーが出なくなるラインがあると思うけど未確認
- エラーログは、DBのdns名前解決が一時的に失敗したという内容
    - アプリケーションの言語はphp
    - DBはk8sクラスタの外（vpc内）にあるRDS
- エラーは一定時間出続けると一旦出なくなり、その後また出る

### DNS調査

エラーログにdnsの問題と言われているので、dns周りに注目していじりながら同様のテストを行い調べます

- dnsはeksデフォルト設定のcoredns
- corednsのエラーログは出ていない
- corednsがcpuやメモリを大量に消費しているような様子はない
- corednsのメトリクスを見てみると、エラーが多発する時間でクエリ数が落ちている
    - これだとcorednsまでたどり着く前段階の問題に見える
- corednsのpodを増減させてテストすると、エラーが出るまでの時間が増減する

メトリクスを観察した感じだと、エラーログがdnsの問題と言ってるだけで、実際の原因はdnsじゃないのではないかと思ったりしましたが、corednsのpodの増減が結果に影響するならdnsの問題なのかな、と、難しい
調査を続けると有力情報を発見しました

- 同一のnodeにcorednsのpodを増やしてもテストの結果は変わらない

つまり、corednsが展開されているnodeの問題である可能性が高そう
coredns自体は `dnsPolicy: Default` ということで、hostのdnsに依存しているので一応筋は通る？
そしてやはりメトリクスに表れているdnsまでたどり着けてない感じは気になる

### DNS以外調査

DBとpodを同じAZに集めてみましたが遅延やエラーに変化はありませんでした
ちなみに正常処理部分の平均レイテンシは向上しました

ログを精査していると遅延しているものに明らかな特徴がありました
それらは処理時間がキリよく5秒か10秒ばかりであからさまに怪しい感じ

# 続く調査

googleしまくる

### single-request-reopen

https://medium.com/techmindtickle/intermittent-delays-in-kubernetes-e9de8239e2fa
大まかには、AレコードとAAAAレコードのlookupを同一ポートで行っていることが原因で5秒単位の遅延が生じるというものらしいです
解決するには podのdnsConfigのoptionに `single-request-reopen` を付与すること

```yaml
spec.template.spec:
  dnsConfig:
    options:
      - name: single-request-reopen
```

結論から言うと、これで解決はしませんでした

この問題とは関係なくこのoptionは使ったほうがいいかもしれませんが、それはまた今度ということで

### conntrack

https://www.weave.works/blog/racy-conntrack-and-dns-lookup-timeouts
iptablesを使用するとnf_conntrackというものが増え、その上限はカーネルパラメータnf_conntrack_maxの値、ということだそうです

実際に、問題が起きるインスタンスのnf_conntrackを観察しながらテストを行うとすごい勢いで増えていきました
今度こそ原因にたどり着いたと思ったんですが、nf_conntrack_maxの値に到達する前にエラーが発生し始め、結局それ以上は上がらずnf_conntrack_maxに到達することはありませんでした

ですが、これも将来対応が必要になる部分かもしれません

また、記事で言及されているように、kube-proxyをiptablesモードからipvsモードに変更することではこの問題は解決しません
一応藁にもすがる思いで変更してみてやはり解決しませんでしたが、これとは関係なしにipvsモードにしたほうが良さそうな雰囲気があります
（このへんはeksにマネージドで対応してほしい…）

### awsのdns制限

aws vpcでのdnsリクエストには上限が設定されています
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-dns.html#vpc-dns-limits
こちらの方法で調査しましたが、この制限には抵触していませんでした
https://aws.amazon.com/jp/premiumsupport/knowledge-center/vpc-find-cause-of-failed-dns-queries/

# nodelocal dnscacheの導入

今となっては使って当然くらいのnodelocal dnscacheですが、当時はbetaでそこまでメジャーな存在ではなかったと思います
なんか怪しいな大丈夫かなと思いながら試した記憶があります
https://kubernetes.io/ja/docs/tasks/administer-cluster/nodelocaldns/

__結論、この対応でエラーは出なくなりました__

# 根本原因の追求

nodelocal dnscacheを使うことで、とりあえず死亡は免れたわけですが、根本原因を特定しないことにはいつまた同じ事象に悩まされるかわかりませんので、それはそれということで調査を続行します

### 決定的かもしれないやつ

なんと、corednsのpodを配置するec2インスタンスのサイズを1ランク上げるとエラーが出なくなりました

ので、インスタンスのサイズによる違いをawsに問い合わせたところ、違いはcpuとメモリの基本スペック、あとはアタッチ可能なENIとそのセカンダリIPの数、とのことでした
ということで、今回の事象に影響しそうな違いはないようです

併せて、両インスタンスのカーネルパラメータの差分を出して全部合わせてみましたが、これでは解決しませんでした

### aws追加調査

corednsのログやtcpdumpの結果などを取得して送ればawsが追加調査してくれるとのことなので、お願いします
ファイルを提出したら待つだけです
かなり待った記憶があります

# 原因発覚

awsの調査により、原因が判明しました

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/security-group-connection-tracking.html
セキュリティグループの接続追跡というものがあり、これが上限に抵触したと推測される、とのことです
なるほど、最初の数分は順調にリクエストを処理して、一定時間後にエラーになり、しばらく経つと再び処理できるようになる、という流れは確かにこれに合致します

そしてこれはインスタンスのスケールアップにより解決できるとのこと
いやいやここにもインスタンスサイズで違いあるじゃないですか…
確かにインスタンスサイズを上げたら出なくなるのでこれで間違いないでしょう
なお、このあたりは内部仕様なので詳細は教えられないとのことでした
これは自力解決はちょっと無理でしたね…

記事を書くにあたってドキュメントを改めて見ているのですが、現在はこのあたりの制限に言及があり、メトリクスも追加されているようです
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html
今回の事象だと `conntrack_allowance_exceeded` に注目すればよさそう
ちなみにこれは去年のre:Inventで発表されたようです

# 改めて対応を考える

インスタンスのスケールアップで対応できるとのことでしたが

- ひとつサイズを上げるとどのくらい上限が引き上げられるのか（単純に2倍になるのか違うのか）
- t系やm系などのタイプで違いはあるのか
- セキュリティグループのルールの数が多いと上限に抵触しやすくなるようなことはあるのか

このあたりを聞いてみましたが、内部仕様ということで何も教えてくれませんでした
対応するのに欲しい情報だったんですけどね…

なので、どうしてもスケールアップせずにこの問題をクリアしないといけない、ということになれば、上記ドキュメントの「追跡されていない接続」の条件を満たすようにセキュリティグループを変更するしかなさそうです（要はフルオープンにするということ）

今回は、nodelocal dnscacheでエラーや遅延は出なくなるのでそれで解決とし、インスタンスのスケールアップなどの対応は取りませんでした

# 5秒10秒とはなんだったのか

副作用的に出ちゃったんでしょうね
結果だけ見るとここを掘り下げて調査する必要はなかったのですが、数多の現象からトラブル原因の本質を捉えるのは難しい

# 最後に

冒頭にも書きましたが、現状とくに問題なくてもnodelocal dnscacheは導入していいと思います
あと、awsサポートが使える状況ならガンガン使っていきましょう :muscle: