
Service的虚拟IP为Pod在集群内部的互通提供了便利,而ExternalIP和NodePort,则让我们从集群外部对Service的访问成为了可能。本文介绍NodePort的网络原理。

注:本文为节选,欢迎点击文末“阅读原文”,移步晴耕小筑网站,获取更多信息

本文接续Kubernetes篇:认识External IP一文,向大家介绍另一种将Service暴露到集群以外的方法,来看一看Kubernetes的NodePort是怎么实现的。注:本文中的示例沿用了Kubernetes篇:认识External IP一文中所用的例子。
NodePort
NodePort是除ExternalIP以外的另一种暴露Service的方法。它把Service通过端口号暴露到集群中的节点主机上,这也是为什么它被称为NodePort的原因。这样一来,通过访问主机上的某个端口号,我们就可以访问到Service了。
下面我们就来为test-svc配置一个NodePort。还是执行kubectl edit命令:
$ kubectl edit service test-svc
按照下面的内容对test-svc的配置进行修改:
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"test-svc","namespace":"default"},"spec":{"ports":[{"port":80,"targetPort":"web-port"}],"selector":{"app":"lab-web"}}}
creationTimestamp: "2019-06-24T03:30:08Z"
name: test-svc
namespace: default
resourceVersion: "3832"
selfLink: /api/v1/namespaces/default/services/test-svc
uid: 5d85e807-9630-11e9-ab86-82d6af7a4ac8
spec:
clusterIP: 10.107.169.79
ports:
- port: 80
protocol: TCP
targetPort: web-port
selector:
app: lab-web
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
这里,我们去掉了之前的externalIPs;并且,把type属性从ClusterIP改成了NodePort。保存退出以后,再执行kubectl get services,确保test-svc的配置已经成功得到了更新:
$ kubectl get services -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24d <none>
test-svc NodePort 10.107.169.79 <none> 80:30454/TCP 47h app=lab-web
可以看到,EXTERNAL-IP一栏现在变成了<none>,而PORTS栏和原来相比则有了变化。NodePort的这种处理方式和Docker在bridge network模式下对外暴露容器端口号的方式很像。这里,冒号前面的数字就是Service在容器内部的端口号;冒号后面的数字则是Service在节点主机上暴露出来的一个随机端口号,也就是NodePort。
另外,我们还注意到CLUSTER-IP一栏和之前保持一致。这表明,NodePort是工作在ClusterIP的基础上的。当我们为Service配置NodePort时,Kubernetes依然会为Service配置ClusterIP。因此,NodePort不会阻止Pod从集群内部通过Cluster IP对Service进行访问。
再来看一下,iptables规则方面的变化情况:
② -A KUBE-NODEPORTS -p tcp -m comment --comment "default/test-svc:" -m tcp --dport 30454 -j KUBE-MARK-MASQ
③ -A KUBE-NODEPORTS -p tcp -m comment --comment "default/test-svc:" -m tcp --dport 30454 -j KUBE-SVC-W3OX4ZP4Y24AQZNW
⎢ ...
① -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
⎢ ...
⎢ -A KUBE-SVC-W3OX4ZP4Y24AQZNW -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-E2HMOHPUOGTHZJEP
⎢ -A KUBE-SVC-W3OX4ZP4Y24AQZNW -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-WFXGQBTRL5EC2R2Y
⎢ -A KUBE-SVC-W3OX4ZP4Y24AQZNW -j KUBE-SEP-EEXR7SABLH35O4XP
和原来相比,行①处的规则是一直存在于iptables的规则集里面的。即使没有定义NodePort,它也存在,只是那时还没有KUBE-NODEPORTS链。这条规则的意思是,当数据包的目标地址是LOCAL类型的时候(--dst-type LOCAL),则匹配该规则,并跳转到KUBE-NODEPORTS链。这表明我们是在集群里的节点主机上向Service发起访问的;
行②和行③处的规则是新加入的,表示当我们访问的端口号是30454时,会跳转到KUBE-SVC-W3OX4ZP4Y24AQZNW链,从那再往后就是正常的负载均衡逻辑了;
集成外部负载均衡
因为集群里的每个节点上都有大体相近的iptables规则集,所以在集群中的任何一个节点上向NodePort端口号发送请求,都可以访问到我们的test-svc。
基于这个原因,我们也可以把Kubernetes集群与外部的负载均衡服务进行集成,把集群中的所有节点都作为负载均衡服务连接的后端服务。然后再配上相应的健康检查(Health Check),比如监听集群节点的某个端口。当集群中有节点出现故障而无法访问时,可以由外部的负载均衡服务自动进行调度。
小结
ExternalIP和NodePort都是为了将Service暴露到Kubernetes集群之外,从而让外部的客户端也能访问到集群内部的Service。其中,
ExternalIP为Service提供了一个对外可见的IP地址;
NodePort则通过端口号直接把Service暴露到了集群节点上,通过访问节点IP和端口号,就可以访问到Service;
从iptables规则的角度来看,ExternalIP和NodePort都不过是原有Service基础上的规则叠加。在理解了Service网络的工作原理之后,再去理解ExternalIP和NodePort是非常容易的。
(未完待续)




