Share
Embed
Reliable_and_Performant_DNS_Resolution_with_High_Available_NodeLocal_DNSCache.pdf
Tsubasa Nagasawa
September 09, 2020
Transcript
101 slides
-
2 • 長澤 翼 (Tsubasa Nagasawa) • インフラエンジニア • 株式会社コロプラ所属
(2020.3-) ◦ ゲームタイトルの GKE クラスター運用と改善 • 外部登壇 ◦ Zero Scale Abstraction in Knative Serving About me -
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 • 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 • 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 • 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 • 昔からある技術で Internet を支える重要な技術のひとつ ◦ Web サービスや Computing リソースなどの識別子
◦ 階層構造になっている巨大な分散システムのひとつ • ドメイン名に関連付けられた情報 (レコード) を解決 • レコードには種類がある ◦ ドメイン名と IP アドレスの変換 (A/AAAA レコード) ▪ e.g.) example.com => 93.184.216.34 Domain Name System (DNS) -
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 • サービス間通信で IP や VIP を指定するのは辛い ◦ IP や
VIP が変わるとサービスの設定変更が必要 ◦ ドメイン名を使えばサービスの設定変更が不要 • DNS をベースにしたサービス検出 ◦ DNS-SD (RFC-6763) ◦ Consul ◦ Cloud Map (AWS), Service Directory (GCP), ... DNS in Cloud Native -
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 • Kubernetes 1.11 からデフォルトの DNS サーバー • Golang で有名な
DNS library を書いている Google の方がリード • CoreDNS は機能を plugin として実装 ◦ 設定ファイルに必要な plugin の情報を記載 ◦ メインプロセスもライブラリ化されている ▪ CoreDNS 外でも使い易い 参考: CoreDNS -
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 • DNS コンポーネントが Kubernetes API から必要な情報を返却 ◦ 裏側で自動的に処理されるのでユーザーは気にする必要なし ◦
Service が作成されたら svc.cluster.local のレコードを返却 ◦ Headless Service に紐づいた Pod IP リストの更新も行う DNS in Kubernetes -
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 • 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 • 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 • glibc/musl libc などで実装されている DNS resolver のオプション ◦ 以下の条件の場合、search
で指定した検索パスを追加して再帰的に検索 ▪ 送信先のエンドポイントを FQDN ではなく PQDN で指定 ▪ PQDN 内のドットの数が ndots より少ない Ndots and search path -
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 • ndots=5 の背景 ◦ Explainer on why ndots=5 in
kubernetes • 自動補完の回避方法 ◦ PQDN ではなく FQDN 指定 ▪ ♂ somehost.example.com. ▪ ♂ somehost.example.com ◦ dnsConfig で設定を上書き Ndots and search path -
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 • Kubernetes には DNS ベースのサービス検出の機能がある ◦ Kubernetes Service を使った
L4 負荷分散 ◦ kube-dns や CoreDNS がデフォルトの DNS キャッシュサーバー • Pod の DNS の設定は kubelet が管理 ◦ デフォルトだと Kubernetes Service の名前解決が可能 ◦ ndots と search path による自動補完に注意 ▪ 無駄な DNS クエリによってレイテンシ影響も Summary -
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 • 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 • 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 • netns と bridge (L2) を使った同一ノード上の Pod 間通信 Network
Namespaces eth0 cbr0 eth0 eth0 Pod A netns Pod B netns vethxx vethyy iptables root netns -
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 • 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 • 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 • 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 • 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 • kube-dns (ClusterIP, 10.96.0.10/32) への通信 ◦ iptables のルールにより送信先の IP
が DNAT される ◦ ClusterIP への通信は基本的に DNAT される ◦ iptables のルールでランダムに PodIP が選ばれる • iptables の NAT テーブルのルールに引っ掛かると... ◦ conntrack テーブルで接続情報を管理 Summary -
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 • 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 • 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 • 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 • 接続情報はすぐに conntrack テーブルに保存される訳ではない ◦ unconfirmed テーブルの存在 • nf_conntrack_in
◦ PREROUTING のフックで DNAT 前の接続情報を unconfirmed に保持 • nf_conntrack_confirm ◦ INPUT のフックで接続情報を unconfirmed から破棄して、conntrack に接続情報を 追加 Conntrack and Unconfirmed Table -
37 1. unconfirmed テーブルに接続情報を追加 ◦ 応答パケットの tuple は送信パケットの tuple を反転させたもの
2. iptables の NAT テーブルのルールを辿る 3. DNAT のルールで応答パケットの送信元の情報を更新 4. 3. の情報をもとにパケットの送信先 IP とポートを変換 5. unconfirmed テーブルから接続情報を削除し、同一の送信パケット or 応答パ ケットの tuple が conntrack テーブルになければ追加 ◦ 既に存在する場合はパケット破棄して insert_failed++ Conntrack Insertion Flow -
38 • 前提知識 ◦ glibc/musl libc で DNS クエリは A/AAAA
レコードのクエリを並列に実行 ◦ UDP パケットは connect() で接続情報が conntrack に残らない ◦ UDP パケットは send() で初めてパケットが飛んで接続情報が残る • 競合する原因 ◦ 同一プロセスからの複数の UDP パケットが同一ソケットに同時に入る ▪ DNS クエリは conntrack races の影響を受けやすい Possible Conntrack Races -
39 • DNS サーバーに初めてクエリを発行する場合 1. unconfirmed テーブルに接続情報を保存 2. iptables の
NAT テーブルのルールを辿る 3. DNAT のルールにより応答パケットの送信元の情報を変換 4. 3. の情報をもとにパケットの送信先 IP とポートを変換 5. どちらかのパケットが先に conntrack テーブルに追加 ◦ 遅れたパケットは破棄されて insert_failed++ Possible Conntrack Races Case (1) -
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 • DNS サーバーに初めてクエリを発行する場合 1. unconfirmed テーブルに接続情報を保存 2. iptables の
NAT テーブルのルールを辿り、異なる DNAT のルールに移動 3. 異なる DNAT のルールにより応答パケットの送信元の情報を異なる DNS サー バーの情報に変換 4. 3. の情報をもとにパケットの送信先 IP とポートを変換 5. 送信パケットの tuple が同じ接続情報が存在するので、パケット破棄して insert_failed++ Possible Conntrack Races Case (3) -
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 • [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 • [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 • [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 • iptables の NAT テーブルのルールに引っ掛かると... ◦ conntrack テーブルで接続情報を管理し、後続のパケットの処理を高速化 •
conntrack テーブルに接続情報を保存する時... ◦ 3 条件で競合が発生してパケットを破棄 ◦ TCP の場合は再送処理が発生するが、UDP の場合は再送処理が発生しない • conntrack races は Kubernetes 固有の問題ではない ◦ Linux 上の同一プロセスから複数パケットを並列送信で発生 ◦ 競合 2 条件の修正はマージ済み (4.19.1 と 4.19.29) Summary -
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 • Kubernetes の世界で起こる名前解決の問題の緩和策 • NAT テーブルに囚われる DNS クエリを減らすことが大目的 ◦
名前解決のレイテンシ改善 ◦ 名前解決のエラー率の軽減 ▪ conntrack races によるパケット破棄の軽減 ▪ パケット破棄時の DNS クエリのタイムアウト時間短縮 • UDP の DNS クエリのタイムアウト時間 (30 sec) ▪ conntrack table の肥大化を回避 Why NodeLocal DNSCache -
49 • CoreDNS ベースの DNS キャッシュサーバー • 本セッションでは local-dns と呼びます
• DaemoSet として各ノードに存在 • kubernetes/dns で開発中 ◦ Kubernetes v1.18 で GA ◦ GKE でも add-on が GA 済み ◦ Google の方がメンテナー (ingress-gce の中の人でもある) NodeLocal DNSCache -
50 • 現状の問題と緩和/解決策 ◦ 高負荷な Pod の DNS クエリに kube-dns
が耐えられない ▪ 各ノード上に DNS キャッシュサーバーを置く ▪ ネガティブキャッシュの有効化 ◦ conntrack races の発生 ▪ DNS クエリが DNAT されないようにすることで緩和 ◦ conntrack テーブルの枯渇 ▪ DNS クエリが DNAT されないようにすることで軽減 NodeLocal DNSCache -
51 • 現状の問題と緩和/解決策 ◦ conntrack races によるタイムアウト時間の短縮 ▪ DNS キャッシュサーバから
kube-dns へのクエリを TCP に更新 ◦ DNS 関連の問題のデバッグが難しい ▪ ノードレベルのメトリクス取得 NodeLocal DNSCache -
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 -
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 • 既存の kube-dns の ClusterIP に変更を加えずに再利用する工夫 ◦ kubelet の設定変更と再起動を避けたい
◦ ClusterIP へのパケットを local-dns に向ける ▪ メインプロセスとは別に起動終了時に処理を追加 • DNS クエリを conntrack に捕捉させない • Pluggable な CoreDNS のアーキテクチャの活用 • ホストネットワークの利用 Implementation Overview -
57 • ClusterIP へのパケットを local-dns に向けるには? ◦ ノード上にダミーのネットワークインターフェイスを作成 ◦ kube-dns
の ClusterIP を割り当て ◦ local-dns のメインプロセスをそのソケットに繋げる ◦ iptables ルールを作成して NAT されないようにする ◦ iptables に更にルールを追加してパケットをソケットに向ける ◦ local-dns はあくまで DNS キャッシュサーバー ▪ キャッシュミス時の挙動を考える Implementation Tricks -
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 • ノード上にダミーの仮想ネットワークインターフェイスを作成 ◦ デフォルトの 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 • ノード上にダミーの仮想ネットワークインターフェイスを作成 ◦ デフォルトの 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 • 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 • 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 • 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 • 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 • 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 • 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 • 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 • 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 • 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 • 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 • 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 • kube-dns の ClusterIP へのパケットを local-dns に向ける ◦ 仮想
NIC の作成と ClusteIP の紐付け ◦ iptables のルールの追加 ▪ RAW テーブルの NOTRACK ルールによる NAT の回避 ▪ FILTER テーブルの ACCEPT ルールによるパケットの受入れ • キャッシュミス時に kube-dns にフォールバック ◦ force_tcp によるパケット破棄時のコネクション維持を短縮 Summary -
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 • コロプラはマルチクラスター戦略 ◦ Kubernetes Master コンポーネントの負荷軽減 ◦ アプリケーションのデプロイ時間の短縮 ◦
クラスター切り替えによる無停止 GKE アップグレード ▪ トラフィック制御が容易な Global Load Balancer (HAProxy) ◦ 障害時の Blast Radius の最小化 Multi-cluster Strategy -
77 • app -> backend のクラスター間の名前解決 ◦ backend クラスターの Pod
のサービス検出 ▪ 名前解決の処理は backend クラスタで行う ▪ データストアの PodIP リストは Headless Service で取得 ▪ app クラスタ側で発生した DNS クエリを backend クラスタに転送 ▪ PodIP リストは順序が固定なので振り分ける機構が必要 ◦ local-dns で当時未サポートだった CoreDNS plugin たち... ▪ local-dns の独自実装へ Communication between multi-cluster -
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 • 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 • local-dns ◦ cluster.local 以外のドメインへの DNS クエリは forward plugin
で/etc/resolve.conf の設定に転送 ▪ local-dns は dnsPolicy が Default に設定されているので、DNS クエリが内部 DNS に向けられる Communication between multi-cluster -
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 • 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 • local-dns が単一障害点 ◦ 各ノードに 1 台しか起動しない ▪ ホストネットワークを利用しているため、同一ホスト上で複数の
local-dns のプ ロセス (ポート) を同時に起動できない ◦ DaemonSet の RollingUpdate は maxSurge を未サポート ▪ 気軽に local-dns のイメージ更新などができない Single Point of Failure -
85 • local-dns が単一障害点 ◦ local-dns 停止時に仮想 NIC や iptables
のルールを削除 ▪ OOMKill などでプロセスが異常終了した場合に、teardown の処理が行われ ず DNS クエリの名前解決に失敗してしまう ▪ そもそも...コロプラの使い方だと、teardown 処理でデフォルトの kube-dns に フォールバックしても backend と疎通できず障害になる Single Point of Failure -
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 • 複数台起動する 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 • local-dns の HA 構成 ◦ SO_REUSEPORT を使って複数のプロセスを同一のポートに紐付け ◦
DNS の処理も複数のプロセスにオフロードされる ◦ local-dns の teardown 処理をスキップするフラグを立て忘れないように ◦ GKE の add-on の場合どうなっているんだろう... Summary -
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 • 公式と独自実装のイメージの違いは読み込んでいる CoreDNS plugin ◦ 1.15.13 以降の公式イメージでマルチクラスター構成を可能にする CoreDNS plugin
が全て読み込まれたので、いつでも移行可能 ▪ loadbalance #344 ▪ template #374 Migrate to Official local-dns image -
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 • ノードがスケールアウトする時の local-dns の起動順の落とし穴 ◦ HPA による急激な app のスケールアウト
◦ Cluster Autoscaler がノードをスケールアウト ◦ Pending 状態の app が先にノードにスケジューリング ◦ local-dns のスケジューリングが 20-30 秒程度遅れる ▪ 通常の負荷ででは app と local-dns のスケジューリング時刻は同じ ◦ local-dns より app が先にサービスインして 1 分程度障害 DaemonSet Pod Scheduling Order -
93 • local-dns のスケジューリングが遅れた原因 ◦ DaemonSet は Node が Ready
にならないとスケジューリングされない ▪ local-dns は DaemonSet ◦ 急激なノードのスケールアウトで Master ノードに負荷が掛かり、Node が Ready かの判断に遅れが生じた? DaemonSet Pod Scheduling Order -
94 • 導入予定の対策 ◦ そもそも... app に local-dns が準備完了かのチェックができていなかった ◦
Kubernetes には Pod の依存関係 (起動順) を指定する機能がない ▪ initContainers, コンテナのメインプロセスの初期化処理中, readinessProbe で ゴニョゴニョするしかない ◦ backend クラスターの Headless Service の名前解決ができるまで一時停止する処 理を追加予定 DaemonSet Pod Scheduling Order -
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 • クラスターの壁を超えて Service を利用可能に ◦ ServiceExport で他クラスターに公開する Service を選択
◦ ServiceImport で他クラスターから取り込む Service を選択 ◦ <service>.<namespace>.svc.clusterset.local でサービス検出可能 • API 仕様を決めるだけで公式のコントローラー実装はない ◦ API 仕様は CRD で公開、Kubernetes のリリースに依存せず利用可能 ◦ CoreDNS などを使って独自実装 or サードパーティ製品 Multi-cluster Services API -
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 • DNS は Kubernetes の世界でも問題児で難しい ◦ 気づかずに影響を受けていることもある • Kubernetes
に限らず conntrack races の問題には注意 ◦ ローカルに DNS キャッシュサーバーを持つという選択肢 ◦ local-dns はその緩和策の一つ • クラスターを跨いだ DNS ベースのサービス検出 ◦ Multi-cluster Services API 楽しみ Summary -
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 • コロプラでは Kubernetes エンジニアを積極採用中です!! ◦ GKE, Cloud Spanner を使ったゲームや基盤の開発
◦ Kubernetes のエコシステムを含めた Cloud Native への取り組み We are Hiring! コロプラ 採用 検索 ▼詳しくは https://be-ars.colopl.co.jp/recruit/career/engineer/corporate-business-support/kubernetes.html