我们在聊到Servcie的时候,会有一个事实,就是Service的访问信息在Kubernetes集群之外,是无效的

所谓Service的访问入口,也只是Kubernetes配置在宿主机上的iptables规则,一旦离开了这个集群,这些信息也就无法生效了,那么如果想要在集群外,访问到其中的Service,如何办呢?

常见的方式,就是使用一个api对象,比如NodePort

apiVersion: v1

kind: Service

metadata:

name: my-nginx

labels:

run: my-nginx

spec:

type: NodePort

ports:

– nodePort: 8080

targetPort: 80

protocol: TCP

name: http

– nodePort: 443

protocol: TCP

name: https

selector:

run: my-ng

在这个Api对象的声明定义中,声明类型是type=NodePort

然后在Ports字段中声明了Service的8080端口代理Pod的80端口

443端口代理Pod的443

如果不手动的指定nodePort的话,会随机分配可用端口来设置代理,端口的范围默认是30000-32767,这个范围自然可以通过kube-apiserver的-service-node-port-range来进行修改

访问这个Service,需要访问

<任何一台宿主机的IP地址>:8080

就可以访问到某个被代理的Pod的80端口了

其实就是Docker的端口映射,其实本质上就是在宿主机上生成一个iptables规则

-A KUBE-NODEPORTS -p tcp -m comment –comment “default/my-nginx: nodePort” -m tcp –dport 8080 -j KUBE-SVC-67RL4FN6JRUPOJYM

而且对应着,IP包离开的时候还有一个IPtables的规则

-A KUBE-POSTROUTING -m comment –comment “kubernetes service traffic requiring SNAT” -m mark –mark 0x4000/0x4000 -j MASQUERADE

这个规则设置在POSTROUTING检查点,给即将离开这台主机的IP包,进行了一次SNAT,将这个IP包的源地址替换为了这台宿主机上的CNI网桥地址,或者宿主机本身的IP地址

这个SNAT操作只需要对Service转发出来的IP包执行,而进行执行,执行的依据是利用了之前DNAT打上去的 0x4000的标志

这个SNAT之所以需要做,是当一个外部的client通过node2地址访问一个Service的时候,node2上的负载均衡规则,就可能直接转发给了node1的Pod

当node1这个Pod处理完成后,就会按照这个IP的源地址进行返回

但是如果不进行SNAT,可能导致,转发过来的IP包的目标地址是node1,会和当初client发出的目标地址node2显示不一致,导致报错

这就是SNAT的原因,这样源IP地址就会被改为node2的地址后,再发给client

当然,对于需要明确知道请求来源的场景,可以通过设置Service的spec.externalTrafficPolicy字段为local,保证返回的是真实的Client地址

这个机制很简单,只不过是Pod直接返回了,不进行SNAT

这也意味着,K8S不会转发请求,如果一个node2上没有需要的代理Pod存在,那么使用node2的IP地址访问的话,就会无效的,直接被DROP掉

访问Service的第二种方式,使用于公有云上的Kubernets,这时候,可以指定一个LoadBalancer类型的Service,如下所示

kind: Service

apiVersion: v1

metadata:

name: example-service

spec:

ports:

– port: 8765

targetPort: 9376

selector:

app: example

type: LoadBalancer

公有云提供的KUbernetes,都有一个CloudProvider的转发层,来和公有云的API进行对接,上面的LoadBalancer类型的Service被提交后,Kubernetes机会调用CloudProvider在公有云创建一个负载均衡服务,并将被代理的IP提供给负载均衡服务

第三种方式,则是ExternalName,举个例子

kind: Service

apiVersion: v1

metadata:

name: my-service

spec:

type: ExternalName

externalName: my.database.example.com

指定了一个com的字段,这个YAML中也没有指定selector

通过Service的DNS名字访问这个的时候,比如访问my-service.default.svc.cluster.local,返回的就是my.database.example.com.这样就好比是公有云提供的CNAME的功能

此外,Kubernets的Service还可以为Service分配公有云的IP地址,比如下面例子

kind: Service

apiVersion: v1

metadata:

name: my-service

spec:

selector:

app: MyApp

ports:

– name: http

protocol: TCP

port: 80

targetPort: 9376

externalIPs:

– 80.11.12.10

就是代理目标端口的9376,并且指定了对应的公网IP为80.11.12.10,这样就能该访问到对应的Pod了

当Service没法通过DNS进行访问到的时候,需要区分是Service本身的配置问题还是集群的DNS出了问题,一个有效的方法,就是检查Master的DNS是否正常

# 在一个Pod里执行

$ nslookup kubernetes.default

Server:    10.0.0.10

Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default

Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

如果访问主节点的值都有问题,就需要检查kube-dns的状态和日志了,不然才是看自己的Service是否有问题

如果这个Service 没法通过ClusterIP访问到的时候,可以单是否有对应的EndPoints

$ kubectl get endpoints hostnames

NAME        ENDPOINTS

hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

而且如果readniessProbe没通过,也不会出现Endpoints列表中

如果Endpoints正常,就需要确认kube-porxy是否正确运行,我们通过kubeadm部署的集群,可以通过kube-proxy看到输出的日志

如果kube-proxy也没问题的话,就看下宿主机的iptables的话,一个iptables对应的规则如下

1.KUBE-SERVICE或者KUBE-NODEPORTS规则对应的Service的入口链,这是VIP和Service端口一一对应的

2.KUBE-SEP对应的DNAT,这个和Endpoints一一对应

3.KUBE-SVC 对应的负载均衡连,和Endpoints数目一致

4.如果是NodePort的话,还有POSTROUTING处的SNAT链

通过这些链的数目,转发的目的地址,过滤条件,查看是否具有异常

最后的没法访问的情况,是没配置好kubelet的hairpin-mode,没有正确的配置为hairpin-veth或者promiscuous-bridge

在本片中,我们说了如何从外部访问Service的三种方式 NodePort LoadBalancer和 External Name和具体的工作原理

这样就可以说明,Service,就是Kuberentes为Pod分配的,固定的,基于iptables的访问入口,这些入口代理的Pod信息,来自Etcd,由kube-proxy进行维护

Kubernetes中的Serivce和DNS机制,不具有多租户的能力,每个租户都有一套独立的Service规则,再比如,DNS也是每个租户最好都拥有自己的kube-dns

为何要求externallPs必须要是一个能够路由到Kubenrets的节点?

如果任何的Kubernetes都无法路由到,那么自然无法将流量转发给具体的Service

发表评论

邮箱地址不会被公开。 必填项已用*标注