我们在聊到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