来源:TechWeb 发表时间:2021-09-07 10:19 阅读量:10952
我们来谈谈Kubernetes的服务发现首先,这个大前提是与主机和跨主机的通信是可以的,也就是说,同一个Kubernetes集群中的所有PODs都是可以互操作的这一点通过低层方案来实现,包括docker0/CNI桥,法兰绒vxlan/host—gw模式等本文将不讨论这一点
在所有PODs都可以互操作的前提下,我们可以通过访问podp来调用PODs上的资源,那么离服务发现还有多远呢首先,Pod的IP不是固定的另一方面,当我们访问一组Pod实例时,我们经常需要负载平衡,因此使用Service对象来解决此类问题
集群内通信端点
服务首先解决集群内通信的需求。首先,我们编写一个普通的部署:
API version : apps/v1kind : deploymentmetadata : name : hostname sspec : selecte : matchlabels : hostname replica 33603 template 3360 metadata 3360 label ls : app : hostname sspec : containers :—name 3: hostname simage 333333330
这个应用程序所做的就是访问它并返回它自己的主机名,每个Pod都被标记为主机名。
然后让我们为这些吊舱编写一个普通的服务:
API version : v1kind : service metadata : name : hostname sspec : electro : app : hostname sports :—name : defaultprotocol : cpport 336080 targetport 33609376
您可以看到服务已经通过选择器选择了带有相应标签的PODs,并且这些选择的PODs成为端点。我们可以试试:
~/cloud/k8 skubectlegetthephstnames snameendpointshostnames 172 . 28 . 21 . 66:9376,172 . 28 . 29 . 5233369376,172 . 28 . 70 . 52333333626
当Pod出现问题,未处于运行状态或就绪探测失败时,端点列表将删除它。
集群IP
上面我们有服务和端点,默认的服务创建类型是集群IP类型。让我们检查之前创建的服务:
~ kubectlgetsvhostname snametypecluster—IPEXTERNAL—IPPORTagehostname sclusterip 10 . 212 . 8 . 12780/tcp8 m2s
我们看到集群IP是10.212.8.127,因此此时我们可以通过Kubernetes集群中的这个地址访问端点列表中的任何Pod:
sh—4.2 # curl 10 . 212 . 8 . 127 hostname—8548 b 869d 7—9qk 6 bsh—4.2 # curl 10 . 212 . 8 . 127 hostname—8548 b 869d 7—wzksppsh—4.2 # curl 10 . 212 . 8 . 127 hostname—8548 b 869d 7—bvl w8
在访问了集群IP地址三次并返回了三个不同的主机名之后,我们意识到集群IP模式下的服务会以循环的形式自动平衡请求。
此时,对于集群IP模式下的服务,它有一个服务名称.命名空间.名称. svc.cluster.local的记录,该记录指向集群IP地址:
sh—4.2 # nslookuphostnames . coops—dev . SVC . cluster . local server :10 . 212 . 0 . 2 address :10 . 212 . 0 . 2 # 53 name : hostname . coops—dev . SVC . cluster . local address 336010 . 212 . 8 . 127
当然,我们通过访问这个A记录得到同样的效果:
sh—4.2 # curlhostnames . coops—dev . SVC . cluster . localhostnames—8548 b 869d 7—wzksp
它为豆荚创造的最高纪录是多少。我们可以看看它:
sh—4.2 # nslookup 172 . 28 . 21 . 6666 . 21 . 28 . 172 . in—addr . arpaname=172—28—21—66 . hostname . coops—dev . SVC . cluster . local . Headless service
默认情况下,服务CluserIP由Kubernetes自动分配,当然也可以自己分配。
设置,当我们将 CluserIP 设置成 None 的时候,它就变成了 Headless service。
Headless service 一般配合 StatefulSet 使用StatefulSet 是一种有状态应用的容器编排方式,其核心思想是给予 Pod 指定的编号名称,从而让 Pod 有一个不变的唯一网络标识码那这么说来,使用 CluserIP 负载均衡访问 Pod 的方式显然是行不通了,因为我们渴望通过某个标识直接访问到 Pod 本身,而不是一个虚拟 vip
这个时候我们其实可以借助 DNS,每个 Pod 都会有一条 A 记录 pod—name.service—name.namespace—name.svc.cluster.local 指向 podIP,我们可以通过这条 A 记录直接访问到 Pod。
我们编写相应的 StatefulSet 和 Service 来看一下:
apiVersion:apps/v1kind:StatefulSetmetadata:name:hostnamesspec:serviceName:"hostnames"selector:matchLabels:app:hostnamesreplicas:3template:metadata:labels:app:hostnamesspec:containers:—name:hostnamesimage:mirrorgooglecontainers/serve_hostnameports:—containerPort:9376protocol:TCPapiVersion:v1kind:Servicemetadata:name:hostnamesspec:selector:app:hostnamesclusterIP:Noneports:—name:defaultprotocol:TCPport:80targetPort:9376
如上,StatefulSet 和 deployment 并没有什么不同,多了一个字段 spec.serviceName,这个字段的作用就是告诉 StatefulSet controller,在逻辑处理时使用 hostnames 这个 Service 来保证 Pod 的唯一可解析性。
当你执行 apply 之后,一会你就可以看到生成了对应的 Pod:
~kubectlgetpods—w—lapp=hostnamesNAMEREADYSTATUSRESTARTSAGEhostnames—01/1Running09m54shostnames—11/1Running09m28shostnames—21/1Running09m24s
如意料之中,这里对 Pod 名称进行了递增编号,并不重复,同时这些 Pod 的创建过程也是按照编号依次串行进行的我们知道,使用 deployment 部署的 Pod 名称会加上 replicaSet 名称和随机数,重启后是不断变化的而这边使用 StatefulSet 部署的 Pod,虽然 podIP 仍然会变化,但名称是一直不会变的,基于此我们得以通过固定的 DNS A 记录来访问到每个 Pod
那么此时,我们来看一下 Pod 的 A 记录:
sh—4.2#nslookuphostnames—0.hostnamesServer:10.212.0.2Address:10.212.0.2#53Name:hostnames—0.hostnames.coops—dev.svc.cluster.localAddress:172.28.3.57sh—4.2#nslookuphostnames—1.hostnamesServer:10.212.0.2Address:10.212.0.2#53Name:hostnames—1.hostnames.coops—dev.svc.cluster.localAddress:172.28.29.31sh—4.2#nslookuphostnames—2.hostnamesServer:10.212.0.2Address:10.212.0.2#53Name:hostnames—2.hostnames.coops—dev.svc.cluster.localAddress:172.28.23.31
和之前的推论一致,我们可以通过 pod—name.service—name.namespace—name.svc.cluster.local 这条 A 记录访问到 podIP,在同一个 namespace 中,我们可以简化为 pod—name.service—name。
而这个时候,Service 的 A 记录是什么呢:
sh—4.2#nslookuphostnamesServer:10.212.0.2Address:10.212.0.2#53Name:hostnames.coops—dev.svc.cluster.localAddress:172.28.29.31Name:hostnames.coops—dev.svc.cluster.localAddress:172.28.3.57Name:hostnames.coops—dev.svc.cluster.localAddress:172.28.23.31
原来是 Endpoints 列表里的一组 podIP,也就是说此时你依然可以通过service—name.namespace—name.svc.cluster.local这条 A 记录来负载均衡地访问到后端 Pod。
iptables
或多或少我们知道 Kubernetes 里面的 Service 是基于 kube—proxy 和 iptables 工作的Service 创建之后可以被 kube—proxy 感知到,那么它会为此在宿主机上创建对应的 iptables 规则
以 CluserIP 模式的 Service 为例,首先它会创建一条 KUBE—SERVICES 规则作为入口:
—AKUBE—SERVICES—d10.212.8.127/32—ptcp—mcomment——comment"default/hostnames:clusterIP"—mtcp——dport80—jKUBE—SVC—NWV5X2332I4OT4T3
这条记录的意思是:所有目的地址是 10.212.8.127 这条 CluserIP 的,都将跳转到 KUBE—SVC iptables 链处理。
那么我们来看 KUBE—SVC 链都是什么:
—AKUBE—SVC—NWV5X2332I4OT4T3—mcomment——comment"default/hostnames:"—mstatistic——moderandom——probability0.33332999982—jKUBE—SEP—WNBA2IHDGP2BOBGZ—AKUBE—SVC—NWV5X2332I4OT4T3—mcomment——comment"default/hostnames:"—mstatistic——moderandom——probability0.50000000000—jKUBE—SEP—X3P2623AGDH6CDF3—AKUBE—SVC—NWV5X2332I4OT4T3—mcomment——comment"default/hostnames:"—jKUBE—SEP—57KPRZ3JQVENLNBR
这组规则其实是用于负载均衡的,我们看到了——probability 依次是 1/3,1/2,1,由于 iptables 规则是自上而下匹配的,所以设置这些值能保证每条链匹配到的几率一样。处理完负载均衡的逻辑后,又分别将请求转发到了另外三条规则,我们来看一下:
—AKUBE—SEP—57KPRZ3JQVENLNBR—s172.28.21.66/32—mcomment——comment"default/hostnames:"—jMARK——set—xmark0x00004000/0x00004000—AKUBE—SEP—57KPRZ3JQVENLNBR—ptcp—mcomment——comment"default/hostnames:"—mtcp—jDNAT——to—destination172.28.21.66:9376—AKUBE—SEP—WNBA2IHDGP2BOBGZ—s172.28.29.52/32—mcomment——comment"default/hostnames:"—jMARK——set—xmark0x00004000/0x00004000—AKUBE—SEP—WNBA2IHDGP2BOBGZ—ptcp—mcomment——comment"default/hostnames:"—mtcp—jDNAT——to—destination172.28.29.52:9376—AKUBE—SEP—X3P2623AGDH6CDF3—s172.28.70.13/32—mcomment——comment"default/hostnames:"—jMARK——set—xmark0x00004000/0x00004000—AKUBE—SEP—X3P2623AGDH6CDF3—ptcp—mcomment——comment"default/hostnames:"—mtcp—jDNAT——to—destination172.28.70.13:9376
可以看到 KUBE—SEP 链就是三条 DNAT 规则,并在 DNAT 之前设置了一个 0x00004000 的标志DNAT 规则就是在 PREROUTING,即路由作用之前,将请求的目的地址和端口改为 ——to—destination 指定的 podIP 和端口这样一来,我们起先访问 10.212.8.127 这个 CluserIP 的请求,就会被负载均衡到各个 Pod 上
那么 Pod 重启了,podIP 变了怎么办自然是 kube—proxy 负责监听 Pod 变化以及更新维护 iptables 规则了
而对于 Headless service 来说,我们直接通过固定的 A 记录访问到了 Pod,自然不需要这些 iptables 规则了。
iptables 理解起来比较简单,但实际上性能并不好可以想象,当我们的 Pod 非常多时,成千上万的 iptables 规则将被创建出来,并不断刷新,会占用宿主机大量的 CPU 资源一个行之有效的方案是基于 IPVS 模式的 Service,IPVS 不需要为每个 Pod 都设置 iptables 规则,而是将这些规则都放到了内核态,极大降低了维护这些规则的成本
集群间通信 外界访问 Service
以上我们讲了请求怎么在 Kubernetes 集群内互通,主要基于 kube—dns 生成的 DNS 记录以及 kube—proxy 维护的 iptables 规则而这些信息都是作用在集群内的,那么自然我们从集群外访问不到一个具体的 Service 或者 Pod 了
Service 除了默认的 CluserIP 模式外,还提供了很多其他的模式,比如 nodePort 模式,就是用于解决该问题的。
apiVersion:v1kind:Servicemetadata:name:hostnamesspec:selector:app:hostnamestype:NodePortports:—nodePort:8477protocol:TCPport:80targetPort:9376
我们编写了一个 NodePort 模式的 Service,并且设置 NodePort 为 8477,那么意味着我们可以通过任意一台宿主机的 8477 端口访问到 hostnames 这个 Service。
sh—4.2#curl10.1.6.25:8477hostnames—8548b869d7—j5lj9sh—4.2#curl10.1.6.25:8477hostnames—8548b869d7—66vnvsh—4.2#curl10.1.6.25:8477hostnames—8548b869d7—szz4f
我们随便找了一台 Node 地址去访问,得到了相同的返回配方。
那么这个时候它的 iptables 规则是怎么作用的呢:
—AKUBE—NODEPORTS—ptcp—mcomment——comment"default/hostnames:nodePort"—mtcp——dport8477—jKUBE—SVC—67RL4FN6JRUPOJYM
kube—proxy 在每台宿主机上都生成了如上的 iptables 规则,通过 ——dport 指定了端口,访问该端口的请求都会跳转到 KUBE—SVC 链上,KUBE—SVC 链和之前 CluserIP Service 的配方一样,接下来就和访问 CluserIP Service 没什么区别了。
不过还需要注意的是,在请求离开当前宿主机发往其他 Node 时会对其做一次 SNAT 操作:
—AKUBE—POSTROUTING—mcomment——comment"kubernetesservicetrafficrequiringSNAT"—mmark——mark0x4000/0x4000—jMASQUERADE
可以看到这条 postrouting 规则给即将离开主机的请求进行了一次 SNAT,判断条件为带有 0x4000 标志,这就是之前 DNAT 带的标志,从而判断请求是从 Service 转发出来的,而不是普通请求。
需要做 SNAT 的原因很简单,首先这是一个外部的未经 Kubernetes 处理的请求,如果它访问 node1,node1 的负载均衡将其转发给 node2 上的某个 Pod,这没什么问题,而这个 Pod 处理完后直接返回给外部 client,那么外部 client 就很疑惑,明明自己访问的是 node1,给自己返回的确是 node2,这时往往会报错。
SNAT 的作用与 DNAT 相反,就是在请求从 node1 离开发往 node2 时,将源地址改为 node1 的地址,那么当 node2 上的 Pod 返回时,会返回给 node1,然后再让 node1 返回给 client。
^||v|node2v|endpoints
Ingress
Service 有一种类型叫作 LoadBalancer,不过如果每个 Service 对外都配置一个负载均衡服务,成本很高而且浪费一般来说我们希望有一个全局的负载均衡器,通过访问不同 url,转发到不同 Service 上,而这就是 Ingress 的功能,Ingress 可以看做是 Service 的 Service
Ingress 其实是对反向代理的一种抽象,相信大家已经感觉到,这玩意儿和 Nginx 十分相似,实际上 Ingress 是抽象层,而其实现层其中之一就支持 Nginx。
我们可以部署一个 nginx ingress controller:
mandatory.yaml是官方维护的 ingress controller,我们看一下:
总的来说,我们定义了一个基于 nginx—ingress—controller 镜像的 Pod,而这个 Pod 自身,是一个监听 Ingress 对象及其代理后端 Service 变化的控制器。
当一个 Ingress 对象被创建时,nginx—ingress—controller 就会根据 Ingress 对象里的内容,生成一份 Nginx 配置文件,并依此启动一个 Nginx 服务。
当 Ingress 对象被更新时,nginx—ingress—controller 就会更新这个配置文件nginx—ingress—controller 还通过 Nginx Lua 方案实现了 nginx upstream 的动态配置
为了让外界可以访问到这个 Nginx,我们还得给它创建一个 Service 来把 Nginx 暴露出去:
这里面的内容描述了一个 NodePort 类型的 Service:
可以看到这个 Service 仅仅是把 Nginx Pod 的 80/443 端口暴露出去,完了你就可以通过宿主机 IP 和 NodePort 端口访问到 Nginx 了。
接下来我们来看 Ingress 对象一般是如何编写的,我们可以参考一个例子。
我们可以查看到 Ingress 对象的详细信息:
我们最后来试一下请求:
可以看到 Nginx Ingress controller 已经为我们成功将请求转发到了对应的后端 Service而当请求没有匹配到任何一条 ingress rule 的时候,理所当然我们会得到一个 404
至此,Kubernetes 的容器网络是怎么实现服务发现的已经讲完了,而服务发现正是微服务架构中最核心的问题,解决了这个问题,那么使用 Kubernetes 来实现微服务架构也就实现了一大半。
郑重声明:此文内容为本网站转载企业宣传资讯,目的在于传播更多信息,与本站立场无关。仅供读者参考,并请自行核实相关内容。
下一篇:返回列表