1.核心对象
NameSpaces
当集群有多个用户或一个用户有多个应用需要管理时,需要对被管理的对象进行隔离。不同的对象被划分到不同的namespaces后,可以通过权限控制来限制用户以何种权限访问namespaces的哪些对象,进而构建一个多租户,彼此隔离的通用集群。
namespaes,它提供一种内核级别的隔离方式,系统可以为进程分配不同的namespaces,并保证不同namespace资源独立分配,进程彼此隔离,即不同的namespace下的进程互不干扰。
namespace提供六项隔离:分别是ipc,network,pid,mount,uts,usr,分别为“system v ipc和posix消息队列”,“网络”,“进程”,“挂载点”,“主机名和域名”,“用户和用户组”。
Pod
容器云平台需要解决的最核心的问题就是应用运行,kubernetes将容器化应用运行的实例抽象为pod。它是一个或者多个容器镜像的组合。当应用启动后,容器镜像对应一组进程。并且在同一个pod中的容器共用同一网络标识。pod具有基本的自恢复能力,当每个副本出现问题时,它会按照预定策略重启。
pod分配原则是选择最佳节点运行。每个计算节点汇报自己的心跳信息,并上报节点的资源总量和可用资源。
pod分为静态pod和普通pod
静态pod:静态pod不经过apiserver,kubelet通过观测本地目录或者http url下的定义文本文件所创建的pod。静态pod始终绑定到kubelet所在节点上。比如我们通过kubelet绑定了静态pod目录,那么kubelet观测到该目录下的yaml文件会自动创建pod,删除的方法也就删除该yaml文件。
普通pod:通过api server创建的pod,是被scheduler调度到该节点上的。
ServiceAccount
pod运行中需要与kubernetes api通信,在启用了安全配置的集群后,pod一定要以某种身份与kubernetes通信,这个身份就是系统用户(serviceaccount),k8s会自动为每个namespaces创建一个default serviceaccount 并且为每个service account分配一个jwt token,这个token会存储在secret中。用户可以在pod定义中指定service account,其对应的token会被挂载在pod中,pod进程可以通过该token与k8s进行通信。
ReplicaSet
保证总是按照用户期望的数量保证pod正常运行,当某个副本宕机后,控制器建立一个新的副本。当业务发生变化后需要调整扩容缩容,可以方便调整副本数量。
Deployment
如何在更新的时候业务不中断,一直是需要解决的问题。
deployment就是描述发布过程的对象,其实现的机制是,当某个应用有新版本发布时,deployment会同时操作两个版本的replicaset。其内置多种滚动升级策略。
1.能够保证目标数量的pod运行,且使应用的服务在宕机后也不会降级。
2.即按照制定策略滚动升级,同时支持暂停,恢复和回滚。
3.可以便利的进行扩容和缩容,以应对负载的频繁变化。
service和ingress
待补充
persistenvolume和persistenvolumeclaim
pv是集群的一块存储卷,可以由管理员手动设置,或当用户创建pvc时根据storageclass动态设置。
pv和pvc的声明周期与pod无关,也就是说当pod出现重启,重新调度,删除时,pv和pvc不会受到影响,pod存储在pv中的数据得以保留.
纪要
1.采用本地静态存储
使用nfs挂载方式。
1.创建pv-绑定pvc-创建pod
特点:1.pod删除或者重启,数据都会存储在硬盘中,pod重启后pod中的数据依然在。
2.无法直接删除pvc因为pvc保护机制,pod还在使用这个pvc,必须先删除pod。删除pvc,pv后,数据依然保留。
2.动态存储storageclass
1.创建serviceaccount
2.rbc鉴权
3.创建storageclass
4.用户创建pv自动创建pvc
2.控制器
控制器模式是一个标准的生产者-消费者模式。一方面控制器在启动后,informer会监听其所关注的对象变化,一旦pod发生了创建,更新和删除等事件,这些事件会由核心组件api server推送给控制器,控制器会将对象保存在本地缓存中,将对象的信息推送给消息队列,此为生产者。
控制器会启动多个工作子进程(worker)从队列中依次获取对象信息,并从缓存中读取完整状态,并按照期望状态完整配置更改,并将最终状态回写至api server,此为消费者。
etcd
etcd是高可用的键值对的分布式安全存储系统,用于持久化存储集群中所有的资源对象。例如集群中的node,service,pod的状态和元数据,以及配置数据等。
apiserver
承担api的网关职责,是用户请求及其他系统组件与集群交互的唯一入口。所有资源的创建,更新和删除都需要通过调用api server中的api接口来完成。
对内,api server是各个模块之间数据交互的通信枢纽,api能够让其他组件监听到集群资源中的增删改查。
对外,充当网关作用,拥有集群的安全机制,完成客户端的身份验证,授权,并对资源进行准入控制。
controller manager
控制器是k8s集群自动化管理控制中心,里面包含了30多个控制器,有管理pod的控制器,有管理网络的控制器,有存储相关的控制器。
有很多场景需要多个控制器协同工作,比如某个节点宕机,kubelet会停止汇报状态到node对象,nodelifecycle控制器会发现状态没有按时更新,超过一段时间,它将驱逐节点上的pod。如果副本数量变少,控制器会补全pod副本数量,替换掉因为宕机而被删除掉的pod。
scheduler
集群中的调度器主要负责pod在集群节点中的调度分配。
scheduler通过运算查看node是否满足pod运行,不满足则被过滤
满足条件的node会按照端口占用,空闲资源,对实际资源的利用率等信息排序,评分最高的节点被更新到nodename属性中,该属性经api server保存到etcd中。
预选策略
podfitshostports:判断pod所要求的端口是否在节点中被占用。
podfitshost:判断node是否是pod中的spec.nodename指定的节点
podfitsresources:判断节点是否潘祖pod中申请的resource(例如cpu,memory的要求)。
podmatchnodeselector:判断节点是否满足spec.nodeselector限制。
novolumezoneconflict:评估pod spec.volume申请的存储卷是否在这个节点可用。
nodiskconflict:判断pod的volumes和该节点上挂载的磁盘是否有冲突。
maxcsivolumecount:判断节点是否已经在汇报是否有内存压力。
checknodediskpressure:判断节点是否已经在汇报有存储压力。
checknodecondition:根据节点的status.conditions判断节点状态,node节点是否可用。
podtoleratesnodetaints:判断pod上的toleration是否满足节点上的taints。
checkvolumebinding:检查节点是否满足pod所有的volume请求,包括bound和unbound的pvcs。
leastrequestedpriority:节点上的已有pod申请的资源越少,得分越高。
selectorspreadpriority:便利weighted的podaffinityterm的元素,如果节点满足相应的podaffinityterm条件。
还有一部分预选策略不再赘述,比如检查node是否已有pod所需的镜像,比如保证service后端的pod运行在不同的节点上。
kubelet
kubelet是运行在每个节点上的负责启动容器的重要的守护即成,在启动时,kubelet进程加载配置参数,向api server处创建一个node对象来注册自身信息。例如操作系统,kernel版本,ip地址,总容量,可分配的容量。向api server汇报自身的情况。
kubelet并不是直接进行容器操作的,而是通过容器运行时接口cri(container runtime interface)调用cri对容器和镜像进行操作的。目前默认情况下,kubelet是通过内置的dockershim调用docker来完成操作的。
kubelet还有很多其他功能:
1.对liveness和readiness进程检测。livedess用来探测容器是否处于“存活状态”,readiness用于探测容器中的用户进程是否处于“服务可用状态”
2.处理master节点下发到本节点的任务,比如exec,logs和attach等请求,api server是无法完成这些工作的,此时api server需要向kubelet发送请求,让kubelet完成此类请求处理。
kube-proxy
它通过api server监听service和endpoint对象的变化,并根据end point对象的信息设置service到后端pod的路由,维护路由规则,转发网络请求。
kube-proxy有两种模式都可以实现流量转发,分别是iptables和ipvs(可以通过--proxy-mode来指定)。该模式是通过每个节点上的iptables规则来实现的。可通过iptables -t nat -S查看相关iptables rules。
3.相关插件
容器运行时
cri是真正删除和管理容器的组件,docker是k8s支持的第一个容器运行时,kubelet通过内嵌的dockershim操作docker api来操作容器,进而达到面向终态的效果。使kubelet能够通过cri接口对容器沙盒及容器镜像进行操作。
cri相关概念:
1.cri被同一个容器调用时,不能并行操作,但被不同的操作调用时可以并行操作。
2.cri必须在调用任何插件之前为容器创建一个新的网络命名空间。
3.cri必须决定这个容器属于哪个网络,针对哪个网络,哪个插件必须要执行。
4.cri必须加载配置文件,并确定设置网络时哪些操作必须要执行。
还有很多概念,但是不是太重要,不再赘述。
网络插件
k8s网络模型设计的基础原则是:
所有的pod能够不通过nat就互相访问。
所有的节点能够不能过nat就能互相访问。
容器内看见的ip地址和外部组件看到的容器ip是一致的。
在k8s中,ip地址是以pod为单位进行分配的,每个pod都拥有一个独立的ip地址,一个pod内部的所有容器共享一个网络栈,即宿主机上的一个网络命名空间,包括它们的ip地址,网络地址配置都是共享的。也就是说,pod里面的所有容器能够通过localhost:port来连接对方。
在k8s中,提供了一个轻量级的通用容器网络接口cri,专门用于设置和删除容器的网络连通性,cri通过cni调用国网插件来完成容器的网络设置。
flannel
flannel使用k8s集群现有的etcd存储其状态信息,从而不必提供专门的数据存储,只需要在每台节点在安装flanneld守护进程即可。每个节点都被分配一个子网,为该节点上的pod分配ip地址,同一主机内的pod可以使用网桥进行通信,而不同主机上的pod将通过flanneld将其流量封装到udp数据包中,以路由到适当的目的地。封装的方式默认和推荐的方法是使用vxlan,它具有良好的性能,并且比其他选项要少一些人为干预,但缺点是该过程使流量跟踪变得困难。
calico
calico以其性能,灵活性和网络策略而闻名。不仅涉及在主机和pod之间提供网络连接,而且还涉及网络安全性和策略管理。
1.对于同网段基于第三层,calico使用bgp路由协议在主机之间路由数据包,使用bgp路由协议也意味着数据包在主机之间移动时不需要包装在额外的封装层中。这样在网络出现问题的时候可以很快的解决问题。
2.对于跨网段通信,基于ip in ip使用虚拟网卡tun10,用一个ip数据包封装另一个ip数据库包,外层ip数据包头的源地址为隧道入口设备的ip地址,目标地址为隧道出口的ip地址。
它通过acls协议和kube-proxy来创建iptables过滤规则,从而实现隔离容器网络的目的。calico还可以和服务网格istio即成,在服务网格层和网络基础结构层上解释和实施集群中工作负载的策略
4.关于pod生命周期
pod创建过程
1.用户或者控制器通过kubectl,rest api或其他客户端向api server提交pod创建请求。
2.api server将pod对象写入etcd中进行永久性存储。如果写入成功,则api server会受到来自etcd的确定信息并将创建结果返回给客户端。
3.api server处能够反映出pod资源发生的变化,一个新的pod被创建出来。
4.scheduler监听到api server处新的pod创建,它首先会检查pod是否已经被调度(spec.nodename是否为空)。如果该pod并没有调度到任何节点。那么scheduler会给pod分配到一个最优节点,并把它更新到spec.nodename中,从而完成pod的节点bind。
5.scheduler对pod的更新也将被api server写回道etcd中。scheduler同样会监听到pod对象发生了变化。但由于已经调度过该pod(spec.nodename不为空),所以不做任何处理。
6.kubelet也会一直监听api server处pod的资源变化。当其发现pod被分配到自己所在的节点上(自身节点ip和spec.nodename一样的时候),kubelet会调用cri grpc向容器运行时申请启动容器。
7.kubelet首先会调用runpodsandbox接口,确保podsandbox镜像是否存在。如果pause镜像已经存在。接着它会创建一个network namespaces 调用cni接口为network namespaces设置容器网络,containered使用这个network namespaces启动podsanbox。
8.当podsanbox启动成功后,kubelet才会在podsenbox下请求创建容器。这里kubelet会先检查镜像是否存在,如果镜像不存在,则调用cri的pullimage接口通过containerd将容器下载下来。
9.当镜像下载完成后,kubelet调用cri的createcontainer接口向容器运行时请求创建容器。
10.当容器创建成功后,kubelet调用cri的startcontainer接口向容器运行时请求启动容器。
11.无论容器是否启动成功,kubelet都会将最新的容器状态更新到pod状态的status中,让其他控制器也能够监听到pod对象的变化,从而采取相应的措施。
控制器监控api server中的对象变更,在自己关注的对象发生变更后完成即定变更控制逻辑,在将控制逻辑执行完成后的结果更新回api server,并持久化到etcd中
pod删除过程
1.用户或者控制器通过kubectl,rest api或其他客户端向api server提交pod删除请求。
2.pod对象不会立刻被api server删除。api server会在pod添加deleteiontimestamp和deletiongraceperiodseconds(默认值30s字段),并将pod的spec更改回写到edcd中。
3.api server将pod已删除的信息返回给用户,此时用户通过kubectl查看pod状态,发现pod已经被标记为terminating了。
4.当kubelet监听到api server处的pod对象的deleteiontimestamp被设置时,就会准备删除这个pod(killpod)。
5.kubelet首先会停止pod内的所有容器,调用cri的stopcontainer接口向容器运行时发起停止容器的请求。这里我们同样以containerd为例子,containerd会先调用runC向容器发送sigterm信号,容器停止或者deleteiongraceperiodseconds超时,再发送sigkill信号去杀死容器内的所有进程,完成容器的停止操作。
6.当容器被停止后,容器运行时会向kubelet发送消息,表示容器状态发送了变化,kubelet这时会把容器被停止的信息更新到pod的status当中。
7.endpoint控制器监听到这个pod的状态变化,接着将pod的ip地址从相关的endpoint对象中删除。
8.kube-proxy检测到endopint的变化,将根据新的endpoint设置的转发规则,从而移除pod ip的转发规则。
9.当pod内的容器被停止后,kubelet可以通过stoppodsandbox停止podsandbox,containerd首先会调用cni将容器内的网络插件删除,然后停止podsandbox,podsandbox停止后,kubelet会进行一些清理工作,例如清理pod的cgroup等。
10.如果pod上有finalizer,即使pod中的容器和podsandbox被全部停止,这个pod也不能消失,需要等到其他关联控制器完成相关的清理工作,并将pod上的finalizer删除。
11.当kubelet再次监听到pod的变化时,finalizer被清理干净了,canbedeleted方法返回true,发送deletiongraceperiodseconds为0的删除请求。
12.api server将这个pod从etcd中彻底删除。
pod的质量保证
k8s根据cpu,memory资源的requests和limit来划分pod的质量保证(QoS)级别,针对cpu或memory,每个容器可以指定requests和limit的值,requests表示k8s分配给容器可用的资源,limit表示k8s分配给容器最大可用的资源。
resources:
limits:
cpu: 500m #cpu的核数为500m,即为半个核,这里可以用0.5代替。
memory: 300Mi #mem的单位默认为bytes,这里约等于315M,可以用10进制表示或2的幂方。
requests:
cpu: 500m
memory: 200Mi
pod质量保证级别有三种:
1.guarantee:是pod的QoS最高级别,如果pod填写了limit没有填写request则会自动给它添贴一个与limits值相等的request。k8s不会主动杀死pod,除非它们所用的资源超过了limits。
2.pod中至少有一个容器制定了memory和cpu的request和limits,pod不满足guarantee的条件,requests的值和limits的值不相等,request和limits的匹配规则:
1)如果容器指定了limit和requests,那么当容器的cpu超过limits时会被限制。
2)如果容器只指定了cpu的requests,没有指定limit,那么当节点压力不大时,该容器可以使用超过requests值的节点上剩余的cpu。
3)假设容器设定了memory的request和limits,当容器的内存超过requests且没有超过limits时,如果节点内存不足,那么这个容器会被杀掉。当容器的内存超过limits时,容器将会被kernel杀掉,容器的状态变为ommkilledd(out-of-memory killed)。
4)如果容器只指定了memory的requests,没有指定limits,那么当容器的内存使用量超过request时,容器可以使用节点上的剩余内存,但是当节点内存不足以满足其他容器时,该容器可能会被“杀掉”。
3.besteffort:pod的每个容器都没有memory和cpu的requests和limits的值,这个级别的pod的优先级就是最低的。当系统有了cpu和memory的压力时,该pod会被率先杀掉。
pod的节点亲和性
我们知道scheduler负责pod的调度,通过一系列算法为每个pod统计出每个node的得分,得分最高的就是最佳目标节点,但是有些特殊节点我们需要把pod指定都某台特定的节点上,这时我们就需要使用pod的节点亲和性。
节点的亲和性使用方法有两种分别是nodeselector和nodeaffinity:
1.nodeselector:有k8s的基础都知道selector时k8s的标签选择器,那么使用的则是lable selector选择节点。如果pod指定了nodeselector。例如通过标签选择器调度到dedicated=demo和run=nginx的节点上,如果标签满足则调度到相匹配的节点,如果不满足,例如cpu和memory都不能满足pod的正常使用,则重新回到scheduler队列中重新调度,直到nodeselector的节点资源能够满足pod。
spec:
nodeSelector:
run=nginx
dedicated=demo
2.nodeaffinity:表达式能够更丰富一些分为硬规则和软件则,如果为软规则,当node不能够满足的时候,pod依然可以被调度。如果为硬性规则和nodeselector一样。
requiredDuringSchedulingIgnoredDuringExecution
表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中IgnoreDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,pod也会继续运行。
requiredDuringSchedulingRequiredDuringExecution
表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中RequiredDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,则重新选择符合要求的节点。
preferredDuringSchedulingIgnoredDuringExecution
表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。
preferredDuringSchedulingRequiredDuringExecution
表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。其中RequiredDuringExecution表示如果后面节点标签发生了变化,满足了条件,则重新调度到满足条件的节点。
运算符 作用
In lable的值在某个列表中
NotIn label的值不在某个列表中
Gt label的值大于某个值
Lt label的值小于某个值
Exists 某个label存在
DoesNotExist 某个label不存在
apiVersion:v1
kind:Pod
metadata:
name:affinity
labels:
app:node-affinity-pod
spec:
containers:
- name:with-node-affinity
image:nginx:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: #硬策略
nodeSelectorTerms:
- matchExpressions:
- key:kubernetes.io/hostname #也就是kubernetes.io/hostname不等于k8s-node01的节点上
operator:NotIn
values:
- k8s-node01
preferredDuringScheculingIgnoredDuringExecution: #软策略
- weight:1
preference:
matchExpressions:
- key:source #source=hahaha
operator:In
values:
- hahaha
对于nodeselector和nodeaffinity的使用:
1.如果pod同时指定nodeselector和nodeaffinity则必须同时满足才能够完成调度。
2.如果nodeaffinity中关联多个nodeSelectorTerms,且节点满足一个就可以完成调度。
3.如果包含多个matchExpressions,需要同时满足才能够完成调度。
4.preferredDuringScheculingIgnoredDuringExecution中多个node节点同时完成matchExpressions的匹配,则按照weight的权重。
pod的节点容忍性
ds
5.计算节点管理
操作系统规划
对比主流的三个系统:
rhel:是否也收费版本,随着集群规模license的收费也会迅速提高。
ubuntu和centos:免费;应用程序丰富;社区活跃度高,社区讨论是解决问题的高效手段。
centos相比较ubantu,centos的维护周期更长,软件更新频率更低,企业可以根据实际的节点需求和操作系统升级周期来选择操作系统的发行版本。
文件系统规划
centos和redhat文件系统默认都采用了xfs,ubantu默认采用ext4.xfs和ext4已经广泛使用很多年,技术较为成熟,
xfs和ext4共性:
1.测试和生产案例多,bug少。
2.bug修复周期短,出现问题,可以根据社区活跃解决问题。
3.使用范围广,用户多。
4.两者都是日志文件系统,都会记录文件在磁盘中的位置及磁盘元数据的任何更改。系统崩溃,文件系统可以快速恢复。
5.ext4通过quota对目录进行空间限制,xfs通过xfs_quota对目录进行空间限制。k8s可以利用该特性来限制emptyDir的目录大小。
6.支持文件系统扩容。满足k8s对磁盘扩容特性的支持。
xfs和ext4区别:
1.性能 对于小文件direct I/o xfs比ext4差。而在很多场景下的buffer I/O xfs占用cpu的时间比ext4占用的cpu更短。
2.文件大小和空间限制 ext4支持的最大单个文件大小为16TB,文件系统容量为16EB。xfs支持的最大单个文件大小8E,文件系统容量为8EB。
3.xfs支持快照,ext4不支持。
4.ext4采用对supper block,和inode采用预分配机制,所以格式化较慢,xfs在创建文件系统的时候,对inode等资源进行预分配的方式修改为动态分配,格式化较快。
容器分层
容器镜像以分层的形式组织管理,不同的分层被不同的镜像管理容器的镜像层是只读的,在基础镜像之上不断叠加。在运行容器的时候,镜像最上层会被挂载容器的可写层,容器的可写层一般不用于存储用户的容器数据,以防止容器重启后改数据丢失,但由于容器依然需要往容器可写层上写数据。因此需要存储驱动来管理容器的文件系统。
1.写时复制
copy-on-write。一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个copy。在需要对镜像提供的文件进行修改时,改文件会从镜像的文件系统中被复制到容器的可写层的文件系统中进行修改,而镜像中的文件不会改变。不同容器对文件的修改都相互独立,互不影响。
2.用时分配
按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。对文件的添加,删除,读写都只发生在容器的可写层,因此不同容器对文件的修改都相互独立。
节点状态上报
村kubelet向api server上报的信息如下:
1.节点基础信息,包括ip地址,操作系统,内核,运行时,kubelet,kube-proxy版本信息。
2.节点资源信息,包括cpu,内存,hugepage,临时存储,gpu等注册设备。以及可以分配给容器的部分。
3.调度器在为pod选择节点的时候会以机器的状态信息作为依据,
reday,memorypressure,pidpresshure,diskpresshure,networkunavailable
1.以下三个参数可以控制kubelet更新节点的状态频率
nodestatusupdatefrequency
nodestatusreportfrequency
nodeleasedurationseconds
早起版本只有nodestatusupdatefreque,默认配置下所有节点每隔10s上报一次节点状态,而数据量传输量比较大。随着集群规模的扩大,状态的频繁更新给对控制平面组件造成较大压力,与节点相关的控制器开销;极端场景,它甚至会使etcd迅速到达其存储上线。
自1.12版本后引入nodelease特性;k8s为每个节点创建一个轻量级的lease对象,该对象小很多。kubelet在节点状态发生变更或者默认的一分钟的nodestatusreportfrequency时钟周期到达时,更新节点的状态信息,同时以默认的10snodestatusupdatefrequency周期更新lease对象,在默认的40s的nodeleasedurationseconds周期内,若lease对象没有更新,则对应节点可以判定为不健康。
schedule在调度pod时根据节点状态来决定是否可以将新的pod调度到该节点,以免让本来处于不健康状态的节点的情况进一步恶化。
资源预留
kubelet的启动配置中有一个Node Allocatable特性,来为系统守护进程和k8s组件预留计算资源,使得即使节点满负载运行时,也不至于出现pod去和系统守护进程以及k8s组件争抢资源,导致节点挂掉的情况。目前支持对CPU, memory, ephemeral-storage三种资源进行预留。kubernetes官方建议根据各个节点的负载情况来具体配置相关参数。
ode Capacity:Node的硬件资源总量
kube-reserved:给k8s系统进程预留的资源(包括kubelet、container runtime、node problem detector等,但不会给以pod形式起的k8s系统进程预留资源)
system-reserved:给linux系统守护进程预留的资源
eviction-threshold:通过--eviction-hard参数为节点预留内存,当节点可用内存值低于此值时,kubelet会进行pod的驱逐
allocatable:是真正可供节点上Pod使用的容量,kube-scheduler调度Pod时的参考此值(kubectl describe node可以看到,Node上所有Pods的request量不超过Allocatable)
节点可供Pod使用资源总量的计算公式如下:
allocatable = NodeCapacity - [kube-reserved] - [system-reserved] - [eviction-threshold]
kubelet启动配置:
--cgroups-per-qos
--cgroup-driver
--cgroup-root
--enforce-node-allocatable=pods[,][system-reserved][,][kube-reserved]
--kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi]
--kube-reserved-cgroup
--system-reserved=[cpu=100mi][,][memory=100Mi][,][ephemeral-storage=1Gi]
--system-reserved-cgroup
--eviction-hard
具体参数使用用时百度理解具体使用方法
驱逐管理
kubelet会在系统资源不足的时候终止一些容器进程,以空出系统资源,保障节点的稳定性。在kubelet发起驱逐后,之后停止pod的所有容器进程,并不会删除pod。pod的status会被标记为evicted,并记录被驱逐的原因。kubelet内嵌cadvisor,周期性检查节点资源是否短缺。kubelet获取到节点可用额信息后,会结合节点的容量信息来判断当前节点运行的pod是否满足驱逐条件。根据当前资源的使用情况,驱逐方式可分为软驱逐和硬驱逐:
软驱逐:kubelet参数为evictionsorf,当检查当前资源到达软驱逐的阈值时,并不会立刻启动驱逐操作,而是要等待一个宽限期。这个宽限期选取evictionsortgraceperiod和pod指定的terminationgraceperiodseconds中选取最小的值。
硬驱逐:kubelet参数为evictionhard,没有宽限期,一旦检测到满足硬驱逐的条件,就直接终止容器来释放紧张资源。
1.基于内存压力的驱逐
kubelet默认设置memory.avaiable<100mi的硬性驱逐条件,当kubelet检测到当前节点可用内存资源紧张的时候满足驱逐条件,会将节点的memorypressure状态设置为true(也就是查看node状态为memory-pressure),调度器会阻止besteffort pod调度到承担内存压力的节点。
触发驱逐策略会根据以下顺序pod:
1)判断pod所有容器的内存使用量总和是否超出了请求的内存量,超出限制的pod作为驱逐备选目标。
2)查询pod调度的优先级,低优先级的pod被有限驱逐。
3)计算pod所有容器的内存使用量和pod请求的内存差值,差值越小,越容易被驱逐。
2.基于磁盘压力的驱逐
当磁盘资源使用情况高时,kubelet将会在驱逐正在运行的pod之前,尝试通过删除一些已经退出的容器和未使用的镜像来释放磁盘和inode。
系统分区nodefs达到阈值,删除已经退出的容器。imagefs达到驱逐阈值,则删除不再使用的镜像。容器运行时分区超过阈值,同时删除退出的容器和未使用的镜像。
当回收退出的容器和未使用的镜像后,仍满足驱逐条件:
1)根据pod磁盘使用量请求量,超出请求作为驱逐备选。
2)判断pod优先级,按照优先级驱逐。
3)根据磁盘超出请求的数量进行排序,差值越小,则越容易被驱逐。
数据在不同的分区上,所以kubelet根据不同的驱逐信号采用的驱逐策略也不一样。
1)有容器运行时分区,计算所有容器的日志和本地卷数据;如果是运行是分区出发了驱逐,那么kubelet计算pod的磁盘使用量中所有容器的可写层。
2)无容器运行时分区。对于系统分区出发的驱逐,kubelet将计算pod的磁盘使用量中的容器日志,本地卷和容器可写层。
hostpath卷管理
k8s允许用户通过hostpath卷将计算节点的文件目录挂载给容器,但是目录挂载完成后,系统无法限制用户往该卷写入数据的大小。这就导致在删除容器后,如果不主动进行数据清理,数据就会遗留在这台节点上,占用磁盘空间。
emptydir卷管理
emptydir是和pod的生命周期绑定的,可以限制用量的主机磁盘挂载。
容器的可写层管理
如用户可能会将大量的数据写入pod,容器的可写层与容器的生命周期也是绑定的管理。容器重启数据丢失。
存储方案
k8s支持多种存储类型可按照数据持久化方式分为临时存储如(emptydir),半持久化存储(hostpath)和持久化存储(包括网络存储和本地存储)
1.临时存储
emptydir的概念和特性就不赘述。empty的设计初衷是为了给应用充当缓存空间或者存储中间数据,用于快速恢复。但因为emptydir的空间位于系统根盘,被所有容器共享,所以在磁盘的使用率较高时会出发pod的eviction操作,从而影响业务的稳定。
2.半持久化存储
hostpath的作用是将主机节点文件指定到pod当中。
应用场景:
1.某个pod需要获取节点上所有pod的log,可以通过hostpath访问所有pod的stdout输出存储目录,例如/var/log/pods路径。
2.某个pod需要统计系统相关的信息,可以通过hostpath访问系统的/proc目录。
注意点:
1.使用同一个目录的pod可能会调度到不同的节点,导致目录中的内容有所不同。
2.kubernetes在调度时无法估计由hostpath使用的资源。
3.pod被删除后,如果没有特别处理,那么hostpath上写的数据会遗留在节点上,占用磁盘空间。
3.持久化存储
针对持久化存储k8s引入了storageclass,volume,pvc,pv的概念,将存储独立与pod的生命周期进行管理
pv存储由集群管理员提前创建,或者根据pvc的申请需求动态的创建,它代表系统后端的真实存储空间。用户通过创建pvc来申请存储。
用户通过创建pvc来申请存储。控制器通过pvc的storageclass的和请求大大小声明来存储后端创建卷,进而申请pv,pod通过指定pvc来引用存储。
1)网络存储
#待补充
创建过程:
1.创建pvc:用户创建了一个cinder存储的pvc
2.创建卷:当pv controller监听到该pvc的创建的时候,调用cinder的存储插件,从cinder存储后端申请卷。
3.创建pv:pv controller创建pv,pv主要包括以下字段:
spec.accessmode:卷的访问模式。
spec.capactiy.storage:卷的大小。
spec.cinder.volumeID:cinder卷的id。
4.绑定pvc/pv:在pv创建成功后,pv controller会将pv和pvc进行绑定,pvc和pv的状态都设置为bound。
5.创建pod:用户创建pod,并在其中申明使用这个pvc。
6.调度pod:kube-scheduler调度pod到某个节点。
7.attach卷:attach-detach controller检测到pod已经被调度到某个节点,遂将该pod使用的卷attach到某个节点
8.更新节点卷的attach信息:attach-detach controller将卷的相关信息添加到节点对象的node.status.volumesattached中。
9.更新节点卷的inuse信息:当kubelet监听到由pod已经调度到本机后,在汇报节点状态的时候,将pod使用的卷信息添加到对象节点node.status.volumeesinuse中。
10.挂载卷,启动容器:kubelet从节点对象上获知该卷已经执行了attach,在节点上找到该卷的设备信息,开始对盘做mount操作
删除过程:
1.在pod受到delete请求后,kubelet会终止容器,对磁盘进行umount操作,并对pod执行删除操作,这样pod就从api server中被删除了。
2.kubelet在汇报节点状态的时候,将该卷的相关信息从节点对象的node.status.volumeuse中删除。
3.在pod被删除后,attach-detach controller会判断卷是否已经从节点对象node.status.volumesinuse中删除,然后对卷从节点上进行detach。
2)本地存储
本地存储并没有相对网络存储一般的可靠性,而是需要使用本地存储的应用做数据备份,但具有低成本和高性能的优势。
相比较网络存储的话,本地存储会有相对的局限性,网络存储可以attach到不同的节点上,但是本地节点时一一绑定的,即如果某个本地存储的pv和pvc绑定,那么pod就必须调度到该pv所在的节点
本地存储的实现:每个本地存储的pv都需要对应节点上的某个物理磁盘空间。这块空间可以是独立的磁盘,也可以是基于磁盘的分区吗还可以时基于lvm创建出来的逻辑卷。由于本地存储pv是基于节点上真实的物理磁盘的,所以当磁盘损坏或丢失时会影响本地存储的使用。
6.实战篇
command
1.使用 kubectl drain 从集群中移除节点
对节点执行维护操作之前(例如:内核升级,硬件维护等),您可以使用kubectl drain 安全驱逐节点上面所有的 pod。安全驱逐的方式将会允许 pod 里面的容器遵循指定的 PodDisruptionBudgets 执行优雅的中止。
2.因为k8s的yaml格式缩紧以及单词拼写要求特别严格,对于这些问题,都会造成无法创建pod,一个实用的技巧生成yaml。
关于污点
1.污点标签打上之后,节点不会驱逐上面的pod。
2.就算手动驱逐也不会驱逐控制器为daemon set类型的。
3.关于创建statefuset,deployment,daemon对象的控制器时,当节点配置的污点,三种控制器能否调度过去?
deployment
apiVersion: apps/v1 #api版本
kind: Deployment #选择什么控制器来创建
metadata: #定义元数据
name: nginx-deployment #deployment资源名称
namespace: default #指定pod运行命名空间
spec: #关于pod的详细定义
selector: #选择器
matchLabels: #给pod打标签
app: nginx
replicas: 2 #副本数
template:
metadata:
labels: #这里的标签要和selector选择器匹配
app: nginx
spec: #spec对象的容器信息
containers:
- name: nginx #容器名称
image: nginx:1.8
ports:
- containerPort: 80
概念问题
1.configmap和secret的区别是
最本质的区别是明文和密文。
configmap和pvc和secret本质上都是作为卷加载给运行的pod,pod中运行可以像访问本地文件一样访问它们。configmap和secret本质上没有区别。secret只是将内容进行base64编码。我们知道base64是一个对称加密算法可以轻松进行解密。但是k8s支持secret在持久化时的加密存储,这样保存在银盘的secret数据是无法解密的。并且k8s可以通过权限严格的控制能够访问secret的用户。
2.容器沙箱概念
容器沙箱是“pause”容器的抽象概念,pod在启动时会运行一个非常简单的pause进程,它不执行任何功能,一启动就永远把自己阻塞住了(pause系统调用),容器沙箱最大的作用是维护pod的网络协议栈。作用是为了在pod可能因为各种原因退出,但是因为容器沙箱的存在,容器的网络命名空间不会被摧毁,当重新创建用户容器时,就无需再为他设置网络了。
3.liveness和readiness指针的区别
1.liveness是存活指针,检测容器可用性,当kubelet检测容器处于“死亡状态”则kubelet则会停止该容器,并重新创建新的容器。
2.readiness是就绪指针,检测容器中的服务可用行,当检测容器当前处于“不可服务状态”,则kubelet不会立刻重启重启,则会把pod中的容器状态更新为containersready=flase,endpoint控制器会将该pod剔除网络,这个pod将不再接受任何用户请求。
4.资源紧张pod会被怎么处理
1.当磁盘超过90%则kubelet就会清理不用的镜像。
2.cpu和memory超过80%触发迁移策略,首先驱逐优先级较低的pod。(有限制可以在定义pod时候指定,一般由算法生成)
kubelet通过这些操作保障节点已有的pod能够保证的QoS下继续正常运行。
5.kube-proxy iptables和ipvs区别
iptables:随着service数量的增大,iptables模式由于线程查找匹配,全量更新等特点,其性能会明显下降,通过iptables -t nat -S查看iptables rules。
ipvs:在k8s 1.8版本之后引入了ipvs,ipvs和iptables同样基于netfilter,但是采用的是哈希表而且运行在内核态,在service比较多的时候ipvs的优势能够完全显现出来,从而提高service的服务性能。通过ipvsadm查看ipvs转发规则。