Kubernetes Interview Questions
k8s是什么?请说出你的了解?
Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统。主要功能是生产环境中的容器编排。
K8S是Google公司推出的,它来源于由Google公司内部使用了15年的Borg系统,集结了Borg的精华。
K8s架构
和大多数分布式系统一样,K8S集群至少需要一个主节点(Master)和多个计算节点(Node)。
- 主节点主要用于暴露API,调度部署和节点的管理;
- 计算节点运行一个容器运行环境,一般是docker环境(类似docker环境的还有rkt),同时运行一个K8s的代理(kubelet)用于和master通信。计算节点也会运行一些额外的组件,像记录日志,节点监控,服务发现等等。计算节点是k8s集群中真正工作的节点。
Master节点(默认不参加实际工作):
- Kubectl:客户端命令行工具,作为整个K8s集群的操作入口;
- Api Server:在K8s架构中承担的是“桥梁”的角色,作为资源操作的唯一入口,它提供了认证、授权、访问控制、API注册和发现等机制。客户端与k8s群集及K8s内部组件的通信,都要通过Api Server这个组件;
- Controller-manager:负责维护群集的状态,比如故障检测、自动扩展、滚动更新等;
- Scheduler:负责资源的调度,按照预定的调度策略将pod调度到相应的node节点上;
- Etcd:担任数据中心的角色,保存了整个群集的状态;
Node节点:
- Kubelet:负责维护容器的生命周期,同时也负责Volume和网络的管理,一般运行在所有的节点,是Node节点的代理,当Scheduler确定某个node上运行pod之后,会将pod的具体信息(image,volume)等发送给该节点的kubelet,kubelet根据这些信息创建和运行容器,并向master返回运行状态。(自动修复功能:如果某个节点中的容器宕机,它会尝试重启该容器,若重启无效,则会将该pod杀死,然后重新创建一个容器);
- Kube-proxy:Service在逻辑上代表了后端的多个pod。负责为Service提供cluster内部的服务发现和负载均衡(外界通过Service访问pod提供的服务时,Service接收到的请求后就是通过kube-proxy来转发到pod上的);
- container-runtime:是负责管理运行容器的软件,比如docker
- Pod:是k8s集群里面最小的单位。每个pod里边可以运行一个或多个container(容器),如果一个pod中有两个container,那么container的USR(用户)、MNT(挂载点)、PID(进程号)是相互隔离的,UTS(主机名和域名)、IPC(消息队列)、NET(网络栈)是相互共享的。我比较喜欢把pod来当做豌豆夹,而豌豆就是pod中的container;
容器和主机部署应用的区别是什么
容器的中心思想就是秒级启动;一次封装、到处运行;这是主机部署应用无法达到的效果,但同时也更应该注重容器的数据持久化问题。
另外,容器部署可以将各个服务进行隔离,互不影响,这也是容器的另一个核心概念。
kubenetes针对pod资源对象的健康监测机制
K8s中对于pod资源对象的健康状态检测,提供了三类probe(探针)来执行对pod的健康监测:
livenessProbe探针
可以根据用户自定义规则来判定pod是否健康,如果livenessProbe探针探测到容器不健康,则kubelet会根据其重启策略来决定是否重启,如果一个容器不包含livenessProbe探针,则kubelet会认为容器的livenessProbe探针的返回值永远成功。
ReadinessProbe探针
同样是可以根据用户自定义规则来判断pod是否健康,如果探测失败,控制器会将此pod从对应service的endpoint列表中移除,从此不再将任何请求调度到此Pod上,直到下次探测成功。
startupProbe探针
启动检查机制,应用一些启动缓慢的业务,避免业务长时间启动而被上面两类探针kill掉,这个问题也可以换另一种方式解决,就是定义上面两类探针机制时,初始化时间定义的长一些即可。
每种探测方法能支持以下几个相同的检查参数,用于设置控制检查时间:
- initialDelaySeconds:初始第一次探测间隔,用于应用启动的时间,防止应用还没启动而健康检查失败
- periodSeconds:检查间隔,多久执行probe检查,默认为10s;
- timeoutSeconds:检查超时时长,探测应用timeout后为失败;
- successThreshold:成功探测阈值,表示探测多少次为健康正常,默认探测1次。
如何控制滚动更新过程?
可以通过下面的命令查看到更新时可以控制的参数:
1 | [root@master yaml]# kubectl explain deploy.spec.strategy.rollingUpdate |
maxSurge: 此参数控制滚动更新过程,副本总数超过预期pod数量的上限。可以是百分比,也可以是具体的值。默认为1。
(上述参数的作用就是在更新过程中,值若为3,那么不管三七二一,先运行三个pod,用于替换旧的pod,以此类推)
maxUnavailable: 此参数控制滚动更新过程中,不可用的Pod的数量。
(这个值和上面的值没有任何关系,举个例子:我有十个pod,但是在更新的过程中,我允许这十个pod中最多有三个不可用,那么就将这个参数的值设置为3,在更新的过程中,只要不可用的pod数量小于或等于3,那么更新过程就不会停止)。
image的状态有哪些?
- Running:Pod所需的容器已经被成功调度到某个节点,且已经成功运行,
- Pending:APIserver创建了pod资源对象,并且已经存入etcd中,但它尚未被调度完成或者仍然处于仓库中下载镜像的过程
- Unknown:APIserver无法正常获取到pod对象的状态,通常是其无法与所在工作节点的kubelet通信所致。
pod的重启策略
可以通过命令`kubectl explain pod.spec查看pod的重启策略。(restartPolicy字段)
- Always:但凡pod对象终止就重启,此为默认策略。
- OnFailure:仅在pod对象出现错误时才重启
Service这种资源对象的作用是什么?
用来给相同的多个pod对象提供一个固定的统一访问接口,常用于服务发现和服务访问。
标签与标签选择器的作用是什么?
标签:是当相同类型的资源对象越来越多的时候,为了更好的管理,可以按照标签将其分为一个组,为的是提升资源对象的管理效率。
标签选择器:就是标签的查询过滤条件。目前API支持两种标签选择器:
- 基于等值关系的,如:“=”、“”“==”、“!=”(注:“==”也是等于的意思,yaml文件中的matchLabels字段);
- 基于集合的,如:in、notin、exists(yaml文件中的matchExpressions字段);
注:in:在这个集合中;notin:不在这个集合中;exists:要么全在(exists)这个集合中,要么都不在(notexists);
使用标签选择器的操作逻辑:
- 在使用基于集合的标签选择器同时指定多个选择器之间的逻辑关系为“与”操作(比如:- {key: name,operator: In,values: [zhangsan,lisi]} ,那么只要拥有这两个值的资源,都会被选中);
- 使用空值的标签选择器,意味着每个资源对象都被选中(如:标签选择器的键是“A”,两个资源对象同时拥有A这个键,但是值不一样,这种情况下,如果使用空值的标签选择器,那么将同时选中这两个资源对象)
- 空的标签选择器(注意不是上面说的空值,而是空的,都没有定义键的名称),将无法选择出任何资源;
在基于集合的选择器中,使用“In”或者“Notin”操作时,其values可以为空,但是如果为空,这个标签选择器,就没有任何意义了。
pod的生命周期有哪些状态?
- Pending:表示pod已经被同意创建,正在等待kube-scheduler选择合适的节点创建,一般是在准备镜像;
- Running:表示pod中所有的容器已经被创建,并且至少有一个容器正在运行或者是正在启动或者是正在重启;
- Succeeded:表示所有容器已经成功终止,并且不会再启动;
- Failed:表示pod中所有容器都是非0(不正常)状态退出;
- Unknown:表示无法读取Pod状态,通常是kube-controller-manager无法与Pod通信。
创建一个pod的流程
情况一 使用kubectl run命令创建的pod:
- 客户端提交Pod的配置信息(可以是yaml文件定义好的信息)到kube-apiserver;
- Apiserver收到指令后,通知给controller-manager创建一个资源对象;
- Controller-manager通过api-server将pod的配置信息存储到ETCD数据中心中;
- Kube-scheduler检测到pod信息会开始调度预选,会先过滤掉不符合Pod资源配置要求的节点,然后开始调度调优,主要是挑选出更适合运行pod的节点,然后将pod的资源配置单发送到node节点上的kubelet组件上。
- Kubelet根据scheduler发来的资源配置单运行pod,运行成功后,将pod的运行信息返回给scheduler,scheduler将返回的pod运行状况的信息存储到etcd数据中心。
情况二 使用deployment来创建pod:
1、首先,用户使用kubectl create命令或者kubectl apply命令提交了要创建一个deployment资源请求;
2、api-server收到创建资源的请求后,会对客户端操作进行身份认证,在客户端的~/.kube文件夹下,已经设置好了相关的用户认证信息,这样api-server会知道我是哪个用户,并对此用户进行鉴权,当api-server确定客户端的请求合法后,就会接受本次操作,并把相关的信息保存到etcd中,然后返回确认信息给客户端。
3、apiserver开始反馈etcd中过程创建的对象的变化,其他组件使用watch机制跟踪apiserver上的变动。
4、controller-manager组件会监听api-server的信息,controller-manager是有多个类型的,比如Deployment Controller, 它的作用就是负责监听Deployment,此时Deployment Controller发现有新的deployment要创建,那么它就会去创建一个ReplicaSet,一个ReplicaSet的产生,又被另一个叫做ReplicaSet Controller监听到了,紧接着它就会去分析ReplicaSet的语义,它了解到是要依照ReplicaSet的template去创建Pod, 它一看这个Pod并不存在,那么就新建此Pod,当Pod刚被创建时,它的nodeName属性值为空,代表着此Pod未被调度。
5、调度器Scheduler组件开始介入工作,Scheduler也是通过watch机制跟踪apiserver上的变动,发现有未调度的Pod,则根据内部算法、节点资源情况,pod定义的亲和性反亲和性等等,调度器会综合的选出一批候选节点,在候选节点中选择一个最优的节点,然后将pod绑定该该节点,将信息反馈给api-server。
6、kubelet组件布署于Node之上,它也是通过watch机制跟踪apiserver上的变动,监听到有一个Pod应该要被调度到自身所在Node上来,kubelet首先判断本地是否在此Pod,如果不存在,则会进入创建Pod流程,创建Pod有分为几种情况,第一种是容器不需要挂载外部存储,则相当于直接docker run把容器启动,但不会直接挂载docker网络,而是通过CNI调用网络插件配置容器网络,如果需要挂载外部存储,则还要调用CSI来挂载存储。kubelet创建完pod,将信息反馈给api-server,api-servier将pod信息写入etcd。
7、Pod建立成功后,ReplicaSet Controller会对其持续进行关注,如果Pod因意外或被我们手动退出,ReplicaSet Controller会知道,并创建新的Pod,以保持replicas数量期望值。
pod的终止过程
1、用户向apiserver发送删除pod对象的命令;
2、apiserver中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead;
3、将pod标记为terminating状态;
4、kubectl在监控到pod对象为terminating状态了就会启动pod关闭过程;
5、endpoint控制器监控到pod对象的关闭行为时将其从所有匹配到此endpoint的server资源endpoint列表中删除;
6、如果当前pod对象定义了preStop钩子处理器,则在其被标记为terminating后会意同步的方式启动执行;
7、pod对象中的容器进程收到停止信息;
8、宽限期结束后,若pod中还存在运行的进程,那么pod对象会收到立即终止的信息;
9、kubelet请求apiserver将此pod资源的宽限期设置为0从而完成删除操作,此时pod对用户已不可见
删除一个Pod会发生什么事情?
Kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态,此时Pod的状态Terminating,kubelet看到pod标记为Terminating就开始了关闭Pod的工作;
关闭流程如下:
- pod从service的endpoint列表中被移除;
- 如果该pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅的结束进程;
- 进程被发送TERM信号(kill -14)
- 当超过优雅退出的时间后,Pod中的所有进程都会被发送SIGKILL信号(kill -9)。
pod一致处于pending状态排查?
一个pod一开始创建的时候,它本身就是会处于pending状态,这时可能是正在拉取镜像,正在创建容器的过程。
如果等了一会发现pod一直处于pending状态,
那么我们可以使用kubectl describe命令查看一下pod的Events详细信息。一般可能会有这么几种情况导致pod一直处于pending状态:
1、调度器调度失败。
Scheduer调度器无法为pod分配一个合适的node节点。
而这又会有很多种情况,比如,node节点处在cpu、内存压力,导致无节点可调度;pod定义了资源请求,没有node节点满足资源请求;node节点上有污点而pod没有定义容忍;pod中定义了亲和性或反亲和性而没有节点满足这些亲和性或反亲和性;以上是调度器调度失败的几种情况。
2、pvc、pv无法动态创建。
如果因为pvc或pv无法动态创建,那么pod也会一直处于pending状态,比如要使用StatefulSet 创建redis集群,因为粗心大意,定义的storageClassName名称写错了,那么会造成无法创建pvc,这种情况pod也会一直处于pending状态,或者,即使pvc是正常创建了,但是由于某些异常原因导致动态供应存储无法正常创建pv,那么这种情况pod也会一直处于pending状态。
service的类型
通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。service的类型一般有4种,分别是:
ClusterIP:表示service仅供集群内部使用,默认值就是ClusterIP类型,虚拟的服务IP地址,该地址用于Kubernetes集群内部的Pod访问,在Node上kube-proxy通过设置的iptables规则进行转发;
NodePort:表示service可以对外访问应用,会在每个节点上暴露一个端口,这样外部浏览器访问地址为:任意节点的IP:NodePort就能连上service了。使用宿主机的端口,使能够访问各Node的外部客户端通过Node的IP地址和端口号就能访问服务;
LoadBalancer:表示service对外访问应用,这种类型的service是公有云环境下的service,此模式需要外部云厂商的支持,需要有一个公网IP地址。使用外接负载均衡器完成到服务的负载分发,需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址,通常用于公有云。
ExternalName:这种类型的service会把集群外部的服务引入集群内部,这样集群内直接访问service就可以间接的使用集群外部服务了
一般情况下,service都是ClusterIP类型的,通过ingress接入的外部流量。
简述Kubernetes Service分发后端的策略
Service负载分发的策略有:RoundRobin和SessionAffinity
- RoundRobin:默认为轮询模式,即轮询将请求转发到后端的各个Pod上。
- SessionAffinity:基于客户端IP地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。
弹性伸缩
- Replication Controller:Replication Controller用来管理Pod的副本,保证集群中存在指定数量的Pod副本。集群中副本的数量大于指定数量,则会停止指定数量之外的多余容器数量。反之,则会启动少于指定数量个数的容器,保证数量不变。Replication Controller是实现弹性伸缩、动态扩容和滚动升级的核心。
Pod到Service的通信?
1)k8s在创建服务时为服务分配一个虚拟IP,客户端通过该IP访问服务,服务则负责将请求转发到后端Pod上;
2)Service是通过kube-proxy服务进程实现,该进程在每个Node上均运行可以看作一个透明代理兼负载均衡器;
3)对每个TCP类型Service,kube-proxy都会在本地Node上建立一个SocketServer来负责接受请求,然后均匀发送到后端Pod默认采用Round Robin负载均衡算法;
4)Service的Cluster IP与NodePort等概念是kube-proxy通过Iptables的NAT转换实现,kube-proxy进程动态创建与Service相关的Iptables规则;
5)kube-proxy通过查询和监听API Server中Service与Endpoints的变化来实现其主要功能,包括为新创建的Service打开一个本地代理对象,接收请求针对针对发生变化的Service列表,kube-proxy会逐个处理;
一个应用pod是如何发现service的,或者说,pod里面的容器用于是如何连接service的?
答:有两种方式,一种是通过环境变量,另一种是通过service的dns域名方式。
1、环境变量:
当pod被创建之后,k8s系统会自动为容器注入集群内有效的service名称和端口号等信息为环境变量的形式,
这样容器应用直接通过取环境变量值就能访问service了,
如curl http://${WEBAPP_SERVICE_HOST}:{WEBAPP_SERVICE_PORT}
2、DNS方式:
使用dns域名解析的前提是k8s集群内有DNS域名解析服务器,
默认k8s中会有一个CoreDNS作为k8s集群的默认DNS服务器提供域名解析服务器;
service的DNS域名表示格式为<servicename>.<namespace>.svc.<clusterdomain>
,
servicename是service的名称,namespace是service所处的命名空间,clusterdomain是k8s集群设置的域名后缀,一般默认为 cluster.local ,
这样容器应用直接通过service域名就能访问service了,
如wget http://svc-deployment-nginx.default.svc.cluster.local:80
,
另外,service的port端口如果定义了名称,那么port也可以通过DNS进行解析,
格式为:_<portname>._<protocol>.<servicename>.<namespace>.svc.<clusterdomain>
如何创建一个service代理外部的服务,或者换句话来说,在k8s集群内的应用如何访问外部的服务,如数据库服务,缓存服务等?
答:可以通过创建一个没有标签选择器的service来代理集群外部的服务。
1、创建service时不指定selector标签选择器,但需要指定service的port端口、端口的name、端口协议等,这样创建出来的service因为没有指定标签选择器就不会自动创建endpoint;
2、手动创建一个与service同名的endpoint,endpoint中定义外部服务的IP和端口,endpoint的名称一定要与service的名称一样,端口协议也要一样,端口的name也要与service的端口的name一样,不然endpoint不能与service进行关联。
完成以上两步,k8s会自动将service和同名的endpoint进行关联,
这样,k8s集群内的应用服务直接访问这个service就可以相当于访问外部的服务了。
简述Kubernetes自动扩容机制
Kubernetes使用Horizontal Pod Autoscaler(HPA)的控制器实现基于CPU使用率进行自动Pod扩缩容的功能。HPA控制器周期性地监测目标Pod的资源性能指标,并与HPA资源对象中的扩缩容条件进行对比,在满足条件时对Pod副本数量进行调整。
Kubernetes中的某个Metrics Server(Heapster或自定义Metrics Server)持续采集所有Pod副本的指标数据。HPA控制器通过Metrics Server的API(Heapster的API或聚合API)获取这些数据,基于用户定义的扩缩容规则进行计算,得到目标Pod副本数量。
当目标Pod副本数量与当前副本数量不同时,HPA控制器就向Pod的副本控制器(Deployment、RC或ReplicaSet)发起scale操作,调整Pod的副本数量,完成扩缩容操作。
标签和nodeSelector
标签(Label)是附加到 Kubernetes 对象上的键值对,如果用 json 表示附加到 metadata 的 label:
1 | "metadata": { |
yaml:
1 | metadata: |
标签主要是用于表示对用户有意义的对象的属性标识。
可以给节点设定一些 Label,例如在 kube-system 命名空间中,运行着 Kubernetes 的核心组件,我们可以查看此命名空间中所有组件的 Label。
1 | kubectl get nodes --namespace=kube-system --show-labels |
1 | beta.kubernetes.io/arch=amd64, |
我们也可以手动给一个 Node 添加标签。
1 | kubectl label nodes <node-name> <label-key>=<label-value> |
例如我们给节点设置一个 disksize
,表示节点的硬盘是否够大。
1 | kubectl label nginx disksize=big |
然后我们在编写 yaml 文件时,希望这个 pod 在容量大的 Node 上运行,可以这样写:
1 | nodeSelector: |
顺便聊一下官方的一个例子,设置 Node 的 Label,表示硬盘是 ssd。
1 | kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd |
在 yaml 文件的节点选择器中,添加选择。
1 | spec: |
Label 可以在多个地方使用,例如在 Node 上添加 Label,标识此 Node;而在 NodeSelector 里使用,可以选择合适的 Node 运行 Pod;在 metadata
中使用,可以对元数据加以描述。
在 metadata 中添加的 Label,可以在命令查询时做筛选。
查询 pod 的 Label:
1 | kubectl get pods --show-labels |
查找符合条件的 pod(参考 LABELS 字段,可以根据里面的标签选择):
1 | kubectl get pods -l app=nginx |
标签选择
在前面,我们学习了 nodeSelector ,可以帮助我们选择合适的 Node 运行 Pod,实际上 Kubernets 的标签选择是丰富多样的,例如:
1 | nodeSelector: |
则表示节点选择器是等值选择,表达式是 disktype=ssd && disksize=big
。
标签选择有等值和集合两种,其中等值选择有 =
、==
、!=
三种,=
和 ==
无区别。在多个需求(多个label)的情况下,相对于使用 &&
运算符,但是选择器不存在 ||
这种逻辑或运算符。
yaml 只支持 {key}:{value}
这种形式,而我们使用命令形式时,则可使用以上三种运算符。
1 | kubectl get nodes -l disktype=ssd,disksize!=big |
对于集合选择方式,支持三种操作符:in
、notin
和 exists
。不过别理解成是从集合中选择,下面举个例子。
假如有三个 Node,其 disksize 有 big、medium、small,我们要部署一个 pod,在 big、medium 中都可以运行,则:
1 | ... -l disksize in (big,medium) |
1 | ... -l disksize notin (small) |
而 exists 则跟 !=
类似,但是 exists 表示只要存在这个 label 即可,而不论其设置了是什么值。
1 | -l disksize |
我们也可以使用 ''
把选择表达式包起来。
1 | kubectl get pods -l 'app=nginx' |
前面已经提到了 yaml 的 nodeSelector 和 命令式的选择,这里我们介绍 yaml 的 selector。
前面我们提到在 Deployment 的 metadata 中加上 Label,即 pod 加上 Label,我们也在 kubectl get pods
中使用 Label 选择过滤 pod。同样,当我们创建 Service 或者使用 ReplicationController 时,也可以使用标签选择合适的 pod。
假如我们已经部署了 nginx,那么查询 kubectl get pods --show-labels
时,其 pod 的 LABELS 会有 app=nginx
,那么我们可以这样选择:
1 | selector: |
完整版本:
1 | apiVersion: v1 |
selector 还支持以下选择方式 matchLabels
、matchExpressions
:
matchLabels
是由 {key,value}
对组成的映射。 matchLabels
映射中的单个 {key,value }
等同于 matchExpressions
的元素, 其 key
字段为 “key”,operator
为 “In”,而 values
数组仅包含 “value”。
matchExpressions
是 Pod 选择算符需求的列表。 有效的运算符包括 In
、NotIn
、Exists
和 DoesNotExist
。 在 In
和 NotIn
的情况下,设置的值必须是非空的。 来自 matchLabels
和 matchExpressions
的所有要求都按逻辑与的关系组合到一起 – 它们必须都满足才能匹配。
示例如下:
1 | selector: |
这里就不在详细说这些选择规则了,前面提到的已经够用了,读者可以查阅官方文档学习更多复杂的操作:标签和选择算符 | Kubernetes
亲和性和反亲和性
前面我们学习了 nodeSelector
,使用 nodeSelector
选择合适的 Label,可以表达我们约束的类型。
亲和性则类似于 nodeSelector,可以根据节点上的标签约束 pod 可以调度到哪些节点。
pod 亲和性有两种别为:
requiredDuringSchedulingIgnoredDuringExecution
硬需求,将 pod 调度到一个节点必须满足的规则。
preferredDuringSchedulingIgnoredDuringExecution
。尝试执行但是不能保证偏好。
这是官方的一个例子:
1 | apiVersion: v1 |
亲和性的约束相对于:
1 | ... ... -l kubernetes.io/e2e-az-name in (e2e-az1,e2e-az2) |
affinity 设置亲密关系,nodeAffinity 设置节点亲密关系,最后才到 亲和性,它们表示必须满足和尽量满足。
如果我们设置了多个 nodeSelectorTerms :
1 | requiredDuringSchedulingIgnoredDuringExecution: |
则只需要满足其中一种即可调度 pod 到 node 上。
如果你同时指定了 nodeSelector
和 nodeAffinity
,两者必须都要满足, 才能将 Pod 调度到候选节点上。
节点亲和性语法支持下面的操作符: In
,NotIn
,Exists
,DoesNotExist
,Gt
,Lt
。
Pod 亲和性与反亲和性的合法操作符有 In
,NotIn
,Exists
,DoesNotExist
。
通过 -Affinity
可以设置亲和性,例如节点亲和性 nodeAffinity
,而且设置反亲和性使用 -AntiAffinity
,例如 nodeAntiAffinity
。
反亲和性跟亲和性一样,都有 requiredDuringSchedulingIgnoredDuringExecution
硬限制和 preferredDuringSchedulingIgnoredDuringExecution
软限制,只是反亲和性是相反的表示,如果符合条件则不能调度。
关于亲和性和反亲和性的说明就到这里,着两者的配置比较多和复杂,读者可以参考官方文档,这里不在赘述。
污点和容忍度
前面提到亲和性和反亲和性,我们加以通过 pod 选择合适的 node,或者 service 选择合适的 pod,这些拥有 Label 的对象都是被选择的。
这里,我们介绍污点和容忍度,它们可以排斥 “被选择” 的命运。
节点污点(taint) 可以排斥一类特定的 pod,而 容忍度(Tolerations)则表示能够容忍这个对象的污点。
当节点添加一个污点后,除非 pod 声明能够容忍这个污点,否则 pod 不会被调度到这个 节点上。
系统会 尽量 避免将 Pod 调度到存在其不能容忍污点的节点上, 但这不是强制的。Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。
但是如果你只有一个 worker,那么设置了污点,那 pod 也只能选择在这个节点上运行。
添加污点格式:
1 | kubectl taint node [node] key=value:[effect] |
更新污点或覆盖:
1 | kubectl taint node [node] key=value:[effect] --overwrite=true |
使用 kubectl taint
给节点增加一个污点。
1 | kubectl taint nodes node1 key1=value1:NoSchedule |
移除污点:
1 | kubectl taint nodes node1 key1=value1:NoSchedule- |
其中,污点需要设置 label ,并设置这个 label 的效果为 NoSchedule。
污点的效果称为 effect ,节点的污点可以设置为以下三种效果:
NoSchedule
:不能容忍此污点的 Pod 不会被调度到节点上;不会影响已存在的 pod。PreferNoSchedule
:Kubernetes 会避免将不能容忍此污点的 Pod 安排到节点上。NoExecute
:如果 Pod 已在节点上运行,则会将该 Pod 从节点中逐出;如果尚未在节点上运行,则不会将其安排到节点上。
但是某些系统创建的 Pod 可以容忍所有 NoExecute
和 NoSchedule
污点,因此不会被逐出,例如 master 节点是不能被部署 pod 的,但是 kube-system
命名空间却有很多系统 pod。当然通过修改污点,可以让 户 pod 部署到 master 节点中。
查询节点的污点:
1 | kubectl describe nodes | grep Taints |
1 | Taints: node-role.kubernetes.io/master:NoSchedule |
系统默认污点
我们去除 master 的污点:
1 | kubectl taint node instance-1 node-role.kubernetes.io/master:NoSchedule- |
然后部署 nginx pod。
1 | kubectl create deployment nginxtaint --image=nginx:latest --replicas=3 |
查看 pod:
1 | kubectl get pods -o wide |
结果笔者查到三个副本都在 master 节点上。
为了保证集群安全,我们需要恢复 master 的污点。
1 | kubectl taint node instance-1 node-role.kubernetes.io/master:NoSchedule |
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready
:节点未准备好。这相当于节点状态Ready
的值为 “False
“。node.kubernetes.io/unreachable
:节点控制器访问不到节点. 这相当于节点状态Ready
的值为 “Unknown
“。node.kubernetes.io/out-of-disk
:节点磁盘耗尽。node.kubernetes.io/memory-pressure
:节点存在内存压力。node.kubernetes.io/disk-pressure
:节点存在磁盘压力。node.kubernetes.io/network-unavailable
:节点网络不可用。node.kubernetes.io/unschedulable
: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized
:如果 kubelet 启动时指定了一个 “外部” 云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
容忍度
一个 node 可以设置污点,排斥 pod,但是 pod 也可以设置 容忍度,容忍 node 的污点。
1 | tolerations: |
也可以设置 value。
1 | tolerations: |
operator
的默认值是 Equal
。
一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果,并且:
如果
operator
是Exists
此时容忍度不能指定
value
,如果存在 key 为 key1 的 label,且污点效果为NoSchedule
,则容忍。如果
operator
是Equal
,则它们的value
应该相等
如果 effect
留空,则表示只要是 label 为 key1
的节点,都可以容忍。
如果:
1 | tolerations: |
则表示此 pod 能够容忍任意的污点,无论 node 怎么设置 key
、value
、effect
,此 pod 都不会介意。
如果要在 master 上也能部署 pod,则可以修改 pod 的容忍度:
1 | spec: |
DaemonSet资源对象的特性
DaemonSet这种资源对象会在每个k8s集群中的节点上运行,并且每个节点只能运行一个pod,这是它和deployment资源对象的最大也是唯一的区别。
所以,在其yaml文件中,不支持定义replicas,
除此之外,与Deployment、RS等资源对象的写法相同,
DaemonSet一般使用的场景有
- 在去做每个节点的日志收集工作;
- 监控每个节点的的运行状态;
DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
pod的共享资源?
1)PID 命名空间:Pod 中的不同应用程序可以看到其他应用程序的进程 ID;
2)网络命名空间:Pod 中的多个容器能够访问同一个IP和端口范围;
3)IPC 命名空间:Pod 中的多个容器能够使用 SystemV IPC 或 POSIX 消息队列进行通信;
4)UTS 命名空间:Pod 中的多个容器共享一个主机名;
5)Volumes(共享存储卷):Pod 中的各个容器可以访问在 Pod 级别定义的 Volumes;
Kubernetes网络模型
kubernetes网络由于它集群的特性,面对不同的服务器,不同的pod,那么其网络流程就可以分出不同的场景。
同一个pod的不同容器之间的通信
在kubernetes中每一个pod都会有一个根容器,这个根容器里面就会包含ip、端口等网络信息,每个pod都会有一个单独的根容器,你在后面加的容器都只会添加,不会改变pod的网络内容。官方内容是指在同一个pod当中的不同容器是共享网络命名空间的。所以在同一个pod的不同容器是可以直接通过localhost的方式直接访问的。
同一台机器的不同pod之间的通信
同一机器的pod网络,以下图为例,pod当中含有container1,container2两个容器和一个pause容器(也就是根容器)。图中有三个网卡设备,eth0是kubernetes集群主机的网卡设备,这个作为服务器之间通信的基本条件一般都会有。docker0是一个虚拟网桥,可以简单理解为一个虚拟交换机,它是支持该节点上的Pod之间进行IP寻址和互通的设备。veth0则是Pod1的虚拟网卡,是支持该Pod内容器互通和对外访问的虚拟设备。docker0网桥和veth0网卡,都是linux支持和创建的虚拟网络设备。Pod的IP是由docker0网桥分配的,例如上图docker0网桥的IP是172.17.0.1,它给第一个Pod1分配IP为172.17.0.2。如果该节点上再启一个Pod2,那么相应的分配IP为172.17.0.3,如果再启动Pod可依次类推。因为这些Pods都连在同一个网桥上,在同一个网段内,它们可以进行IP寻址和互通。
不同机器之间不同pod的通信
在不同机器之间网络通信时,docker0网桥就不能跨机器了,这个时候我们需要一个桥梁将两者关联起来,也就是cni网络插件,现在比较流行的是flannel和calico。以flannel为例,flannel会在每一台集群主机之中创建一个flannel0,flannel0给创建的pod分配一个和它同网段的ip,flannel0和主机ip做关联,这时不同主机的pod就可以通过flannel0进行通信。