K8S.md 15 KB

什么是K8S

Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。

image-20230905160248241

pod

容器的本质是进程,Kubernetes就是操作系统。

在一个OS当中,进程并非独自运行的,而是以进程组的方式,有原则的组织在一起。

Kubernetes当中,将“进程组”的概念映射到了容器技术中。

为什么需要这个“组”的概念??

应用之间往往具有密切的协作关系,类似于“进程和进程组”。

关于如何妥善处理成组调度的问题?

  • Mesos有一个资源囤积(resource hoarding)机制,当所有设置了Affinity约束的任务都到达之后,才开始对他们进行统一调度
  • Google Omega论文,提出乐观调度处理冲突的方法,即:先不管这些冲突,而是通过回滚机制在出现冲突后解决问题

Kubernetes中得到了妥善解决,调度器统一按照Pod而非容器的资源需求进行计算

但是如果仅仅这样,那么Kubernetes可以在调度器层面解决 容器应用之间的紧密关系,而不一定要把Pod设置为最基本单位?

引出Pod在Kubernetes项目中更加重要的意义,即:容器设计模式

  • Pod只是一个逻辑概念,Kubernetes真正处理的,还是宿主机OS上的Namespace和Cgroups,物理上并没有存在一个所谓的Pod的边界或者隔离环境。

  • Pod,是一组共享了某些资源的容器。

  • Pod当中所有的容器,共享的是同一个Network Namespace,并且可以声明共享同一个Volume。

在Pod当中,容器是对等的关系,而如果是用简单用docker run共享,则必然会有容器先启动,进而产生拓扑关系。

Infra容器(新版本叫Init容器)

为了实现这样的对等关系,Pod的实现需要一个中间容器,叫做Infra容器。

它永远是首先被创建的容器,其他用户定义的容器通过Join Network Namespace方式,与Infra关联起来。

Infra容器一定要占据非常少的资源,所以它使用一个非常特殊的image,叫k8s.gcr.io/pause,(称为pause 镜像)它是用汇编语言编写、永远处于暂停状态,解压后也只有100~200KB左右。

Pod 的生命周期和 Infra 容器一致,而与其中的用户定义容器无关。

image-20230905161245730

要为Kubernetes开发一个网络插件时,应该重点考虑如何配置这个Pod的Network Namespace,而不是每一个用户容器如何使用你的网络配置

对于共享Volume而言,只需要把Volume的定义设计在Pod层级即可:一个 Volume 对应的宿主机目录对于 Pod 来说就只有一个,Pod 里的容器只要声明挂载这个 Volume,就一定可以共享这个 Volume 对应的宿主机目录。

pod配置文件config

特殊的Projected Volume

它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用,是为容器提供预先定义好的数据。

一共支持四种Volume:

  1. Secret: 保密数据

把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息。

经典场景:存放数据库的Credential信息:

  1. ConfigMap: 配置文件

ConfigMap 保存的是不需要加密的、应用所需的配置信息,其他用法与Secret相同。

用ConfigMap来保存一个java应用的配置文件ui.properties:

  1. Downward API: Pod元数据

让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。

  1. ServiceAccountToken: 服务账户,通过验证

疑问:现在有了一个 Pod,我能不能在这个 Pod 里安装一个 Kubernetes 的 Client,这样就可以从容器里直接访问并且操作这个 Kubernetes 的 API 了呢?

答案是:可以,但是需要解决API Server的授权问题。

Service Account 对象的作用,就是 Kubernetes 系统内置的一种“服务账户”,它是 Kubernetes 进行权限分配的对象。

Service Account 的授权信息和文件,实际上保存在它所绑定的一个特殊的 Secret 对象里的。这个特殊的 Secret 对象,就叫作ServiceAccountToken

任何运行在 Kubernetes 集群上的应用,都必须使用这个 ServiceAccountToken 里保存的授权信息,也就是 Token,才可以合法地访问 API Server

Kubernetes为了方便使用,为用户提供一个默认的服务账户(default Service Account),任何一个运行在 Kubernetes 里的 Pod,都可以直接使用这个默认的 Service Account,而无需显示地声明挂载它。

Replicaset

ReplicaSet是kubernetes中的一种副本控制器,简称rs,主要作用是控制由其管理的pod,使pod副本的数量始终维持在预设的个数。它的主要作用就是保证一定数量的Pod能够在集群中正常运行,它会持续监听这些Pod的运行状态,在Pod发生故障时重启pod,pod数量减少时重新运行新的 Pod副本。官方推荐不要直接使用ReplicaSet,用Deployments取而代之,Deployments是比ReplicaSet更高级的概念,它会管理ReplicaSet并提供很多其它有用的特性,最重要的是Deployments支持声明式更新,声明式更新的好处是不会丢失历史变更。所以Deployment控制器不直接管理Pod对象,而是由 Deployment 管理ReplicaSet,再由ReplicaSet负责管理Pod对象。

Replicaset核心作用在于用户创建指定数量的pod副本,并确保pod副本一直处于满足用户期望的数量, 起到多退少补的作用,并且还具有自动扩容缩容等制。 Replicaset控制器主要由三个部分组成: 1、用户期望的pod副本数:用来定义由这个控制器管控的pod副本有几个 2、标签选择器:选定哪些pod是自己管理的,如果通过标签选择器选到的pod副本数量少于我们指定的数量,需要用到下面的组件 3、pod资源模板:如果集群中现存的pod数量不够我们定义的副本中期望的数量怎么办,需要新建pod,这就需要pod模板,新建的pod是基于模板来创建的。

Deployment

为了更好地解决服务编排的问题,k8s在V1.2版本开始,引入了deployment控制器,值得一提的是,这种控制器并不直接管理pod,

而是通过管理replicaset来间接管理pod,即:deployment管理replicaset,replicaset管理pod。所以deployment比replicaset的功能更强大。

image-20230905164914619

deployment的主要功能有下面几个:

  • 支持replicaset的所有功能
  • 支持发布的停止、继续
  • 支持版本的滚动更新和版本回退

StatefulSet

作为一个后端工程师,因为负责的大部分项目都是Web服务这类的“无状态应用”,在平时工作中接触到的最常用的Kubernetes控制器是Deployment,但是Deployment只适合于编排“无状态应用”,它会假设一个应用的所有 Pod是完全一样的,互相之间也没有顺序依赖,也无所谓运行在哪台宿主机上。正因为每个Pod都一样,在需要的时候可以水平扩/缩,增加和删除Pod

但是并不是所有应用都是无状态的,尤其是每个实例之间有主从关系的应用和数据存储类应用,针对这类应用使用Deployment控制器无法实现正确调度,所以Kubernetes里采用了另外一个控制器StatefulSet负责调度有状态应用的Pod,保持应用的当前状态始终等于应用定义的所需状态。

node affinity

pod绑定节点最简单的方法是使用 nodeSelector,但它比较简单粗暴,使用起来不能灵活调度,这个在后续版本中也会慢慢过时,所以我们一般用 nodeAffinity来实现这些需求。

Node Affinity Affinity 翻译成中文是“亲和性”,它对应的是 Anti-Affinity,我们翻译成“互斥”。这两个词比较形象,可以把 pod 选择 node 的过程类比成磁铁的吸引和互斥,不同的是除了简单的正负极之外,pod 和 node 的吸引和互斥是可以灵活配置的。

Affinity的优点:

匹配有更多的逻辑组合,不只是字符串的完全相等 调度分成软策略(soft)和硬策略(hard),在软策略下,如果没有满足调度条件的节点,pod会忽略这条规则,继续完成调度。 目前主要的node affinity:

requiredDuringSchedulingIgnoredDuringExecution 表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中IgnoreDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,pod也会继续运行。

requiredDuringSchedulingRequiredDuringExecution 表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中RequiredDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,则重新选择符合要求的节点。

preferredDuringSchedulingIgnoredDuringExecution 表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。

preferredDuringSchedulingRequiredDuringExecution 表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。其中RequiredDuringExecution表示如果后面节点标签发生了变化,满足了条件,则重新调度到满足条件的节点。

Toleration

节点亲和性 是 Pod的一种属性,它使 Pod 被吸引到一类特定的节点(这可能出于一种偏好,也可能是硬性要求)。 污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。

容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。

污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。

service

Service是一种抽象的对象,它定义了一组Pod的逻辑集合和一个用于访问它们的策略,一个Serivce下面包含的Pod集合一般是由Label Selector来决定的。假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的Pod集合会发生变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service的这种抽象就可以帮我们达到这种解耦的目的。

Node IP:Node节点的IP地址 Pod IP:Pod的IP地址 Cluster IP:Service的IP地址

首先,Node IP是Kubernetes集群中节点的物理网卡IP地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以Kubernetes集群外要想访问Kubernetes集群内部的某个节点或者服务,肯定得通过Node IP进行通信(这个时候一般是通过外网IP了)

然后Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的(我们这里使用的是flannel这种网络插件保证所有节点的Pod IP不会冲突)

最后Cluster IP是一个虚拟的IP,仅仅作用于Kubernetes Service这个对象,由Kubernetes自己来进行管理和分配地址,当然我们也无法ping这个地址,他没有一个真正的实体对象来响应,他只能结合Service Port来组成一个可以通信的服务。

在这里插入图片描述

Ingress

ingress翻译过来是入口的意思,k8s希望ingress是整个集群流量的入口,引入ingress后整个请求如下图所示

image.png

为什么需要Ingress资源

由于K8S集群拥有强大的副本控制能力,Pod随时可能从一个节点上被驱逐到另一个节点上,或者直接销毁再来一个新的。

然而伴随着Pod的销毁和重生,Pod的IP等信息不断地在改变,此时使用K8S提供的Service机制可以解决这一问题,Service通过标签选定指定的Pod作为后端服务,并监听这些Pod的变化。

在对外暴露服务时,使用Service的NodePort是一个方法,但还会有以下几个问题

问题1 - 如何管理端口

当需要对外暴露的服务量比较多的时候,端口管理的问题便会暴露出来。并且service只支持4层代理,也就是只能根据ip+端口

此时的一个处理方案是使用一个代理服务(例如Nginx)根据请求信息将请求转发到不同的服务上去。

问题2 - 如何管理转发配置

每当有新服务加入,都需要对该服务的配置进行修改、升级,在服务数量逐渐变多后,该配置项目会变得越来越大,手工修改的风险也会逐渐增高。

那么需要一个工具来简化这一过程,希望可以通过简单的配置动态生成代理中复杂的配置,最好还可以顺手重新加载配置文件。

K8S刚好也提供了此类型资源。

健康检查

健康检查(health check)是用于检测应用实例是否正常工作,对应用状态的监控,保障业务高可用的一种机制。

k8s健康检测主要分为以下三种:

  • 存活性探测(Liveness probes) :主要是探测应用是否还活着。如果检测到应用没有存活就杀掉当前pod并重启。
  • 就绪性探测(Readiness probes):只要是探测应用是否准备好接受请求访问,如果检测应用准备好了,就把请求流量放进来;反之,则把应用节点从注册中心拿掉。
  • 启动探测(Startup Probes):对于旧应用需要更长的启动时间,这时候既不想重启应用也不想让请求访问进来,可以设置启动探测给足够的启动时间保证应用启动成功。

image-20230905203232434

二者不能相互替代,根据实际情况,配合使用。只配置了readiness是无法触发容器重启的;只配置了liveness,可能应用还没准备好,导致请求失败,status是running,Ready是0/1。