本文主要是介绍漫话Kubernetes的网络架构,该用NodePort还是Ingress还是load balancer?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一、基本概念
1. Kubernetes pod
2. Kubernetes service
3. Kubernetes NodePort
4. Kubernets Ingress
5. Kubernetes loadbalancer
二、从实际需求谈Kubernetes引入的各种网络概念
问题No 1:NodePort和Ingress好像实现原理差不多,有了NodePort为啥还整出个Ingress,既生瑜何生亮么?
问题No 2: 通过NodePort和Ingress已经可以实现从k8s集群外部访问k8s集群内部pod提供的服务,为啥还要在k8s集群前边放置一个传统的LB?
问题No 3: 再看一眼操蛋的service, 他真的有必要存在么?没有他不可以么?
要说清楚Kubernetes的网络架构,需要对计算机网络有比较深入的理解,至少是实战的CCNA or CCNP level的网络工程师,并且要对现代Linux所具备的各种网络功能非常理解才能彻底搞清楚底层实现细节,比如Linux的birdge, firewall (iptable), router/NAT等等功能。
现代Linux操作系统已经远远不是一个传统的单一操作系统,他集成了操作系统,交换机,路由器,防火墙等等很多功能。由于虚拟机、容器技术的发展,很多在传统网络设备中才有的功能,比如switch/route/firwall等等,都被移植到了单个Linux server中,做为Linux的一个一个模块单元。这样一个运行Linux操作系统的物理主机,如果开启虚拟化或容器功能,那就相当于传统的由多个物理主机 + 多个网络设备(交换机 + 路由器 + 防火墙)共同组成的环境。这种转变是由vmware, redhat, openstack, docker, kubernetes等等厂家和开源社区根据虚拟化/容器化技术逐渐演进,秉持节省成本,由软件代替专有网络设备、减少网络硬件设备支出的理念而产生的,这也是随着各种虚拟化,容器化和云技术逐步发展自然而然产生的结果。
本文不打算从底层的网络实现细节介绍Linux的交换、路由、防火墙功能,也不打算介绍Kubernetes实现的各种网络功能是如何由Linux的各种网络模块来支撑的,而是从最上层、最基本的、现实的业务组网需求来逐一介绍Kubernetes的基本网络相关的概念。
如果想了解kubernetes底层网络实现,大家可以参考以下这篇文章,写的非常好:
https://blog.csdn.net/gui951753/article/details/87387197
Kubernetes底层网络实现有多种方式可选,上面这篇文章介绍了其中比较流行的一种方式 -- flannel插件。什么?Kubernetes的底层网络要以插件的方式来实现?没错,这就是k8s设计牛B的的地方之一。底层网络可以采用任何我们见过的已有的、成熟的SDN(Software Defined Network)网络技术来稍加改造,以插件方式部署到k8s集群中。当然这些网络插件也还是建立在Linux操作系统的交换、路由、防火墙功能模块之上。具体有那些网络插件,大家可以参考如下官方文档:
https://kubernetes.io/zh/docs/concepts/cluster-administration/networking/
另外,想补充的是云原生计算基金会 (CNCF -- Cloud Native Computing Foundation) 最近宣布由灵雀云公司开源的容器网络项目Kube-OVN 正式进入 CNCF 沙箱(Sandbox)托管。这是全球范围内首个被CNCF纳入托管的开源CNI网络项目,也是国内容器公司首次将独立设计研发的项目成功贡献进入CNCF基金会。
Kube-OVN是灵雀云公司开源的基于OVN的Kubernetes网络组件Kube-OVN,提供了大量目前Kubernetes不具备的网络功能,并在原有基础上进行增强。通过将OpenStack领域成熟的网络功能平移到Kubernetes,来应对更加复杂的基础环境和应用合规性要求。
Kube-OVN主要具备五大主要功能:Namespace 和子网的绑定,以及子网间访问控制,静态IP分配,动态QoS,分布式和集中式网关,内嵌 LoadBalancer。将OpenStack社区的网络功能向Kubernetes平移,从而弥补Kubernetes网络的不足,打造更加完整的网络体系。
说了这么多,其实想说的是,k8s尽管已经出现有些时间了,但是网络这一块儿还是处于百家争鸣、不断变更,急速发展之中。
本文演示环境的k8s集群, 网络插件就是使用的flannel,是相对来说最简单、最易用的一个插件。
我们先来介绍k8s的一些基本概念(这些基本概念不做详细介绍,具体可以参考相关文档),然后从具体业务组网需求来介绍Kubernetes为什么会出现这些网络概念,或者为什么要支持这些网络功能。
一、基本概念
在介绍基本概念之前,为了后边行文简化,介绍几个约定的术语:
k8s: 指kubernetes, k根s之间正好是8个字母。
node: 一个node这里转指一个k8s集群中的主机。在k8s集群中,有master node(上边只部署k8s管理组件)和work node(上边可以运行用户自己创建的pod)
k8s集群:一个k8s集群由多个node组成。
k8s集群外部主机: 没有加入k8s集群的主机,不管是不是根k8s集群主机在同一个网段内,都叫k8s集群外部主机。
LB:Load Balancer - 复杂均衡器的简写。
1. Kubernetes pod
在kubernetes中, pod是最小的管理单元,在采用docker容器化技术的kubernetes环境中,一个pod可以包含一个和多个docker容器。但是一个pod只有一个network namespace,当一个pod有多个docker container(容器)时,这些docker container共享一个network namespace。简单说,就是这些同属一个pod的docker container共享相同的网络环境- 共同的网卡、IP、hostname、路由、网络协议栈等等。如果感觉理解困难,不知道什么时network namespace,可以暂时不考虑那么多,就认为这些同属于一个pod的docker container,就相当于功能属于一个主机,有共同主机名和IP等,但是他们除了网络,对内存、cpu的访问又是隔离的,不会因为一个container里的应用内存泄漏(OOM - Out Of Memory),或者High CPU,而导致其他container的应用受影响。同时各个container还会拥有自己的文件系统 - 对, 文件系统也是隔离的。轻易不要把多个container放到一个pod中,只有极个别情况才需要这样做。这一点<<kubernetes In Action>>这本书中解释的就很清楚:
2. Kubernetes service机制
简单说,一个k8s service就是由一组后端的、运行同样应用的pod共同提供的服务。为什么是一组,当然是为了提高高可用性和吞吐量。然而客户端访问不是直接访问这一组pod的每一个IP,这样一个一个指定太麻烦了,而且一旦某个pod发生故障在其他node重启,IP地址变更,客户端也需要随着更改。所以就需要为这一组pod提供的服务指定一个共同的位于前端IP。这个IP叫Cluster IP,或者Service IP。客户端(Client)将请求直接发给Service的ClusterIP,然后由这个ClusterIP所代表的service 对象再转发给后端的分布在不同的pod上,比如下图的pod1, pod2, pod3。为了简化,下图中没有标出pod1, pod2, pod3分别所处的node。
在client将请求发给clusterIP之后,service有多种代理模式将请求转发给后端pod,具体请参考如下官方文档:
https://kubernetes.io/zh/docs/concepts/services-networking/service/
从k8s集群外部如何访问k8s集群内部的service角度,又可以将service分为ClusterIP, NodePort, LoadBalancer等等。具体同样可以参考上边的官方文档连接。
下边我们以实验室环境进一步介绍k8s ClusterIP 类型的service。 比如下边代表了一个由三个物理主机组成的k8s集群,其中k8s-node1专门用于管理,k8s-node2根k8s-node3上面可以运行用户创建的pod:
[root@k8s-node1 ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-node1 Ready master 27h v1.17.3 10.0.2.4 <none> CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://20.10.2
k8s-node2 Ready <none> 26h v1.17.3 10.0.2.5 <none> CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://20.10.2
k8s-node3 Ready <none> 26h v1.17.3 10.0.2.15 <none> CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://20.10.2
在这个环境中,我们先创建一个yaml文件定义一个deployment,这个depoyment使用docker iamge tomcat:6.0.53-jre8来创建pod,并且是创建了三个pod副本。
[root@k8s-node1 k8s]# cat tomcat6-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: tomcat6name: tomcat6
spec:replicas: 3selector:matchLabels:app: tomcat6template:metadata:labels:app: tomcat6spec:containers:- image: tomcat:6.0.53-jre8name: tomcat
然后我们再创建一个yaml文件来为这个deployment(也即与这个deployment关联的这三个运行tomcat的pod)创建一个service,注意我们这里是要创建一个最基本的ClusterIP类型的service (默认就是这种类型),所以我们将NodePort相关的内容都注释掉,在下边一节我们再介绍NodePort类型的service。
[root@k8s-node1 k8s]# cat tomcat6-service.yaml
apiVersion: v1
kind: Service
metadata:labels:app: tomcat6name: tomcat6namespace: default
spec:
# externalTrafficPolicy: Clusterports:
# - nodePort: 31160- port: 80protocol: TCPtargetPort: 8080selector:app: tomcat6
# sessionAffinity: None
# type: NodePort
最后我们再定义一个yaml文件来创建一个独立的pod(不与任何deployment/service关联)
[root@k8s-node1 k8s]# cat nginx-standalone.yaml
apiVersion: v1
kind: Pod
metadata:labels:app: nginxname: nginxnamespace: default
spec:containers:- image: nginximagePullPolicy: IfNotPresentname: nginx
有了上边的三个yaml文件,我们就可以执行'kubectl apply -f‘ 跟上上边的三个文件来创建相应的资源了:
[root@k8s-node1 k8s]# kubectl apply -f tomcat6-deployment.yaml
deployment.apps/tomcat6 created
[root@k8s-node1 k8s]# kubectl apply -f tomcat6-service.yaml
service/tomcat6 created
[root@k8s-node1 k8s]# kubectl apply -f nginx-standalone.yaml
pod/nginx created
创建完之后,我们会看到如下资源:有三个运行tomcat 的pod,和一个独立的nginx pod。有一个名字是tomcat6的service, Service IP (CLUSTER-IP)是10.96.130.163, Service port是80。那这个service如何直到对应后端哪些个pod呢?它是通过最后一列SELECTOR,lable是app=tomcat6来获取哪些pod属于这个service的。因为通过deployment 创建的三个运行tomcat的pod,他们的label都是app:tomcat6, 而单独创建的nginx pod的label是app: nginx,所以这样就能区分。
k8s集群内部其他的pod就可以service ip+port的方式或者直接通过service name的方式,将请求发给这个前端的service IP, 然后由service 对象转发给后端这个service对应的某一个运行tomcat的后端pod(嗯,你们看错,我把k8s的service 的前端部分叫做service对象,纯粹为了说明简单,理解更容易,避免进入底层的实现细节,我们可以把一个service理解成一个真实存在的对象或者软件模块,他有域名或者name, IP,PORT)。
[root@k8s-node1 k8s]# kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx 1/1 Running 0 13h 10.244.1.15 k8s-node2 <none> <none>
pod/tomcat6-5f7ccf4cb9-8jxz7 1/1 Running 2 2d11h 10.244.1.11 k8s-node2 <none> <none>
pod/tomcat6-5f7ccf4cb9-gbtt5 1/1 Running 0 13h 10.244.2.10 k8s-node3 <none> <none>
pod/tomcat6-5f7ccf4cb9-hvxs5 1/1 Running 0 13h 10.244.2.11 k8s-node3 <none> <none>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d6h <none>
service/tomcat6 ClusterIP 10.96.73.198 <none> 80/TCP 13s app=tomcat6NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/tomcat6 3/3 3 3 2d11h tomcat tomcat:6.0.53-jre8 app=tomcat6NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/tomcat6-5f7ccf4cb9 3 3 3 2d11h tomcat tomcat:6.0.53-jre8 app=tomcat6,pod-template-hash=5f7ccf4cb9
这个例子中,pod里边每个tomcat监听在8080端口:
[root@k8s-node1 ~]# kubectl get service/tomcat6 -o yaml
apiVersion: v1
kind: Service
metadata:creationTimestamp: "2021-01-30T17:26:23Z"labels:app: tomcat6name: tomcat6namespace: defaultresourceVersion: "160877"selfLink: /api/v1/namespaces/default/services/tomcat6uid: d3c2ee07-bd28-4d9b-941e-2b4dacc40faf
spec:clusterIP: 10.96.246.235externalTrafficPolicy: Clusterports:port: 80protocol: TCPtargetPort: 8080 <<<< 这个targetPort就是pod里,tomcat的监听端口。简单起见,可以 将一个pod理解成一个主机或虚拟机哦selector:app: tomcat6 <<<< 通过app=tomcat6这个选择器,将我的k8s集群环境中所有打有 app=tomcat6 label的pod做为后端server。而我这个实验环境中正好运行tomcat的三个pod都打有app=tomcat6 标识。sessionAffinity: Nonetype: NodePort
status:loadBalancer: {}
这是不是很熟悉的节奏?对,这就是传统的负载均衡机制。在LB后端可以有多个服务器,上边跑的同样的应用。LB上可以配置一个对外IP。所有客户端的访问,都是将请求发给LB上配置的对外的这个service IP,然后LB收到请求后,将请求再转发给后端的server。这样就实现了系统高可用性(High Availability)或者说冗余性(redundancy),避免单点故障(single point failure)。比如下图:
k8s的service我们也可以把他看成一个LB, 只不过这个LB不是硬件实现的,而是由软件实现的,而且他就存在与我们运行Linux的node内部。
另外,k8s service对象与传统的硬件或者软件LB还有一点不一样。在我们的例子中,如果在三个node上(k8s-node1, k8s-node2, k8s-node3) 分别执行ifconfig 或者ip address命令,你会发现没有哪一个node的网卡配置了service ip - 10.96.9.14。
[root@k8s-node1 ~]# ip a |grep inetinet 127.0.0.1/8 scope host loinet6 ::1/128 scope hostinet 10.0.2.4/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0inet6 fe80::a00:27ff:fe3c:4edf/64 scope linkinet 192.168.56.100/24 brd 192.168.56.255 scope global noprefixroute eth1inet6 fe80::a00:27ff:fe9a:2380/64 scope linkinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0inet 10.244.0.0/32 scope global flannel.1inet6 fe80::c8a6:99ff:fe19:fb79/64 scope linkinet 10.244.0.1/24 brd 10.244.0.255 scope global cni0inet6 fe80::2839:46ff:fefa:3234/64 scope linkinet6 fe80::f433:2eff:fe7b:d9cf/64 scope linkinet6 fe80::689a:d3ff:fe54:a446/64 scope link[root@k8s-node2 ~]# ip a |grep inetinet 127.0.0.1/8 scope host loinet6 ::1/128 scope hostinet 10.0.2.5/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0inet6 fe80::a00:27ff:febc:459d/64 scope linkinet 192.168.56.101/24 brd 192.168.56.255 scope global noprefixroute eth1inet6 fe80::a00:27ff:fea8:cf80/64 scope linkinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0inet 10.244.1.0/32 scope global flannel.1inet6 fe80::ac94:26ff:fe72:2907/64 scope linkinet 10.244.1.1/24 brd 10.244.1.255 scope global cni0inet6 fe80::c74:acff:fe48:aa3d/64 scope linkinet6 fe80::60ca:61ff:fed6:b4c2/64 scope linkinet6 fe80::c07c:63ff:feb2:71d4/64 scope linkinet6 fe80::5c34:2dff:fe5d:b8e2/64 scope link[root@k8s-node3 ~]# ip a |grep inetinet 127.0.0.1/8 scope host loinet6 ::1/128 scope hostinet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0inet6 fe80::a00:27ff:fe18:f310/64 scope linkinet 192.168.56.102/24 brd 192.168.56.255 scope global noprefixroute eth1inet6 fe80::a00:27ff:fe51:5aa2/64 scope linkinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0inet 10.244.2.0/32 scope global flannel.1inet6 fe80::48b3:98ff:fe6b:681b/64 scope link
然而我们在 三个node节点上执行命令"curl http://10.96.9.14/index.html"时,发现都能正常返回结果(目前所有运行tomcat的三个pod都跑在2好节点上),哪怕是不运行用户创建的pod,只运行管理服务的1号节点也能返回结果:
[root@k8s-node3 ~]# curl http://10.96.9.14/index.html
...
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><title>Apache Tomcat</title><style type="text/css">...
...<p id="footer"><img src="tomcat-power.gif" width="77" height="80" alt="Powered by Tomcat"/><br/> Copyright © 1999-2017 Apache Software Foundation<br/>All Rights Reserved</p></td></tr>
</table>
在不属于该service
是不是有点神奇?这其实是通过在这三个物理主机的iptable中做了相关配置实现的。这里不做详细介绍。简单说就是当你在kubernetes中创建一个service时,kubernetes就会在kubernetes集群的每一个node的iptables做同样的配置来实现将发往该service IP+port的包转发给该service对应的后端pods(具体过程是kubectl 或别的客户端在向api-server提交一个创建一个service资源请求后,api-server会发指令给部署在每个node节点上kube-proxy,然后由kube-proxy完成在每个node上的iptable修改,这是kube-proxy组件的功能之一,后边在介绍NodePort时,会介绍kube-proxy组件另外一个功能)。这样实现了当你在任何一个主机上或者其他的pod里边执行"curl http://10.96.9.14/index.html"时,请求先会经过node本机的iptable防火墙的处理,根据里边的配置,将请求转发给该service对应的其中一个pod。也就是说我们上边将service理解成一个object或者一个软件模块,完全是为了理解和说明简单,底层实现完全是依赖了Linux的各种网络功能。我们后边还会延续这种化繁为简的思路,将service就理解成一个前端是一个具备LB功能的对象,后端由一组提供相同服务的pod组成。
3. Kubernetes NodePort
上边说了service IP不存在于kubernetes集群任何一个node的任何一个网卡上,所以想要从集群外访问service 提供的服务,就不能通过service IP来访问。service IP只能在node上或pod内部可以访问。
为了解决从外部访问kubernetes 集群的service IP问题,最自然的想法就是将请求发给集群中的某一个node的对外网卡,然后网卡收到请求后,再将请求发给service 的IP + port,最终service对象将请求转发给后端的某一个pod。对 -- 这就是NodePort的由来。以下示例中,我们先删除之前创建的service,然后通过“ kubectl expose deployment tomcat6 --port=80 --target-port=8080 --type=NodePort”命令(该命令虽然叫expose,实际就是创建一个service),重新为tomcat6这个deployment创建了一个service,只不过这次我们指定了service type=NodePort,这时我们会看到在service 一栏, PORT(S)一栏,在80:后多了个31160端口,这个就是kubernetes为我们随机指定的一个端口。然后你在一个k8s集群外部主机上(非k8s node节点),通过浏览器访问http://node-ip:31160,就可以访问tomcat service了。这里node-ip可以是k8s集群中任何一个node。
[root@k8s-node1 ~]# kubectl delete service/tomcat6
service "tomcat6" deleted
[root@k8s-node1 ~]# kubectl expose deployment tomcat6 --port=80 --target-port=8080 --type=NodePort
service/tomcat6 exposed
[root@k8s-node1 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/tomcat6-5f7ccf4cb9-8jxz7 1/1 Running 1 8h
pod/tomcat6-5f7ccf4cb9-lwp48 1/1 Running 0 5h22m
pod/tomcat6-5f7ccf4cb9-vc4wq 1/1 Running 0 5h14mNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28h
service/tomcat6 NodePort 10.96.246.235 <none> 80:31160/TCP 5sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/tomcat6 3/3 3 3 8hNAME DESIRED CURRENT READY AGE
replicaset.apps/tomcat6-5f7ccf4cb9 3 3 3 8h
NodePort实际上是利用了kubernetes一个叫做kube-proxy的组件来实现了将接收到的k8s集群外部发来的请求,代理转发给kubernetes创建的service,然后再由service 转发给对应的后端pod。
[root@k8s-node1 ~]# netstat -anlp |grep 31160
tcp6 0 0 :::31160 :::* LISTEN 2899/kube-proxy
[root@k8s-node1 ~]#[root@k8s-node2 ~]# netstat -anlp |grep 31160
tcp6 0 0 :::31160 :::* LISTEN 1704/kube-proxy
[root@k8s-node2 ~]#[root@k8s-node3 ~]# netstat -anlp |grep 31160
tcp6 0 0 :::31160 :::* LISTEN 1635/kube-proxy
[root@k8s-node3 ~]#
这些kube-proxy进程,实际上是由以下这些名字以kube-proxy开头的pod提供的,他们都属于kube-system名称空间,在安装kubenetes软件时,由安装程序创建的。
[root@k8s-node1 ~]# kubectl get pods --namespace kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-7f9c544f75-n4z6r 1/1 Running 3 28h 10.244.0.9 k8s-node1 <none> <none>
coredns-7f9c544f75-w4wqm 1/1 Running 3 28h 10.244.0.8 k8s-node1 <none> <none>
etcd-k8s-node1 1/1 Running 3 28h 10.0.2.4 k8s-node1 <none> <none>
kube-apiserver-k8s-node1 1/1 Running 3 28h 10.0.2.4 k8s-node1 <none> <none>
kube-controller-manager-k8s-node1 1/1 Running 4 28h 10.0.2.4 k8s-node1 <none> <none>
kube-flannel-ds-amd64-2wcb4 1/1 Running 3 28h 10.0.2.15 k8s-node3 <none> <none>
kube-flannel-ds-amd64-jtpvg 1/1 Running 2 28h 10.0.2.5 k8s-node2 <none> <none>
kube-flannel-ds-amd64-xbpfs 1/1 Running 3 28h 10.0.2.4 k8s-node1 <none> <none>
kube-proxy-7dhbm 1/1 Running 3 28h 10.0.2.4 k8s-node1 <none> <none>
kube-proxy-lxx2w 1/1 Running 3 28h 10.0.2.15 k8s-node3 <none> <none>
kube-proxy-z5wcf 1/1 Running 2 28h 10.0.2.5 k8s-node2 <none> <none>
kube-scheduler-k8s-node1 1/1 Running 3 28h 10.0.2.4 k8s-node1 <none> <none>
并且kube-proxy-xxx 这几个pod还有个特点,就是他们与他们所在的node 共享网络name space。比如以运行在node1上的kube-proxy-7dhbm为例,他的IP是10.0.2.4,其实就是k8s-node1的IP:
[root@k8s-node1 ~]# kubectl get pods kube-proxy-7dhbm -o yaml -n kube-system |grep host- --hostname-override=$(NODE_NAME)hostNetwork: true- hostPath:- hostPath:hostIP: 10.0.2.4
4. Kubernets Ingress
有了NodePort,似乎解决了从k8s外部访问k8s内部pod提供的服务的问题,但是NodePort有一些问题(这里不做解释,会放在本文第二部分详细介绍)而Ingress比较好的解决了这些问题,而且Ingress在针对http/https服务特别好使,所以Ingress非常重要,使用的非常多。
类似于NodePort,Ingress概念(docker也有个Ingress,但是完全不是一样的东东哦)的引入也是为了解决从k8s集群外访问k8s集群提供的服务的。他的思路根NodePort有写类似,外部也是先将请求通过k8s任何一个node的IP发给k8s node,当node收到请求后,就不是发给kube-proxy代理了,而是发给一个Node上的一个叫做Ingress的监听进程(在我们的例子中,Ingress用nginx实现,所以实际上就是nginx进程),然后Ingress进程再将请求抓发给该node上对应的service IP + port,最后转发给对应的后端port。我们来通过一个例子来直观感受一下:
首先安装Ingress组件。像其他k8s管理组件一样, Ingress也是以pod/service等形式部署,并且存在于一个自定义的namspace - 叫ingress-nginx的名称空间。
[root@k8s-node1 ~]# kubectl apply -f ingress-controller.yaml
安装完之后,
[root@k8s-node1 ~]# kubectl get ns
NAME STATUS AGE
default Active 29h
ingress-nginx Active 9h <<<<<<<
kube-node-lease Active 29h
kube-public Active 29h
kube-system Active 29h[root@k8s-node1 ~]# kubectl get all --namespace ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-ingress-controller-jj88x 1/1 Running 2 9h <<<<<
pod/nginx-ingress-controller-k2d4n 1/1 Running 2 9h <<<<<NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx ClusterIP 10.96.38.18 <none> 80/TCP,443/TCP 9hNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/nginx-ingress-controller 2 2 2 2 2 <none> 9h
[root@k8s-node1 ~]# kubectl get all -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-ingress-controller-jj88x 1/1 Running 2 9h 10.0.2.5 k8s-node2 <none> <none>
pod/nginx-ingress-controller-k2d4n 1/1 Running 2 9h 10.0.2.15 k8s-node3 <none> <none>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/ingress-nginx ClusterIP 10.96.38.18 <none> 80/TCP,443/TCP 9h app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginxNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
daemonset.apps/nginx-ingress-controller 2 2 2 2 2 <none> 9h nginx-ingress-controller siriuszg/nginx-ingress-controller:0.20.0 app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
我们这里的Ingress采用的是nginx实现的,这其实也很好理解,我们都知道nginx性能高,而且可以实现反向代理功能(实际就是代理功能,网上好多文章,一直没搞懂为啥叫反向代理,而不是简简单单就叫代理....),所以用nginx做Ingress再合适不过,除了用ngin实现ingress,也有使用别的厂家实现的Ingress,但是天下乌鸦一般黑0_0,原理都是差不多的。进一步检查Ingress的配置,发现hostNetwork也设置的是true,就表示Ingress的pod与所在node的主机也是共享network namespace,根上边提到的kube-proxy一样。简单理解,就是Ingress的pod里边的进程就相当直接运行在node上一样。
[root@k8s-node1 ~]# less ingress-controller.yaml
apiVersion: v1
kind: Namespace
metadata:name: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
...
...
spec:spec:hostNetwork: true <<<<<<<<<<
...
...ports:- name: httpcontainerPort: 80 <<<<<<<<- name: httpscontainerPort: 443 <<<<<<<<
并且ingress是在每个node上都部署的,他的监听端口是80和443。因为他根node共享网络,我们可以直接在node上来确认下:
[root@k8s-node2 ~]# netstat -anlp |egrep ':80|:443'
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2930/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2930/nginx: master[root@k8s-node3 ~]# netstat -anlp |egrep ':80|:443'
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2148/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2148/nginx: master
下一步,我们需要设置规则,将客户端访问的 不同URL映射到不同的k8s service上。举个例子,比如www.test.com/app1映射到k8s service 1上,www.test.com/app2映射到另外一个k8s service 2上,假设这两个k8s service 后端对应的分别是两组pod,第一组pod的上的tomcat部署的是app1,的二组部署的是app2,这样我们就需要为ingress设置规则,将URL是www.test.com/app1,发给service 1对应的IP + port,将 www.test.com/app2发给第二给service IP + port。我们这里为了简单演示,只简单创建一个URL,然后映射到我们的tomcat对应的service上
[root@k8s-node1 k8s]# cat ingress-demo.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: web
spec:rules:- host: www.test.comhttp:paths:- backend:serviceName: tomcat6servicePort: 80[root@k8s-node1 k8s]# kubectl apply -f ingress-demo.yml
ingress.extensions/web created[root@k8s-node1 k8s]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
web www.test.com 80 98s
然后我们在k8s集群外部主机编辑etc/host文件,增加一行:
node-ip www.test.com
上边的node-ip可以是k8s集群中,除了管理node,任何一个node的对外ip。这样在外部主机上,浏览器里边输入www.test.com,就可以响应tomcat的默认index.html文件内容了。具体消息包转发流程是:
客户端(k8s集群外部主机)将HTTP请求发给某一个node的80端口,而80端口是ingress-nginx在监听的,收到包后,解析里边的URL,然后根据我们上边设置的规则,将包转发给service tomcat6,就是我们这个集群中目前唯一的一个service,然后通过Linux的iptables模块转发给对应的pod上的tomcat进程。
5. Kubernetes loadbalancer
所谓的k8s LB,其实就是在k8s集群外部放置了一个LB,这里的LB专指传统的LB哦,不是什么k8s service, ingress, kube-proxy这些东东。客户端请求首先送到这个LB,然后LB再将请求发给每一个k8s node节点。
二、从实际需求谈Kubernetes引入的各种网络概念
上边谈了那么多,是不是很晕?下边重点来了,要让你不晕,化繁为简,才是正道 - 即使底层的细节不了解也没关系 ...
下边我们带着问题来梳理一下:
问题No 1:NodePort和Ingress好像实现原理差不多,有了NodePort为啥还整出个Ingress,既生瑜何生亮么?
以后添加
问题No 2: 通过NodePort和Ingress已经可以实现从k8s集群外部访问k8s集群内部pod提供的服务,为啥还要在k8s集群前边放置一个传统的LB?
以后添加
问题No 3: 再看一眼操蛋的service机制(docker的routing mesh机制), 他真的有必要存在么?没有他不可以么?
如果只考虑下边这个场景:
因为使用了service这个功能之后, 我们发现客户端将请求发给k8s集群外部的LB之后,LB再将请求转发给k8s集群的node,而不管是LB将请求发给k8s集群的NodePort还是Ingress的监听端口,NodePort和Ingress都是将请求转发给k8s service的ip+port, k8s service又做了一遍负载均衡,将请求最终发给pod。为什么不能NodePort或者Ingress将收到的请求直接发给自己所在node上的pod呢?这样做两次负载均衡,效率势必会下降,难道就不能取消service机制(routing mesh机制),让他变成像我们传统的负载均衡组网,就只完成一次负载均衡么?
这个还真不行!!!
其实原因也是显而易见的,第一点就是,一个庞大的k8s集群,可能有几百台、上千台node,而你的某一个服务可能只由几个pod提供。这几个pod最多就分布在几个node上。如果没有service,前端的LB在指定后边的要转发的目的server时,应该如何指定呢?如果只指定了pod所在的那几个node做为转发目的server,那一担pod故障宕机被重新在别的node上拉起,前端LB起不是不知道如何再将请求发给这个新拉起的pod了?这也是service存在的原因 - 我前段LB可以随便指定几个node的IP做为转发目的地,不是这几个node上有没有最终要发给的pod,这些node收到之后,都会将请求再转发给service, 然后最终转发给pod。
另外service机制必须一个原因,就是kubernetes官网文档在介绍service概念开头时提到的,这里原封不动贴过来:
https://kubernetes.io/zh/docs/concepts/services-networking/service/
动机
创建和销毁 Kubernetes Pod 以匹配集群状态。 Pod 是非永久性资源。 如果你使用 Deployment 来运行你的应用程序,则它可以动态创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用工作量的后端部分?
进入 Services。
这篇关于漫话Kubernetes的网络架构,该用NodePort还是Ingress还是load balancer?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!