GKE 1.22 with Node Local DNS Cache で Alpine 3.13+ の名前解決に失敗する
先日、GKE の静的リリースに 1.22 がやってきました。
今回は、Node Local DNS Cache を有効化した GKE 1.22 において、Alpine を使用する際に注意しておいたほうが良い挙動とその検証結果をご紹介します。
以下の検証は、現時点の GKE 1.22 における最新の静的リリースである 1.22.6-gke.1000 を用いて行っています。
Node Local DNS Cache の設定
GKE の Node Local DNS Cache には、アップストリームに基づいた CoreDNS ベースのイメージが使われており、一応 ”-gke.” のようなサフィックスが付いていますがほぼ同じような挙動となっています。
設定は GKE 独自のものがコントロールプレーンのバージョンに応じて自動的に展開される仕組みとなっています。
ちなみに、GKE の Node Local DNS Cache は CoreDNS をベースとしている一方、GKE クラスタのデフォルト DNS は kube-dns となっています(最近は Cloud DNS を利用する選択肢も出てきていますね)。
GKE の Node Local DNS Cache の設定は、"kube-system" Namespace の "node-local-dns" という ConfigMap の "Corefile" の値として定義されています。
GKE 1.22 では、以下のように AAAA クエリ (IPv6) に関する設定が追加されました。
$ kubectl version Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.3", GitCommit:"816c97ab8cff8a1c72eccca1026f7820e93e0d25", GitTreeState:"clean", BuildDate:"2022-01-25T21:25:17Z", GoVersion:"go1.17.6", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.6-gke.1000", GitCommit:"5595443086b60d8c5c62342fadc2d4fda9c793e8", GitTreeState:"clean", BuildDate:"2022-02-02T09:35:41Z", GoVersion:"go1.16.12b7", Compiler:"gc", Platform:"linux/amd64"} $ kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE event-exporter-gke-5877b595cd-6cw96 2/2 Running 0 7m fluentbit-gke-lpvn6 2/2 Running 0 5m10s fluentbit-gke-nsdwj 2/2 Running 0 5m9s fluentbit-gke-z7l9p 2/2 Running 0 5m9s gke-metrics-agent-bmb52 1/1 Running 0 5m10s gke-metrics-agent-fq5gw 1/1 Running 0 5m9s gke-metrics-agent-nrqvv 1/1 Running 0 5m9s konnectivity-agent-7dbd649949-6vfcp 1/1 Running 0 5m2s konnectivity-agent-7dbd649949-h864p 1/1 Running 0 5m2s konnectivity-agent-7dbd649949-s7lx9 1/1 Running 0 6m50s konnectivity-agent-autoscaler-698b6d8768-gq2dx 1/1 Running 0 6m46s kube-dns-6bb46c7474-4vfqf 4/4 Running 0 7m14s kube-dns-6bb46c7474-wlj7p 4/4 Running 0 5m1s kube-dns-autoscaler-f4d55555-btzmw 1/1 Running 0 7m12s kube-proxy-gke-cluster-1-22-clone-default-pool-058fd934-0pvn 1/1 Running 0 4m18s kube-proxy-gke-cluster-1-22-clone-default-pool-058fd934-g67w 1/1 Running 0 4m38s kube-proxy-gke-cluster-1-22-clone-default-pool-058fd934-nbrj 1/1 Running 0 4m20s l7-default-backend-69fb9fd9f9-vsqf2 1/1 Running 0 6m42s metrics-server-v0.4.5-bbb794dcc-87ttk 2/2 Running 0 4m44s node-local-dns-8f2mf 1/1 Running 0 5m7s node-local-dns-sf8l6 1/1 Running 0 5m7s node-local-dns-t7v9x 1/1 Running 0 5m7s pdcsi-node-tpxh6 2/2 Running 0 5m8s pdcsi-node-wzgr8 2/2 Running 0 5m9s pdcsi-node-z4r68 2/2 Running 0 5m10s $ kubectl describe configmaps node-local-dns -n kube-system Name: node-local-dns Namespace: kube-system Labels: addonmanager.kubernetes.io/mode=Reconcile Annotations: app.kubernetes.io/created-by: kube-addon-manager Data ==== Corefile: ---- cluster.local:53 { errors template ANY AAAA { rcode NXDOMAIN } cache { success 9984 30 denial 9984 5 } reload loop bind 169.254.20.10 10.120.0.10 health 169.254.20.10:8080 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } in-addr.arpa:53 { errors cache 30 reload loop bind 169.254.20.10 10.120.0.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } ip6.arpa:53 { errors cache 30 reload loop bind 169.254.20.10 10.120.0.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } .:53 { errors template ANY AAAA { rcode NXDOMAIN } cache 30 reload loop bind 169.254.20.10 10.120.0.10 forward . __PILLAR__UPSTREAM__SERVERS__ { force_tcp } prometheus :9253 } BinaryData ==== Events: <none>
ひとつ前の GKE 1.21 (1.21.6-gke.1500) と比較してみるとわかりやすいかと思います。
$ kubectl version Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.3", GitCommit:"816c97ab8cff8a1c72eccca1026f7820e93e0d25", GitTreeState:"clean", BuildDate:"2022-01-25T21:25:17Z", GoVersion:"go1.17.6", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.6-gke.1500", GitCommit:"7ce0f9f1939dfc1aee910732e84cba03840df91e", GitTreeState:"clean", BuildDate:"2021-11-17T09:30:26Z", GoVersion:"go1.16.9b7", Compiler:"gc", Platform:"linux/amd64"} WARNING: version difference between client (1.23) and server (1.21) exceeds the supported minor version skew of +/-1 $ kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE event-exporter-gke-5479fd58c8-w889w 2/2 Running 0 5d6h fluentbit-gke-8zj4h 2/2 Running 0 5d6h fluentbit-gke-dwwjk 2/2 Running 0 5d6h fluentbit-gke-h2wp2 2/2 Running 0 5d6h gke-metrics-agent-72h26 1/1 Running 0 5d6h gke-metrics-agent-dxsht 1/1 Running 0 5d6h gke-metrics-agent-v27lf 1/1 Running 0 5d6h konnectivity-agent-5b9bf44468-kttgm 1/1 Running 0 5d6h konnectivity-agent-5b9bf44468-l74sx 1/1 Running 0 5d6h konnectivity-agent-5b9bf44468-tmmzs 1/1 Running 0 5d6h konnectivity-agent-autoscaler-5c49cb58bb-cdlk8 1/1 Running 0 5d6h kube-dns-697dc8fc8b-8v5nl 4/4 Running 0 5d6h kube-dns-697dc8fc8b-rzgcl 4/4 Running 0 5d6h kube-dns-autoscaler-844c9d9448-v6gx2 1/1 Running 0 5d6h kube-proxy-gke-cluster-1-21-default-pool-2a39355d-ghz4 1/1 Running 0 5d6h kube-proxy-gke-cluster-1-21-default-pool-2a39355d-hw8r 1/1 Running 0 5d6h kube-proxy-gke-cluster-1-21-default-pool-2a39355d-xl2n 1/1 Running 0 5d6h l7-default-backend-69fb9fd9f9-wq98k 1/1 Running 0 5d6h metrics-server-v0.4.4-857776bc9c-v5296 2/2 Running 0 5d6h node-local-dns-j2r7v 1/1 Running 0 5d6h node-local-dns-pzn75 1/1 Running 0 5d6h node-local-dns-vcwr2 1/1 Running 0 5d6h pdcsi-node-5d99x 2/2 Running 0 5d6h pdcsi-node-g47xg 2/2 Running 0 5d6h pdcsi-node-n6btn 2/2 Running 0 5d6h $ kubectl describe configmaps node-local-dns -n kube-system Name: node-local-dns Namespace: kube-system Labels: addonmanager.kubernetes.io/mode=Reconcile Annotations: <none> Data ==== Corefile: ---- cluster.local:53 { errors cache { success 9984 30 denial 9984 5 } reload loop bind 169.254.20.10 10.36.16.10 health 169.254.20.10:8080 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } in-addr.arpa:53 { errors cache 30 reload loop bind 169.254.20.10 10.36.16.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } ip6.arpa:53 { errors cache 30 reload loop bind 169.254.20.10 10.36.16.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp expire 1s } prometheus :9253 } .:53 { errors cache 30 reload loop bind 169.254.20.10 10.36.16.10 forward . __PILLAR__UPSTREAM__SERVERS__ { force_tcp } prometheus :9253 } BinaryData ==== Events: <none>
設定追加の契機
上述のように GKE 1.22 の Node Local DNS Cache に設定が追加された背景には、アップストリームにおけるプラグインの追加が関係していると思われます。
GKE 1.21 と GKE 1.22 のそれぞれにおける Node Local DNS Cache のバージョンは以下のとおりです。
- GKE 1.21 : 1.21.1-gke.0
- GKE 1.22 : 1.21.4-gke.0
以下の URL から、実際に GitHub でこのバージョン間の差分を確認することができます。
ふたつのバージョンの差はパッチバージョンレベルのみとなっていますが、この差分に設定追加のきっかけと思われるコミットが含まれています。
たどってみると、差分に含まれる以下のコミットで Node Local DNS Cache に dns64 プラグインが追加されていることがわかります。
このアップストリームの変更をきっかけに、GKE 1.22 の Node Local DNS Cache に AAAA クエリ (IPv6) の設定が追加された可能性が考えられそうです。
CoreDNS の dns64 プラグインの説明はこちらにあります。
変更の影響
今回の変更が何をもたらすのでしょうか。
タイトルのとおりですが、 Node Local DNS Cache を有効化した GKE 1.22 において、Alpine 3.13+ のコンテナが名前解決に失敗するようになる という影響が生じます。
DNS クライアントの実装によっては、A + AAAA ペアクエリの一方で NXDOMAIN が返却された場合にクエリ全体を失敗とみなすことがあり、Alpine 3.13+ が名前解決に使用している musl libc 1.2 系はまさにこのような挙動を取ります。
Node Local DNS Cache を有効化した GKE 1.22 (1.22.6-gke.1000) における実際の挙動は次のようになります。
$ kubectl run alpine313 --image=alpine:3.13 --tty -i sh If you don't see a command prompt, try pressing enter. / # nslookup www.google.com Server: 10.120.0.10 Address: 10.120.0.10:53 ** server can't find www.google.com: NXDOMAIN Non-authoritative answer: Name: www.google.com Address: 142.250.148.99 Name: www.google.com Address: 142.250.148.103 Name: www.google.com Address: 142.250.148.106 Name: www.google.com Address: 142.250.148.147 Name: www.google.com Address: 142.250.148.104 Name: www.google.com Address: 142.250.148.105 / # wget www.google.com wget: bad address 'www.google.com' / #
nslookup の結果から、AAAA クエリに対して NXDOMAIN が返却されていることがわかります。
これは上述の Node Local DNS Cache に加えられた設定変更によるものです。
Node Local DNS Cache はこの設定により、AAAA クエリに対して無条件に NXDOMAIN を返却しています。
wget の結果から、名前解決が失敗し、"bad address" のエラーとなっていることがわかります。
詳細は後述しますが、このとき musl libc は A + AAAA ペアクエリの一方 (ここでは AAAA クエリ) で NXDOMAIN が返却されたことにより、もう一方 (A クエリ) が成功する状況であってもクエリ全体を失敗とみなし、名前解決に失敗しています。
ちなみに、GKE 1.21 (+ Node Local DNS Cache) 、または Node Local DNS Cache なしの GKE 1.22 であれば、Alpine 3.13+ のコンテナも名前解決に成功します。
以下に Node Local DNS Cache なしの GKE 1.22 の環境における実行結果を示します。
$ kubectl run alpine313 --image=alpine:3.13 --tty -i sh If you don't see a command prompt, try pressing enter. / # nslookup www.google.com Server: 10.93.0.10 Address: 10.93.0.10:53 Non-authoritative answer: Name: www.google.com Address: 173.194.197.104 Name: www.google.com Address: 173.194.197.147 Name: www.google.com Address: 173.194.197.106 Name: www.google.com Address: 173.194.197.99 Name: www.google.com Address: 173.194.197.103 Name: www.google.com Address: 173.194.197.105 Non-authoritative answer: Name: www.google.com Address: 2607:f8b0:4001:c5a::69 Name: www.google.com Address: 2607:f8b0:4001:c5a::67 Name: www.google.com Address: 2607:f8b0:4001:c5a::6a Name: www.google.com Address: 2607:f8b0:4001:c5a::63 / # wget www.google.com Connecting to www.google.com (173.194.192.104:80) saving to 'index.html' index.html 100% |**************************************************************************************************************************| 14119 0:00:00 ETA 'index.html' saved / #
NXDOMAIN
上述の挙動の差を生んでいる NXDOMAIN について少し掘り下げてみましょう。
NXDOMAIN の定義を詳細化している RFC 8020 によれば、NXDOMAIN は「その名前にいかなる型のレコードも存在しない」ことを意味します。
アドレスファミリが AF_UNSPEC の場合、名前解決のために A クエリと AAAA クエリで計 2 回のクエリが発行されることになりますが、どちらか 1 回目のクエリで NXDOMAIN によってその名前に対するレコードの非存在を確認できれば、クエリの試行回数を最適化することができます。
一方、「その名前に他の型のレコード、もしくはサブドメインが存在し得る」という場合には、NXDOMAIN ではなく NODATA を返却する必要があります(NODATA は疑似 RCODE であり、実際には NOERROR + 空の Answer セクションの返却となります)。
これは RFC 8020 の 3.1. Updates to RFC 1034 で定義されています。
This document clarifies possible ambiguities in [RFC1034] that did not clearly distinguish Empty Non-Terminal (ENT) names ([RFC7719]) from nonexistent names, and it refers to subsequent documents that do. ENTs are nodes in the DNS that do not have resource record sets associated with them but have descendant nodes that do. The correct response to ENTs is NODATA (i.e., a response code of NOERROR and an empty answer section). Additional clarifying language on these points is provided in Section 7.16 of [RFC2136] and in Sections 2.2.2 and 2.2.3 of [RFC4592].
musl libc の作者であり現在も主要メンテナである Rich Felker 氏の下記 Issue のコメントでも、この挙動とセマンティクスの対応関係が言及されています。
したがって、musl libc 1.2 系の「A + AAAA ペアクエリの一方が成功する (Answer セクションで有効な IP アドレスの回答が得られる) 状況であったとしても、もう一方で NXDOMAIN が返却された場合にクエリ全体を失敗とみなす挙動」は、特殊なものではなく、DNS のセマンティクスに沿った挙動であると言えそうです。
一方、他の DNS クライアントでは、ペアクエリの一方で NXDOMAIN が返却されたとしても、もう一方のクエリで有効な IP アドレスが得られればこれを名前解決の結果とする実装もあるようです。
ライブラリによって異なる名前解決の挙動
Alpine は、軽量な Linux ディストリビューションとして GKE などのコンテナ実行環境でよく使われています。
しかし、Alpine の元々の用途は組み込み系であり、BusyBox と musl libc をベースとしているため、他の Linux ディストリビューションの多くが採用している glibc などとは実装が異なります。
musl libc, glibc はそれぞれのライブラリにネットワークの共通的な機能を備えており、どちらのライブラリを採用しているかで名前解決の際の挙動が変わってくるということになります。
musl libc, glibc のいずれでも構成可能な、Gentoo Linux を使って動作を確認してみます。
まずは Gentoo (5.10.90+) + musl libc 1.2.2 (musl-1.2.2-r7) の場合です。
$ kubectl run gentoo-musl --image=gentoo/stage3:amd64-musl-20220224 --tty -i sh If you don't see a command prompt, try pressing enter. sh-5.1# emerge --info !!! Section 'gentoo' in repos.conf has location attribute set to nonexistent directory: '/var/db/repos/gentoo' !!! Invalid Repository Location (not a dir): '/var/db/repos/gentoo' WARNING: One or more repositories have missing repo_name entries: /var/db/repos/gentoo/profiles/repo_name NOTE: Each repo_name entry should be a plain text file containing a unique name for the repository on the first line. !!! It seems /run is not mounted. Process management may malfunction. Portage 3.0.30 (python 3.9.9-final-0, unavailable, gcc-11.2.0, musl-1.2.2-r7, 5.10.90+ x86_64) ================================================================= System uname: Linux-5.10.90+-x86_64-Intel-R-_Xeon-R-_CPU_@_2.20GHz-with-libc KiB Mem: 4026068 total, 131520 free KiB Swap: 0 total, 0 free sh bash 5.1_p16 ld GNU ld (Gentoo 2.37_p1 p2) 2.37 dev-lang/python: 3.9.9-r1::gentoo, 3.10.0_p1-r1::gentoo sys-devel/autoconf: 2.71-r1::gentoo sys-devel/automake: 1.16.4::gentoo sys-devel/binutils: 2.37_p1-r2::gentoo sys-devel/libtool: 2.4.6-r6::gentoo sys-kernel/linux-headers: 5.15-r3::gentoo (virtual/os-headers) Repositories: ACCEPT_LICENSE="* -@EULA" CFLAGS="-O2 -pipe" CHOST="x86_64-gentoo-linux-musl" CONFIG_PROTECT="/etc /usr/share/gnupg/qualified.txt" CONFIG_PROTECT_MASK="/etc/ca-certificates.conf /etc/env.d /etc/gentoo-release /etc/sandbox.d /etc/terminfo" CXXFLAGS="-O2 -pipe" DISTDIR="/var/cache/distfiles" FEATURES="assume-digests binpkg-docompress binpkg-dostrip binpkg-logs buildpkg-live config-protect-if-modified distlocks ebuild-locks fixlafiles ipc-sandbox merge-sync multilib-strict network-sandbox news parallel-fetch pid-sandbox preserve-libs protect-owned qa-unresolved-soname-deps sandbox sfperms strict unknown-features-warn unmerge-logs unmerge-orphans userfetch userpriv usersandbox usersync xattr" GENTOO_MIRRORS="http://distfiles.gentoo.org" PKGDIR="/var/cache/binpkgs" PORTAGE_TMPDIR="/var/tmp" USE="" Unset: ACCEPT_KEYWORDS, EMERGE_DEFAULT_OPTS, ENV_UNSET, PORTAGE_BINHOST, PORTAGE_BUNZIP2_COMMAND sh-5.1# wget www.google.com --2022-02-28 00:10:30-- http://www.google.com/ Resolving www.google.com... failed: Name does not resolve. wget: unable to resolve host address 'www.google.com' sh-5.1# nslookup www.google.com sh: nslookup: command not found sh-5.1# emerge --sync !!! It seems /run is not mounted. Process management may malfunction. >>> Syncing repository 'gentoo' into '/var/db/repos/gentoo'... * Using keys from /usr/share/openpgp-keys/gentoo-release.asc * Refreshing keys via WKD ... [ ok ] !!! getaddrinfo failed for 'rsync.gentoo.org': [Errno -2] Name does not resolve >>> Starting rsync with rsync://rsync.gentoo.org/gentoo-portage... rsync: getaddrinfo: rsync.gentoo.org 873: Name does not resolve rsync error: error in socket IO (code 10) at clientserver.c(137) [Receiver=3.2.3] >>> Retrying... !!! Exhausted addresses for rsync.gentoo.org * IMPORTANT: 12 news items need reading for repository 'gentoo'. * Use eselect news read to view new items. Action: sync for repo: gentoo, returned code = 1 sh-5.1#
Alpine のときと同様、wget に失敗します。
なお、nslookup はデフォルトでは含まれていないため、Portage を使ってインストールする必要がありますが、名前解決に失敗するため emerge コマンドが機能しません。
次は Gentoo (5.10.90+) + glibc (glibc-2.33-r7) の場合です。
$ kubectl run gentoo-glibc --image=gentoo/stage3:amd64-systemd-20220224 --tty -i sh If you don't see a command prompt, try pressing enter. sh-5.1# emerge --info !!! Section 'gentoo' in repos.conf has location attribute set to nonexistent directory: '/var/db/repos/gentoo' !!! Invalid Repository Location (not a dir): '/var/db/repos/gentoo' WARNING: One or more repositories have missing repo_name entries: /var/db/repos/gentoo/profiles/repo_name NOTE: Each repo_name entry should be a plain text file containing a unique name for the repository on the first line. !!! It seems /run is not mounted. Process management may malfunction. Portage 3.0.30 (python 3.9.9-final-0, unavailable, gcc-11.2.0, glibc-2.33-r7, 5.10.90+ x86_64) ================================================================= System uname: Linux-5.10.90+-x86_64-Intel-R-_Xeon-R-_CPU_@_2.20GHz-with-glibc2.33 KiB Mem: 4026068 total, 128684 free KiB Swap: 0 total, 0 free sh bash 5.1_p16 ld GNU ld (Gentoo 2.37_p1 p2) 2.37 dev-lang/python: 3.9.9-r1::gentoo, 3.10.0_p1-r1::gentoo sys-devel/autoconf: 2.71-r1::gentoo sys-devel/automake: 1.16.4::gentoo sys-devel/binutils: 2.37_p1-r2::gentoo sys-devel/libtool: 2.4.6-r6::gentoo sys-kernel/linux-headers: 5.15-r3::gentoo (virtual/os-headers) Repositories: ACCEPT_LICENSE="* -@EULA" CFLAGS="-O2 -pipe" CONFIG_PROTECT="/etc /usr/share/gnupg/qualified.txt" CONFIG_PROTECT_MASK="/etc/ca-certificates.conf /etc/env.d /etc/gentoo-release /etc/sandbox.d /etc/terminfo" CXXFLAGS="-O2 -pipe" DISTDIR="/var/cache/distfiles" FEATURES="assume-digests binpkg-docompress binpkg-dostrip binpkg-logs buildpkg-live config-protect-if-modified distlocks ebuild-locks fixlafiles ipc-sandbox merge-sync multilib-strict network-sandbox news parallel-fetch pid-sandbox preserve-libs protect-owned qa-unresolved-soname-deps sandbox sfperms strict unknown-features-warn unmerge-logs unmerge-orphans userfetch userpriv usersandbox usersync xattr" GENTOO_MIRRORS="http://distfiles.gentoo.org" PKGDIR="/var/cache/binpkgs" PORTAGE_TMPDIR="/var/tmp" USE="" Unset: ACCEPT_KEYWORDS, CHOST, EMERGE_DEFAULT_OPTS, ENV_UNSET, PORTAGE_BINHOST, PORTAGE_BUNZIP2_COMMAND sh-5.1# wget www.google.com --2022-02-28 00:17:16-- http://www.google.com/ Resolving www.google.com... 108.177.111.106, 108.177.111.103, 108.177.111.105, ... Connecting to www.google.com|108.177.111.106|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: 'index.html' index.html [ <=> ] 13.91K --.-KB/s in 0s 2022-02-28 00:17:16 (36.6 MB/s) - 'index.html' saved [14243] sh-5.1# emerge --sync ... (略) ... sh-5.1# emerge net-dns/bind-tools -q ... >>> Verifying ebuild manifests >>> Emerging (1 of 1) net-dns/bind-tools-9.16.22::gentoo >>> Installing (1 of 1) net-dns/bind-tools-9.16.22::gentoo >>> Recording net-dns/bind-tools in "world" favorites file... ... sh-5.1# nslookup www.google.com Server: 10.120.0.10 Address: 10.120.0.10#53 Non-authoritative answer: Name: www.google.com Address: 173.194.194.104 Name: www.google.com Address: 173.194.194.147 Name: www.google.com Address: 173.194.194.105 Name: www.google.com Address: 173.194.194.103 Name: www.google.com Address: 173.194.194.99 Name: www.google.com Address: 173.194.194.106 ** server can't find www.google.com: NXDOMAIN sh-5.1#
こちらは wget に成功しています。
以上から、musl libc と glibc で名前解決に関する挙動が異なっていることがわかります。
バージョンが 3.12 までの Alpine であれば、GKE 1.22 + Node Local DNS Cache の環境であっても名前解決に成功します。
3.12 までの Alpine では musl libc 1.1 系が利用されており、1.2 系とは実装が異なるためです。
Alpine のリリースアナウンスでも 3.13 から musl libc 1.2 系 に変更されたことが記載されています。
musl libc のコミットを追いかけてみると、1.2.1 で A + AAAA ペアクエリに対する挙動が変更されていることを確認できます。
この変更は、初版の 1.2.0 にはバックポートされなかったようです。
なお、musl libc では getaddrinfo のデフォルトのアドレスファミリが AF_UNSPEC となっているため、変更しない限りは A + AAAA のペアクエリになります。
回避手段
では GKE 1.22 において、Alpine 3.13+ のコンテナが名前解決を行えるようにするにはどうすればよいでしょうか。
以下のような手段が考えられます。
- クラスタの Node Local DNS Cache を無効化する
- 該当するコンテナの resolv.conf を書き換えて Node Local DNS Cache を迂回する
- Alpine の使用をやめる
いずれもクラスタやアプリケーションの構成変更が必要になるため、GKE ユーザとしては対処が悩ましいところですね。
1 はキャッシュが効かなくなるため性能面の影響が懸念されるでしょう。
2 は設定の変更対象が多いと大変ですし、その他の NW 要件との干渉で適切に迂回できない場合も考えられます。
3 はユーザアプリケーションなら検討の余地があるかもしれませんが、ミドルウェアのコンテナが Alpine 3.13+ を使用している場合には対処が困難です。
Node Local DNS Cache の Corefile の設定を書き換えることも可能ですが、kube-system Namespace 内のリソースはコントロールプレーンの一部として Google 側が管理・変更を行うため、手動で書き換えたとしても再び上書きされてしまう可能性があります。
GKE 1.22 の Node Local DNS Cache のデフォルト設定が、AAAA クエリに対して NODATA で応答する、もしくは kube-dns / Cloud DNS への再帰問い合わせを許可してくれるようになれば、根本的な解決となるかもしれません。
なお、GKE Autopilot では Node Local DNS Cache が強制的に有効化されるため、Autopilot 環境の Alpine ユーザは軒並みこの影響を受けることになると思われます。
こちらは resolv.conf で迂回するか、Alpine の使用をやめるくらいしか対応方法がなさそうですね。
まとめ
以下の相性問題により、GKE 1.22 で Node Local DNS Cache を有効化すると 3.13 以上の Alpine が名前解決に失敗することを確認しました。
- GKE 1.22 の Node Local DNS Cache (1.21.4-gke.0) は AAAA に対して一律 NXDOMAIN を返す設定が追加されている
- Alpine 3.13+ は musl libc 1.2.1+ を使用しており、A + AAAA ペアクエリのいずれかで NXDOMAIN が返るとクエリ全体が失敗したものとして扱われる
お読み頂いた方のお役に立てば幸いです。