Kubernetes架构原理小家电维修

在研究Kubernetes如何实现其功能之前,先具体了解下Kubernetes集群有哪些组件。Kubernetes集群分为两部分:

具体看下这两个部分做了什么,以及内部运行的内容。

控制平面的组件

控制平面负责控制并使得整个集群正常运转。控制平面包含如下组件:

这些组件用来存储、管理集群状态,但它们不是运行应用的容器。

工作节点上运行的组件

运行容器的任务依赖于每个工作节点上运行的组件:

附加组件

除了控制平面(和运行在节点上的组件,还要有几个附加组件,这样才能提供所有之前讨论的功能。包含:

之前提到的组件都是作为单独进程运行的。图11.1描述了各个组件及它们之间的依赖关系。

若要启用Kubernetes提供的所有特性,需要运行所有的这些组件。但是有几个组件无须其他组件,单独运行也能提供非常有用的工作。接下来会详细查看每一个组件。

检查控制平面组件的状态

API服务器对外暴露了一个名为ComponentStatus的API资源,用来显示每个控制平面组件的健康状态。可以通过kubectl列出各个组件以及它们的状态:

$kubectlgetcomponentstatusesNAMESTATUSMESSAGEERRORschedulerHealthyokcontroller-managerHealthyoketcd-0Healthy{"health":"true"}组件间如何通信

Kubernetes系统组件间只能通过API服务器通信,它们之间不会直接通信。API服务器是和etcd通信的唯一组件。其他组件不会直接和etcd通信,而是通过API服务器来修改集群状态。

API服务器和其他组件的连接基本都是由组件发起的,如图11.1所示。但是,当使用kubectl获取日志、使用kubectlattach连接到一个运行中的容器或运行kubectlport-forward命令时,API服务器会向Kubelet发起连接。

注意:kubectlattach命令和kubectlexec命令类似,区别是:前者会附属到容器中运行着的主进程上,而后者是重新运行一个进程。

单组件运行多实例

组件是如何运行的

控制平面的组件以及kube-proxy可以直接部署在系统上或者作为pod来运行(如图11.1所示)。听到这个你可能比较惊讶,不过后面我们讨论Kubelet时就都说得通了。

Kubelet是唯一一直作为常规系统组件来运行的组件,它把其他组件作为pod来运行。为了将控制平面作为pod来运行,Kubelet被部署在master上。下面的代码清单展示了通过kubeadm创建的集群里的kube-system命名空间里的pod。

#代码11.1作为pod运行的kubernetes组件$kubectlgetpo-ocustom-columns=POD:metadata.name,NODE:spec.nodeName--sort-byspec.nodeName-nkube-systemPODNODEkube-controller-manager-mastermasterkube-dns-2334855451-37d9kmasteretcd-mastermasterkube-apiserver-mastermasterkube-scheduler-mastermasterkube-flannel-ds-tgj9knode1kube-proxy-ny3xmnode1kube-flannel-ds-0eek8node2kube-proxy-sp362node2kube-flannel-ds-r5yf4node3kube-proxy-og9acnode3如代码清单所示,所有的控制平面组件在主节点上作为pod运行。这里有三个工作节点,每一个节点运行kube-proxy和一个Flannelpod,用来为pod提供重叠网络(后面会再讨论Flannel)。

提示:如代码清单所示,可以通过-ocustom-columns选项自定义展示的列以及--sort-by对资源列表进行排序。

现在,对每一个组件进行研究,从控制平面的底层组件---持久化存储组件开始。

让创建的所有对象---pod、ReplicationController、服务和私密凭据等,需要以持久化方式存储到某个地方,这样它们的manifest在API服务器重启和失败的时候才不会丢失。为此,Kubernetes使用了etcd,etcd是一个响应快、分布式、一致的key-value存储。因为它是分布式的,故可以运行多个etcd实例来获取高可用性和更好的性能。

唯一能直接和etcd通信的是Kubernetes的API服务器。所有其他组件通过API服务器间接地读取、写入数据到etcd。这带来一些好处,其中之一就是增强乐观锁系统、验证系统的健壮性;并且,通过把实际存储机制从其他组件抽离,未来替换起来也更容易。值得强调的是,etcd是Kubernetes存储集群状态和元数据的唯一的地方。

关于乐观并发控制

两个客户端尝试更新同一个数据条目,只有第一个会成功。

所有的Kubernetes包含一个metadata.resourceVersion宇段,当更新对象时,客户端需要返回该值到API服务器。如果版本值与etcd中存储的不匹配,API服务器会拒绝该更新。

资源如何存储在etcd中

K8s1.12发布的时候,Kubernetes既可以用etcd版本2也可以用版本3,但目前更推荐版本3,它的性能更好。etcdv2把key存储在一个层级键空间中,这使得键值对类似文件系统的文件。etcd中每个key要么是一个目录,包含其他key,要么是一个常规key,对应一个值。etcdv3不支持目录,但是由于key格式保持不变(键可以包含斜杠),仍然可以认为它们可以被组织为目录。Kubernetes存储所有数据到etcd的/registry下。下面的代码清单显示/registry下存储的一系列key。

#代码清单11.2etcd中存储的Kubernetes的顶层条目$etcdctlls/registry/registry/configmaps/registry/daemonsets/registry/deployments/registry/events/registry/namespaces/registry/pods注意:如果使用etcdv3的API,就无法使用ls命令来查看目录的内容。但是,可以通过etcdctlget/registry--prefix=true列出所有以给定前缀开始的key。

#代码11.3/registry/pods目录下的key$etcdctlls/registry/pods/registry/pods/default/registry/pods/kube-system从名称可以看出,这两个条目对应default和kube-system命名空间,意味着pod按命名空间存储。下面的代码清单显示/registry/pods/default目录下的条目。

#代码11.4default命名空间中的pod的etcd条目$etcdctlls/registry/pods/default/registry/pods/default/kubia-159041347-xk0vc/registry/pods/default/kubia-159041347-wt6ga/registry/pods/default/kubia-159041347-hp2o5每个条目对应一个单独的pod。这些不是目录,而是键值对。下面的代码清单展示了其中一条存储的内容。

#代码11.5一个etcd条目代表一个pod$etcdctlget/registry/pods/default/kubia-159041347-wt6ga{"kind":"Pod","apiVersion":"v1","metadata":{"name":"kubia-159041347-wt6ga","generateName":"kubia-159041347-","namespace":"default","selfLink":...这就是一个JSON格式的pod定义。API服务器将资源的完整JSON形式存储到etcd中。由于etcd的层级键空间,可以想象成把资源以JSON文件格式存储到文件系统中。简单易懂,对吧?

警告:Kubernetes1.7之前的版本,密钥凭据的JSON内容也像上面一样存储(没有加密)。如果有人有权限直接访问etcd,那么可以获取所有的密钥凭据。从1.7版本开始,密钥凭据会被加密,这样存储起来更加安全。

确保存储对象的一致性和可验证性

确保etcd集群一致性

连接到etcd集群不同节点的客户端,得到的要么是当前的实际状态,要么是之前的状态(在Kubernetes中,etcd的唯一客户端是API服务器,但有可能有多个实例)。

一致性算法要求集群大部分(法定数量)节点参与才能进行到下一个状态。结果就是,如果集群分裂为两个不互联的节点组,两个组的状态不可能不一致,因为要从之前状态变化到新状态,需要有过半的节点参与状态变更。如果一个组包含了大部分节点,那么另外一组只有少量节点成员。第一个组就可以更改集群状态,后者则不可以。当两个组重新恢复连接,第二个组的节点会更新为第一个组的节点的状态。

为什么eted实例数量应该是奇数

etcd通常部署奇数个实例。你一定想知道为什么。比较有一个实例和有两个实例的情况时。有两个实例时,要求另一个实例必须在线,这样才能符合超过半数的数量要求。如果有一个宕机,那么etcd集群就不能转换到新状态,因为没有超过半数。两个实例的情况比一个实例的情况更糟。对比单节点宕机,在有两个实例的情况下,整个集群挂掉的概率增加了100%。

比较3节点和4节点也是同样的情况。3节点情况下,一个实例宕机,但超过半数(2个)的节点仍然运行着。对于4节点情况,需要3个节点才能超过半数(2个不够)。对于3节点和4节点,假设只有一个实例会宕机。当以4节点运行时,一个节点失败后,剩余节点宕机的可能性会更大(对比3节点集群,一个节点宕机还剩两个节点的情况)。

通常,对于大集群,etcd集群有5个或7个节点就足够了。可以允许2-3个节点宕机,这对于大多数场景来说足够了。

KubernetesAPI服务器作为中心组件,其他组件或者客户端(如kubectl)都会去调用它。以RESTfulAPI的形式提供了可以查询、修改集群状态的CRUDCCreate、Read、Update、Delete)接口。它将状态存储到etcd中。

API服务器除了提供一种一致的方式将对象存储到etcd,也对这些对象做校验,这样客户端就无法存入非法的对象了(直接写入存储的话是有可能的)。除了校验,还会处理乐观锁,这样对于并发更新的情况,对对象做更改就不会被其他客户端覆盖。

API服务器的客户端之一就是使用的命令行工具kubectl。举个例子,当以JSON文件创建一个资源,kubectl通过一个HTTPPOST请求将文件内容发布到API服务器。图11.3显示了接收到请求后API服务器内部发生了什么,后面会做更详细的介绍。

通过认证插件认证客户端

首先,API服务器需要认证发送请求的客户端。这是通过配置在API服务器上的一个或多个认证插件来实现的。API服务器会轮流调用这些插件,直到有一个能确认是谁发送了该请求。这是通过检查HTTP请求实现的。

根据认证方式,用户信息可以从客户端证书或者HTTP标头(例如Authorization)获取。插件抽取客户端的用户名、用户ID和归属组。这些数据在下一阶段,认证的时候会用到。

通过准入控制插件验证AND/OR修改资源请求

注意:如果请求只是尝试读取数据,则不会做准入控制的验证。

准入控制插件包括

验证资源以及持久化存储

请求通过了所有的准入控制插件后,API服务器会验证存储到etcd的对象,然后返回一个响应给客户端。

除了前面讨论的,API服务器没有做其他额外的工作。例如,当创建一个ReplicaSet资源时,它不会去创建pod,同时它不会去管理服务的端点。那是控制器管理器的工作。

API服务器甚至也没有告诉这些控制器去做什么。它做的就是,启动这些控制器,以及其他一些组件来监控己部署资源的变更。控制平面可以请求订阅资源被创建、修改或删除的通知。这使得组件可以在集群元数据变化时候执行任何需要做的任务。

客户端通过创建到API服务器的HTTP连接来监听变更。通过此连接,客户端会接收到监听对象的一系列变更通知。每当更新对象,服务器把新版本对象发送至所有监听该对象的客户端。图11.4显示客户端如何监听pod的变更,以及如何将pod的变更存储到etcd,然后通知所有监听该pod的客户端。

kubectl工具作为API服务器的客户端之一,也支持监听资源。例如,当部署pod时,不需要重复执行kubectlgetpods来定期查询pod列表。可以使用--watch标志,每当创建、修改、删除pod时就会通知你,如下面的代码清单所示。

#代码11.6监听创建删除pod事件$kubectlgetpods--watchNAMEREADYSTATUSRESTARTSAGEkubia-159041347-14j3i0/1Pending00skubia-159041347-14j3i0/1Pending00skubia-159041347-14j3i0/1ContainerCreating01skubia-159041347-14j3i0/1Running03skubia-159041347-14j3i1/1Running05skubia-159041347-14j3i1/1Terminating09skubia-159041347-14j3i0/1Terminating017skubia-159041347-14j3i0/1Terminating017skubia-159041347-14j3i0/1Terminating017s甚至可以让kubectl打印出整个监听事件的YAML文件,如下:

$kubectlgetpods-oyaml--watch监听机制同样也可以用于调度器。调度器是下一个要着重讲解的控制平面组件。

前面己经学习过,通常不会去指定pod应该运行在哪个集群节点上,这项工作交给调度器。宏观来看,调度器的操作比较简单。就是利用API服务器的监听机制等待新创建的pod,然后给每个新的、没有节点集的pod分配节点。

调度器不会命令选中的节点(或者节点上运行的Kubelet)去运行pod。调度器做的就是通过API服务器更新pod的定义。然后API服务器再去通知Kubelet(同样,通过之前描述的监听机制)该pod己经被调度过。当目标节点上的Kubelet发现该pod被调度到本节点,它就会创建并且运行pod的容器。

尽管宏观上调度的过程看起来比较简单,但实际上为pod选择最佳节点的任务并不简单。当然,最简单的调度方式是不关心节点上己经运行的pod,随机选择一个节点。另一方面,调度器可以利用高级技术,例如机器学习,来预测接下来几分钟或几小时哪种类型的pod将会被调度,然后以最大的硬件利用率、无须重新调度已运行pod的方式来调度。Kubernetes的默认调度器实现方式处于最简单和最复杂程度之间。

默认的调度算法

选择节点操作可以分解为两部分,如图11.5所示:

查找可用节点

为了决定哪些节点对pod可用,调度器会给每个节点下发一组配置好的预测函数。这些函数检查

所有这些测试都必须通过,节点才有资格调度给pod。在对每个节点做过这些检查后,调度器得到节点集的一个子集。任何这些节点都可以运行pod,因为它们都有足够的可用资源,也确认过满足pod定义的所有要求。

为pod选择最佳节点

尽管所有这些节点都能运行pod,其中的一些可能还是优于另外一些。假设有一个2节点集群,两个节点都可用,但是其中一个运行10个pod,而另一个,不知道什么原因,当前没有运行任何pod。本例中,明显调度器应该选第二个节点。

或者说,如果两个节点是由云平台提供的服务,那么更好的方式是,pod调度给第一个节点,将第二个节点释放回云服务商以节省资金。

pod高级调度

考虑另外一个例子。假设一个pod有多个副本。理想情况下,你会期望副本能够分散在尽可能多的节点上,而不是全部分配到单独一个节点上。该节点的宕机会导致pod支持的服务不可用。但是如果pod分散在不同的节点上,单个节点宕机,并不会对服务造成什么影响。

仅通过这两个简单的例子就说明了调度有多复杂,因为它依赖于大量的因子。因此,调度器既可以配置成满足特定的需要或者基础设施特性,也可以整体替换为一个定制的实现。可以抛开调度器运行一个Kubernetes不过那样的话,就需要手动实现调度了。

使用多个调度器

可以在集群中运行多个调度器而非单个。然后,对每一个pod,可以通过在pod特性中设置schedulerName属性指定调度器来调度特定的pod。

未设置该属性的pod由默认调度器调度,因此其schedulerName被设置为default-scheduler。其他设置了该属性的pod会被默认调度器忽略掉,它们要么是手动调用,要么被监听这类pod的调度器调用。

可以实现自己的调度器,部署到集群,或者可以部署有不同配置项的额外Kubernetes调度器实例。

如前面提到的,API服务器只做了存储资源到etcd和通知客户端有变更的工作。调度器则只是给pod分配节点,所以需要有活跃的组件确保系统真实状态朝API服务器定义的期望的状态收敛。这个工作由控制器管理器里的控制器来实现。

单个控制器、管理器进程当前组合了多个执行不同非冲突任务的控制器。这些控制器最终会被分解到不同的进程,如果需要的话,能够用自定义实现替换它们每一个。控制器包括

每个控制器做什么通过名字显而易见。通过上述列表,几乎可以知道创建每个资源对应的控制器是什么。资源描述了集群中应该运行什么,而控制器就是活跃的Kubernetes组件,去做具体工作部署资源。

了解控制器做了什么以及如何做的

控制器做了许多不同的事情,但是它们都通过API服务器监听资源(部署、服务等)变更,并且不论是创建新对象还是更新、删除己有对象,都对变更执行相应操作。大多数情况下,这些操作涵盖了新建其他资源或者更新监听的资源本身(例如,更新对象的status)。

控制器之间不会直接通信,它们甚至不知道其他控制器的存在。每个控制器都连接到API服务器,通过1.3节《API服务器做了什么》描述的监听机制,请求订阅该控制器负责的一系列资源的变更。

概括地了解了每个控制器做了什么,但是如果想深入了解它们做了什么,建议直接看源代码。边栏阐述了如何上手看源代码。

浏览控制器源代码的几个要点

每个控制器一般有一个构造器,内部会创建一个Informer,其实是个监听器,每次API对象有更新就会被调用。通常,Informer会监听特定类型的资源变更事件。查看构造器可以了解控制器监听的是哪个资源。

接下来,去看worker()方法。其中定义了每次控制器需要工作的时候都会调用worker()方法。实际的函数通常保存在一个叫syncHandler或类似的字段里。该字段也在构造器里初始化,可以在那里找到被调用函数名。该函数是所有魔法发生的地方。

Replication管理器

启动ReplicationController资源的控制器叫作Replication管理器。应该都了解ReplicationController是如何工作的,其实不是ReplicationController做了实际的工作,而是Replication管理器。

ReplicationController的操作可以理解为一个无限循环,每次循环,控制器都会查找符合其pod选择器定义的pod的数量,并且将该数值和期望的复制集(replica)数量做比较。

API服务器可以通过监听机制通知客户端,那么明显地,控制器不会每次循环去轮询pod,而是通过监听机制订阅可能影响期望的复制集(replica)数量或者符合条件pod数量的变更事件(见图11.6)。任何该类型的变化,将触发控制器重新检查期望的以及实际的复制集数量,然后做出相应操作。

当运行的pod实例太少时,ReplicationController会运行额外的实例,但它自己实际上不会去运行pod。它会创建新的pod清单,发布到API服务器,让调度器以及Kubelet来做调度工作并运行pod。

Replication管理器通过API服务器操纵podAPI对象来完成其工作。所有控制器就是这样运作的。

RerlicaSet、DaemonSet以及Job控制器

ReplicaSet控制器基本上做了和前面描述的Replication管理器一样的事情,所以这里不再赘述。DaemonSet以及Job控制器比较相似,从它们各自资源集中定义的pod模板创建pod资源。与Replication管理器类似,这些控制器不会运行pod,而是将pod定义到发布API服务器,让Kubelet创建容器并运行。

Deployment控制器

Deployment控制器负责使deployment的实际状态与对应DeploymentAPI对象的期望状态同步。

每次Deployment对象修改后(如果修改会影响到部署的pod),Deployment控制器都会滚动升级到新的版本。通过创建一个ReplicaSet,然后按照Deployment中定义的策略同时伸缩新、旧RelicaSet,直到旧pod被新的代替。并不会直接创建任何pod。

StatefulSet控制器

Node控制器

Node控制器管理Node资源,描述了集群工作节点。其中,Node控制器使节点对象列表与集群中实际运行的机器列表保持同步。同时监控每个节点的健康状态,删除不可达节点的pod。

Node控制器不是唯一对Node对象做更改的组件。Kubelet也可以做更改,那么显然可以由用户通过RESTAPI调用做更改。

Service控制器

在服务中,有很多存在不同服务类型。其中一个是LoadBalancer服务,从基础设施服务请求一个负载均衡器使得服务外部可以用。Service控制器就是用来在LoadBalancer类型服务被创建或删除时,从基础设施服务请求、释放负载均衡器的。

Endpoint控制器

Service不会直接连接到pod,而是包含一个端点列表(IP和端口),列表要么是手动,要么是根据Service定义的pod选择器自动创建、更新。Endpoint控制器作为活动的组件,定期根据匹配标签选择器的pod的IP、端口更新端点列表。

如图11.7所示,控制器同时监听了Service和pod。当Service被添加、修改,或者pod被添加、修改或删除时,控制器会选中匹配Service的pod选择器的pod,将其IP和端口添加到Endpoint资源中。请记住,Endpoint对象是个独立的对象,所以当需要的时候控制器会创建它。同样地,当删除Service时,Endpoint对象也会被删除。

Namespace控制器

大部分资源归属于某个特定命名空间。当删除一个Namespace资源时,该命名空间里的所有资源都会被删除。这就是Namespace控制器做的事情。当收到删除Namespace对象的通知时,控制器通过API服务器删除所有归属该命名空间的资源。

PersistentVolume控制器

唤醒控制器

现在,总体来说应该了解每个控制器做了什么,以及是如何工作的有个比较好的感觉。再一次强调,所有这些控制器是通过API服务器来操作API对象的。它们不会直接和Kubelet通信或者发送任何类型的指令。实际上,它们不知道Kubelet的存在。控制器更新API服务器的一个资源后,Kubelet和KubernetesServiceProxy(也不知道控制器的存在)会做它们的工作,例如启动pod容器、加载网络存储,或者就服务而言,创建跨pod的负载均衡。

控制平面处理了整个系统的一部分操作,为了完全理解Kubernetes集群的内部运作方式,还需要理解Kubelet和KubernetesServiceProxy做了什么。下面将学习这些内容。

所有Kubernetes控制平面的控制器都运行在主节点上,而Kubelet以及ServiceProxy都运行在工作节点(实际pod容器运行的地方)上。Kubelet究竟做了什么事情?

了解Kubelet的工作内容

简单地说,Kubelet就是负责所有运行在工作节点上内容的组件。它第一个任务就是在API服务器中创建一个Node资源来注册该节点。然后需要持续监控API服务器是否把该节点分配给pod,然后启动pod容器。具体实现方式是告知配置好的容器运行时(Docker、CoreOS的Rkt,或者其他一些东西)来从特定容器镜像运行容器。Kubelet随后持续监控运行的容器,向API服务器报告它们的状态、事件和资源消耗。

Kubelet也是运行容器存活探针的组件,当探针报错时它会重启容器。最后一点,当pod从API服务器删除时,Kubelet终止容器,并通知服务器pod己经被终止了。

抛开API服务器运行静态pod

尽管Kubelet—般会和API服务器通信并从中获取pod清单,它也可以基于本地指定目录下的pod清单来运行pod,如图11.8所示。如本章开头所示,该特性用于将容器化版本的控制平面组件以pod形式运行。

不但可以按照原有的方式运行Kubernetes系统组件,也可以将pod清单放到Kubelet的清单目录中,让Kubelet运行和管理它们。

也可以同样的方式运行自定义的系统容器,不过推荐用DaemonSet来做这项工作。

除了Kubelet,每个工作节点还会运行kube-proxy,用于确保客户端可以通过KubernetesAPI连接到定义的服务。kube-proxy确保对服务IP和端口的连接最终能到达支持服务(或者其他,非pod服务终端)的某个pod处。如果有多个pod支撑一个服务,那么代理会发挥对pod的负载均衡作用。

为什么被叫作代理

kubeproxy最初实现为userspace代理。利用实际的服务器集成接收连接,同时代理给pod。为了拦截发往服务IP的连接,代理配置了iptables规则(iptables是一个管理Linux内核数据包过滤功能的工具),重定向连接到代理服务器。userspace代理模式大致如图11.9所示。

kube-proxy之所以叫这个名字是因为它确实就是一个代理器,不过当前性能更好的实现方式仅仅通过iptables规则重定向数据包到一个随机选择的后端pod,而不会传递到一个实际的代理服务器。这个模式称为iptables代理模式,如图11.10所示。

但是iptables受限于集群的规模,规模越大,iptables的速度越慢,所以在后期出现了一个IPVS模式,性能有着更大的提升。

Userspace和iptables的主要区别是:数据包是否会传递给kube-proxy,是否必须在用户空间处理,或者数据包只会在内核处理(内核空间)。这对性能有巨大的影响。加上后期出现的IPVS,有了多样化的调度算法和专门针对负载均衡的IPVS组件。各方面有了更大的提升

讨论了Kubernetes集群正常工作所需要的一些核心组件。但在开头的几章中,也罗列了一些插件,它们不是必需的;这些插件用于启用Kubernetes服务的DNS查询,通过单个外部IP地址暴露多个HTTP服务、Kubernetesweb仪表板等特性。

如何部署插件

通过提交YAML清单文件到API服务器,各插件可以去kubernetes查找,这里不做过多讲解。

介绍DNS服务器如何工作

集群中的所有pod默认配置使用集群内部DNS服务器。这使得pod能够轻松地通过名称查询到服务,甚至是无头服务pod的IP地址。

Ingress控制器如何工作

和DNS插件相比,Ingress控制器的实现有点不同,但它们大部分的工作方式相同。Ingress控制器运行一个反向代理服务器(例如,类似Nginx),根据集群中定义的Ingress、Service以及Endpoint资源来配置该控制器。所以需要订阅这些资源(通过监听机制),然后每次其中一个发生变化则更新代理服务器的配置。

尽管Ingress资源的定义指向一个Service,Ingress控制器会直接将流量转到服务的pod而不经过服务IP。当外部客户端通过Ingress控制器连接时,会对客户端IP进行保存,这使得在某些用例中,控制器比Service更受欢迎。

使用其他插件

了解了DNS服务器和Ingress控制器插件同控制器管理器中运行的控制器比较相似,除了它们不会仅通过API服务器监听、修改资源,也会接收客户端的连接。

其他插件也类似。它们都需要监听集群状态,当有变更时执行相应动作。会在剩余的章节中介绍一些其他的插件。

了解了整个Kubernetes系统由相对小的、完善功能划分的松耦合组件构成。API服务器、调度器、控制器管理器中运行的控制器、Kubelet以及kube-proxy一起合作来保证实际的状态和定义的期望状态一致。

例如,向API服务器提交一个pod配置会触发Kubernetes组件间的协作,这会导致pod的容器运行。这里的细节将会在接下来的部分详细说明。

现在了解了Kubernetes集群包含哪些组件。为了强化对Kubernetes工作方式的理解,看一下当一个pod资源被创建时会发生什么。因为一般不会直接创建pod,所以创建Deployment资源作为替代,然后观察启动pod的容器会发生什么。

在启动整个流程之前,控制器、调度器、Kubelet就己经通过API服务器监听它们各自资源类型的变化了。如图11.11所示。图中描画的每个组件在即将触发的流程中都起到一定的作用。图表中不包含etcd,因为它被隐藏在API服务器之后,可以想象成API服务器就是对象存储的地方。

准备包含Deployment清单的YAML文件,通过kubetctl提交到Kubernetes。kubectl通过HTTPPOST请求发送清单到KubernetesAPI服务器。API服务器检查Deployment定义,存储到etcd,返回响应给kubectl。现在事件链开始被揭示出来,如图11.12所示。

Deployment控制器生成ReplicaSet

当新创建Deployment资源时,所有通过API服务器监听机制监听Deployment列表的客户端马上会收到通知。其中有个客户端叫Deployment控制器,之前讨论过,该控制器是一个负责处理部署事务的活动组件。

一个Deployment由一个或多个Replicaset支持,ReplicaSet后面会创建实际的pod。当Deployment控制器检查到有一个新的Deployment对象时,会按照Deploymnet当前定义创建ReplicaSet。这包括通过KubernetesAPI创建一个新的ReplicaSet资源。Deployment控制器完全不会去处理单个pod。

ReplicaSet控制器创建pod资源

新创建的ReplicaSet由ReplicaSet控制器(通过API服务器创建、修改、删除ReplicaSet资源)接收。控制器会考虑replica数量、ReplicaSet中定义的pod选择器,然后检查是否有足够的满足选择器的pod。

然后控制器会基于ReplicatSet的pod模板创建pod资源(当Deployment控制器创建ReplicaSet时,会从Deployment复制pod模板)。

调度器分配节点给新创建的pod

新创建的pod目前保存在etcd中,但是它们每个都缺少一个重要的东西——它们还没有任何关联节点。它们的nodeName属性还未被设置。调度器会监控像这样的pod,发现一个,就会为pod选择最佳节点,并将节点分配给podLpod的定义现在就会包含它应该运行在哪个节点。

目前,所有的一切都发生在Kubernetes控制平面中。参与这个全过程的控制器没有做其他具体的事情,除了通过API服务器更新资源。

Kubelet运行pod容器

目前,工作节点还没做任何事情,pod容器还没有被启动起来,pod容器的图片还没有下载。

随着pod目前分配给了特定的节点,节点上的Kubelet终于可以工作了。Kubelet通过API服务器监听pod变更,发现有新的pod分配到本节点后,会去检查pod定义,然后命令Docker或者任何使用的容器运行时来启动pod容器,容器运行时就会去运行容器。

下面的代码清单展示了前述过程中发出的事件(由于页面空间有限,有些列被删掉了,输出也做了改动)。

当pod运行时,仔细看一下,运行的pod到底是什么。如果pod包含单个容器,那么你认为Kubelet会只运行单个容器,还是更多?

想象运行单个容器的pod,假设创建了一个Nginxpod:

$kubectlrunnginx--image=nginxdeployment"nginx"created此时,可以ssh到运行pod的工作节点,检查一系列运行的Docker容器。使用minikubessh来ssh到单个节点。如果用GKE,可以通过gcloudcomputessh来ssh到一个节点。

一旦进入节点内部,可以通过dockerps命令列出所有运行的容器,如下面的代码清单所示。

#代码11.10列出运行的Docker容器:这里已经把不想关的信息删除了docker@minikubeVM:~$dockerpsCONTAINERIDIMAGECOMMANDCREATEDc917a6f3c3f7nginx"nginx-g'daemonoff"4secondsago98b8bf797174gcr.io/.../pause:3.0"/pause"7secondsago...看到了Nginx容器,以及一个附加容器。从COMMAND列判断,附加容器没有做任何事情(容器命令是"pause")。仔细观察,会发现容器是在Nginx容器前几秒创建的。它的作用是什么?

被暂停的容器将一个pod所有的容器收纳到一起。还记得一个pod的所有容器是如何共享同一个网络和Linux命名空间的吗?暂停的容器是一个基础容器,它的唯一目的就是保存所有的命名空间。所有pod的其他用户定义容器使用pod的该基础容器的命名空间。(见图11.13)

实际的应用容器可能会挂掉并重启。当容器重启,容器需要处于与之前相同的Linux命名空间中。基础容器使这成为可能,因为它的生命周期和pod绑定,基础容器pod被调度直到被删除一直会运行。如果基础pod在这期间被关闭,Kubelet会重新创建它,以及pod的所有容器。

现在,你知道每个pod有自己唯一的IP地址,可以通过一个扁平的、非NAT网络和其他pod通信。Kubernetes是如何做到这一点的?简单来说,Kubernetes不负责这块。网络是由系统管理员或者ContainerNetworklnterface(CNI)插件建立的,而非Kubernetes本身。

看图11.14,当podA连接(发送网络包)到podB时,podB获取到的源IP地址必须和podA自己认为的IP地址一致。其间应该没有网络地址转换(NAT)操作---podA发送到podB的包必须保持源和目的地址不变。

这很重要,保证运行在pod内部的应用网络的简洁性,就像运行在同一个网关机上一样。pod没有NAT使得运行在其中的应用可以自己注册在其他pod中。

例如,有客户端podX和podY,为所有通过它们注册的pod提供通知服务。podX连接到podY并且告诉podY,“你好,我是podX,IP地址为1.2.3.4请把更新发送到这个IP地址”。提供服务的pod可以通过收到的IP地址连接第一个pod。

pod到节点及节点到pod通信也应用了无NAT通信。但是当pod和internet上的服务通信时,pod发送包的源IP不需要改变,因为pod的IP是私有的。向外发送包的源IP地址会被改成主机工作节点的IP地址。

构建一个像样的Kubernetes集群包含按照这些要求建立网络。有不同的方法和技术来建立,在给定场景中它们都有其优点和缺点。因此,这里不会深入探究特定的技术,会阐述跨pod网络通用的工作原理。

<了解运行中pod是什么>介绍了创建pod的IP地址以及网络命名空间,由基础设施容器(暂停容器)来保存这些信息,然后pod容器就可以使用网络命名空间了。pod网络接口就是生成在基础设施容器的一些东西。现在看一下接口是如何被创建的,以及如何连接到其他pod的接口,如图11.15所示。

同节点pod通信

基础设施容器启动之前,会为容器创建一个虚拟Ethernet接口对(一个vethpair),其中一个对的接口保留在主机的命名空间中(在节点上运行ifconfg命令时可以看到vethXXX的条目),而其他的对被移入容器网络命名空间,并重命名为ethO。两个虚拟接口就像管道的两端(或者说像Ethernet电缆连接的两个网络设备)一从一端进入,另一端出来,等等。

主机网络命名空间的接口会绑定到容器运行时配置使用的网络桥接上。从网桥的地址段中取IP地址赋值给容器内的ethO接口。应用的任何运行在容器内部的程序都会发送数据到ethO网络接口(在容器命名空间中的那一个),数据从主机命名空间的另一个veth接口出来,然后发送给网桥。这意味着任何连接到网桥的网络接口都可以接收该数据。

如果podA发送网络包到podB,报文首先会经过podA的veth对到网桥然后经过podB的veth对。所有节点上的容器都会连接到同一个网桥,意味着它们都能够互相通信。但是要让运行在不同节点上的容器之间能够通信,这些节点的网桥需要以某种方式连接起来。

不同节点上的pod通信

有多种连接不同节点上的网桥的方式。可以通过overlay或underlay网络,或者常规的三层路由,会在后面看到。

跨整个集群的pod的IP地址必须是唯一的,所以跨节点的网桥必须使用非重叠地址段,防止不同节点上的pod拿到同一个IP。如图11.16所示的例子,节点A上的网桥使用10.1.1.0/24IP段,节点B上的网桥使用10.1.2.0/24IP段,确保没有IP地址冲突的可能性。

图11.16显示了通过三层网络支持跨两个节点pod通信,节点的物理网络接口也需要连接到网桥。节点A的路由表需要被配置成图中所示,这样所有目的地为10.1.2.0/24的报文会被路由到节B,同时节点B的路由表需要被配置成图中所示,这样发送到10.1.1.0/24的包会被发送到节点A。

按照该配置,当报文从一个节点上容器发送到其他节点上的容器,报文先通过vethpair,通过网桥到节点物理适配器,然后通过网线传到其他节点的物理适配器,再通过其他节点的网桥,最终经过vethpair到达目标容器。

仅当节点连接到相同网关、之间没有任何路由时上述方案有效。否则,路由器会扔包因为它们所涉及的podIP是私有的。当然,也可以配置路由使其在节点间能够路由报文,但是随着节点数量增加,配置会变得更困难,也更容易出错。因此,使用SDN(软件定义网络)技术可以简化问题,SDN可以让节点忽略底层网络拓扑,无论多复杂,结果就像连接到同一个网关上。从pod发出的报文会被封装,通过网络发送给运行其他pod的网络,然后被解封装、以原始格式传递给pod。

为了让连接容器到网络更加方便,启动一个项目容器网络接口(CNI)。CNI允许Kubernetes可配置使用任何CNI插件。这些插件包含

安装一个网络插件并不难,只需要部署一个包含DaemonSet以及其他支持资源的YAML。每个插件项目首页都会提供这样一个YAML文件。如你所想,DaemonSet用于往所有集群节点部署一个网络代理,然后会绑定CNI接口到节点。但是,注意Kubetlet需要用--network-plugin=cni命令启动才能使用CNI。

当在Kubernetes运行应用时,有不同的控制器来保证你的应用平滑运行,即使节点宕机也能够保持特定的规模。为了保证你的应用的高可用性,只需通过Deployment资源运行应用,配置合适数量的复制集,其他的交给Kubernetes处理。

运行多实例来减少宕机可能性

对不能水平扩展的应用使用领导选举机制

为了避免宕机,需要在运行一个活跃的应用的同时再运行一个附加的非活跃复制集,通过一个快速起效租约或者领导选举机制来确保只有一个是有效的。以防不熟悉领导者选举算法,提一下,它是一种分布式环境中多应用实例对谁是领导者达成一致的方式。例如,领导者要么是唯一执行任务的那个,其他所有节点都在等待该领导者宕机,然后自己变成领导者;或者是都是活跃的,但是领导者是唯一能够执行写操作的,而其他的只能读数据。这样能保证两个实例不会做同一个任务,否则会因为竞争条件导致不可预测的系统行为。

保证应用高可用相对简单,因为Kubernetes几乎替你完成所有事情。但是假如Kubernetes自身宕机了呢?如果是运行Kubernetes控制平面组件的服务器挂了呢?这些组件是如何做到高可用的呢?

为了使得Kubernetes高可用,需要运行多个主节点,即运行下述组件的多个实例:

不需要深入了解如何安装和运行这些组件的细节。看一下如何让这些组件高可用。

因为etcd被设计为一个分布式系统,其核心特性之一就是可以运行多个etcd实例,所以它做到高可用并非难事。要做的就是将其运行在合适数量的机器上(3个、5个或者7个,如章节刚开始所述),使得它们能够互相感知。实现方式通过在每个实例的配置中包含其他实例的列表。例如,当启动一个实例时,指定其他etcd实例可达的IP和端口。

etcd会跨实例复制数据,所以三节点中其中一个宕机并不会影响处理读写操作。为了增加错误容忍度不仅仅支持一台机器宕机,需要运行5个或者7个etcd节点,这样集群可以分别容忍2个或者3个节点宕机。拥有超过7个实例基本上没有必要,并且会影响性能。

运行多实例API服务器

保证API服务器高可用甚至更简单,因为API服务器是(几乎全部)无状态的(所有数据存储在etcd中,API服务器不做缓存),需要多少就能运行多少API服务器,它们直接不需要感知对方存在。通常,一个API服务器会和每个etcd实例搭配。这样做,etcd实例之前就不需要任何负载均衡器,因为每个API服务器只和本地etcd实例通信。

而API服务器确实需要一个负载均衡器,这样客户端(kubectl,也有可能是控制器管理器、调度器以及所有Kubelet)总是只连接到健康的API服务器实例。

确保控制器和调度器的高可用性

对比API服务器可以同时运行多个复制集,运行控制器管理器或者调度器的多实例情况就没那么简单了。因为控制器和调度器都会积极地监听集群状态,发生变更时做出相应操作,可能未来还会修改集群状态(例如,当ReplicaSet上期望的复制集数量增加1时,ReplicaSet控制器会额外创建一个pod),多实例运行这些组件会导致它们执行同一个操作,会导致产生竞争状态,从而造成非预期影响(如前例提到的,创建了两个新pod而非一个)。

控制器管理器和调度器可以和API服务器、etcd搭配运行,或者也可以运行在不同的机器上。当搭配运行时,可以直接跟本地API服务器通信;否则就是通过负载均衡器连接到API服务器。

控制平面组件使用的领导选举机制

选举领导时这些组件不需要互相通信。领导选举机制的实现方式是在API服务器中创建一个资源,而且甚至不是什么特殊种类的资源---Endpoint资源就可以拿来用于达到目的(滥用更贴切一点)。

使用Endpoint对象来完成该工作没有什么特别之处。使用Endpoint对象的原因是只要没有同名Service存在,就没有副作用。也可以使用任何其他资源(事实上,领导选举机制不就会使用ConfigMap来替代Endpoint)。

你一定对资源如何被应用于该目的感兴趣。以调度器为例。所有调度器实例都会尝试创建(之后更新)一个Endpoint资源,称为kube-scheduler。可以在kube-system命名空间中找到它,如下面的代码清单所示。

#代码11.11用于领导选举kube-schedulerEndpoint资源$kubectlgetendpointskube-scheduler-nkube-system-oyamlapiVersion:v1kind:Endpointsmetadata:annotations:control-plane.alpha.kubernetes.io/leader:'{"holderIdentity":"minikube","leaseDurationSeconds":15,"acquireTime":"2017-05-27T18:54:53Z","renewTime":"2017-05-28T13:07:49Z","leaderTransitions":0}'creationTimestamp:2017-05-27T18:54:53Zname:kube-schedulernamespace:kube-systemresourceVersion:"654059"selfLink:/api/v1/namespaces/kube-system/endpoints/kube-scheduleruid:f847bd14-430d-11e7-9720-080027f8fa4esubsets:[]control-plane.alpha.kubernetes.io/leader注释是比较重要的部分。如你所见,其中包含了一个叫作holderldentity的字段,包含了当前领导者的名字。第一个成功将姓名填入该字段的实例成为领导者。实例之间会竞争,但是最终只有一个胜出。

还记得之前讨论过的乐观并发概念吗?乐观并发保证如果有多个实例尝试写名字到资源,只有一个会成功。根据是否写成功,每个实例就知道自己是否是领导者。

一旦成为领导者,必须顶起更新资源(默认每2秒),这样所有其他的实例就知道它是否还存活。当领导者宕机,其他实例会发现资源有一阵没被更新了,就会尝试将自己的名字写到资源中尝试成为领导者。简单吧,对吧?

THE END
1.最新各种小家电在这个快速发展的时代,随着科技的进步,家庭用电产品也在不断地更新换代。从无线充电垫到智能音箱,从自动扫地机器人到微波炉烹饪助手,一系列最新各种小家电涌现,让我们的生活更加便捷、高效。 首先,我们来看看那些让厨房工作更轻松的小家电。比如说,这款“烘焙大师”微波炉,它不仅能精确控制温度和时间,还配备有多种https://www.7h7ysy3k.com/xiao-jia-dian/411746.html
2.家电小能手从智能扫地机器人到超级多功能厨房帮手变革生活的趣味在这个快速变化的世界里,小家电不仅能够让我们的生活更加便捷,也为我们带来了无限的乐趣和创意。从智能扫地机器人到超级多功能厨房帮手,从追求高效节能到满足个性化需求,最新各种小家电正不断推陈出新,为我们带来全新的生活体验。 智慧照明系统 随着科技的发展,一些公司开始研发智能灯泡,它们可以根据室内外光线自动调节亮https://www.wdksiekjx.cn/ji-qi-ren/386935.html
3.智能家居新潮流小家电大变革智能家居新潮流小家电大变革 语音控制的便捷性 在不久前,人们还需要通过遥控器或者手机APP来控制家里的各种电子设备,如电视、空调和灯光。但随着科技的进步,现在我们可以通过简单的话语就能让这些设备按照我们的指令执行。比如说,有些智能音箱能够识别出不同的家庭成员的声音,并根据每个人的偏好自动调整温度、播放音乐https://www.kc4hfm3p3.cn/ji-qi-ren/399700.html
4.万能绝招网万能绝招网(www.wnjzw.com)是专门向社会推广各种神奇实用方法及创业信息和方法的专业网站,始创于2009就凭—10元生财小项目●奇谋—不用分文投资,月收入六千元的秘法●一小时赚50元的秘密●马上拥有的电子原件价值几万元的技术应用●最新节电技术—不用电的电灯●最新机密:不入网照看有线电视●制作http://www.wnjzw.com/
5.夏日清凉必备品冷热水壶除湿机等夏季小家电选项在炎热的夏季,保持家中的清凉是每个家庭成员都需要关注的问题。随着科技的不断进步,市场上涌现了各种各样的最新各种小家电,它们不仅能够帮助我们更好地应对酷暑,还能提升我们的生活质量。以下是一些值得推荐的冷热水壶和除湿机等夏季小家电。 冷热水壶——让饮用温水更加便捷 https://www.dzvj5vem.cn/jing-hua-qi/329606.html
6.最新各种小家电我都买了哪些超用心的新宠最新各种小家电我都买了哪些超用心的新宠 最近我发现自己越来越喜欢在家里使用各种小家电,它们不仅能让我的生活变得更加便捷,还能带给我很多方便。说到最新的各种小家电,我觉得这几款新出的产品真的是太棒了! 首先是智能扫地机器人,这个东西真的很神奇,完全可以替代传统的扫把和拖鞋。我只需要给它设定一个时间,就https://www.7eldx35s5.cn/wei-bo-lu/108885.html
7.亚洲东亚KaiserRoad御道品牌总代理汕头市加盟最新产品小家电亚洲东亚KaiserRoad 御道品牌总代理汕头市加盟最新产品小家电 '御道电器以“品质决定生命”为经营准则,御道电器以 “执着、把握、果断、激情”作为企业文化核心,树立产品与品牌的“差异化”,御道电器实施观念、技术、管理、机制、营销的创新,不断延伸御道品牌优势,专注于“御道电器”的品牌推广和管理,并致力于成为全国https://m.makepolo.com/product/100516449422.html
8.智能生活新篇章小家电大变革随着科技的飞速发展,小家电也在不断地向前进。从传统的简单功能到现在的智能互联,各种小家电正以其独特的方式改变我们的生活。 二、小型但不简单 如今的小家电虽然体积微小,但内涵丰富。例如,一些智能牙刷除了清洁牙齿之外,还能提供口腔健康分析和个性化建议;一些手持式扫地机器人,不仅可以自动清理地面,还能避开障碍物,https://www.hgvcqqzq.cn/ji-qi-ren/224060.html
9.4500+合作资源免费对接:实物寻分销善诊提供免费体验券上月全网曝光量1000万以上,可为联合活动的品牌方提供品牌曝光(含小程序、认领证书、公众号、直播等),提供优惠券分发(用户福利专区),短视频内容植入以及其他方式。 13、【芒果tv综艺节目】提供芒果tv资源,寻各大品牌主。 我需要:美妆护肤品 小家电 家纺产品 https://www.niaogebiji.com/article-27299-1.html
10.最新各种小家电我在网上找到了超级划算的小秘书机最新各种小家电我在网上找到了超级划算的小秘书机 在这个快节奏的时代,我们的生活中充满了各种各样的小家电,它们不仅让我们的日常生活变得更加便捷,也为我们的工作和娱乐带来了无限可能。今天,我要跟大家分享我最近在网上发现的一个超级划算的小秘书机。https://www.8nul9g47.cn/re-dian-zi-xun/404001.html
11.广州小家电批发市场电子产品的海量宝库广州小家电批发市场,是中国最大的电子产品批发市场之一,位于广东省广州市天河区五山路。它以其庞大的规模、丰富的商品种类和高效的物流配送系统闻名于世。这里不仅吸引了国内外商户,也成为了消费者购买各种小家电必备品的理想之地。 首先,广州小家电批发市场遍布着各式各样的电子产品,从日常生活中不可或缺的小器具,https://www.zjukjmv7.com/xiao-jia-dian/99251.html
12.「合肥荣事达怎么样」合肥荣事达小家电有限公司荣事达小家电依托产业链精品资源,占领常规通路、礼品通路、电子商务和电视购物等各种通路,成为销售渠道、营销实力有后劲的企业之一。在常规通路,稳坐市场前四的宝座,跨界营销和工厂店新模式的成功推广为企业赢得了广阔的市场空间。在礼品通路,荣电礼业以“三位一体、主动营销、会员专供”的黄金模式成为“中国实用礼品合作https://www.jobui.com/company/1530148/
13.家电生活10个非常实用的小家电让日常更便捷在现代生活中,随着科技的发展,小家电已经成为我们日常生活中的不可或缺之物,它们不仅能让我们的生活更加便捷,也能提高我们的工作效率。今天,我们就来一起探讨10个非常实用的小家电,以及它们是如何改变我们的日常生活。 智能扫地机器人 智能扫地机器人的出现,让清洁变得如此简单。它能够自动识别地板类型,无需人为干预https://www.dejsulm5.com/ji-qi-ren/85375.html