k8s编排之StatefulSet知识点详解二

目录
  • StatefulSet 对存储状态的管理机制
    • 第一步:定义一个 PVC,声明想要的 Volume 的属性
    • 第二步:在应用的 Pod 中,声明使用这个 PVC
  • 常见的 PV 对象的 YAML 文件

StatefulSet 对存储状态的管理机制

这个机制,主要使用的是一个叫作 Persistent Volume Claim 的功能。

要在一个 Pod 里声明 Volume,只要在 Pod 里加上 spec.volumes 字段即可。然后,你就可以在这个字段里定义一个具体类型的 Volume 了,比如:hostPath。

可是,你有没有想过这样一个场景:如果你并不知道有哪些 Volume 类型可以用,要怎么办呢?

更具体地说,作为一个应用开发者,我可能对持久化存储项目(比如 Ceph、GlusterFS 等)一窍不通,也不知道公司的 Kubernetes 集群里到底是怎么搭建出来的,我也自然不会编写它们对应的 Volume 定义文件。

这些关于 Volume 的管理和远程持久化存储的知识,不仅超越了开发者的知识储备,还会有暴露公司基础设施秘密的风险。

比如,下面这个例子,就是一个声明了 Ceph RBD 类型 Volume 的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: rbd
spec:
  containers:
    - image: kubernetes/pause
      name: rbd-rw
      volumeMounts:
      - name: rbdpd
        mountPath: /mnt/rbd
  volumes:
    - name: rbdpd
      rbd:
        monitors:
        - '10.16.154.78:6789'
        - '10.16.154.82:6789'
        - '10.16.154.83:6789'
        pool: kube
        image: foo
        fsType: ext4
        readOnly: true
        user: admin
        keyring: /etc/ceph/keyring
        imageformat: "2"
        imagefeatures: "layering"

其一,如果不懂得 Ceph RBD 的使用方法,那么这个 Pod 里 Volumes 字段,你十有八九也完全看不懂。其二,这个 Ceph RBD 对应的存储服务器的地址、用户名、授权文件的位置,也都被轻易地暴露给了全公司的所有开发人员,这是一个典型的信息被“过度暴露”的例子。

这也是为什么,在后来的演化中,Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象,大大降低了用户声明和使用持久化 Volume 的门槛。

举个例子,有了 PVC 之后,一个开发人员想要使用一个 Volume,只需要简单的两步即可。

第一步:定义一个 PVC,声明想要的 Volume 的属性

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

可以看到,在这个 PVC 对象里,不需要任何关于 Volume 细节的字段,只有描述性的属性和定义。比如,storage: 1Gi,表示我想要的 Volume 大小至少是 1 GiB;accessModes: ReadWriteOnce,表示这个 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-storage
  volumes:
    - name: pv-storage
      persistentVolumeClaim:
        claimName: pv-claim

可以看到,在这个 Pod 的 Volumes 定义中,我们只需要声明它的类型是 persistentVolumeClaim,然后指定 PVC 的名字,而完全不必关心 Volume 本身的定义。

这时候,只要我们创建这个 PVC 对象,Kubernetes 就会自动为它绑定一个符合条件的 Volume。可是,这些符合条件的 Volume 又是从哪里来的呢?

答案是,它们来自于由运维人员维护的 PV(Persistent Volume)对象。

常见的 PV 对象的 YAML 文件

kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv-volume
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  rbd:
    monitors:
    - '10.16.154.78:6789'
    - '10.16.154.82:6789'
    - '10.16.154.83:6789'
    pool: kube
    image: foo
    fsType: ext4
    readOnly: true
    user: admin
    keyring: /etc/ceph/keyring
    imageformat: "2"
    imagefeatures: "layering"

可以看到,这个 PV 对象的 spec.rbd 字段,正是我们前面介绍过的 Ceph RBD Volume 的详细定义。而且,它还声明了这个 PV 的容量是 10 GiB。这样,Kubernetes 就会为我们刚刚创建的 PVC 对象绑定这个 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:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

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

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

如果你还是不太理解 PVC 的话,可以先记住这样一个结论:PVC 其实就是一种特殊的 Volume。只不过一个 PVC 具体是什么类型的 Volume,要在跟某个 PV 绑定之后才知道。关于 PV、PVC 更详细的知识,我会在容器存储部分做进一步解读。

当然,PVC 与 PV 的绑定得以实现的前提是,运维人员已经在系统里创建好了符合条件的 PV(比如,我们在前面用到的 pv-volume);或者,你的 Kubernetes 集群运行在公有云上,这样 Kubernetes 就会通过 Dynamic Provisioning 的方式,自动为你创建与 PVC 匹配的 PV。

所以,我们在使用 kubectl create 创建了 StatefulSet 之后,就会看到 Kubernetes 集群里出现了两个 PVC

可以看到,这些 PVC,都以“<PVC 名字 >-<StatefulSet 名字 >-< 编号 >”的方式命名,并且处于 Bound 状态。

我们前面已经讲到过,这个 StatefulSet 创建出来的所有 Pod,都会声明使用编号的 PVC。比如,在名叫 web-0 的 Pod 的 volumes 字段,它会声明使用名叫 www-web-0 的 PVC,从而挂载到这个 PVC 所绑定的 PV。

所以,我们就可以使用如下所示的指令,在 Pod 的 Volume 目录里写入一个文件,来验证一下上述 Volume 的分配情况

for i in 0 1; do kubectl exec web-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done

如上所示,通过 kubectl exec 指令,我们在每个 Pod 的 Volume 目录里,写入了一个 index.html 文件。这个文件的内容,正是 Pod 的 hostname。比如,我们在 web-0 的 index.html 里写入的内容就是 "hello web-0"。

此时,如果你在这个 Pod 容器里访问“http://localhost”,你实际访问到的就是 Pod 里 Nginx 服务器进程,而它会为你返回 /usr/share/nginx/html/index.html 里的内容。这个操作的执行方法如下所示:

$ for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
hello web-0
hello web-1

如果你使用 kubectl delete 命令删除这两个 Pod,这些 Volume 里的文件会不会丢失呢?

可以看到,正如我们前面介绍过的,在被删除之后,这两个 Pod 会被按照编号的顺序被重新创建出来。而这时候,如果你在新创建的容器里通过访问“http://localhost”的方式去访问 web-0 里的 Nginx 服务

就会发现,这个请求依然会返回:hello web-0。也就是说,原先与名叫 web-0 的 Pod 绑定的 PV,在这个 Pod 被重新创建之后,依然同新的名叫 web-0 的 Pod 绑定在了一起。对于 Pod web-1 来说,也是完全一样的情况。

这是怎么做到的呢?

其实,我和你分析一下 StatefulSet 控制器恢复这个 Pod 的过程,你就可以很容易理解了。

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

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

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

所以,在这个新的 web-0 Pod 被创建出来之后,Kubernetes 为它查找名叫 www-web-0 的 PVC 时,就会直接找到旧 Pod 遗留下来的同名的 PVC,进而找到跟这个 PVC 绑定在一起的 PV。

这样,新的 Pod 就可以挂载到旧 Pod 对应的那个 Volume,并且获取到保存在 Volume 里的数据。

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

看到这里,你是不是已经大致理解了 StatefulSet 的工作原理呢?现在,我再为你详细梳理一下吧。

首先,StatefulSet 的控制器直接管理的是 Pod。这是因为,StatefulSet 里的不同 Pod 实例,不再像 ReplicaSet 中那样都是完全一样的,而是有了细微区别的。比如,每个 Pod 的 hostname、名字等都是不同的、携带了编号的。而 StatefulSet 区分这些实例的方式,就是通过在 Pod 的名字里加上事先约定好的编号。

其次,Kubernetes 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录。只要 StatefulSet 能够保证这些 Pod 名字里的编号不变,那么 Service 里类似于 web-0.nginx.default.svc.cluster.local 这样的 DNS 记录也就不会变,而这条记录解析出来的 Pod 的 IP 地址,则会随着后端 Pod 的删除和再创建而自动更新。这当然是 Service 机制本身的能力,不需要 StatefulSet 操心。

最后,StatefulSet 还为每一个 Pod 分配并创建一个同样编号的 PVC。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。

在这种情况下,即使 Pod 被删除,它所对应的 PVC 和 PV 依然会保留下来。所以当这个 Pod 被重新创建出来之后,Kubernetes 会为它找到同样编号的 PVC,挂载这个 PVC 对应的 Volume,从而获取到以前保存在 Volume 里的数据。

以上就是k8s编排之StatefulSet知识点详解二的详细内容,更多关于k8s编排StatefulSet的资料请关注我们其它相关文章!

(0)

相关推荐

  • K8S 实用工具之合并多个kubeconfig实现详解

    目录 开篇 解决方案 方案一:KUBECONFIG 环境变量指向多个文件 方案二:flatten 方案三:kubectl 插件 konfig 实用工具:krew 实用工具:konfig 总结 开篇 磨刀不误砍柴工 工欲善其事必先利其器 K8S 集群规模,有的公司倾向于少量大规模 K8S 集群,也有的公司会倾向于大量小规模的 K8S 集群. 如果是第二种情况,是否有一个简单的 kubectl 命令来获取一个 kubeconfig 文件并将其合并到 ~/.kube/config 文件作为一个额外的上

  • k8s实现身份认证策略及过程解析

    目录 身份认证策略 API Server启用的身份认证机制 kubelet启用的身份认证机制 X.509数字证书认证 静态令牌文件 Service Account令牌 OpenID Connect(OIDC)令牌 Webhook令牌认证 身份认证代理 静态令牌认证配置案例 静态令牌认证的基础配置 配置示例 X509 数字证书认证 所有的证书 X509数字证书认证测试 身份认证策略 X.509客户端证书认证 持有者令牌(bearer token) 静态令牌文件(Static Token File)

  • k8s编排之DaemonSet知识点详解

    目录 如何对 StatefulSet 进行“滚动更新”(rolling update)? 下面重点讲解一个\知识点:DaemonSet 列举几个例子: API 对象的定义 如何在指定的 Node 上创建新 Pod 呢? nodeAffinity 含义 如何对 StatefulSet 进行“滚动更新”(rolling update)? 你只要修改 StatefulSet 的 Pod 模板,就会自动触发“滚动更新”: kubectl patch statefulset mysql --type='j

  • k8s编排之Deployment知识点详解

    目录 Pod 复杂的API对象 nginx-deployment Deployment 及类似控制器总结 Deployment 所控制的 ReplicaSet查看 Pod 复杂的API对象 Pod 这个看似复杂的 API 对象,实际上就是对容器的进一步抽象和封装而已. 说得更形象些,“容器”镜像虽然好用,但是容器这样一个“沙盒”的概念,对于描述应用来说,还是太过简单了.这就好比,集装箱固然好用,但是如果它四面都光秃秃的,吊车还怎么把这个集装箱吊起来并摆放好呢? 所以,Pod 对象,其实就是容器的

  • k8s编排之StatefulSet知识点详解一

    目录 正文 StatefulSet 的设计理解 Service 如何被访问 Headless Service 对应的 YAML文件 StatefulSet 的 YAML 文件 解析一下 Pod 对应的 Headless Service 正文 Deployment认为,一个应用的所有 Pod,是完全一样的.所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上.需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod:不需要的时候,Deployment 就可以“杀掉”任意一个 P

  • k8s应用监控探针详解

    目录 应用监控 pod状态转换 pod的启动流程? Pod支持的监测类型(健康探针) 监测机制 配置参数 示例 image pull policy 镜像管理策略 应用监控 参考 https://www.jb51.net/article/241418.htm 在pod之上 添加一个探针, kubelet通过探针去检查应用 pod状态转换 pod的启动流程? schduler环节 先绑定节点 kubelet接管 准备CNI CSI CRI 启动pod中的container 启动探针 存活探针 监测p

  • k8s部署redis集群实现过程实例详解

    目录 写在前面 前置准备 一.nfs安装 二.SC.PV 创建 2.1创建SC 2.2创建PV 三.redis集群搭建 3.1创建headless服务 3.2创建redis对应pod集群 写在前面 一般来说,REDIS部署有三种模式. 单实例模式,一般用于测试环境. 哨兵模式 集群模式 后两者用于生产部署 哨兵模式 在redis3.0以前,要实现集群一般是借助哨兵sentinel工具来监控master节点的状态. 如果master节点异常,则会做主从切换,将某一台slave作为master. 引

  • k8s部署redis集群搭建过程示例详解

    目录 写在前面 一.redis集群搭建 1.1使用redis-cli创建集群 1.2redis集群状态验证(可选) 1.3重启pod,验证集群(可选) 1.4创建Service服务 1.5 Springboot项目配置 1.6相关疑问分析 写在前面 在上一篇文章中,我们已经做到了已经创建好6个redis副本了. 具体的详情,可以查看这里:k8s部署redis集群(一) 那么接下来,我们就继续实现redis集群的搭建过程. 一.redis集群搭建 1.1使用redis-cli创建集群 # 查看re

  • k8s编排之StatefulSet知识点详解二

    目录 StatefulSet 对存储状态的管理机制 第一步:定义一个 PVC,声明想要的 Volume 的属性 第二步:在应用的 Pod 中,声明使用这个 PVC 常见的 PV 对象的 YAML 文件 StatefulSet 对存储状态的管理机制 这个机制,主要使用的是一个叫作 Persistent Volume Claim 的功能. 要在一个 Pod 里声明 Volume,只要在 Pod 里加上 spec.volumes 字段即可.然后,你就可以在这个字段里定义一个具体类型的 Volume 了

  • 基于java集合中的一些易混淆的知识点(详解)

    (一) collection和collections 这两者均位于java.util包下,不同的是: collection是一个集合接口,有ListSet等常见的子接口,是集合框架图的第一个节点,,提供了对集合对象进行基本操作的一系列方法. 常见的方法有: boolean add(E e) 往容器中添加元素:int size() 返回collection的元素数:boolean isEmpty() 判断此容器是否为空: boolean contains(Object o) 如果此collecti

  • MySQL使用TEXT/BLOB类型的知识点详解

    一.TEXT和BLOB的区别 TEXT和BLOB家族之间仅有的不同是BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT类型有字符集或排序规则.说白了如果要储存中文则选择TEXT. 二.默认值问题 Strict Mode下不能设置默认值,否则会报can't have a default value错: mysql> create table `test`.`text_blob`( -> `a_text` text DEFAULT ' ' , -> `b_blob` blob

  • Django用数据库表反向生成models类知识点详解

    Django根据已有数据库表反向生成models类 一. 创建一个Django项目 django-admin startproject 'xxxx' 二.修改settings文件 在setting里面设置你要连接的数据库名称,地址,账号密码之类的信息,和创建新项目的时候一致 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djangodemo', # 数据库名称 'USER': 'root', '

  • C语言 数据存储方式知识点详解

    C语言 数据存储方式 一.源码 一个数的原码(原始的二进制码)有如下特点: 最高位做为符号位,0表示正,为1表示负 其它数值部分就是数值本身绝对值的二进制数 负数的原码是在其绝对值的基础上,最高位变为1 下面数值以1字节的大小描述: 十进制数 原码 +15 0000 1111 -15 1000 1111 +0 0000 0000 -0 1000 0000 注:原码表示法简单易懂,与带符号数本身转换方便,只要符号还原即可,但当两个正数相减或不同符号数相加时,必须比较两个数哪个绝对值大,才能决定谁减

  • Vue+Vuex实现自动登录的知识点详解

    在之前实现的版本中,如果你进行测试,可以看到在浏览器的local Storage中,确实里面有了我们加入的Authorization,而且如果没有登录的话,直接访问主页会进入登录页面.但其实有好几个问题并没有解决: 一.我们所加的Authorzation其实并不是从服务器传过来的,而是自己的测试:只要服务器传过来了200的响应状态码,我们就自己加上固定的Authorization 二.我们重新进入的时候,判断条件是只要有Authorization就可以直接进入了,但其实应该提交给服务器判断这个A

  • R语言关于数据帧的知识点详解

    数据帧是表或二维阵列状结构,其中每一列包含一个变量的值,并且每一行包含来自每一列的一组值. 以下是数据帧的特性. 列名称应为非空. 行名称应该是唯一的. 存储在数据帧中的数据可以是数字,因子或字符类型. 每个列应包含相同数量的数据项. 创建数据帧 # Create the data frame. emp.data <- data.frame( emp_id = c (1:5), emp_name = c("Rick","Dan","Michelle&

随机推荐