Kubernetes 已经成为容器编排领域的王者,它是基于容器的集群编排引擎,具备扩展集群、滚动升级回滚、弹性伸缩、自动治愈、服务发现等多种特性能力。

 

 

 

不懂Kubernetes,被老板邀请爬山!_java

 

图片来自 Pexels

 

 

 

本文将带着大家快速了解 Kubernetes ,了解我们谈论 Kubernetes 都是在谈论什么。

 

 

 

Kubernetes 架构

 

 

不懂Kubernetes,被老板邀请爬山!_java_02

从宏观上来看 Kubernetes 的整体架构,包括 Master、Node 以及 Etcd。

 

 

 

Master 即主节点,负责控制整个 Kubernetes 集群,它包括 API Server、Scheduler、Controller 等组成部分,它们都需要和 Etcd 进行交互以存储数据:

  • API Server:主要提供资源操作的统一入口,这样就屏蔽了与 Etcd 的直接交互。功能包括安全、注册与发现等。

  • Scheduler:负责按照一定的调度规则将 Pod 调度到 Node 上。

  • Controller:资源控制中心,确保资源处于预期的工作状态。

 

 

 

Node 即工作节点,为整个集群提供计算力,是容器真正运行的地方,包括运行容器、kubelet、kube-proxy:

  • kubelet:主要工作包括管理容器的生命周期、结合 cAdvisor 进行监控、健康检查以及定期上报节点状态。

  • kube-proxy:主要利用 service 提供集群内部的服务发现和负载均衡,同时监听 service/endpoints 变化并刷新负载均衡。

 

 

 

从创建 Deployment 开始

 

 

不懂Kubernetes,被老板邀请爬山!_java_03

Deployment 是用于编排 Pod 的一种控制器资源,我们会在后面做介绍。这里以 Deployment 为例,来看看架构中的各组件在创建 Deployment 资源的过程中都干了什么。

 

 

 

步骤如下:

  • 首先是 kubectl 发起一个创建 deployment 的请求。

  • apiserver 接收到创建 deployment 请求,将相关资源写入 etcd;之后所有组件与 apiserver/etcd 的交互都是类似的。

  • deployment controller list/watch 资源变化并发起创建 replicaSet 请求

  • replicaSet controller list/watch 资源变化并发起创建 pod 请求。

  • scheduler 检测到未绑定的 pod 资源,通过一系列匹配以及过滤选择合适的 node 进行绑定。

  • kubelet 发现自己 node 上需创建新 pod,负责 pod 的创建及后续生命周期管理。

  • kube-proxy 负责初始化 service 相关的资源,包括服务发现、负载均衡等网络规则。

 

 

 

至此,经过 Kubenetes 各组件的分工协调,完成了从创建一个 Deployment 请求开始到具体各 Pod 正常运行的全过程。

 

 

 

Pod

 

 

在 Kubernetes 众多的 API 资源中,Pod 是最重要和基础的,是最小的部署单元。

 

 

 

首先我们要考虑的问题是,我们为什么需要 Pod?Pod 可以说是一种容器设计模式,它为那些”超亲密”关系的容器而设计,我们可以想象 Servelet 容器部署 War 包、日志收集等场景。

 


这些容器之间往往需要共享网络、共享存储、共享配置,因此我们有了 Pod 这个概念。不懂Kubernetes,被老板邀请爬山!_java_04

 

对于 Pod 来说,不同 Container 之间通过 Infra Container 的方式统一识别外部网络空间,而通过挂载同一份 Volume 就自然可以共享存储了,比如它对应宿主机上的一个目录。

 

 

 

容器编排

 

 

容器编排是 Kubernetes 的看家本领了,所以我们有必要了解一下。

 

 

 

Kubernetes 中有诸多编排相关的控制资源,例如编排无状态应用的 Deployment,编排有状态应用的 Statefulset,编排守护进程 Daemonset 以及编排离线业务的 job/cronjob 等等。

 

 

 

我们还是以应用最广泛的 Deployment 为例。Deployment、Replicatset、Pod 之间的关系是一种层层控制的关系。

 

简单来说,Replicaset 控制 Pod 的数量,而 Deployment 控制 Replicaset 的版本属性。

 

 

 

这种设计模式也为两种最基本的编排动作实现了基础,即数量控制的水平扩缩容、版本属性控制的更新/回滚。

 

 

 

水平扩缩容

 

 

 

不懂Kubernetes,被老板邀请爬山!_java_05

 

水平扩缩容非常好理解,我们只需修改 Replicaset 控制的 Pod 副本数量即可,比如从 2 改到 3,那么就完成了水平扩容这个动作,反之即水平收缩。

 

 

 

更新/回滚

 

 

 

不懂Kubernetes,被老板邀请爬山!_java_06

 

更新/回滚则体现了 Replicaset 这个对象的存在必要性。例如我们需要应用 3 个实例的版本从 v1 改到 v2。

 

 

 

那么 v1 版本 Replicaset 控制的 Pod 副本数会逐渐从 3 变到 0,而 v2 版本 Replicaset 控制的 Pod 数会注解从 0 变到 3,当 Deployment 下只存在 v2 版本的 Replicaset 时变完成了更新。回滚的动作与之相反。

 

 

 

滚动更新

 

 

可以发现,在上述例子中,我们更新应用,Pod 总是一个一个升级,并且最小有 2 个 Pod 处于可用状态,最多有 4 个 Pod 提供服务。

 

 

 

这种”滚动更新”的好处是显而易见的,一旦新的版本有了 Bug,那么剩下的 2 个 Pod 仍然能够提供服务,同时方便快速回滚。

 

 

 

在实际应用中我们可以通过配置 RollingUpdateStrategy 来控制滚动更新策略。

 

 

 

maxSurge 表示 Deployment 控制器还可以创建多少个新 Pod;而 maxUnavailable 指的是,Deployment 控制器可以删除多少个旧 Pod。

 

 

 

Kubernetes 中的网络


我们了解了容器编排是怎么完成的,那么容器间的又是怎么通信的呢?

 

不懂Kubernetes,被老板邀请爬山!_java_07

讲到网络通信,Kubernetes 首先得有“三通”基础:

  • Node 到 Pod 之间可以通

  • Node 的 Pod 之间可以通

  • 不同 Node 之间的 Pod 可以通

 

 

 

简单来说,不同 Pod 之间通过 cni0/docker0 网桥实现了通信,Node 访问 Pod 也是通过 cni0/docker0 网桥通信即可。

 

 

 

而不同 Node 之间的 Pod 通信有很多种实现方案,包括现在比较普遍的 Flannel 的 vxlan/hostgw 模式等。

 

Flannel 通过 Etcd 获知其他 Node 的网络信息,并会为本 Node 创建路由表,最终使得不同 Node 间可以实现跨主机通信。

 

 

 

微服务—Service

 

 

在了解接下来的内容之前,我们得先了解一个很重要的资源对象:Service。

 

 

 

我们为什么需要 Service 呢?在微服务中,Pod 可以对应实例,那么 Service 对应的就是一个微服务。

 

 

 

而在服务调用过程中,service 的出现解决了两个问题:

  • Pod 的 IP 不是固定的,利用非固定 IP 进行网络调用不现实

  • 服务调用需要对不同 Pod 进行负载均衡

 

Service 通过 Label 选择器选取合适的 Pod,构建出一个 Endpoints,即 Pod 负载均衡列表。

 

 

 

实际运用中,一般我们会为同一个微服务的 Pod 实例都打上类似 app=xxx 的标签,同时为该微服务创建一个标签选择器为 app=xxx 的 Service。

 

 

 

Kubernetes 中的服务发现与网络调用

 

 

在有了上述“三通”的网络基础后,我们可以开始微服务架构中的网络调用在 Kubernetes 中是怎么实现的了。

 

 

 

这部分内容其实在说说 Kubernetes 是怎么实现服务发现的已经讲得比较清楚了,比较细节的地方可以参考上述文章,这里做一个简单的介绍。

 

 

 

服务间调用

 

 

首先是东西向的流量调用,即服务间调用。这部分主要包括两种调用方式,即 ClusterIp 模式以及 DNS 模式。

 


ClusterIp 是 Service 的一种类型,在这种类型模式下,kube-proxy 通过 iptables/ipvs 为 Service 实现了一种 VIP(虚拟 IP)的形式。只需要访问该 VIP,即可负载均衡地访问到 Service 背后的 Pod。不懂Kubernetes,被老板邀请爬山!_java_08

 

上图是 ClusterIp 的一种实现方式,此外还包括 userSpace 代理模式(基本不用),以及 ipvs 模式(性能更好)。

 

 

 

DNS 模式很好理解,对 ClusterIp 模式的 Service 来说,它有一个 A 记录是 service-name.namespace-name.svc.cluster.local,指向 ClusterIp 地址。所以一般使用过程中,我们直接调用 service-name 即可。

 

 

服务外访问

 

 

 

不懂Kubernetes,被老板邀请爬山!_java_09

 

南北向的流量,即外部请求访问 Kubernetes 集群,主要包括三种方式:

  • nodePort

  • loadbalancer

  • ingress

 

nodePort 同样是 Service 的一种类型,通过 IPtables 赋予了调用宿主机上的特定 Port 就能访问到背后 Service 的能力。

 

 

 

Loadbalancer 则是另一种 Service 类型,通过公有云提供的负载均衡器实现。

 

 

 

我们访问 100 个服务可能需要创建 100 个 nodePort/Loadbalancer。我们希望通过一个统一的外部接入层访问内部 Kubernetes 集群,这就是 Ingress 的功能。

 

 

 

Ingress 提供了统一接入层,通过路由规则的不同匹配到后端不同的 Service 上。

 

 

 

Ingress 可以看做是“Service 的 Service”。Ingress 在实现上往往结合 nodePort 以及 Loadbalancer 完成功能。

 

到现在为止,我们简单了解了 Kubernetes 的相关概念,它大致是怎么运作的,以及微服务是怎么运行在 Kubernetes 中的。于是当我们听到别人讨论 Kubernetes 时,我们可以知道他们在讨论什么。