Dynamic Provisioning原理分析

static provisioning

在kubernetes pod应用中如果有持久化数据的场景,一般会接触到pvc、pv和storageClass这些概念:

  • pvc: 描述的是pod希望使用的持久化存储的属性
  • pv: 描述的是具体的持久化存储数据卷信息
  • storageClass: 创建pv的模板

如果在实际中我们要用到nfs作为pod应用的持久化存储,一般会如下几个步骤:

  1. nfs server端创建一个目录
  2. 创建pv,并配置nfs server和挂载路径
  3. 创建pvc,并与上述pv绑定
  4. pod引用上述pvc

暂且抛开第4步,前面3步我们可以通过手动(或者通过程序)一步一步地执行,这种方式在kubernetes中叫作static provisioning。相比这种人工管理pv的方式,kubernetes也提供了一套自动创建pv的的机制,叫作dynamic provisioning

dynamic provisioning

所谓的dynamic provisioning,从功能上来讲就是用户基于某个storageClass创建pvc后,会有一个provisioner负责自动创建卷自动创建pv自动和pvc绑定

kubernetes已经支持了一些内置的provisioner,例如AzureDisk、RBD等。如果遇到一些不在kubernetes内置prvisioner范围内的存储(例如nfs)且有自动创建pv需求的场景,就需要我们自己实现一个provisioner。

实现一个provisioner其实很简单,只需要实现如下的Provisioner接口即可,其它工作社区维护的库中已经有了相关逻辑:

// sigs.k8s.io/sig-storage-lib-external-provisioner/v8/controller
type Provisioner interface {
    Provision(context.Context, ProvisionOptions) (*v1.PersistentVolume, ProvisioningState, error)
    Delete(context.Context, *v1.PersistentVolume) error
}

nfs-provisioner

provisioner接口有两个方法:ProvisionDelete,这两个方法要实现的逻辑也很简单。我们以nfs为例,尝试实现一个provisioner:

完整示例代码在github:https://github.com/nuczzz/nfs-provisioner

  • Provision

Provision方法里实现两个逻辑:创建卷(volume)返回一个pv对象,对于nfs来说,创建卷的时候创建一个目录用于挂载即可,目录名可以用pv名称。于是有如下代码:

import (
    "github.com/pkg/errors"
    v1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "sigs.k8s.io/sig-storage-lib-external-provisioner/v8/controller"
)

func (nfs *nfsProvisioner) Provision(_ context.Context, opt controller.ProvisionOptions) (*v1.PersistentVolume, controller.ProvisioningState, error) {
    // 创建卷
    mountPath := filepath.Join(nfs.mountPath, opt.PVName)
    if err := os.Mkdir(mountPath, 0755); err != nil {
        log.Printf("mkdir %s error: %s", mountPath, err.Error())
        return nil, controller.ProvisioningFinished, errors.Wrap(err, "mkdir error")
    }

    // 返回pv对象
    return &v1.PersistentVolume{
        ObjectMeta: metav1.ObjectMeta{
            Name: opt.PVName,
        },
        Spec: v1.PersistentVolumeSpec{
            PersistentVolumeReclaimPolicy: *opt.StorageClass.ReclaimPolicy,
            AccessModes:                   opt.PVC.Spec.AccessModes,
            MountOptions:                  opt.StorageClass.MountOptions,
            Capacity: v1.ResourceList{
                v1.ResourceStorage: opt.PVC.Spec.Resources.Requests[v1.ResourceStorage],
            },
            PersistentVolumeSource: v1.PersistentVolumeSource{
                NFS: &v1.NFSVolumeSource{
                    Server:   nfs.server,
                    Path:     filepath.Join(nfs.rootMountPath, opt.PVName),
                    ReadOnly: false,
                },
            },
        },
    }, controller.ProvisioningFinished, nil
}
  • Delete

Delete方法则只需要移除对应的nfs目录即可:

func (nfs *nfsProvisioner) Delete(_ context.Context, pv *v1.PersistentVolume) error {
    return os.Remove(filepath.Join(nfs.mountPath, pv.Name))
}
部署验证

相应的部署yaml如下,包含storageClass、rbac和一个deployment的定义(注意最后deployment yaml中需要配置好实际的nfs服务IP和路径):

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: nfs # 需要和程序中的provisioner名称对应

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-provisioner
rules:
  - apiGroups:
      - ""
    resources:
      - "persistentvolumes"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "create"
      - "delete"
  - apiGroups:
      - ""
    resources:
      - "persistentvolumeclaims"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "update"
  - apiGroups:
      - "storage.k8s.io"
    resources:
      - "storageclasses"
    verbs:
      - "get"
      - "list"
      - "watch"
  - apiGroups:
      - ""
    resources:
      - "events"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "create"
      - "update"
      - "patch"
  - apiGroups:
      - "coordination.k8s.io"
    resources:
      - "leases"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "create"
      - "update"
      - "patch"

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: nfs-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
  namespace: kube-system

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: kube-system
  name: nfs-provisioner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-provisioner
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccountName: nfs-provisioner
      containers:
        - name: nfs-provisioner
          image: nfs-provisioner:v0.1
          imagePullPolicy: IfNotPresent
          args:
            - --server= # 配置nfs server ip
            - --serverPath= # 配置nfs server root path
          volumeMounts:
            - name: nfs
              mountPath: /mount
      volumes:
        - name: nfs
          nfs:
            server: "" # 配置nfs server ip
            path: "" # 配置nfs server root path

我们创建一个pvc来测试下功能:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs
  resources:
    requests:
      storage: 1Gi

apply这个pvc文件,观察是否会自动创建卷自动创建pv并绑定

# kubectl get pv
No resources found.
# kubectl get pvc
No resources found.
# kubectl apply -f pvc.yaml 
persistentvolumeclaim/test-pvc created
# kubectl get pvc
NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-pvc   Bound    pvc-48f40158-027a-482f-899d-7b1aa9b5090f   1Gi        RWO            nfs            15s
# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
pvc-48f40158-027a-482f-899d-7b1aa9b5090f   1Gi        RWO            Delete           Bound    default/test-pvc   nfs                     20s

// 查看nfs server目录下是否创建对应的卷
# ls
pvc-48f40158-027a-482f-899d-7b1aa9b5090f

再验证删除功能,观察删除pvc后,对应的pv和卷是否会自动删除:

# kubectl delete -f pvc.yaml 
persistentvolumeclaim "test-pvc" deleted
# kubectl get pvc
No resources found.
# kubectl get pv
No resources found.

// 检查卷是否被删除
# ls
#

dynamic provisioning的原理

通过前面nfs provisioner示例大家应该能比较清晰地理解dynamic provisioning的作用:基于pvc自动创建卷和pv并绑定。但是为什么我们实现了前文的Provisioner接口的ProvisionDelete方法,就可以实现dynamic provisioning?

我们基于sigs.k8s.io/sig-storage-lib-external-provisioner/v8@v8.0.0来分析下原理,在sigs.k8s.io/sig-storage-lib-external-provisioner/v8@v8.0.0中会启动三种资源的informer:

  • storageClass informer

缓存storageClass资源,一方面用于校验用户创建pvc中的storageClass是否匹配,另一方面因为storageClass可以看做pv的模板,有些参数需要从storageClass对象中拿到并填充到pv对象中。

  • pvc informer:

缓存pvc对象并注册pvc对象的事件处理函数,当收到pvc事件后会先判断是否需要provision,例如必须是没有绑定pv的pvc才能通过判断。通过判断后会调用Provisoner接口的Provision方法,也就是走到了我们自己实现的逻辑里去创建卷并初始化pv对象。之后基于Provision方法返回的pv对象填充pv.spec.ClaimRef字段(与pvc绑定)增加一个finalizer等,最后调apiServer接口创建pv

  • pv informer:

缓存pv对象并注册pv对象的事件处理函数,当收到pv事件后会先判断是否需要删除,判断条件包括pv对象的DeletionTimestamp不为空provisioner的finalizer还存在等。如果通过判断,会调用我们实现的Delete方法中,也就是去清除对应的物理资源。清理完物理资源后移除pv对象中provisioner的finalizer并更新pv对象。

于是有如下逻辑:

在这里插入图片描述

dynamic provisoning的不足

我们知道了dynamic provisioning可以自动创建卷和pv并绑定,但是它的功能也仅仅如此。对于一些复杂的存储,例如谷歌的Persistent Disk,需要“创建磁盘”挂载到宿主机格式化mount到pod目录等操作后才能正常使用,很显然这种场景下单纯的provisioning过程是满足不了需求的。面对这些复杂场景,kubernetes抽象出了CSI(Container Storage Interface)来处理,敬请期待接下来CSI相关的文章。

微信公众号卡巴斯同步发布,欢迎大家关注。

<think>好的,用户之前问过Kubernetes的本地持久化卷,现在又问本地持久化卷是否支持动态配置(Dynamic Provisioning)。我需要先确认用户对Dynamic Provisioning的理解程度。动态配置是指在创建PVC时自动创建PV,而不需要手动预先配置PV。对于本地持久化卷来说,传统的做法是手动创建PV,但用户可能想知道是否有自动化方法。 首先,我应该回顾Kubernetes官方文档中的相关信息。记得在旧版本中,本地卷不支持动态配置,需要手动管理。但后来社区开发了一个Local Volume Provisioner,属于SIG-Storage项目的一部分。这个Provisioner可以监控特定目录,当发现新的存储设备时自动创建PV。因此,现在是可以实现动态配置的,但需要额外配置Provisioner。 接下来,用户可能需要知道如何实现这一点。需要分步骤说明:安装Provisioner、配置存储类、创建PVC等。还要注意节点标签和路径的配置,以及动态配置的限制,比如仍然依赖节点本地存储,无法跨节点迁移,节点故障时的处理等。 还要比较静态配置和动态配置的区别,让用户明确两者的适用场景。动态配置适合有多个本地磁盘或频繁需要分配的场景,而静态配置适合固定配置的情况。同时,要提醒用户节点亲和性和数据持久性的问题,确保他们了解潜在的风险,比如节点故障导致数据不可用,需要备份策略。 可能用户的实际场景是在生产环境中部署有状态应用,比如数据库或消息队列,需要高性能的本地存储,同时希望减少手动管理PV的工作量。因此,动态配置可以简化运维,但需要正确设置Provisioner和存储类。 需要确保回答结构清晰,分点说明,提供配置示例,并强调注意事项。还要验证所有信息是否正确,比如Provisioner的GitHub链接是否有效,存储类配置的参数是否正确。可能用户会进一步询问如何部署Provisioner,所以提供示例YAML片段会有帮助。 最后,总结本地持久化卷的动态配置现状,说明通过Provisioner可以实现,但需要了解其限制,并建议根据实际需求选择合适的配置方式。</think>### Kubernetes 本地持久化卷(Local Persistent Volume)是否支持动态配置(Dynamic Provisioning)? #### **直接回答** **是的,Kubernetes 本地持久化卷(Local Persistent Volume)可以通过 [Local Volume Provisioner](https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner) 实现动态配置(Dynamic Provisioning)**,但需要额外部署组件并配置存储类(StorageClass)。 **核心限制**是:动态配置仅适用于预先划分好的本地磁盘或目录,无法像云存储(如 AWS EBS)那样完全按需创建存储设备。 --- #### **实现原理** 1. **Local Volume Provisioner** 由 Kubernetes SIG-Storage 维护的组件,部署为 DaemonSet,监控节点上指定的目录(如 `/mnt/disks`)。 - 当发现新目录或设备时,自动创建对应的 PV。 - 当目录/设备被删除时,自动清理 PV。 2. **存储类(StorageClass)** 需定义存储类,并指定卷绑定模式(`WaitForFirstConsumer`),确保 Pod 调度到正确节点后再绑定 PV。 --- #### **配置步骤** ##### 1. **部署 Local Volume Provisioner** ```yaml # 示例:Local Volume Provisioner 配置(部分) apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer ``` ```yaml # DaemonSet 配置(核心部分) apiVersion: apps/v1 kind: DaemonSet metadata: name: local-volume-provisioner spec: template: spec: containers: - name: provisioner image: k8s.gcr.io/sig-storage/local-volume-provisioner:v2.5.0 volumeMounts: - mountPath: /mnt/disks # 监控此目录 name: local-disks volumes: - name: local-disks hostPath: path: /mnt/disks # 节点本地存储路径 ``` ##### 2. **创建 PVC(自动触发 PV 生成)** ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: dynamic-local-pvc spec: storageClassName: local-storage accessModes: - ReadWriteOnce resources: requests: storage: 10Gi ``` --- #### **动态配置的限制** 1. **依赖预先划分的存储** - 需运维人员在节点上预先创建目录(如 `/mnt/disks/disk1`)或挂载磁盘。 - Provisioner 仅负责发现目录并生成 PV,不负责底层存储设备的创建。 2. **节点亲和性不可变** - PV 始终绑定到特定节点,Pod 必须调度到该节点。 - 若节点故障,PVC 无法自动迁移到其他节点。 3. **容量管理** - 需人工确保节点本地存储容量充足,无自动扩容机制。 --- #### **动态 vs 静态配置对比** | **特性** | **动态配置** | **静态配置** | |------------------------|----------------------------------------------|----------------------------------| | **PV 创建方式** | 自动发现目录并生成 PV | 手动定义 PV YAML | | **灵活性** | 适合多磁盘节点,批量管理 | 适合固定存储设备 | | **运维成本** | 需部署 Provisioner,但减少手动操作 | 完全手动管理 | | **适用场景** | 节点有多个本地磁盘或频繁创建/删除 PVC 的环境 | 少量固定本地存储需求 | --- #### **注意事项** 1. **数据持久性风险** - 若节点宕机,本地卷数据可能丢失,需配合备份方案(如定期快照)。 2. **调度策略优化** - 使用 `topology-aware` 调度(如 `volumeBindingMode: WaitForFirstConsumer`)避免 Pod 调度冲突。 3. **资源清理** - 删除 PVC 时,动态生成的 PV 会根据回收策略(`persistentVolumeReclaimPolicy`)处理,通常设为 `Delete` 或 `Retain`。 --- #### **总结** - **支持动态配置**:通过 Local Volume Provisioner 可实现,但需预先划分存储目录。 - **核心价值**:减少手动创建 PV 的工作量,适合多节点、多磁盘环境。 - **推荐场景**:边缘计算、高性能数据库集群等需要本地存储且需批量管理的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值