Pod概述

2023-04-22,

Pod的类型

Pod的类型有如下两个:

自主式Pod:自主式Pod的含义简白来说就是不是被控制器管理的Pod,另一种就是被控制管理的Pod,不被控制器管理的Pod你会发现,它一旦死亡的话,就没有人把它拉起来,也不会进行重启,比如Pod死亡了,也不会去创建Pod副本,去满足它的期望值,这都是自主式Pod的缺点。
控制器管理的Pod:只要创建Pod就会创建Pause容器,如果已经创建了容器,会共用Pause的网络栈,共用Pause的存储卷,也就意味着创建的容器没有自己独立的IP地址,容器有的只是Pause的地址或者Pod的地址,容器之间互相隔离,但是容器之间的进程不隔离,比如一个容器里是php-fpm服务,一个容器里是nginx服务,nginx容器想要反向代理到php-fpm容器上只需要写localhost:9000即可,不需要写IP地址、映射等等。原因是因为两个容器都共享的Pause的网络栈,既然是共享容器之间都是可以见面的,也就意味着在同一个Pod里面,端口不能冲突,如果一个容器是80,另一个容器也是80,那这个端口是起不来的或者是起来以后无限重启。假如Pod挂载了一个存储,容器1和容器2都想去访问到这个存储,同理,容器1和容器2也会共享Pause的存储,也就意味着在同一个Pod里即共享网络又共享存储卷。


Pod的控制器类型

以下是Pod的控制器类型:

ReplicationController:ReplicationController用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来替代;而如果异常多出来的容器也会被自动回收。在新版本的Kubernetes中建议使用ReplicationControlle;
ReplicaSet:跟ReplicationController没有本质的不同,只是名字不一样,并且ReplicaSet支持集合式的Selector,我们在创建Pod的时候会给它打标签例如:app=apache,version=v1,我们会为它打一堆的标签,当我们有一天想删除我们的容器或一些设施的时候。我可以这样去说。当app=apache,version=v1的时候,我要干嘛干嘛。
Deployment:虽然ReplicaSet可以独立使用,但一般还是建议使用Deployment来自动管理ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如ReplicaSet不支持rolling-update(滚动更新)但Deployment支持)。比如我们创建了两个Pod,两个Pod都是v1版,我们现在想要进行滚动更新,它先删除一个Pod或者是它先多一个Pod(多一个Pod是v2版,新版本),然后再把这个老旧Pod v1版删除,然后再创建一个Pod是v2版的,然后再删除一个旧的v1版的Pod,最终就会出现最新的版本状态了。为什么要把两个放在一起说呢?原因是Deployment并不支持Pod的创建。
HAP(Horizo ntal Pod AutoScale):Horizontal Pod Autoscaling仅适用于Deployment和ReplicaSet,在v1版本中仅支持根据Pod的CPU利用率扩缩容,在vlalpha版本中,支持根据内存和用户自定义的metric扩缩容。
StatefullSet:StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:
稳定的持久化存储:即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现。
稳定的网络标志:即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现。
有序部署,有序扩展:即Pod是有顺序的,在部署或者扩展的时候要依次定义的顺序依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现。为什么之前的Pod都必须是Running状态和Ready状态呢?是因为在大型集群中是有一定的启动顺序的,比如说一个nginx、一个apache,一个mysql,如果先起nginx,它会去解析apache,发现apache不在,所以要设置先后顺序。
有序收缩,有序删除:(即从N-1到0)
DaemonSet:DaemonSet确保全部(或者一些)Node上运行一个Pod的副本。当有Node加入集群时,也会为它们新增一个Pod。当有Node从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有的Pod,这里面有一些说法,为什么说是全部(或者一些呢),因为我们在创建Pod的时候,会为Pod打一些污点,那这些污点可以是不被调度的,所以在DaemonSet创建的时候,这些打了污点的Node就不会创建Pod,但是正常情况下默认是,所有的Node都会被运行Pod,运行一个且只有一个需要注意。又假如我在一个Node上运行了好几个不同的Pod,你可以把这个好几个Pod里的主要进程给它提取出来,放到同一个Pod里面,不同容器上,那么这样的话也可以通过一个DaemonSet也可以去设置,当然也可以设置三个不同的DaemonSet。使用DaemonSet的一些典型用法:
运行集群存储daemon:例如在每个Node上运行glusterd、ceph。
在每个Node上运行日志收集daemon:例如fluentd、logstash。
在每个Node上运行监控daemon:例如Prometheus Node Exporter。
Job,Cronjob:Job负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。Cron Job管理基于时间的Job,举个例子,我想备份数据库,备份数据库完全可以把代码放到Pod里去运行,那我再放到Job里去执行一下,那我们的Job就可以正常运行,把我们的数据库备份出来,在Linux运行脚本不是也是一样的嘛?但是,我们这个Pod是可以重复利用的,这是之一,之二如果脚本执行意外退出是没办法重新执行的,但是Job如果判断这个脚本不是正常退出,那它就会重新执行一遍,直到正常退出为止,并且你还可以设置它的正常退出的次数,比如你正常退出2次,我才允许你这个脚本执行成功。即:
在给定时间点只运行一次
周期性地在给定时间点运行


Deployment和ReplicaSet滚动更新原理

  假设我们创建了一个Deployment,它会去创建一个ReplicaSet,也就意味着这个ReplicaSet并不是我们自己定义的,它是由Deployment定义的,那ReplicaSet再去创建我们对应的Pod,先创建3个Pod,如果有一天跟Deployment说,把镜像给我更新成v2版,它会怎么做呢?

它会创建一个ReplicaSet,它会先启动第一个容器,镜像为v2版,然后退出一个v1版的容器,启动第二个容器v2版,会退出一个v1版的容器,启动第三个容器v2版,退出一个v1版的容器,达到滚动更新。

并且还可以回滚,比如v2版本有一些bug,它会去新建老旧版的v1版,然后删除新版的v2,依次类推,当回滚的时候,它并不会把老旧版本删掉,而是禁用,回滚的时候会启用老旧版本,达到回滚的目的。


HPA根据Pod的CPU利用率扩缩容原理

  例如,我现在运行了一个ReplicaSet,ReplicaSet下面管理了两个Pod,然后我再定义一个HPA(HPA也是一个对象,需要被定义),HPA定义的是基于ReplicaSet去定义的,当我们的CPU大于80的时候,那就进行扩展,最大值是10个,最小值是2个,这是一种描述方式,cpu > 80;Max 10;Min 2。也就意味着HPA会去监控当前Pod的资源利用率,当CPU利用率达到80的时候,它会去新建Pod,直到达到它的最大值,当然,它创建了3个Pod的CPU利用率已经达不到80%了,它就不会创建了,当每创建一个时,它如果还是大于80%,它才会去创建,一旦Pod的CPU利用率变低了以后,Pod就会被回收,保持到最小值,所以这就达到了一个水平扩展的效果。


服务发现

  客户端想要去访问一组Pod,记住是一组Pod,如果这些Pod不相干的话,是不能通过我们的service去代理的,那Pod必须要有相关性,比如说是我们的ReplicaSet、ReplicationController、Deployment创建的或者说有同一组标签,那都可以被我们的service所收集到,这些又衍生出来一个含义,service去收集这些Pod是通过我们的标签去选择到的。选择到以后,service会有自己的一个IP+端口,那我们访问service的IP+端口就会间接的访问到我们的Pod服务,并且这里是有一个RR(轮询)算法存在的,访问Pod1、Pod2、Pod3依次类推。


理解案例

  假如我有一套上面架构图的集群,也就意味着,首先我要构建一个apache的集群,再构建一个mysql的集群,再构建一个squid的集群,然后再构建一个LVS,如果我想把这个架构放在K8S里运行的话,我们来看一下需要几步:第一步:mysql要先运行成一个Pod,当然我们mysql现在放在我们K8S中,放在我们的StatefullSet控制器里是可以做到的,但是集群化的话,不是那么方便,mysql封装完以后。

  我们还有apache+fpm,既然有这么多节点,我们之前学过控制器,我们是不是可以在Deployment上去创建,也就是说我创建一个Deployment,Deployment会指定它的副本数目,指定3个副本,那这样的话就有三个不同的apache+fpm的Pod的存在了。

  再往上会有三个squid,那么squid是不是也可以把它封装成Pod,通过控制器把它给控制,那LVS就可以靠我们集群本身的一个功能把它负载调度,那这样的话,会出现一种结构,你会发现不太好写,squid要写反向代理的话需要写三台机器,并且我们之前说过一个概念,Pod在退出以后,在重新建立以后,这个IP地址会变换对吧,除非你采用的是StatefullSet,但是在php-fpm里面是不是没有意义啊,所以怎么办呢?我们就可以在前端,在前面加一个service,这个service就是php-fpm的service,它会绑定php-fpm的标签,选择进行绑定,那squid想要进行反向代理的话就不需要去写php-fpm这3台php-fpm的地址了,因为我们一直在强调这个概念,php-fpm一旦死亡以后,我们控制器也会把它维持在3个副本,它会创建一个新的副本来取代死亡的副本,那取代它的副本跟现在的副本,它的IP是不一样的,那squid在里面填写的是它的目标IP的话,那就会出现问题,副本一换的话,我们squid里面的配置是不是得重新写,要重新修改,重启服务,这样不太友好,所以我们的squid里面写的是service php-fpm的地址,那这样的话,squid只需要指定到我们的service即可,并且php-fpm是一个Pod,mysql也是一个Pod,它们之间肯定会出现一定的关联,比如我们把我们的mysql部署在StatefullSet里面,那它的名称是不是就不会变啊,那我可以通过它的名称,去固定到我们对应的Pod上面,因为我之前提到过,K8S内部是一个扁平化的网络,那容器之间是能够互相访问的,所以这样我php-fpm里面写它的地址信息是没有问题的,那对于这三台squid来说,外网用户要访问对吧,我可以创建个service,已知绑定squid,通过去判断我们的squid的一些标签进行确定,那这样的话我们只需要把service暴露到外部即可,service其中有一种暴露模式叫NodePort,比如还可以通过我们的Ingrees等一些方案都可以做到,那这样的话我们就可以把这个架构完整的部署在我们的K8S集群中了,这也是我们的Pod与Pod的通讯方案,可以这么去做。


网络通讯模式

网络通讯模式简介

  Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中,这在GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes假定这个网络已经存在。而在私有云里搭建Kubernetes集群,就不能假定这个网络已经存在。我们需要自己实现这个网络假设,将不同节点的Docker容器之间的互相访问先打通,然后运行Kubernetes。

网络通讯模式

同一个Pod内的多个容器之间:localhost

各Pod之间的通讯:Overlay Network

Pod与Service之间的通讯:各节点的IP tables格则

网络解决方案Kubernetes + Flannel

  Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。而且它还能再这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内。

  上述的图中,分别是以IP为192.168.66.11/24和192.168.66.12/24的两台主机,两台主机各运行了两个Pod,首先在我们真实的主机上会去安装一个Flanneld的守护进程,这个进程会去监听一个端口,这个端口就是用于后期转发数据包以及接收数据包的服务端口,一旦Flanneld这个进程启动以后,它会开启一个监听端口,Flannel0,这个Flannel0专门去收集Docker0转发出来的数据报文,Docker0会分配自己的IP到对应的Pod上。

  如果是同一台主机不同的Pod访问的话,它走的是Docker0的网桥,因为Pod在同一个网桥下,不同的子IP,也就是在真实的服务器中已经完成了一次数据包转换。

  如何通过跨主机还可以通过对方的IP到达呢?首先假设Web app2想把数据包发送到Backend,它的数据包要写自己源地址10.1.15.2/24,目标要写10.1.20.3/24,因为目标网段跟我不是同一个网段,所以10.1.15.2.24发送到自己的网段Docker0,Docker0会有自己对应的钩子函数,会把数据包抓到Flannel0里面,Flannel0里面会有一堆的路由表记录,是从ETCD数据库里面获取到的,写入到我们当前的路由表里,判断到底路由到哪台机器,因为Flannel0是Flanneld的一个进程,所以数据包会转发到Flanneld,到Flanneld它会对数据报文进行封装,下面解释如何封装:

  Mac部分到我们的三层,三层写的是源是192.168.66.11,目标写的是192.168.66.12,下一层封装的是udp的数据报文,也就意味着Flanneld它使用的是udp的数据报文,去转发这些数据包的,因为更快,毕竟在同一个局域网内部,在下一层又封装了一层新的三层信息,源是10.1.15.2,目标是10.1.20.3,封装到这一层以后,外面封装了一个数据包实体Payload,然后转发到192.168.66.12/24。

  所以数据包是肯定能够到192.168.66.12/24的,并且它的目标端口是Flanneld的端口,所以这个数据包会被Flanneld所截获,截获以后它会拆封,它知道这个是干嘛的,会进行拆封,拆封完以后会被转发到Flannel0,Flannel0会转发到docker0,docker0自然而然会转发到Backend,并且数据包是经过二次解封的,第一层的信息,docker0是看不见的,docker0看的是第二层信息,所以docker0一看源是10.1.15.2/24的机器发过来的,目标找的是我自己下面的机器地址,这样的话就可以实现跨主机的扁平化网络。

Etcd在这里的作用,为Flannel提供说明:

存储管理Flannel可分配的IP地址段资源。
监控Etcd中每个Pod的实际地址,并在内存中建立维护Pod节点路由表。

网络通讯方式总结

同一个Pod内部通讯

同一个Pod共享同一个网络命名空间,共享同一个Linux协议栈。
Pod1和Pod2通讯,在同一台机器
Pod1与Pod2不在同一台主机,Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同的node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在的node的IP关联起来,通过这个关联让Pod可以互相访问。
Pod1和Pod2通讯,不再同一台机器
Pod1与Pod2在同一台机器,由docker0网桥直接转发请求至Pod2,不需要经过Flannel。
Pod至Service的网络
目前基于性能考虑,全部为IP tables(现在版本都是通过LVS)维护和转发
Pod到外网
Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主网卡完成路由选择后,IP tables执行Masquerade,把源IP更改为宿主网卡的IP,然后向外网服务器发送请求。
外网访问Pod
外网访问Pod通过Service。

组件通讯示意图

在我们K8S里面,它有三层网络:

    节点网络
    Pod网络
    Service网络

  需要注意的是真实的物理的网络只有一个就是节点网络,也就意味着我们在构建服务器的时候,只需要一张网卡就可以去实现,并不是说多张网卡不行,一张网卡就可以实现,Pod网络是一个虚拟网络,Service网络也是一个虚拟网络,这里可以理解为是一个内部网络,所有的Pod都会在这个扁平化的网络中进行通讯,如果想访问Service的话,那就需要在Service的网络中去访问,Service在去跟后端的Pod通过对应的IP tables或者说通过LVS的转换去达到,最新版会用到LVS转换,这样效率会更高。

Pod概述的相关教程结束。

《Pod概述.doc》

下载本文的Word格式文档,以方便收藏与打印。