kubernetes the hard way on PC (１４．CoreDNS)

#初めに
「[kubernetes the hard way](https://github.com/kelseyhightower/kubernetes-the-hard-way) を PC でやってみる」の１４回目、「CoreDNS」についてです。 ([目次](https://qiita.com/shimauma_Zzzzz/items/84ecf5c748ceafd05380#%E8%AA%AC%E6%98%8E%E8%A8%98%E4%BA%8B%E7%9B%AE%E6%AC%A1))


今回は CoreDNS です。
DNS は Control plane のコンポーネントですが、
kubernetes the hard way では POD としてワーカーノード上にデプロイします。

この回もハマりました、が、基本的には前回のネットワークの問題がほとんどだったので、
ネットワークさえ問題なければ順当に終わると思います。

やることは以下の通りです。　**今回は Control plane ノードの1台で実施します**

- CoreDNS のデプロイ
- 検証

# CoreDNS のデプロイ

kubernetes のドキュメントや kubernetes the hard way では、 NW 周りや CoreDNS では
外部にある yaml ファイルを apply するだけ、という構成になっています。
確かにそれだけでできるのはすごいことではありますが、　
やっているほうはなんとなくできた、という感じになってしまいます。
ここでは、一旦 デプロイしたうえで、 yaml の中身も少し見ておきたいと思います。
（私のわかる範囲で…）

Core DNS のデプロイは kubernetes the hard way と同じです。

まず、ダウンロードしておきます。

```shell-session
# curl -o coredns.yaml https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3715  100  3715    0     0   5415      0 --:--:-- --:--:-- --:--:--  5415
```

次にそれを apply します。（もちろん、kubernetes the hard way 通り、いきなりapply してもよいです）

```shell-session
# kubectl apply -f coredns.yaml

serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
```

サービスアカウント、ClusterRole、ClusterRoleBinding、 ConfigMap、Deployment、 Service が作成されているのが見て取れます。
kube-system の namespace に coredns の POD が2つできて、 それぞれ  1/1  Running となっていれば OK です。


```shell-session
# kubectl get pods --namespace kube-system -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE         NOMINATED NODE   READINESS GATES
coredns-68567cdb47-8s42p   1/1     Running   0          46s   10.200.0.2   k8sworker2   <none>           <none>
coredns-68567cdb47-wnh6c   1/1     Running   0          46s   10.200.0.2   k8sworker0   <none>           <none>
```

#検証

yaml ファイルの中身を見る前に、先に検証を済ませておきます。
busybox の Pod を作成しておきます。
※さてここで**注意事項**ですが、下記の例では、さらっと `--image=busybox:1.28` としています。
これが実は非常に重要で、　**busybox の 1.28.4 より新しいバージョンでは CoreDNS の名前解決にエラーが出る** ようです。
私も最近なぜか CoreDNS がつれなくなった、、、と嘆いていましたが、 `--image=busybox` としていたため
最新版の busybox になっていました。。。
[Issue](https://github.com/kubernetes/kubernetes/issues/66924) が出ているようです。そしてまだ解消されていないようです。。。

```shell-session
# kubectl run --generator=run-pod/v1 busybox --image=busybox:1.28 --command -- sleep 3600
pod/busybox created

# kubectl get pods -l run=busybox -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          18s   10.200.2.87   k8sworker2   <none>           <none>
```

次に、 jsonpath で POD 名を取得します。 
（ kubernetes the hard way に沿って変数に入れていますが、別に POD 名をコピペしてもよいです）

```shell-session
# PODNAME=$( kubectl get pods -l run=busybox -o jsonpath="{.items[0].metadata.name}" )
# echo $PODNAME
busybox
```

該当の POD で nslookup kubernetes　を実行します。
kubernetes は API Server の Service 名でしたね。

```shell-session
# kubectl exec -it ${PODNAME} -- nslookup kubernetes
Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local
```

確かに、 service を確認しても 10.32.0.1 になっています。

```shell-session
# kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
kubernetes   ClusterIP   10.32.0.1    <none>        443/TCP   93d   <none>
```
ついでに kubernetes サービスの endpoint も観ておきましょう。　6443 は API Server のポートですね。

```shell-session
# kubectl get endpoints kubernetes
NAME         ENDPOINTS                                                        AGE
kubernetes   192.168.199.200:6443,192.168.199.201:6443,192.168.199.202:6443   94d
```

# CoreDNS 補足

## CoreDNS yaml ファイル補足
CoreDNS の yaml ファイルの補足です。　補足が不要であれば飛ばしてください。
いずれも　すべて kube-system ネームスペースに作成されています。

■まずサービスアカウントです
CoreDNS 用のユーザーアカウントのようなものです。
こんな簡単でいいんですね。

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: coredns
  namespace: kube-system
```

■ClusterRole

endpoints, services, pods, namespaces に対しては、 list, watch 
nodes については get できるようになっているロールです。　ClusterRole なので Cluster 全体に対して有効です。
ただ、それで何をしているのかはわかりませんね。。。
そこをサラサラ説明できるようになりたいものです。。

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:coredns
rules:
- apiGroups:
  - ""
  resources:
  - endpoints
  - services
  - pods
  - namespaces
  verbs:
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
```

■ClusterRoleBinding

前述の system:coredns という ClusterRole と最初に作成した coredns という名前の ServiceAccount を
`roleRef:` と `subjects:` のフィールドで関連付けています。
これで、 coredns サービスアカウントで、 node の get などができる、という話になります。

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:coredns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:coredns
subjects:
- kind: ServiceAccount
  name: coredns
  namespace: kube-system
```

■ConfigMap

さて見慣れない記述ですが、どうもこれが CoreDNS の設定ファイルのようです。
kubernetes の下記のドキュメントあたりに説明が記載されています。
[Customizing DNS Service](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/)、[Debugging DNS Resolution](https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/) 
また、Corefile については、[Corefile Explained](https://coredns.io/2017/07/23/corefile-explained/) に記載されています。

root ゾーン (.) の 53 番ポートで、｛｝の中が読み込まれている CoreDNS のプラグインです。
[kubernetes プラグイン](https://coredns.io/plugins/kubernetes/) も読み込まれていますね。
ここで、 cluster.local ゾーンについては 　同じネームスペースに実際に Pod がいるかいないかにかかわらず
A レコードとIP を返す設定 (insecure) になっているようです。
また、prometheus プラグインも入っており、CoreDNS のメトリックスを Prometheus に連携できるようですね。
プラグインの種類については [Plugins](https://coredns.io/plugins/) に詳しく記載されています。

さて、もう一つのポイントが `cluster.local` だと思います。
kubernetes 上で service を定義すると、  `<service_name>.<namespace>.svc.cluster.local` という名前が定義されます。

これは、kubernetes で決められている模様です。
詳しくは、「[ServiceとPodに対するDNS](https://kubernetes.io/ja/docs/concepts/services-networking/dns-pod-service/)」「[Kubernetes DNS-Based Service Discovery](https://github.com/kubernetes/dns/blob/master/docs/specification.md)」をご覧ください。
ただ、それがどうやって Pod の resolv.conf に入ってくるのかは「[PodのDNSポリシー](https://kubernetes.io/ja/docs/concepts/services-networking/dns-pod-service/#pod%E3%81%AEdns%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC)」が参考になります。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        cache 30
        loop
        reload
        loadbalance
    }
```

■Deployment 

ようやくなじみのある Deployment ですね。　長いです。
以下のような設定が入っていますね。

- `replicas:` が 2　
- `image:` に coredns/coredns:1.6.2
- `args` に `-conf`, `/etc/coredns/Corefile` を指定
- `/etc/coredns` に ReadOnly で `config-volume` ボリューム(ConfigMap)をマウント
- Readyness Probe, Liveness Probe を利用
- Port は 53/TCP, 53/UDP とメトリック用に 9153/TCP を公開

なお、 2020/08/10 時点では、 CoreDNS は 1.7.0 が最新のようです。 (2020/06/15 リリース)

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/name: "CoreDNS"
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: coredns
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      nodeSelector:
        beta.kubernetes.io/os: linux
      containers:
      - name: coredns
        image: coredns/coredns:1.6.2
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        args: [ "-conf", "/etc/coredns/Corefile" ]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
          readOnly: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9153
          name: metrics
          protocol: TCP
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - all
          readOnlyRootFilesystem: true
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
      dnsPolicy: Default
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
            - key: Corefile
              path: Corefile
```

■Service

サービスです。　一番のポイントは　`clusterIP: 10.32.0.10` と固定で IP がふられているところですね。
このIP は重要で、 kubelet の設定の中にも登場します。（`/var/lib/kubelet/kubelet-config.yaml`)
※kubernetes のドキュメントでは、 --cluster-dns=10.32.0.10  のように渡す形で記載がありますが、 
kubernetes the hard way では kubelet-config.yaml に記載されています。

このIP については、重要な割には kubernetes the hard way では説明がありません。。。　気にならないんですかね。。
IP 以外に、当然ですが Port も同様に 53/TCP, 53/UDP, 9153/TCP を公開しています。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "CoreDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.32.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
  - name: metrics
    port: 9153
    protocol: TCP
```

以上が CoreDNS の yaml ファイルの内容になります。
全てが理解できているわけではありませんが、概要を理解するだけでも違うと思います。

##resolv.conf 補足

名前解決の際、以下のようになっていたと思います。

```shell-session
# k exec -it busybox-686db6799-2bp82 -- nslookup kubernetes
Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local
```

ここで、 kubernetes の名前解決をすると、 kubernetes.default.svc.cluster.local と返ってきています。
<サービス名>.<ネームスペース名>.svc.cluster.local なのですが、
勝手に default.svc.cluster.local がつく理由は Pod の resolv.conf にあります。

```shell-session
/etc # cat resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.32.0.10
options ndots:5
```

ここに、 search ドメインとして、 `default.svc.cluster.local svc.cluster.local cluster.local` が
入っているため、 kubernetes.default.svc.cluster.local が検索されます。

また、 ndots:5 ですが、 これは "." の数が 5個以下の場合、先に search ドメインを付けたものを検索する、という設定です。
これがあると、（場合により）名前解決が非効率になりパフォーマンスが落ちることがあるようです。
詳細は　「[Kubernetes pods /etc/resolv.conf ndots:5 option and why it may negatively affect your application performances](https://pracucci.com/kubernetes-dns-resolution-ndots-options-and-why-it-may-affect-application-performances.html)」をご覧ください。






---
今回はここまでとして、次回は Smoke Test の部分を実施します。


← [１３．Pod Network](https://qiita.com/shimauma_Zzzzz/items/9a968bd1fac186586e37)
↑ [目次](https://qiita.com/shimauma_Zzzzz/items/84ecf5c748ceafd05380#%E8%AA%AC%E6%98%8E%E8%A8%98%E4%BA%8B%E7%9B%AE%E6%AC%A1)
→ [１５．Smoke Test](https://qiita.com/shimauma_Zzzzz/items/7ba1de95d9bf69e1bfde)

----