细说kubernetes - 为什么是pod?
k8s作为现在最火的容器编排调度平台,好用我也就不必多说了。当我们初识k8s的时候一个新的概念就到了我们眼前,那就是pod。我们在使用了之后也就渐渐的接受了pod这个东西,但是你有没有想过,为什么是pod?k8s为什么会有这样的设计?今天我们就来细细说说这个pod
首先我们来回忆看看k8s的架构图是什么样子的
从架构图中我们可以看到,整个k8s的设计架构有以下几个要点:
当然其他组件都非常重要,这个我们以后再说,我们今天就来看看主角“pod”
一开始用的时候我就好奇为什么k8s要弄出一个pod,因为我们一开始使用的是docker,操作的是docker容器,构建的也是docker镜像,为什么不直接调度docker容器就好了,这样粒度不是更加细致,调度也会更加方便吗?
我们在使用k8s之前也使用过docker-compose,从另一个角度说,这也是一种容器的管理,看起来也挺好的。
下面我们就来说说pod
从上面的图你大概可以感受到pod在k8s中其实是一个什么样的角色。
我们如果使用虚拟机,那么上面就会有一系列的服务这些服务可能会有一些依赖,而这样的依赖就好像在服务器中运行的一个个进程组,往往其中也有着相关的依赖,而pod中的容器也是一样的道理,其中也会有类似这样的依赖,为了更好的描述和管理这样的依赖,于是就有了pod。
其实这样的理念往往可以类比出很多这样的设计。
一定会有这样的关系吗?我的感觉是,在现代技术服务的开发的过程中,这样的关系是不可避免的。我下面来举几个例子。
我们知道 java 的 web 应用往往需要部署在tomcat这样的容器之中,在 springboot 还没有出现以前,需要自己启动一个 tomcat 容器,然后将需要部署的应用打包部署到容器中去。
在以前你可能会想着,将 javaweb 打包一个war,然后编写一个 dockerfile 将 war 包cp到 tomcat 中的 webapp 目录中。但是这样带来的就是每次更新发布的时候,镜像会很大,因为每次构建都会有一个基础的tomcat镜像。
而在k8s使用的时候,会有的设计的是,将tomcat作为一个不变的镜像(它也不应该改变)而把 war 包作为另外一个容器,而这样个容器同时挂载同一个目录,将 webapp 挂载的目录与 war包挂载的目录相同来达到目的,然后将他们放到同一个 pod 中。
类似的操作还有: https://kubernetesio/zh/docs/tasks/access-application-cluster/communicate-containers-same-pod-shared-volume/ 里面说的就是前端静态页面和 nginx 的关系。
下面引用官网设计理念中的一句话:“ 比如你运行一个操作系统发行版的软件仓库,一个Nginx容器用来发布软件,另一个容器专门用来从源仓库做同步,这两个容器的镜像不太可能是一个团队开发的,但是他们一块儿工作才能提供一个微服务;这种情况下,不同的团队各自开发构建自己的容器镜像,在部署的时候组合成一个微服务对外提供服务。 ”
在使用 docker 部署项目的时候会遇到一个问题就是日志持久化的问题,因为 docker 容器如果被删除的话,其中的文件也会被删除,那么我们的日志文件同时也会被删除,也就是说我们必须要将日志持久化。
最常见的方式是,将日志存储的目录挂载到宿主机上,这样容器被删除的时候日志不会被删除。
而在k8s中常见的日志处理架构是怎么样的呢?
https://kubernetesio/zh/docs/concepts/cluster-administration/logging/
使用的是 sidecar,这个是什么呢?其实就是一个辅助性质的容器,同时与主容器放在同一个 pod 中,读取主容器挂载出来的日志目录。其实后续还可以做更多的操作,比如日志发送es等等。
总之我们可以看到,在一个pod中的容器关系是非常密切的,他们可以拥有同一个目录,甚至可以拥有同一个网络,可以拥有相互的服务,这样的关系我听过的名词叫做“超亲密关系”。就类似一对夫妻之间的关系了。
因为在现在的多说应用中,已经几乎做不到一个人顶天立地了,总是会有各种各样的依赖,依赖一些组件,依赖一些工具,依赖一些网络服务等等,一个进程组有很多的进程互相帮助来最终实现功能一样。
这样的关系太过常见,于是k8s就将它设计为了pod。
如果你已经对docker的实现比较熟悉,其实pod的实现并不复杂。(如果对docker实现不熟悉可以翻看之前的博客)
其实pod是一个逻辑上的概念,其实pod做的事情很简单:
其实k8s做的就是初始化一个infra的容器(这是一个很小的容器),利用这个容器去抢先占用需要使用的 Namespace ,然后在将用户指定的容器加载进来,同时使用的就是相同的 Namespace 了。(如果有 InitContainer 会优先按顺序初始化它,图上就不做说明了)
这样共享网络应该是没有问题了,那么要共享Volume也很简单。pod 只需要将 Volume 目录挂载到宿主机,让内部的容器挂载这个目录就可以了。
再来说说pod还有哪些功能,这些功能也是k8s为什么设计pod的原因之一
通过Probe:LivenessProbe或者ReadinessProbe,可以探测应用是否处于健康状态,如果不健康做出相关的反应。
这就好比k8s可以定期的帮你监控、维持一整个应用的健康。
其实在我们看来,很多时候服务挂了,需要重启,需要做高可用,那么nginx呢?tomcat呢?也是一样的。所以pod的健康能保证整个服务的全部健康使用。
我们可以通过给Pod增加kubernetesio/ingress-bandwidth和kubernetesio/egress-bandwidth这两个annotation来限制Pod的网络带宽。
为什么我提到了这个功能呢?因为在实际的业务开发过程中经常会使用一些网络插件,这些网络插件在流量的控制上非常有用,有的时候我们会根据网络流量来做一系列的操作,用户的突然增长导致的流量剧增是否要扩容等等而这样的监控和限制对于pod来说无疑会更加方便,而不需要管pod内部的容器的流量。
重启的策略,这个也算是一个功能吧
官网:一个Pod(就像一群鲸鱼,或者一个豌豆夹)。
我觉得 Pod 更证明了一种设计模式“组合”,在有的时候会,组合的合理,就会方便很多东西,比如设计了一堆组件,组合在一起;ps画图的时候方块更圆组合在一起,就可以一起多复制几个组合。
当然我们在认识到为什么要这样设计 Pod 的同时需要意识到,我们应该将什么样的容器组合放置在同一个 Pod 之中才比较合适。遵循一定的“容器设计模式”进行编排,调度的时候才会更加得心应手。
一般情况下kubernets可以通过kube-scheduler默认的调度策略合理的将pod分配到可用的节点上, 但是随着pod数量的增加以及不同pod对资源的使用情况不同我们需要更加合理的分配集群中的资源, 所以对一些pod运行节点的控制是由必要的。
源于硬件和软件层多样性,我们需要将某个 pod 调度到某些特定的节点上,例如指定机房,存储类型,网络类型等等:
有两种方法 nodeSelector 以及 affinity 可以实现对应的需求
K8S中pod的调度都是通过节点label实现的 , 所以对于除必要的节点label外对于其它用途也要做一些规划
设置参数
为 node 设置 taint 与 label:
or
删除taint:
删除node的label
查看 node上的 taint:
查看node1的label
kubernetes中是通过label-selector机制进行节点选择,由scheduler调度策略 MatchNodeSelector 进行label匹配,调度pod到目标节点,该匹配规则是强制约束。
类型包括:
限制方式:
匹配逻辑label
如果nodeAffinity中nodeSelector有多个选项,节点满足任何一个条件即可;如果matchExpressions有多个选项,则节点必须同时满足这些选项才能运行pod 。需要说明的是,node并没有anti-affinity这种东西,因为NotIn和DoesNotExist能提供类似的功能。
这个 pod 同时定义了 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution 两种nodeAffinity。第一个要求 pod 运行在特定 devops 的节点上,第二个希望节点最好有对应的department:model和dedicated:app标签, 根据权重决定了顺序 NodeSelectorTerms 可以有多个,之间是或的关系,满足任意一个既满足, MatchExpressions 也可以有多个,他们之间是且的关系 必须都满足
preferredDuringSchedulingIgnoredDuringExecution 值为列表,根据权重决定顺序 MatchExpressions 值为列表 关系为且,必须都满足
PodAffinit是根据通过已运行在节点上的pod的标签而不是node的标签来决定被调度pod的运行节点,因为pod运行在指定的namespace所以需要自己指定运行pod的namesapce
上面这个例子中的 POD 需要调度到某个指定的主机上,至少有一个节点上运行了这样的 POD:这个 POD 有一个app=busybox-pod的 label。podAntiAffinity则是希望最好不要调度到这样的节点:这个节点上运行了某个 POD,而这个 POD 有app=node-affinity-pod的 label。根据前面两个 POD 的定义,我们可以预见上面这个 POD 应该会被调度到一个busybox-pod被调度的节点上,而node-affinity-pod被调度到了该节点以外的节点
对于nodeAffinity无论是硬策略还是软策略方式,都是调度 POD 到预期节点上,而Taints恰好与之相反,如果一个节点标记为 Taints ,除非 POD 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度pod。
比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 POD,则污点就很有用了,POD 不会再被调度到 taint 标记过的节点。taint 标记节点举例如下:
如果仍然希望某个 POD 调度到 taint 节点上,则必须在 Spec 中做出Toleration定义,才能调度到该节点,举例如下:
effect 共有三个可选项,可按实际需求进行设置:
设置label 和 taint, edgenode为特殊属性的节点所以需要设置taint
资源文件
运维服务部署在固定的几台主机上, 每个主机设置 department 的label, 部署服务时指定该label
设置label
资源文件
k8s的网络通讯方式
k8s的网络模型假定是所有Pod都在一个可以直接联通的扁平的网路空间,我们需要自己实现这个网络假设,将不同节点上的Docker容器之间的互相访问先打通,然后运行K8s。
同一个Pod内的多个容器: 同一个pod共享一个网络命名空间,共享同一个linux协议栈
同一个节点各pod之间的通讯: 通过docker0网桥直接发送数据,不通过flannel
pod与service之间的通讯: 各节点的iptables规则。
pod到外网: pod向外网发送请求,查找路由表转发数据包到宿主机的网卡,宿主机网卡完成路由选择后,iptables执行Masquerade,把源IP更改为著网卡的IP,然后向外网发送请求。
flannel的功能是让集群中的不同节点主机创建的Docker容器具有集群唯一的虚拟IP地址,能够在这些Ip地址之间建立
一个覆盖网络(OverlayNetwork),通过这个覆盖网络,将数据包原封不动传递到目标容器内部。
不同节点的pod之间的访问:
首先,在node服务器上会安装flanneld的守护进程,这个进程会监听一个端口,这个端口是用于接收和转发数据包的端口,flanneld进程启动以后,会开启一个网桥叫flannel0,flannel0会抓取docker0转发的数据包,docker0会分配自己的IP到对应的pod上,如果是同一台主机中的不同pod的话,他们之间的通讯走的是docker0网桥。如果是不同节点之间的pod进行通讯,数据包源地址写自己的ip,目标地址写目标地址的ip,然后将数据发送到docker0网桥也就是网关,此时flannel0有自己钩子函数,将数据从docker0抓取,flannel0是flanneld开启端口,数据到了flanneld之后会将数据报文进行一个封装,然后将数据转发到目标节点,然后到了目标节点之后会将数据进行一次解封,到达目标节点的flanneld,然后数据会进行二次解封,flanneld会通过自己的端口将数据发送到docker0网桥,然后在对数据进行转发,从而发送到相应的目标pod。
这里的etcd与flannel的关系:
存储管理flannel可分配的IP地址段资源
监控etcd中每个pod的实际地址,并在内存中建立维护pod节点路由表。
flannel启动以后会向etcd中插入需要分配的网段以及这些网段分别分配在哪个节点上。
k8s考点灵魂拷问9连击之5
上一期,讨论了前面五个考点,感兴趣去可以 看一看点我进入传送门 ,好了,接下来继续看本期4个考点!
假设给Pod打上的标签是 AA,同时RS标签选择器设置匹配 AA。
分为两种情况
第一种:RS已经创建,裸Pod随后创建
第二种:裸Pod先创建,随后创建RS
结论:无论RS何时创建,一旦创建,会将自己标签选择器能识别到的所有Pod纳入麾下,接管生存权,遵循RS规约定义的有效副本数,去开启平衡机制,维持有效标签Pod的副本数。
总之,RS 尽力保证 系统当前正在运行的 Pod 数 等于期望状态 里指定的 Pod 数目。
如果想要独立创建可生存的裸Pod,一定要检查所有的RS标签选择器的可识别范围,避免自己创建的裸Pod被收纳接管。
如果线上发现有些Pod没 有按照我们期望的状态 来进行运行,发生了某些故障,但是其他同类型Pod却没有发生。
这种故障 一般属于不易复现的故障 ,只会在某些偶然性的条件下触发故障,但是这个触发条件我们又不清楚,所以我们要专门针对这个故障进行问题排查。
这个时候又 不希望在排查过程中影响服务的正常响应 ,那该怎么办呢?
隔离法 ,所谓隔离法,就是将 Pod 从 ReplicaSet 集合中隔离出来,让Pod脱离RS的管控范围,额有点类似赎身。
可以通过改变标签来从 ReplicaSet 的目标集中移除 Pod。
这种技术可以用来从服务中 去除 Pod ,以便进行排错、数据恢复等。
以这种方式移除的 Pod 将被 自动替换 (假设副本的数量没有改变)。
通过隔离这个目标Pod,RS会自动补充副本Pod去保证集群的高可用,我们不必担心影响到服务线的正常响应。这时候就可以针对这个目标Pod做排查,研究,里里外外的想干啥,就干啥,嘿嘿
0条评论