• 本地 DNS 缓存
    • 为什么需要本地 DNS 缓存
    • 原理
    • IVPS 模式下需要修改 kubelet 参数
    • IPVS 模式下部署方法
    • 参考资料

    本地 DNS 缓存

    为什么需要本地 DNS 缓存

    • 减轻集群 DNS 解析压力,提高 DNS 性能
    • 避免 netfilter 做 DNAT 导致 conntrack 冲突引发 DNS 5 秒延时

      镜像底层库 DNS 解析行为默认使用 UDP 在同一个 socket 并发 A 和 AAAA 记录请求,由于 UDP 无状态,两个请求可能会并发创建 conntrack 表项,如果最终 DNAT 成同一个集群 DNS 的 Pod IP 就会导致 conntrack 冲突,由于 conntrack 的创建和插入是不加锁的,最终后面插入的 conntrack 表项就会被丢弃,从而请求超时,默认 5s 后重试,造成现象就是 DNS 5 秒延时; 底层库是 glibc 的容器镜像可以通过配 resolv.conf 参数来控制 DNS 解析行为,不用 TCP 或者避免相同五元组并发(使用串行解析 A 和 AAAA 避免并发或者使用不同 socket 发请求避免相同源端口),但像基于 alpine 镜像的容器由于底层库是 musl libc,不支持这些 resolv.conf 参数,也就无法规避,所以最佳方案还是使用本地 DNS 缓存。

    原理

    本地 DNS 缓存以 DaemonSet 方式在每个节点部署一个使用 hostNetwork 的 Pod,创建一个网卡绑上本地 DNS 的 IP,本机的 Pod 的 DNS 请求路由到本地 DNS,然后取缓存或者继续使用 TCP 请求上游集群 DNS 解析 (由于使用 TCP,同一个 socket 只会做一遍三次握手,不存在并发创建 conntrack 表项,也就不会有 conntrack 冲突)

    IVPS 模式下需要修改 kubelet 参数

    有两点需要注意下:

    1. ipvs 模式下需要改 kubelet --cluster-dns 参数,指向一个非 kube-dns service 的 IP,通常用 169.254.20.10,Daemonset 会在每个节点创建一个网卡绑这个 IP,Pod 向本节点这个 IP 发 DNS 请求,本机 DNS 再代理到上游集群 DNS
    2. iptables 模式下不需要改 kubelet --cluster-dns 参数,Pod 还是向原来的集群 DNS 请求,节点上有这个 IP 监听,被本机拦截,再请求集群上游 DNS (使用集群 DNS 的另一个 CLUSTER IP,来自事先创建好的 Service,跟原集群 DNS 的 Service 有相同的 selector 和 endpoint)

    ipvs 模式下必须修改 kubelet 参数的原因是:如果不修改,DaemonSet Pod 在本机创建了网卡,会绑跟集群 DNS 的 CLUSTER IP, 但 kube-ipvs0 这个 dummy interface 上也会绑这个 IP (这是 ipvs 的机制,为了能让报文到达 INPUT 链被 ipvs 处理),所以 Pod 请求集群 DNS 的报文最终还是会被 ipvs 处理, DNAT 成集群 DNS 的 Pod IP,最终路由到集群 DNS,相当于本机 DNS 就没有作用了。

    IPVS 模式下部署方法

    这里我们假设是 ipvs 模式,下面给出本地 DNS 缓存部署方法。

    创建 ServiceAccount 与集群上游 DNS 的 Service:

    1. cat <<EOF | kubectl apply -f -
    2. apiVersion: v1
    3. kind: ServiceAccount
    4. metadata:
    5. name: node-local-dns
    6. namespace: kube-system
    7. labels:
    8. kubernetes.io/cluster-service: "true"
    9. addonmanager.kubernetes.io/mode: Reconcile
    10. ---
    11. apiVersion: v1
    12. kind: Service
    13. metadata:
    14. name: kube-dns-upstream
    15. namespace: kube-system
    16. labels:
    17. k8s-app: kube-dns
    18. kubernetes.io/cluster-service: "true"
    19. addonmanager.kubernetes.io/mode: Reconcile
    20. kubernetes.io/name: "KubeDNSUpstream"
    21. spec:
    22. ports:
    23. - name: dns
    24. port: 53
    25. protocol: UDP
    26. targetPort: 53
    27. - name: dns-tcp
    28. port: 53
    29. protocol: TCP
    30. targetPort: 53
    31. selector:
    32. k8s-app: kube-dns
    33. EOF

    获取 kube-dns-upstream 的 CLUSTER IP:

    1. UPSTREAM_CLUSTER_IP=$(kubectl -n kube-system get services kube-dns-upstream -o jsonpath="{.spec.clusterIP}")

    部署 DaemonSet:

    1. cat <<EOF | kubectl apply -f -
    2. apiVersion: v1
    3. kind: ConfigMap
    4. metadata:
    5. name: node-local-dns
    6. namespace: kube-system
    7. labels:
    8. addonmanager.kubernetes.io/mode: Reconcile
    9. data:
    10. Corefile: |
    11. cluster.local:53 {
    12. errors
    13. cache {
    14. success 9984 30
    15. denial 9984 5
    16. }
    17. reload
    18. loop
    19. bind 169.254.20.10
    20. forward . ${UPSTREAM_CLUSTER_IP} {
    21. force_tcp
    22. }
    23. prometheus :9253
    24. health 169.254.20.10:8080
    25. }
    26. in-addr.arpa:53 {
    27. errors
    28. cache 30
    29. reload
    30. loop
    31. bind 169.254.20.10
    32. forward . ${UPSTREAM_CLUSTER_IP} {
    33. force_tcp
    34. }
    35. prometheus :9253
    36. }
    37. ip6.arpa:53 {
    38. errors
    39. cache 30
    40. reload
    41. loop
    42. bind 169.254.20.10
    43. forward . ${UPSTREAM_CLUSTER_IP} {
    44. force_tcp
    45. }
    46. prometheus :9253
    47. }
    48. .:53 {
    49. errors
    50. cache 30
    51. reload
    52. loop
    53. bind 169.254.20.10
    54. forward . /etc/resolv.conf {
    55. force_tcp
    56. }
    57. prometheus :9253
    58. }
    59. ---
    60. apiVersion: apps/v1
    61. kind: DaemonSet
    62. metadata:
    63. name: node-local-dns
    64. namespace: kube-system
    65. labels:
    66. k8s-app: node-local-dns
    67. kubernetes.io/cluster-service: "true"
    68. addonmanager.kubernetes.io/mode: Reconcile
    69. spec:
    70. updateStrategy:
    71. rollingUpdate:
    72. maxUnavailable: 10%
    73. selector:
    74. matchLabels:
    75. k8s-app: node-local-dns
    76. template:
    77. metadata:
    78. labels:
    79. k8s-app: node-local-dns
    80. spec:
    81. priorityClassName: system-node-critical
    82. serviceAccountName: node-local-dns
    83. hostNetwork: true
    84. dnsPolicy: Default # Don't use cluster DNS.
    85. tolerations:
    86. - key: "CriticalAddonsOnly"
    87. operator: "Exists"
    88. containers:
    89. - name: node-cache
    90. image: k8s.gcr.io/k8s-dns-node-cache:1.15.7
    91. resources:
    92. requests:
    93. cpu: 25m
    94. memory: 5Mi
    95. args: [ "-localip", "169.254.20.10", "-conf", "/etc/Corefile", "-upstreamsvc", "kube-dns-upstream" ]
    96. securityContext:
    97. privileged: true
    98. ports:
    99. - containerPort: 53
    100. name: dns
    101. protocol: UDP
    102. - containerPort: 53
    103. name: dns-tcp
    104. protocol: TCP
    105. - containerPort: 9253
    106. name: metrics
    107. protocol: TCP
    108. livenessProbe:
    109. httpGet:
    110. host: 169.254.20.10
    111. path: /health
    112. port: 8080
    113. initialDelaySeconds: 60
    114. timeoutSeconds: 5
    115. volumeMounts:
    116. - mountPath: /run/xtables.lock
    117. name: xtables-lock
    118. readOnly: false
    119. - name: config-volume
    120. mountPath: /etc/coredns
    121. - name: kube-dns-config
    122. mountPath: /etc/kube-dns
    123. volumes:
    124. - name: xtables-lock
    125. hostPath:
    126. path: /run/xtables.lock
    127. type: FileOrCreate
    128. - name: kube-dns-config
    129. configMap:
    130. name: kube-dns
    131. optional: true
    132. - name: config-volume
    133. configMap:
    134. name: node-local-dns
    135. items:
    136. - key: Corefile
    137. path: Corefile.base
    138. EOF

    验证是否启动:

    1. $ kubectl -n kube-system get pod -o wide | grep node-local-dns
    2. node-local-dns-2m9b6 1/1 Running 0 15m 10.0.0.28 10.0.0.28
    3. node-local-dns-qgrwl 1/1 Running 0 15m 10.0.0.186 10.0.0.186
    4. node-local-dns-s5mhw 1/1 Running 0 51s 10.0.0.76 10.0.0.76

    我们需要替换 kubelet 的 --cluster-dns 参数,指向 169.254.20.10 这个 IP。

    在TKE上,对于存量节点,登录节点执行以下命令:

    1. sed -i '/CLUSTER_DNS/c\CLUSTER_DNS="--cluster-dns=169.254.20.10"' /etc/kubernetes/kubelet
    2. systemctl restart kubelet

    对于增量节点,可以将上述命令放入新增节点的 user-data,以便加入节点后自动执行。

    后续新增才会用到本地 DNS 缓存,对于存量 Pod 可以销毁重建,比如改下 Deployment 中 template 里的 annotation,触发 Deployment 所有 Pod 滚动更新,如果怕滚动更新造成部分流量异常,可以参考 服务更新最佳实践

    参考资料

    • https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns
    • https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/20190424-NodeLocalDNS-beta-proposal.md