漫话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: tomcat6
  name: tomcat6
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat6
  template:
    metadata:
      labels:
        app: tomcat6
    spec:
      containers:
      - image: tomcat:6.0.53-jre8
        name: 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: tomcat6
  name: tomcat6
  namespace: default
spec:
#  externalTrafficPolicy: Cluster
  ports:
#  - nodePort: 31160
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    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: nginx
  name: nginx
  namespace: default
spec:
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: 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=tomcat6

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES               SELECTOR
deployment.apps/tomcat6   3/3     3            3           2d11h   tomcat       tomcat:6.0.53-jre8   app=tomcat6

NAME                                 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: tomcat6
  name: tomcat6
  namespace: default
  resourceVersion: "160877"
  selfLink: /api/v1/namespaces/default/services/tomcat6
  uid: d3c2ee07-bd28-4d9b-941e-2b4dacc40faf
spec:
  clusterIP: 10.96.246.235
  externalTrafficPolicy: Cluster
  ports:
    port: 80
    protocol: TCP
    targetPort: 8080           <<<< 这个targetPort就是pod里,tomcat的监听端口。简单起见,可以 
                                     将一个pod理解成一个主机或虚拟机哦
  selector:
    app: tomcat6               <<<< 通过app=tomcat6这个选择器,将我的k8s集群环境中所有打有 
                                    app=tomcat6 label的pod做为后端server。而我这个实验环境中正好运行tomcat的三个pod都打有app=tomcat6 标识。
  sessionAffinity: None
  type: 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 inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
    inet 10.0.2.4/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
    inet6 fe80::a00:27ff:fe3c:4edf/64 scope link
    inet 192.168.56.100/24 brd 192.168.56.255 scope global noprefixroute eth1
    inet6 fe80::a00:27ff:fe9a:2380/64 scope link
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    inet 10.244.0.0/32 scope global flannel.1
    inet6 fe80::c8a6:99ff:fe19:fb79/64 scope link
    inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0
    inet6 fe80::2839:46ff:fefa:3234/64 scope link
    inet6 fe80::f433:2eff:fe7b:d9cf/64 scope link
    inet6 fe80::689a:d3ff:fe54:a446/64 scope link

[root@k8s-node2 ~]# ip a |grep inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
    inet 10.0.2.5/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
    inet6 fe80::a00:27ff:febc:459d/64 scope link
    inet 192.168.56.101/24 brd 192.168.56.255 scope global noprefixroute eth1
    inet6 fe80::a00:27ff:fea8:cf80/64 scope link
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    inet 10.244.1.0/32 scope global flannel.1
    inet6 fe80::ac94:26ff:fe72:2907/64 scope link
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
    inet6 fe80::c74:acff:fe48:aa3d/64 scope link
    inet6 fe80::60ca:61ff:fed6:b4c2/64 scope link
    inet6 fe80::c07c:63ff:feb2:71d4/64 scope link
    inet6 fe80::5c34:2dff:fe5d:b8e2/64 scope link

[root@k8s-node3 ~]# ip a |grep inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
    inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
    inet6 fe80::a00:27ff:fe18:f310/64 scope link
    inet 192.168.56.102/24 brd 192.168.56.255 scope global noprefixroute eth1
    inet6 fe80::a00:27ff:fe51:5aa2/64 scope link
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    inet 10.244.2.0/32 scope global flannel.1
    inet6 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          5h14m

NAME                 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   5s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/tomcat6   3/3     3            3           8h

NAME                                 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   9h

NAME                                      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-nginx

NAME                                      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-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
...
...
spec:
    spec:
      hostNetwork: true     <<<<<<<<<<
...
...
          ports:
            - name: http
              containerPort: 80   <<<<<<<<
            - name: https
              containerPort: 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.com
    http:
      paths:
        - backend:
           serviceName: tomcat6
           servicePort: 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。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值