灵魂拷问x8:关于K8SPod通信和DNS那些事儿!

在一些项目和技术交流中,发现很多朋友对OCP pod通信机制和OCP中的DNS犯懵。我专门写一篇进行分析。

创新互联公司专注于企业全网营销推广、网站重做改版、左权网站定制设计、自适应品牌网站建设、H5建站商城网站建设、集团公司官网建设、成都外贸网站建设公司、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为左权等各大城市提供网站开发制作服务。

本文中大量内容和K8S是相通的,文中也直接大量使用了K8S的概念,因此本文题目起的是K8S。OCP=OpenShift Container Platform,OCP内部的SDN用的是OVS。

我们以反问的方式进行“灵魂拷问”

拷问1. OCP中pod之间通讯是否一定需要service?

不需要。

没有service,pod能通信,而且好好的,如下图所示。

在一个namespace里(因为有networkpolicy,因此先做这个假设),只要指定的端口是pod开放的端口,在一个pod中,curl另外一个pod,就绝对能通。pod之间能不能通和俩pod跨不跨service,有没有service,没半毛钱关系。

拷问2. 从一个pod curl另外一个pod+端口号能通这件事,背后的链路机制是什么?

(1)俩pod在一个节点上,链路如下,流量没有绕出本宿主机的ovs:

Pod 1的eth0 → vethxx → br0 → vethyy → Pod 2的eth0

(2).俩pod在不同node上,链路如下,pod之间的通讯经过了vxlan:

Pod 1的eth0 → vethxx → br0 → vxlan0 → host1 eth0(192.168.1.101)→ network → host2 eth0(192.168.1.102)→ vxlan0 → br0 → vethmm → Pod 3的eth0

也就是说,截止到目前,我们把俩pod之间的通讯机制,简单介绍了。这里面没service啥事,这点记住。还需要记住的是,这是OCP集群中,pod之间通讯的实际数据链路(和后面service寻址对应)。

拷问3:我们都知道K8S service的基本概念,说说为啥K8S需要它?

我们首先要明确:Service是对一组提供相同功能的Pods的抽象,并为它们提供一个统一的内部访问入口。它主要解决:

1.负载均衡(这个大家会最先想到)。

2.服务注册与发现:解决不同服务之间的通信问题。在OpenShift中的创建应用后,需要提供访问应用的地址供其他服务调用,这个地址就是由Service提供。

每创建一个Service会分配一个ServiceIP地址,称为ClusterIP,这个IP地址是一个虚拟的地址,无法执行ping操作。同时自动在内部DNS注册一条对应的A记录(A记录很重要,后面展开说),这就完成了服务注册,注册信息全部保存在Etcd中。

拷问4:详细说说服务注册到底向etcd里注册了啥?

注册到etcd的,实际上是service yaml的先关内容。

我们查看etcd中router service 的内容。我们看到了什么?

service name、namespace、service ip、port number的,以及对应的endpoints等很多信息。

也就是说,etcd里有namespace,service name,service ip等,

通过这三个信息就可组成DNS A记录,也就是,service的FQDN和service ip之间的对应关系。但需要说明的是,etcd不是DNS!DNS A记录是通过查询生成的!OCP的DNS是由SkyDNS/CoreDNS实现的,这个后面说。

我们再说说服务发现。

服务发现这个词经常被妖魔化。我们把字换个顺序,就好理解:发现服务。也就说,service A要和serviceB通讯,我得知道serviceB是谁、在哪、service IP、pod ip都是啥?这就叫服务发现。

拷问5:有了servcice以后,pod之间的通讯和没有service有啥区别?

在数据通讯层,没区别!因为service只是逻辑层面的东西。

但是,没有service,K8S是无法控制pod之间的通讯的,也无法为pod之间进行寻址。也就是说,没有service,pod之间的通讯,就不会和K8S发生任何关系,和自己在笔记本linux中装起两容器ping着玩,没太大区别(在网络层)。

有了service以后,pod之间怎么寻址?

回答这个问题,我们要站在开发者角度。如果一个程序员,要写微服务,微服务之间要相互调用,怎么写?写 pod IP和service ip都不靠谱吧,你都不知道IP地址会是啥。

如果程序员决定用k8s做服务发现的前提下写服务之间的调用(如果使用spring cloud,那就用它的服务注册中心做解析,也就不必用K8S的service了),那么就得写K8S的service名称!

因为service名称我们是可以固定的。

K8S中service有短名和长名。

以下图为例,jws-app就是service的短名,service的长名是:..svc.cluser.local,也就是jws-app.web.svc.cluser.local。service短名可以自动补充成长名,这是OCP中的DNS做的,这个后面说。

那么,这时候大魏也有个疑问了。如果在两个不同的namespace中,有两个相同的service短名,微服务调用不是会出现混乱?程序员的代码里是不是要写service全名?

不能说想法不对,但一般不这样干。

首先,站在OCP集群cluster-admin的角度,我们看所有的项目,有几十个或者而更多,会觉得在不同namespaces中起相同的service短名是可能(比如namespace A中有个acat的service,namespace B中也有个acat的service)。但站在程序员角度,他只是OCP的使用者,他有自己的namespace,他能访问的namespace很有限,可能就1个。绝大多数情况下,同一个业务项目的微服务一般会运行在同一个namespace中,默认如果使用短名称(只写service name),则会自动补全成当前namespace的FQDN,只有在跨namespace调用的时候才必须写全名FQDN。

所以,程序员写的程序,用到了K8S service name,那么,真正跑应用的pod之间的通讯,也必然会以service name去找。通过service名称找到service ip,然后最终找到pod ip(一个service可能多个pod,从service ip到pod ip的负载均衡实现后面讲)。找到pod的ip以后,接下来实际的数据交换,就和拷问2讲述机制就接上头了。

拷问6. 我们知道Service的作用了,那从service ip到pod ip这段的负载均衡怎么实现的?

K8S中通过service name做服务发现,选短名会自动自动扩展成FQDN,然后解析成service ip,然后再走kube-proxy负载均衡倒pod ip。

Service的负载均衡可以由很多的实现方式,目前Kubernetes官方提供了三种代理模式:userspace、iptables、ipvs。目前版本OpenShift默认的代理模式是iptables

从图中可以看出,当客户端访问Servcie的ClusterIP时,由Iptables实现负载均衡,选择一个后端处理请求,默认的负载均衡策略是轮询。在这种模式下,每创建一个Service,会自动匹配后端实例Pod记录在Endpoints对象中,并在所有Node节点上添加相应的iptables规则,将访问该Service的ClusterIP与Port的连接重定向到Endpoints中的某一个后端Pod,由于篇幅有限,关于负载均衡实现的细节不再赘述。

这种模式有两个缺点需要关注:第一,不支持复杂的负载均衡算法;第二,当选择的某个后端Pod没有响应时,无法自动重新连接到另一个Pod,用户必须利用Pod的健康监测来保证Endpoints列表中Pod都是存活的(也就是说,生产上你好把liveness和rediness配上。)。

对于OCP而言,每个节点上都有iptables。而iptables之前的通讯和同步,是通过每个节点的kube-proxy实现的(这个进程在sdn pod中运行。想详细研究的可以看我前两天写的文章: 深度解析:kube-proxy在OpenShift上的实现)

拷问7. 我记得iptables不是做防火墙的么?

OCP中的iptables,主要目的做的是service ip和pod ip链路的事情。一般不涉及常规意义上的防火墙规则的INPUT/OUTPUT ACCEPT/DENY。也就是说,不要尝试自己ssh到OCP4节点上通过iptables做安全规则,不是这样玩的。

拷问8. 前面7个问题清楚了,说说OCP的DNS机制吧。

首先,OCP3和OCP4的DNS机制是不同的。

OpenShift 3内置的是SkyDNS,SkyDNS会监测Kubernetes API,当新创建一个Service,SkyDNS中就会提供..svc.cluster.local域名的解析。除了解析Service,还可以通过..endpoints.cluster.local解析endpoints。

例如,如果 myproject 服务中存在 myapi 服务,则整个OpenShift集群中的所有Pod都可以解析 myapi.myproject.svc.cluster.local 主机名以获取Service ClusterIP地址。除此之外,OpenShift DNS还提供以下两种短域名:

在简单了解了SkyDNS的机制之后,我们来看看OpenShift3是如何使用和配置DNS的。为了便于理解,我们用下图来进行说明。

上图表示了OpenShift 3中DNS解析流程:

以上就是OpenShift 3中DNS的解析流程,核心是通过每个节点上运行的Dnsmasq进程做了SkyDNS和上游DNS的代理。

在OpenShift 3的DNS里面需要注意以下几点:

OpenShift 4的DNS

OpenShift 4使用CoreDNS替换了OpenShift 3使用的SkyDNS,起到的作用是一样的,同样是提供OpenShift内部的域名解析服务。

在OpenShift 4中CoreDNS使用Operator实现部署,最终会创建出DaemonSet部署CoreDNS,也就是在每个节点会启动一个CoreDNS容器。在Kubelet将--cluster-dns设定为CoreDNS的ServiceClusterIP,这样Pod中就可以使用CoreDNS进行域名解析。

在安装OpenShift 4时,通过名为dns的Clusteroperator创建整个DNS堆栈,最终会在项目openshift-dns-operator下实例化一个dns pod完成具体的部署配置操作。

Cluster Domain定义了集群中Pod和Service域名的基本DNS域,默认为cluster.local,域名服务的地址是CoreDNS的ClusterIP,是配置的Service IP CIDR网段中的第10个地址,默认网段为172.30.0.0/16,第十个地址为172.30.0.10。DNS解析流程如下图所示:

上图表示了OpenShift 4的DNS解析流程

说简单点,在OCP中,随便创建一个pod,这个pod中的name server都会指向到172.30.0.10,这是coredns pod的service ip。

我们查看coredns的pod和service ip:

我们访问prometheus-k8s-0 这个pod进行查看。

查看这个规则:

sh-4.2$ cat /etc/resolv.conf

search openshift-monitoring.svc.cluster.local svc.cluster.local cluster.local

nameserver 172.30.0.10

options ndots:5

我们查看宿主机的dns。

OCP宿主机的nameserver可以是数据中心内部的,也可以自行构建。

举例说,如果我要在pod中nslookup baidu.com:

1.如果coredns pod中有缓存,直接返回

2. coredns pod中没有缓存,coredns一看这是外部域名,不归它管,他就会转到宿主机指向的192.168.91.8去解析,如果这192.168.91.8也解析不了,那就看还有没有上级的DNS了。总之,得有dns把baidu.com能解析出来。如果在OCP在纯离线的环境,baidu.com八成就解析失败了。(数据中心内部应该没人给baidu.com自己配解析)

好了,灵魂拷问结束,这种拷问方式是不是感觉比直接叙述看着爽一些?


分享名称:灵魂拷问x8:关于K8SPod通信和DNS那些事儿!
分享URL:http://ybzwz.com/article/sddigc.html