一、计算节点相关

在 k8s 生产话落地时,有许多问题需要考虑,问题主要分为两大类:
计算节点相关:

  • 如何批量安装和升级计算节点的操作系统?
  • 如何管理配置计算节点的网络信息?
  • 如何管理不同SKU(Stock Keeping Unit)的计算节点?
  • 如何快速下架故障的节点?
  • 如何快速扩缩集群的规模?

控制平面相关:

  • 如何在主节点上下载、安装和升级控制平面组件及其所需的配置文件?
  • 如何确保集群所需的其他插件,例如 CoreDNS、监控系统等部署完成?
  • 如何准备控制平面组件的各种安全证书?
  • 如何快速升级或回滚控制平面组件的版本

二、操作系统相关

改选哪种类型的操作系统来部署 k8s,市面上的操作系统主要分为两大类:
通用操作系统:

  • Ubuntu
  • CentOS
  • Fedora

专为容器优化的最小化操作系统:

  • CoreOS
  • RedHat Atomic
  • Snappy Ubuntu Core
  • RancherOS

这么多种类的操作系统,改选择哪一种,主要从以下方面进行评估和选择:

  • 是否有生态系统
  • 成熟度
  • 内核版本
  • 对运行时的支持
  • Init System
  • 包管理和系统升级
  • 安全

生态系统与成熟度
image.png
通用操作系统相较于容器化操作系统的优势就是成熟,但是容器化也有其优势所在:

  • 原子升级和回退
  • 更高的安全性

为了保证生产环境的稳定,生产环境的很多基础设施应该是不可变的,不可变基础设施包括:不可变的容器镜像,不可变的主机操作系统等,可变意味着风险。

可变基础设施的风险

  • 在灾难发生的时候,难以重新构建服务。持续过多的手工操作,缺乏记录,会导致很难由标准初始化后的服务器来重新构建起等效的服务。
  • 在服务运行过程中,持续的修改服务器,就犹如程序中的可变变量的值发生变化而引入的状态不一致的并发风险。这些对于服务器的修改,同样会引入中间状态,从而导致不可预知的问题。

其中 Atomic 操作系统就是一个不可变操作系统,它是由 Red Hat 支持的软件包安装系统,支持多种 Distro: Fedora, CentOS, RHEL,其优势如下:

  • 不可变操作系统,面向容器优化的基础设施,灵活和安全性较好,只有 /etc 和 /var 可以修改,其他目录均为只读。
  • 基于 rpm-ostree 管理系统包,使得生产系统中构建镜像非常简单,支持操作系统升级和回滚的原子操作。

在云原生环境下,追求最小化操作系统的原则就是只安装必要的工具,这里的必要指的是支持系统运行的最小工具集。任何调试工具,比如性能排查,网络排查工具,均可以后期以容器形式运行,这样就可以保证操作系统的性能和稳定性,以及减少漏洞,安全保障。

操作系统构建流程
image.png
社区有一个一个的rpm的repo,会在rpm的repo基础之上,创建自己的rpm的snapshot,我们在公司会去拉取社区rpm的镜像,然后构建我们自己的snapshot,这个snapshot除了镜像之外,还会去看社区有哪些patch,以及生产系统发现的问题,这些问题修复一并会通过rpm rebuilder变成一个rpm包,放在rpm的snapshot里面。

这样其实我们自己维护了一套rpm的repo,有了这个repo之后,有个工具叫做rpm-ostree,这个工具做的事情就是将rpm包编译成一个ostree的格式。

我们追求的是最小的操作系统镜像,所以这里面只安装最核心的服务,需要辅助的工具怎么办?这里面有buildah,它的输出就是docker image。

所以这里面会构建两种,一种是基本的操作系统镜像,通过ostree保持最小的集合,一些utility一些辅助的工具,我们通过docker build构建成docker的镜像,提供后续做整个操作系统的问题排查。
有了ostree之后,它可以通过http的形式暴露出来,那这里面会有两条线,一条就是裸金属的这条线,我们启的是bare mental,bare mental里面有kick start,kick start可以直接去调用ostree直接加载这个操作系统,完成操作系统的启动。

ostree这种模式对于虚拟机来说,是不能完全支持的,所以还有另外一条线,对于虚拟化的技术栈上面我们会有packer builder,通过这个builder将ostree构建成一个一个的存在glance的一个一个操作系统镜像,openstack在启动虚拟机的时候会去读取这些镜像,然后将虚拟机启动起来。
所以以用户最后在使用的时候,无论是裸金属,还是虚拟机,它的感受是一样的。

三、节点资源管理

状态汇报

kubelet 周期性地向 API Server 进行汇报,并更新节点的相关健康和资源使用信息。

  • 节点基础信息,包括IP 地址、操作系统、内核、运行时、kubelet、kube-proxy 版本信息。
  • 节点资源信息包括 CPU、内存、存储等信息,以及这些资源中可以分配给容器使用的部分。
  • 调度器在为Pod 选择节点时会将机器的状态信息作为依据。
    image.png
  • 早期k8s 的状态上报直接更新 node 对象,而上报的信息包含状态信息和资源信息,因此需要传输的数据包较大,给 API Server 和 etcd 造成较大的压力。
    后引入 lease 对象用来保存健康信息,在默认 40s 的 nodeLeaseDurationSeconds 周期内,若 Lease 对象没有被更新,则对应节点可以被判定为不健康。
    image.png

资源预留

计算节点在保证容器有足够资源运行的同时,也得保证支撑节点正常运行的进程有足够的资源,比如 systemd、journald、sshd、dockerd、kubelet 等。

为了使这些服务能够正常运行,需要为其预留足够的资源,kubelet 通过众多启动参数为系统预留 CPU、内存、PID等资源,比如 SystemReserved、KubeReserved等。

kubelet 会获取当前节点的资源容量(Capacity)并计算资源可分配额(Allocatable):
image.png

  • CPU 是从 /proc/cpuinfo 文件中获取CPU核数。
  • memory 是从 /proc/memoryinfo 中获取的节点内存大小。
  • ephemeral-storage 是指节点根分区的大小。

Allocatable 是用户 Pod 可用的资源,是资源容量减去分配给系统的资源的剩余部分。
image.png

驱逐管理

kubelet 会在系统资源不够时中止一些容器进程,以空出系统资源,保证节点的稳定性。

kubelet 的驱逐只停止 Pod 所有容器进程,并不会直接删除 Pod。

Pod 的 status.phase 会被标记为 Failed, status.reason 会被设置为 Evicted,status.message 则会记录被驱逐的原因。

kubelet 依赖内嵌的开源软件 cAdvisor 周期性检查节点资源使用情况,当不可用资源(内存,磁盘等)紧张时,将按照一定的驱逐策略进行驱逐。

驱逐策略
kubelet 获取节点的可用额信息后,会结合节点的容量信息来判断当前节点运行的 Pod 是否满足驱逐条件,驱逐条件可以是绝对值或百分比,当监控资源的可使用额少于设定的驱逐条件时,就会发起驱逐。
为了防止小资源被多次回收,可以通过参数 evictionMinimumReclaim 设置每次回收资源的最小值。
驱逐又分为软驱逐和硬驱逐:

  • evictionSoft(软驱逐):当资源达到软驱逐的阈值时,并不会立即驱逐,而是会等待一个宽限期,宽限期过后资源还是不足,执行驱逐。
  • evictionHard(硬驱逐):立即驱逐,终止容器释放资源。

基于内存压力的驱逐

memory.available 表示当前系统的可用内存情况,kubelet 默认设置了 memory.available<100Mi 的硬驱逐条件。

当发生驱逐时,kubelet 会将节点的 MemoryPressure 状态设置为 True, 调度器会阻止 BestEffort Pod 调度到内存承压的节点。

当内存不足时,kubelet 将按照以下策略按顺序驱逐 Pod:

  1. 判断 Pod 所有容器的内存使用量总和是否超出了请求的内存量,超出请求资源的 Pod 会成为备选目标。
  2. 调度优先级低的 Pod 优先驱逐。
  3. 计算 Pod 所有容器的内存使用量和 Pod 请求的内存量的差值,差值越小,越不容易被驱逐。

基于磁盘压力的驱逐

以下任何一项满足驱逐条件时,它会将节点的 DiskPressure 状态设置为 True, 调度器不会再调度任何 Pod 到该节点上。

  • nodefs.available
  • nodefs.inodesFree
  • imagefs.available
  • imagefs.inodesFree

有容器运行时分区的驱逐行为:

  • nodefs 达到驱逐阈值,那么 kubelet 删除已经退出的容器。
  • Imagefs 达到驱逐阈值,那么 kubelet 删除所有未使用的镜像。

无容器运行时分区驱逐行为:

  • kubelet 同时删除未运行的容器和未使用的镜像。

回收已经退出的容器和未使用的镜像后,如果节点依然满足驱逐条件,kubelet 就会开始驱逐正在运行的 Pod, 进一步释放磁盘空间。

kubelet 按照以下驱逐顺序:

  1. 判断 Pod 所有磁盘的内存使用量总和是否超出了请求的内存量,超出请求资源的 Pod 会成为备选目标。
  2. 调度优先级低的 Pod 优先驱逐。
  3. 计算 Pod 所有容器的磁盘使用量和 Pod 请求的内存量的差值,差值越小,越不容易被驱逐。

容器和系统资源配置

通过设置 Pod 的 request 和 limit 参数,分为三种不同的 QoS Class: BestEffort, Burstable, Guaranteed。
这三个参数会计算出 Pod 所需的资源限制,然后设置容器和Pod 对应的 Cgroup。
关于Cgroup 与 容器的关系可以看我之前写的这篇文章:
Docker与Linux之间的关系——Namespace,Cgroups, 网络通信总结

CPU Cgroup 配置
image.png
内存 Cgroup 配置
image.png
OOM Killer 行为

  • 系统的 OOM Killer 可能会采取 OOM 的方式来中止某些容器的进程,进行必要的内存回收操作。
  • 而系统根据进程的 oom_score 来进行优先级排序,选择待终止的进程,且进程的 oom_score 越高,越容易终止。
  • 进程的 oom_score 是根据当前进程使用的内存占节点总内存的比例值乘以 10, 再加上 oom_score_adj 综合得到的。
  • 而容器进程的 oom_score_adj 正是 kubelet 根据 memory.request 进行设置的。
    image.png

四、节点异常检测

异常分类与上报

当k8s 节点发生故障时,仅依靠 k8s 本身的服务组件是无法发现的,比如:

  • 基础架构守护程序问题:NTP 服务关闭
  • 硬件问题:CPU,内存或磁盘损坏
  • 内核问题:内核死锁,文件系统损坏
  • 容器运行时问题:运行时守护程序无响应

为了解决这个问题,社区引入了守护进程 node-problem-detector,从各个守护进程收集节点问题,并且上报给上游服务组件。
k8s 中的故障主要分为三大类,系统故障,用户自定义监控故障,健康检查故障:
image.png
当节点发生故障时,node-problem-detector 会通过设置 NodeCondition 或者创建 Event 对象来汇报问题。

  • NodeCondition: 针对永久性故障,会通过设置 NodeCondition 来改变节点状态。
  • Event: 临时故障通过Event来提醒相关对象,比如通知当前节点运行的所有Pod。

但是NPD 只修改 node condition 对象,并不会对节点状态和调度产生影响,也就是说当节点发生故障时,NPD 只负责上报,不负责处理。
要想驱逐异常节点上的Pod 以及阻止 Pod 调度到故障节点,需要自定义控制器,监听 NPD 汇报的 condition, taint node 信息,进行处理。

常用节点问题排查手段

当节点或者Pod 中的容器异常时,可以通过下边的方法进行排查。

  • ssh 到内网节点:创建一个支持 ssh 的 Pod, 并通过负载均衡器转发 ssh 请求。
  • 查看日志:
    • 针对使用 system 拉起的服务
      image.png
    • 对于标准的容器日志
      image.png
    • 如果容器日志被 shell 转储到文件,则需通过 exec
      image.png

节点扩展资源

除了像 CPU,内存这种必备的节点资源被 k8s 内置监控外,用户也可以自定义扩展资源,并且在像使用内置监控资源一样在定义 Pod 的时候进行限制,需要注意的是自定义扩展资源无法使用 kubernetes.io 作为资源域名。
常见的扩展资源包括:

  • 节点级扩展资源:节点级资源绑定到节点
  • 设备插件管理的资源:发布在各节点上由设备插件所管理的资源,如 GPU,智能网卡等。

定义扩展资源

  • 集群管理员可以向 API 服务器提交 PATCH HTTP 请求,在集群中节点的 status.capacity 中配置扩展资源可用数量,完成此操作后,节点的 status.capacity 字段中将包含新资源。
    image.png
  • k8s 会异步的对 status.allocatable 字段执行自动更新操作,使之包含新资源。
  • 调度器在评估 Pod 是否合适在某节点上执行时会使用节点的 status.allocatable 值,在更新节点容量使之包含新资源之后和请求该资源的第一个 Pod 被调度到该节点之间,可能会有短暂的延迟。

在Pod 中使用扩展资源
image.png

  • 可选择由默认调度器管理资源,默认调度器像管理其他资源一样管理扩展资源,但是 Request 和 Limit 必须一致,因为 k8s 无法确保扩展资源的超售。
  • 更常见的场景是,由调度器扩展程序管理,修改调度器策略配置 ignoredByScheduler 字段,让调度器不检查自定义资源。
    image.png

七、构建和管理高可用集群

k8s 高可用层级架构

image.png
k8s 集群在生产化落地有许多需要注意的地方,第一步就是如何搭建高可用的集群,搭建高可用集群需要考虑许多方面的内容,比如说基础架构层的容灾与安全,如何高效的与企业自身的公共服务集成,如何搭建高可用的数据层和控制层,应用开发者与集群管理员需要关注的方向点有哪些等等。
高可用的数据中心

  • 数据中心要实现多地部署,且每个数据中心需要划分成具有独立供电、制冷、网络设备的高可用区
  • 每个可用区管理独立的硬件资产,包括机架、计算节点、存储、负载均衡器、防火墙等硬件设备

Node 的生命周期管理

  • 运营 k8s 集群,不仅仅是集群搭建那么简单,运营需要对集群中所有节点的完整申明周期负责,包括集群搭建、集群扩容/缩容、集群销毁(慎用)等。
  • 无论是集群搭建还是扩容,核心是 Node 的生命周期管理:
    • Onboard: 上线状态包括物理资产上架,操作系统安装,网络配置,k8s 组件安装, 创建 Node 对象等
    • 故障处理:包括临时故障如何处理?是否重启?永久故障如何处理?是否机器下架?
    • Offboard: 节点下线状态包括删除 Node 对象,物理资产下架,送修/报废等

主机管理

  • 选定哪个版本的系统内核、哪个发行版、安装哪些工具集、主机网络如何规划等。
  • 日常的主机镜像升级更新也可能是造成服务不可用的因素之一,主机镜像更新可以通过 A/B 系统OTA 升级方式进行。
    • 分别使用 A、B 两个存储空间,共享一份用户数据。在升级过程中,OTA 更新即往其中一个存储空间写入升级包,同时保证了另一个系统可以正常运行,而不会打断用户。如果 OTA 失败,那么设备会启动到 OTA 之前的磁盘分区,并且仍然可以使用。

生产化集群管理

  • 如何设定单个集群规模
    社区声明单一集群可支持5000节点,在如此规模的集群中,大规模部署应用是有诸多挑战的。应该更多还是更少?如何权衡?
  • 如何根据地域划分集群
    是将不容地域的计算节点划分到同一集群?还是将同一地域的节点划分到同一集群?
  • 如何规划集群的网络
    企业办公环境、测试环境、预生产环境和生产环境应如何进行网络分离?
    不同租户之间应如何进行网络隔离?
  • 如何自动化搭建集群
    如何自动化搭建和升级集群,包括自动化部署控制平面和数据平面的核心组件。
    如何与企业的公共服务集群。

企业公共服务
image.png
控制平面的高可用保证
image.png
控制平面高可用架构:
image.png

kubespray 搭建高可用集群

目前集群的搭建主要由四种方式:
image.png
kubespray 就是利用 kubeadmin 和 ansible 将对应的搭建命令给自动化了,缺点就是由于网络等问题会导致中间经常出现部署超时等错误,需要不断的重试。
搭建步骤可以参考这篇文章:
kubespray部署高可用K8s集群
使用 Kubespray 安装 Kubernetes

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐