Featured image of post k8s应用架构白话解说

k8s应用架构白话解说

简单描述k8s架构中最高频的一些用户侧内容

简述

目的是写一篇关于k8s的入门文章。

当前的构思是先写一篇关于最常用逻辑组件的介绍,尽量通俗易懂一些。
以后再增加或修改其它内容。

本篇主要是关于一些 核心的名词解释。

这里有一张我画的 k8s 核心架构图, 只包含最常见的应用类的组件。
不包括物理组件,第三方组件,和自身的一些不常用的细节或控制器。

k8s-arch-user架构图

基础名词

containers 容器

简单理解就是一个被隔离的应用程序。

推荐是一个容器运行一个不可再拆分的进程。
但也不是强制要求,也可以走胖容器的路线,一个容器内跑多个进程也可以。

如果是传统架构迁移过来时,或者多程序之间关联极为耦合,可以临时用胖容器过度一下,后期最好还是单一进程合适。

为什么推荐单一进程呢?目的是增加资源利用率和细化控制。

设想以下场景

  1. 如果一个容器内有3个进程,1忙2闲。现在因为这个忙的进程资源使用率很高,需要将容器增加2份。就导致2个闲的进程也跟着被扩了2份,不合理。
  2. 容器因为主进程的需要被授予了A权限,但容器内的其它进程并不需要这个权限。
  3. 容器内多个进程都向标准输出吐了日志,如何区分是谁的?

POD

理解为一个最小调度单元。

例子1: A 程序向某个 unix socket 文件发送数据,B程序读取此 socket 文件。这就决定了2个进程必须在同一个机器上运行,且必须同等份数的扩容、缩减,以及同时启停等。

所以需要视为一个整体来进行调度。

例子2:多个程序都是使用的 127.0.0.1 来进行相互通信的, 如果调度到不同节点则无法进行通信,即便在同一个节点还不能隔离它们的网络。

所以它们之间需要共享网络栈

services 服务

当一个类型的应用程序启动了多份之后, 如何使用一个入口访问到全部服务呢?

传统方法就是配置一个负载均衡,如 nginx, HAproxy, lvs 等。

k8s体系内也提供了的负载均衡能力,早期 Userspace 的 kube-proxy + iptable 模式,中期的 iptables 模式,目前主流的 lvs 模式,再如今推荐的 ebpf 模式, 都是来解决这个问题的。
达到一个效果, 客户端访问集群ip和端口,将负载分配到后端的多个pod端口去。

通常使用服务名字来解析出集群IP来使用。

Ingress

Ingress 的主要用途是处理南北向流量,就是外部进入集群时的请求,因为外部一般来说是访问不到集群IP地址的,所以需要单独的一个服务来承载入口。并附带了一个高级功能,如 通用负载算法,ssl证书, 前缀或正则路由等。

说明
Ingress 本身是对负载均衡功能的一个抽象,理解为一套标准。
Ingress控制器, 则是对这套标准的一个具体实现,也就是具体承载入口流量的那个应用程序。

常用的控制器
nginx 生态好, 性能好,文档丰富, 主流, 配合lua插件;
Traefik 非常适合 k8s 环境,自动化程度很高,配合go插件,个人主推;
envoy 据说是性能最好的,配合lua插件, 我使用过,没有自己搭过;
APISix 新出不久,本质还是 nginx+lua, 只是更贴合k8s环境一些。

我目前是主要生产环境用 nginx, 非核心环境用 Traefik, 预研和学习和下一步 envoy。

存储相关

configmap

理解为将 应用程序的配置文件给 apiserver 进行集中存储,然后挂载到 容器内进行使用

注意: 修改 cm 后,需要10-20s 容器内的配置文件才会更新

secret

一般来说就是存放 帐号密码,证书,认证信息等关键私密信息的。
和 configmap 本质没有区别, 单独取个名字 估计只是让用户好区分而已。

emptyDir

临时目录, 用于同一个 pod 内的多个容器之间共享文件系统目录的。
非持久化,pod 关闭后数据清除。

有个特性: 可以使用内存文件系统

HostPath

挂载宿主机上的文件系统目录到容器内使用的方式。

pvc

对存储资源的一种抽象;

存储管理员 将实际的多个存储各取一个名字, 然后标识这个存储的元数据(空间大小,读写特性等),每个存储就是一个 pv 了;

业务管理员在创建 pod 时, 写一个清单,表明我这个程序所需要的存储元数据, 这个需求清单即 PVC;
pvc 创建后会自动绑定空闲的 pv, 绑定成功后, pod 既可以正常使用存储了。

问题: 多个 pod 使用同一个 pvc(需求清单) 时如何绑定 pv 呢?
实际上每个 pod 都会拿着在这个 pvc(需求清单) 去新建一个 pvc 实例, 这个 pvc 实例才是真正绑定的存储。

StorageClass

原来的 pvc 模式需要存储管理员提前把 pv 都创建好, 才能在使用 pvc 时自动绑定上;
可是业务系统需要多少个 pv, 需要哪些大小和类型的 pv 都不是一开始就能定下来的。

所以需要有一种机制按需创建 pv,这也就是 StorageClass 的作用了;

同 Ingress 和 Ingress控制器类似,
StorageClass 也只是一套标准,具体如何创建 pv, 还得由具体的控制器去实现, 这里的控制器就叫做 provisioner 供应商,这个 provisioner 也是一个单独的 pod。

例如创建 ceph rbd, cephFS 的 ceph provisioner, 创建 nfs 空间的 provisioner,创建 oss 的provisioner,创建 gfs 的 provisioner,基本上每一个不同类型的物理存储类型,都需要一个 provisioner。

发布相关

ReplicaSet

ReplicaSet 这个单词我理解为 版本。

比如 目前运行的 10 个相同的 pod 就归为一个版本 ReplicaSet,
但对涉及这10个 pod 的任何变更都会产生一个新的版本。

例如增加了 2 个实例,或者更换了镜像版本, 则新增一个版本。
如果需要回退,则切换版本即可。

滚动升级的原理也是靠 新旧2个版本依次替换实现的。

一般来说除了回滚时,用户也不需要管它。

deployment

主要针对无状态服务,即 pod 自身不存储下次启动所需要的数据这类程序,最常用的类型。

可以随时扩容缩容的pod,例如 nginx, 就只需要 程序二进制文件本身 + 存储在 configmap 内的配置文件,这就是典型的无状态服务。

StatefulSet

针对有状态服务,什么是有状态呢?

  1. 启动后外部传入进来配置或数据,并存储到了 Pod 本地, 下次启动还需要它。
  2. 这个服务的 ip和端口外部是固定的配置,服务迁移或重建后 ip 不能变。

非常典型的如 mysql, 不可能每次启动都是空库吧,不可能每次启动都需要客户端更新IP吧,扩容节点后新节点没数据怎么办,有文件锁不能使用共享文件系统只能单点使用这些问题。

思考: mysql 能否做成无状态服务
当然也可以的,但场景非常受限制

无状态读: N个只读数据库, 共享一份只读文件系统,可以随意扩缩容,客户端使用服务名轮询负载各pod, 无数据冲突或不一致性问题。

无状态写:N个只写数据库, 各自独立使用一份块存储, 相同表结构; 只写不读的场景就无数据冲突,定期将N份数据库的内容单独查出来汇总。

有读有写:固定只有一个 pod 的 deployment

DaemonSet

每个 Node 节点都 有且只有 一个的 pod;
一般为监控, 日志采集, 网络或存储插件等特殊的容器,

HPA

水平扩缩容
一般是根据实例的CPU或内存等资源使用量来决定扩容或缩容;
也可以定时触发,或根据其它自定义条件来触发。

VPA

纵向扩缩容
其实也就是调整单个 pod 的资源限制大小

PDB

实例数量的保证,避免更新升级或者迁移时一下关停了太多的实例

保障某服务, 最多不可用 或 最少必须可用 的 pod 数量

scheduler 调度相关

即决定这个 pod 应该去哪个节点上运行

nodeName

最高优先级的策略,点名这个 pod 去哪个节点运行

nodeSelector

选择去哪些节点上运行,可以使用标签选择器 或 名字选择器等多种选择方式。

Taints

污点调度

即给一些 Node 做个标记, 默认不运行其它 pod 过来运行, 除非这个 pod 显式声明自己可以接受这个标记。

nodeAffinity

节点 亲和性, 优先去这些节点上运行

podAffinity

POD 亲和性, 优先去运行有这些 pod 的 Node 上运行。

Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
最后更新于 2023-08-10 00:00 UTC