架构

集群中的节点分为两种:control plane 和 node

Control Plane

负责管理和控制集群的整体行为,通常包含以下组件:

  • kube-apiserver:负责对外提供 RESTful API,可水平扩容
  • kube-controller-manager:负责运行控制器,通常包括以下控制器:
    • 节点控制器(Node Controller):负责监控和管理集群中的节点(Node)。它的主要功能是确保集群中的节点正常运行,并处理节点故障和恢复。
    • 任务控制器(Job Controller):监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
    • 端点分片控制器(EndpointSlice controller):填充端点分片(EndpointSlice)对象(以提供 Service 和 Pod 之间的链接)
    • 服务账号控制器(ServiceAccount controller):为新的命名空间创建默认的服务账号(ServiceAccount)
  • cloud-controller-manager:允许你将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。只有在服务上云的时候才会用到。包括以下控制器:
    • 节点控制器:在云上监控和管理集群中的节点。
    • 路由控制器:负责适当地配置云平台中的路由,以便 Kubernetes 集群中不同节点上的容器之间可以相互通信。
    • 服务控制器:确保服务的正常运行以及与其他组件的协调。
  • kube-scheduler: 负责监视新创建的、未指定运行 node 的 Pods, 并选择 node 来让 Pod 在上面运行。
  • etcd:存储集群元信息

Node

实际运行 Pod,通常包含以下组件:

  • kubelet:负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理
  • kube-proxy:负责为 Service 提供 cluster 内部的服务发现和负载均衡
  • cri:负责镜像管理以及 Pod 和容器的真正运行

Kubernetes 组件

附加组件

  • Kube-dns:为集群提供 DNS 服务
  • ingress-controller:为集群提供外网可访问的能力
  • prometheus:监控
  • dashboard:GUI
  • federation:提供跨可用区的集群
  • Fluentd-elasticsearch:提供集群日志采集、存储与查询

分层设计

  1. 生态系统层:构建在Kubernetes之上的一系列开源项目、工具和解决方案,如日志、监控等
  2. 接口层:kubectl 命令行工具、客户端 SDK 以及集群联邦
  3. 治理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态 Provision 等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy 等)
  4. 应用层:部署(无状态/有状态应用、批处理、集群应用等)和路由(服务发现、DNS解析等)
  5. 内核层:对外提供API构建高层的应用,对内提供插件式应用执行环境,类似 linux 内核(这一层是必须的,这一层往上的都是可插拔,可替换为其他解决方案)

资源与对象

k8s 中所有的东西都是资源,如 node,pod…。k8s 中的资源是声明式的。 通过 yaml 或 json 来声明

对象就是某一资源的持久化实体,对象的管理都是通过调用 kube-apiserver 提供的 RESTful API 来实现的,kubectl 本质也是调 API。

规约和状态

几乎每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置

规约(spec):描述对象的期望状态(Desired State)

状态(status):描述对象的当前状态(Current State),任意时刻 master 都管理着当前状态使其向期望状态发展

对象的创建

  1. 使用 json 或 yaml 描述资源,在描述中有一些必填字段:
    • apiVersion - 创建该对象所使用的 Kubernetes API 的版本,弃用的 API 版本:https://kubernetes.io/zh-cn/docs/reference/using-api/deprecation-guide/
    • kind - 想要创建的对象的类别
    • metadata - 帮助唯一标识对象的一些数据,包括一个 name 字符串、UID 和可选的 namespace
    • spec - 你所期望的该对象的状态
  2. 调用 API 创建对象
    • 直接发 HTTP 请求,在请求体中携带 json 数据
    • 使用 kubectl 指定用于描述资源的文件(一般是 yaml),kubectl 自动将其转换为 json 格式然后发起 HTTP 请求
    • 使用其他工具调用 API

常见资源

元数据型

对于资源元数据的描述

Horizontal Pod Autoscaler(HPA)

HPA 可以根据指标(如CPU利用率或自定义指标)来监视应用程序的负载,并根据预定义的规则自动调整 Pod 的副本数量。

  • 控制管理器每隔 30s(可以通过–horizontal-pod-autoscaler-sync-period 修改)查询 metrics 的资源使用情况
  • 支持三种 metrics 类型
    • 预定义metrics(比如 Pod 的 CPU )以利用率的方式计算
    • 自定义的 Pod metrics,以原始值(raw value)的方式计算
    • 自定义的 object metrics
  • 支持两种 metrics 查询方式:Heapster 和自定义的 REST API
  • 支持多 metrics

PodTemplate

对 pod 的定义,被包含在其他的 Kubernetes 对象中(例如 Deployment、StatefulSet、DaemonSet 等控制器)

LimitRange

可以对集群内 Request 和 Limits 的配置做一个全局的统一的限制,相当于批量设置了某一个范围内(某个命名空间)的 Pod 的资源使用限制。

集群级

集群级的资源,在集群内部的所有资源都可以共享使用

Namaspace

命名空间提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一名字空间内的资源名称要唯一,但跨名字空间时没有这个要求。 名字空间作用域仅针对带有命名空间的对象(例如 Deployment、Service 等),这种作用域对集群范围的对象 (例如 StorageClass、Node、PersistentVolume 等)不适用。

Node

node 属于一种特殊的资源,k8s 只是负责管理 node,创建 node 对象时需要向 api-server 注册,注册后 k8s 会检查 node 是否健康,只有健康的 node 才会去真正运行 pod。

一个节点包含以下信息:

  • 地址
    • HostName:由节点的内核报告。可以通过 kubelet 的 --hostname-override 参数覆盖。
    • ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。
    • InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。
  • 状态
  • 容量和可分配
    • capacity 块中的字段标示节点拥有的资源总量。
    • allocatable 块指示节点上可供普通 Pod 使用的资源量。
  • 信息
    • 节点的一般信息,如内核版本、Kubernetes 版本

CluterRole

ClusterRole 是一组权限的集合,但与 Role 不同的是,ClusterRole 可以在包括所有 Namespace 和集群级别的资源或非资源类型进行鉴权。

ClusterRoleBinding

ClusterRoleBinding:将 Subject 绑定到 ClusterRole,ClusterRoleBinding 将使规则在所有命名空间中生效。可以绑定到该集群下的任意 User、Group 或 Service Account

命名空间级

命名空间提供一种机制,将同一集群中的资源划分为相互隔离的组。

工作负载型

工作负载是在 Kubernetes 上运行的应用程序。

Pod

pod = containers + resource ,pod 是 k8s 中的最小可执行单元,可以看作一组容器加上它们所共享的资源,包括网络,文件系统等。但一般不会直接操作 pod,pod 的管理都是通过一些控制器来实现的。pod 通常有以下两种用法:

  • 运行单个容器的 Pod。“每个 Pod 一个容器” 模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
  • 运行多个协同工作的容器的 Pod。 Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。 这些位于同一位置的容器可能形成单个内聚的服务单元

pod 可以很轻松的横向扩展,创建出多个同等的 pod,这些同等的 pod 通常被称为副本(Replication)

控制器

控制器是用来创建和管理多个 Pod,管理副本和上线,并在集群范围内提供自修复能力。

适用无状态服务

  • ReplicationController(RC):可以保证在任意时间运行 Pod 的副本数量,能够保证 Pod 总是可用的。(v1.11 弃用)

  • ReplicaSet(RS):和 RC 的作用是一样的,可以通过 selector 来选择对哪些 pod 生效。

  • Deployment: 对 RS 的更高层次封装,提供了一种声明性的方式来定义和管理 Pod 副本集的创建和更新过程。常见用途:

    • 应用部署
    • 应用滚动升级和回滚
    • 动态平滑扩缩容
    • 灰度发布
    • 健康检查与自愈

    Deployment 并不直接操作 pod,是通过 ReplicaSet 来间接操作 pod

适用有状态服务

StatefulSet:与 Deployment 类似,但会为 pod 维护稳定持久的状态

特点:

  • 持久化存储:Pod 重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现
  • Pod 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有 Cluster IP 的 Service)来实现
  • 有序扩缩:扩缩容都是有序的

注意事项:

  • 所有Pod的Volume必须使用PersistentVolume或者是管理员事先创建好
  • 删除StatefulSet时不会删除Volume
  • StatefulSet 需要一个 Headless Service 来定义 DNS domain,需要在 StatefulSet 之前创建好

守护进程

DaemonSet:保证在每个 Node 上都运行一个 pod 副本。常见用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

任务/定时任务

  • Job:一次性运行的任务,运行完后就删除 pod
  • CronJob:定时的 Job

服务发现

Service

Service API 是 Kubernetes 的组成部分,它是一种抽象,帮助你将 Pod 集合在网络上公开出去。 每个 Service 对象定义端点的一个逻辑集合(通常这些端点就是 Pod)以及如何访问到这些 Pod 的策略。

通常用作集群内部的服务发现

Ingress

Ingress 是用于将外部流量路由到 Kubernetes 集群内部服务的 API 和资源对象。它充当了集群内部服务的入口点,并提供了流量路由、负载均衡和 TLS 终止等功能。

ingress 需要配合 ingress controller 一起使用才能发挥作用,ingress 只是相当于路由规则的集合而已,真正实现路由功能的,是 Ingress Controller,ingress controller 和其它 k8s 组件一样,也是在 Pod 中运行。常用的 ingress controller 有 nginx、taefik 等

存储

Volume

Volume 用于提供持久性和可共享的存储解决方案,以供Pod中的容器共享使用。Volume 可以被理解为容器中的一个目录或文件,可以在容器的生命周期内保持数据的持久性。

CSI

Container Storage Interface 是 k8s 中的存储标准接口规范,只要满足了这个接口的规范,都可以用来作为容器存储系统。

配置

ConfigMap

用来放配置,与 Secret 是类似的,只是 ConfigMap 放的是明文的数据,Secret 是密文存放。

Secret

Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用。有以下三种类型:

  • Service Account:用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的 /run/secrets/kubernetes.io/serviceaccount 目录中;
  • Opaque:base64 编码格式的 Secret,用来存储密码、密钥等;
  • kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息。
DownwardAPI

downwardAPI 是提供了一种直接将 pod 信息注入到容器里面的能力,通常有两种方式:

  • 环境变量:用于单个变量,可以将 pod 信息和容器信息直接注入容器内部

  • volume 挂载:将 pod 信息生成为文件,直接挂载到容器内部中去

其他

Role

Role 是一组权限的集合,例如 Role 可以包含列出 Pod 权限及列出 Deployment 权限,Role 用于给某个 Namespace 中的资源进行鉴权。

RoleBinding

将某一个资源绑定到 role 上,使其具备 role 所包含的权限

深入 Pod

探针

Pod 探针(Probe)是用于检查和监视运行中的容器状态的一种机制。让 Kubernetes 知道容器是否正常工作,以及何时需要重新启动容器或将容器从服务负载均衡器中移除。

探测机制

exec

在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。

grpc

使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC 健康检查。 如果响应的状态是 “SERVING”,则认为诊断成功。

httpGet

对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。

tcpSocket

对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

注意: 和其他机制不同,exec 探针的实现涉及每次执行时创建/复制多个进程。 因此,在集群中具有较高 pod 密度、较低的 initialDelaySecondsperiodSeconds 时长的时候, 配置任何使用 exec 机制的探针可能会增加节点的 CPU 负载。 这种场景下,请考虑使用其他探针机制以避免额外的开销。

探测类型

livenessProbe

指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success

readinessProbe

指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success

startupProbe

指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器, 而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success

生命周期

init 容器

init 容器是在 Pod 就绪前运行的特殊容器,在 sepc.initContainers 中设定,Pod 创建时按设定的顺序执行,不支持探针和 lifecycle。

容器回调

PostStart 在容器被创建后就执行,不能保证其与 ENTRYPOINT 的执行顺序

PreStop 就是在 Pod 终止前容器会执行的一些行为。通常使用 PreStop 进行服务下线,数据清理,数据销毁等操作。

有三种类型:

  • exec:描述了在容器中运行的操作
  • httpGet:由 kubelet 向容器发送 HTTP 请求
  • tcpSocket:已经弃用

使用 .spec.containers.lifecycle 配置

Pod 退出流程

  1. Endpoint 删除 pod 的 ip
  2. Pod 变为 Terminating 状态:变为删除中的状态后,会给 pod 一个宽限期,让 pod 去执行一些清理或销毁操作。使用 terminationGracePeriodSeconds 控制,对 pod 中的所有容器起作用
  3. 执行 PreStop

资源调度

Label 和 Selector

lable 是一组描述资源元信息的键值对。创建方式:

  • 配置文件时标明:metadata.labels
  • 临时创建或修改:kubectl label {object} {objectName} {labelName}={labelValue} --overwrite,临时创建的 label 在 deployment 的 template 改变后会丢失。

selector 匹配的是 label 满足条件的资源,常见的操作符包括等于(=)、不等于(!=)、存在(in)、不存在(notin)等。

Deployment 的 selector 用于匹配 replicaSet,replicaSet 的 selector 用于匹配 pod。

Deployment

deployment 通过绑定一个 replicaSet 来间接实现对 pod 的管理,并且在 replicaSet 的功能上还增加了滚动更新、回滚等高级功能

不需要显示的创建 RS,创建 deployment 时会自动创建一个 RS 并与其绑定在一起。

滚动更新

仅当 Deployment Pod 模板(即 .spec.template)发生改变时,例如模板的标签或容器镜像被更新, 才会触发 Deployment 上线。其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作。

  • 使用 kubectl set 命令更改 deloyment 中的 template,可以修改 image、env…
  • 通过 kubectl edit deploy {deployName} 直接修改配置文件中的 template

使用 kubectl rollout status deploy {deployName} 查看滚动更新过程

多 Deployment 动态更新

当一个滚动更新没完成的时候,template 又变了,就会将上一次的更新直接舍弃,去执行新的滚动更新。

回滚

当一个 deployment 不稳定或根本跑不起来的时候,可以回滚到原来稳定的版本。

使用 kubectl rollout history deploy {deployName} 来列出历史版本

使用 kubuctl rollout undo deploy {deployName} 来回退到上一个版本,也可以在末尾加上 --to-revision={desired} 来指定回滚到哪一个版本

通过设置 .spec.revisonHistoryLimit 来指定 deployment 保留多少 revison,如果设置为 0,则不允许 deployment 回退

扩缩容

  • 使用 kubectl scale --replicas={desired} {object} {objectName} 命令,deployment, replicaSet, replicaController, stateful Set 都可以使用这个命令来扩缩容

  • kubectl edit 编辑 replicas 的数量即可扩缩容

  • kubectl patch 命令就地更新

扩缩容时是直接创建或者删除副本,pod template 没有发生改变,不会触发更新

暂停与恢复

只要 pod template 一发生变更,就会触发 deployment 的更新,但是只有最后一个状态才是想要的,中途触发的更新都是没没必要的。

使用 kubectl rollout pause deploy {deployName} 来暂时停止自动更新,更改完成后使用 kubectl rollout resume deploy {deploName} 来恢复暂停的 deployment。

StatefulSet

statefulSet 作业与 deployment 类似,但通常用于管理有状态的应用,通常用于需要满足以下情况的应用:

  • 稳定的、唯一的网络标识符: StatefulSet 确保每个 Pod 的名称按照指定的名称基础和序号顺序分配,并在重新部署、调度到新节点或者其他任何操作中保持不变。
  • 稳定的、持久的存储: 即使 StatefulSet 中的 Pod 被重新调度,它们也能够保持对存储卷的访问。每个 Pod 都会被分配一个与 Pod 名称相对应的持久存储卷(PersistentVolume),这意味着即使 Pod 被删除,相同身份的新 Pod 也能重新挂载同一个存储卷。
  • 有序的、优雅的部署和扩展: StatefulSet 中的 Pods 是按顺序创建和删除的;在创建下一个 Pod 前,上一个必须是运行且就绪的。这适用于扩展和缩容操作。这种有序性对于一些依赖于严格启动和关闭顺序的系统来说非常重要,比如分布式数据存储。
  • 有序的、优雅的删除和终止: 当 Pods 被删除时,它们的终止也是有序的,并且遵循相反的顺序。这确保了在关闭时不会破坏应用程序的一致性和稳定性。

DNS

StatefulSet 中每个 Pod 的 DNS 格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local

  • serviceName 为 Headless Service 的名字
  • 0..N-1 为 Pod 所在的序号,从 0 开始到 N-1
  • statefulSetName 为 StatefulSet 的名字
  • namespace 为服务所在的 namespace,Headless Servic 和 StatefulSet 必须在相同的 namespace
  • .cluster.local 为 Cluster Domain

扩缩容

  • 使用 kubectl scale --replicas={desired} {object} {objectName} 命令,deployment, replicaSet, replicaController, stateful Set 都可以使用这个命令来扩缩容

  • kubectl edit 编辑 replicas 的数量即可扩缩容

  • kubectl patch 命令就地更新

statefulSet 的扩缩容是有序的,如从 0 ~ 4 创建,那么缩容时是 4 ~ 0

更新

StatefulSet 的 .spec.updateStrategy 字段让你可以配置和禁用掉自动滚动更新 Pod 的容器、标签、资源请求或限制、以及注解。有两个允许的值:

  • OnDelete

    当 StatefulSet 的 .spec.updateStrategy.type 设置为 OnDelete 时, 控制器将不会自动更新 StatefulSet 中的 Pod。 必须手动删除 Pod 以便让控制器创建新的 Pod,以此来对 StatefulSet 的 .spec.template 的变动作出反应。只有在 pod 被删除时会进行更新操作

  • RollingUpdate

    RollingUpdate 更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。这是默认的更新策略。只有当 pod 的 template 发生改变时才会触发

在 StatefulSet 中更新时是基于 pod 的顺序倒序更新的

灰度发布

利用滚动更新中的 partition 属性,可以实现简易的灰度发布的效果

例如我们有 5 个 pod,如果当前 partition 设置为 3,那么此时滚动更新时,只会更新那些 序号 >= 3 的 pod

利用该机制,我们可以通过控制 partition 的值,来决定只更新其中一部分 pod,确认没有问题后再主键增大更新的 pod 数量,最终实现全部 pod 更新

删除

删除 StatefulSet 和 Headless Service

**级联删除:**删除 statefulset 时会同时删除 pods

kubectl delete sts {stsName}

**非级联删除:**删除 statefulset 时不会删除 pods,删除 sts 后,pods 就没人管了,此时再删除 pod 不会重建的

kubectl deelte sts {stsName} --cascade=false

删除 service kubectl delete svc {svcName}

删除 pvc

kubectl delete pvc {pvcName}

DaemonSet

为每一个匹配的 node 部署一个守护进程,一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。

建议将更新模式设置为 OnDelete

指定节点运行

使用 .spec.template.spec.nodeSelector 指定节点

HPA 自动扩缩容

HPA 通过观察 pod 的 cpu、内存使用率或自定义 metrics 指标进行自动的扩容或缩容 pod 的数量。

通常用于 Deployment,不适用于无法扩/缩容的对象,如 DaemonSet

控制管理器每隔30s(可以通过–horizontal-pod-autoscaler-sync-period修改)查询metrics的资源使用情况

CPU\Memory 指标监控

直接使用 kubectl autoscale {object} {objectName} --cpu-percent=20 --min=2 --max=5 就可以创建一个 HPA,cpu-percent 指扩缩容的阈值,max, min 分别代表副本数的最大与最小值

自定义指标

  • 控制管理器开启 –horizontal-pod-autoscaler-use-rest-clients
  • 控制管理器的 –apiserver 指向 API Server Aggregator
  • 在 API Server Aggregator 中注册自定义的 metrics API

Job 和 CronJob

Job 只执行一次,CronJob 是定时任务,基于 crontab 实现

InitContainer

Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。

  • 它们总是运行到完成。
  • 每个都必须在下一个启动之前成功完成。

如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。 然而,如果 Pod 对应的 restartPolicy 值为 “Never”,并且 Pod 的 Init 容器失败, 则 Kubernetes 会将整个 Pod 状态设置为失败。

污点和容忍

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

使用 kubectl taint no noName {key}={value}:{effect} 为节点打污点,再末尾加个 - 就可以移除污点

污点的影响:

  • NoSchedule:不能容忍的 pod 不能被调度到该节点,但是已经存在的节点不会被驱逐
  • NoExecute:不能容忍的节点会被立即清除;能容忍且没有配置 tolerationSeconds 属性,则可以一直运行;设置了 tolerationSeconds: 3600 属性,则该 pod 还能继续在该节点运行 3600 秒

容忍就是在 pod spec 下配置 tolerances,需要与节点污点的键值对及影响相匹配,有两种匹配类型

  • Equal:要求 k-v 都必须相同才表示能容忍此污点
  • Exist:只比较 k 不关心 v,只要 k 存在就可以容忍

亲和性

nodeSelector 只能选择拥有指定标签的节点,亲和性和反亲和性提供了逻辑上更细粒度的控制。

节点亲和性

节点亲和性是为 pod 匹配 nod

  • requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强。
  • preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。

Pod 亲和性和反亲和性

pod 亲和性和反亲和性用于 pod 与 pod 之间调度的关系。Pod 的亲和性与反亲和性也有两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

例如,使用 requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器, 将两个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。 类似地,使用 preferredDuringSchedulingIgnoredDuringExecution 反亲和性来将同一服务的多个 Pod 分布到多个云提供商可用区中。

要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity 字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity 字段。

服务发布

service、ingress 和 Gateway API 都可以用于服务发布,service 通常用于集群内部的通信,ingress 和 Gateway API 通常用于对集群外暴露服务。

Service

每当 service 被创建时,endpoint controller 都会创建一个与其绑定的 endpoints,endpoints 包含了所有与 service 选择器匹配到 ip 和端口。然后 endpoint controller 监视 pod 来动态的更新 service 和 endpoints。

路由过程

  1. DNS 解析: 集群内部的组件(比如一个 Pod)使用 Service 的名称来访问服务,该名称通过集群的 DNS 服务(比如 CoreDNS)解析为 ClusterIP
  2. 到达 kube-proxy: 发往 ClusterIP 的流量到达节点上运行的 kube-proxy。根据 kube-proxy 的工作模式(如 iptablesipvs 或用户空间模式),它会处理流量的转发。
  3. 网络规则处理:
    • iptables 模式下,kube-proxy 为每个 Service 配置 iptables 规则。当流量到达 ClusterIP 时,这些规则会被用来选择一个后端 Pod 并将流量转发到那里。
    • ipvs 模式下,kube-proxy 使用 IP 虚拟服务器(IPVS)来转发流量,这种方式通常提供更好的性能。
  4. 选择后端 Pod: kube-proxy 根据 ServiceEndpoints 对象(包含所有匹配 Service 选择器的 Pod 的 IP 地址)来选择一个后端 Pod。如果配置了负载均衡(如轮询),则 kube-proxy 会相应地选择一个 Pod
  5. 流量转发到 Pod: kube-proxy 将流量转发到选定的 Pod。如果 Pod 位于不同的节点上,流量将被路由到该节点,并最终到达目标 Pod

重点:

  • ClusterIP 为服务提供了一个稳定的内部 IP 地址,无需关心后端 Pod 的实际位置。
  • kube-proxy 负责在节点层面上实现 Service 定义的流量路由机制。
  • 流量管理和负载均衡是透明的,对于使用服务的 Pod 来说,它们只是简单地向 ServiceClusterIP 发送请求。

类型

ClusterIP
  • 特点:
    • 提供一个内部的 IP 地址来访问服务,只能在集群内部访问。
    • kube-proxy 使用 iptablesipvs 路由到实际的 Pods
    • 默认的 Service 类型。
  • 应用场景:
    • 当你需要在 Kubernetes 集群内部让 Pods 之间通信时使用。
    • 适用于不需要从集群外部访问的服务,例如内部数据库或后端服务。
  • 访问方式:
    • 在集群内部的任何 Pod 里,使用 <service-name>.<namespace>.svc.cluster.local 的 DNS 名称来访问 Service。如果 Service 和 Pod 在同一个命名空间中,可以省略命名空间和域后缀,直接使用 <service-name>
NodePort
  • 特点:
    • 在集群的每个节点上开放一个端口(NodePort),将该端口路由到 Service
    • 外部流量可以通过 <NodeIP>:<NodePort> 访问服务。
    • NodePort 范围通常在 30000-32767
  • 应用场景:
    • 当你需要从集群外部访问服务时使用,例如一个公共的 Web 应用。
    • 常用于开发和测试环境,以便于访问集群内的服务。不推荐生产环境使用。
  • 访问方式:
    • 从集群外部, 使用 <node-ip>:<node-port> 来访问 Service,其中 <node-ip> 是集群中任何一个节点的 IP 地址,<node-port> 是 Service 定义中指定的 NodePort。
LoadBalancer
  • 特点:
    • 集成外部的云提供商负载均衡器。
    • 自动为服务分配外部可访问的公共 IP 地址。
    • 在后端自动配置节点上的 NodePort 和路由规则。
  • 应用场景:
    • 适用于需要从互联网或外部网络直接访问的服务。
    • 常用于生产环境,特别是在云平台上运行的 Kubernetes 集群。
  • 访问方式:
    • Service 会被分配一个外部 IP 地址。你可以直接使用这个外部 IP 地址来访问 Service。
ExternalName
  • 特点:
    • 通过返回一个名字(而不是 IP 地址)来映射服务。
    • 不使用代理或转发,仅作为 DNS 的别名。
    • spec.externalName 字段指定返回的名称。
  • 应用场景:
    • 当你需要通过 Kubernetes 服务访问外部服务时使用,例如引用集群外部的 REST 服务或数据库。
    • 对于将传统服务迁移到 Kubernetes 环境时作为中间步骤。
Headless Service
  • 特点:
    • 没有 ClusterIP,DNS 查询会返回 Pods 的直接 IP 地址。
    • 通过将 spec.clusterIP 设置为 "None" 来创建。
  • 应用场景:
    • 当你不需要负载均衡和单一服务 IP,而是需要直接访问每个 Pod 时使用。
    • 常用于需要直接与 Pods 通信的服务发现机制,例如分布式数据库(如 Cassandra 或 Elasticsearch)。

访问集群外部服务

两种解决方案

手动创建并维护 Endpoints

在创建 service 时不指定 selector,然后创建并维护一个 endpoints 与 service 绑定在一起。

使用场景和优势:

  • 当需要将服务路由到特定的外部 IP 地址(而不是 DNS 名称)时。
  • 提供了对负载均衡和健康检查等更细粒度控制的可能性。
  • 适用于更复杂的网络场景,比如当外部服务有多个端点或特定的网络要求时。

注意事项:

  • 需要手动维护和更新 Endpoints,特别是当外部服务的 IP 地址发生变化时。
  • 这种方法相对更复杂,需要更多的手动配置。
使用 ExternalName

将服务映射到外部的 DNS 名称,不会代理流量,而是依赖于 DNS 解析来指向外部服务。

使用场景和优势

  • 当需要将服务映射到外部 DNS 名称时使用。
  • 简单易用,无需手动维护 Endpoints
  • 适用于不需要复杂网络配置的场景,例如,当外部服务有一个稳定的 DNS 名称。

注意事项

  • 不支持 IP 地址,只能使用 DNS 名称。

  • 不会实现任何形式的负载均衡或健康检查。

Ingress

Ingress 需要 ingress 和 ingress controlle 一起才能起作用,常用的 ingress controller 有 nginx、traefik、kong 等。

ingress 可通常用于管理对 HTTP 和 HTTPS 流量的访问,包括 URL 路由、重定向、TLS/SSL 终端和其他。

  1. Ingress 资源定义:
    • 定义一个 Ingress 资源,指定外部请求如何路由到集群内的服务。
    • 例如,基于不同的主机名或路径将流量路由到不同的服务。
  2. Ingress controller 的作用:
    • Ingress 控制器监视 Kubernetes API 中的 Ingress 资源。
    • 当 Ingress 资源被创建或更新时,控制器读取配置并更新其内部路由表,以便正确地转发外部请求。

流量链路

  1. 外部请求:
    • 用户从外部发起 HTTP/HTTPS 请求,例如通过浏览器或其他客户端。
  2. DNS 解析:
    • 请求中的域名通过 DNS 解析为指向 Ingress 控制器的 IP 地址。
  3. 到达 Ingress 控制器:
    • 请求到达托管 Ingress 控制器的节点。这通常是通过云提供商的负载均衡器或者直接暴露的节点端口实现的。
  4. Ingress 控制器处理请求:
    • Ingress 控制器根据其路由规则(根据请求的主机名、路径等)确定请求应该路由到哪个服务。
  5. 转发到后端服务:
    • Ingress 控制器将请求转发到集群内相应的 Service
  6. Service 的负载均衡:
    • Service 根据其负载均衡规则将请求路由到后端的一个或多个 Pods
  7. 处理请求:
    • Pod 接收请求,处理并返回响应。
  8. 响应返回给用户:
    • 响应通过同样的路径返回给用户,完成请求的往返。

Gateway API

配置

ConfigMap

configMap 可以看做一个明文的键值对

直接使用 kubectl create cm -h 查看创建示例与 flag 说明

常用命令:

  • kubectl create cm {cmName} --from-file=/xxx/xxx/:读取某一文件夹或文件来创建 configMap
  • kubectl create cm {cmNmae} --from-file=key=/xxx/xxx:读取某一个具体的文件并命名为 key 来创建 configMap
  • kubectl create cm {cmName} --from-literal=key1=config1 --from-literal=key2=config2:根据 key-value 创建 configMap

常见用法

  • 在 Pod template 中设置 .spec.containers.env 时使用 valueFrom.configMapKeyRef ,指定想要的 configMap
  • 在 Pod template 中使用类型为 configMap 的 volume 并挂载到容器中

Secret

secret 对键值对有一个加密的处理,如果要加密的字符中,包含了有特殊字符,需要使用转义符转移,例如 $ 转移后为 $,也可以对特殊字符使用单引号描述。

默认情况下 secrete 是未加密的存储在 etcd 里,仅仅做了一个 base64 编码操作

一般有三种类型的 secrete:

  • docker-registry:使用得最多,数据存储格式为 base64 编码后的 json,在 Pod template 中设置 .spec.imagePullSecrets.name 即可使用创建的 secret

    kubectl create secret docker-registry NAME --docker-username=user --docker-password=password
    --docker-email=email [--docker-server=string] [--from-file=[key=]source]
    [--dry-run=server|client|none] [options]
    
  • generic:和 configMap 很相似

  • tls

目录覆盖

使用 ConfigMap 或 Secret 挂载到目录的时候,会将容器中源目录给覆盖掉,此时我们可能只想覆盖目录中的某一个文件,但是这样的操作会覆盖整个文件,因此需要使用到 SubPath

配置方式:

  1. 定义 volumes 时需要增加 items 属性,配置 key 和 path,且 path 的值不能从 / 开始
  2. 在容器内的 volumeMounts 中增加 subPath 属性,该值与 volumes 中 items.path 的值相同

修改配置

  • 使用 edit 命令直接修改
  • 使用 replace 命令修改:kubectl create cm --from-file=xxx --dry-run -o yaml | kubectl replace -f- 因为 --dry-run 命令只会打印 yaml,不会创建对象,所以得到了 yaml 后再使用 replace 即可

对于一些敏感服务的配置文件,在线上有时是不允许修改的,此时在配置 configmap 时可以设置 immutable: true 来禁止修改

热加载

更新 configmap 中的配置,pod 中的更新策略:

  • 默认方式:会更新,更新周期是更新时间 + 缓存时间
  • subPath:不会更新
  • 变量形式:如果 pod 中的一个变量是从 configmap 或 secret 中得到,同样也是不会更新的

对于 subPath 的方式,可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置

Downward API

Downward API 允许容器在不使用 Kubernetes 客户端或 API 服务器的情况下获得自己或集群的信息。

存储

列出常见存储

Volumes

volumes 主要解决两个问题:

  • 存储持久化
  • 跨容器共享文件

HostPath

hostPath 将节点上的文件或目录挂载到 Pod 上,此时该目录会变成持久化存储目录,即使 Pod 被删除后重启,也可以重新加载到该目录,该目录下的文件不会丢失

EmptyDir

EmptyDir 主要用于一个 Pod 中不同的 Container 共享数据使用的,由于只是在 Pod 内部使用,因此与其他 volume 比较大的区别是,当 Pod 如果被删除了,那么 emptyDir 也会被删除。

存储介质可以是任意类型,如 SSD、磁盘或网络存储。可以将 emptyDir.medium 设置为 Memory 让 k8s 使用 tmpfs(内存支持文件系统),速度比较快,但是重启 tmpfs 节点时,数据会被清除,且设置的大小会计入到 Container 的内存限制中。

emptyDir 的一些用途:

  • 缓存空间,例如基于磁盘的归并排序。
  • 为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
  • 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。

NFS

nfs 卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。 不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。

效率较低,不适用于频繁读写的场景。

PV 与 PVC

PV 是对持久化存储的一个抽象,对于 Pod 来说,屏蔽了底层存储的细节,仅对其提供存储资源(存储资源实际是由磁盘、nfs等提供的,但对于 Pod 来说就是 PV 提供的)。具有独立的生命周期,与 Pod 无关。

每一个 Pod 中就通过 PVC 去申请 PV 的资源。

CSI 是一个标准化的接口,旨在使存储提供商(如云供应商和存储硬件供应商)能够更容易地为 Kubernetes 提供存储解决方案,而无需每次都对 Kubernetes 本身进行修改或更新。任何实现了容器存储接口(Container Storage Interface,CSI)规范的存储系统都可以为 Kubernetes 提供持久卷(Persistent Volume,PV)资源。

构建

  • 静态构建:集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
  • 动态构建:如果 PV 已经不足以满足 PVC 的需求,可以通过 StorageClass 动态构建新的 PV

绑定

当用户创建一个 PVC 对象后,主节点会监测新的 PVC 对象,并且寻找与之匹配的 PV 卷,找到 PV 卷后将二者绑定在一起。

如果找不到对应的 PV,则需要看 PVC 是否设置 StorageClass 来决定是否动态创建 PV,若没有配置,PVC 就会一致处于未绑定状态,直到有与之匹配的 PV 后才会申领绑定关系。

使用

Pod 直接使用的是 PVC,当 PVC 与 PV 绑定后,PV 会被保护起来而无法删除

回收策略

可以从 API 中将 PVC 对象删除, 从而允许该资源被回收再利用。PersistentVolume 对象的回收策略告诉集群, 当其被从申领中释放时如何处理该数据卷。

有以下策略:

  • 保留:数据不会被删除,卷被视为已释放
  • 删除
  • 回收(已经弃用,用动态制备替代)

PV 状态

  • Available:空闲,未被绑定
  • Bound:已经被 PVC 绑定
  • Released:PVC 被删除,资源已回收,但是 PV 未被重新使用
  • Failed:自动回收失败

StorageClass

存储类可以实现 PV 的动态构建,每一个 sc 都有一个 Provisioner 制备器用来决定使用哪个卷插件制备 PV,该字段必须指定。

使用存储类就可以不再手动创建 PV。

认证与权限

所有对 k8s 的操作其实都是调 control-plane 上 api-server 的 RESTful 接口,调用这些接口都要经过认证和鉴权。

认证

k8s 集群中有两类用户:服务账户(Service Account)和普通账户(User Account)

注意:在 Kubernetes 中不能通过 API 调用将普通用户添加到集群中

  • 普通帐户是针对(人)用户的,服务账户针对 Pod 进程。
  • 普通帐户是全局性。在集群所有 namespaces 中,名称具有惟一性。
  • 通常,群集的普通帐户可以与企业数据库同步,新的普通帐户创建需要特殊权限。服务账户创建目的是更轻量化,允许集群用户为特定任务创建服务账户。
  • 普通帐户和服务账户的审核注意事项不同。
  • 对于复杂系统的配置包,可以包括对该系统的各种组件的服务账户的定义。

service account 由三个控制器来维护:

  • Service Account Admission Controller:是 api-server 的一部分

    1. 如果 pod 没有设置 ServiceAccount,则将 ServiceAccount 设置为 default。

    2. 确保 pod 引用的 ServiceAccount 存在,否则将会拒绝请求。

    3. 如果 pod 不包含任何 ImagePullSecrets,则将ServiceAccount 的 ImagePullSecrets 会添加到 pod 中。

    4. 为包含 API 访问的 Token 的 pod 添加了一个 volume。

    5. 把 volumeSource 添加到安装在 pod 的每个容器中,挂载在 /var/run/secrets/kubernetes.io/serviceaccount。

  • Token Controller:是 controller-manager 的一部分:

    • 观察 serviceAccount 的创建,并创建一个相应的 Secret 来允许 API 访问。
    • 观察 serviceAccount 的删除,并删除所有相应的ServiceAccountToken Secret
    • 观察 secret 添加,并确保关联的 ServiceAccount 存在,并在需要时向 secret 中添加一个 Token。
    • 观察 secret 删除,并在需要时对应 ServiceAccount 的关联
  • Service Account Controller:在 namespaces 里管理ServiceAccount,并确保每个有效的 namespaces 中都存在一个名为 “default” 的 ServiceAccount。

RBAC

k8s 中的权限控制是基于 RBAC 的。有两组作用域不同但机制相同的对象来控制权限

集群作用域:

  • ClusterRole:功能与 Role 一样,区别是资源类型为集群类型,而 Role 只在 Namespace
  • ClusterRolebinding:作用于集群之上,可以绑定到该集群下的任意 User、Group 或 Service Account

命名空间作用域:

  • Role:代表一个角色,会包含一组权限,没有拒绝规则,只是附加允许。它是 Namespace 级别的资源,只能作用与 Namespace 之内。
  • RoleBinding:作用于 Namespace 内,可以将 Role 或 ClusterRole 绑定到 User、Group、Service Account 上。