[Kubernetes] 7控制平面组件
1. 调度
kube- scheduler
- what
- 负责分配调度pod到集群节点
- 监听kube-apiserver,查询未分配node的pod
- 根据调度策略分配这些pod(更新pod的nodename)
- 需要考虑的因素:
- 公平调度,资源有效利用,QoS,affinity, anti-affinity, 数据本地化(data localoity),数据本地化,内部负载干扰,
调度器
- 两个阶段: 1. predicate, priority
- predicate: 过滤不符合条件的节点
- priority: 优先级排序,选择优先级最高的节点
predicate 策略
- PodFitsHostPorts:检查是否有HostPorts冲突。
- PodFitsPorts: 同PodFitsHostPorts
- PodFitsResources:检查Node的资源是否充足,包括允许的Pod数量、CPU内存、GPU个数以及其他的- - OpaquelntResources。
- HostName:检查pod.Spec.NodeName是否与候选节点一致
- MatchNodeSelector:检查候选节点的pod.Spec.NodeSelector是否匹配
- NoVolumeZoneConflict:检查volumezone是否冲突
- MatchlnterPodAffinity:检查是否匹配Pod的亲和性要求。
- NoDiskConflict:检查是否存在Volume冲突,仅限于GCEPD、AWSEBS、CephRBD以及iscSl。
- PodToleratesNodeTaints:检查Pod是否容忍NodeTaints,
- CheckNodeMemoryPressure:检查Pod是否可以调度到MemoryPressure的节点上。
- CheckNodeDiskPressure:检查Pod是否可以调度到DiskPressure的节点上,
- NoVolumeNodeConflict:检查节点是否满足Pod所引用的Volume的条件。
- 还有很多其他策略,你也可以编写自已的策略
predicate的工作原理
priority 策略
-
SelectorSpreadPriority
- 优先将Pod分散到不同的节点上,减少同一Service或Replication Controller下的Pod在同一节点上的数量。
-
InterPodAffinityPriority
- 优先将Pod调度到与现有Pod具有相同拓扑的节点上,例如在同一机架或区域内。
-
LeastRequestedPriority
- 优先选择资源请求最少的节点。
-
BalancedResourceAllocation
- 平衡各节点的资源使用,避免某些节点资源过载而其他节点资源空闲。
-
NodePreferAvoidPodsPriority
- 根据
alpha.kubernetes.io/preferAvoidPods
字段的判断,给予高权重,以避免Pod被调度到某些节点上。
- 根据
-
NodeAffinityPriority
- 优先调度到与Pod的NodeAffinity匹配的节点上。
-
TaintTolerationPriority
- 优先调度到能够容忍Pod的Taints的节点上。
-
ServiceSpreadingPriority
- 尽管已被SelectorSpreadPriority替代,但此策略旨在将同一Service的Pod分散到不同节点上。
-
EqualPriority
- 默认未使用,将所有节点的优先级设置为相同值。
-
ImageLocalityPriority
- 默认未使用,优先将使用大镜像的Pod调度到已经缓存了该镜像的节点上。
-
MostRequestedPriority
- 默认未使用,优先调度到资源使用最多的节点上,适用于自动扩缩的集群。
资源需求
-
CPU
- Requests: 确定Pod启动所需的最小CPU量。
- Limits: 设置Pod可以使用的最大CPU量,超过此限制可能会导致Pod被终止。
-
内存 (Memory)
- Requests: 确定Pod启动所需的最小内存量。
- Limits: 设置Pod可以使用的最大内存量,超过此限制可能会导致Pod被终止。
-
磁盘资源需求 (Disk Resource Requirements)
- 容器临时存储 (Ephemeral Storage): 包含日志和可写层数据。
- 通过定义
PodSpec
中的limits.ephemeral-storage
和requests.ephemeral-storage
来申请。
- 通过定义
- 计算节点对临时存储的限制:不是基于cGroup的,而是由
kubelet
定时获取容器的日志和容器可写层的磁盘使用情况。如果超过限制,则会对Pod进行驱逐
- 容器临时存储 (Ephemeral Storage): 包含日志和可写层数据。
-
InitContainer的资源需求 (InitContainer Resource Requirements)
- 当
kube-scheduler
调度带有多个init容器的Pod时:- 只计算
cpu.request
最多的init容器,而不是计算所有init容器总和。 - 因为多个init容器按顺序执行,并且执行完成立即退出,所以申请最多的资源init容器中的所需资源,即可满足所有init容器需求。
kube-scheduler
在计算该节点被占用的资源时,init容器的资源依然会被纳入计算。因为init容器在特定情况下可能会被再次执行,比如由于更换镜像而引起sandbox重建时
- 只计算
- 当
pod调度到指定的node上
- 使用Node Selector
- 通过给Node打上标签,并在Pod定义中使用nodeSelector来指定相应的标签,Pod就会被调度到带有这些标签的Node
- kubectl label nodes node-01 disktype=ssd
- 在daemonset中指定nodeSelector为disktype=ssd
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test # 添加的标签
spec:
nodeSelector:
disktype: ssd # 指定调度到带有disktype=ssd标签的Node
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
- 使用Node Affinity
- NodeAffinity提供了两种类型的节点亲和性规则:必须满足的规则(requiredDuringSchedulingIgnoredDuringExecution)和优选的规则(preferredDuringSchedulingIgnoredDuringExecution)
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/e2e-az-name
operator: In
values:
- e2e-az1
- e2e-az2
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
weight: 1 # 正确放置的weight字段
containers:
- name: with-node-affinity
image: gcr.io/google_containers/pause:2.0
- 直接设置 nodeName
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeName: <指定的Node名称> # 直接指定Node名称
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
- Pod Affinity
- 通过podAffinity和podAntiAffinity,基于其他Pod的标签选择Node
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: gcr.io/google_containers/pause:2.0
- 使用Taints 和 Tolerations
- aints应用于Node上,而Toleration则应用于Pod上。使用Taints和Tolerations可以确保Pod只被调度到满足特定条件的Node上
- 支持的Taint类型
- NoSchedule:新的Pod不会调度到该Node上,但不影响已经在运行的Pod。
- PreferNoSchedule:相当于一个软性的NoSchedule,尽量不让新的Pod调度到该Node上。
- NoExecute:新的Pod不会调度到该Node上,并且会驱逐(evict)已经在运行的Pod。Pod可以设置一个tolerationSeconds字段,表示在被驱逐前等待的时间
- Pod如何调度到带有Taints的Node
- 当Pod的Tolerations能够匹配Node的所有Taints时,Pod可以被调度到该Node上
- 如果Pod已经运行在Node上,即使Node后来被添加了Taints,Pod也不会被驱逐,除非Taint是NoExecute类型,并且Pod没有设置tolerationSeconds
多租户Kubernetes集群的计算资源隔离
- 在多租户Kubernetes集群中,一些自带计算资源的客户可能希望将他们的资源加入集群,并要求资源隔离
- 实现资源隔离的一种方案是将要隔离的计算节点打上Taints,在用户创建Pod时,定义Tolerations来指定要调度到带有这些Taints的Node上
生产环境中的一些经验
- 用户可能会忘记设置Toleration,导致Pod无法调度,处于Pending状态。这是新员工常犯的错误,可以通过聊天机器人的Q&A来解决
- 其他用户可能会查看Node详情,查到Taints信息,并使用相同的Tolerations来占用资源
- 企业内部可以通过规范管理,通过统计数据来监控谁占用了哪些Node
- 为了防止用户查看到Taints信息,可以限制他们获取Node详情或他人的Pod详情
调度的优先级
- Pod优先级调度:从Kubernetes版本1.8开始,kube-scheduler支持定义Pod的优先级,确保高优先级的Pod在资源有限的情况下能够优先调度
- 启用Pod优先级:要使用Pod优先级调度功能,需要在API server和kube-scheduler的配置中开启相应的feature
--feature-gates=PodPriority=true
--runtime-config=scheduling.k8s.io/v1alpha1=true
- PriorityClass
- 在设置Pod的优先级之前,需要先定义一个PriorityClass(非namespace资源)。PriorityClass定义了一组可用于Pod的优先级级别
apiVersion: v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
- 为Pod设置PriorityClass
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
- 多调度器
- 如果默认的调度器不满足要求,可以部署自定义的调度器,并在整个集群中同时运行多个调度器实例。可以通过podSpec.schedulerName来选择使用哪一个调度器
来自生产的一些经验
生产经验分享
-
Pod Eviction 后IP变化
- Pod被驱逐(evict)后,其IP地址可能会发生变化。但如果endpoint中的address更新失败,可能会导致连接问题。
-
Endpoint Worker 线程卡死
- 分析stack trace发现endpoint在更新LoadBalancer时调用gophercloud连接hang住,导致endpoint worker线程全部卡死。
-
用户忘记设置Toleration
- 新员工常犯的错误是忘记给Pod设置Toleration,导致Pod无法调度,处于Pending状态。这可以通过聊天机器人的Q&A来辅助解决。
-
用户查看Node详情获取Taints信息
- 其他用户可能会查看Node详情,查到Taints信息,并使用相同的Tolerations来占用资源。
-
Dashboard 监控
- 通过dashboard可以监控哪些用户的什么应用跑在哪些节点上,对于违规用户,主要采取批评教育的方式。
-
调度器的稳定性
- 调度器是运营过程中稳定性最好的组件之一,基本不需要太多的维护工作。
-
调度性能
- 在拥有100个node的小集群中,并发创建8000个Pod的最大调度耗时大约是2分钟左右。
-
Node删除后的问题
- Node删除后,如果scheduler cache中还有该Node的信息,可能会导致Pod调度失败。
-
Node故障时的放大效应
- 当一个Node出现问题负载较小时,用户的Pod可能会优先调度到该Node上,如果新创建的Pod都失败,可能会因为打开文件句柄过多导致Node宕机。
-
应用炸弹问题
- 存在危险的用户Pod(如forkbomb),在调度到某个Node上后,可能会导致该Node以及其他Node受到连锁影响,最终可能导致整个集群的所有节点不可用。
Controler Manager
-
控制器的工作流程
-
informer的内部机制
-
控制器的协同工作原理
通过controler
- JobController:处理job。
- PodAutoScaler:处理pod的自动缩容/扩容。
- ReplicaSet:依据ReplicasetSpec创建Pod。
- ServiceController:为LoadBalancertype的service创建LB ViP。
- ServiceAccountController:确保serviceaccount在当前namespace存在。
- StatefulSetController:处理statefulset中的pod。
- VolumeController:依据PVspec创建volume。
- ResourcequotaController:在用户使用资源之后,更新状态。
- NamespaceController:保证namespace删除时,该namespace下的所有资源都先被删除。
- ReplicationController:创建RC后,负责创建POD。
- NodeController:维护node状态,处理evict请求等。
- DaemonController:依据daemonset创建pod。
- DeploymentController:依据deploymentspec创建replicaset。
- EndpointController:依据servicespec创建endpoint,依据podip更新endpoint。
- GarbageCollector:处理级联删除,比如删除deployment的同时删除replicaset以及pod。
- CronJobController:处理cronjob。
cloud control manager
-
什么时候需要cloudcontrollermanager
- 认证授权:企业cloud往往需要认证信息,Kubernetes要与CloudAPi通信,需要获取cloud系统里的ServiceAccount
- Cloudcontrollermanager本身作为一个用户态的component,需要在Kubernetes中有正确的RBAC设置,获得资源操作权限
- 高可用:需要通过leaderelection来确保cloudcontrollermanger高可用
-
配置
- cloudcontrollermanager是从老版本的APiServer分离出来的。
Kube-APiServer和kube-controller-manager中一定不能指定cloud-provider,否则会加载
内置的cloudcontrollermanager。
Kubelet要配置–cloud-provider=external
- cloudcontrollermanager是从老版本的APiServer分离出来的。
-
CloudControllerManager主要支持
- Nodecontroller:访问cloudAPl,来更新node状态;在cloud删除该节点以后,从
kubernetes删除node - Servicecontroller:负责配置为loadbalancer类型的服务配置LBViP;
- RouteController:在cloud环境配置路由;
- 可以自定义任何需要的CloudController。需要定制的Cloud controller
- Nodecontroller:访问cloudAPl,来更新node状态;在cloud删除该节点以后,从
-
需要定制的cloud controller
-
Ingress Controller:负责管理Ingress资源,处理外部访问到集群内部服务的流量路由。
-
Service Controller:管理Service资源,确保服务发现和负载均衡的正常工作。
-
自主研发的controller:
-
RBAC Controller:基于角色的访问控制(Role-Based Access Control)的控制器,负责管理集群内角色和角色绑定。
-
Account Controller:负责用户账户相关操作的控制器
-
-
生产经验总结
- 保护好controller manager的kubeconfig:0
确保schdeduler和controller的高可用
- Kubernetes 提供基于 configmap 和 endpoint 的 leader election 类库
- 采用 leader election 模式启动 component 后,会创建对应 endpoint,并把当前的 leader 信息 annotate 到 endpoint 上
apiVersion: v1
kind: Endpoints
metadata:
annotations:
control-plane.alpha.kubernetes.io/leader: '{"holderldentity":"minikube","leaseDurationSeconds":15,"acquireTime":"2018-04-05T17:31:29Z","renewTime":"2018-04-07T07:18:39Z","leaderTransitions":0,"resourceVersion":"138930"}'
selfLink: /api/v1/namespaces/kube-system/endpoints/kube-scheduler
creationTimestamp: 2018-04-05T17:31:29Z
name: kube-scheduler
namespace: kube-system
subsets: null
uid: 2d12578d-38f7-11e8-8dfo-0800275259e5
election 机制
kubelet
kubelet 的架构
kubelet 管理pod的核心流程
kubelet
- 在每个节点上都运行着一个kubelet服务进程,默认监听10250端口
- 接收并执行来自master的指令;
- 管理Pod及Pod中的容器;
- 每个kubelet进程会在API Server上注册节点自身信息,定期向master节点汇报节点的资源使用情况,并通过cAdvisor监控节点和容器的资源
节点管理
-
节点管理主要包括节点自注册和节点状态更新:
- Kubelet可以通过设置启动参数–register-node来确定是否向API Server注册自己;
- 如果Kubelet没有选择自注册模式,则需要用户自己配置Node资源信息,同时需要告知Kubelet集群上的API Server位置;
- Kubelet在启动时通过API Server注册节点信息,并定时向API Server发送节点新消息,API Server在接收到新消息后,将信息写入etcd。
pod 管理
-
获取Pod清单:
-
文件:通过启动参数–config指定的配置目录下的文件(默认为/etc/Kubernetes/manifests/)。该- - 文件每20秒会重新检查一次,这个时间间隔是可配置的。
-
HTTP endpoint(URL):通过启动参数–manifest-url设置。每20秒会检查一次这个端点,这个时间间隔也是可配置的。
-
API Server:通过API Server监听etcd目录,同步Pod清单。
-
HTTP server:kubelet侦听HTTP请求,并响应简单的API以提交新的Pod清单。
pod 启动流程
kubelet 启动pod的流程
4 CRI
容器运行时(Container Runtime)运行于 Kubernetes(k8s)集群的每个节点中,负责容器的整个生命周期。其中,Docker 是目前应用最广泛的。随着容器云的发展,越来越多的容器运行时涌现。为解决这些容器运行时与 Kubernetes 的集成问题,在 Kubernetes 1.5 版本中,社区推出了 CRI(容器运行时接口,Container Runtime Interface),以支持更多容器运行时。
CRI(容器运行时接口)
- 是 Kubernetes 定义的一组 gRPC 服务。kubelet 作为客户端,基于 gRPC 框架,通过套接字(Socket)与容器运行时通信。它包含两类服务:
镜像服务(Image Service):提供镜像下载、检查和删除的远程过程调用。
运行时服务(Runtime Service):包含用于管理容器生命周期,以及与容器交互(如 exec、attach、port-forward)的远程过程调用。
运行时的层级
Dockershim、containerd 和 CRI-O 均为遵循 CRI 的容器运行时,我们将其称为 高层级运行时(High-level Runtime)。
OCI(开放容器计划,Open Container Initiative)定义了创建容器的格式与运行时的开源行业标准,涵盖镜像规范(Image Specification)和运行时规范(Runtime Specification)。
镜像规范明确了 OCI 镜像的标准。高层级运行时会下载 OCI 镜像,并将其解压为 OCI 运行时文件系统包(filesystem bundle)。
运行时规范则描述了如何通过 OCI 运行时文件系统包运行容器程序,同时定义其配置、运行环境及生命周期。例如,为新容器设置命名空间(namespaces)、控制组(cgroups),以及挂载根文件系统等操作,均在该规范中定义。其参考实现之一是 runC,我们称其为 低层级运行时(Low-level Runtime)。除 runC 外,还有许多遵循 OCI 标准的运行时,如 kata-runtime。
Docker 内部实现容器运行时功能的核心组件是 containerd,后来 containerd 可直接通过 CRI 与 kubelet 对接,独立用于 Kubernetes。相较于 Docker,containerd 减少了 Dockerd 和 Docker-shim 等处理模块,优化了存储驱动支持,在容器创建、启动、停止、删除及镜像拉取上具备性能优势,且简化架构便于维护。尽管 Docker 有不少 containerd 不具备的功能(如支持 zfs 存储驱动、日志大小和文件限制,基于 overlayfs2 存储驱动时通过 xfs_quota 限制容器可写层大小等),但 containerd 已基本满足容器管理需求,使用它作为运行时的场景也越来越多
开源运行时比较
Docker 的多层封装和调用,导致其在可维护性上略逊一筹,增加了线上问题的定位难度;几乎除了重启 Docker,我们就毫无他法了。
containerd 和 CRI-O 的方案比起 Docker 简洁很多。
docker 和 containerd的区别
containerd在各个方面表现良好,除了启动容器
运行时有劣势对比
功能性:containerd 和 CRI-O 都符合 CRI 和 OCI 的标准。
稳定性:containerd 略胜一筹。
性能:containerd 胜出。
containerd | CRI-O | 备注 | |
---|---|---|---|
性能 | 更优 | 优 | |
功能 | 优 | 优 | CRI与OCI兼容 |
稳定性 | 稳定 | 未知 |
5 CNI
Kubernetes 网络模型设计的基础原则是:
- 所有的 Pod 能够不通过 NAT 就能相互访问。
- 所有的节点能够不通过 NAT 就能相互访问。
- 容器内看见的 IP 地址和外部组件看到的容器 IP 是一样的。
在 Kubernetes 的集群里,IP 地址以 Pod 为单位分配,每个 Pod 拥有独立 IP 地址。一个 Pod 内部的所有容器共享宿主机上的一个网络命名空间(网络栈),包括 IP 地址、网络设备、配置等。因此,Pod 内所有容器能通过 localhost:port
连接对方。Kubernetes 提供轻量通用的容器网络接口 CNI(Container Network Interface),专门用于设置和删除容器的网络连通性,容器运行时通过 CNI 调用网络插件完成容器网络设置。
CNI插件分类和常见插件
- IPAM:IP 地址分配
- 主插件:网卡设置
- bridge:创建一个网桥,并把主机端口和容器端口插入网桥
- ipvlan:为容器添加 ipvlan 网口
- loopback:设置 loopback 网口
- Meta:附加功能
- portmap:设置主机端口和容器端口映射
- bandwidth:利用 Linux Traffic Control 限流
- firewall:通过 iptables 或 firewalld 为容器设置防火墙规则
链接:https://github.com/containernetworking/plugins
CNI 插件运行机制
容器运行时在启动时会从 CNI 的配置目录中读取 JSON 格式的配置文件,文件后缀为“.conf”“.conflist”“.json”。若配置目录含多个文件,通常以名字排序选用第一个作为默认网络配置,加载其中指定的 CNI 插件名称和配置参数。
CNI 的运行机制
关于容器网络管理,容器运行时一般需配置 --cni-bin-dir
和 --cni-conf-dir
两个参数。特殊情况:当 kubelet 内置的 Docker 作为容器运行时,由 kubelet 查找 CNI 插件并为容器设置网络,此时这两个参数配置在 kubelet 处:
cni-bin-dir
:网络插件可执行文件目录,默认/opt/cni/bin
。cni-conf-dir
:网络插件配置文件目录,默认/etc/cni/net.d
。
CNI插件的设计考量
- 容器运行时必须在调用任何插件之前为容器创建一个新的网络命名空间。
- 容器运行时必须决定这个容器属于哪些网络,针对每个网络,哪些插件必须要执行。
- 容器运行时必须加载配置文件,并确定设置网络时哪些插件必须被执行。
- 网络配置采用 JSON 格式,可以很容易地存储在文件中。
- 容器运行时必须按顺序执行配置文件里相应的插件。
- 在完成容器生命周期后,容器运行时必须按照与执行添加容器相反的顺序执行插件,以便将容器与网络断开连接。
- 容器运行时被同一容器调用时不能并行操作,但被不同的容器调用时,允许并行操作。
- 容器运行时针对一个容器必须按顺序执行 ADD 和 DEL 操作,ADD 后面总是跟着相应的 DEL。DEL 可能跟着额外的 DEL,插件应该允许处理多个 DEL。
- 容器必须由 ContainerID 来唯一标识,需要存储状态的插件需要使用网络名称、容器 ID 和网络接口组成的主 key 用于索引。
- 容器运行时针对同一个网络、同一个容器、同一个网络接口,不能连续调用两次 ADD 命令。
打通主机层网络
除 CNI 插件外,Kubernetes 还需标准的 CNI 插件 lo
(最低版本 0.2.0)。网络插件除支持设置和清理 Pod 网络接口,还需支持 Iptables。若 Kube-proxy 工作在 Iptables 模式,网络插件需确保容器流量通过 Iptables 转发。例如:
- 若网络插件将容器连接到 Linux 网桥,必须通过
sysctl
将net/bridge/bridge-nf-call-iptables
参数设为 1,使网桥上数据包遍历 Iptables 规则。 - 若插件不使用 Linux 网桥(如类似 Open vSwitch 等机制的插件),则需确保容器流量路由设置正确。
CNI Plugin
- ContainerNetworking 组维护的插件:
- 网络接口创建:bridge、ipvlan、loopback、macvlan、ptp、host-device 等。
- IP 地址分配:DHCP、host-local、static 等。
- 其他:Flannel、tunning、portmap、firewall 等。
- 第三方网络策略插件:社区还有 Calico、Cilium、Weave 等,多样的选项使用户能根据需求和部署环境选择 CNI 插件,并在需求变化时快速切换方案。
Flannel
Flannel 是由 CoreOS 开发的项目,属于早期入门级 CNI 插件,具备简单易用的特点。
- 存储与部署:借助 Kubernetes 集群现有的 etcd 集群存储状态信息,无需专用数据存储,仅需在每个节点运行
flanneld
守护进程。 - IP 分配:为每个节点分配子网,用于给节点上的 Pod 分配 IP 地址。
- 通信机制:
- 同一主机内的 Pod 通过网桥通信。
- 不同主机的 Pod 流量经
flanneld
封装在 UDP 数据包中,路由至目标地址。
- 封装技术:默认及推荐使用 VxLAN 封装,优势是性能好、人为干预少,不足是流量跟踪难度高。
Calico
Calico 以性能、灵活性和网络策略著称,不仅实现主机与 Pod 间的网络连接,还涉及网络安全性与策略管理:
- 通信机制:
- 同网段:基于第 3 层,通过 BGP 路由协议在主机间路由数据包,无需额外封装层。
- 跨网段:基于 IPinIP,利用虚拟网卡设备
tunl0
,用一个 IP 数据包封装另一个 IP 数据包,外层 IP 数据包头的源地址为隧道入口设备 IP,目标地址为隧道出口设备 IP。
- 网络策略:使用 ACLs 协议和 kube-proxy 创建 iptables 过滤规则,实现容器网络隔离,是其最受欢迎的功能之一。
- 集成能力:可与服务网格 Istio 集成,在服务网格层和网络基础结构层解释并实施集群工作负载策略,支持配置规则控制 Pod 流量收发,提升安全性与网络控制。
- 架构优势:采用完全分布式横向扩展结构,便于开发人员和管理员快速、平稳扩展部署规模,适合对性能和网络策略要求高的环境。
- Calico 初始化
initContainers:
- command:
- /opt/cni/bin/install
env:
- name: CNI_CONF_NAME
value: 10-calico.conflist
- name: SLEEP
value: "false"
- name: CNI_NET_DIR
value: /etc/cni/net.d
- name: CNI_NETWORK_CONFIG
valueFrom:
configMapKeyRef:
key: config
name: cni-config
- name: KUBERNETES_SERVICE_HOST
value: 10.96.0.1
- name: KUBERNETES_SERVICE_PORT
value: "443"
image: docker.io/calico/cni:v3.20.1
imagePullPolicy: IfNotPresent
name: install-cni
- calico 配置一览
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"datastore_type": "kubernetes",
"mtu": 0,
"nodename_file_optional": false,
"log_level": "Info",
"log_file_path": "/var/log/calico/cni/cni.log",
"ipam": { "type": "calico-ipam", "assign_ipv4": "true", "assign_ipv6": "false" },
"container_settings": {
"allow_ip_forwarding": false
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"k8s_api_root": "https://10.96.0.1:443",
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
},
{"type": "portmap", "snat": true, "capabilities": {"portMappings": true}}
]
}
- calico vxlan
IPPool
IPPool 用于定义集群的预定义 IP 段,示例内容如下:
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
blockSize: 26
cidr: 192.168.0.0/16
ipipMode: Never
natOutgoing: true
nodeSelector: all()
vxlanMode: CrossSubnet
IPAMBlock
IPAMBlock 用于定义每个主机预分配的 IP 段,内容提取如下:
apiVersion: crd.projectcalico.org/v1
kind: IPAMBlock
metadata:
annotations:
name: 192-168-119-64-26
spec:
affinity: host:cadmin
allocations:
- null
- 0
- null
- 1
- 2
- 3
attributes:
- handle_id: vxlan-tunnel-addr-cadmin
secondary:
- node: cadmin
type: vxlanTunnelAddress
handle_id: k8s-pod-network.6680d3883c6150e75ffbd031f6c689a97a5be0f260c6442b2bb46b567c2ca40
secondary:
namespace: calico-apiserver
node: cadmin
pod: calico-apiserver-77dffffcdf-g2tcx
timestamp: 2021-09-30 09:46:57.4551016 +0000 UTC
- handle_id: k8s-pod-network.b10d7702bf334fc55a5e399a731ab3201ea9990a1e3bc79894abddd712646699
secondary:
namespace: calico-system
node: cadmin
pod: calico-kube-controllers-bdd5f97c5-554z5
timestamp: 2021-09-30 09:46:57.502331346 +0000 UTC
- IPAMHandle
apiVersion: crd.projectcalico.org/v1
kind: IPAMHandle
metadata:
name: k8s-pod-network.8d75b941d85c4998016b72c83f9c5a75512c82c052357daf0ec8e67365635d93
spec:
block: 192.168.119.64/26: 1
deleted: false
handleID: k8s-pod-network.8d75b941d85c4998016b72c83f9c5a75512c82c052357daf0ec8e67365635d93
创建Pod并查看IP配置情况
容器 namespace
# 查看IP配置
nsenter -t 720702 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.1/8 scope host lo
valid_lft forever preferred_lft forever
3: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether f2:86:d2:4f:1f:30 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.119.84/32 brd 192.168.119.84 scope global eth0
valid_lft forever preferred_lft forever
# 查看路由
nsenter -t 720702 -n ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
# 查看ARP表
nsenter -t 720702 -n arp
Address HWtype HWaddress Flags Mask iface
169.254.1.1 ether ee:ee:ee:ee:ee:ee C eth0
10.0.2.15 ether ee:ee:ee:ee:ee:ee C eth0
主机 Namespace
# 查看网络接口
ip link
27: cali23a582ef038@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 9
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
# 查看路由
ip route
192.168.119.84 dev cali23a582ef038 scope link
CNI plugin对比
CSI
容器运行时
除外挂存储卷外,容器启动后,运行时所需文件系统性能直接影响容器性能。
早期的 Docker 采用 Device Mapper 作为容器运行时存储驱动,因 OverlayFS 尚未合并进 Kernel。
目前 Docker 和 containerd 均默认以 OverlayFS 作为运行时存储驱动。
OverlayFS 性能表现优异,与 DeviceMapper 相比提升约 20%,且与操作主机文件性能几乎一致
存储卷插件管理
Kubernetes 支持以插件形式实现对不同存储的支持和扩展,基于以下三种方式:
in-tree 插件
- Kubernetes 社区已不再接受新的 in-tree 存储插件,新存储需通过 out-of-tree 插件支持。
out-of-tree FlexVolume 插件
- 交互方式:Kubernetes 通过调用计算节点本地可执行文件与存储插件交互。
- 权限与工具:需宿主机用
root
权限安装插件驱动,且存储驱动需宿主机安装attach
、mount
等工具,具备root
访问权限。
out-of-tree CSI 插件
- 图中仅呈现标题,未展开具体说明内容。
out-of-tree CSI 插件
- 交互方式:CSI 通过 RPC 与存储驱动进行交互。
- Kubernetes 模块定义:设计 CSI 时,Kubernetes 对存储驱动打包部署要求少,主要定义以下模块:
kube-controller-manager
- 功能:感知 CSI 驱动存在。
- 交互特点:
- 主控模块通过 Unix domain socket(非 CSI 驱动)或其他方式直接交互。
- 仅与 Kubernetes 相关 API 交互。
- 操作依赖:若 CSI 驱动涉及依赖 Kubernetes API 的操作(如卷创建、卷 attach、卷快照等),需在驱动内通过 Kubernetes API 触发对应 CSI 操作。
kubelet
- 功能:与 CSI 驱动交互。
- 交互流程:
- 通过 Unix domain socket 向 CSI 驱动发起 CSI 调用(如
NodeStageVolume
、NodePublishVolume
等),再执行 mount 卷和 umount 卷操作。
- 通过 Unix domain socket 向 CSI 驱动发起 CSI 调用(如
- 注册机制:
- 通过插件注册机制发现 CSI 驱动及交互所用的 Unix Domain Socket。
- 所有部署在 Kubernetes 集群中的 CSI 驱动,均需通过 kubelet 的插件注册机制完成注册。
CSI驱动
CSI 的驱动一般包含 external-attacher、external-provisioner、external-resizer、 external-snapshotter、node-driver-register、CSI driver 等模块,可以根据实际的存储类型和需求进行不同方式的部署。
临时存储
常见的临时存储主要是emptyDir卷。
- 数据生命周期:emptyDir卷最初为空,Pod从节点删除时,卷中数据会永久删除;Pod的容器退出再重启,卷内数据不会丢失。
- 存储介质:默认存储在节点支持的介质上,如本地磁盘或网络存储。也可通过设置emptyDir.medium为“Memory”使用tmpfs,数据存于内存,速度快,但节点重启数据清除;存于磁盘则重启后数据保留。使用tmpfs的内存计入容器内存总量,受Cgroup限制。
- 设计初衷与使用建议:主要用于充当应用缓存空间或存储中间数据,方便快速恢复。但不建议所有有此类需求的用户都使用,因其空间在系统根盘且被容器共享,磁盘使用率高时可能触发Pod驱逐操作,影响业务稳定 ,需根据业务实际特点判断是否使用。
半持久化存储
常见的半持久化存储主要是hostPath卷。
- 功能与适用场景:能将主机节点文件系统上的文件或目录挂载到指定Pod中。普通用户一般不需要,对于需获取节点系统信息的Pod很必要。
- 用法举例:
- 若Pod需获取节点上所有Pod的log,可通过hostPath访问所有Pod的stdout输出存储目录,如/var/log/pods路径。
- 若Pod需统计系统相关信息,可通过hostPath访问系统的/proc目录。
- 使用要点:使用时除设置必需的path属性外,还可选择性为hostPath卷指定类型,支持目录、字符设备、块设备等类型。
hostPath卷需要注意
- 若多个Pod使用同一个目录,因调度到不同节点,目录内容可能不同。
- Kubernetes调度时不会考虑hostPath使用的资源。
- Pod删除后,若未特殊处理,hostPath上的数据会留在节点上,占用磁盘空间 。
持久化存储
- 概念引入:支持持久化的存储是分布式系统必备特性。Kubernetes引入StorageClass、Volume、PVC(Persistent Volume Claim)、PV(Persitent Volume)概念,使存储独立于Pod生命周期管理。
- 支持类型:Kubernetes目前支持多种主流的块存储和文件存储,如awsElasticBlockStore、azureDisk、cinder、NFS、cephfs、iscsi等,总体可分为网络存储和本地存储两类。
以下是提取的内容:
StorageClass
- 概念与构成:StorageClass 用于指示存储类型,不同存储类型可通过它为用户服务,主要包含存储插件
provisioner
、卷的创建和mount
参数等字段。 - 示例代码:
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "false"
name: rook - ceph - block
parameters:
clusterID: rook - ceph
csi.storage.k8s.io/controller - expand - secret - name: rook - csi - rbd - provisioner
csi.storage.k8s.io/controller - expand - secret - namespace: rook - ceph
csi.storage.k8s.io/fstype: ext4
csi.storage.k8s.io/node - stage - secret - name: rook - csi - rbd - node
csi.storage.k8s.io/node - stage - secret - namespace: rook - ceph
csi.storage.k8s.io/provisioner - secret - name: rook - csi - rbd - provisioner
csi.storage.k8s.io/provisioner - secret - namespace: rook - ceph
imageFeatures: layering
imageFormat: "2"
pool: replicapool
provisioner: rook - ceph.rbd.csi.ceph.com
reclaimPolicy: Delete
volumeBindingMode: Immediate
PVC
由用户创建,代表用户对存储需求的声明,主要包含需要的存储大小、存储卷的访问模式、StorageClass 等类型,其中存储卷的访问模式必须与存储的类型一致。
简称 | 全称 | 说明 |
---|---|---|
RWO | ReadWriteOnce | 该卷只能在一个节点上被 mount,属性为可读可写 |
ROX | ReadOnlyMany | 该卷可以在不同的节点上被 mount,属性为只读 |
RWX | ReadWriteMany | 该卷可以在不同的节点上被 mount,属性为可读可写 |
PV
由集群管理员提前创建,或者根据PVC的申请需求动态地创建,它代表系统后端的真实的存储空间,可以称之为卷空间。
存储对象关系
用户通过创建 PVC 来申请存储。控制器通过 PVC 的 StorageClass 和请求的大小声明来存储后端创建卷,进而创建 PV,Pod 通过指定 PVC 来引用存储。
生产实践经验分享
- StorageClass设置:不同介质类型磁盘,需设置不同StorageClass以便用户区分,且应设置磁盘介质类型,让用户了解存储属性。
- 本地存储PV部署:本地存储的PV静态部署模式下,每个物理磁盘尽量只创建一个PV,避免分区间I/O干扰。
- 磁盘检测配合:本地存储需配合磁盘检测。集群规模化后磁盘损坏频发,检测到磁盘损坏等问题,需对节点磁盘和本地存储PV 进行处理,如触发告警、自动cordon节点、自动通知用户。
- 磁盘管理要点:本地存储节点的磁盘管理要灵活且自动化,节点磁盘信息可统一集中管理。在local-volume-provisioner中增加部署逻辑,容器运行时拉取磁盘信息,依此对磁盘格式化或加入VG,形成本地存储支持的自动化闭环。
生产实践经验分享
- StorageClass设置:针对不同介质类型磁盘,设置不同StorageClass 以便用户区分,同时在其中明确磁盘介质类型,展示存储属性。
- 本地存储PV部署:本地存储PV静态部署时,每个物理磁盘尽量只创建一个PV,防止分区间I/O干扰。
- 磁盘检测与处理:本地存储要结合磁盘检测。集群规模扩大后磁盘损坏可能增多,检测到磁盘损坏等问题,需对节点磁盘及对应本地存储PV 采取措施,如触发告警、自动隔离节点(cordon)、通知用户。
- 磁盘管理自动化:本地存储节点的磁盘管理需灵活且自动化。将节点磁盘信息统一集中管理,在local-volume-provisioner中添加部署逻辑,容器运行时获取本地存储磁盘信息,如设备路径等,依此对磁盘格式化或加入虚拟组(VG),实现本地存储支持的自动化闭环。
独占的 Local Volume
- 创建PV:通过local-volume-provisioner DaemonSet创建本地存储的PV。
- 创建PVC:用户创建PVC,此时其处于pending状态,kube-controller-manager不会对该PVC做任何操作。
- 创建Pod:用户创建Pod。
- Pod挑选节点:kube-scheduler开始调度Pod,依据PVC的resources.request.storage和volumeMode选择满足条件的PV,并为Pod选择合适节点。
- 更新PV:kube-scheduler将PV的pv.Spec.claimRef设置为对应的PVC,并设置annotation pv.kubernetes.io/bound-by-controller的值为“yes”。
- PVC和PV绑定:pv_controller同步PVC和PV的状态,并将二者绑定。
- 监听PVC对象:kube-scheduler等待PVC的状态变为Bound状态。
- Pod调度到节点:若PVC状态变为Bound则调度成功;若一直处于pending状态,超时后会再次调度。
- Mount卷启动容器:kubelet监听到Pod调度到节点上,对本地存储进行mount操作并启动容器。
Dynamic Local Volume
- CSI驱动要求:CSI驱动需汇报节点上相关存储的资源信息,用于调度,但因机器厂家不同,汇报方式各异。
- 示例情况:部分厂家的机器节点存在NVMe、SSD、HDD等多种存储介质,希望分别汇报这些介质信息。
- 现状与挑战:该需求与其他存储类型的CSI驱动接口需求不同,目前在如何汇报节点存储信息以及如何将其应用于调度方面,尚无统一意见。
- 应对措施:集群管理员可根据节点存储实际情况,对开源CSI驱动和调度进行代码修改后再部署使用。
Local Dynamic 的挂载流程
- 创建PVC:用户创建PVC,此时PVC处于pending状态。
- 创建Pod:用户创建Pod。
- Pod选择节点:kube-scheduler依据PVC的pvc.spec.resources.request.storage等条件,调度选择满足要求的节点。
- 更新PVC:选定节点后,kube-scheduler给PVC添加包含节点信息的annotation(volume.kubernetes.io/selected-node: <节点名字>)。
- 创建卷:运行在节点上的external-provisioner容器监听到PVC带有该节点相关的annotation,向对应的CSI驱动申请分配卷。
- 创建PV:PVC申请到所需存储空间后,external-provisioner创建PV,并将PV的pv.Spec.claimRef设置为对应的PVC。
- PVC和PV绑定:kube-controller-manager将PVC和PV绑定,并把状态修改为Bound。
- 监听PVC状态:kube-scheduler等待PVC变为Bound状态。
- Pod调度到节点:当PVC状态为Bound时,Pod调度成功;若一直处于Pending状态,超时后会重新调度。
- Mount卷:kubelet监听到Pod调度到节点,对本地存储进行mount操作。
- 启动容器:启动容器。
Local Dynamic 的挑战
- 若将磁盘空间作为存储池(如LVM)进行动态分配,分配出的逻辑卷空间在使用时,可能因底层物理卷相同,而受到其他逻辑卷的I/O干扰。
- 若PV后端的磁盘空间是独立的物理磁盘,则I/O不会受到干扰 。
生产实践经验分享
- StorageClass设置:不同介质类型磁盘需设置不同StorageClass 以便用户区分,且应设置磁盘介质类型,展示存储属性。
- 本地存储PV部署:本地存储的PV静态部署时,每个物理磁盘尽量只创建一个PV,避免分区间I/O干扰。
- 磁盘检测与处理:本地存储要配合磁盘检测。集群规模扩大后磁盘损坏可能增多,检测到磁盘损坏等问题,需对节点磁盘及对应本地存储PV 采取触发告警、自动隔离节点(cordon)、通知用户等措施。
- 磁盘管理自动化:本地存储节点的磁盘管理需灵活且自动化。将节点磁盘信息统一集中管理,在local-volume-provisioner中添加部署逻辑,容器运行时获取本地存储磁盘信息,如设备路径等,依此对磁盘格式化或加入虚拟组(VG),实现本地存储支持的自动化闭环。
Rook
Rook是云原生环境下的开源分布式存储编排系统,支持Ceph、NFS、EdgeFS、Cassandra、CockroachDB等存储系统。具备自动管理、自动扩容、自动修复功能,支持自动部署、启动、配置、分配、扩容/缩容、升级、迁移、灾难恢复、监控及资源管理。
- 架构
Rook Operator
- 基本属性:Rook Operator是Rook的核心组件,以deployment形式存在。
- 资源创建:利用Kubernetes的controller-runtime框架实现CRD(自定义资源定义),接收Kubernetes创建资源的请求,进而创建集群、pool、块存储服务、文件存储服务等相关资源。
- 存储监控:监控存储守护进程,保障存储集群健康;监听Rook Discovers收集的存储磁盘设备,并创建相应服务(如Ceph中的OSD )。
Rook Discover
- 部署与功能:Rook Discover以DaemonSet形式部署在所有存储机上,用于检测挂接到存储节点的存储设备,记录符合要求的设备,以便Rook Operator基于这些设备创建相应服务。
- 操作命令:
- 发现设备:
$ lsblk --all --noheadings --list --output KNAME
$ lsblk /dev/vdd --bytes --nodeps --pairs --paths --output SIZE,ROTA,RO,TYPE,PKNAME,NAME,KNAME
$ udevadm info --query=property /dev/vdd
$ lsblk --noheadings --pairs /dev/vdd
- 发现Ceph清单:
$ ceph-volume inventory --format json
,若设备有ceph清单,device.CephVolumeData = CVData
- 将设备信息存入每个节点的ConfigMap :
put device info into configmap per node
- 发现设备:
CSIDriver发现
- 驱动发现方式:若CSI驱动创建CSIDriver对象,Kubernetes用户可通过
get CSIDriver
命令发现它们。 - CSI对象特点:
- 具备自定义的Kubernetes逻辑。
- 可自定义支持Kubernetes对存储卷的哪些操作。
Provisioner
- 组件性质:CSI external - provisioner是监控Kubernetes PVC对象的Sidecar容器。
- 工作流程:用户创建PVC后,Kubernetes监测PVC对应的StorageClass,若其中的provisioner与某插件匹配,该容器通过CSI Endpoint(通常是unix socket)调用CreateVolume方法。若调用成功,Provisioner sidecar创建Kubernetes PV对象。