## 什么是K8S Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。 ![image-20230905160248241](assets/image-20230905160248241.png) ## 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](assets/image-20230905161245730.png) 要为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信息: 2. ConfigMap: 配置文件 ConfigMap 保存的是不需要加密的、应用所需的配置信息,其他用法与Secret相同。 用ConfigMap来保存一个java应用的配置文件ui.properties: 3. Downward API: Pod元数据 让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。 4. 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](assets/image-20230905164914619.png) 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来组成一个可以通信的服务。 ![在这里插入图片描述](assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxMTg3NTE1,size_16,color_FFFFFF,t_70.png) ## Ingress ingress翻译过来是入口的意思,k8s希望ingress是整个集群流量的入口,引入ingress后整个请求如下图所示 ![image.png](assets/c8054fbec1b14607b2759ea0d1ddc2f4tplv-k3u1fbpfcp-zoom-in-crop-mark1512000.awebp) ### 为什么需要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](assets/image-20230905203232434.png) > **二者不能相互替代,根据实际情况,配合使用。只配置了readiness是无法触发容器重启的;只配置了liveness,可能应用还没准备好,导致请求失败,status是running,Ready是0/1。**