varu3/techBlog

IT infrastructure technology memo.

Kubernetesの名前解決を確認する

最近ではドキュメントも充実した感のあるKubernetesですが、実際の挙動ってどうなっているの?という部分に関しては ドキュメントを漁って読むに加えて、実際に手を動かして調べてみることでだいぶ理解が深まっていく感覚があります。 というわけで今回のテーマはKubernetesにおけるPodの名前解決はどのように動作しているのか、です。

公式ドキュメントのDNS for Services and Pods - Kubernetesを実際に手を動かしながら確認していきます。

Serviceリソースの名前解決

それでは早速シンプルな構成のk8s環境を用意して、その挙動を確認していきます。

Node: 1つ Master: 1つ

環境はGKEでも何でもいいのですが、今回はKOPSを使ってAWS上にk8s環境を用意することにしました。 Kopsでクラスタを構成すると以下の様にpodが作成されます。

$ kubectl get pods -l k8s-app=kube-dns -n kube-system
NAME                        READY     STATUS    RESTARTS   AGE
kube-dns-56cf5cb57d-7gghr   3/3       Running   0          4d
kube-dns-56cf5cb57d-n47jl   3/3       Running   0          4d

kube-dnsというpodが作成されていることがわかります。まずは適当なpodを作ってみます。 以下のbusybox-pod.yamlを用意しました。

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
$ kubectl create -f busybox-pod.yaml
$ kubectl get pods
NAME            READY     STATUS    RESTARTS   AGE
busybox   1/1       Running   0          2m

またServiceリソースも作成しておきます。

apiVersion: v1
kind: Service
metadata:
  name: busybox-svc
  labels:
    app: busybox-svc
spec:
  ports:
  - protocol: TCP
    name: busybox-svc-port
    port: 1234
    targetPort: 5678
  selector:
    app: busybox
$ kubectl create -f busybox-svc.yaml

以下のように作成されました。

$ kubectl get svc --all-namespaces
NAMESPACE     NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
default       busybox-svc   ClusterIP   100.64.233.34   <none>        6379/TCP        1m
default       kubernetes    ClusterIP   100.64.0.1      <none>        443/TCP         5d
kube-system   kube-dns      ClusterIP   100.64.0.10     <none>        53/UDP,53/TCP   5d

このpodに入って諸々の設定を確認してみます。

$ kubectl exec -it busybox -- /bin/sh
/ # cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
100.103.66.147  busybox

/ # cat /etc/resolv.conf
nameserver 100.64.0.10
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-1.compute.internal
options ndots:5

/etc/hostsにある100.103.66.147 busyboxはこのpodのIPアドレスとホスト名(pod名)ですね。
resolv.confに書いてあるnameserver 100.64.0.10はkube-dnsのserviceのClusterIPとなります。
searchには、namespace名.svc.cluster.localとAWSのリージョン別のドメイン名が定義されていることがわかります。
searchに設定したドメインはその名前をつけてサーチしてくれるという意味となるので、
例えばnslookup hogehogeとした場合には

  • hogehoge.default.svc.cluster.local
  • hogehoge.svc.cluster.local
  • hogehoge.cluster.local

を順に検索してくれるということになります

それでは、作成したserviceリソースを名前解決してみます。
busyboxとは別のpodを作ります。

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  labels:
    name: dnsutils
spec:
  containers:
  - name: dnsutils
    image: tutum/dnsutils
    command:
    - sleep
    - "3600"
$ kubectl create -f dnsutil.yaml
$ kubectl exec -it dnsutils -- /bin/sh
/# nslookup busybox-svc
Server:     100.64.0.10
Address:    100.64.0.10#53

Name:   busybox-svc.default.svc.cluster.local
Address: 100.64.233.34

busybox-svcで設定されている100.64.233.34が返されました。
これでIPアドレスまでは特定できましたが、PORTはどのように通知されるのでしょうか。
kubernetesではkube-dnsにサービス名、protocol, port名, プロトコルを指定してSRVレコードを問い合わせるとポート番号が返されます。

/# nslookup
> set type=SRV
> _busybox-svc-port._tcp.busybox-svc.default.svc.cluster.local
Server:     100.64.0.10
Address:    100.64.0.10#53

_busybox-svc-port._tcp.busybox-svc.default.svc.cluster.local    service = 10 100 1234 busybox-svc.default.svc.cluster.local.

service = 10 100 1234 busybox-svc.default.svc.cluster.local.が返ってきました。 これはservice = [Priority] [Weight] [Port] [Target]という定義なのでPortが1234となり、 Serviceで指定したPortとなっていることがわかります。

ここまでをまとめると、

  • resolve.confに定義されるnameserverはkube-dnsのserviceリソースのCluster-IPが設定される
  • resolve.confに定義されるsearchにはnamespace名.svc.cluster.local...が設定される
  • nslookupでサービス名を問い合わせると、kube-dnsからIPアドレスを返される
  • nslookupでサービス名、protocol, port名, プロトコルを指定してSRVレコードを問い合わせるとポート名が返されます。

ということがわかりました。

別のnamespaceでの名前解決

今まではnamespace: defaultでの名前解決を実行していましたが、別のnamespaceではどうでしょうか。

新しい名前空間を作ります。

$ kubectl create ns hogehoge

先ほどのyamlを書き換えて、podとsvcを同じように作成します。

apiVersion: v1
kind: Pod
metadata:
  name: busybox-hogehoge
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox-hogehoge
  restartPolicy: Always
busybox-svc-hogehoge.yaml
apiVersion: v1
kind: Service
metadata:
  name: busybox-svc-hogehoge
  labels:
    app: busybox-svc-hogehoge
  namespace: hogehoge
spec:
  ports:
  - protocol: TCP
    name: busybox-svc-hogehoge-port
    port: 1234
    targetPort: 5678
  selector:
    app: busybox

そして、このような状態となります。

$ kubectl get svc -n hogehoge
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
busybox-svc-hogehoge   ClusterIP   100.64.81.224   <none>        1234/TCP   1m

$ kubectl get pod -n hogehoge
NAME            READY     STATUS    RESTARTS   AGE
busybox-hogehoge   1/1       Running   0          2m

この状態でnamespace: defaultにあるdnsutilで名前解決をしてみます。

/# nslookup busybox-svc-hogehoge
Server:     100.64.0.10
Address:    100.64.0.10#53

** server can't find busybox-svc-hogehoge: NXDOMAIN

別のnamespaceの名前解決はできません。 下のようにサービス名にnamespaceをくっつけると返ってくるようになりました。

root@dnsutils:/# nslookup busybox-svc-hogehoge.hogehoge
Server:     100.64.0.10
Address:    100.64.0.10#53

Name:   busybox-svc-hogehoge.hogehoge.svc.cluster.local
Address: 100.64.81.224

これは、前述のresolve.confの設定でsearchの項目で設定されている値を考えれば、こうなるのも頷けます。

つまり

  • サービス名 + namespacesで他のnamespaceのサービスの名前解決ができる
    • resolve.confのsearchの項目で制御している

ということがわかります。

Podの名前解決

ここまではKubernetesのServiceの名前解決を見ていましたが、続いてpodの名前解決をみていきます。
podのDNSのAレコードは以下のように登録されています。
pod-ip-address.my-namespace.pod.cluster.local

例えば、1.2.3.4のpodがnamespace: defaultに置かれていた場合、
1-2-3-4.default.pod.cluster.localという名前で登録されています。

実際に確認してみます。先ほどのdnsutilに入って、podの名前解決をしてみます。

$ kubectl exec -it dnsutils -- /bin/bash
# nslookup 100-103-66-157.default.pod.cluster.local
Server:     100.64.0.10
Address:    100.64.0.10#53

Name:   100-103-66-157.default.pod.cluster.local
Address: 100.103.66.157

以上のようにPodのIPが返ってくることがわかりました。

PodのDNSポリシー

Kubernetesでは現在、以下の方式でPod単位でDNSの設定することができます。 これらのDNSポリシーはPodのspec:dnsPolicyフィールドで指定します。 それぞれのDNSポリシーを確認します。

  • "Default": Podが動作しているNodeから名前解決の設定を継承する。
  • "ClusterFirst": kube-dnsに設定されているstubDomainsやupstreamNameserversを参照する。そうでなければ、Nodeから名前解決の設定を継承する
  • "ClusterFirstWithHostNet": NodeとPodを同じサブネットにおくhostNetworkが有効な場合はこれを設定する必要がある
  • "None": Kubernetes1.9から追加された機能でPodのDNS設定はdnsConfigフィールドのみを使用して提供される。

※ Defaultは、デフォルトDNSポリシーではありません。DNSポリシーを指定しない場合、 "ClusterFirst"が選択されます。

実際にそれぞれのDNSポリシーを適用してみます。それぞれのポリシーを設定したPodを用意してみます。 まずはDefaultから。

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  dnsPolicy: Default
  restartPolicy: Always
$ kubectl create -f busybox-default.yaml

中に入って確認してみます

$ kubectl exec -it busybox-default -- /bin/sh
/ # cat /etc/resolv.conf
nameserver XX.XX.XX.2 (AWSのVPCのネームサーバのIPアドレス)
search XXXX.compute.internal

このように確かにpodが存在しているNodeと同じ設定が入っているようです。 そして、resolve.confの設定が書き換わっていますので、クラスター内部の名前解決はできない状態となってしまいます。

/ # nslookup kubernetes.default
Server:    XX.XX.XX.2
Address 1: .XX.XX.XX.2 <nodeのhostname>.<zone>.internal

nslookup: can't resolve 'kubernetes.default'
/ #
/ #
/ # nslookup busybox-svc.default.svc.cluster.local
Server:    XX.XX.XX.2
Address 1: .XX.XX.XX.2 <nodeのhostname>.<zone>.internal

nslookup: can't resolve 'busybox-svc.default.svc.cluster.local'

続いてClusterFirstですが、こちらは何も設定しなければこのポリシーが適用されるため、 前回のKubernetes内部の名前解決と同じ設定が反映されます。確認は割愛します 前述したこちらの設定が含まれることになります。

$ kubectl exec -it busybox -- /bin/sh
/ # cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
100.103.66.147  busybox

/ # cat /etc/resolv.conf
nameserver 100.64.0.10
search default.svc.cluster.local svc.cluster.local cluster.local XXXX.compute.internal
options ndots:5

続いて、ClusterFirstWithHostNetです。まずそもそもHostNetworkをtrueにする必要があります。HostNetwork設定とは、Nodeのネットワークインターフェースを直接参照するための設定です。 例えば、以下のようにHostNetworkをtrueにして、dnsPolicyをClusterFirstWithHostNetに設定してみます。

apiVersion: v1
kind: Pod
metadata:
  name: busybox-clusterfirstwithhostnet
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox-clusterfirstwithhostnet
  dnsPolicy: ClusterFirstWithHostNet
  hostNetwork: true
  restartPolicy: Always

podを作成してみます。

$ kubectl create -f busybox-clusterfirstwithhostnet.yaml
$ kubectl get pods -o wide

PodのIPがNODEのIPと同一のものになっていることがわかります。 このpodのDNSの設定はどのようになっているのかを確認してみましょう。

$kubectl exec -it busybox-clusterfirstwithhostnet -- /bin/sh/ 
# cat /etc/resolv.conf
nameserver 100.64.0.10
search default.svc.cluster.local svc.cluster.local cluster.local XXXX.compute.internal
options ndots:5

ClusterFirstと同じ設定が入っています。 確かにこちらの[コード](https://github.com/kubernetes/kubernetes/blob/317853c90c674920bfbbdac54fe66092ddc9f15f/pkg/kubelet/network/dns/dns.go#L268をみてみると、ClusterFirstと同じ設定が入るようになっています。

そして最後にdnsPolicyがNoneの場合です。

この場合、クラスタ内のkube-dnsのconfigmapにstub-domainとupstream DNS serversを設定することができます。 また、pod個別にDNSへ設定することもできます。

例として、以下のようなConfigMapとなります。

apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-dns
  namespace: kube-system
data:
  stubDomains: |
    {"acme.local": ["1.2.3.4"]}
  upstreamNameservers: |
    ["8.8.8.8", "8.8.4.4"]

pod個別に設定する場合は以下のようになります。

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: busybox-none
spec:
  containers:
    - name: busybox-none
      image: busybox
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

実際にみてみます。

$ kubectl exec -it busybox -- cat /etc/resolv.conf
nameserver 1.2.3.4
search ns1.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0

この設定では、acme.localを持ったDNS要求は1.2.3.4でlistenされているDNSに転送されます。 それ以外は8.8.8.8と8.8.4.4に転送されるという設定となります。

dnsPolicyによってそれぞれのresolve.confが書き換わるということがわかりました。

終わりに

KubernetesのDNS設定は多岐に渡り、それがyamlファイルに抽象化されていてわかりにくいところがありましたが、 実際に確認してみますと、結局のところ、podでのconfを書き換えているだけということを理解することができました。 このほかにもKuberenetesのドキュメントには多くの設定項目がありますが、理解が難しい時にはこのように手を動かして確認して行きたいですね。

参考