Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reliable_and_Performant_DNS_Resolution_with_High_Available_NodeLocal_DNSCache.pdf

 Reliable_and_Performant_DNS_Resolution_with_High_Available_NodeLocal_DNSCache.pdf

Tsubasa Nagasawa

September 09, 2020
Tweet

More Decks by Tsubasa Nagasawa

Other Decks in Programming

Transcript

  1. 1
    Reliable and Performant

    DNS Resolution

    with High Available

    NodeLocal DNSCache


    View Slide

  2. 2
    ● 長澤 翼 (Tsubasa Nagasawa)

    ● インフラエンジニア

    ● 株式会社コロプラ所属 (2020.3-)

    ○ ゲームタイトルの GKE クラスター運用と改善

    ● 外部登壇

    ○ Zero Scale Abstraction in Knative Serving

    About me


    View Slide

  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

    View Slide

  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


    View Slide

  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


    View Slide

  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


    View Slide

  7. 7
    ● 昔からある技術で Internet を支える重要な技術のひとつ

    ○ Web サービスや Computing リソースなどの識別子

    ○ 階層構造になっている巨大な分散システムのひとつ

    ● ドメイン名に関連付けられた情報 (レコード) を解決

    ● レコードには種類がある

    ○ ドメイン名と IP アドレスの変換 (A/AAAA レコード)

    ■ e.g.) example.com => 93.184.216.34

    Domain Name System (DNS)


    View Slide

  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


    View Slide

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

    ○ IP や VIP が変わるとサービスの設定変更が必要

    ○ ドメイン名を使えばサービスの設定変更が不要

    ● DNS をベースにしたサービス検出

    ○ DNS-SD (RFC-6763)

    ○ Consul

    ○ Cloud Map (AWS), Service Directory (GCP), ...

    DNS in Cloud Native


    View Slide

  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


    View Slide

  11. 11
    ● Kubernetes 1.11 からデフォルトの DNS サーバー

    ● Golang で有名な DNS library を書いている Google の方がリード

    ● CoreDNS は機能を plugin として実装

    ○ 設定ファイルに必要な plugin の情報を記載

    ○ メインプロセスもライブラリ化されている

    ■ CoreDNS 外でも使い易い

    参考: CoreDNS


    View Slide

  12. 12
    ● A/AAAA レコード

    ○ Service type ClusterIP

     ..svc.cluster.local. IN A 

    ■ VIP を返却

    ○ Headless Service

     ..svc.cluster.local. IN A 

    ■ PodIP のリストを返却

    DNS in Kubernetes


    View Slide

  13. 13
    ● DNS コンポーネントが Kubernetes API から必要な情報を返却

    ○ 裏側で自動的に処理されるのでユーザーは気にする必要なし

    ○ Service が作成されたら svc.cluster.local のレコードを返却

    ○ Headless Service に紐づいた Pod IP リストの更新も行う

    DNS in Kubernetes


    View Slide

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

    View Slide

  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

    View Slide

  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


    View Slide

  17. 17
    ● glibc/musl libc などで実装されている DNS resolver のオプション

    ○ 以下の条件の場合、search で指定した検索パスを追加して再帰的に検索

    ■ 送信先のエンドポイントを FQDN ではなく PQDN で指定

    ■ PQDN 内のドットの数が ndots より少ない

    Ndots and search path


    View Slide

  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


    View Slide

  19. 19
    ● ndots=5 の背景

    ○ Explainer on why ndots=5 in kubernetes

    ● 自動補完の回避方法

    ○ PQDN ではなく FQDN 指定

    ■ ‍♂ somehost.example.com.

    ■ ‍♂ somehost.example.com

    ○ dnsConfig で設定を上書き

    Ndots and search path


    View Slide

  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

    View Slide

  21. 21
    ● Kubernetes には DNS ベースのサービス検出の機能がある

    ○ Kubernetes Service を使った L4 負荷分散

    ○ kube-dns や CoreDNS がデフォルトの DNS キャッシュサーバー

    ● Pod の DNS の設定は kubelet が管理

    ○ デフォルトだと Kubernetes Service の名前解決が可能

    ○ ndots と search path による自動補完に注意

    ■ 無駄な DNS クエリによってレイテンシ影響も

    Summary


    View Slide

  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


    View Slide

  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


    View Slide

  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


    View Slide

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

    Network Namespaces

    eth0

    cbr0

    eth0
 eth0

    Pod A netns
 Pod B netns

    vethxx
 vethyy

    iptables

    root netns


    View Slide

  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


    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  31. 31
    ● kube-dns (ClusterIP, 10.96.0.10/32) への通信

    ○ iptables のルールにより送信先の IP が DNAT される

    ○ ClusterIP への通信は基本的に DNAT される

    ○ iptables のルールでランダムに PodIP が選ばれる

    ● iptables の NAT テーブルのルールに引っ掛かると...

    ○ conntrack テーブルで接続情報を管理

    Summary


    View Slide

  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


    View Slide

  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)


    View Slide

  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]

    View Slide

  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


    View Slide

  36. 36
    ● 接続情報はすぐに conntrack テーブルに保存される訳ではない

    ○ unconfirmed テーブルの存在

    ● nf_conntrack_in

    ○ PREROUTING のフックで DNAT 前の接続情報を unconfirmed に保持

    ● nf_conntrack_confirm

    ○ INPUT のフックで接続情報を unconfirmed から破棄して、conntrack に接続情報を
    追加

    Conntrack and Unconfirmed Table


    View Slide

  37. 37
    1. unconfirmed テーブルに接続情報を追加

    ○ 応答パケットの tuple は送信パケットの tuple を反転させたもの

    2. iptables の NAT テーブルのルールを辿る

    3. DNAT のルールで応答パケットの送信元の情報を更新

    4. 3. の情報をもとにパケットの送信先 IP とポートを変換

    5. unconfirmed テーブルから接続情報を削除し、同一の送信パケット or 応答パ
    ケットの tuple が conntrack テーブルになければ追加

    ○ 既に存在する場合はパケット破棄して insert_failed++

    Conntrack Insertion Flow


    View Slide

  38. 38
    ● 前提知識

    ○ glibc/musl libc で DNS クエリは A/AAAA レコードのクエリを並列に実行

    ○ UDP パケットは connect() で接続情報が conntrack に残らない

    ○ UDP パケットは send() で初めてパケットが飛んで接続情報が残る

    ● 競合する原因

    ○ 同一プロセスからの複数の UDP パケットが同一ソケットに同時に入る

    ■ DNS クエリは conntrack races の影響を受けやすい

    Possible Conntrack Races


    View Slide

  39. 39
    ● DNS サーバーに初めてクエリを発行する場合

    1. unconfirmed テーブルに接続情報を保存

    2. iptables の NAT テーブルのルールを辿る

    3. DNAT のルールにより応答パケットの送信元の情報を変換

    4. 3. の情報をもとにパケットの送信先 IP とポートを変換

    5. どちらかのパケットが先に conntrack テーブルに追加

    ○ 遅れたパケットは破棄されて insert_failed++

    Possible Conntrack Races Case (1)


    View Slide

  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)


    View Slide

  41. 41
    ● DNS サーバーに初めてクエリを発行する場合

    1. unconfirmed テーブルに接続情報を保存

    2. iptables の NAT テーブルのルールを辿り、異なる DNAT のルールに移動

    3. 異なる DNAT のルールにより応答パケットの送信元の情報を異なる DNS サー
    バーの情報に変換

    4. 3. の情報をもとにパケットの送信先 IP とポートを変換

    5. 送信パケットの tuple が同じ接続情報が存在するので、パケット破棄して
    insert_failed++

    Possible Conntrack Races Case (3)


    View Slide

  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)


    View Slide

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

    View Slide

  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)


    View Slide

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

    View Slide

  46. 46
    ● iptables の NAT テーブルのルールに引っ掛かると...

    ○ conntrack テーブルで接続情報を管理し、後続のパケットの処理を高速化

    ● conntrack テーブルに接続情報を保存する時...

    ○ 3 条件で競合が発生してパケットを破棄

    ○ TCP の場合は再送処理が発生するが、UDP の場合は再送処理が発生しない

    ● conntrack races は Kubernetes 固有の問題ではない

    ○ Linux 上の同一プロセスから複数パケットを並列送信で発生

    ○ 競合 2 条件の修正はマージ済み (4.19.1 と 4.19.29)

    Summary


    View Slide

  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


    View Slide

  48. 48
    ● Kubernetes の世界で起こる名前解決の問題の緩和策

    ● NAT テーブルに囚われる DNS クエリを減らすことが大目的

    ○ 名前解決のレイテンシ改善

    ○ 名前解決のエラー率の軽減

    ■ conntrack races によるパケット破棄の軽減

    ■ パケット破棄時の DNS クエリのタイムアウト時間短縮

    ● UDP の DNS クエリのタイムアウト時間 (30 sec)

    ■ conntrack table の肥大化を回避

    Why NodeLocal DNSCache


    View Slide

  49. 49
    ● CoreDNS ベースの DNS キャッシュサーバー

    ● 本セッションでは local-dns と呼びます

    ● DaemoSet として各ノードに存在

    ● kubernetes/dns で開発中

    ○ Kubernetes v1.18 で GA

    ○ GKE でも add-on が GA 済み

    ○ Google の方がメンテナー (ingress-gce の中の人でもある)

    NodeLocal DNSCache


    View Slide

  50. 50
    ● 現状の問題と緩和/解決策

    ○ 高負荷な Pod の DNS クエリに kube-dns が耐えられない

    ■ 各ノード上に DNS キャッシュサーバーを置く

    ■ ネガティブキャッシュの有効化

    ○ conntrack races の発生

    ■ DNS クエリが DNAT されないようにすることで緩和

    ○ conntrack テーブルの枯渇

    ■ DNS クエリが DNAT されないようにすることで軽減

    NodeLocal DNSCache


    View Slide

  51. 51
    ● 現状の問題と緩和/解決策

    ○ conntrack races によるタイムアウト時間の短縮

    ■ DNS キャッシュサーバから kube-dns へのクエリを TCP に更新

    ○ DNS 関連の問題のデバッグが難しい

    ■ ノードレベルのメトリクス取得

    NodeLocal DNSCache


    View Slide

  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

    View Slide

  53. 53
    kube-dns


    View Slide

  54. 54
    local-dns


    View Slide

  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


    View Slide

  56. 56
    ● 既存の kube-dns の ClusterIP に変更を加えずに再利用する工夫

    ○ kubelet の設定変更と再起動を避けたい

    ○ ClusterIP へのパケットを local-dns に向ける

    ■ メインプロセスとは別に起動終了時に処理を追加

    ● DNS クエリを conntrack に捕捉させない

    ● Pluggable な CoreDNS のアーキテクチャの活用

    ● ホストネットワークの利用

    Implementation Overview


    View Slide

  57. 57
    ● ClusterIP へのパケットを local-dns に向けるには?

    ○ ノード上にダミーのネットワークインターフェイスを作成

    ○ kube-dns の ClusterIP を割り当て

    ○ local-dns のメインプロセスをそのソケットに繋げる

    ○ iptables ルールを作成して NAT されないようにする

    ○ iptables に更にルールを追加してパケットをソケットに向ける

    ○ local-dns はあくまで DNS キャッシュサーバー

    ■ キャッシュミス時の挙動を考える

    Implementation Tricks


    View Slide

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

    View Slide

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

    View Slide

  60. 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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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


    View Slide

  69. 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


    View Slide

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

    View Slide

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

    View Slide

  72. 72
    ● kube-dns の ClusterIP へのパケットを local-dns に向ける

    ○ 仮想 NIC の作成と ClusteIP の紐付け

    ○ iptables のルールの追加

    ■ RAW テーブルの NOTRACK ルールによる NAT の回避

    ■ FILTER テーブルの ACCEPT ルールによるパケットの受入れ

    ● キャッシュミス時に kube-dns にフォールバック

    ○ force_tcp によるパケット破棄時のコネクション維持を短縮

    Summary


    View Slide

  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


    View Slide

  74. 74
    ● コロプラはマルチクラスター戦略

    ○ Kubernetes Master コンポーネントの負荷軽減

    ○ アプリケーションのデプロイ時間の短縮

    ○ クラスター切り替えによる無停止 GKE アップグレード

    ■ トラフィック制御が容易な Global Load Balancer (HAProxy)

    ○ 障害時の Blast Radius の最小化

    Multi-cluster Strategy


    View Slide

  75. 75
    ● コロプラはマルチクラスター戦略

    Multi-cluster Strategy


    View Slide

  76. 76
    ● コロプラはマルチクラスター戦略

    Multi-cluster Strategy

    app -> backend のクラスター間通信


    View Slide

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

    ○ backend クラスターの Pod のサービス検出

    ■ 名前解決の処理は backend クラスタで行う

    ■ データストアの PodIP リストは Headless Service で取得

    ■ app クラスタ側で発生した DNS クエリを backend クラスタに転送

    ■ PodIP リストは順序が固定なので振り分ける機構が必要

    ○ local-dns で当時未サポートだった CoreDNS plugin たち...

    ■ local-dns の独自実装へ

    Communication between multi-cluster


    View Slide

  78. 78
    Communication between multi-cluster


    View Slide

  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


    View Slide

  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


    View Slide

  81. 81
    ● local-dns

    ○ cluster.local 以外のドメインへの DNS クエリは forward plugin で/etc/resolve.conf
    の設定に転送

    ■ local-dns は dnsPolicy が Default に設定されているので、DNS クエリが内部
    DNS に向けられる

    Communication between multi-cluster


    View Slide

  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


    View Slide

  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


    View Slide

  84. 84
    ● local-dns が単一障害点

    ○ 各ノードに 1 台しか起動しない

    ■ ホストネットワークを利用しているため、同一ホスト上で複数の local-dns のプ
    ロセス (ポート) を同時に起動できない

    ○ DaemonSet の RollingUpdate は maxSurge を未サポート

    ■ 気軽に local-dns のイメージ更新などができない

    Single Point of Failure


    View Slide

  85. 85
    ● local-dns が単一障害点

    ○ local-dns 停止時に仮想 NIC や iptables のルールを削除

    ■ OOMKill などでプロセスが異常終了した場合に、teardown の処理が行われ
    ず DNS クエリの名前解決に失敗してしまう

    ■ そもそも...コロプラの使い方だと、teardown 処理でデフォルトの kube-dns に
    フォールバックしても backend と疎通できず障害になる

    Single Point of Failure


    View Slide

  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


    View Slide

  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


    View Slide

  88. 88
    ● local-dns の HA 構成

    ○ SO_REUSEPORT を使って複数のプロセスを同一のポートに紐付け

    ○ DNS の処理も複数のプロセスにオフロードされる

    ○ local-dns の teardown 処理をスキップするフラグを立て忘れないように

    ○ GKE の add-on の場合どうなっているんだろう...

    Summary


    View Slide

  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


    View Slide

  90. 90
    ● 公式と独自実装のイメージの違いは読み込んでいる CoreDNS plugin

    ○ 1.15.13 以降の公式イメージでマルチクラスター構成を可能にする CoreDNS plugin
    が全て読み込まれたので、いつでも移行可能

    ■ loadbalance #344

    ■ template #374

    Migrate to Official local-dns image


    View Slide

  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


    View Slide

  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


    View Slide

  93. 93
    ● local-dns のスケジューリングが遅れた原因

    ○ DaemonSet は Node が Ready にならないとスケジューリングされない

    ■ local-dns は DaemonSet

    ○ 急激なノードのスケールアウトで Master ノードに負荷が掛かり、Node が Ready
    かの判断に遅れが生じた?

    DaemonSet Pod Scheduling Order


    View Slide

  94. 94
    ● 導入予定の対策

    ○ そもそも... app に local-dns が準備完了かのチェックができていなかった

    ○ Kubernetes には Pod の依存関係 (起動順) を指定する機能がない

    ■ initContainers, コンテナのメインプロセスの初期化処理中, readinessProbe で
    ゴニョゴニョするしかない

    ○ backend クラスターの Headless Service の名前解決ができるまで一時停止する処
    理を追加予定

    DaemonSet Pod Scheduling Order


    View Slide

  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


    View Slide

  96. 96
    ● クラスターの壁を超えて Service を利用可能に

    ○ ServiceExport で他クラスターに公開する Service を選択

    ○ ServiceImport で他クラスターから取り込む Service を選択

    ○ ..svc.clusterset.local でサービス検出可能

    ● API 仕様を決めるだけで公式のコントローラー実装はない

    ○ API 仕様は CRD で公開、Kubernetes のリリースに依存せず利用可能

    ○ CoreDNS などを使って独自実装 or サードパーティ製品

    Multi-cluster Services API


    View Slide

  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


    View Slide

  98. 98
    ● DNS は Kubernetes の世界でも問題児で難しい

    ○ 気づかずに影響を受けていることもある

    ● Kubernetes に限らず conntrack races の問題には注意

    ○ ローカルに DNS キャッシュサーバーを持つという選択肢

    ○ local-dns はその緩和策の一つ

    ● クラスターを跨いだ DNS ベースのサービス検出

    ○ Multi-cluster Services API 楽しみ

    Summary


    View Slide

  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


    View Slide

  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

    View Slide

  101. 101
    Thank you for listening!!!

    View Slide