摘要

通过现代的 Web 服务,用户希望应用程序能够 24/7 全天候使用,开发人员希望每天可以多次发布部署新版本的应用程序。 容器化可以帮助软件包达成这些目标,使应用程序能够以简单快速的方式发布和更新,而无需停机。Kubernetes 帮助您确保这些容器化的应用程序在您想要的时间和地点运行,并帮助应用程序找到它们需要的资源和工具。Kubernetes 是一个可用于生产的开源平台,根据 Google 容器集群方面积累的经验,以及来自社区的最佳实践而设计。

一、Kubernetes系统架构

Kubernetes Cluster = N Master Node + N Worker Node:N主节点+N工作节点; N>=1。

我们可以用如下一张架构设计图更能深刻理解和快速掌握 Kubernetes 的核心组件的布局:

  • kube-apiserver,提供了 Kubernetes 各类资源对象(Pod、RC、Service 等)的增删改查及 watch 等 HTTP REST 接口,是整个系统的管理入口。
  • kube-controller-manager,作为集群内部的管理控制中心,负责集群内的 Node、Pod 副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)等对象管理。
  • kube-scheduler,集群调度器,提供了策略丰富,弹性拓扑能力。调度实现专注在业务可用性、性能和容量等能力上。
  • kube-proxy,提供南北向流量负载和服务发现的反向代理。
  • kubelet,是工作节点的管理 Pod 的控制程序,专门来调度启动 Pod 容器组。
  • etcd,是集群数据集中存储的分布式键值服务,用来存储 Kubernetes 集群中所有数据和状态的数据库。
  • cni-plugins,容器网络工作组维护的标准网络驱动如 fannel、ptp、host-local、portmap、tuning、vlan、sample、dhcp、ipvlan、macvlan、loopback、bridge 等网络插件供业务需求使用。这层 Overlay 网络只能包含一层,无法多层网络的互联互通。
  • runc,运行单个容器的容器运行时进程,遵循 OCI(开放容器标准)。
  • cri-o,容器运行时管理进程,类似 Docker 管理工具 containerd,国内业界普遍使用 containerd。

Kubernetes 设计理念和功能其实就是一个类似 Linux 的分层架构,如下图所示

  • 核心层:Kubernetes 最核心的功能,对外提供 API 构建高层的应用,对内提供插件式应用执行环境
  • 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS 解析等)
  • 管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态 Provision 等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy 等)
  • 接口层:kubectl 命令行工具、客户端 SDK 以及集群联邦
  • 生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴
    • Kubernetes 外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS 应用、ChatOps 等
    • Kubernetes 内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等

1.1 Kubernetes 的核心技术概念和 API 对象

API 对象是 Kubernetes 集群中的管理操作单元。Kubernetes 集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的 API 对象,支持对该功能的管理操作。例如副本集 Replica Set 对应的 API 对象是 RS。

每个 API 对象都有 3 大类属性:元数据 metadata、规范 spec 和状态 status。元数据是用来标识 API 对象的,每个对象都至少有 3 个元数据:namespace,name 和 uid;除此以外还有各种各样的标签 labels 用来标识和匹配不同的对象,例如用户可以用标签 env 来标识区分不同的服务部署环境,分别用 env=dev、env=testing、env=production 来标识开发、测试、生产的不同服务。规范描述了用户期望 Kubernetes 集群中的分布式系统达到的理想状态(Desired State),例如用户可以通过复制控制器 Replication Controller 设置期望的 Pod 副本数为 3;status 描述了系统实际当前达到的状态(Status),例如系统当前实际的 Pod 副本数为 2;那么复制控制器当前的程序逻辑就是自动启动新的 Pod,争取达到副本数为 3。

Kubernetes 中所有的配置都是通过 API 对象的 spec 去设置的,也就是用户通过配置系统的理想状态来改变系统,这是 Kubernetes 重要设计理念之一,即所有的操作都是声明式(Declarative)的而不是命令式(Imperative)的。声明式操作在分布式系统中的好处是稳定,不怕丢操作或运行多次,例如设置副本数为 3 的操作运行多次也还是一个结果,而给副本数加 1 的操作就不是声明式的,运行多次结果就错了。

1.2 Pod

Kubernetes 有很多技术概念,同时对应很多 API 对象,最重要的也是最基础的是 Pod。Pod 是在 Kubernetes 集群中运行部署应用或服务的最小单元,它是可以支持多容器的。Pod 的设计理念是支持多个容器在一个 Pod 中共享网络地址和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。Pod 对多容器的支持是 K8 最基础的设计理念。比如你运行一个操作系统发行版的软件仓库,一个 Nginx 容器用来发布软件,另一个容器专门用来从源仓库做同步,这两个容器的镜像不太可能是一个团队开发的,但是他们一块儿工作才能提供一个微服务;这种情况下,不同的团队各自开发构建自己的容器镜像,在部署的时候组合成一个微服务对外提供服务。Pod 是 Kubernetes 集群中所有业务类型的基础,可以看作运行在 Kubernetes 集群中的小机器人,不同类型的业务就需要不同类型的小机器人去执行。目前 Kubernetes 中的业务主要可以分为长期伺服型(long-running)、批处理型(batch)、节点后台支撑型(node-daemon)和有状态应用型(stateful application);分别对应的小机器人控制器为 Deployment、Job、DaemonSet 和 StatefulSet

1.3 副本控制器(Replication Controller,RC)

RC 是 Kubernetes 集群中最早的保证 Pod 高可用的 API 对象。通过监控运行中的 Pod 来保证集群中运行指定数目的 Pod 副本。指定的数目可以是多个也可以是 1 个;少于指定数目,RC 就会启动运行新的 Pod 副本;多于指定数目,RC 就会杀死多余的 Pod 副本。即使在指定数目为 1 的情况下,通过 RC 运行 Pod 也比直接运行 Pod 更明智,因为 RC 也可以发挥它高可用的能力,保证永远有 1 个 Pod 在运行。RC 是 Kubernetes 较早期的技术概念,只适用于长期伺服型的业务类型,比如控制小机器人提供高可用的 Web 服务。

1.4 副本集(Replica Set,RS)

RS 是新一代 RC,提供同样的高可用能力,区别主要在于 RS 后来居上,能支持更多种类的匹配模式。副本集对象一般不单独使用,而是作为 Deployment 的理想状态参数使用。

1.5 部署(Deployment)

部署表示用户对 Kubernetes 集群的一次更新操作。部署是一个比 RS 应用模式更广的 API 对象,可以是创建一个新的服务,更新一个新的服务,也可以是滚动升级一个服务。滚动升级一个服务,实际是创建一个新的 RS,然后逐渐将新 RS 中副本数增加到理想状态,将旧 RS 中的副本数减小到 0 的复合操作;这样一个复合操作用一个 RS 是不太好描述的,所以用一个更通用的 Deployment 来描述。以 Kubernetes 的发展方向,未来对所有长期伺服型的的业务的管理,都会通过 Deployment 来管理。

1.6 服务(Service)

RC、RS 和 Deployment 只是保证了支撑服务的微服务 Pod 的数量,但是没有解决如何访问这些服务的问题。一个 Pod 只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的 IP 启动一个新的 Pod,因此不能以确定的 IP 和端口号提供服务。要稳定地提供服务需要服务发现和负载均衡能力。服务发现完成的工作,是针对客户端访问的服务,找到对应的的后端服务实例。在 K8 集群中,客户端需要访问的服务就是 Service 对象。每个 Service 会对应一个集群内部有效的虚拟 IP,集群内部通过虚拟 IP 访问一个服务。在 Kubernetes 集群中微服务的负载均衡是由 Kube-proxy 实现的。Kube-proxy 是 Kubernetes 集群内部的负载均衡器。它是一个分布式代理服务器,在 Kubernetes 的每个节点上都有一个;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的 Kube-proxy 就越多,高可用节点也随之增多。与之相比,我们平时在服务器端做个反向代理做负载均衡,还要进一步解决反向代理的负载均衡和高可用问题。

1.7 任务(Job)

Job 是 Kubernetes 用来控制批处理型任务的 API 对象。批处理业务与长期伺服业务的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job 管理的 Pod 根据用户的设置把任务成功完成就自动退出了。成功完成的标志根据不同的 spec.completions 策略而不同:单 Pod 型任务有一个 Pod 成功就标志完成;定数成功型任务保证有 N 个任务全部成功;工作队列型任务根据应用确认的全局成功而标志成功。

1.8 后台支撑服务集(DaemonSet)

长期伺服型和批处理型服务的核心在业务应用,可能有些节点运行多个同类业务的 Pod,有些节点上又没有这类 Pod 运行;而后台支撑型服务的核心关注点在 Kubernetes 集群中的节点(物理机或虚拟机),要保证每个节点上都有一个此类 Pod 运行。节点可能是所有集群节点也可能是通过 nodeSelector 选定的一些特定节点。典型的后台支撑型服务包括,存储,日志和监控等在每个节点上支持 Kubernetes 集群运行的服务。

1.9 有状态服务集(StatefulSet)

Kubernetes 在 1.3 版本里发布了 Alpha 版的 PetSet 功能,在 1.5 版本里将 PetSet 功能升级到了 Beta 版本,并重新命名为 StatefulSet,最终在 1.9 版本里成为正式 GA 版本。在云原生应用的体系里,有下面两组近义词;第一组是无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable);第二组是有状态(stateful)、宠物(pet)、有名(having name)、不可丢弃(non-disposable)。RC 和 RS 主要是控制提供无状态服务的,其所控制的 Pod 的名字是随机设置的,一个 Pod 出故障了就被丢弃掉,在另一个地方重启一个新的 Pod,名字变了。名字和启动在哪儿都不重要,重要的只是 Pod 总数;而 StatefulSet 是用来控制有状态服务,StatefulSet 中的每个 Pod 的名字都是事先确定的,不能更改。StatefulSet 中 Pod 的名字的作用,并不是《千与千寻》的人性原因,而是关联与该 Pod 对应的状态。

对于 RC 和 RS 中的 Pod,一般不挂载存储或者挂载共享存储,保存的是所有 Pod 共享的状态,Pod 像牲畜一样没有分别(这似乎也确实意味着失去了人性特征);对于 StatefulSet 中的 Pod,每个 Pod 挂载自己独立的存储,如果一个 Pod 出现故障,从其他节点启动一个同样名字的 Pod,要挂载上原来 Pod 的存储继续以它的状态提供服务。

适合于 StatefulSet 的业务包括数据库服务 MySQL 和 PostgreSQL,集群化管理服务 ZooKeeper、etcd 等有状态服务。StatefulSet 的另一种典型应用场景是作为一种比普通容器更稳定可靠的模拟虚拟机的机制。传统的虚拟机正是一种有状态的宠物,运维人员需要不断地维护它,容器刚开始流行时,我们用容器来模拟虚拟机使用,所有状态都保存在容器里,而这已被证明是非常不安全、不可靠的。使用 StatefulSet,Pod 仍然可以通过漂移到不同节点提供高可用,而存储也可以通过外挂的存储来提供高可靠性,StatefulSet 做的只是将确定的 Pod 与确定的存储关联起来保证状态的连续性。

1.10 集群联邦(Federation)

Kubernetes 在 1.3 版本里发布了 beta 版的 Federation 功能。在云计算环境中,服务的作用距离范围从近到远一般可以有:同主机(Host,Node)、跨主机同可用区(Available Zone)、跨可用区同地区(Region)、跨地区同服务商(Cloud Service Provider)、跨云平台。Kubernetes 的设计定位是单一集群在同一个地域内,因为同一个地区的网络性能才能满足 Kubernetes 的调度和计算存储连接要求。而联合集群服务就是为提供跨 Region 跨服务商 Kubernetes 集群服务而设计的。

每个 Kubernetes Federation 有自己的分布式存储、API Server 和 Controller Manager。用户可以通过 Federation 的 API Server 注册该 Federation 的成员 Kubernetes Cluster。当用户通过 Federation 的 API Server 创建、更改 API 对象时,Federation API Server 会在自己所有注册的子 Kubernetes Cluster 都创建一份对应的 API 对象。在提供业务请求服务时,Kubernetes Federation 会先在自己的各个子 Cluster 之间做负载均衡,而对于发送到某个具体 Kubernetes Cluster 的业务请求,会依照这个 Kubernetes Cluster 独立提供服务时一样的调度模式去做 Kubernetes Cluster 内部的负载均衡。而 Cluster 之间的负载均衡是通过域名服务的负载均衡来实现的。

Federation V1 的设计是尽量不影响 Kubernetes Cluster 现有的工作机制,这样对于每个子 Kubernetes 集群来说,并不需要更外层的有一个 Kubernetes Federation,也就是意味着所有现有的 Kubernetes 代码和机制不需要因为 Federation 功能有任何变化。

1.11 存储卷(Volume)

Kubernetes 集群中的存储卷跟 Docker 的存储卷有些类似,只不过 Docker 的存储卷作用范围为一个容器,而 Kubernetes 的存储卷的生命周期和作用范围是一个 Pod。每个 Pod 中声明的存储卷由 Pod 中的所有容器共享。Kubernetes 支持非常多的存储卷类型,特别的,支持多种公有云平台的存储,包括 AWS,Google 和 Azure 云;支持多种分布式存储包括 GlusterFS 和 Ceph;也支持较容易使用的主机本地目录 emptyDir, hostPath 和 NFS。Kubernetes 还支持使用 Persistent Volume Claim 即 PVC 这种逻辑存储,使用这种存储,使得存储的使用者可以忽略后台的实际存储技术(例如 AWS,Google 或 GlusterFS 和 Ceph),而将有关存储实际技术的配置交给存储管理员通过 Persistent Volume 来配置。

1.12 持久存储卷(Persistent Volume,PV)和持久存储卷声明(Persistent Volume Claim,PVC)

PV:持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置(表示持久卷)

PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷规格(表示持久卷申请)

1.13 节点(Node)

Kubernetes 集群中的计算能力由 Node 提供,最初 Node 称为服务节点 Minion,后来改名为 Node。Kubernetes 集群中的 Node 也就等同于 Mesos 集群中的 Slave 节点,是所有 Pod 运行所在的工作主机,可以是物理机也可以是虚拟机。不论是物理机还是虚拟机,工作主机的统一特征是上面要运行 kubelet 管理节点上运行的容器。

1.14 密钥对象(Secret)

Secret 是用来保存和传递密码、密钥、认证凭证这些敏感信息的对象。使用 Secret 的好处是可以避免把敏感信息明文写在配置文件里。在 Kubernetes 集群中配置和使用服务不可避免的要用到各种敏感信息实现登录、认证等功能,例如访问 AWS 存储的用户名密码。为了避免将类似的敏感信息明文写在所有需要使用的配置文件中,可以将这些信息存入一个 Secret 对象,而在配置文件中通过 Secret 对象引用这些敏感信息。这种方式的好处包括:意图明确,避免重复,减少暴漏机会。

1.15 用户帐户(User Account)和服务帐户(Service Account)

顾名思义,用户帐户为人提供账户标识,而服务账户为计算机进程和 Kubernetes 集群中运行的 Pod 提供账户标识。用户帐户和服务帐户的一个区别是作用范围;用户帐户对应的是人的身份,人的身份与服务的 namespace 无关,所以用户账户是跨 namespace 的;而服务帐户对应的是一个运行中程序的身份,与特定 namespace 是相关的。

1.16 命名空间(Namespace)

命名空间为 Kubernetes 集群提供虚拟的隔离作用,Kubernetes 集群初始有两个命名空间,分别是默认命名空间 default 和系统命名空间 kube-system,除此以外,管理员可以可以创建新的命名空间满足需要。

1.17 RBAC 访问授权

Kubernetes 在 1.3 版本中发布了 alpha 版的基于角色的访问控制(Role-based Access Control,RBAC)的授权模式。相对于基于属性的访问控制(Attribute-based Access Control,ABAC),RBAC 主要是引入了角色(Role)和角色绑定(RoleBinding)的抽象概念。在 ABAC 中,Kubernetes 集群中的访问策略只能跟用户直接关联;而在 RBAC 中,访问策略可以跟某个角色关联,具体的用户在跟一个或多个角色相关联。显然,RBAC 像其他新功能一样,每次引入新功能,都会引入新的 API 对象,从而引入新的概念抽象,而这一新的概念抽象一定会使集群服务管理和使用更容易扩展和重用。

二、主控节点组件的使用策略

从刚接手维护 Kubernetes 集群的新用户角度考虑,一般第一步要做的就是遵循安装文档把集群搭建起来。世面上把集群安装的工具分为两类,一类是学习用集群安装工具:

  • Docker Desktop for Kubernetes
  • Kind(Kubernetes IN Docker)
  • Minikube
  • MicroK8s

另一类是生产级别的安装工具:

  • kubeadm
  • kops
  • kubespray
  • Rancher Kubernetes Engine(RKE)
  • K3s

其中 kubeadm 和 RKE 采用了容器组件的形式来安装 Kubernetes 集群。虽然采用系统容器来运行集群环境对主机系统的侵入性降低了,但运维的维护成本会线性增加。维护过容器应用的用户都会知道,容器技术主要是对运行进程的隔离,它并不是为系统进程设计的隔离工具,容器的生命周期都很短,随时可以失败。当容器进程出错的时候,隔离环境很难还原故障的环境。常用的办法就是通过重启容器来忽略故障,期望能快速排除故障。

但是往往这种潜在的小问题,就是让你很烦恼并对长时间无法重现感到烦恼。那么对于系统进程,Linux 是有对应的系统维护工具 Systemd 来维护的。它的生态足够完善,并能在多种 Linux 环境中保持行为一致。当出现问题的时候,运维可以直接登录主机快速排查系统日志来定位排错。根据这样的经验积累,笔者推荐生产环境还是采用原生进程的方式来维护 Kubernetes 的组件,让运维可以集中精力在集群架构上多做冗余优化。

接下来我们重新理解 etcd 集群的架构。根据 Kubernetes 官方文档的参考资料介绍,通常按照 etcd 集群的拓扑模型可以分为两类生产级别的 Kubernetes 集群。

栈式 etcd 集群拓扑:

独立式 etcd 集群拓扑:

参考上面的架构图,我们可以看到 etcd 集群的部署方式影响着 Kubernetes 集群的规模。在落地实践中因为采购的机器都是高性能大内存的刀片服务器,业务部门的期望是能充分的把这些资源利用上,并不期望用这些机器来跑集群控制管理组件。当遇到这种情况,很多部署方案会采用第一种方案,是把主机节点、工作节点和 etcd 集群都放在一起复用资源。

从高可用架构来讲,高度的应用密度集合并不能给用户带来无限制的好处。试想当出现节点宕机后这种架构的隐患是业务应用会受到极大的影响。所以通常的高可用架构经验是工作节点一定要和主控节点分开部署。在虚拟化混合环境下,主控节点可以使用小型的虚拟机来部署最为合适。当你的基础设施完全采用物理机的时候,直接使用物理机来部署主控节点是很浪费的,建议在现有物理机集群中先使用虚拟化软件虚拟化一批中小型虚拟机来提供管理节点的管理资源。开源的管理系统有 OpenStack,商业的方案是 VMware vSphere,按需求丰俭由人即可。

除了以上标准的部署解决方案,社区还提供了单机模式部署的 K3s 集群模式。把核心组件直接绑定为一个单体二进制文件,这样的好处就是这个系统进程只有一个,非常容易管理和恢复集群。在纯物理机环境,使用这种单点集群架构来部署应用,我们可以通过冗余部署多套集群的方式来支持应用的高可用和容灾。下图就是 K3s 的具体架构图:

K3s本来是为嵌入式环境提供的精简 Kubernetes 集群,但是这个不妨碍我们在生产实践中灵活运用。K3s 提供的是原生 Kubernetes 的所有稳定版本 API 接口,在x86 集群下可以发挥同样的编排容器业务的能力。

三、工作节点组件的使用策略

在工作节点上默认安装的组件是 kubelet 和 kube-proxy。在实际部署的过程中,kubelet 是有很多配置项需要调优的,这些参数会根据业务需求来调整,并不具备完全一样的配置方案。让我们再次认识一下 kubelet 组件,对于 kubelet,它是用来启动 Pod 的控制管理进程。虽然 kubelet 总体启动容器的工作流程,但是具体的操作它是依赖主机层面的容器引擎来管理的。对于依赖的容器引擎,我们可以选择的组件有 containerd、ori-o等。Kubernetes 默认配置的组件是 cri-o。但是业界实际落地部署最多的还是 containerd,因为它的部署量巨大,很多潜在的问题都会被第一时间解决。containerd 是从 docker 引擎抽离出来的容器管理工具,用户具备长期的使用经验,这些经验对于运维和管理容器会带来很多潜在的使用信心。

ctr 是 containerd 的客户端级别的命令行工具,主要的能力是管理运行中的容器。crictl 这个工具是管理 CRI 运行时环境的,在上图中是操作 cri-containerd 的组件。它的功能主要聚焦在 Pod 层面的镜像加载和运行。还请大家注意下 Docker、ctr、crictl 三者细节实现上的差别。举个例子,Docker 和 ctr 确实都是管理主机层面的镜像和容器的,但是他们都有自己独立的管理目录,所以你即使是同样的加载镜像的操作,在主机存储的文件位置也是不同的,他们的镜像层无法复用。而 crictl 是操作 Pod 的,它并不是直接操作镜像的进程,一般把命令发送给对应的镜像管理程序,比如 containerd 进程。

另外一个组件是 kube-proxy,它是面向 Service 对象概念的南北向反向代理服务。通过对接 Endpoint 对象,可以按照均衡策略来负载流量。另外为了实现集群全局的服务发现机制,每一个服务都会定义全局唯一的名字,也就是 Service 的名字。这个名字可以通过附加的组件 coredns 来实现集群内的名字解析,也就是服务发现。对于流量的负载,Kubernetes 是通过 iptables 或 IPVS(IP Virtual Server)来实现。

在正常的集群规模下,Service 并不会超过500个,但是华为容器技术团队做了一个极限压测,发现了 iptables 在实现反向代理的时候出现的性能瓶颈。试验验证了,当 Service 增加到足够大的时候,Service 规则增加对于 iptables 是 O(n) 的复杂度,而切换到 IPVS 却是 O(1)。压测结果如下:

service基数 1 5000 2000
Reules基数 8 40000 160000
增加1条IPtables 50us 11min 5hours
增加1条IPVS规则 30us 50us 70us

目前 Kubernetes 默认反向代理激活模块为 IPVS 模式,iptables 和 IPVS 都是基于 Linux 的子模块 netfilter,他们的相同点就是做反向代理,但是还是有以下 3 点区别需要知道:

  • IPVS 提供大规模集群扩展性和高性能
  • IPVS 提供更丰富的负载均衡算法(最小负载、最小链接数、基于局部性调度、加权轮叫等等)
  • IPVS 支持服务器健康检查和网络重试机制

当 IPVS 在处理流量的 packet filtering、SNAT 和 masquerade 需求时,仍然需要使用 iptables 的扩展包工具 ipset 配置固定数量的转换规则,不会像 iptables 模式下随着服务和 Pod 的增加而线性写入规则导致系统的计算 CPU 负载加大,影响集群的处理能力。

以下表单是 IPVS 模式需要使用的 ipset 规则:

set name members usage
KUBE-CLUSTER-IP All service IP + port Mark-Masq for cases that masquerade-all=true or clusterCIDR specified
KUBE-LOOP-BACK All service IP + port + IP masquerade for solving hairpin purpose
KUBE-EXTERNAL-IP service external IP + port masquerade for packages to external IPs
KUBE-LOAD-BALANCER load balancer ingress IP + port masquerade for packages to load balancer type service
KUBE-LOAD-BALANCER-LOCAL LB ingress IP + port with externalTrafficPolicy=local accept packages to load balancer with externalTrafficPolicy=local
KUBE-LOAD-BALANCER-FW load balancer ingress IP + port with loadBalancerSourceRanges package filter for load balancer with loadBalancerSourceRanges specified
KUBE-LOAD-BALANCER-SOURCE-CIDR load balancer ingress IP + port + source CIDR package filter for load balancer with loadBalancerSourceRanges specified
KUBE-NODE-PORT-TCP nodeport type service TCP port masquerade for packets to nodePort(TCP)
KUBE-NODE-PORT-LOCAL-TCP nodeport type service TCP port with externalTrafficPolicy=local accept packages to nodeport service with externalTrafficPolicy=local
KUBE-NODE-PORT-UDP nodeport type service UDP port masquerade for packets to nodePort(UDP)
KUBE-NODE-PORT-LOCAL-UDP nodeport type service UDP port with externalTrafficPolicy=local accept packages to nodeport service with externalTrafficPolicy=local

另外,IPVS模式会在以下场景下降级使用iptables 模式:

  • kube-proxy 启动的同时启用 --masquerade-all=true
  • kube-proxy 启动时指定 Pod 层网段
  • Load Balancer 类型的 Service
  • NodePort 类型的 Service
  • 配置了externalIP的Service

四、工作节点附加组件的使用策略

提到附加的组件,一般常识是这些组件可有可无,锦上添花的能力而已。但是在 Kubernetes 集群中,这些附加组件是不得不安装的,不然整个集群就是一套鸡肋的展览品。Kubernetes 官方把这些附加组件分为以下五类:

  • 网络和网络策略
  • 服务发现
  • 可视化管理
  • 基础设施
  • 遗留组件

大家看到标题,基本上就能理解这些组件的用处。我这里还是从实用的角度和大家一起重新认识一下这些组件,为之后的使用提供经验参考。

1. 网络和网络策略

对于网络,我们主要指容器网络。注意在 Kubernetes 集群里面,是有两层虚拟网络的。一说虚拟网络,就会有丢包率,这个是以往虚拟化环境不可想象的问题。为了提高或者说规避这方面的棘手问题,我们会放弃所有官方的方案,采用传统的网络方案来支持。当然传统的网络方案大都不是为 Kubernetes 网络设计的,需要做很多自定义适配工作来完善体验。在不理想的传统方案之外,容器网络方案中最流行的有 Calico、Cilium、Flannel、Contiv 等等。采用这些方案之后,随着业务流量的增加一定会带来网络丢包的情况。网络丢包带来的问题是业务处理能力的降低,为了恢复业务实例的处理能力,我们常规的操作是水平扩展容器实例数。注意,正是因为实例数的增加反而会提高业务处理能力,让运维人员忽略容器网络带来的性能损耗。另外,Kubernetes 在业务实践中还参考了主流网络管理的需求设计,引入了 Network Policies。这些策略定义了 Pod 之间的连通关系,方便对业务容器组的安全网络隔离。当然笔者在实践中发现,这些策略完全依赖容器网络的实现能力,依赖性强,只能作为试验品体验,但是在实际业务中,还没有看到实际的能力优势。

2. 服务发现

目前提供的能力就是给 Pod 提供 DNS 服务,并引入了域名的定义规则。官方认可的只有 CoreDNS。注意 ,这个服务发现只能在集群内部使用。不推荐直接暴露给外部服务,集群对外暴露的服务仍然是 IP 和端口。外部 DNS 可以灵活的指定这个固定 IP 来让业务在全局服务发现。

3. 可视化管理

官方提供了 Dashboard,这是官方提供的标准管理集群的 web 界面,很多开发集成测试环境,使用它就可以满足业务管理的需求。这个可选安装。

4. 基础设施

官方提供了 KubeVirt,是可以让 Kubernetes 运行虚拟机的附加组件,默认运行在裸机群集上。从目前的实践经验来看,这种能力还属于试验性的能力,一般很少人使用。

5. 遗留组件

对于很多老版本的 Kubernetes,有很多历史遗留的组件可以选用,所以官方把这些可选的组件都保留了下来,帮助用户在迁移集群版本的过程中可以继续发挥老集群的能力。一般很少人使用。

通过三个纬度的介绍,我相信大家对 Kubernetes 的核心组件有了更深入的理解。在生产实践中,为了标准化运维模型,我们对 Kubernetes 的组件可以按照业务需求定义一个基线模型,有选择的使用这些组件,相信一定可以规避很多兼容性的问题。在笔者遇到的大部分的 Kubernetes 集群故障案例中,大部分就是对组件的错用或者误用,让问题变的更复杂,更难以复现。当然,云端的 Kubernetes 可以彻底解决基线的问题,我相信未来用户会越来越容易的使用到靠谱的 Kubernetes 集群环境。只是你一定要记住,我们只是把运维 Kubernetes 的难题交给专业的云服务开发者而已。

五、kube-proxy工作原理

说到kube-proxy,就不得不提到k8s中service,下面对它们两做简单说明:

  • kube-proxy其实就是管理service的访问入口,包括集群内Pod到Service的访问和集群外访问service。
  • kube-proxy管理sevice的Endpoints,该service对外暴露一个Virtual IP,也成为Cluster IP, 集群内通过访问这个Cluster IP:Port就能访问到集群内对应的serivce下的Pod。
  • service是通过Selector选择的一组Pods的服务抽象,其实就是一个微服务,提供了服务的LB和反向代理的能力,而kube-proxy的主要作用就是负责service的实现。
  • service另外一个重要作用是,一个服务后端的Pods可能会随着生存灭亡而发生IP的改变,service的出现,给服务提供了一个固定的IP,而无视后端Endpoint的变化。

5.1 服务发现

k8s提供了两种方式进行服务发现

环境变量: 当你创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。比如,一个ServiceName为redis-master的Service,对应的ClusterIP:Port为10.0.0.11:6379,则其对应的环境变量为:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379     REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379    REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

DNS:这也是k8s官方强烈推荐的方式。可以通过cluster add-on的方式轻松的创建KubeDNS来对集群内的Service进行服务发现。

5.2 发布(暴露)服务

k8s原生的,一个Service的ServiceType决定了其发布服务的方式。

  • ClusterIP:这是k8s默认的ServiceType。通过集群内的ClusterIP在内部发布服务。
  • NodePort:这种方式是常用的,用来对集群外暴露Service,你可以通过访问集群内的每个NodeIP:NodePort的方式,访问到对应Service后端的Endpoint。
  • LoadBalancer: 这也是用来对集群外暴露服务的,不同的是这需要Cloud Provider的支持,比如AWS等。
  • ExternalName:这个也是在集群内发布服务用的,需要借助KubeDNS(version >= 1.7)的支持,就是用KubeDNS将该service和ExternalName做一个Map,KubeDNS返回一个CNAME记录。

5.3 kube-proxy内部原理

kube-proxy当前实现了两种proxyMode:userspace和iptables。其中userspace mode是v1.0及之前版本的默认模式,从v1.1版本中开始增加了iptables mode,在v1.2版本中正式替代userspace模式成为默认模式。

5.3.1 userspace mode userspace是在用户空间,通过kube-proxy来实现service的代理服务。

可见,这种mode最大的问题是,service的请求会先从用户空间进入内核iptables,然后再回到用户空间,由kube-proxy完成后端Endpoints的选择和代理工作,这样流量从用户空间进出内核带来的性能损耗是不可接受的。这也是k8s v1.0及之前版本中对kube-proxy质疑最大的一点,因此社区就开始研究iptables mode。

$ kubectl get serviceNAME             LABELS                                    SELECTOR              IP(S)            PORT(S)
kubernetes       component=apiserver,provider=kubernetes   <none>                10.254.0.1       443/TCP
ssh-service1     name=ssh,role=service                     ssh-service=true      10.254.132.107   2222/TCP$ kubectl describe service ssh-service1 Name:          ssh-service1
Namespace:      default
Labels:         name=ssh,role=service
Selector:       ssh-service=true
Type:           LoadBalancer
IP:         10.254.132.107
Port:           <unnamed> 2222/TCP
NodePort:       <unnamed> 30239/TCP
Endpoints:      <none>
Session Affinity:   None
No events.

NodePort的工作原理与ClusterIP大致相同,发送到某个NodeIP:NodePort的请求,通过iptables重定向到kube-proxy对应的端口(Node上的随机端口)上,然后由kube-proxy再将请求发送到其中的一个Pod:TargetPort。

这里,假如Node的ip为10.0.0.5,则对应的iptables如下:

sudo iptables -S -t nat
...
-A KUBE-NODEPORT-CONTAINER -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 30239 -j REDIRECT --to-ports 36463
-A KUBE-NODEPORT-HOST -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 30239 -j DNAT --to-destination 10.0.0.5:36463
-A KUBE-PORTALS-CONTAINER -d 10.254.132.107/32 -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 2222 -j REDIRECT --to-ports 36463
-A KUBE-PORTALS-HOST -d 10.254.132.107/32 -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 2222 -j DNAT --to-destination 10.0.0.5:36463

可见:访问10.0.0.5:30239端口会被转发到node上的36463端口(随机监听端口)。而且在访问clusterIP 10.254.132.107的2222端口时,也会把请求转发到本地的36463端口。 36463端口实际被kube-proxy所监听,将流量进行导向到后端的pod上。

5.3.2 iptables mode 另一种mode是iptables,它完全利用内核iptables来实现service的代理和LB。是v1.2及之后版本默认模式,其原理图如下所示:

iptables mode因为使用iptable NAT来完成转发,也存在不可忽视的性能损耗。另外,如果集群中存在上万的Service/Endpoint,那么Node上的iptables rules将会非常庞大,性能还会再打折扣。这也导致,目前大部分企业用k8s上生产时,都不会直接用kube-proxy作为服务代理,而是通过自己开发或者通过Ingress Controller来集成HAProxy, Nginx来代替kube-proxy。

#Example iptables的方式则是利用了linux的iptables的nat转发进行实现。apiVersion: v1
kind: Service
metadata:labels:name: mysqlrole: servicename: mysql-service
spec:ports:- port: 3306targetPort: 3306nodePort: 30964type: NodePortselector:mysql-service: "true"

mysql-service对应的nodePort暴露出来的端口为30964,对应的cluster IP(10.254.162.44)的端口为3306,进一步对应于后端的pod的端口为3306。

mysql-service后端代理了两个pod,ip分别是192.168.125.129和192.168.125.131。先来看一下iptables。

iptables -S -t nat
...
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.129:3306
-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.131:3306
-A KUBE-SERVICES -d 10.254.162.44/32 -p tcp -m comment --comment "default/mysql-service: cluster IP" -m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -j KUBE-SEP-IN2YML2VIFH5RO2T首先如果是通过node的30964端口访问,则会进入到以下链:-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM然后进一步跳转到KUBE-SVC-67RL4FN6JRUPOJYM的链:-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -j KUBE-SEP-IN2YML2VIFH5RO2T这里利用了iptables的–probability的特性,使连接有50%的概率进入到KUBE-SEP-ID6YWIT3F6WNZ47P链,50%的概率进入到KUBE-SEP-IN2YML2VIFH5RO2T链。KUBE-SEP-ID6YWIT3F6WNZ47P的链的具体作用就是将请求通过DNAT发送到192.168.125.129的3306端口。-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.129:3306同理KUBE-SEP-IN2YML2VIFH5RO2T的作用是通过DNAT发送到192.168.125.131的3306端口。-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.131:3306分析完nodePort的工作方式,接下里说一下clusterIP的访问方式。 对于直接访问cluster IP(10.254.162.44)的3306端口会直接跳转到KUBE-SVC-67RL4FN6JRUPOJYM。-A KUBE-SERVICES -d 10.254.162.44/32 -p tcp -m comment --comment "default/mysql-service: cluster IP" -m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM接下来的跳转方式同NodePort方式。

六、kubenetes中etcd的相关原理

Etcd 是 Kubernetes 集群中的一个十分重要的组件,用于保存集群所有的网络配置和对象的状态信息。在后面具体的安装环境中,我们安装的 etcd 的版本是 v3.1.5,整个 Kubernetes 系统中一共有两个服务需要用到 etcd 用来协同和存储配置,分别是:

  • 网络插件 flannel、对于其它网络插件也需要用到 etcd 存储网络的配置信息
  • Kubernetes 本身,包括各种对象的状态和元信息配置

6.1 etcd的原理

Etcd 使用的是 raft 一致性算法来实现的,是一款分布式的一致性 KV 存储,主要用于共享配置和服务发现。

Etcd 架构与实现解析

6.2 使用 Etcd 存储 Flannel 网络信息

我们在安装 Flannel 的时候配置了 FLANNEL_ETCD_PREFIX="/kube-centos/network" 参数,这是 Flannel 查询 etcd 的目录地址。

查看 Etcd 中存储的 flannel 网络信息:

# etcdctl --ca-file=/etc/kubernetes/ssl/ca.pem --cert-file=/etc/kubernetes/ssl/kubernetes.pem --key-file=/etc/kubernetes/ssl/kubernetes-key.pem ls /kube-centos/network -r
2018-01-19 18:38:22.768145 I | warning: ignoring ServerName for user-provided CA for backwards compatibility is deprecated
/kube-centos/network/config
/kube-centos/network/subnets
/kube-centos/network/subnets/172.30.31.0-24
/kube-centos/network/subnets/172.30.20.0-24
/kube-centos/network/subnets/172.30.23.0-24```查看 flannel 的配置:```bash
$ etcdctl --ca-file=/etc/kubernetes/ssl/ca.pem --cert-file=/etc/kubernetes/ssl/kubernetes.pem --key-file=/etc/kubernetes/ssl/kubernetes-key.pem get /kube-centos/network/config
2018-01-19 18:38:22.768145 I | warning: ignoring ServerName for user-provided CA for backwards compatibility is deprecated
{"Network": "172.30.0.0/16", "SubnetLen": 24, "Backend": { "Type": "host-gw"} }

6.3 使用 Etcd 存储 Kubernetes 对象信息

Kubernetes 使用 etcd v3 的 API 操作 etcd 中的数据。所有的资源对象都保存在 /registry 路径下,如下:

ThirdPartyResourceData
apiextensions.k8s.io
apiregistration.k8s.io
certificatesigningrequests
clusterrolebindings
clusterroles
configmaps
controllerrevisions
controllers
daemonsets
deployments
events
horizontalpodautoscalers
ingress
limitranges
minions
monitoring.coreos.com
namespaces
persistentvolumeclaims
persistentvolumes
poddisruptionbudgets
pods
ranges
replicasets
resourcequotas
rolebindings
roles
secrets
serviceaccounts
services
statefulsets
storageclasses
thirdpartyresources

如果你还创建了 CRD(自定义资源定义),则在此会出现 CRD 的 API。

6.4 查看集群中所有的 Pod 信息

例如我们直接从 etcd 中查看 kubernetes 集群中所有的 pod 的信息,可以使用下面的命令:

ETCDCTL_API=3 etcdctl get /registry/pods --prefix -w json|python -m json.tool

此时将看到 json 格式输出的结果,其中的key使用了base64 编码,关于 etcdctl 命令的详细用法请参考 使用 etcdctl 访问 kubernetes 数据。

Etcd 数据备份:我们安装的时候指定的 Etcd 数据的存储路径是 /var/lib/etcd,一定要对该目录做好备份。

博文参考

Kubernetes——K8s架构与组件原理相关推荐

  1. kubernetes(k8s)架构和组件,工作流程 ,资源

    文章目录 一: kubernetes 概述 1.1 K8S 是什么 1.2 为什么使用k8s 1.2.1 传统后端部署的方法和缺陷 1.2.2 裸跑docker的缺陷 1.3 k8s的特性 二: ku ...

  2. Kubernetes精华问答 | K8s架构和组件是怎样的?

    kubernetes,简称K8s,是用8代替8个字符"ubernete"而成的缩写.是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化 ...

  3. K8S架构常用组件核心资源

    架构 k8s的物理架构师master/node模式.集群至少需要一个master结点和多个工作结点,Master节点是集群的控制节点,负责整个集群的管理和控制,主要用于暴露API.调度部署和对节点进行 ...

  4. Kubernetes/K8s架构师实战集训营【高级班】

    [内容] ┣━━第 1 章:Ansible 自动化部署 K8S 集群 [3.2G] ┃ ┣━━工具 ┃ ┣━━文档 ┃ ┣━━01-Ansible 概述,基本使用.mp4 [341.5M] ┃ ┣━━ ...

  5. Kubernetes K8S架构师实战集训营-[高级班] 视频教程

    [内容]       ┣━━第 1 章:Ansible 自动化部署 K8S 集群 [3.2G]       ┃    ┣━━工具       ┃    ┣━━文档       ┃    ┣━━01-A ...

  6. 从零开始入门 K8s | Kubernetes 存储架构及插件使用

    作者 | 阚俊宝 阿里巴巴高级技术专家 本文整理自<CNCF x Alibaba 云原生技术公开课>第 21 讲. 关注"阿里巴巴云原生"公众号,回复关键词**&quo ...

  7. 云原生钻石课程 | 第6课:Kubernetes网络架构原理深度剖析(上)

    点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 本篇文章来自<华为云云原生王者之路训练营>钻 ...

  8. kubernetes的部署架构以及工作原理

    kubernetes 整体架构 Kubernetes最初源于谷歌内部的Borg,提供了面向应用的容器集群部署和管理系统,所以整体的架构和borg很相似,整个架构有api server,control ...

  9. 云原生钻石课程 | 第5课:Kubernetes存储架构原理深度剖析(下)

    点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 本篇文章来自<华为云云原生王者之路训练营>钻 ...

最新文章

  1. thinkphp-许愿墙-2
  2. android listview ontouchlistener,Android ListView监听滑动事件的方法(详解)
  3. 实例47:python
  4. GIF动画解析RNN,LSTM,GRU
  5. 华为ac控制器web配置手册_欧姆龙AC伺服系统1S系列产品型号说明及功能介绍
  6. 跨专业留学学计算机硕士,跨专业申请中国香港计算机硕士需要注意哪些?
  7. Python基础import导包问题
  8. 在工作中你卑微到什么程度?
  9. 方法、hadoop源码之JobQueueTaskScheduler-by小雨
  10. 7限制cpu使用_macOS限制CPU或进程使用率
  11. 伪原创方法-学习一下
  12. sysbench线程数_分享3个Sysbench性能压测脚本及多并发压测过程
  13. RecyclerView Widget 使用
  14. 开源中文语音识别项目介绍:ASRFrame
  15. python 网页上显示数据_Python实战【第二节】在网页上显示信息
  16. Hadoop 与 HBase 版本对应
  17. 【Python量化】 Scipy库求解最优资产投资组合
  18. codeforces 894A QAQ
  19. python爬虫爬取下厨房食谱,周末聚餐真的停不下来
  20. Html 用户反馈界面

热门文章

  1. LeetCode题解(1386):安排电影院座位(Python)
  2. 解决项目版本冲突——maven-shade插件使用
  3. 《人工智能及其应用》第2章书后题 | 西电《人工智能导论》作业
  4. Android Spinner
  5. ethos专用系统 登录 教程
  6. S4HANA For ABAP(6):ABAP Channels
  7. solox,免费的移动应用性能测试工具
  8. 史上最全的一句话木马
  9. 敦煌网跨境电商实验报告_2018年母婴用品行业跨境电商报告及2019年产品线发展解析...
  10. 排序算法 | 快速排序算法原理及实现和优化(一)