侧边栏壁纸
博主头像
枕头下放双臭袜子博主等级

今我何功德,曾不事农桑

  • 累计撰写 162 篇文章
  • 累计创建 30 个标签
  • 累计收到 0 条评论

k8s集群内部dns解析梳理排查(emqx通过k8s自动集群问题解决)

枕头下放双臭袜子
2022-04-26 / 0 评论 / 1 点赞 / 56 阅读 / 17,500 字 / 正在检测是否收录...

0、导

我司物联网接入平台使用的是EMQX,使用k8s Deployment方式部署。在我来之前,环境已经部署完成投入使用了。开源版的EMQX在使用过程中时不时的会出现些小Bug,导致Pod重启。

集群中已经部署的EMQX,在后续的使用过程中出现了这样的问题:重启的emqx副本无法自动加入已有emqx集群,导致物理设备在新emqx集群和旧emqx集群之间犹犹豫豫,进而出现连接缓慢,不时断开掉线的问题。

出了问题就要解决嘛,排查发现,正在使用的EMQX副本,EMQX_CLUSTER__K8S__APISERVER环境变量使用的是某台Master节点的8888端口,如下:

- name: EMQX_CLUSTER__K8S__APISERVER
  value: http://192.168.1.31:8888

这个8888端口整的我一头雾水,后来扒到官方文档后才发现是通过使用 kubectl proxy --accept-hosts="^.*$" --address='xxxx' -p=8888 &创建了一个反向代理,使EMQX通过此代理端口直接与集群apiServer进行交互的方式,这样的好处是免了验证的环节。但是这个放到后台的代理不知道什么时候就挂掉了,这样就导致每次重启的emqx副本没办法直接与k8s apiServer交互,也就造成了重启的emqx副本无法自动加入已有emqx集群的局面。

1、实现EMQX Pod重启自动加入集群

没扒到官方文档之前,我的想法是使用sts形式部署EMQX,去Dockerhub扒一下容器启动命令,自己定制化一个脚本,通过k8s资源清单的spec.template.spec.command覆盖掉容器的启动命令,并实现emqx pod根据sts的固定名称,一启动便加入已有集群。

在Dockerhub查找特定版本Dockerfile启动命令的时候,才发现emqx官方已经提供了通过 kubernetes 自动集群 EMQX MQTT 服务器的方法:

 # 修改 EMQX Broker 的配置,通过kubernetes实现EMQX自动集群
 cluster.discovery = k8s
 cluster.kubernetes.apiserver = https://apiserver.cluster.local:443   # 这里的apiserver内部域名地址是错误的,后文会讲到
 cluster.kubernetes.service_name = emqx-service
 cluster.kubernetes.address_type = ip
 cluster.kubernetes.app_name = emqx

cluster.kubernetes.apiserver 为 kubernetes apiserver 的地址,可以通过 kubectl cluster-info 命令获取
cluster.kubernetes.service_name 为EMQX Service 的 Name,即资源清单Service的metadata.name
cluster.kubernetes.app_name 为 EMQX Broker 的 node.name 中 @ 符号之前的部分,也就是Deploy/Sts的metadata.name

 # 以上配置都可以通过ENV环境变量修改对应配置
      - name: EMQX_NAME
        value: emqx
      - name: EMQX_CLUSTER__DISCOVERY
        value: k8s
      - name: EMQX_CLUSTER__K8S__APP_NAME
        value: emqx
      - name: EMQX_CLUSTER__K8S__SERVICE_NAME
        value: emqx-service
      - name: EMQX_CLUSTER__K8S__APISERVER
        value: "https://apiserver.cluster.local:443"    # 这里的apiserver内部域名地址是错误的,后文会讲到
      - name: EMQX_CLUSTER__K8S__NAMESPACE
        value: default
      - name: EMQX_CLUSTER__K8S__ADDRESS_TYPE
        value: ip

因为emqx pod需要与apiServer进行通信,要么通过创建一个proxy开放kubernetes apiServer的http接口,要么通过ServiceAccount、Role、RoleBinding配置RBAC鉴权,前者存在一定的安全隐患,所以不能为了方便放弃安全,所以还是推荐后者。

$ cat rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: default
  name: emqx
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: emqx
rules:
- apiGroups:
  - ""
  resources:
  - endpoints 
  verbs: 
  - get
  - watch
  - list
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: emqx
subjects:
  - kind: ServiceAccount
    name: emqx
    namespace: default
roleRef:
  kind: Role
  name: emqx
  apiGroup: rbac.authorization.k8s.io

2、Pod无法解析集群内ApiServer域名

文档和实际操作总是有区别的嘛,实际测试验证的时候发现,emqx pod报错无法连接apiServer的集群内域名[error] Ekka(AutoCluster): Discovery error: {failed_connect,[{to_address,{"apiserver.cluster.local",6443}},{inet,[inet],nxdomain}]},通过报错一眼就能看出来Pod无法解析该内部域名,更无法与集群apiServer进行通信,emqx自然成集群自然无从谈起

emqx_ctl_cluster

## 获取apiServer IP地址
$ kubectl get pod -n kube-system -owide  | grep api
kube-apiserver-qld        1/1     Running   32         23d   192.168.3.34     qld    <none>           <none>

$ kubectl exec -it -n emqx emqx-deployment-657d48b89d-6l42k -- /bin/sh

##  尝试ping集群外部域名
/opt/emqx $ sudo ping www.baidu.com
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
PING www.baidu.com (220.181.38.149): 56 data bytes
64 bytes from 220.181.38.149: seq=0 ttl=52 time=3.747 ms
64 bytes from 220.181.38.149: seq=1 ttl=52 time=3.769 ms
^C
--- www.baidu.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 3.747/3.758/3.769 ms

## Ping 集群内部 kube-apiserver
/opt/emqx $ sudo ping apiserver.cluster.local
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
ping: bad address 'apiserver.cluster.local'

## Ping 集群内部 域名
/opt/emqx $ sudo ping cluster.local
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
ping: bad address 'cluster.local'

## 直接 Ping 集群内部 kube-apiserver pod的 IP 地址
/opt/emqx $ sudo ping 192.168.3.34
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
PING 192.168.3.34 (192.168.3.34): 56 data bytes
64 bytes from 192.168.3.34: seq=0 ttl=64 time=0.120 ms
^C
--- 192.168.3.34 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.067/0.093/0.120 ms

## 使用 nslookup 命令查询外部域名信息
/opt/emqx $ nslookup www.baidu.com
Server:         10.96.0.10
Address:        10.96.0.10:53

Non-authoritative answer:
www.baidu.com   canonical name = www.a.shifen.com
Name:   www.a.shifen.com
Address: 220.181.38.150
Name:   www.a.shifen.com
Address: 220.181.38.149

Non-authoritative answer:
www.baidu.com   canonical name = www.a.shifen.com

## 使用 nslookup 命令查询内部apiServer域名信息
/opt/emqx $ nslookup apiserver.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10:53

** server can't find apiserver.cluster.local: NXDOMAIN
** server can't find apiserver.cluster.local: NXDOMAIN

/opt/emqx $ nslookup cluster.local
Server:         10.96.0.10
Address:        10.96.0.10:53

由上输出可以看出,Pod内可以ping通内、外网的所有IP地址,使用nslookup解析外网域名成功,解析apiServer的内部域名失败,所以经过以上测试可以排查掉集群网络插件问题。初步判断是coredns解析的问题。

3、k8s集群coreDns组件相关原理

在 kubernetes集群中,可以从 apiserver 中直接查询获取到 service 对应的后端 Endpoints信息。如果仅某个应用偶尔通过apiserver去查询Service后面的 Endpoints是没问题的,但是如果每个应用都在启动的时候去查询依赖的服务,这不但增加了应用的复杂度,也会导致应用过分依赖Kubernetes,耦合度太高,不具有通用性。

为了解决上面的问题,在早期版本中,Kubernetes 采用了环境变量的方法,每个 Pod 启动的时候,会通过环境变量设置所有服务的 IP 和 port 信息,这样 Pod 中的应用可以通过读取环境变量来获取依赖服务的地址信息,这种方法使用起来相对简单,但是有一个很大的问题就是依赖的服务必须在 Pod 启动之前就存在,不然是不会被注入到环境变量中的。

由于环境变量这种方式的局限性,我们需要一种更加智能的方案:那就是直接使用 Service 的名称,因为 Service 的名称不会变化,我们不需要去关心分配的ClusterIP的地址,因为这个地址并不是固定不变的,所以如果我们直接使用 Service 的名字,然后对应的ClusterIP地址的转换能够自动完成就好了。名字和 IP 直接的转换功能通过DNS就可以解决,所以后期Kubernetes就提供了DNS的方案来解决上面的服务发现的问题。

DNS 服务不是一个独立的系统服务,而是作为一种 addon 插件而存在,也就是说不是 Kubernetes 集群必须安装的,当然我们强烈推荐安装,可以将这个插件看成是一种运行在 Kubernetes 集群上的一直比较特殊的应用,我的环境下使用的是CoreDNS。

K8S dns解析步骤:
k8s的四个dnsPolicy:

None,无任何策略:表示空的DNS设置,这种方式一般用于想要自定义 DNS 配置的场景,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。

Default,默认: 此种方式是让kubelet来决定使用何种DNS策略。而kubelet默认的方式,就是使用宿主机的/etc/resolv.conf文件。同时,kubelet也可以配置指定的DNS策略文件,使用kubelet参数即可,如:–resolv-conf=/etc/resolv.conf

ClusterFirst,集群 DNS 优先:此种方式是使用kubernets集群内部中的kubedns或coredns服务进行域名解析。若解析不成功,才会使用宿主机的DNS配置来进行解析。

ClusterFistWithHostNet,集群 DNS 优先,并伴随着使用宿主机网络:在某些场景下,我们的 POD 是用 HOST 模式启动的(HOST模式,是共享宿主机网络的),一旦用 HOST 模式,表示这个 POD 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行DNS查询,但如果你想使用了 HOST 模式,还继续使用 Kubernetes 的DNS服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet。

因为在k8s中,普通Pod的dnsPolicy属性是默认值ClusterFirst,也就是会指向集群内部的DNS服务器,由kube-dns负责解析集群内部的域名,而k8s集群组件coredns的dnsPolicy值是Default,意思是从所在Node继承DNS服务器,对于无法解析的外部域名,kube-dns会继续向集群外部的dns进行查询。kube-dns只能解析集群内部地址,而集群外部地址应该发给外部DNS服务器进行解析。
kubernetes集群中,Pod的dnsPolicy有4种None、Default、ClusterFirst、ClusterFistWithHostNet,例如资源清单中Deployment的设置字段即为deployments.spec.template.spec.dnsPolicy:

 # kubectl explain deployments.spec.template.spec.dnsPolicy
KIND:     Deployment
VERSION:  apps/v1

FIELD:    dnsPolicy <string>

DESCRIPTION:
    Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are
    'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS
    parameters given in DNSConfig will be merged with the policy selected with
    DNSPolicy. To have DNS options set along with hostNetwork, you have to
    specify DNS policy explicitly to 'ClusterFirstWithHostNet'.

image-1651129896483

CoreDNS 可以通过四种方式对外直接提供 DNS 服务,分别是 UDP、gRPC、HTTPS 和 TLS,同时它支持通过简单的配置文件来定义DNS服务:

coredns.io:5300 {
    file db.coredns.io
}

example.io:53 {
    log
    errors
    file db.example.io
}

example.net:53 {
    file db.example.net
}

.:53 {
    kubernetes
    proxy . 8.8.8.8
    log
    errors
    cache
}

对于上述的配置文件Corefile,CoreDNS 会根据每一个代码块前面的区和端点对外暴露两个端点提供服务,该配置文件对外暴露了两个 DNS 服务,其中一个监听在 5300 端口,另一个在 53 端口,请求这两个服务时会根据不同的域名选择不同区中的插件进行处理:

image

.:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

# errors:错误记录到标准输出。
# health:在 http://localhost:8080/health 处提供 CoreDNS 的健康报告。
# ready:在端口 8181 上提供的一个 HTTP 末端,当所有能够 表达自身就绪的插件都已就绪时,在此末端返回 200 OK。
# kubernetes:CoreDNS 将基于 Kubernetes 的服务和 Pod 的 IP 答复 DNS 查询。你可以在 CoreDNS 网站阅读更多细节。 你可以使用 ttl 来定制响应的 TTL。默认值是 5 秒钟。TTL 的最小值可以是 0 秒钟, 最大值为 3600 秒。将 TTL 设置为 0 可以禁止对 DNS 记录进行缓存。

pods insecure 选项是为了与 kube-dns 向后兼容。你可以使用 pods verified 选项,该选项使得 仅在相同名称空间中存在具有匹配 IP 的 Pod 时才返回 A 记录。如果你不使用 Pod 记录,则可以使用 pods disabled 选项。
fallthrough表示当在hosts找不到要解析的域名时,会将解析任务传递给CoreDNS的下一个插件。如果不写fallthrough的话,任务就此结束,不会继续解析,会导致集群内部域名解析失败的情况。

# prometheus:CoreDNS 的度量指标值以 Prometheus 格式在 http://localhost:9153/metrics 上提供。
# forward: 不在 Kubernetes 集群域内的任何查询都将转发到 预定义的解析器 (/etc/resolv.conf).
# cache:启用前端缓存。
# loop:检测到简单的转发环,如果发现死循环,则中止 CoreDNS 进程。
# reload:允许自动重新加载已更改的 Corefile。 编辑 ConfigMap 配置后,请等待两分钟,以使更改生效。
# loadbalance:这是一个轮转式 DNS 负载均衡器, 它在应答中随机分配 A、AAAA 和 MX 记录的顺序。

CoreDNS作为kubernetes的另一项服务,kubelet和CoreDNS之间没有紧密的绑定,只需要将DNS服务的IP地址和域名传递给kubelet,而Kubernetes并不关心谁在实际处理该IP请求。

kubelet可以通过--cluster-dns = <dns-service-ip>--cluster-domain = <default-local-domain>两个参数分别被用来设置集群DNS服务器的IP地址和主域名后缀,这两项配置将会写入Pod内的DNS域名解析配置文件/etc/resolv.conf,如下所示:

# nameserver 定义DNS服务器的IP地址
nameserver xx.xx.0.10
# 设置域名的查找后缀规则,查找配置越多,说明域名解析查找匹配次数越多。
search kube-system.svc.cluster.local svc.cluster.local cluster.local
# 定义域名解析配置文件选项,支持多个KV值。
# 例如该参数设置成ndots:5,说明如果访问的域名字符串内的点字符数量超过ndots值,则认为是完整域名,并被直接解析;
# 如果不足ndots值,则追加search段后缀再进行查询
options ndots:5

4、排查dns解析问题

Pod有关dns解析问题的一般排查步骤是首先查看Pod的dnsPolicy,确定Pod会去找谁进行dns解析;然后查看Pod的dns配置文件/etc/resolv.conf 配置参数是否正确(可以手动修改解析文件使用其他的公有dns服务器进行排查);排查Pod与dns服务器能否正常连接;如果dns服务器使用的是集群的dns组件,排查dns组件本身是否在正常运行;如果普通Pod和dns服务器连接有问题的话最后再排查一下kube-proxy组件运行状态和相关日志。

我的环境中,Pod的dnsPolicy是clusterFirst,这样它首先会去找coredns进行解析。而k8s中一般pod的dns配置文件/etc/resolv.conf的nameserver都是coredns svc的ip地址,这里查看Pod的/etc/resolv.conf发现nameserver也正是coredns svc的ip地址,而且第二步的测试也显示了它可以直接与coredns连接;这几个排查步骤都说明Pod本身并没有问题。

 ## Pod内查看dns配置文件
/opt/emqx $ cat /etc/resolv.conf 
nameserver 10.96.0.10
search  emqx.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
  
 ## 查看pod的dnsPolicy
 $ kubectl get pod -n emqx emqx-deployment-657d48b89d-lglh2 -oyaml | grep dnsPolicy:
        f:dnsPolicy: {}
  dnsPolicy: ClusterFirst
  

接着查看coredns的dnsPolicy发现是Default,也就是说coredns会从所在Node继承DNS服务器(这从Pod可以解析外网的www.baidu.com来看也没啥问题)

 #kubectl describe cm -n kube-system coredns 
Name:         coredns
Namespace:    kube-system
Labels:       <none>
Annotations:  <none>

Data
====
Corefile:
----
.:53 {
   errors
   health {
      lameduck 5s
   }
   ready
   kubernetes cluster.local in-addr.arpa ip6.arpa {
      pods insecure
      fallthrough in-addr.arpa ip6.arpa
      ttl 30
   }
   prometheus :9153
   forward . /etc/resolv.conf
   cache 30
   loop
   reload
   loadbalance
}
 
 ## 查看coredns pod的dnsPolicy
 $ kubectl get pod -n kube-system coredns-66bff467f8-cq7ls -oyaml | grep dnsPolicy:
       f:dnsPolicy: {}
 dnsPolicy: Default

在coredns pod的配置文件Corefile(我的环境下是configmap)中添加log日志记录,以便排查问题

## 编辑 coredns 配置
$ kubectl edit configmap coredns -n kube-system
Corefile:
----
.:53 {
  log      #添加log
  errors
  health {
     lameduck 5s
  }
  ready
  kubernetes cluster.local in-addr.arpa ip6.arpa {
     pods insecure
     fallthrough in-addr.arpa ip6.arpa
     ttl 30
  }
  prometheus :9153
  forward . /etc/resolv.conf
  cache 30
  loop
  reload
  loadbalance
}

## 查看coredns pod日志
$ kubectl logs -n kube-system coredns-66bff467f8-jd8mg -f --tail=10
[INFO] 100.127.243.33:44449 - 22364 "A IN apiserver.cluster.local.emqx.svc.cluster.local. udp 64 false 512" NXDOMAIN qr,aa,rd 157 0.00012665s
[INFO] 100.127.243.33:36423 - 18428 "A IN apiserver.cluster.local.cluster.local. udp 55 false 512" NXDOMAIN qr,aa,rd 148 0.000070356s
[INFO] 100.127.243.3:42302 - 61140 "A IN apiserver.cluster.local.emqx.svc.cluster.local. udp 64 false 512" NXDOMAIN qr,aa,rd 157 0.000092449s
[INFO] 100.127.243.3:36186 - 54465 "A IN apiserver.cluster.local.cluster.local. udp 55 false 512" NXDOMAIN qr,aa,rd 148 0.000060435s
[INFO] 100.127.243.5:44903 - 3177 "A IN apiserver.cluster.local.emqx.svc.cluster.local. udp 64 false 512" NXDOMAIN qr,aa,rd 157 0.000100942s
[INFO] 100.127.243.5:41044 - 58236 "A IN apiserver.cluster.local.cluster.local. udp 55 false 512" NXDOMAIN qr,aa,rd 148 0.000057225s

coredns配置文件增加log选项之后,可以看到已经收到来自pod对apiserver.cluster.local的A解析请求,说明Pod的dns解析请求确实来到了coredns。

经过这些排查,确实没有发现有问题的地方。那我尝试自定义一个域名解析,看看能不能成功解析apiServer的这个内部域名apiserver.cluster.local

## 修改coredns的配置configmap
$ kubectl edit configmap coredns -n kube-system
apiVersion: v1
data:
  Corefile: |
    .:53 {
        log
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        ##    增加自定义域名解析 hosts 字段  		
        hosts {
           192.168.3.34 apiserver.cluster.local
           fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

$ kubectl exec -it -n emqx emqx-deployment-657d48b89d-255jl -- /bin/sh
/opt/emqx $ 
/opt/emqx $ sudo nslookup apiserver.cluster.local
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
Server:         10.96.0.10
Address:        10.96.0.10:53

Name:   apiserver.cluster.local
Address: 192.168.3.34

** server can't find apiserver.cluster.local: NXDOMAIN

/opt/emqx $ emqx_ctl cluster status
Cluster status: #{running_nodes =>
                      ['emqx@100.127.243.51','emqx@100.127.243.52',
                       'emqx@100.127.243.7'],
                  stopped_nodes => []}

当在coredns增加自定义解析后,我的测试emqx pod就可以解析apiserver.cluster.local这个域名了,我开始重新审视apiServer监听的这个自定义域名是不是本来就不能被普通的Pod所解析?毕竟在k8s的服务发现中,内部域名是有固定格式的。

我在社区群中尝试咨询大佬,得到的回答是该域名可以被解析(我有些怀疑)。然后我又和关系比较好的师兄探讨了一番,探讨后经过探索确定了答案。废话少说先上结论: apiserver.cluster.local这个内部域名只是初始创建集群时apiServer对一些系统组件(比如etcd、controller-manager、proxy、scheduler、kubelet、kubectl等组件)进行宣告的地址,普通的业务pod根本不能解析这个地址。这个地址的作用就是代理高可用master的时候所使用的,比如三个master节点的环境下,apiserver.cluster.local:6443地址解析到slb:6443(真实/虚拟),slb:6443监听后端三个master节点的6443端口(6443是master apiserver的监听端口),以此来实现高可用。

但是!但是,并不是说所有的Pod无法连接apiServer。普通的业务Pod通过sa、role、rolebinding是可以连接并向apiserver发起请求的。在k8s的服务发现中,普通的Service(包括apiServer的svc)向普通的pod提供的是dns域名解析记录,格式是servicename.namespace.svc.cluster.local。而一般情况下apiServer的svc是在default名称空间下,名叫kubernetes,通过k8s的服务发现,可以知道普通pod可解析的apiServer域名应该是kubernetes.default.svc.cluster.local

## 系统组件与apiServer交互时才使用apiserver.cluster.local这个域名
$ cat /etc/kubernetes/kubelet.conf | grep server:
    server: https://apiserver.cluster.local:6443

$ cat /etc/kubernetes/controller-manager.conf | grep server:
    server: https://apiserver.cluster.local:6443
  
$ cat /etc/kubernetes/scheduler.conf | grep server
    server: https://apiserver.cluster.local:6443

$ cat /root/.kube/config | grep server
    server: https://apiserver.cluster.local:6443
  
## 那为什么这些系统组件能够解析apiserver.cluster.local这个域名呢?
## 因为这些系统组件都是静态Pod形式运行在Node上的,在运行的时候已经把Node的hosts文件挂载进了静态Pod中
$ kubectl exec -it -n kube-system kube-controller-manager-qld -- /bin/sh
# cat /etc/hosts 
# Kubernetes-managed hosts file (host network).
127.0.0.1       localhost
127.0.0.1      qld

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.3.34 apiserver.cluster.local
  
  
  
## 看一下apiServer的svc和端口是什么?
$ kubectl get svc -n kube-system 
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   24d
  
$ kubectl get svc 
NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes           ClusterIP   10.96.0.1      <none>        443/TCP          24d
local-httpd-manage   NodePort    10.99.27.126   <none>        8081:30211/TCP   24d

$ kubectl describe svc kubernetes 
Name:              kubernetes
Namespace:         default
Labels:            component=apiserver
                   provider=kubernetes
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP:                10.96.0.1
Port:              https  443/TCP
TargetPort:        6443/TCP
Endpoints:         192.168.3.34:6443
Session Affinity:  None
Events:            <none>

## 再来看一下普通的业务Pod能否解析apiServer svc的内部域名地址
$ kubectl exec -it -n emqx emqx-deployment-657d48b89d-255jl -- /bin/sh
  
/opt/emqx $ sudo nslookup kubernetes.default.svc.cluster.local
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
Server:         10.96.0.10
Address:        10.96.0.10:53

Name:   kubernetes.default.svc.cluster.local
Address: 10.96.0.1			# 通过服务发现提供的apiSever svc的域名,成功解析到了该svc的ip

以上一小节的验证都说明了我们的结论,apiserver.cluster.local这个内部域名只是初始创建集群时apiServer对一些系统组件(比如etcd、controller-manager、proxy、scheduler、kubelet、kubectl等组件)进行宣告的地址,普通的业务pod根本不能解析这个地址。而一般情况下apiServer的svc是在default名称空间下,叫kubernetes,通过k8s的服务发现提供的普通service的域名格式servicename.namespace.svc.cluster.local,可以知道普通pod可解析的apiServer域名应该是kubernetes.default.svc.cluster.local

5、EMQX通过k8s自动集群问题解决

第一种解决方法,可以在coredns的配置文件中添加自定义域名解析,将apiserver.cluster.local域名解析到正确的ip地址,随后在EMQX的EMQX_CLUSTER__K8S__APISERVER处使用https://apiserver.cluster.local可实现EMQX的自动集群

## coredns配置文件
$ kubectl edit cm -n kube-system coredns
        ......
        hosts {
           192.168.3.34 apiserver.cluster.local
           fallthrough
        }
        ......
  
## emqx pod资源清单
$ cat emqx.yaml | grep -B 14 apiserver.cluster.local
        env:
        - name: EMQX_NAME
          value: emqx
        - name: EMQX_CLUSTER__DISCOVERY
          value: k8s
        - name: EMQX_CLUSTER__K8S__APP_NAME
          value: emqx
        - name: EMQX_CLUSTER__K8S__SERVICE_NAME
          value: emqx-service
        - name: EMQX_CLUSTER__K8S__NAMESPACE
          value: emqx
        - name: EMQX_CLUSTER__K8S__ADDRESS_TYPE
          value: ip
        - name: EMQX_CLUSTER__K8S__APISERVER
          value: "https://apiserver.cluster.local:6443"  

第二种解决办法,emqx资源清单EMQX_CLUSTER__K8S__APISERVER变量直接使用apiserver的svc地址+端口kubernetes.default.svc.cluster.local:443(前提sa、role、rolebinding等已配好)

## 查看apiserver svc的name与端口
$ kubectl get svc -n default
NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes           ClusterIP   10.96.0.1      <none>        443/TCP          24d
  
## 查看emqx资源清单传入ENV值
$ cat emqx.yaml | grep -B 14 "https://kubernetes.default.svc.cluster.local:443"
        env:
        - name: EMQX_NAME
          value: emqx
        - name: EMQX_CLUSTER__DISCOVERY
          value: k8s
        - name: EMQX_CLUSTER__K8S__APP_NAME
          value: emqx
        - name: EMQX_CLUSTER__K8S__SERVICE_NAME
          value: emqx-service
        - name: EMQX_CLUSTER__K8S__NAMESPACE
          value: emqx
        - name: EMQX_CLUSTER__K8S__ADDRESS_TYPE
          value: ip
        - name: EMQX_CLUSTER__K8S__APISERVER
          value: "https://kubernetes.default.svc.cluster.local:443"

## 创建emqx相关资源
$ kubectl apply -f emqx.yaml 
configmap/emqxauthconf created
serviceaccount/emqx created
role.rbac.authorization.k8s.io/emqx created
rolebinding.rbac.authorization.k8s.io/emqx created
service/emqx-service created
deployment.apps/emqx-deployment created

## 查看emqx自动集群情况
$ kubectl exec -it -n emqx emqx-deployment-76b7fcb968-h4rsx -- emqx_ctl cluster status 
Cluster status: #{running_nodes =>
                      ['emqx@100.127.243.1','emqx@100.127.243.17',
                       'emqx@100.127.243.2'],
                  stopped_nodes => []}

6、总结

本篇文章,在解决emqx不能自动集群的问题的时候,回顾和学习了kubernetes有关dns解析的相关知识,同时也分享了有关kubernetes dns解析问题的排查过程,供大家参考。

kubectl cluster-info所看到的apiserver监听的内部域名(例如apiserver.cluster.local)只是初始时apiServer对一些系统组件(比如etcd、controller-manager、proxy、scheduler、kubelet、kubectl等组件)进行宣告的地址,其作用是高可用负载多apiserver时使用。普通的业务pod根本不能通过内部dns解析这个地址。而一般情况下apiServer的svc是在default名称空间下,叫kubernetes,通过k8s的服务发现提供的普通service的域名格式servicename.namespace.svc.cluster.local,可以知道普通pod可解析的apiServer的域名应该是kubernetes.default.svc.cluster.local`

参考:
1、从零开始建立 EMQX MQTT 服务器的 K8S 集群
2、使用CoreDNS实现自定义域名解析
3、搭建Kubernetes集群时DNS无法解析问题的处理过程
4、访问 Kubernetes 集群的方式
5、解决 Kubernetes 中 Pod 无法正常域名解析问题分析与 IPVS parseIP Error 问题
6、修改CoreDNS配置直接解析
7、服务发现CoreDNS和Kubernetes内部域名解析
8、阿里云ACK DNS原理和配置说明
9、k8s的coreDNS解析组件调试

0

评论