Slide 1

Slide 1 text

1 Reliable and Performant
 DNS Resolution
 with High Available
 NodeLocal DNSCache


Slide 2

Slide 2 text

2 ● 長澤 翼 (Tsubasa Nagasawa)
 ● インフラエンジニア
 ● 株式会社コロプラ所属 (2020.3-)
 ○ ゲームタイトルの GKE クラスター運用と改善
 ● 外部登壇
 ○ Zero Scale Abstraction in Knative Serving
 About me


Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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


Slide 5

Slide 5 text

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


Slide 6

Slide 6 text

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


Slide 7

Slide 7 text

7 ● 昔からある技術で Internet を支える重要な技術のひとつ
 ○ Web サービスや Computing リソースなどの識別子
 ○ 階層構造になっている巨大な分散システムのひとつ
 ● ドメイン名に関連付けられた情報 (レコード) を解決
 ● レコードには種類がある
 ○ ドメイン名と IP アドレスの変換 (A/AAAA レコード)
 ■ e.g.) example.com => 93.184.216.34
 Domain Name System (DNS)


Slide 8

Slide 8 text

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


Slide 9

Slide 9 text

9 ● サービス間通信で IP や VIP を指定するのは辛い
 ○ IP や VIP が変わるとサービスの設定変更が必要
 ○ ドメイン名を使えばサービスの設定変更が不要
 ● DNS をベースにしたサービス検出
 ○ DNS-SD (RFC-6763)
 ○ Consul
 ○ Cloud Map (AWS), Service Directory (GCP), ...
 DNS in Cloud Native


Slide 10

Slide 10 text

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


Slide 11

Slide 11 text

11 ● Kubernetes 1.11 からデフォルトの DNS サーバー
 ● Golang で有名な DNS library を書いている Google の方がリード
 ● CoreDNS は機能を plugin として実装
 ○ 設定ファイルに必要な plugin の情報を記載
 ○ メインプロセスもライブラリ化されている
 ■ CoreDNS 外でも使い易い
 参考: CoreDNS


Slide 12

Slide 12 text

12 ● A/AAAA レコード
 ○ Service type ClusterIP
  ..svc.cluster.local. IN A 
 ■ VIP を返却
 ○ Headless Service
  ..svc.cluster.local. IN A 
 ■ PodIP のリストを返却
 DNS in Kubernetes


Slide 13

Slide 13 text

13 ● DNS コンポーネントが Kubernetes API から必要な情報を返却
 ○ 裏側で自動的に処理されるのでユーザーは気にする必要なし
 ○ Service が作成されたら svc.cluster.local のレコードを返却
 ○ Headless Service に紐づいた Pod IP リストの更新も行う
 DNS in Kubernetes


Slide 14

Slide 14 text

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 (...)

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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


Slide 17

Slide 17 text

17 ● glibc/musl libc などで実装されている DNS resolver のオプション
 ○ 以下の条件の場合、search で指定した検索パスを追加して再帰的に検索
 ■ 送信先のエンドポイントを FQDN ではなく PQDN で指定
 ■ PQDN 内のドットの数が ndots より少ない
 Ndots and search path


Slide 18

Slide 18 text

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


Slide 19

Slide 19 text

19 ● ndots=5 の背景
 ○ Explainer on why ndots=5 in kubernetes
 ● 自動補完の回避方法
 ○ PQDN ではなく FQDN 指定
 ■ ‍♂ somehost.example.com.
 ■ ‍♂ somehost.example.com
 ○ dnsConfig で設定を上書き
 Ndots and search path


Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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


Slide 22

Slide 22 text

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


Slide 23

Slide 23 text

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


Slide 24

Slide 24 text

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


Slide 25

Slide 25 text

25 ● netns と bridge (L2) を使った同一ノード上の Pod 間通信
 Network Namespaces
 eth0
 cbr0
 eth0
 eth0
 Pod A netns
 Pod B netns
 vethxx
 vethyy
 iptables
 root netns


Slide 26

Slide 26 text

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


Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

31 ● kube-dns (ClusterIP, 10.96.0.10/32) への通信
 ○ iptables のルールにより送信先の IP が DNAT される
 ○ ClusterIP への通信は基本的に DNAT される
 ○ iptables のルールでランダムに PodIP が選ばれる
 ● iptables の NAT テーブルのルールに引っ掛かると...
 ○ conntrack テーブルで接続情報を管理
 Summary


Slide 32

Slide 32 text

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


Slide 33

Slide 33 text

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)


Slide 34

Slide 34 text

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]

Slide 35

Slide 35 text

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


Slide 36

Slide 36 text

36 ● 接続情報はすぐに conntrack テーブルに保存される訳ではない
 ○ unconfirmed テーブルの存在
 ● nf_conntrack_in
 ○ PREROUTING のフックで DNAT 前の接続情報を unconfirmed に保持
 ● nf_conntrack_confirm
 ○ INPUT のフックで接続情報を unconfirmed から破棄して、conntrack に接続情報を 追加
 Conntrack and Unconfirmed Table


Slide 37

Slide 37 text

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


Slide 38

Slide 38 text

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


Slide 39

Slide 39 text

39 ● DNS サーバーに初めてクエリを発行する場合
 1. unconfirmed テーブルに接続情報を保存
 2. iptables の NAT テーブルのルールを辿る
 3. DNAT のルールにより応答パケットの送信元の情報を変換
 4. 3. の情報をもとにパケットの送信先 IP とポートを変換
 5. どちらかのパケットが先に conntrack テーブルに追加
 ○ 遅れたパケットは破棄されて insert_failed++
 Possible Conntrack Races Case (1)


Slide 40

Slide 40 text

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


Slide 41

Slide 41 text

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


Slide 42

Slide 42 text

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)


Slide 43

Slide 43 text

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; } ... }

Slide 44

Slide 44 text

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)


Slide 45

Slide 45 text

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; ... }

Slide 46

Slide 46 text

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


Slide 47

Slide 47 text

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


Slide 48

Slide 48 text

48 ● Kubernetes の世界で起こる名前解決の問題の緩和策
 ● NAT テーブルに囚われる DNS クエリを減らすことが大目的
 ○ 名前解決のレイテンシ改善
 ○ 名前解決のエラー率の軽減
 ■ conntrack races によるパケット破棄の軽減
 ■ パケット破棄時の DNS クエリのタイムアウト時間短縮
 ● UDP の DNS クエリのタイムアウト時間 (30 sec)
 ■ conntrack table の肥大化を回避
 Why NodeLocal DNSCache


Slide 49

Slide 49 text

49 ● CoreDNS ベースの DNS キャッシュサーバー
 ● 本セッションでは local-dns と呼びます
 ● DaemoSet として各ノードに存在
 ● kubernetes/dns で開発中
 ○ Kubernetes v1.18 で GA
 ○ GKE でも add-on が GA 済み
 ○ Google の方がメンテナー (ingress-gce の中の人でもある)
 NodeLocal DNSCache


Slide 50

Slide 50 text

50 ● 現状の問題と緩和/解決策
 ○ 高負荷な Pod の DNS クエリに kube-dns が耐えられない
 ■ 各ノード上に DNS キャッシュサーバーを置く
 ■ ネガティブキャッシュの有効化
 ○ conntrack races の発生
 ■ DNS クエリが DNAT されないようにすることで緩和
 ○ conntrack テーブルの枯渇
 ■ DNS クエリが DNAT されないようにすることで軽減
 NodeLocal DNSCache


Slide 51

Slide 51 text

51 ● 現状の問題と緩和/解決策
 ○ conntrack races によるタイムアウト時間の短縮
 ■ DNS キャッシュサーバから kube-dns へのクエリを TCP に更新
 ○ DNS 関連の問題のデバッグが難しい
 ■ ノードレベルのメトリクス取得
 NodeLocal DNSCache


Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

53 kube-dns


Slide 54

Slide 54 text

54 local-dns


Slide 55

Slide 55 text

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


Slide 56

Slide 56 text

56 ● 既存の kube-dns の ClusterIP に変更を加えずに再利用する工夫
 ○ kubelet の設定変更と再起動を避けたい
 ○ ClusterIP へのパケットを local-dns に向ける
 ■ メインプロセスとは別に起動終了時に処理を追加
 ● DNS クエリを conntrack に捕捉させない
 ● Pluggable な CoreDNS のアーキテクチャの活用
 ● ホストネットワークの利用
 Implementation Overview


Slide 57

Slide 57 text

57 ● ClusterIP へのパケットを local-dns に向けるには?
 ○ ノード上にダミーのネットワークインターフェイスを作成
 ○ kube-dns の ClusterIP を割り当て
 ○ local-dns のメインプロセスをそのソケットに繋げる
 ○ iptables ルールを作成して NAT されないようにする
 ○ iptables に更にルールを追加してパケットをソケットに向ける
 ○ local-dns はあくまで DNS キャッシュサーバー
 ■ キャッシュミス時の挙動を考える
 Implementation Tricks


Slide 58

Slide 58 text

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 (...)

Slide 59

Slide 59 text

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 { (...) } }

Slide 60

Slide 60 text

60 ● ノード上にダミーの仮想ネットワークインターフェイスを作成
 ○ デフォルトの DNS サーバーの VIP (ClusterIP) を割り当て
 ○ hostNetwork を有効化して接続可能にする必要あり
 Binding to Dummy Interface
 $ sudo ip addr list nodelocaldns (...) 38: nodelocaldns: 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

Slide 61

Slide 61 text

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 } (...) }

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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


Slide 69

Slide 69 text

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


Slide 70

Slide 70 text

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 } (...) }

Slide 71

Slide 71 text

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 } (...) }

Slide 72

Slide 72 text

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


Slide 73

Slide 73 text

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


Slide 74

Slide 74 text

74 ● コロプラはマルチクラスター戦略
 ○ Kubernetes Master コンポーネントの負荷軽減
 ○ アプリケーションのデプロイ時間の短縮
 ○ クラスター切り替えによる無停止 GKE アップグレード
 ■ トラフィック制御が容易な Global Load Balancer (HAProxy)
 ○ 障害時の Blast Radius の最小化
 Multi-cluster Strategy


Slide 75

Slide 75 text

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


Slide 76

Slide 76 text

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


Slide 77

Slide 77 text

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


Slide 78

Slide 78 text

78 Communication between multi-cluster


Slide 79

Slide 79 text

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


Slide 80

Slide 80 text

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


Slide 81

Slide 81 text

81 ● local-dns
 ○ cluster.local 以外のドメインへの DNS クエリは forward plugin で/etc/resolve.conf の設定に転送
 ■ local-dns は dnsPolicy が Default に設定されているので、DNS クエリが内部 DNS に向けられる
 Communication between multi-cluster


Slide 82

Slide 82 text

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


Slide 83

Slide 83 text

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


Slide 84

Slide 84 text

84 ● local-dns が単一障害点
 ○ 各ノードに 1 台しか起動しない
 ■ ホストネットワークを利用しているため、同一ホスト上で複数の local-dns のプ ロセス (ポート) を同時に起動できない
 ○ DaemonSet の RollingUpdate は maxSurge を未サポート
 ■ 気軽に local-dns のイメージ更新などができない
 Single Point of Failure


Slide 85

Slide 85 text

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


Slide 86

Slide 86 text

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


Slide 87

Slide 87 text

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


Slide 88

Slide 88 text

88 ● local-dns の HA 構成
 ○ SO_REUSEPORT を使って複数のプロセスを同一のポートに紐付け
 ○ DNS の処理も複数のプロセスにオフロードされる
 ○ local-dns の teardown 処理をスキップするフラグを立て忘れないように
 ○ GKE の add-on の場合どうなっているんだろう...
 Summary


Slide 89

Slide 89 text

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


Slide 90

Slide 90 text

90 ● 公式と独自実装のイメージの違いは読み込んでいる CoreDNS plugin
 ○ 1.15.13 以降の公式イメージでマルチクラスター構成を可能にする CoreDNS plugin が全て読み込まれたので、いつでも移行可能
 ■ loadbalance #344
 ■ template #374
 Migrate to Official local-dns image


Slide 91

Slide 91 text

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


Slide 92

Slide 92 text

92 ● ノードがスケールアウトする時の local-dns の起動順の落とし穴
 ○ HPA による急激な app のスケールアウト
 ○ Cluster Autoscaler がノードをスケールアウト
 ○ Pending 状態の app が先にノードにスケジューリング
 ○ local-dns のスケジューリングが 20-30 秒程度遅れる
 ■ 通常の負荷ででは app と local-dns のスケジューリング時刻は同じ
 ○ local-dns より app が先にサービスインして 1 分程度障害
 DaemonSet Pod Scheduling Order


Slide 93

Slide 93 text

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


Slide 94

Slide 94 text

94 ● 導入予定の対策
 ○ そもそも... app に local-dns が準備完了かのチェックができていなかった
 ○ Kubernetes には Pod の依存関係 (起動順) を指定する機能がない
 ■ initContainers, コンテナのメインプロセスの初期化処理中, readinessProbe で ゴニョゴニョするしかない
 ○ backend クラスターの Headless Service の名前解決ができるまで一時停止する処 理を追加予定
 DaemonSet Pod Scheduling Order


Slide 95

Slide 95 text

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


Slide 96

Slide 96 text

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


Slide 97

Slide 97 text

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


Slide 98

Slide 98 text

98 ● DNS は Kubernetes の世界でも問題児で難しい
 ○ 気づかずに影響を受けていることもある
 ● Kubernetes に限らず conntrack races の問題には注意
 ○ ローカルに DNS キャッシュサーバーを持つという選択肢
 ○ local-dns はその緩和策の一つ
 ● クラスターを跨いだ DNS ベースのサービス検出
 ○ Multi-cluster Services API 楽しみ
 Summary


Slide 99

Slide 99 text

99 ● KubeCon Europe 2020 の DNS 周りのより実用的な話
 ○ Kubernetes DNS Horror Stories (And How to Avoid Them)
 ■ https://www.youtube.com/watch?v=Yq-SVNa_W5E
 Summary


Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

101 Thank you for listening!!!