秋栈博客

七月

初探Kubernetes

2022-09-15

一、背景

1.1、传统部署

直接在物理机上运行程序
  • 在一台服务器上运行多个程序,可能因某个应用影响到其他应用。
  • 若在多个服务器上运行不同应用程序,资源利用率低。
  • 需要专人维护,成本高。

1.2、虚拟化部署

一台物理机通过虚拟化软件创建出不同的虚拟机来运行不同程序
虽然隔离性强,但每个虚拟机仍包含了整个操作系统,资源利用率仍然低。性能损耗高。

1.3、容器化时代

轻量级VM,具有自己的文件系统、CPU 、内存、进程空间等。
  • 敏捷性:敏捷应用程序的创建和部署;和使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。
  • 及时性:持续开发、集成和部署;通过快速简单的回滚(由于镜像的不可变性),支持可靠且频繁的容器镜像的构建和部署。
  • 解耦性:关注开发和运维的分离;在构建、发布时创建应用程序的容器镜像,而不是在部署的时候,从而将应用程序和基础架构分离。
  • 跨平台:跨开发、测试和生产的环境一致性;在便捷式的计算机上和在云上相同的运行。
  • 可移植:跨云和 Linux 发行版本的可移植性;可以在 Ubuntu、CentOS、RedHat、本地、Google Kubernetes Engine 和其他任何地方运行。
  • 简易性:以应用程序为中心的管理;提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。
  • 大分布式:松散耦合、分布式、弹性、解放的微服务;应用程序被分解成较小的独立部分,并且可以动态的部署和管理 --- 而不是在一台大型单机上整体运行(垂直扩展是有上限的)。

1.4、为什么使用Kubernetes

容器是打包和运行应用程序的最佳方式,在生产环境中,我们需要管理运行应用程序的容器,并且确保这些容器不会停机。那么管理容器成为了新的难题。
Kubernetes 为我们提供了一个可弹性运行的分布式系统的框架,Kubernetes 可以满足我们的扩展要求、故障转移、部署模式。 功能:
    • 服务发现和负载均衡:Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器,如果进入容器的流量很大,Kubernetes 可以负载均衡并分配网络流量,从而使得部署稳定。
    • 存储编排:Kubernetes 允许我们自动挂载自己选择的存储系统,如:本地存储、公有云提供商等。
    • 自动部署和回滚:我们可以使用 Kubernetes 描述已部署容器的所需状态,Kubernetes 可以以受控的速率将实际状态更改为期望状态,如:我们可以自动化 Kubernetes 来为我们的部署创建新的容器,删除现有容器并将它们的所有资源用于新的容器。
    • 自动完成装箱计算:Kubernetes 允许我们指定每个容器所需要的 CPU 和内存(RAM)。当容器指定了资源请求时,Kubernetes 可以做出更好的决策来管理容器的资源。
    • 自我修复:Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。
    • 密钥和配置管理:Kubernetes 允许我们存储和管理敏感信息,如:密码、OAuth2 令牌和 SSH 密钥。我们可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需再堆栈配置中暴露密钥。

二、核心组件&核心概念

2.1 kube-apiserver-Master

api server 是通过kube-apiserver 进程来提供服务的 核心功能:
  • 集群管理的API 入口
  • 资源配置控制入口
  • 提供完备的集群安全机制
  • 通信枢纽,Kubernetes各个模块的交互中心

2.2 kube-controller-manager-Master

Controller-Manager包含一系列的控制器组件,比如 Deployment、StatefulSet 等控制器。 控制器的核心思想是监听、比较资源实际状态与期望状态是否一致,若不一致则进行协调工作使其最终一致。

2.3 kube-scheduler-Master

调度器组件,负责集群 Pod 的调度。 基本原理是通过监听 kube-apiserver 获取待调度的 Pod,然后基于一系列筛选和评优算法,为 Pod 分配最佳的 Node 节点。

2.4 etcd-Master

分布式的一个存储系统,API Server 中所需要的这些原信息都被放置在 etcd 中,etcd 本身是一个高可用系统,通过 etcd 保证整个 Kubernetes 的 Master 组件的高可用性。

2.5 kubelet-Worker

负责 Pod 的创建运行,部署在每个节点上的 Agent 的组件。 基本原理是通过监听 APIServer 获取分配到其节点上的 Pod,然后根据 Pod 的规格详情,调用运行时组件创建 pause 和业务容器等。

2.6 kube-proxy-Worker

kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件; kube-proxy负责为Pod创建代理服务,从apiserver获取所有server信息,并根据server信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。

2.6.1、代理模式

  1. userspaceservice的请求会先从用户空间进入内核iptables,然后再回到用户空间,由kube-proxy完成后端Endpoints的选择和代理工作
  2. iptables利用内核iptables来实现service的代理和LB。
  3. IPVS在IPVS模式下,kube-proxy观察Kubernetes中service和endpoint对象的变化,通过调用netlink接口创建相应的IPVS规则,并周期性的对Kubernetes的service、endpoint和IPVS规则进行同步,当访问一个service时,IPVS负责选择一个真实的后端提供服务。

2.7、Kubernetes 对象

Kubernetes中操作的资源实体即Kubernetes对象,用yaml来声明后让Kubernetes根据声明来创建出这个对象。

2.7.1、Kubernetes对象描述

YAML字段:
  • apiVersion:Kubernetes API版本
  • kind:对象类型,如POD、Deployment等
  • spec:期望的目标状态
  • metadata:用来唯一确定该对象的元数据,包括 name 、namespace 等,如果 namespace 为空,则默认值为 default 。

2.7.2、标签(Label)&注解(Annotations)

使用标签(Label)可以高效的查询和监听 Kubernetes 对象,在 Kubernetes 界面工具(如:Kubernetes DashBoard)和 kubectl 中,标签使用的非常普遍。而那些非标识性的信息应该记录在 注解(Annotation) 中。
  1. 标签
    • 使用标签,用户可以按照自己期望的形式组织 Kubernetes 对象之间的结构,而无需对 Kubernetes 有任何修改。
    • 标签例子:release:stable、release:canary
  2. 标签选择器通常来讲,会有多个 Kubernetes 对象包含相同的标签。通过使用标签选择器(label selector),用户/客户端可以选择一组对象。标签选择器是 Kubernetes 中最主要的分类和筛选手段。
  3. 注解可以用来向 Kubernetes 对象的 meta.annotations 字段添加任意的信息。Kubernetes 的客户端或者自动化工具可以存取这些信息以实现自定义的逻辑。

2.8、NameSpace 命名空间

Kubernetes中用来隔离对象资源(Pod、Service、副本控制器等)的方式,但其本身不在命名空间中。并且,底层资源如node、volume不属于任何命名空间。

2.8.1、概述

默认情况下Kubernetes会初始化4个命名空间:
  • default:所有没有指定 namespace 的对象都会被分配到此名称空间中。
  • kube-node-lease:Kubernetes 集群节点之间的心跳维护,V 1.13 开始引入。
  • kube-system:Kubernetes 系统创建的对象放在此名称空间中。
  • kube-public:此名称空间是 Kubernetes 集群安装时自动创建的,并且所有的用户都可以访问(包括未认证的用户),主要是为集群预留的,如:在某些情况中,某些 Kubernetes 对象应用应该能被所有集群用户访问到。

2.8.2、特点

  • 名称空间资源隔离、网络不隔离:例如配置文件不可以跨名称空间访问,但是网络访问可以跨名称空间访问。
  • 默认会初始化一个default命名空间:用来承载那些没有指定名称空间的 Pod 、Service 、Deployment 等对象

三、Kubernetes 工作负载

工作负载即在Kubernetes中运行的应用程序。

3.1、POD

Pod 是 Kubernetes 的一个最小调度以及资源单元,一个POD为一组容器。

3.2、工作负载

工作负载能让 Pod 拥有自愈能力。不同的工作负载有着不同的方式去控制 Pod 。

3.2.1、Deployment

v1.2版本引入。通过管理 ReplicaSet 来间接管理 Pod,部署无状态应用:网络可能会变(IP 地址)、存储可能会变(卷)、顺序可能会变(启动的顺序)。
  • 仅当 Deployment 的 Pod 模板(spec.template)发生改变的时候(如:模板的标签或容器镜像被更新),才会触发 Deployment 上线,其他更新(如:Deployment 的扩缩容等操作)均不会触发上线动作。
  • 上线动作:创建新的 ReplicaSet,准备就绪后,替换旧的 ReplicaSet(此时不会删除,因为 revisionHistoryLimit 指定了保留几个版本)。

3.2.1.1、ReplicaSet

保证一定数量的 Pod 能够正常运行,它会持续监听这些 Pod 的运行状态,一旦 Pod 发生故障,就会重启或重建。同时它还支持对 Pod 数量的扩缩容。
滚动更新(默认):杀死一部分,就启动一部分,在更新过程中,存在两个版本的Pod。 重建更新:在创建出新的Pod之前会先杀掉所有已经存在的Pod。

3.2.1.2、Canary 金丝雀发布

滚动更新短时间就直接结束了,不能直接控制新老版本的存活时间;而金丝雀发布却可以实现这样的功能。
发布流程:
  1. 将 service 的标签设置为 app=nginx ,这就意味着集群中的所有标签是 app=nginx 的 Pod 都会加入负载均衡网络。
  2. 使用 Deployment v=v1 app=nginx 去筛选标签是 app=nginx 以及 v=v1 的 所有 Pod。
  3. 同理,使用 Deployment v=v2 app=nginx 去筛选标签是 app=nginx 以及 v=v2 的所有 Pod 。
  4. 逐渐加大 Deployment v=v2 app=nginx 控制的 Pod 的数量,根据轮询负载均衡网络的特点,必定会使得此 Deployment 控制的 Pod 的流量增大。
  5. 当测试完成后,删除 Deployment v=v1 app=nginx 即可。

3.2.2、DaemonSet

确保所有(或一部分)的节点都运行了一个指定的 Pod 副本。
  1. 每当集群中添加一个节点时,指定的POD副本也将添加到该节点上。
  2. 当节点从集群中被移除时,POD被回收。
  3. 删除一个DaemonSet可以清理其创建的所有POD。

3.2.3、StatefulSet

部署有状态应用:要求网络不变、存储不变、顺序不变。
优点:
  1. 稳定、唯一的网络标识且必须配置HeadlessService:StatefulSet 通过和其相关的无头服务为每个 Pod 提供 DNS 解析条目。
  2. 稳定、持久的存储:每个POD始终对应各自的存储路径:PVC Template。
  3. 有序的、优雅的部署和缩放:按顺序地增加、减少副本,并在减少的时候清理。
  4. 有序的、自动的滚动更新:按顺序自动执行滚动更新
限制:
  1. 需要Headless来负责POD网络
  2. 由PV驱动基于所请求的存储类来提供或运维人员预先提供
  3. 在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新,可能进入需要 人工干预才能修复的损坏状态。
  4. 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。
  5. 删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止,可以在删除之前将 StatefulSet 缩放为 0。

3.2.3、Job&CronJob

  • Job:负责批量处理短暂的一次性任务。
  • CronJob:类似 Linux 操作系统的周期性任务作业计划的方式、在特定的时间点反复去执行 Job 任务。

3.2.4、HPA

通过追踪分析目标Pod的负载变化情况,来确定是否需要针对性的自动调整目标 Pod 的副本数。
工作流程:
  1. HPA 获取每个 Pod 的利用率
  2. 和 HPA 中定义的指标进行对比,同时计算出需要伸缩的具体值
  3. 实现 Pod 的数量的调整

3.3、容器健康检查

容器的livenessProbe探针(httpGet、tcpSocket、ExecAction)用于判断容器的健康状态。kubelet会定期调用这些探针来判断容器是否存活。 httpGet:向容器发送Http Get请求,调用成功(通过Http状态码判断)则确定Pod就绪; tcpSocket:打开一个TCP连接到容器的指定端口,连接成功建立则确定Pod就绪; ExecAction:在容器内执行某命令,命令执行成功(通过命令退出状态码为0判断)则确定Pod就绪。

四、Kubernetes 配置与存储

4.1、配置

Secret 和 ConfigMap 保存在 etcd 中

4.1.1、Secret

保存敏感信息:密码、令牌、密钥等。使用base64编码和解码。
POD引用Secret:
  1. 作为挂载到一个或多个容器上的卷中的文件
  2. 作为容器的环境变量(envFrom字段引用)
  3. 由kubelet在为POD拉取镜像时使用(docker-register类型)

4.1.2、ConfigMap

  • 类似Secret,但无编码解码。
  • 和Secret一样,环境变量引用不会热更新,但卷挂载可热更新。
 

4.2、临时存储

4.2.1、emptyDir

当POD分配到Node上时,emptyDir被自动创建,并且在运行期间一直存在。POD中的容器可以读写其中相同的文件。 当POD从节点中被剔除时,该卷中的数据也被永久删除,但容器崩溃不影响emptyDir中的数据。
作用:
  1. 多容器共享目录
  2. 某些程序运行所需的临时目录
emptyDir存储在节点的磁盘或内存中,如果设置为数据保存在内存中,在POD被重启或删除前所写入的文件都会计入容器的内存消耗、也受到容器的内存限制约束。

4.2.2、hostPath

即简单的数据持久化:将Node中的实际目录挂载到POD中供容器使用。
作用:
  1. POD的时间同步
  2. POD中容器需要访问宿主机文件
之所以为临时储存,是因为POD被Deployment重启拉起时不一定在原来的Node中。

4.3、持久化存储

PVC与PV是通过PersistentVolume Controller 进行绑定的。PV Controller 会不断地循环去查看每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与未绑定的 PVC 进行绑定,这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。而所谓将PV 与 PVC 进行“绑定”,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName 字段上 。

4.4.1、Persistent Volume(PV)

即持久化存储数据卷,对底层的共享存储的一种抽象。一般由运维人员事先创建。 PV的生命周期:
  • Available:可用、未被任何PVC绑定
  • Bound:PV与PVC已绑定
  • Release:PVC被删除,但资源未被释放
  • Failed:资源自动回收失败。

4.4.2、Persistent Volume Claim(PVC)

即持久化卷声明,用户对储存需求的一种声明。一般由开发人员创建。 工作流程:
  1. 用户创建PVC,Kubernetes根据声明去寻找PV并绑定。
    • 若找不到,PVC会处于Pending状态,直到符合要求的PV出现
  2. 用户将PVC挂载到容器内的某路径使用
  3. 资源释放:
    • 删除PVC来释放PV,但该PV需要在清除数据后才能被再次使用
  4. 资源回收:kubernetes根据PV设置的回收策略进行回收,然后供新的PVC绑定与使用。

4.4.3、StorageClass

提供PV的动态供应与资源利用。
  1. 运维人员创建存储类
  2. 用户创建PVC,并通知kubernetes需要一个PV
  3. kubernetes读取存储类信息自动创建PVC需要的PV
  4. PVC使用PV进行数据的持久化

四、Kubernetes网络

4.1、网络架构

4.1.1、架构图

4.1.2、访问流程

4.2、Service

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。

4.2.1、为什么用到Service

  1. Pod 的 IP 不固定
  2. 一组 Pod 间会有负载均衡的需求。

4.2.2、ClusterIP

4.2.2.1、Cluster Service

通过集群的内部 IP 暴露服务,服务只能够在集群内部访问。
通过控制iptables将对Service ClusterIP的请求,转发到后端Endpoints中,剩下就交给容器网络。

4.2.2.2、Endpoint

  • Endpoint 是 Kubernetes 中的一个资源对象,存储在etcd 中,用来记录一个 Service 对应的所有 Pod 的访问地址,它是根据Service配置文件中的 Selector 描述产生的(当然,也可以自定义端点)。
  • 一个 Service 由一组 Pod 组成,这些 Pod 通过 Endpoints 暴露出来,Endpoints 是实现实际服务的端点集合。换言之,Service 和 Pod 之间的联系是通过 Endpoints 实现的。

4.2.3、Headless

不希望 Kubernetes 集群分配 IP ,针对这种情况,Kubernetes 提供了 Headless Service,这类 Service 不会分配 Cluster IP,如果想要访问 Service ,只能通过 Service 的域名进行查询,一般配合 StatefulSet 使用。

4.2.4、NodePort

如果希望 Service 暴露给集群外部使用,那么就需要使用到 NodePort 类型的 Service。 工作原理:将 Service 的端口映射到 Node 的一个端口上,然后就可以通过 NodeIP:NodePort来访问 Service 了。

4.2.5、LoadBalancer

LoadBalancer 和 NodePort 很相似,目的都是向外部暴露一个端口,区别在于 LoadBalancer 会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境的支持,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。这个功能需要云平台厂商的支持。

4.2.6、ExternalName

引入集群外部的服务,它通过 externalName 属性指定一个服务的地址,然后在集群内部访问此 Service 就可以访问到外部的服务。

4.2.7、CoreDNS

为kubernetes提供服务发现功能。为以下三类资源维护对应的域名记录:
  1. POD
  2. Service
  3. StatefulSet

4.2.8、CNI网络插件

例如Calico、Flanel(不支持NetworkPolicy)。

4.2.9、IPVS模式

kube-proxy通过iptables处理service需要在宿主机上设置大量的iptables规则、还需要在控制循环里不断刷新这些规则以保证正确性。对宿主机性能有所影响。
工作原理:
  1. 创建service之后,kube-proxy在宿主机上创建虚拟网卡
  2. 为虚拟网卡分配Service VIP作为IP地址
  3. kube-proxy通过IPVS为VIP设置三个IPVS虚拟主机(rr)来代理POD
IPVS通过NAT实现转发,不需要在宿主机上为每个POD设置iptables规则。 但整个Service还是需要配合iptables的包过滤、SNAT来实现。

4.3、Ingress

4.3.1、为什么需要Ingress

  1. Service 可以使用 NodePort 暴露集群外访问端口,但是性能低、不安全并且端口的范围有限。
  2. Service 缺少七层(OSI 网络模型)的统一访问入口,负载均衡能力很低,不能做限流、验证非法用户、链路追踪等等。
  3. Ingress 公开了从集群外部到集群内 服务的 HTTP 和 HTTPS 路由。流量路由由 Ingress 资源上定义的规则控制。
  4. 使用 Ingress 作为整个集群统一的入口,配置 Ingress 规则转发到对应的 Service 。

4.3.2、Ingress工作原理

  1. 用户编写 Ingress 规则,说明哪个域名对应 k8s 集群中的哪个 Service
  2. Ingress 控制器动态感知 Ingress 服务规则的变化,然后生成一段对应的 Nginx 的反向代理配置。
  3. Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中,并动态更新。

4.3.2、Ingress Controller & INgress Class

Ingress Controller:INgress即规则的集合,但自身无流量管理能力,需要通过INgress Controller(类似Service的kube-proxy)应用INgress规则。 INgress Class:耦合了INgress和INgressController,用来定义不同的业务逻辑分组,简化INgress复杂度。

4.3.3、Nginx INgress Controller

INgress Controller即集群的流量入口,各大公司也纷纷开发自己的INgress Controller,其中NginxINgressController目前下载量最高。

五、Operator

Operator 是使用自定义资源(CR)管理应用及其组件的自定义 Kubernetes 控制器。高级配置和设置由用户在 CR 中提供。Kubernetes Operator 基于嵌入在 Operator 逻辑中的最佳实践将高级指令转换为低级操作。

六、Helm

6.1、为什么需要Helm

Kubernetes中的对象都由特定的资源描述组成,包括Deployment、Service等,都保存在各自的文件中或集中写在一个配置文件、通过kubectl apply -f 部署。
简单的应用足矣,但是对于一个复杂的应用,会有很多类似上面的资源描述文件,如:微服务架构应用,组成应用的服务可能多达几十、上百个,如果有更新或回滚应用的需求,可能要修改和维护所涉及到大量的资源文件,而这种组织和管理应用的方式就显得力不从心了。并且由于缺少对发布过的应用进行版本管理和控制,使得 Kubernetes 上的应用维护和更新面临诸多的挑战,主要面临以下的问题:
    • 如何将这些服务作为一个整体管理?
    • 这些资源文件如何高效复用?
    • 应用级别的版本如何管理?

6.2、Helm核心概念

  • Chart(类似Docker中的镜像) 代表着 Helm 包。它包含在 Kubernetes 集群内部运行应用程序,工具或服务所需的所有资源定义。你可以把它看作是 Homebrew formula,Apt dpkg,或 Yum RPM 在Kubernetes 中的等价物。
  • Repository(类似DockerHub仓库) 是用来存放和共享 charts 的地方。它就像 Perl 的 CPAN 档案库网络 或是 Fedora 的 软件包仓库,只不过它是供 Kubernetes 包所使用的。
  • Release(类似Docker中的容器) 是运行在 Kubernetes 集群中的 chart 的实例。一个 chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 release 。以 MySQL chart为例,如果你想在你的集群中运行两个数据库,你可以安装该 chart 两次。每一个数据库都会拥有它自己的 release 和 release name 。

七、总结

通过上一周的学习,我大致了解了Kubernetes中的重要组件及核心概念和日常操作。
基础认识
  • Kubernetes概念
  • Kubernetes配置、存储、网络
  • Kubernetes工作负载
  • Kubernetes Pod控制器
  • Kubernetes 声明式API
有待提升
  • Kubernetes Operator
  • Kubernetes RABC
  • Kubernetes调度器
  • Helm应用
  • API Server
   
  • 0