Reliable_and_Performant_DNS_Resolution_with_High_Available_NodeLocal_DNSCache.pdf

 Reliable_and_Performant_DNS_Resolution_with_High_Available_NodeLocal_DNSCache.pdf

2661b52b92e28d73635638a17c96f4af?s=128

Tsubasa Nagasawa

September 09, 2020
Tweet

Transcript

  1. 1 Reliable and Performant
 DNS Resolution
 with High Available
 NodeLocal

    DNSCache

  2. 2 • 長澤 翼 (Tsubasa Nagasawa)
 • インフラエンジニア
 • 株式会社コロプラ所属

    (2020.3-)
 ◦ ゲームタイトルの GKE クラスター運用と改善
 • 外部登壇
 ◦ Zero Scale Abstraction in Knative Serving
 About me

  3. 3 https://www.reddit.com/r/sysadmin/comments/4oj7pv/network_solutions_haiku/
 
 
 DNS is troublesome
 It’s not DNS

    There’s no way it’s DNS It was DNS - SSBroski
  4. 4 • Kubernetes Failure Stories
 ◦ https://k8s.af/
 ◦ 26% が

    DNS 関連の障害 (50 件中 13 件) 
 • Total DNS outage in Kubernetes cluster (Zalando)
 ◦ https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/docs/postm ortems/jan-2019-dns-outage.md
 DNS is troublesome

  5. 5 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  6. 6 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  7. 7 • 昔からある技術で Internet を支える重要な技術のひとつ
 ◦ Web サービスや Computing リソースなどの識別子


    ◦ 階層構造になっている巨大な分散システムのひとつ
 • ドメイン名に関連付けられた情報 (レコード) を解決
 • レコードには種類がある
 ◦ ドメイン名と IP アドレスの変換 (A/AAAA レコード)
 ▪ e.g.) example.com => 93.184.216.34
 Domain Name System (DNS)

  8. 8 • Cloud Native な世界では Computing リソースは短命
 ◦ Autoscaling Groups

    (AWS)
 ◦ Managed Instance Groups (GCP)
 ◦ Virtual Machine Scale Sets (Azure)
 ◦ ReplicaSets/Deployments (Kubernetes)
 • Pets vs Cattle
 DNS in Cloud Native

  9. 9 • サービス間通信で IP や VIP を指定するのは辛い
 ◦ IP や

    VIP が変わるとサービスの設定変更が必要
 ◦ ドメイン名を使えばサービスの設定変更が不要
 • DNS をベースにしたサービス検出
 ◦ DNS-SD (RFC-6763)
 ◦ Consul
 ◦ Cloud Map (AWS), Service Directory (GCP), ...
 DNS in Cloud Native

  10. 10 • Kubernetes における DNS の仕様
 ◦ Kubernetes DNS-Based Service

    Discovery
 ▪ https://github.com/kubernetes/dns/blob/master/docs/specification.md 
 ◦ 必須ではないコンポーネント
 • 上記の仕様を満たす DNS サービス
 ◦ kube-dns (GKE)
 ◦ CoreDNS (EKS, AKS, ...), ...
 DNS in Kubernetes

  11. 11 • Kubernetes 1.11 からデフォルトの DNS サーバー
 • Golang で有名な

    DNS library を書いている Google の方がリード
 • CoreDNS は機能を plugin として実装
 ◦ 設定ファイルに必要な plugin の情報を記載
 ◦ メインプロセスもライブラリ化されている
 ▪ CoreDNS 外でも使い易い
 参考: CoreDNS

  12. 12 • A/AAAA レコード
 ◦ Service type ClusterIP
  <service>.<ns>.svc.cluster.local. <ttl>

    IN A <cluster-ip>
 ▪ VIP を返却
 ◦ Headless Service
  <service>.<ns>.svc.cluster.local. <ttl> IN A <endpoint-ip>
 ▪ PodIP のリストを返却
 DNS in Kubernetes

  13. 13 • DNS コンポーネントが Kubernetes API から必要な情報を返却
 ◦ 裏側で自動的に処理されるのでユーザーは気にする必要なし
 ◦

    Service が作成されたら svc.cluster.local のレコードを返却
 ◦ Headless Service に紐づいた Pod IP リストの更新も行う
 DNS in Kubernetes

  14. 14 • 各ノードに kubelet がエージェントとして動作
 • kubelet の設定の中に DNS サーバーの情報がある


    ◦ Pod 起動時に DNS サーバーの情報を埋め込み可能
 DNS in Kubernetes
 $ sudo cat /home/kubernetes/kubelet-config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 (...) clusterDNS: - 10.96.0.10 # kube-dns or CoreDNS の VIP clusterDomain: cluster.local (...)
  15. 15 • Pod の /etc/resolve.conf は dnsPolicy で変わる
 ◦ ClusterFirst

    (デフォルト)
 ▪ nameserver に kube-dns or CoreDNS の VIP を指定
 ▪ kubelet で設定した clusterDomain と Pod が存在する namespace の情報か ら search path を動的に生成
 DNS in Kubernetes
 nameserver 10.96.0.10 search kube-system.svc.cluster.local svc.cluster.local cluster.local c.gcp-project-id.internal google.internal options ndots:5
  16. 16 • Fully Qualified Domain Name (FQDN)
 ◦ Top Level

    Domain も含めた完全修飾ドメイン名
 ▪ e.g.) somehost.example.com.
 ◦ Absolute Domain Name とも呼ばれる
 • Partially Qualified Domain Name (PQDN)
 ◦ Top Level Domain まで含めないドメイン名
 ▪ e.g.) somehost.example.com
 ◦ Relative Domain Name とも呼ばれる
 Ndots and search path

  17. 17 • glibc/musl libc などで実装されている DNS resolver のオプション
 ◦ 以下の条件の場合、search

    で指定した検索パスを追加して再帰的に検索
 ▪ 送信先のエンドポイントを FQDN ではなく PQDN で指定
 ▪ PQDN 内のドットの数が ndots より少ない
 Ndots and search path

  18. 18 • e.g.) somehost.example.com
 ◦ search path による自動補完のパターン
 ▪ somehost.example.com.kube-system.svc.cluster.local


    ▪ somehost.example.com.svc.cluster.local
 ▪ somehost.example.com.cluster.local
 ▪ somehost.example.com.c.gcp-project-id.internal
 ▪ somehost.example.com.google.internal
 ◦ IPv4, IPv6 それぞれで余分にクエリを投げる
 ▪ 5 x 2 = 10 queries/resolve
 Ndots and search path

  19. 19 • ndots=5 の背景
 ◦ Explainer on why ndots=5 in

    kubernetes
 • 自動補完の回避方法
 ◦ PQDN ではなく FQDN 指定
 ▪ ‍♂ somehost.example.com.
 ▪ ‍♂ somehost.example.com
 ◦ dnsConfig で設定を上書き
 Ndots and search path

  20. 20 • Pod の /etc/resolve.conf は dnsPolicy で変わる
 ◦ Default


    ▪ GCP の場合、メタデータサーバー (内部 DNS) を向く
 ▪ Pod が動作しているノードの DNS の設定を継承
 ▪ kube-dns で名前解決できない場合 (e.g. kube-dns 自体)
 
 
 ◦ 独自の /etc/resolve.conf を埋め込むことも可能
 DNS in Kubernetes
 nameserver 169.254.169.254 search c.gcp-project-id.internal google.internal
  21. 21 • Kubernetes には DNS ベースのサービス検出の機能がある
 ◦ Kubernetes Service を使った

    L4 負荷分散
 ◦ kube-dns や CoreDNS がデフォルトの DNS キャッシュサーバー
 • Pod の DNS の設定は kubelet が管理
 ◦ デフォルトだと Kubernetes Service の名前解決が可能
 ◦ ndots と search path による自動補完に注意
 ▪ 無駄な DNS クエリによってレイテンシ影響も
 Summary

  22. 22 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  23. 23 • The ins and outs of networking in Google

    Container Engine and Kubernetes (Google Cloud Next '17)
 ◦ https://www.youtube.com/watch?v=y2bhV81MfKQ
 • Life of a Packet [I] - Michael Rubin, Google
 ◦ https://www.youtube.com/watch?v=0Omvgd7Hg1I
 Kubernetes Networking

  24. 24 • Understanding kubernetes networking: services
 ◦ https://medium.com/google-cloud/understanding-kubernetes-networking-service s-f0cb48e4cc82
 •

    Kubernetes and Networks - Why is this so dang hard
 ◦ https://speakerdeck.com/thockin/kubernetes-and-networks-why-is-this-so-dan g-hard
 • kube-proxy iptables "nat" control flow
 ◦ https://docs.google.com/drawings/d/1MtWL8qRTs6PlnJrW4dh8135_S9e2SaawT4 10bJuoBPk/edit
 Kubernetes Networking

  25. 25 • netns と bridge (L2) を使った同一ノード上の Pod 間通信
 Network

    Namespaces
 eth0
 cbr0
 eth0
 eth0
 Pod A netns
 Pod B netns
 vethxx
 vethyy
 iptables
 root netns

  26. 26 • kube-proxy による iptables の書き換え (iptables モード)
 ◦ 各ノード上で

    kube-proxy が動作
 ◦ Kubernetes API 経由で Object の作成/変更/削除を監視 
 ▪ Services
 ▪ Endpoints (or EndpointSlices)
 ◦ kube-proxy をパケットが通過している訳ではない
 • Kubernetes の ClusterIP (VIP) を PodIP に変換
 ◦ iptables (Netfilter) の NAT テーブルのルールに従う
 kube-proxy and iptables

  27. 27 • kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ◦ (1) のルールで PREROUTING

    チェインに入り、KUBE-SERVICES のルールへ移動
 iptables rules for Service
 *nat (1) -A PREROUTING -j KUBE-SERVICES (2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m udp --dport 53 -j KUBE-SVC-A (3) -A KUBE-SVC-A -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-A (4) -A KUBE-SVC-A -j KUBE-SEP-B (5) -A KUBE-SEP-A -p udp -m udp -j DNAT --to-dest 10.32.0.6:53 (6) -A KUBE-SEP-B -p udp -m udp -j DNAT --to-dest 10.32.0.7:53
  28. 28 • kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ◦ 送信先の IP とポートの組み合わせから

    (2) のルールに一致
 ◦ (2) のルールから (3) のルールに移動
 iptables rules for Service
 *nat (1) -A PREROUTING -j KUBE-SERVICES (2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m udp --dport 53 -j KUBE-SVC-A (3) -A KUBE-SVC-A -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-A (4) -A KUBE-SVC-A -j KUBE-SEP-B (5) -A KUBE-SEP-A -p udp -m udp -j DNAT --to-dest 10.32.0.6:53 (6) -A KUBE-SEP-B -p udp -m udp -j DNAT --to-dest 10.32.0.7:53
  29. 29 • kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ◦ (3) のルールにマッチする確率が 50%

    で、マッチすると (5) のルールに移動して 10.32.0.6:53 に DNAT される
 iptables rules for Service
 *nat (1) -A PREROUTING -j KUBE-SERVICES (2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m udp --dport 53 -j KUBE-SVC-A (3) -A KUBE-SVC-A -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-A (4) -A KUBE-SVC-A -j KUBE-SEP-B (5) -A KUBE-SEP-A -p udp -m udp -j DNAT --to-dest 10.32.0.6:53 (6) -A KUBE-SEP-B -p udp -m udp -j DNAT --to-dest 10.32.0.7:53
  30. 30 • kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ◦ (3) のルールにマッチしない場合は、(4) のルールに移動して

    (6) のルールで DNAT される
 iptables rules for Service
 *nat (1) -A PREROUTING -j KUBE-SERVICES (2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m udp --dport 53 -j KUBE-SVC-A (3) -A KUBE-SVC-A -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-A (4) -A KUBE-SVC-A -j KUBE-SEP-B (5) -A KUBE-SEP-A -p udp -m udp -j DNAT --to-dest 10.32.0.6:53 (6) -A KUBE-SEP-B -p udp -m udp -j DNAT --to-dest 10.32.0.7:53
  31. 31 • kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ◦ iptables のルールにより送信先の IP

    が DNAT される
 ◦ ClusterIP への通信は基本的に DNAT される
 ◦ iptables のルールでランダムに PodIP が選ばれる
 • iptables の NAT テーブルのルールに引っ掛かると...
 ◦ conntrack テーブルで接続情報を管理
 Summary

  32. 32 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  33. 33 • Racy conntrack and DNS lookup timeouts
 ◦ https://www.weave.works/blog/racy-conntrack-and-dns-lookup-timeouts

    
 • 5 – 15s DNS lookups on Kubernetes?
 ◦ https://blog.quentin-machu.fr/2018/06/24/5-15s-dns-lookups-on-kubernetes/
 • A reason for unexplained connection timeouts on Kubernetes/Docker
 ◦ https://tech.xing.com/a-reason-for-unexplained-connection-timeouts-on-kubernetes-docke r-abd041cf7e02
 Conntrack Races (in Kubernetes)

  34. 34 • NAT テーブルのルールに一致すると conntrack で接続情報を管理
 ◦ パケットの 5-tuple: protocol,

    src IP/port and dst IP/port
 ◦ 2 つの tuple を conntrack で管理
 ▪ 送信パケットの tuple (IP_CT_DIR_ORIGINAL)
 ▪ 応答パケットの tuple (IP_CT_DIR_REPLY)
 ◦ 2 つの tuple が一致するパケットは同一コネクションに属する
 NAT and conntrack
 $ conntrack -E [UPDATE] tcp 6 30 LAST_ACK src=10.96.0.17 dst=169.254.169.254 sport=41606 dport=53 src=169.254.169.254 dst=10.96.0.17 sport=53 dport=41606 [ASSURED]
  35. 35 • Pod A (10.56.0.6) から ClusterIP (10.96.0.10) への通信を考える 


    ◦ 10.96.0.10:53 が 10.32.0.6:53 に変換されたとすると...
 ◦ 送信パケットの tuple (IP_CT_DIR_ORIGINAL)
 ▪ protocol=udp, src=10.56.0.20 dst=10.96.0.10 sport=53378 dport=53
 ◦ 応答パケットの tuple (IP_CT_DIR_REPLY)
 ▪ protocol=udp, src=10.32.0.6 dst=10.56.0.20 sport=53 dport=53378 
 ◦ 上記の情報があれば iptables を辿る必要がない
 NAT and conntrack

  36. 36 • 接続情報はすぐに conntrack テーブルに保存される訳ではない
 ◦ unconfirmed テーブルの存在
 • nf_conntrack_in


    ◦ PREROUTING のフックで DNAT 前の接続情報を unconfirmed に保持
 • nf_conntrack_confirm
 ◦ INPUT のフックで接続情報を unconfirmed から破棄して、conntrack に接続情報を 追加
 Conntrack and Unconfirmed Table

  37. 37 1. unconfirmed テーブルに接続情報を追加
 ◦ 応答パケットの tuple は送信パケットの tuple を反転させたもの


    2. iptables の NAT テーブルのルールを辿る
 3. DNAT のルールで応答パケットの送信元の情報を更新
 4. 3. の情報をもとにパケットの送信先 IP とポートを変換
 5. unconfirmed テーブルから接続情報を削除し、同一の送信パケット or 応答パ ケットの tuple が conntrack テーブルになければ追加
 ◦ 既に存在する場合はパケット破棄して insert_failed++
 Conntrack Insertion Flow

  38. 38 • 前提知識
 ◦ glibc/musl libc で DNS クエリは A/AAAA

    レコードのクエリを並列に実行
 ◦ UDP パケットは connect() で接続情報が conntrack に残らない
 ◦ UDP パケットは send() で初めてパケットが飛んで接続情報が残る
 • 競合する原因
 ◦ 同一プロセスからの複数の UDP パケットが同一ソケットに同時に入る
 ▪ DNS クエリは conntrack races の影響を受けやすい
 Possible Conntrack Races

  39. 39 • DNS サーバーに初めてクエリを発行する場合
 1. unconfirmed テーブルに接続情報を保存
 2. iptables の

    NAT テーブルのルールを辿る
 3. DNAT のルールにより応答パケットの送信元の情報を変換
 4. 3. の情報をもとにパケットの送信先 IP とポートを変換
 5. どちらかのパケットが先に conntrack テーブルに追加
 ◦ 遅れたパケットは破棄されて insert_failed++
 Possible Conntrack Races Case (1)

  40. 40 • DNS サーバーに初めてクエリを発行する場合
 1. unconfirmed テーブルに接続情報を保存
 2. iptables の

    NAT テーブルのルールを辿ってる間に、片方のパケットの接続情報が conntrack テーブルに追加される
 3. DNAT のルール適用しようとするが、unconfirmed テーブルに接続情報がないの で、別の応答パケットの tuple を使う
 4. 3. の情報をもとにパケットの送信先 IP とポートを変換
 5. 送信パケットの tuple が同じ接続情報が存在するので、パケットを破棄して insert_failed++
 Possible Conntrack Races Case (2)

  41. 41 • DNS サーバーに初めてクエリを発行する場合
 1. unconfirmed テーブルに接続情報を保存
 2. iptables の

    NAT テーブルのルールを辿り、異なる DNAT のルールに移動
 3. 異なる DNAT のルールにより応答パケットの送信元の情報を異なる DNS サー バーの情報に変換
 4. 3. の情報をもとにパケットの送信先 IP とポートを変換
 5. 送信パケットの tuple が同じ接続情報が存在するので、パケット破棄して insert_failed++
 Possible Conntrack Races Case (3)

  42. 42 • [v2] netfilter: nf_conntrack: resolve clash for matching conntracks


    ◦ conntrack テーブルに既に送信 or 応答パケットの tuple が存在
 ▪ 競合に勝った (winner) パケットと競合に負けた (loser) パケット
 ▪ winner パケットの接続情報が loser パケットに一致する場合、問答無用でパ ケットを破棄する必要はない
 • winner パケットと同じルールで loser パケットも DNAT されているので問 題ない
 Fix for Conntrack Races (1)

  43. 43 • [v2] netfilter: nf_conntrack: resolve clash for matching conntracks


    Fix for Conntrack Races (1)
 /* Resolve race on insertion if this protocol allows this. */ static int nf_ct_resolve_clash(...) { ... if (... || nf_ct_match(ct, loser_ct)) { nf_ct_acct_merge(ct, ctinfo, loser_ct); nf_conntrack_put(&loser_ct->ct_general); nf_ct_set(skb, ct, oldinfo); return NF_ACCEPT; } ... }
  44. 44 • [nf] netfilter: nf_nat: skip nat clash resolution for

    same-origin entries
 ◦ winner パケットの接続情報が既に conntrack テーブルに存在
 ◦ loser パケットの応答パケットと winner パケットの応答パケットが一致した場合に、 ポートの再割り当てせずに処理を続ける
 ◦ Fix (1) で修正した nf_ct_resolve_clash で正しく処理させる
 Fix for Conntrack Races (2)

  45. 45 • [nf] netfilter: nf_nat: skip nat clash resolution for

    same-origin entries
 Fix for Conntrack Races (2)
 int nf_conntrack_tuple_taken(...) { ... if (nf_ct_key_equal(h, tuple, zone, net)) { * If the *original tuples* are identical, then both * conntracks refer to the same flow. * * Let nf_ct_resolve_clash() deal with this later. if (nf_ct_tuple_equal(&ignored_conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple)) continue; ... }
  46. 46 • iptables の NAT テーブルのルールに引っ掛かると...
 ◦ conntrack テーブルで接続情報を管理し、後続のパケットの処理を高速化
 •

    conntrack テーブルに接続情報を保存する時...
 ◦ 3 条件で競合が発生してパケットを破棄
 ◦ TCP の場合は再送処理が発生するが、UDP の場合は再送処理が発生しない
 • conntrack races は Kubernetes 固有の問題ではない
 ◦ Linux 上の同一プロセスから複数パケットを並列送信で発生
 ◦ 競合 2 条件の修正はマージ済み (4.19.1 と 4.19.29)
 Summary

  47. 47 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  48. 48 • Kubernetes の世界で起こる名前解決の問題の緩和策
 • NAT テーブルに囚われる DNS クエリを減らすことが大目的
 ◦

    名前解決のレイテンシ改善
 ◦ 名前解決のエラー率の軽減
 ▪ conntrack races によるパケット破棄の軽減
 ▪ パケット破棄時の DNS クエリのタイムアウト時間短縮
 • UDP の DNS クエリのタイムアウト時間 (30 sec)
 ▪ conntrack table の肥大化を回避
 Why NodeLocal DNSCache

  49. 49 • CoreDNS ベースの DNS キャッシュサーバー
 • 本セッションでは local-dns と呼びます


    • DaemoSet として各ノードに存在
 • kubernetes/dns で開発中
 ◦ Kubernetes v1.18 で GA
 ◦ GKE でも add-on が GA 済み
 ◦ Google の方がメンテナー (ingress-gce の中の人でもある)
 NodeLocal DNSCache

  50. 50 • 現状の問題と緩和/解決策
 ◦ 高負荷な Pod の DNS クエリに kube-dns

    が耐えられない
 ▪ 各ノード上に DNS キャッシュサーバーを置く
 ▪ ネガティブキャッシュの有効化
 ◦ conntrack races の発生
 ▪ DNS クエリが DNAT されないようにすることで緩和
 ◦ conntrack テーブルの枯渇
 ▪ DNS クエリが DNAT されないようにすることで軽減
 NodeLocal DNSCache

  51. 51 • 現状の問題と緩和/解決策
 ◦ conntrack races によるタイムアウト時間の短縮
 ▪ DNS キャッシュサーバから

    kube-dns へのクエリを TCP に更新
 ◦ DNS 関連の問題のデバッグが難しい
 ▪ ノードレベルのメトリクス取得
 NodeLocal DNSCache

  52. 52 • 本番環境のメトリクスをお見せできないので擬似的に再現します
 ◦ chrisohaver/dnsdrone を改造したツールで 500 rps の負荷を掛ける
 ◦

    jwkohnen/conntrack-stats-exporter で insert_failed のカウンタ取得
 ◦ conntrack テーブルのサイズ: 2097152
 ◦ ノードのカーネルバージョン
 
 
 ◦ conntrack table の使用率の比較だけでも...
 Performance Comparison
 $ kubectl get nodes ****** -o jsonpath="{.status.nodeInfo.kernelVersion}" 5.3.0-1016-gke
  53. 53 kube-dns


  54. 54 local-dns


  55. 55 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  56. 56 • 既存の kube-dns の ClusterIP に変更を加えずに再利用する工夫
 ◦ kubelet の設定変更と再起動を避けたい


    ◦ ClusterIP へのパケットを local-dns に向ける
 ▪ メインプロセスとは別に起動終了時に処理を追加
 • DNS クエリを conntrack に捕捉させない
 • Pluggable な CoreDNS のアーキテクチャの活用
 • ホストネットワークの利用
 Implementation Overview

  57. 57 • ClusterIP へのパケットを local-dns に向けるには?
 ◦ ノード上にダミーのネットワークインターフェイスを作成
 ◦ kube-dns

    の ClusterIP を割り当て
 ◦ local-dns のメインプロセスをそのソケットに繋げる
 ◦ iptables ルールを作成して NAT されないようにする
 ◦ iptables に更にルールを追加してパケットをソケットに向ける
 ◦ local-dns はあくまで DNS キャッシュサーバー
 ▪ キャッシュミス時の挙動を考える
 Implementation Tricks

  58. 58 • キャッシュミス時に kube-dns に問い合わせるので...
 ◦ kube-dns を向く Service を新たに作成


    Prerequisites
 apiVersion: v1 kind: Service metadata: name: kube-dns-uncached namespace: kube-system spec: type: ClusterIP selector: k8s-app: kube-dns (...)
  59. 59 • ノード上にダミーの仮想ネットワークインターフェイスを作成
 ◦ デフォルトの DNS サーバーの VIP (ClusterIP) を割り当て


    Binding to Dummy Interface
 func (c *CacheApp) setupNetworking() { (...) exists, err := c.netifHandle.EnsureDummyDevice(c.params.InterfaceName) if !exists { (...) } if err != nil { (...) } }
  60. 60 • ノード上にダミーの仮想ネットワークインターフェイスを作成
 ◦ デフォルトの DNS サーバーの VIP (ClusterIP) を割り当て


    ◦ hostNetwork を有効化して接続可能にする必要あり
 Binding to Dummy Interface
 $ sudo ip addr list nodelocaldns (...) 38: nodelocaldns: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default link/ether 4e:b0:eb:e8:19:e2 brd ff:ff:ff:ff:ff:ff inet 10.96.0.10/32 brd 10.96.0.10 scope global nodelocaldns valid_lft forever preferred_lft forever
  61. 61 • local-dns のメインプロセスを仮想 NIC のソケットに紐付け
 ◦ CoreDNS の bind

    plugin を利用
 Binding to Dummy Interface
 cluster.local:53 { (...) bind 10.96.0.10 forward . 10.96.0.35 { force_tcp } (...) }
  62. 62 • iptables のルールを追加
 ◦ RAW テーブルに NOTRACK ルールを追加
 ▪

    ノード上の Pod や外部の Pod から local-dns へのパケットに NAT を発生さ せないためのルール
 ▪ UDP/TCP どちらも設定 (以下は TCP のルールのみ記載)
 Untrackable iptables rules
 *raw -A PREROUTING -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --dport 8080 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 8080 -j NOTRACK
  63. 63 • iptables のルールを追加
 ◦ RAW テーブルに NOTRACK ルールを追加
 ▪

    hostNetwork が有効な Pod などノード上で生成したパケットに NAT を発生さ せないためのルール 
 ▪ UDP/TCP どちらも設定 (以下は TCP のルールのみ記載)
 Untrackable iptables rules
 *raw -A PREROUTING -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --dport 8080 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 8080 -j NOTRACK
  64. 64 • iptables のルールを追加
 ◦ RAW テーブルに NOTRACK ルールを追加
 ▪

    local-dns からのパケットに対して NAT を発生させないためのルール
 ▪ UDP/TCP どちらも設定 (以下は TCP のルールのみ記載)
 Untrackable iptables rules
 *raw -A PREROUTING -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --dport 8080 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 8080 -j NOTRACK
  65. 65 • iptables のルールを追加
 ◦ RAW テーブルに NOTRACK ルールを追加
 ▪

    kubelet から local-dns に対する livenessProbe で設定したヘルスチェクとそ の返答に NAT を発生させないためのルール
 ▪ UDP/TCP どちらも設定 (以下は TCP のルールのみ記載)
 Untrackable iptables rules
 *raw -A PREROUTING -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -d 10.96.0.10/32 -m tcp --dport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --dport 8080 -j NOTRACK -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 8080 -j NOTRACK
  66. 66 • iptables のルールを追加
 ◦ FILTER テーブルに ACCEPT ルールを追加
 ▪

    NOTRACK で NAT を回避したパケットをノード上のソケット宛に流すことを許 可するルール
 ▪ UDP/TCP どちらも設定
 Untrackable iptables rules
 *filter -A INPUT -d 10.96.0.10/32 -m udp --dport 53 -j ACCEPT -A INPUT -d 10.96.0.10/32 -m tcp --dport 53 -j ACCEPT -A OUTPUT -s 10.96.0.10/32 -m udp --sport 53 -j ACCEPT -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j ACCEPT
  67. 67 • iptables のルールを追加
 ◦ FILTER テーブルに ACCEPT ルールを追加
 ▪

    NOTRACK で NAT を回避した local-dns からのパケットを許可するルール
 ▪ UDP/TCP どちらも設定
 Untrackable iptables rules
 *filter -A INPUT -d 10.96.0.10/32 -m udp --dport 53 -j ACCEPT -A INPUT -d 10.96.0.10/32 -m tcp --dport 53 -j ACCEPT -A OUTPUT -s 10.96.0.10/32 -m udp --sport 53 -j ACCEPT -A OUTPUT -s 10.96.0.10/32 -m tcp --sport 53 -j ACCEPT
  68. 68 • Pod A が DNS クエリを発行した場合 (without local-dns)
 1.

    Pod が DNS クエリを発行
 2. Pod の eth0 -> bridge (cbr0) を通ってノードにパケットが移動
 3. ノード上に設定された iptables のルールをスキャン
 4. NAT テーブルの KUBE-SERVICES のルールに一致
 5. 送信先 IP を VIP から Pod IP に DNAT
 6. 同一ノード上であれば bridge (cbr0) に戻って kube-dns に到達
 7. 別ノード上にあればノードの eth0 から外に出て...
 Packet Flow without local-dns

  69. 69 • Pod A が DNS クエリを発行した場合
 1. Pod が

    DNS クエリを発行
 2. Pod の eth0 -> bridge (cbr0) を通ってノードにパケットが移動
 3. ノード上に設定された iptables のルールをスキャン
 4. RAW テーブルの NOTRACK のルールに一致
 5. FILTER テーブルの ACCEPT のルールに一致
 6. ノード上の <ClusterIP>:53 で listen しているソケットに到達
 7. CoreDNS のメインプロセスが名前解決の結果を返答...
 Packet Flow with local-dns

  70. 70 • local-dns のキャッシュミス時に kube-dns に転送
 ◦ NOTRACK ルールのせいで既存の ClusterIP

    は使えない
 ◦ ClusterIP を新規作成して forward plugin を利用
 Cache Missing Behavior
 cluster.local:53 { (...) bind 10.96.0.10 forward . 10.96.0.35 { force_tcp } (...) }
  71. 71 • local-dns のキャッシュミス時に kube-dns に転送
 ◦ force_tcp オプションを有効化
 ◦

    パケット破棄時のタイムアウト時間を短縮
 Cache Missing Behavior
 cluster.local:53 { (...) bind 10.96.0.10 forward . 10.96.0.35 { force_tcp } (...) }
  72. 72 • kube-dns の ClusterIP へのパケットを local-dns に向ける
 ◦ 仮想

    NIC の作成と ClusteIP の紐付け
 ◦ iptables のルールの追加
 ▪ RAW テーブルの NOTRACK ルールによる NAT の回避
 ▪ FILTER テーブルの ACCEPT ルールによるパケットの受入れ
 • キャッシュミス時に kube-dns にフォールバック
 ◦ force_tcp によるパケット破棄時のコネクション維持を短縮
 Summary

  73. 73 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  74. 74 • コロプラはマルチクラスター戦略
 ◦ Kubernetes Master コンポーネントの負荷軽減
 ◦ アプリケーションのデプロイ時間の短縮
 ◦

    クラスター切り替えによる無停止 GKE アップグレード
 ▪ トラフィック制御が容易な Global Load Balancer (HAProxy)
 ◦ 障害時の Blast Radius の最小化
 Multi-cluster Strategy

  75. 75 • コロプラはマルチクラスター戦略
 Multi-cluster Strategy


  76. 76 • コロプラはマルチクラスター戦略
 Multi-cluster Strategy
 app -> backend のクラスター間通信


  77. 77 • app -> backend のクラスター間の名前解決
 ◦ backend クラスターの Pod

    のサービス検出
 ▪ 名前解決の処理は backend クラスタで行う
 ▪ データストアの PodIP リストは Headless Service で取得
 ▪ app クラスタ側で発生した DNS クエリを backend クラスタに転送
 ▪ PodIP リストは順序が固定なので振り分ける機構が必要
 ◦ local-dns で当時未サポートだった CoreDNS plugin たち...
 ▪ local-dns の独自実装へ
 Communication between multi-cluster

  78. 78 Communication between multi-cluster


  79. 79 • CoreDNS
 ◦ https://github.com/colopl/expose-kubedns
 ◦ デフォルトの kube-dns とは別物
 ◦

    cluster-proportional-autoscaler でクラスターサイズでスケール
 ◦ Internal TCP Load Balancer でクラスター外に公開
 ◦ kubernetes plugin で Service の名前解決
 ▪ データストアは Headless Service で公開
 ▪ データストアの PodIP のリストを返却
 Communication between multi-cluster

  80. 80 • local-dns
 ◦ https://github.com/colopl/k8s-local-dns
 ◦ forward plugin で Headless

    Service の名前解決を backend クラスタの CoreDNS に転送
 ▪ Internal TCP Load Balancer の IP に転送して CoreDNS に振り分け
 ◦ loadbalance plugin で PodIP のリストをシャッフル
 ◦ cache plugin でレコードの結果をキャッシュ
 ▪ prefetch option でキャッシュ消滅のタイミングずらす
 Communication between multi-cluster

  81. 81 • local-dns
 ◦ cluster.local 以外のドメインへの DNS クエリは forward plugin

    で/etc/resolve.conf の設定に転送
 ▪ local-dns は dnsPolicy が Default に設定されているので、DNS クエリが内部 DNS に向けられる
 Communication between multi-cluster

  82. 82 • local-dns の内部実装のトリック
 ◦ kube-dns の ClusterIP と同じ IP

    で仮想 NIC を作成
 ◦ iptables のルールで kube-dns へのクエリをローカルのソケットに向ける
 • マルチクラスターでのサービス検出
 ◦ local-dns から backend の CoreDNS に内部 TCP LB 経由でクエリを流す
 ◦ backend クラスターの CoreDNS が kubernetes plugin でサービス検出
 ◦ データストアへの接続が偏らないように loadbalance plugin で     Headless Service による Pod IP リストをシャッフル
 Summary

  83. 83 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl specific NodeLocal DNSCache implementation
 • High available NodeLocal DNSCache
 • Future works
 Table of Contents

  84. 84 • local-dns が単一障害点
 ◦ 各ノードに 1 台しか起動しない
 ▪ ホストネットワークを利用しているため、同一ホスト上で複数の

    local-dns のプ ロセス (ポート) を同時に起動できない
 ◦ DaemonSet の RollingUpdate は maxSurge を未サポート
 ▪ 気軽に local-dns のイメージ更新などができない
 Single Point of Failure

  85. 85 • local-dns が単一障害点
 ◦ local-dns 停止時に仮想 NIC や iptables

    のルールを削除
 ▪ OOMKill などでプロセスが異常終了した場合に、teardown の処理が行われ ず DNS クエリの名前解決に失敗してしまう
 ▪ そもそも...コロプラの使い方だと、teardown 処理でデフォルトの kube-dns に フォールバックしても backend と疎通できず障害になる
 Single Point of Failure

  86. 86 • Linux 3.9 からの機能である SO_REUSEPORT を使う
 ◦ ノード上に複数の local-dns

    のプロセスを起動
 ◦ 同一ポート (:53) にプロセスを紐付ける
 ◦ 複数のプロセスに処理を振り分け
 ◦ CoreDNS のメインプロセスは SO_REUSEPORT をサポート
 ▪ SO_REUSEPORT 利用するためのフラグなどは不要
 ◦ local-dns 停止時の teardown の処理をスキップ
 ▪ k8s.gcr.io/k8s-dns-node-cache:1.15.13 以降を使う
 High Available Setup

  87. 87 • 複数台起動する local-dns のマニフェスト例
 ◦ https://github.com/kubernetes/dns/issues/324#issuecomment-595314169
 ◦ 53 番ポートの

    containerPort と hostPort の重複設定に注意
 ▪ 最初の DamonSet で指定しているので設定不要
 ◦ local-dns 起動時に --skip-teardown のフラグを立てること
 ◦ healthcheck/metrics 用のポートは別のポートを指定すること
 High Available Setup

  88. 88 • local-dns の HA 構成
 ◦ SO_REUSEPORT を使って複数のプロセスを同一のポートに紐付け
 ◦

    DNS の処理も複数のプロセスにオフロードされる
 ◦ local-dns の teardown 処理をスキップするフラグを立て忘れないように
 ◦ GKE の add-on の場合どうなっているんだろう...
 Summary

  89. 89 • Kubernetes DNS 101
 • Kubernetes Networking 101
 •

    Conntrack races
 • NodeLocal DNSCache
 • Deep dive into NodeLocal DNSCache
 • Colopl Specific Implementation
 • High Available NodeLocal DNSCache
 • Future works
 Table of Contents

  90. 90 • 公式と独自実装のイメージの違いは読み込んでいる CoreDNS plugin
 ◦ 1.15.13 以降の公式イメージでマルチクラスター構成を可能にする CoreDNS plugin

    が全て読み込まれたので、いつでも移行可能
 ▪ loadbalance #344
 ▪ template #374
 Migrate to Official local-dns image

  91. 91 • GKE で未だに kube-dns を使っている理由のひとつ
 ◦ https://issuetracker.google.com/issues/119256507#comment5
 ◦ CoreDNS

    の高負荷時のメモリ使用量の問題
 ▪ upstream の DNS サーバーの処理限界を突破してスロークエリが発生し、 CoreDNS のバッファが溜まりメモリ消費が増加
 ▪ CoreDNS 1.6.9 で forward plugin に max_concurrent option 追加
 ▪ max_concurrent を超えた並列処理数に到達すると SERVFAIL 返却
 ▪ 並列処理に 2 kb のメモリーを使うとして計算
 More optimizations in CoreDNS

  92. 92 • ノードがスケールアウトする時の local-dns の起動順の落とし穴
 ◦ HPA による急激な app のスケールアウト


    ◦ Cluster Autoscaler がノードをスケールアウト
 ◦ Pending 状態の app が先にノードにスケジューリング
 ◦ local-dns のスケジューリングが 20-30 秒程度遅れる
 ▪ 通常の負荷ででは app と local-dns のスケジューリング時刻は同じ
 ◦ local-dns より app が先にサービスインして 1 分程度障害
 DaemonSet Pod Scheduling Order

  93. 93 • local-dns のスケジューリングが遅れた原因
 ◦ DaemonSet は Node が Ready

    にならないとスケジューリングされない
 ▪ local-dns は DaemonSet
 ◦ 急激なノードのスケールアウトで Master ノードに負荷が掛かり、Node が Ready かの判断に遅れが生じた?
 DaemonSet Pod Scheduling Order

  94. 94 • 導入予定の対策
 ◦ そもそも... app に local-dns が準備完了かのチェックができていなかった
 ◦

    Kubernetes には Pod の依存関係 (起動順) を指定する機能がない
 ▪ initContainers, コンテナのメインプロセスの初期化処理中, readinessProbe で ゴニョゴニョするしかない
 ◦ backend クラスターの Headless Service の名前解決ができるまで一時停止する処 理を追加予定
 DaemonSet Pod Scheduling Order

  95. 95 • search path による拡張ロジックを DNS サーバー側に持つ KEP
 ◦ https://github.com/kubernetes/enhancements/pull/967


    ◦ dnsPolicy に新しく autopath を有効化するポリシーを追加
 ◦ EDNS0 (DNS の拡張プロトコル) に必要な情報を詰め込む
 ▪ local-dns から upstream nameserver に投げる時に埋め込む予定
 ▪ local-dns が死ぬと名前解決できないので HA 構成必須
 DNS Autopath in Pod API

  96. 96 • クラスターの壁を超えて Service を利用可能に
 ◦ ServiceExport で他クラスターに公開する Service を選択


    ◦ ServiceImport で他クラスターから取り込む Service を選択
 ◦ <service>.<namespace>.svc.clusterset.local でサービス検出可能
 • API 仕様を決めるだけで公式のコントローラー実装はない
 ◦ API 仕様は CRD で公開、Kubernetes のリリースに依存せず利用可能
 ◦ CoreDNS などを使って独自実装 or サードパーティ製品
 Multi-cluster Services API

  97. 97 • API 仕様を決めるだけで公式のコントローラー実装はない
 ◦ Submariner プロジェクトの lighthouse がサポート予定
 ▪

    https://github.com/submariner-io/lighthouse/issues/78
 • local-dns からマルチクラスターサービス検出を切り離せるなら、GKE add-on の local-dns への移行も可能に
 ◦ https://cloud.google.com/kubernetes-engine/docs/how-to/nodelocal-dns-cache
 Multi-cluster Services API

  98. 98 • DNS は Kubernetes の世界でも問題児で難しい
 ◦ 気づかずに影響を受けていることもある
 • Kubernetes

    に限らず conntrack races の問題には注意
 ◦ ローカルに DNS キャッシュサーバーを持つという選択肢
 ◦ local-dns はその緩和策の一つ
 • クラスターを跨いだ DNS ベースのサービス検出
 ◦ Multi-cluster Services API 楽しみ
 Summary

  99. 99 • KubeCon Europe 2020 の DNS 周りのより実用的な話
 ◦ Kubernetes

    DNS Horror Stories (And How to Avoid Them)
 ▪ https://www.youtube.com/watch?v=Yq-SVNa_W5E
 Summary

  100. 100 • コロプラでは Kubernetes エンジニアを積極採用中です!!
 ◦ GKE, Cloud Spanner を使ったゲームや基盤の開発


    ◦ Kubernetes のエコシステムを含めた Cloud Native への取り組み
 We are Hiring!
 コロプラ 採用 検索 ▼詳しくは https://be-ars.colopl.co.jp/recruit/career/engineer/corporate-business-support/kubernetes.html
  101. 101 Thank you for listening!!!