k8s运行时
文章目录
Kubelet
每个节点上都运行一个kubelet服务进程,默认监听10250端口
- 接收并执行master发来的指令
- 管理Pod及Pod中的容器
- 每个kubelet会在APIServer上注册节点自身信息,定期向master节点汇报节点状态及资源使用情况,监控容器状态
获取Pod清单的方式:
- 本地文件。启动参数–config指定的配置目录下的文件(默认/etc/kubernetes/manifests),每20秒重新检查一次
- URL。启动参数–manifest-url设置,每20秒检查一次
- APIServer。通过APIServer监听etcd,同步Pod清单
- HTTP Server。监听HTTP请求,获取新提交的Pod清单
Sandbox
k8s启动Pod时,会先启动一个Sandbox容器即 /pause 容器。
- 镜像非常小,执行的命令是无限sleep,不消耗资源,及其稳定
- 把这个sandbox容器作为Pod的底座,先启动这个容器,配置好网络,再启动业务容器
- 网络等各种Namespace就配置在sandbox容器上
- 保证网络等配置稳定,业务容器启动时需要网络
kubelet启动Pod流程
kubelet创建Pod时,会依次调用CSI->CRI->CNI接口进行配置。
CRI
容器运行时运行在k8s的每个节点上,负责容器的整个生命周期。为了解决容器运行时和k8s集成的问题,推出CRI容器运行时接口,以支持更多的容器运行时。
CRI是k8s定义的一组gRPC服务,kubelet作为客户端,基于gRPC框架,通过socket和容器运行时通信。包括两类服务:
- 镜像服务,提供下载、检查和删除镜像的rpc接口
- 运行时服务,提供容器生命周期管理、容器交互(exec/attach)的rpc接口
容器运行时细分:
- 高层运行时,如Docker/containerd
- containerd不需要Dockerd/Docker-shim,在容器创建删除和启停上有性能优势,适用于生产
- docker支持镜像推送操作,适用与开发(标准的CRI接口没有定义镜像推送)
- 低层运行时,主要是runc,实现容器程序的运行,Namespace/cgroups的配置,挂在根文件系统等。
Docker早期架构中,daemon是主进程,其它容器进程是由daemon fork出来的。
- 带来的问题是,Docker升级重启时,daemon这个父进程需要销毁,容器进程也要销毁。
- Docker内部也是依赖containerd,层层封装,不好维护,出问题只能重启。
containerd的处理方式是,使用container shim作为容器进程的父进程,shim的父进程是systemd。这样在containerd重启时,容器不受影响。
- shim和容器是一对一的,重启一个shim也只会影响一个容器。
- shim是轻量级的,基本不需要升级。
CNI
k8s网络模型设计原则是:
- 所有的Pod能够不通过NAT就能互相访问
- 所有的节点能够不通过NAT就能互相访问
- 容器内看见的IP地址和外部组件看到的容器IP是一样的
IP地址是以Pod为单位分配的,一个Pod内部所有容器共享一个网络栈,即宿主机上的一个网络命名空间。
在k8s中,提供了一个轻量的通用容器网络接口CNI,专门用于设置和删除容器的网络连通性。容器运行时通过CNI调用网络插件来完成容器的网络设置。
容器运行时一般需要配置两个参数:
- –cni-bin-dir 网络插件的可执行文件所在目录,默认是
/opt/cni/bin
- –cni-conf-dir 网络插件的配置文件所在目录,默认是
/etc/cni/net.d
容器运行时在启动时会从CNI的配置目录中读取JSON格式的配置文件,如果目录下有多个文件,按字母顺序选择第一个文件作为默认配置,并加载其中指定的CNI插件名称和配置参数。
- kubelet调CRI是gRPC,CRI调CNI是二进制调用。
runtime把操作目标和参数告诉CNI插件,然后由CNI插件做网络配置。
- 如,ipam插件给pod分配ip,calico把ip绑定给容器的namespce,bandwidth限制带宽
配置完成后将结果返回给 container runtime,由runtime把ip等信息返回给kubelet,kubelet上报给APIServer,APIServer更新Pod状态。
Flannel
简单。封包解包有额外开销,重效率的系统不会用。只是做CNI插件,没有完备生态,没有network policy的防火墙插件,这点不如Calico。
有三种模式:
- overlay网络
- UDP,IP In UDP,依赖tun设备和用户态进程flanneld,多次用户态/内核态切换开销大
- VxLan,Mac In UDP,依赖内核态的VxLan虚拟设备,涉及ARP表,FDB表,路由表。
- Underlay网络
- hostGW,Node节点IP作为下一跳,要求Node节点二层互通
Calico
提供CNI插件及网络管理功能
三种组网模式:
- VxLan, MAC on UDP
- IPIP,内核tun设备封包
- BGP,基于BGP协议交换路由,Node网卡做网管,要求二层互通
CSI
容器运行时存储,存的是镜像里的层,本身也是一个文件系统(Docker和containerd都使用overlayFS作为运行时存储驱动)。写到这里面也可以,但是性能很低,不建议写到这里。写文件建议使用运行时之外的存储插件。
kubelet和插件怎么交互,命令怎么发到插件里?kubelet不支持动态链接,要么像CNI那样执行本地文件方式,要么像CSI这样通过unix socket调用。
kubelet通过RPC(unix socket)调用存储驱动。kubelet是个framework,它只调接口。至于接口具体怎么实现,是每种插件去定义的。kubelet和插件就解耦了。
External Components/Custom Components
Rook/Ceph
存储相关API对象
主要分为:
- 临时存储 emptyDir
- 半持久化存储 hostPath
- 持久化存储 StorageClass, PersistentVolume, PV, PVC
临时存储 emptyDir
emptyDir卷和Pod的存在是绑定的,Pod删除后emptyDir也会丢失。Pod重启不会丢失emptyDir数据。
- 默认情况下,emptyDir卷存储在该节点所使用的存储介质上,可以是本地磁盘或网络存储
- 也可设置 emptyDir.medium为 Memory 使k8s为容器安装tmpf,此时数据存储在内存。
- 在节点重启时,内存数据会被清除,如果存在磁盘上,则不会被清除
|
|
半持久化存储 hostPath
hostPath能将主机节点文件系统上的文件或目录挂在到指定Pod,Pod删除后数据还在。
- 普通用户一般不需要这样的卷
- 对需要获取节点系统信息的Pod而言是有必要的
- 如把节点日志目录mount到pod
- 通过hostPath访问节点的/proc目录
- DaemonSet向主机拷贝文件
Kubernetes ⽀持 hostPath 类型的 PersistentVolume 使⽤节点上的⽂件或⽬录来模拟附带⽹络的存储。
- 需要注意的是在⽣产集群中,我们不会使⽤ hostPath,集群管理员会提供⽹络存储资源,⽐如 NFS 共享卷或 Ceph 存储卷,集群管理员 还可以使⽤ StorageClasses 来设置动态提供存储。
- 因为 Pod 并不是始终固定在某个节点上⾯的,所以要使⽤ hostPath 的话我们就需要将 Pod 固定在某个节点上,这样显然就⼤⼤降低了应⽤ 的容错性。
使⽤ hostPath 也有⼀些好处的,因为 PV 直接使⽤的是本地磁盘,尤其是 SSD 盘,它的读写性能相⽐于⼤多数远程存储来说, 要好得 多,所以对 于⼀些对磁盘 IO 要求⽐较⾼的应⽤⽐如 etcd 就⾮常实⽤了。不过相⽐于正常的 PV 来说,使⽤了 hostPath 的这些节点⼀旦宕机数据就可能丢 失,所以这就要求使⽤ hostPath 的应⽤必须具备数据备份和恢复的能⼒,允许你把这些数据定时 备份在其他位置。
|
|
Pod调度与Volume挂载的顺序
从卷的⽣命周期来讲,卷被Pod使⽤或者卷被回收,会依赖顺序严格的⼏个阶段
卷被Pod使⽤
- provision,卷分配成功
- attach,卷挂载在对应workernode
- mount,卷挂载为⽂件系统并且映射给对应Pod
卷被回收
- umount,卷已经和对应workernode解除映射,且已经从⽂件系统umount
- detach,卷已经从workernode卸载
- recycle,卷被回收
从Kubernetes存储系统来讲,卷⽣命周期管理的职责,⼜分散于不同的控制器中
- pv controller,负责创建和回收卷
- attach/detach controller,负责挂载和卸载卷
- volume manager,负责mount和umount卷
假设scheduler已经完成worker node选择,确定了调度的节点。此时创建Pod前,需要先完成卷映射到Pod路径中,整个过程如下:
- 卷分配,pvc绑定pv,由pv controller负责完成
- attach/detach controlle,如果pod分配到worker node,并且对应卷已经创建,则将卷attach到对应worker node,并且给worker node资源增加volume已经attach对应卷的状态信息
- volume manager在worker node中负责将卷mount到对应路径
- pod分配到本workernode后,获取Pod需要的volume,通过对⽐node状态中的volumesAttached,确认volume是否已经attach到 node节点
- 如果attach到node节点则将⾃身actualStateOfWorld中的volume状态设置成attached
- 如果已经attached成功,则进⼊到⽂件系统挂载流程
- 先挂载到node中全局路径,⽐如/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-3ecd68c7b7d211e8/globalmount
- 映射到Pod对应路径,⽐如/var/lib/kubelet/pods/49a5fede-b811-11e8-844f-fa7378845e00/volumes/kubernetes.io~csi/pvc-3ecd68c7b7d211e8/mount
- actualStateOfWorld中设置volume为挂载成功状态
- pod controller确认卷已经映射成功,继续启动Pod
PV/PVC
PV和PVC的容量不匹配会怎样?pvc的容量可以小于pv,仍能正常绑定;pvc的容量大于pv,就会pending。
PV 的全称是:PersistentVolume(持久化卷),是对底层共享存储的⼀种抽象,PV 由管理员进⾏创建和配置(或根据PVC的申请需求动态创建,需要CSI Driver支持)。 和具体的底层的共享存储技术的实现⽅式有关,⽐如 Ceph、GlusterFS、NFS、hostPath 等,通过插件机制完成与共享存储的对接。
集群管理员定义完StorageClass,会安装好对应的插件。当用户创建PVC时,指定使用的StorageClass,此时这个SC对应的插件就要去工作:关联由管理员创建好的PV,或者CSI Driver动态创建PV。
PV 的状态,实际上描述的是 PV 的⽣命周期的某个阶段,⼀个 PV 的⽣命周期中,可能会处于4种不同的阶段:
- Available(可⽤):表示可⽤状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PVC 已经被 PVC 绑定
- Released(已释放):PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的⾃动回收失败
PVC 的全称是:PersistentVolumeClaim(持久化卷声明),PVC 是⽤户存储的⼀种声明,PVC 和 Pod ⽐较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,⽽ PVC 可以请求特定的存储空间和访问模式。对于真正使⽤存储的⽤户不需要关⼼底层的存 储实现细节,只需要直接使⽤ PVC 即可。
创建 PVC 之后,Kubernetes 就会去查找满⾜我们声明要求的 PV, ⽐如 storageClassName、accessModes 以及容量这些是否满⾜要求, 如果满⾜要求就会将 PV 和 PVC 绑定在⼀起。
PVC中声明的accessMode:
- RWO, ReadWriteOnce 该卷只能在一个节点上被mount,属性为可读可写
- ROX, ReadOnlyMany 可以在不同节点上被mount,属性为只读
- RWX, ReadWriteMany 可以在不同节点上被mount,属性为可读可写
在持久化容器数据的时候使⽤ PV/PVC 有什么好处呢? ⽐如我们之前直接在 Pod 下⾯也可以使⽤ hostPath 来持久化数据,为什么还要费劲去创建 PV、PVC 对象来引⽤呢?
- PVC 和 PV 的设计,其实跟“⾯向对象”的思想完全⼀致,PVC 可以理解为持久化存储的“接⼝”,它提供了对某种持久化存储的描述,但不提 供具体的实现;⽽这个持久化存储的实现部分则由 PV 负责完成。
- 这样做的好处是,作为应⽤开发者,我们只需要跟 PVC 这个“接⼝”打交道,⽽不必关⼼具体的实现是 hostPath、NFS 还是 Ceph。毕竟这 些存储相关的知识太专业了,应该交给专业的⼈去做,这样对于我们的 Pod 来说就不⽤管具体的细节了,你只需要给我⼀个可⽤的 PVC 即 可了,这样就完全屏蔽了细节实现解耦。
PV和PVC过早绑定
PV和PVC过早绑定可能会带来调度问题
- 如PV和PVC创建完成后,PVC会和第⼀个创建的位于master节点的PV绑定;
- 等创建Pod时,Pod的亲和性只允许运⾏在node0节点导致冲突,Pod状态Pending。
可以利用StorageClass中的volumeBindingMode: WaitForFirstConsumer
配置,将PV和PVC的StorageClass改为这个新建的local-storage,⾥⾯声明了延迟绑定。 这样PVC不会⽴即和PV绑定,⽽是延迟到对应的Pod进⾏调度时。
|
|
StorageClass
仅仅通过 PVC 请求到⼀定的存储空间也很有可能不 ⾜以满⾜应⽤对于存储设备的各种需求,⽽且不同的 应⽤程序对于存储性能的要求可能也不尽相同,⽐如 读写速度、并发性能等。
为了解决这⼀问题,Kubernetes ⼜为我们引⼊了⼀ 个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义 为某种类型的资源,⽐如快速存储、慢速存储等,⽤ 户根据 StorageClass 的描述就可以⾮常直观的知道 各种存储资源的具体特性了,这样就可以根据应⽤的 特性去申请合适的存储资源了。
此外 StorageClass 还可以为我们⾃动⽣成 PV,免 去了每次⼿动创建的麻烦(当然也支持手动创建PV)。
- static provisioner 手动创建PV,通过StorageClass关联,绑定PVC
- dynamic provisioner 自动创建PV,绑定PVC,需要插件支持
在StorageClass的定义中,重要的是指定provisioner插件是哪一个,自动生成PV的能力是由插件实现的。
|
|
|
|
Local PV
hostPath volume并不适合在⽣产环境中使⽤。由于集群中每个节点的差异化,使⽤hostPath,必须通过节点亲和性精确调度,特别繁琐。
Local PV⽤来解决hostPath的各种问题。 PV Controller 和 Scheduler 会对 Local PV做特殊的逻辑处理,实现Pod使⽤本地存储时在发⽣ re-schedule的情况下能再次调度到 local volume 所在的Node。
但是,毕竟本质上还是节点上的本地存储。如果没有存储副本机制,⼀旦节点或者磁盘异常,使⽤该volume的Pod也会异常,甚⾄出现数据丢失。
其它
- Local Volume 用于高IOPS应用,创建独占的Volume,Pod删除后数据也会删除
- Dynamic Local Volume 用于需要大容量存储空间,一块盘不够的场景,利用Linux LVM将多块盘组合成一个Volume Group。