目录

一、基本概念

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/>&nbsp;Copyright &copy; 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?相关推荐

  1. Kubernetes的网络架构

    k8s的网络架构 flannel通信原理 UDP VXLAN host-gw flannel配置详解 calico通信原理 calico架构 BGP模式 IPIP模式 CNI详解 参考文献 flann ...

  2. 【云驻共创】华为云云原生之Kubernetes网络架构原理深度剖析(上)

    文章目录 前言 一.Kubernetes诞生背景 1.云原生的概念 2.云原生架构 3.Kubernetes(k8s) 二.Kubernetes基本网络模型剖析 1.概念厘清 1.1 二层桥接 VS ...

  3. Kubernetes容器平台架构之道

    1.Kubernetes容器平台架构之道         Kubernetes是一个开源容器编排平台,管理大规模分布式容器化软件应用,是云计算发展演进的一次彻底革命性的突破.Kubernetes是谷歌 ...

  4. Cable:360实现的新虚拟网络架构

    前言 OpenStack架构中,Neutron作为虚拟网络模块,管理虚机的网络.随着容器技术的发展,越来越多的应用部署到Kubernetes等容器编排系统中,而Kubernetes也有自带的网络管理模 ...

  5. 干货 | 云计算时代携程的网络架构变迁

    作者简介 赵亚楠,携程云平台资深架构师.2016 年加入携程云计算部门,先后从事 OpenStack.SDN.容器网络(Mesos.K8S).容器镜像存储.分布式存储等产品的开发,目前带领 Ctrip ...

  6. 数据中心网络架构的问题与演进 — 传统路由交换技术与三层网络架构

    戳蓝字"CSDN云计算"关注我们哦! 文章目录 目录传统路由交换技术路由和交换交换技术传统的 2 层交换技术具有路由功能的 3 层交换技术具有网络服务功能的 7 层交换技术路由技术 ...

  7. Cable--新虚拟网络架构介绍

    女主宣言 本文主要介绍为了统一管理不同编排系统的网络模块,简化虚拟网络功能的开发流程,虚拟网络工作组实现的新虚拟网络架构--Cable. PS:丰富的一线技术.多元化的表现形式,尽在"360 ...

  8. Kubernetes的系统架构与设计理念

    Kubernetes与云原生应用简介 随着Docker技术的发展和广泛流行,云原生应用和容器调度管理系统也成为IT领域大热的词汇.事实上,云原生应用的思想,在Docker技术火爆之前,已经由云计算技术 ...

  9. 安全自动化企业网络架构 (毕设分享)

    一.前言: 前段时间完成了自己的毕设项目--安全自动化企业网络架构.总的来说,该项目是一个对自己的挑战,其中涉及到Kubernetes容器云的搭建以及安全加固,DevOps CI/CD部署容器化监控平 ...

  10. 《Kubernetes与云原生应用》系列之Kubernetes的系统架构与设计理念

    http://www.infoq.com/cn/articles/kubernetes-and-cloud-native-applications-part01 <Kubernetes与云原生应 ...

最新文章

  1. 二值网络--Structured Binary Neural Networks for Accurate Image Classification and Semantic Segmentation
  2. 线性多播/线性广播/线性扩散/一般线性网络码
  3. 分享:RethinkDB 1.3 发布,分布式 JSON 数据库
  4. [linux]在Linux里设置环境变量的方法(export PATH)
  5. 前后台分离--概念相关
  6. 35张史上最全IT架构师技术知识图谱
  7. Tomcat如何配置X-Frame-Options头
  8. 为何要进入互联网行业做产品经理?
  9. ubuntu网络设置找不到有线连接_win7系统找不到网络打印机如何解决?局域网打印机共享怎么设置?...
  10. 改变世界面貌的十个数学公式
  11. 最全的测试计划模板参考
  12. Java-----四舍五入保留两位小数的方法
  13. 再谈微服务负载均衡器:Ribbon均衡器和SpringCloud自带LoadBalancer均衡器
  14. Intriguing Properties of Vision Transformers论文解析
  15. Android之页面添加水印
  16. 炉石传说 爬取全部卡牌
  17. 基于ZigBee的桥梁健康监测系统
  18. Datadog 能成为最大的云监控厂商吗
  19. skb_buff 详解(二)
  20. mycat的主从关系 垂直分库 水平分表 以及mycat分片联表查询的配置详解(mysql5.7系列)

热门文章

  1. 图像处理之灰色图转化为RGB图像
  2. 物联网知识1---RFID
  3. ROI区域图像叠加和 初级图像混合
  4. Linux桌面版横评:九、Mandriva Linux 2007 Spring One
  5. python xlwt追加_python excel写入及追加写入
  6. 音箱后面接口 COM 8欧 70V 100V
  7. 重新定义 \maketitle
  8. excel小写转大写公式_英文字母大小写的转换
  9. ARM920T及其MMU,Cache学习杂记(一)
  10. matlab 线性拟合polyfit_Matlab 线性拟合 非线性拟合_matlab非线性拟合函数