04-Kubernetes控制平面组件:调度器和控制器

一、调度器

  1、调度器简述

    kube-scheduler 负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点(更新 Pod 的 NodeName 字段)。

    调度器需要充分考虑诸多的因素:

    公平调度:要保证先到先得,但是对于有的调度来说,紧急程度比较高,因此还会做分优先级,对于优先级高的优先处理,但是在同一优先级内,保证调度公平

    资源高效利用:对于一个调度,可能有多个节点都满足需求,但是需要充分考虑 CPU、内存、磁盘等多方面因素,提高资源的利用率

    QoS:对于业务来说,有时候会超额申请资源,这样资源的利用率就会降低,为了提高资源利用率,就允许超卖,既保证最低需求,又可以提高资源利用率。

    affinity 和 anti-affinity:亲和度,例如 pod 要指定调度到哪个节点,或者指定不调度到哪个节点

    数据本地化(data locality):例如拉去镜像时,为了不多占用资源,以及快速创建 pod,会看哪个节点上已经存在镜像,就优先调度到该节点。

    内部负载干扰(inter-workload interference);

    deadlines。

    一般调度器分为两个阶段,filter 和 score,filter 是做过滤,过滤不符合条件的节点,score 是打分,通过多个维度打分并排序,排第一个的就是最合适被调度的节点。kube-scheduler 调度也分为两个阶段,分为 predicate 和 priority,作用和 filter、score 一致。predicate:过滤不符合条件的节点;priority:优先级排序,选择优先级最高的节点。

    predicate 策略:

      PodFitsHostPorts:检查是否有 Host Ports 冲突,例如需要主机的某个端口,如果当前节点的该端口已经被占用,那么就会把该节点剔除。

      PodFitsPorts:同 PodFitsHostPorts。

      PodFitsResources:检查 Node 的资源是否充足,包括允许的 Pod 数量、CPU、 内存、GPU 个数以及其他的 OpaquelntResources。

      HostName:检查 pod.Spec.NodeName 是否与候选节点一致。

      MatchNodeSelector:检查候选节点的 pod.Spec.NodeSelector 是否匹配。

      NoVolumeZoneConflict:检查 volume zone 是否冲突。

      MatchlnterPodAffinity:检查是否匹配 Pod 的亲和性要求。

      NoDiskConflict:检查是否存在 Volume 冲突,仅限于 GCEPD、AWSEBS、 Ceph RBD 以及 iSCSI

      PodToleratesNodeTaints:检查 Pod 是否容忍 Node Taints

      CheckNodeMemoryPressure:检查 Pod 是否可以调度到 Memorypressure 的 节点上。

      CheckNodeDiskPressure:检查 Pod 是否可以调度到 DiskPressure 的节点上。

      NoVolumeNodeConflict:检查节点是否满足 Pod 所引用的 Volume 的条件。

      还有很多其他策略,同时也支持自定义策略。

    Predicates plugin 工作原理:predicate 策略相当于 predicate 插件,每个策略都是一个插件,其会经过一个个的插件进行筛选,剔除不满足需求的节点,留下满足需求的节点。

        

     priority策略:

      Selectorspread Priority:优先减少节点上属于同一个 Service 或 Replication Controller 的 Pod 数量。

      InterPodAffinityPriority:优先将 Pod 调度到相同的拓扑上(如同一个节点、 Rack、Zone 等)。

      LeastRequestedPriority:优先调度到请求资源少的节点上。

      BalancedResourceAllocation:优先平衡各节点的资源使用。

      NodePreferAvoidPodsPriority:alpha.kubernetes.io/preferAvoidPods 字段判断,权重为10000,避免其他优先级策略的影响。

      NodeAffinityPriority:优先调度到匹配 Node Affinity 的节点上。

      TaintTolerationPriority:优先调度到匹配 TaintToleration 的节点上。

      ServicespreadingPriority:尽量将同一个 service 的 Pod 分布到不同节点上,已经被 SelectorSpreadPriority 替代(默认未使用)。

      EqualPriority:将所有节点的优先级设置为1(默认未使用)。

      ImageLocalityPriority:尽量将使用大镜像的容器调度到已经下拉了该镜像的节 点上(默认未使用)。

      MostRequestedPriority:尽量调度到已经使用过的 Node,特别适用于 cluster-autoscaler(默认未使用)。

  2、调度资源需求

  (1)调度时可以设置对 CPU、内存、磁盘的需求。

    CPU:可以配置 request 和 limit 两个参数项,request 调度的最低要求,limit 是做运行时资源限制的。

      requests:Kubernetes 调度 Pod 时会判断当前节点正在运行的 Pod 的 CPU Request 的总和,再加上当前调度 Pod 的 CPU request,计算其是否超过节点的 CPU 的可分配资源。

      limits:配置 cgroup 以限制资源上限。

    内存:可以配置 request 和 limit 两个参数项,与 CPU 一致。

      requests:判断节点的剩余内存是否满足 Pod 的内存请求量,以确定是否可以将 Pod 调度到该节点。

      limits:配置cgroup以限制资源上限。

    磁盘资源需求:

      容器临时存储 (ephemeralstorage) 包含日志和可写层数据,可以通过定义 Pod Spec 中的 limits.ephemeral-storage 和 requests.ephemeral-storage 来申请。Pod 调度完成后,计算节点对临时存储的限制不是基于 cgroup 的,而是由 kubelet 定时获取容器的日志和容器可写层的磁盘使用情况,如果超过限制,则会对 Pod 进行驱逐。

  (2)Init Container 的资源需求:

      当 kube-scheduler 调度带有多个 init 容器的 Pod 时,只计算 cpu.request 最多的 init 容器,而不是计算所有的 init 容器总和。由于多个 init 容器按顺序执行,并且执行完成立即退出,所以申请最多的资源 init 容器中的所需资源,即可满足所有 init 容器需求。kube-scheduler 在计算该节点被占用的资源时,init 容器的资源依然会被纳入计算。因为 init 容器在特定情况下可能会被再次执行,比如由于更换镜像而引起 Sandbox 重建时。

  3、把Pod调度到指定Node上:

    可以通过 nodeselector,node Affinity,podAffinity 以及 Taints 和 tolerations 等来将 Pod 调度到需要的 Node 上。也可以通过设置 nodeName 参数,将 Pod 调度到指定 Node 节点上。比如,使用 nodeselector,首先给 Node 加上标签:kubectl label nodes <your-node-name> disktype=ssd;接着,指定该 Pod 只想运行在带有 disktype=ssd 标签的 Node

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
nodeselector:
  disktype: ssd

  (1)nodeselector:首先给Node打上标签:kubectl label nodes node-01 disktype=ssd,然后在 daemonset 中指定定 nodeSelector disktype=ssd

spec:
  nodeSelector:
  disktype: ssd

  (2)nodeAffinity:

    nodeAffinity 是节点亲和性,目前支持两种:requiredDuringSchedulinglgnoredDuringExecution(强亲和性) 和 preferredDuringSchedulinglgnoredDuringExecution(弱亲和性),分别代表必须满足条件和优选条件,也就是如果不满足强亲和性,就不会被调度到该节点,如果不满足弱亲和性,只是被调度到该节点的概率变低。

    比如下面的例子代表调度到包含标签 Kubernetes.io/e2e-az-name 并且值为 e2e-az1 或 e2e-az2 的 Node 上,并且优选还带有标签 another-node-label-key=another-node-label-value 的 Node

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:
        -weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values: -another-node-label-value
      containers:
      - name: with-node-affinity
        image: gcr.io/google_containers/pause:2.0

  (3)podAffinity:

    podAffinity 是 pod 亲和性,基于 Pod 的标签来选择 Node,仅调度至满足条件 Pod 所在的 Node 上,支持 podAffinity(pod 亲和性) 和 podAntiAffinity(pod 非亲和性)。这个功能比较绕,以下面的例子为例:

    如果一个 “Node 所在 Zone 中包含至少一个带有 security=S1 标签且运行中的 Pod”,那么可以调度到该 Node,不调度到 “包含至少一个带有 security=S2 标签且运行中 Pod” 的 Node 上。

    podAffinity 示例

apiVersion: vl
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
  podAffinity:
    requiredDuringSchedulinglgnoredDuringExecution:
    - labelSelector:
      matchExpressions:
      - key: security
        operator: In
        values:
        - S1
    topologyKey: failure-domain.beta.kubernetes.io/zone
  podAntiAffinity:
    preferred Du ringscheduling Ignored Du ring Execution:
    - weight: 100
      podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S2
        topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: gcr.io/google_containers/pause:2.0

  (4)Taints 和 Tolerations

    Taints 和 Tolerations 就是用于保证 Pod 不被调度到不合适(资源划分)的 Node,其中 Taint 应用于 Node 上,而 Toleration 则应用于 Pod 上。

    对于不同的应用需要的资源可能不一样,有的应用需要内存,有的需要 GPU,如果在初期没有做资源类型的设定,在后期应用时,想将资源进行分类,让不属于该类资源的应用不跑在该节点;或者在初期就对节点进行分类,保证应用只运行在对应的节点上。或者同样的节点,但是为了隔离出来给不同的部门使用,也是需要进行资源隔离。

    这种隔离可以使用 label,但是 label 并不是强制的,打好 lable 之后别的人也可以使用,因此提供了 taints,也就是污点,一个节点被打上 Taints 之后,该节点会有一些行为(Effctive),那么 pod 就应该由对应的 Tolerations,如果没有,就会被节点做出对应的行为。

    那么在设置 Taints 时(一个 K V 对),还需要设置 Effctive,来表示如果 pod 的  Tolerations 不满足,要做什么样的处理,结果有三类,如下所示:

    目前支持的 Taint 类型:

      NoSchedule:如果新的 Pod 没有对应的 Tolerations ,就不会调度到该 Node 上,不影响正在运行的 Pod;

      PerferNoSchdule:soft 版的 NoSchedule,如果新的 Pod 没有对应的 Tolerations ,尽量不调度到该 Node 上;

      Noexecute:如果新的 Pod 没有对应的 Tolerations ,就不会调度到该 Node 上,并且删除 (evict) 已在运行的 Pod。Pod 可以增加一个时间 (tolerationSeconds),来设定多久之后进行删除。

    然而,当 Pod 的 Tolerations 匹配 Node 的所有 Taints 的时候,可以调度到该 Node;当 Pod 是已经运行的时候,也不会被删除 (evicted) 。另外对于 NoExecute,如果 Pod 增加了一个 tolerationSeconds,则会在该时间之后才删除 Pod。

    多租户Kubernetes集群-计算资源隔离:

      Kubernetes 集群一般是通用集群,可被所有用户共享,用户无需关心计算节点细节。 但有时会有隔离要求,例如公有云的用户要求完全隔离,应用不能和其他租户的应用部署在一起,这样就可以使用 Taints,给不同的租户设置不同的 Taints;另外 Kubernetes 本身也在使用 Taints 进行隔离(当一个节点不响应的时候,就会将该节点的 Taints 设置为 Unhealthey,同时将 Effctive 设置为 Noexecute,然后就会驱逐节点上的 pod,同时避免将任务调度到该节点)。

      该方案有还是由漏洞的,比如用户可以查看全局的节点设置的 Taints,然后偷偷给自己的 pod 设置 Tolerations,针对这种情况,如果是企业内部,可以通过规范管理,通过统计数据看谁占用了哪些 Node,然后告知对方不要这么使用,对于违规用户,批评教育为主;如果是公有云,需要设置不让用户不让用户 get 别人的 pod detail;

      另外对于用户会忘记打 tolerance,导致 Pod 无法调度,一直 pending 的情况,是新员工常犯的错误,通过聊天机器人的 Q&A 解决,另外通过 dashboard,能看到哪些用户的什么应用跑在哪些节点上

  (5)优先级调度:

    从v1.8开始,kube-scheduler 支持定义 Pod 的优先级,从而保证高优先级的 Pod 优先调度。开启方法为(v1.8 已默认开启):

      apiserver 配置 --feature-gates=PodPriority=true 和--runtime-config=scheduling.k8s.io/v1alpha1=true

      kube-scheduler 配置 -feature-gates=PodPriority=true

    在指定Pod的优先级之前需要先定义一个 Priorityclass (非namespace资源),首先要设置 优先级(value),值越大,优先级越高,然后设置是否全局配置(globalDefault),如果为 true,那么没有设置 Priorityclass 的 pod 的优先级都默认为该优先级,其实还有一个属性,就是可不可以抢占,如果设置的可以抢占,同时当前 pod 调度时,资源不够,就要驱逐比当前优先级低的 pod。

    设置 Priorityclass

apiVersion: vl
kind: Priorityclass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only/"

  为 Pod 设置 priority

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    priorityClassName: high-priority

   (6)多调度器

    如果默认的调度器不满足要求,还可以部署自定义的调度器。例如对于离线资源,可能一秒钟就要有几百个调度需求,那么基于 Event 的调度就不合适了,那么就需要有批调度器,例如腾讯的 TKE 调度器,华为的 Volcano 调度器等。另外,批调度器还有一些其他的需求,例如一次启动10个 pod,如果可以启动了,才去调度,如果资源只够调度8个 pod,就不去调度,因为起一部分 pod 没有意义。

    因此在整个集群中还可以同时运行多个调度器实例,通过 pod.Spec.schedulerName 来选择使用哪一个调度器(默认使用内置的调度器 default schduler)。

  (7)来自生产的一些经验:

    小集群:100个 node,并发创建 8000 个 Pod 的最大调度耗时大概是 2分钟左右,发生过 node删除后,scheduler cache 还有信息的情况,导致Pod调度失败。

    放大效应:当一个 node 出现问题所以 load 较小时,通常用户的 Pod 都会优先调度到该 node 上,导致用户所有创建的新 Pod 都失败的情况。

    应用爆炸:存在危险的用 户Pod (比如fork bomb),在调度到某个 node 以后,会因为打开文件句柄过多导致 node down 机,Pod 会被 evict 到其他节点,再对其他节点造成伤害,依次循环会导致整个 cluster 所有节点不可用。

    调度器可以说是运营过程中稳定性最好的组件之一,基本没有太大的维护 effort。

二、Controller Manager

  控制器的工作流程

        

  Informer的内部机制

        

  控制器的协同工作原理

        

  通用 Controller:

    Job Controller:处理 job。

    Pod AutoScaler:处理Pod的自动缩容/扩容。

    RelicaSet:依据 Replicaset Spec 创建 Pod

    Service Controller:为 LoadBalancer type 的 service 创建 LB VIP

    ServiceAccount Controller:确保 serviceaccount 在当前 namespace 存在。

    StatefulSet Controller:处理 statefulset 中的 Pod

    Volume Controller:依据 PV spec 创建 volume

    Resource quota Controller:在用户使用资源之后』更新状态。

    Namespace Controller:保证namespace删除时,该namespace下的所有资源都先被删除 Replication Controller:创建 RC 后/ 负责创建 Pod

    Node Controller:维护node状态,处理evict请求等。

    Daemon Controller:依据 damonset 创建 Pod

    Deployment Controller:依据 deployment spec 创]建 replicaset。

    Endpoint Controller:依据 service spec 创建 endpoint,依据 podip 更新 endpoint

    Garbage Collector:处理级联删除,比如删除deployment的同时删除replicaset以及Pod0 Cronjob Controller:处理 cronjob。

  Cloud Controller Manager:

    什么时候需要 cloud controller manager:

      Cloud Controller Manager § KubernetesL6 开始, 从 kube-controller-manager 中分离出来/ 主 要因为Cloud Controller Manager往往需要跟企业cloud做深度集成, release cycle跟 Kubernetes相对独立。

      与Kubernetes核心管理组件一起升级是一件费时费力的事。

    通常 cloud controller manager 需要:

      认证授权:企业cloud往往需要认证信息,Kubernetes要与Cloud API通信/需要获取cloud系 统里的 ServiceAccount;

      cloud controller manager本身作为一个用户态的component,需要在Kubernetes中有正确的 RBAC设置,获得资源操作权限;

      高可用:需要通过leader election来确保cloud controller manager高可用。

    Cloud Manager Controller的配置:

      Cloud Controller Manager是从老版本的API Server分离出来的。

        kube-apiserver 和 kube-controller-manager 中一定不能指定 cloud-provider,否则会加载内 置的 cloud controller manager

        kubelet 要酉己置--cloud-provider=external

      Cloud Controller Manager 主要支持:

        Node controller:访问cloud API,来更新node状态;在cloud删除该节点以后/从 Kubernetes 删除 node;

        Service controller:负责配置为 loadbalancer 类型的服务配置 LB VIP;

        Route Controller:在 cloud 环境配置路由;

        可以自定义任何需要的Cloud Controller。

    需要定制的Cloud Controller:

      Ingress controller;

      Service Controller;

      自主研发的controller,比如之前提到的:RBAC controller、Account controller。

  来自生产的经验:

    保护好 controller manager 的 kubeconfig:此kubeconfig拥有所有资源的所有操作权限'防止普通用户通过kubectl exec kube-controller- manager cat获取该文件;用户可能做任何你想象不到的操作’然后来找你support。

    Pod evict后IP发生变化,但endpoint中的address更新失败:分析stacktrace发现endpoint在更新LoadBalancer时调用gophercloud连接无响应'导致 endpoint worker线程全部卡死。

  确保scheduler和controller的高可用:

    Leader Election:Kubenetes 提供基于 configmap 和 endpoint 的 leader election 类库。Kubernetes采用leader election模式启动component后,会创建对应endpoint,并把当前的 leader 信息 annotate 至 endponit 上。

apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderldentity":"minikuben","leaseDurationSecondsn":15,"acquireTimen":"2018-04- 05T17:31:29Z","renewTime":"2018-04-07T07:18:39Z","leaderTransitions":0}'
  creationTimestamp: 2018-04-05T17:31:29Z
  name: kube-scheduler
  namespace: kube-system
  resourceversion: "138930"
  selfLink: /api/vl/namespaces/kube-system/endpoints/kube-scheduler
  uid: 2d12578d-38f7-11e8-8df0-0800275259e5
subsets: null

  Leader 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发送节点新消息,APIServer在接收到新消息后,将信息写入etcd

  Pod管理:

    获取Pod清单:

      文件:启动参数一config指定的配置目录下的文件(默认/etc/Kubernetes/manifests/)o该文 件每20秒重新检查一次(可配置)

      HTTP endpoint (URL):启动参数一manifest-url设置。每20秒检查一次这个端点(可配 直)。

      API Server:通过API Server监听etcd目录'同步Pod清单。

      HTTP Server: kubelet侦听HTTP请求’并响应简单的API以提交新的Pod清单。

  Pod启动流程

        

  kubelet启动Pod的流程:

        

四、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-0都是遵循CRI的容器运行时,我们称他们为高层级运行时 (High-level Runtime)。

    OCI (Open Container Initiative,开放容器计划)定义了创建容器的格式和运行时的开源行业标准, 包括镜像规范(Image Specification)和运行时规范(Runtime Specification)

    镜像规范定义了 OCI镜像的标准。高层级运行时将会下载一个OCI镜像,并把它解压成OCI运行时文 件系统包(filesystem bundle)

    运行时规范则描述了如何从OCI运行时文件系统包运行容器程序,并且定义它的配置、运行环境和生 命周期。如何为新容器设置命名空间(namepsaces)和控制组(cgroups) ,以及挂载根文件系统 等等操作/都是在这里定义的。它的一个参考实现是runCo我们称其为低层级运行时(Low-level R untime)。除runC以外,也有很多其他的运行时遵循OCI标准,例如kata-runtime

  CRI:

    容器运行时是真正起删和管理容器的组件。

    容器运行时可以分为高层和低层的运行时。

    高层运行时主要包括Docker, containerd和CRI-O,低层的运行时,包含了 rune, kata,以及 gVisor

    低层运行时kata和gVisor都还处于小规模落地或者实验阶段,其生态成熟度和使用案例都比较欠缺, 所以除非有特殊的需求/否则rune几乎是必然的选择。因此在对容器运行时的选择上,主要是聚焦于 上层运行时的选择。

    Docker内部关于容器运行时功能的核心组件是containerd,后来containerd也可直接和kubelet通 过CRI对接,独立在Kubernetes中使用。

    相对于Docker而言, containerd减少了 Docker所需的处理模块Dockerd和Docker-shim,并且对 Docker支持的存储驱动进行了优化/因此在容器的创建启动停止和删除/以及对镜像的拉取上,都具 有性能上的优势。

    架构的简化同时也带来了维护的便利。当然Docker也具有很多containerd不具有的功能,例如支持 zfs存储驱动,支持对日志的大小和文件限制,在以。verlayfs2做存储驱动的情况下,可以通过 xfs_quota来对容器的可写层进行大小限制等。

    尽管如此,containerd目前也基本上能够满足容器的众多管理需求,所以将它作为运行时的也越来越多。

            

 

   开源运行时的比较:

    Docker的多层封装和调用,导致其在可维护性上略逊一筹,增加了线上问题的定位难度;几乎除了重 启Docker,我们就毫无他法了。

    containerd和CRI-0的方案比起Docker简洁很多。

  Docker和containerd的差异细节:

        

  多种运行时性能比较:containerd ft各个方面都表现良好’除了启动容器这项。从总用时来看,containerd的用时还是要比 CRI-0要短的。

        

  运行时优劣对比

    功能性来讲’containerd和CRI-0都符合CRI和OCI的标准;

    在稳定性上,containerd略胜一筹;

    从性能上讲'containerd胜出。

        

五、CNI

  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为容器设置防火墙规则;

  CNI插件运行机制:

    容器运行时在启动时会从CNI的配置目录中读取JSON格式的配置文件,文件后缀为 conf、conflist、json,如果配置目录中包含多个文件,一般情况下,会以名字排序选用第一个配置文件作为默认的网络配置,并加载获取其中指定的CNI插件名称和配置参数。

        

  CNI的运行机制:

    关于容器网络管理,容器运行时一般需要配置两个参数-cni-bin-dir和-cni-conf-dir0有一种特殊 情况, kubelet内置的Docker作为容器运行时,是由kubelet来查找CNI插件的,运行插件来为容器 设置网络,这两个参数应该配置在kubelet处:

      cni-bin-dir:网络插件的可执行文件所在目录。默认是/opt/cni/bin。

      cni-conf-dir:网络插件的配置文件所在目录。默认是/etc/cni/net.d。

  CNI 插件设计考量:

    容器运行时必须在调用任何插件之前为容器创建一个新的网络命名空间。

    容器运行时必须决定这个容器属于哪些网络’针对每个网络’哪些插件必须 要执行。

    容器运行时必须加载配置文件,并确定设置网络时哪些插件必须被执行。

    网络配置采用JSON格式,可以很容易地存储在文件中。

    容器运行时必须按顺序执行配置文件里相应的插件。

    在完成容器生命周期后’容器运行时必须按照与执行添加容器相反的顺序执行插 件’以便将容器与网络断开连接。

    容器运行时被同一容器调用时不能并行操作,但被不同的容器调用时’允许并行 操伦。

    容器运行时针对一个容器必须按顺序执行ADD和DEL操作’ADD后面总是跟着 相应的DELO DEL可能跟着额外的DEL,插件应该允许处理多个DEL

    容器必须由ContainerlD来唯一标识,需要存储状态的插件需要使用网络名称、 容器ID和网络接口组成的主key用于索引。

    容器运行时针对同一个网络、同一个容器、同一个网络接口’不能连续调用两次 ADD命令。

  打通主机层网络:

    CNI插件外,Kubernetes还需要标准的CNI插件lo,最低版本为0.2.0版本。网络插件除支持设置和 清理Pod网络接口外/该插件还需要支持lptableso如果Kube-proxy I作在Iptables模式,网络插 件需要确保容器流量能使用Iptables转发。

    例如,如果网络插件将容器连接到Linux网桥,必须将net/bridge/bridge-nf-call-iptables参数 sysctl设置为1,网桥上数据包将遍历Iptables规则。

    如果插件不使用Linux桥接器(而是类似OpenvSwitch或其他某种机制的插件)/则应确保容器流 量被正确设置了路由。

  CNI Plugin:

    ContainerNetworking 组维护了一些 CNI 插件,包括网络接口创建的 bridge, ipvlan、loopback、 macvlan. ptp、host-device 等,IP 地址分配的 DHCP、host-local 和 static,其他的 FlanneL tunning、portmap、firewall等。

    社区还有些第三方网络策略方面的插件,例如Calico. Cilium和Weave等。可用选项的多样性意味 着大多数用户将能够找到适合其当前需求和部署环境的CNI插件,并在情况变化时迅捷转换解决方案。

  Flannel:

    Flannel是由CoreOS开发的项目,是CNI插件早期的入门产品,简单易用。

    Flannel使用Kubernetes集群的现有etcd集群来存储其状态信息,从而不必提供专用的数据存储, 只需要在每个节点上运行flanneld来守护进程。

    每个节点都被分配一个子网,为该节点上的Pod分配IP地址。

    同一主机内的Pod可以使用网桥进行通信,而不同主机上的Pod将通过flanneld将其流量封装在 UDP数据包中,以路由到适当的目的地。

    封装方式默认和推荐的方法是使用Vxlan、因为他具有良好的性能,并且比其他选项要少些人为干预,虽然使用 VxLAN之类的技术封装的解决效果会很好,但缺点是该过程使流量追踪变得困难。

        

  Calico:

    Calico以其性能、灵活性和网络策略而闻名,不仅涉及在主机和Pod之间提供网络连接,而且还涉及 网络安全性和策略管理。

    对于同网段通信,基于第3层, Calico使用BGP路由协议在主机之间路由数据包,使用BGP路由协 议也意味着数据包在主机之间移动时不需要包装在额外的封装层中。

    对于跨网段通信,基于IPinlP使用虚拟网卡设备tunlO,用一个IP数据包封装另一个IP数据包,外层 IP数据包头的源地址为隧道入口设备的IP地址,目标地址为隧道出口设备的IP地址。

    网络策略是Calico最受欢迎的功能之一,使用ACLs协议和kube-proxy来创建iptables过滤规则, 从而实现隔离容器网络的目的。

    此外,Calico还可以与服务网格Istio集成,在服务网格层和网络基础结构层上解释和实施集群中工作 负载的策略。这意味着你可以配置功能强大的规则,以描述Pod应该如何发送和接收流量,提高安全 性及加强对网络环境的控制。

    Calico属于完全分布式的横向扩展结构,允许开发人员和管理员快速和平稳地扩展部署规模。对于性 能和功能(如网络策略)要求高的环境, Calico是一个不错选择。

    Calico组件:

          

    Calico初始化:

#配置和CNI二进制文件由initContainer推送
- 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": Hk8s-pod-networkn,
  "cniVersion": "0.3.1",
  "plugins":[
    {
      "type": "calico",
      "datastore_type": "kubernetes",
      "mtu": 0,
      "nodename_file_optional": false,
      "logjever": "Info",
      "log_file_path": "var/log/calico/cni/cni.log",
      "ipam": {"type": "calico-ipam", "nassign_ipv4": "true", "nassign_ipv6": 'false*'},
      "container_settings": {
        "allow_ip_forwarding": false
        "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

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
  - handlejd: k8s-pod-network.6680d3883d6150e75ffbd031f86c689a97a5be0f260c6442b2bb46b567c2ca40
    secondary:
      namespace: calico-apiserver
      node: cadmin
      pod: calico-apiserver-77dffffcdf-g2tcx
      timestamp: 2021-09-30 09:46:57.45651816+0000 UTC
  - handle_id: k8s-pod-network.bl0d7702bf334fc55a5e399a731 ab3201 ea9990ale3bc79894abddd712646699
    secondary:
      namespace: calico-system
      node: cadmin
      pod: calico-kube-controllers-bdd5f97c5-554z5
      timestamp: 2021 -09-30 09:46:57.502351346 +0000 UTC

  IPAMHandle:IPAMHandle用来记录IP分配的具体细节

apiVersion: crd.projectcalico.org/v1
kind: IPAMHandle
metadata:
  name: k8s-pod-network.8d75b941 d85c4998016b72c83f9c5a75512c82c052357daf0ec8e67365635d93
spec:
  block: 192.168.119.64/26:1
  deleted: false
  handlelD: k8s-pod-network.8d75b941 d85c4998016b72c83f9c5a75512c82c052357daf0ec8e67365635d93

  创建Pod并查看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.0.1 /8 scope host lo
      valid_lft forever preferred_lft forever
  3: eth0@if27: <BROADCAST7MULTICAST,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 ethO
      valid_lft forever preferred_lft forever
nsenter -t 720702 -n ip r
default via 169.254.1.1 dev ethO
169.254.1.1 dev ethO scope link
nsenter -t 720702 -n arp
Address    HWtype HWaddress Flags Mask Iface
10.0.2.15    ether ee:ee:ee:ee:ee:ee C     ethO
169.254.1.1 ether ee:ee:ee:ee:ee:ee C    ethO

主机 namespace
ip link
27: cali23a582ef038@if3: <B ROAD CAST, M U LTI CAST, U P, LO WER_U P> 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插件:FlexVolume 是指 Kubernetes 通过调用计算节点的本地可执 行文件与存储插件进行交互 FlexVolume插件需要宿主机 用root权限来安装插件驱动,FlexVolume存储驱动需要宿 主机安装attach, mount等工 具,也需要具有root访问权限。

      out-of-tree CSI插件:

        CSI通过RPC与存储驱动进行交互。

        在设计CSI的时候/ Kubernetes对CSI存储驱动的打包和部署要求很少/主要定义了 Kubernetes的两个相关 模块:

          kube-controller-manager:

            kube-controller-manager模块用于感知CSI驱动存在。

            Kubernetes的主控模块通过Unix domain socket (而不是CSI驱动)或者其他方式进行直接地交互。

            Kubernetes的主控模块只与Kubernetes相关的API进行交互。

            因此CSI驱动若有依赖于Kubernetes API的操作,例如卷的创建、卷的attach.卷的快照等, 需要在CSI驱动里面通过Kubernetes的API,来触发相关的CSI操作。

        kubelet:

            kubelet模块用于与CSI驱动进行交互。

            kubelet 通过 Unix domain socket 向 CSI 驱动发起 CSI 调用(如 NodeStageVolume、 NodePublishVolume 等)/ 再发起 mount 卷和 umount 卷。

            kubelet通过插件注册机制发现CSI驱动及用于和CSI驱动交互的Unix Domain Socket。

            所有部署在Kubernetes集群中的CSI驱动都要通过kubelet的插件注册机制来注册自己。

  CSI驱动:

    CSI 的驱动一般包含 extemal-attacher、external-provisionerx external-resizer, external- snapshotter. node-driver-registerx CSI driver等模块'可以根据实际的存储类型和需求进行不同 方式的部署。

        

  临时存储:

    常见的临时存储主要就是emptyDir卷。

    emptyDir是一种经常被用户使用的卷类型,顾名思义,"卷〃最初是空的。当Pod从节点上删除时, emptyDir卷中的数据也会被永久删除。但当Pod的容器因为某些原因退出再重启时,emptyDir卷内 的数据并不会丢失。

    默认情况下,emptyDir卷存储在支持该节点所使用的存储介质上,可以是本地磁盘或网络存储。 emptyDir也可以通过将emptyDir.medium字段设置为“Memory”来通知Kubernetes为容器安装 tmpfs,此时数据被存储在内存中,速度相对于本地存储和网络存储快很多。但是在节点重启的时候, 内存数据会被清除;而如果存在磁盘上,则重启后数据依然存在。另外,使用tmpfs的内存也会计入 容器的使用内存总量中/受系统的Cgroup限制。

    emptyDir设计的初衷主要是给应用充当缓存空间,或者存储中间数据,用于快速恢复。然而,这并不 是说满足以上需求的用户都被推荐使用emptyDir,我们要根据用户业务的实际特点来判断是否使用 emptyDir。因为emptyDir的空间位于系统根盘,被所有容器共享,所以在磁盘的使用率较高时会触 发Pod的eviction操作,从而影响业务的稳定。

  半持久化存储:

    常见的半持久化存储主要是hostPath卷。hostPath卷能将主机节点文件系统上的文件或目录挂载到 指定Pod中。对普通用户而言一般不需要这样的卷,但是对很多需要获取节点系统信息的Pod而言, 却是非常必要的。

    例如, hostPath的用法举例如下:

      某个Pod需要获取节点上所有Pod的log,可以通过hostPath访问所有Pod的stdout输出存储 目录.例如/var/log/pods路径。

      某个Pod需要统计系统相关的信息,可以通过hostPath访问系统的/proc目录。

    使用hostPath的时候,除设置必需的path属性外,用户还可以有选择性地为hostPath卷指定类型, 支持类型包含目录、字符设备、块设备等。

  hostPath卷需要注意:

    使用同一个目录的Pod可能会由于调度到不同的节点,导致目录中的内容有所不同。

    Kubernetes在调度时无法顾及由hostPath使用的资源。

    Pod被删除后,如果没有特别处理,那么hostPath ±写的数据会遗留到节点上,占用磁盘空间。

  持久化存储:

    支持持久化的存储是所有分布式系统所必备的特性。针对持久化存储,Kubernetes引入了Storageclass、Volume、PVC (Persistent Volume Claim)、PV (Persitent Volume)的概念/ 将 存储独立于Pod的生命周期来进行管理。

    Kuberntes目前支持的持久化存储包含各种主流的块存储和文件存储,譬如awsElasticBlockStores azureDisk、cinder、NFS、cephfs、iscsi等/在大类上可以将其分为网络存储和本地存储两种类型。

  StorageCLass:

    Storageclass用于指示存储的类 型,不同的存储类型可以通过不同 的Storageclass来为用户提供服 务。

    Storageclass主要包含存储插件 provisioner、卷的创建和 mount 参数等字段。

allowVolumeExpansior : true
apiVersion: storage.k8s.io/v1
kind: Storageclass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
  name: rook-ceph-block
parameters:
  clusterlD: 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: H2H
  pool: replicapool
provisioner: rook-ceph.rbd.csi.ceph.com
reclaimpolicy: Delete
volumeBindingMode: Immediate

  PVC:

    由用户创建,代表用户对存储需求的声明,主要包含需要的存储大小、存储卷的访问模式、 StroageClass等类型,其中存储卷的访问模式必须与存储的类型一致。

        

  PV:

    由集群管理员提前创建,或者根据PVC的申请需求动态地创建,它代表系统后端的真实的存储空间, 可以称之为卷空间。 

  存储对象关系:

    用户通过创建PVC来申请存储。控制器通过PVC的storageclass和请求的大小声明来存储后端创建 卷,进而创建PV, Pod通过指定PVC来引用存储。

        

  生产实践经验分享:

    不同介质类型的磁盘/需要设置不同的Storageclass,以便让用户做区分。Storageclass需要设置磁 盘介质的类型,以便用户了解该类存储的属性。

    在本地存储的PV静态部署模式下,每个物理磁盘都尽量只创建一个PV,而不是划分为多个分区来提 供多个本地存储PV,避免在使用时分区之间的I/O干扰。

    本地存储需要配合磁盘检测来使用。当集群部署规模化后,每个集群的本地存储PV可能会超过几万个, 如磁盘损坏将是频发事件。此时,需要在检测到磁盘损坏、丢盘等问题后,对节点的磁盘和相应的本地 存储PV进行特定的处理,例如触发告警、自动cordon节点、自动通知用户等。

    对于提供本地存储节点的磁盘管理,需要做到灵活管理和自动化。节点磁盘的信息可以归一、集中化管 理。在local-volume-provisioner中增加部署逻辑,当容器运行起来时,拉取该节点需要提供本地存 储的磁盘信息,例如磁盘的设备路径,以Filesystem或Block的模式提供本地存储,或者是否需要加 入某个LVM的虚拟组(VG)等。

    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和PV进行绑定。

    监听PVC对象:kube-scheduler等待PVC的状态变成Bound状 态。

    Pod调度到节点:如果PVC的状态变为Bound则说明调度成功/ 而如果PVC 一直处于pending状态’超时后会再次进行调度。

    Mount卷启动容器:kubelet监听到有Pod已经调度到节点上/ 对本地存储进行mount操作/并启动容器。

        

  Dynamic Local Volume:

    CSI驱动需要汇报节点上相关存储的资源信息,以便用于调度。

    但是机器的厂家不同/汇报方式也不同。

    例如,有的厂家的机器节点上具有NVMe、SSD、HDD等多种存储介质,希望将这些存储介质分别进 行汇报。

    这种需求有别于其他存储类型的CSI驱动对接口的需求,因此如何汇报节点的存储信息,以及如何让节 点的存储信息应用于调度,目前并没有形成统一的意见。

    集群管理员可以基于节点存储的实际情况对开源CSI驱动和调度进行一些代码修改/再进行部署和使用。

  Local Dynamic的挂载流程:

    创建PVC:用户创建PVC, PVC处于pending状态。

    创建Pod:用户创建Pod。

    Pod选择节点:kube-scheduler开始调度Pod,通过PVC的 pvc.spec.resources.request.storage 等选择满足条件的节点。

    更新PVC :选择节点后, kube-scheduler会给PVC添加包含节 点信息的 annotation: volume.kubernetes.io/selected- node: v节点名字>。

    创建卷:运行在节点上的容器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才算真正调 度成功了。如果PVC-直处于Pending状态/超时后会再次进 行调度。

    Mount卷:kubelet监听到有Pod已经调度到节点上/对本地 存储进行mount操作。

    启动容器:启动容器。

        

  Local Dynamic 的挑战:

    如果将磁盘空间作为一个存储池(例如LVM)来动态分配,那么在分配出来的逻辑卷空间的使用上, 可能会受到其他逻辑卷的I/。干扰,因为底层的物理卷可能是同一个。

    如果PV后端的磁盘空间是一块独立的物理磁盘,则I/O就不会受到干扰。

  生产经验分享:

    不同介质类型的磁盘/需要设置不同的Storageclass,以便让用户做区分。Storageclass需要设置磁 盘介质的类型,以便用户了解该类存储的属性。

    在本地存储的PV静态部署模式下,每个物理磁盘都尽量只创建一个PV,而不是划分为多个分区来提供 多个本地存储PV,避免在使用时分区之间的I/O干扰。

    本地存储需要配合磁盘检测来使用。当集群部署规模化后,每个集群的本地存储PV可能会超过几万个, 如磁盘损坏将是频发事件。此时,需要在检测到磁盘损坏、丢盘等问题后,对节点的磁盘和相应的本地 存储PV进行特定的处理,例如触发告警、自动cordon节点、自动通知用户等。

    对于提供本地存储节点的磁盘管理,需要做到灵活管理和自动化。节点磁盘的信息可以归一、集中化管 理。在local-volume-provisioner中增加部署逻辑,当容器运行起来时,拉取该节点需要提供本地存 储的磁盘信息,例如磁盘的设备路径,以Filesystem或Block的模式提供本地存储,或者是否需要加 入某个LVM的虚拟组(VG)等。

    local-volume-provisioner根据获取的磁盘信息对磁盘进行格式化,或者加入到某个VG,从而形成对 本地存储支持的自动化闭环。

  Rook:

    Rook是一款云原生环境下的开源分布式存储编排系统,目前支持Ceph、NFS、EdgeFS. Cassandra、CockroachDB 等存储系统。

    它实现了一个自动管理的、自动扩容的、自动修复的分布式存储服务。Rook支持自动部署、启动、 配置、分配、扩容/缩容、升级、迁移、灾难恢复、监控以及资源管理。

  Rook架构:

        

  Rook Operator:

    Rook Operator 是 Rook 的大脑,以 deployment 形式存在。

    其利用Kubernetes的controller-runtime框架实现了 CRD,并进而接受Kubernetes创建资源的请 求并创建相关资源(集群,pool,块存储服务,文件存储服务等)。

    Rook Operator监控存储守护进程,来确保存储集群的健康。

    监听Rook Discovers收集到的存储磁盘设备,并创建相应服务(Ceph的话就是OSD 了)。

  Rook Discover:

    Rook Discover是以DaemonSet形式部署在所有的存储机上的,其检测挂接到存储节点上的存储设 备。把符合要求的存储设备记录下来,这样RookOperater感知到以后就可以基于该存储设备创建相 应服务了。

## discover device
$ 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
## discover ceph inventory
$ ceph-volume inventory --format json

 

  CSIDriver 发现:

    CSI驱动发现: 如果一个CSI驱动创建CSIDriver对象,Kubernetes用户可以通过get CSIDriver命令发现它们;

    CSI对象有如下特点:自定义的Kubernetes逻辑;Kubernetes对存储卷有一些列操作,这些CSIDriver可以自定义支持哪些操作

  Provisioner:

    CSI external-provisioner 是一个监控 Kubernetes PVC 对象的 Sidecar 容器。

    当用户创建PVC后, Kubernetes会监测PVC对应的Storageclass,如果Storageclass中的 provisioner与某插件匹配,该容器通过CSI Endpoint (通常是unix socket)调用CreateVolume方 法。

    如果 CreateVolume 方法调用成功, 则 Provisioner sidecar 创建 Kubernetes PV 对象。

  CSI External Provisioner:

containers:
- args:
  - --csi-address=$(ADDRESS)
  - --v=0
  - --timeout=150s
  - --retry-interval-start=500ms
env:
  - name: ADDRESS
  value: unix:///csi/csi-provisioner.sock
  image: quay.io/k8scsi/csi-provisioner:v1.6.0
name: csi-provisioner
  resources: {}
volumeMounts:
  - mountPath: /csi
    name: socket-dir
  - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    name: rook-csi-rbd-provisioner-sa-token-mxv84
    readonly: true
-args:
  - --nodeid=$(NODE_ID)
  - --endpoinM(CSI_ENDPOINT)
  - --v=0
  - --type=rbd
  - --controllerserver 二 true
  - --drivername=rook-ceph.rbd.csi.ceph.com
  env:
  - name: CSI_ENDPOINT
    value: unix:///csi/csi-provisioner.sock
  image: quay.io/cephcsi/cephcsi:v3.0.0
  name: csi-rbdplugin
- emptyDir:
    medium: Memory
  name: socket-dir

 

  Provisioner 代码:

controller/controller.go
  syncClaim -> provisionClaimOperation-> provisioner.Provision
    pkg/operator/ceph/provisioner/provisioner.go
      Provision-> createVolume
        ceph.Createlmage
      pkg/daemon/ceph/client/image.go
      rbd create poolName/name -size sizeMB
  volumestore.Storevolume
    controller/volume_store.go
      doSaveVolume
        client.CoreV1().PersistentVolumes().Create(volume)

 

  Provisioner log:

10816 10:17:32.535207   1 connection.go:153] Connecting to unix:///csi/csi-provisioner.sock
10816 10:17:54.361911   1 volume_store.go:97] Starting save volume queue
10816 10:17:54.461930   1 controller.go:1284] provision "default/mysql-pv-claim" class "rook-ceph-block": started
10816 10:17:54.462078   1 controller.go:848] Started provisioner controller rook-ceph.rbd.csi.ceph.com_csi-rbdplugin-provisioner-677577c77c-vwkzz_ca5971ab-2293-4e52-9bc9-c490f7f50b07!
10816 10:17:54.465752 1 event.go:281] Event(v1 .ObjectReference(Kind:"PersistentVolumeClaim",Namespace:ndefaultn, Name:nmysql-pv-claimn, UID:n24449707-6738-425c-ac88-de3c470cf91an, APIVersion:nv1",Resourceversion:"45668", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "defauIt/mysqI-pv-claim"

 

  Rook Agent:

    Rook Agent是以DaemonSet形式部署在所有的存储机上的,其处理所有的存储操作,例如挂卸载存 储卷以及格式化文件系统等。

  CSI插件注册:

spec:
  hostNetwork: true
    hostPID: true
-args:
  - --v=0
  - --csi-address=/csi/csi.sock
  - --kubelet-registration-path=/var/lib/kubelet/plugins/rook-ceph.rbd.csi.ceph.com/csi.sock
  image: quay.io/k8scsi/csi-node-driver-registrar:v1.2.0
  name: driver-registrar
  resources: {}
  securitycontext:
    privileged: true
  volumeMounts:
  - mountPath: /csi
    name: plugin-dir
  - mountPath: /registration
    name: registration-dir

 

  CSI DRIVER:

apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
  name: rook-ceph.rbd.csi.ceph.com
spec:
  attachRequired: true
  podlnfoOnMount: false
  volumeLifecycleModes:
    - Persistent
  ls /var/lib/kubelet/plugins/rook-ceph.rbd.csi.ceph.com csi.sock
- args:
  - --nodeid=$(NODE_ID)
  - --endpoint=$(CSljENDPOINT)
  - --v=0
  - --type=rbd
  - --nodeserver=true
  - --drivername=rook-ceph.rbd.csi.ceph.com
  - --pidlimit=-1
  - --metricsport=9090
  - --metricspath=/metrics
  - --enablegrpcmetrics=true
  env:
  - name: CSI_ENDPOINT
    value: unix:///csi/csi.sock
  image: quay.io/cephcsi/cephcsi:v3.0.0
  name: csi-rbdplugin
  securitycontext:
    allowPrivilegeEscalation: true
    capabilities:
      add:
      -SYS_ADMIN
    privileged: true
- hostPath:
    path: /var/lib/kubelet/plugins/rook-ceph.rbd.csi.ceph.com
    type: DirectoryOrCreate
  name: plugin-dir

 

  Agent

pkg/daemon/ceph/agent/agent.go
  flexvolume.NewController(a.context, volumeAttachmentController, volumeManager) 、
  rpc.Register(flexvolumeController)
  flexvolumeServer.Start

 

  Cluster:

    针对不同ceph cluster, rook启动一组管理组件/ 包括:mon、mgr、osd、mds、rgw

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    image: ceph/ceph:v14.2.10
    dataDirHostPath: /var/lib/rook
  mon:
    count: 3
    allowMultiplePerNode: false
  mgr:
    modules:
    - name: pg_autoscaler
      enabled: true
  dashboard:
    enabled: true
  storage:
    useAllNodes: true
    useAllDevices: true

 

  Pool:

    —个ceph cluster可以有多个pool,定义副本数量,故障域等多个属性。

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  compressionMode: ""
  crushRoot: ""
  deviceclass: ""
  erasureCoded:
    algorithm: ""
    codingChunks: ""
    dataChunks: 0
  failureDomain: host
  replicated:
    requireSafeReplicaSize: false
    size: 1
    targetSizeRatio: 0
status:
  phase: Ready

 

  Storage Class:

    Storageclass 是 Kubernetes 用 来自动创建PV的对象。

allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: Storageclass
  name: rook-ceph-block
parameters:
  clusterlD: 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

 

posted @ 2023-02-14 19:37  李聪龙  阅读(333)  评论(0编辑  收藏  举报