当前位置: 首页 > article >正文

k8s 之 StatefulSet

深入理解StatefulSet(一):拓扑状态

k8s有状态与无状态的区别

无状态服务:deployment

Deployment被设计用来管理无状态服务的pod,每个pod完全一致.什么意思呢?

无状态服务内的多个Pod创建的顺序是没有顺序的. 无状态服务内的多个Pod的名称是随机的.pod被重新启动调度后,它的名称与IP都会发生变化. 无状态服务内的多个Pod背后是共享存储的.

有状态服务:StatefulSet

Deployment组件是为无状态服务而设计的,其中的Pod名称,主机名,存储都是随机,不稳定的,并且Pod的创建与销毁也是无序的.这个设计决定了无状态服务并 不适合数据库领域的应用.

而Stateful管理有状态的应用,它的Pod有如下特征:

唯一性: 每个Pod会被分配一个唯一序号. 顺序性: Pod启动,更新,销毁是按顺序进行. 稳定的网络标识: Pod主机名,DNS地址不会随着Pod被重新调度而发生变化. 稳定的持久化存储: Pod被重新调度后,仍然能挂载原有的PV,从而保证了数据的完整性和一致性.

总结: 本文主要介绍了无状态和有状态服务在K8S中的典型应用场景.

通过对Deployment部署无状态服务所遇到问题的分析,引出了Stateful新的部署组件.它是通过支持Pod一些特性(e.g. 名称唯一性,稳定的网络标识, 稳定的持久化存储等)来实现在K8S中部署运维有状态服务.

牢记: Stateful有状态服务,每个Pod有独立的PVC/PV存储组件

StatefulSet 的工作原理之前,必须先为你讲解一个 Kubernetes 项目中非常实用的概念:Headless Service。
Service 是 Kubernetes 项目中用来将 一组 Pod 暴露给外界访问的一种机制。
比如,一个 Deployment 有 3 个 Pod,那么我就可以 定义一个 Service。然后,用户只要能访问到这个 Service,它就能访问到某个具体的 Pod。
那么,这个 Service 又是如何被访问的呢?
第一种方式:通过 VIP(Virtual IP,即:虚拟 IP),如:当访问 10.0.23.1 这个 Service 的 IP 地址时(VIP),它会把请求转发到该 Service 所代理的某一个 Pod 上。
第二种方式: 以service 的 DNS 方式,但可分为2种处理方法:
(1)是 Normal Service。这种情况下,你访问“my-svc.mynamespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致了。
(2)正是 Headless Service,当你访问“my-svc.mynamespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。

区别在于,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录 的方式解析出被代理 Pod 的 IP 地址。

标准的 Headless Service 对应的 YAML 文件

apiVersion: v1
Kind: Service
metadata:
  name: nginx
  lables:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx  

可以观察看到 ClusterIP 字段的值为 None,也就是所谓的 “无头服务” 没有VIP作为头。
当这个 Service 被创建出来后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的Pod。

是如何给一组 Pod 暴露端口给外界访问的呢?在 YAML 你是观察到有一个 Selector 字段,他就是使用 Label Selector 机制选择出所有带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来,这样可以做到负载均衡Pod的访问了。

StatefulSet 又是如何使用这个 DNS 记录来维持 Pod 的拓扑状态的呢?
示例编写一个 StatefulSet 的 YAML 文件,如下所示:

apiVersion: apps/v1 
kind: StatefulSet 
metadata:
  name: web
spec:
  serviceName: "nginx" 
  replicas: 2 
  selector:
    matchLabels:
      app: nginx 
  template:
    metadata:
      labels:
      app: nginx
    spec:
      containers:
      - name :nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web

该 YAML 文件和平时我们写的 Deployment 都差不多,唯一区别就是多一个 ServiceName=nginx 字段。
这个字段的作用,告诉 StatefulSet 控制器,在执行控制循环(Control Loop)时,请使用 nginx 这个 Headless Service 来保证 Pod 的 “可解析身份”
创建完后可以快速实时查看stateful创建的状态:kubectl get pods -w -l app=nginx

试用 nslookup 命令,解析一下 Pod 对应的 Headless Service:
nslookup web-0.nginx

总结:
StatefulSet 控制器作用:使用Pod模版创建 Pod时对他们进行编号,并且按照顺序逐一完成创建工作。StatefulSet 控制循环 发现 Pod的 “实际状态” 与 “期望状态” 不一致,则需要新建或删除 Pod 进行 “调谐” 时会严格按照这些Pod编号的顺序逐一完成这波操作。

深入理解StatefulSet(二):存储状态

在前面的 Stateful 讲解了它是如何保证应用实例的拓扑状态,在 Pod 删除和创建的过程中保持稳定。
今天讲解 StatefulSet 对存储状态的管理机制。
在 Pod 中有个 Volume (卷),想要容器挂载到外面地方三存储或本地机器,只需要在Pod里面加上spec.volumes 字段即可,如 Volume 类型 hostPath就挂在本地机器。

有一个问题,对于开发人员来说,如果你并不知道有哪些 Volume 类型可以用,要怎么办呢?作为开发人员对持久存储项目如Ceph、GlusterFS 都一窍不通,让开发人员来写已经超出了知识储备了

后来 kubernetes 项目引入了一组叫 “Persistent Volume Claim” 和 “Persistent Volume” 的 API 对象,降低用户声明和使用持久 Volume 的门槛。

假设我现在是开发人员,现在想要一个 Volume ,需要两步即可。
第一步:申请。定义一个PVC声明想要的 Volume的属性:

apiVersion: v1
Kind: PersistentVolumeClaim
metadata:
  name: pv-claim
spec:
  accessModes:
  - ReadWirteOnce
  resources:
    requests:
      storage: 1Gi

开发人员定义了一个 PVC,描述只需要的属性定义,如:storage: 1Gi 表示想要 Volume 大小至少为 1GB;accessModes: ReadWirteOnce 表示这个 Volume的挂载方式是可读写,且只能被挂载在一个节点而非多个节点共享。

第二步:在应用的 Pod 中,声明刚才你定义的 PVC 即可使用。

apiVersion: v1 
kind: Pod
metadata:
  name: pv-pod 
spec:
  containers:
  - name: pv-container 
    image: nginx
    ports:
    - containerPort: 80
      name: "http-server"
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: pv-storeage
volumes:
  - name: pv-storage
    persistentVolumeClaim:
      claimName: pv-claim

以上就是申请好一个 1GB 大小的 Volume了,不需要关系 Volume 的类型,然后创建这个 PVC 对象,Kubernetes 就会自动为它绑定一个符合条件的 Volume。

可是,这些符合条件的 Volume 又是从哪里来的呢?这个需要运维人员去维护 PV 对象了。

Kubernetes 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。开发者 只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现,即: PV。

而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。回顾:

apiVersion: apps/v1 
kind: StatefulSet 
metadata:
  name: web
spec:
  serviceName: "nginx" 
  replicas: 2 
  selector:
    matchLabels:
      app: nginx 
  template:
    metadata:
      labels:
      app: nginx
    spec:
      containers:
      - name :nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web
      volumeMounts:
      - name: www
        mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
    name: www
  spec:
    accessModel:
    - ReadWriteOnce
    resources:
      requests:
        storage: 1Gi

volumeClaimTemplates 字段,从名字就可以 看出来,它跟 Deployment 里 Pod 模板(PodTemplate)的作用类似。
也就是说,凡是被这 个 StatefulSet 管理的 Pod,都会声明一个对应的 PVC;而这个 PVC 的定义,就来自于 volumeClaimTemplates 这个模板字段。更重要的是,这个 PVC 的名字,会被分配一个与这个 Pod 完全一致的编号。

这个自动创建的 PVC,与 PV 绑定成功后,就会进入 Bound 状态,这就意味着这个 Pod 可以 挂载并使用这个 PV 了。

如果你使用 kubectl delete 命令删除这两个 Pod,这些 Volume 里的文件会不会丢失呢?
前面介绍过,在被删除之后这两个 Pod 会被按照编号顺序被重启创建出来。如果你在创建新的容器通过本地访问去访问,会发现请求依然会返回,说明原先与名叫web-0的Pod绑定PV,Pod被重新创建后,依然同新的名字web-0的Pod绑定了在一起。

这是怎么做到的呢?
分析:当你把一个 Pod,比如 web-0,删除之后,这个 Pod 对应的 PVC 和 PV,并不会被删除,而这个 Volume 里已经写入的数据,也依然会保存在远程存储服务里(比如,我们在这个 例子里用到的 Ceph 服务器)。

当删除后,StatefulSet 控制器发现,一个名叫 web-0 的 Pod 消失了。所以,控制器就会重新创建 一个新的、名字还是叫作 web-0 的 Pod 来,“纠正”这个不一致的情况。

需要注意的是,在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字,还是叫作:wwwweb-0。这个 PVC 的定义,还是来自于 PVC 模板(volumeClaimTemplates),这是 StatefulSet 创建 Pod 的标准流程。

通过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。

深入理解StatefulSet(三):有状态应用实践

官网:案例
部署一个 MySQL 集群,如何使用 StatefulSet 将它的集群搭建过程“容器化”

首先,用自然语言来描述一下我们想要部署的“有状态应用”。

  1. 是一个“主从复制”(Maser-Slave Replication)的 MySQL 集群;
  2. 有 1 个主节点(Master);
  3. 有多个从节点(Slave);
  4. 从节点需要能水平扩展;
  5. 所有的写操作,只能在主节点上执行;
  6. 读操作可以在所有节点上执行。

在常规环境里,部署这样一个主从模式的 MySQL 集群的主要难点在于:如何让从节点能够拥有 主节点的数据,即:如何配置主(Master)从(Slave)节点的复制与同步。

第一步:通过 XtraBackup 将 Master 节点的数据备份到指定目录。
第二步:配置 Slave 节点。Slave 节点在第一次启动前,需要先把 Master 节点的备份数据,连 同备份信息文件,一起拷贝到自己的数据目录(/var/lib/mysql)下。然后,我们执行这样一句 SQL:
第三步:启动 Slave 节点。在这一步,我们需要执行这样一句 SQL:
第四步:在这个集群中添加更多的 Slave 节点。

将部署 MySQL 集群的流程迁移到 Kubernetes 项目上,需要 能够“容器化”地解决下面的“三座大山”:

  1. Master 节点和 Slave 节点需要有不同的配置文件(即:不同的 my.cnf)

    1. Master 节点和 Salve 节点需要能够传输备份信息文件;
  2. 在 Slave 节点第一次启动之前,需要执行一些初始化 SQL 操作;

“第一座大山:Master 节点和 Slave 节点需要有不同的配置文件”,很容易处理:我们只需要给主从节点分别准备两份不同的 MySQL 配置文件,然后根据 Pod 的序号(Index)挂载进去即可。
配置文件信息保存在 ConfigMap 里供 Pod 使 用。它的定义如下所示:

apiVersion: v1 
kind: ConfigMap 
metadata:
  name: mysql 
  labels:
    app: mysql
data:
  master.cnf: |
    [mysql]
    log-bin
slave.cnf: |
  [mysql]
  super-read-only
  • master.cnf 开启了 log-bin,即:使用二进制日志文件的方式进行主从复制,这是一个标准 的设置。
  • slave.cnf 的开启了 super-read-only,代表的是从节点会拒绝除了主节点的数据同步操作之 外的所有写操作,即:它对用户是只读的。

接下来,我们需要创建两个 Service 来供 StatefulSet 以及用户使用。这两个 Service 的定义如 下所示:

```yaml
# 为 StatefulSet 成员提供稳定的 DNS 表项的无头服务(Headless Service)
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None # Headless Service,作用为 Pod 分配 DNS 记录来固定它的拓扑状态
  selector:
    app: mysql
---
# 用于连接到任一 MySQL 实例执行读操作的客户端服务
# 对于写操作,你必须连接到主服务器:mysql-0.mysql
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
    readonly: "true"
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

两个 Service 都代理了所有携带 app=mysql 标签的 Pod,也就是所有的 MySQL Pod。端口映射都是用 Service 的 3306 端口对应 Pod 的 3306 端口。

第二座大山:Master 节点和 Salve 节点需要能够传输备份文件”的 问题。


http://www.kler.cn/a/428531.html

相关文章:

  • Tensor 基本操作1 unsqueeze, squeeze, softmax | PyTorch 深度学习实战
  • Git实用指南:忽略文件、命令别名、版本控制、撤销修改与标签管理
  • 国产编辑器EverEdit - 快捷目录
  • (3)STM32 USB设备开发-USB存储设备
  • 图解Git——分布式Git《Pro Git》
  • c++学习第七天
  • 力扣100题--移动零
  • C++打造局域网聊天室第四课: 动态启用或禁用窗口及MFC消息映射机制
  • QT 中 QString 转换为 Unicode 和 ASCII 的方法
  • 【JavaEE 初阶】⽹络编程套接字
  • 【Linux】Git
  • 运输层4——TCP格式(重点!)
  • 24/12/8 算法笔记<强化学习> AC:actor-critic
  • 安装部署PowerDNS--实现内网DNS解析
  • AI视频玩法:动物融合技术解析
  • 智驾端到端时代,何以「奔驰」?
  • 图神经网络代码学习—基本使用与分类任务
  • JWT 原理与使用
  • 高阶数据结构--B树B+树实现原理B树模拟实现--Java
  • Arthas采集火焰图
  • esp-idf基于vscode插件开发环境搭建
  • 【数电】常见时序逻辑电路设计和分析
  • 纯虚函数和抽象类
  • 使用Jackson忽略特定字段的序列化
  • 【Windows11系统局域网共享文件数据】
  • idea中手动停止后selenium UI自动化打开的浏览器及chromedriver进程就会一直在后台中,使用钩子程序保证在程序结束时一定会进行退出。