做者 | 阚俊宝 阿里云技术专家linux
导读:云原生存储详解系列文章将从云原生存储服务的概念、特色、需求、原理、使用及案例等方面,和你们一块儿探讨云原生存储技术新的机遇与挑战。本文为该系列文章的第二篇,会对容器存储的相关概念进行讲述,欢迎你们在留言区参与讨论。
相关文章推荐:nginx
云原生存储详解:云原生应用的基石
云原生存储详解:容器存储与 K8s 存储卷
云原生存储的两个关键领域:Docker 存储卷、K8s 存储卷;web
容器服务之因此如此流行,一大优点即来自于运行容器时容器镜像的组织形式。容器经过复用容器镜像的技术,实如今相同节点上多个容器共享一个镜像资源(更细一点说是共享某一个镜像层),避免了每次启动容器时都拷贝、加载镜像文件,这种方式既节省了主机的存储空间,又提升了容器启动效率。docker
为了提升节点存储的使用效率,容器不光在不一样运行的容器之间共享镜像资源,并且还实现了在不一样镜像之间共享数据。共享镜像数据的实现原理:镜像是分层组合而成的,即一个完整的镜像会包含多个数据层,每层数据相互叠加、覆盖组成了最终的完整镜像。json
为了实现多个容器间共享镜像数据,容器镜像每一层都是只读的。而经过实践咱们得知,使用镜像启动一个容器的时候,实际上是能够在容器里随意读写的,这是如何实现的呢?后端
容器使用镜像时,在多个镜像分层的最上面还添加了一个读写层。每个容器在运行时,都会基于当前镜像在其最上层挂载一个读写层,用户针对容器的全部操做都在读写层中完成。一旦容器销毁,这个读写层也随之销毁。api
如上图所示例子,一个节点上共有 3 个容器,分别基于 2 个镜像运行。安全
镜像存储层说明以下:网络
该节点上共包含 6 个镜像层:Layer 1~6。架构
- 镜像 1 由:Layer 一、三、四、5 组成;
- 镜像 2 由:Layer 二、三、五、6 组成。
因此两个镜像共享了 Layer 三、5 两个镜像层;
容器存储说明:
- 容器 1:使用镜像 1 启动
- 容器 2:使用镜像 1 启动
- 容器 3:使用镜像 2 启动
容器 1 和容器 2 共享镜像 1,且每一个容器有本身的可写层;
容器 1(2)和容器 3 共享镜像 2 个层(Layer三、5);
经过上述例子能够看到,经过容器镜像分层实现数据共享能够大幅减小容器服务对主机存储的资源需求。
上面给出了容器读写层结构,而读写的原则:
对于读:容器由这么多层的数据组合而成,当不一样层次的数据重复时,读取的原则是上层数据覆盖下层数据;
对于写:容器修改某个文件时,都是在最上层的读写层进行。主要实现技术有:写时复制、用时配置。
写时复制(CoW:copy-on-write),表示只在须要写时才去复制,是针对已有文件的修改场景。CoW 技术可让全部的容器共享 image 的文件系统,全部数据都从 image 中读取,只有当要对文件进行写操做时,才从 image 里把要写的文件复制到最上面的读写层进行修改。因此不管有多少个容器共享同一个 image,所作的写操做都是对从 image 中复制后在复本上进行,并不会修改 image 的源文件,且多个容器操做同一个文件,会在每一个容器的文件系统里生成一个复本,每一个容器修改的都是本身的复本,相互隔离,相互不影响。
用时分配:在镜像中本来没有某个文件的场景,只有在要新写入一个文件时才分配空间,这样能够提升存储资源的利用率。好比启动一个容器,并不会为这个容器预分配一些磁盘空间,而是当有新文件写入时,才按需分配新空间。
存储驱动是指如何对容器的各层数据进行管理,已达到上述须要实现共享、可读写的效果。即:容器存储驱动实现了容器读写层数据的存储和管理。常见的存储驱动:
以 AUFS 为例,咱们来说述一下存储驱动的工做原理:
AUFS 是一种联合文件系统(UFS),是文件级的存储驱动。
AUFS 是一个能透明叠加一个或多个现有文件系统的层状文件系统,把多层文件系统合并成单层表示。即:支持将不一样目录挂载到同一个虚拟文件系统下的文件系统。
能够一层一层地叠加修改文件,其底层都是只读的,只有最上层的文件系统是可写的。
当须要修改一个文件时,AUFS 建立该文件的一个副本,使用 CoW 将文件从只读层复制到可写层进行修改,结果也保存在可写层。
在 Docker 中,底下的只读层就是 image,可写层就是 Container 运行时。
其余各类存储驱动这里再也不细讲,有兴趣的同窗能够到网上查询资料。
容器中的应用读写数据都是发生在容器的读写层,镜像层+读写层映射为容器内部文件系统、负责容器内部存储的底层架构。当咱们须要容器内部应用和外部存储进行交互时,须要一个相似于计算机 U 盘同样的外置存储,容器数据卷即提供了这样的功能。
另外一方面:容器自己的存储数据都是临时存储,在容器销毁的时候数据会一块儿删除。而经过数据卷将外部存储挂载到容器文件系统,应用能够引用外部数据,也能够将本身产出的数据持久化到数据卷中,因此容器数据卷是容器进行数据持久化的实现方式。
容器存储组成:只读层(容器镜像) + 读写层 + 外置存储(数据卷)
容器数据卷从做用范围能够分为:单机数据卷 和 集群数据卷。单机数据卷即为容器服务在一个节点上的数据卷挂载能力,docker volume 是单机数据卷的表明实现;集群数据卷则关注的是集群级别的数据卷编排能力,K8s 数据卷则是集群数据卷的主要应用方式。
Docker Volume 是一个可供多个容器使用的目录,它绕过 UFS,包含如下特性:
Bind:将主机目录/文件直接挂载到容器内部。
- 须要使用主机的上的绝对路径,且能够自动建立主机目录;
- 容器能够修改挂载目录下的任何文件,是应用更具备便捷性,但也带来了安全隐患。
Volume:使用第三方数据卷的时候使用这种方式。
- Volume命令行指令:docker volume (create/rm);
- 是Docker提供的功能,因此在非 docker 环境下没法使用;
- 分为命名数据卷和匿名数据卷,其实现是一致的,区别是匿名数据卷的名字为随机码;
- 支持数据卷驱动扩展,实现更多外部存储类型的接入。
Tmpfs:非持久化的卷类型,存储在内存中。
数据易丢失。
-v: src:dst:opts 只支持单机版。
- Src:表示卷映射源,主机目录或文件,须要是绝对地址;
- Dst:容器内目标挂载地址;
- Opts:可选,挂载属性:ro, consistent, delegated, cached, z, Z;
- Consistent, delegated, cached:为mac系统配置共享传播属性;
- Z、z:配置主机目录的selinux label。
示例:
$ docker run -d --name devtest -v /home:/data:ro,rslave nginx $ docker run -d --name devtest --mount type=bind,source=/home,target=/data,readonly,bind-propagation=rslave nginx $ docker run -d --name devtest -v /home:/data:z nginx
-v: src:dst:opts 只支持单机版。
- Src:表示卷映射源,数据卷名、空;
- Dst:容器内目标目录;
- Opts:可选,挂载属性:ro(只读)。
示例:
$ docker run -d --name devtest -v myvol:/app:ro nginx $ docker run -d --name devtest --mount source=myvol2,target=/app,readonly nginx
Docker 数据卷使用方式:
docker run -d -v /test:/data nginx
若是主机上没有/test目录,则默认建立此目录。
数据卷容器是一个运行中的容器,其余容器能够继承此容器中的挂载数据卷,则此容器的全部挂载都会在引用容器中体现。
docker run -d --volumes-from nginx1 -v /test1:/data1 nginx
继承全部来自配置容器的数据卷,并包含本身定义的卷。
Docker volume 支持挂载传播的配置:Propagation。
示例:
$ docker run –d -v /home:/data:shared nginx 表示:主机/home下面挂载的目录,在容器/data下面可用,反之可行; $ docker run –d -v /home:/data:slave nginx 表示:主机/home下面挂载的目录,在容器/data下面可用,反之不行;
Volume 挂载可见性:
Bind 挂载可见性:以主机目录为准。
Docker 数据卷实现了将容器外部存储挂载到容器文件系统的方式。为了扩展容器对外部存储类型的需求,docker 提出了经过存储插件的方式挂载不一样类型的存储服务。扩展插件统称为 Volume Driver,能够为每种存储类型开发一种存储插件。
Docker Daemon 与 Volume driver 通讯方式有:
实现接口:
Create, Remove, Mount, Path, Umount, Get, List, Capabilities;
使用示例:
$ docker volume create --driver nas -o diskid="" -o host="10.46.225.247" -o path=”/nas1" -o mode="" --name nas1
Docker Volume Driver 适用在单机容器环境或者 swarm 平台进行数据卷管理,随着 K8s 的流行其使用场景已经愈来愈少,关于 VolumeDriver 的详细介绍这里不在细讲,有兴趣能够参考:https://docs.docker.com/engine/extend/plugins_volume/
根据以前的描述,为了实现容器数据的持久化咱们须要使用数据卷的功能,在 K8s 编排系统中如何为运行的负载(Pod)定义存储呢?K8s 是一个容器编排系统,其关注的是容器应用在整个集群的管理和部署形式,因此在考虑 K8s 应用存储的时候就须要从集群角度考虑。K8s 存储卷定义了在 K8s 系统中应用与存储的关联关系。其包含如下概念:
数据卷定义了外置存储的细节,并内嵌到 Pod 中做为 Pod 的一部分。其实质是外置存储在 K8s 系统的一个记录对象,当负载须要使用外置存储的时候,从数据卷中查到相关信息并进行存储挂载操做。
K8S Volume 经常使用类型:
一些 volume 模板示例以下:
volumes: - name: hostpath hostPath: path: /data type: Directory --- volumes: - name: disk-ssd persistentVolumeClaim: claimName: disk-ssd-web-0 - name: default-token-krggw secret: defaultMode: 420 secretName: default-token-krggw --- volumes: - name: "oss1" flexVolume: driver: "alicloud/oss" options: bucket: "docker" url: "oss-cn-hangzhou.aliyuncs.com"
PVC 是 PersistentVolumeClaim 的缩写,译为存储声明;PVC 是在 K8s 中一种抽象的存储卷类型,表明了某个具体类型存储的数据卷表达。其设计意图是:存储与应用编排分离,将存储细节抽象出来并实现存储的编排(存储卷)。这样 K8s 中存储卷对象独立于应用编排而单独存在,在编排层面使应用和存储解耦。
PV 是 PersistentVolume 的缩写,译为持久化存储卷;PV 在 K8s 中表明一个具体存储类型的卷,其对象中定义了具体存储类型和卷参数。即目标存储服务全部相关的信息都保存在 PV 中,K8s 引用 PV 中的存储信息执行挂载操做。
应用负载、PVC、PV 的关联关系为:
从实现上看,只要有了 PV 既能够实现存储和应用的编排分离,也能实现数据卷的挂载,为什么要用 PVC + PV 两个对象呢?K8s 这样设计是从应用角度对存储卷进行二次抽象;因为 PV 描述的是对具体存储类型,须要定义详细的存储信息,而应用层用户在消费存储服务的时候每每不但愿对底层细节知道的太多,让应用编排层面来定义具体的存储服务不够友好。这时对存储服务再次进行抽象,只把用户关系的参数提炼出来,用 PVC 来抽象更底层的 PV。因此 PVC、PV 关注的对象不同,PVC 关注用户对存储需求,给用户提供统一的存储定义方式;而 PV 关注的是存储细节,能够定义具体存储类型、存储挂载使用的详细参数等。
使用时应用层会声明一个对存储的需求(PVC),而 K8s 会经过最佳匹配的方式选择一个知足 PVC 需求的 PV,并与之绑定。因此从职责上 PVC 是应用所须要的存储对象,属于应用做用域(和应用处于一个名词空间);PV 是存储平面的存储对象,属于整个存储域(不属于某个名词空间);
下面给出 PVC、PV 的一些属性:
PVC 定义的模板以下:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: disk-ssd-web-0 spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: alicloud-disk-available volumeMode: Filesystem
PVC 定义的存储接口包括:存储的读写模式、资源容量、卷模式等;主要参数说明以下:
accessModes:存储卷的访问模式,支持:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三种模式。
- ReadWriteOnce 表示 pvc 只能同时被一个 pod 以读写方式消费;
- ReadWriteMany 能够同时被多个 pod 以读写方式消费;
- ReadOnlyMany 表示能够同时被多个 pod 以只读方式消费;
注意:这里定义的访问模式只是编排层面的声明,具体应用在读写存储文件的时候是否可读可写,须要具体的存储插件实现肯定。
storage:定义此 PVC 对象指望提供的存储容量,一样此处的数据大小也只是编排声明的值,具体存储容量要看底层存储服务类型。
volumeMode:表示存储卷挂载模式,支持 FileSystem、Block 两种模式;
FileSystem:将数据卷挂载成文件系统的方式供应用使用;
Block:将数据卷挂载成块设备的形式供应用使用。
下面为云盘数据卷 PV 对象的编排示例:
apiVersion: v1 kind: PersistentVolume metadata: labels: failure-domain.beta.kubernetes.io/region: cn-shenzhen failure-domain.beta.kubernetes.io/zone: cn-shenzhen-e name: d-wz9g2j5qbo37r2lamkg4 spec: accessModes: - ReadWriteOnce capacity: storage: 30Gi flexVolume: driver: alicloud/disk fsType: ext4 options: VolumeId: d-wz9g2j5qbo37r2lamkg4 persistentVolumeReclaimPolicy: Delete storageClassName: alicloud-disk-available volumeMode: Filesystem
- accessModes:存储卷的访问模式,支持:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三种模式;具体含义同 PVC 字段;
- capacity:定义存储卷容量;
- persistentVolumeReclaimPolicy:定义回收策略,即删除 pvc 的时候如何处理 PV;支持 Delete、Retain 两种类型,动态数据卷部分会详细说明此参数;
- storageClassName:表示存储卷的使用的存储类名字,动态数据卷部分会详细说明此参数;
- volumeMode:同 PVC 中的 volumeMode 定义;
- Flexvolume:此字段表示具体的存储类型,这里 Flexvolume 为一种抽象的存储类型,并在 flexvolume 的子配置项中定义了具体的存储类型、存储参数。
PVC 只有绑定了 PV 以后才能被 Pod 使用,而 PVC 绑定 PV 的过程便是消费 PV 的过程,这个过程是有必定规则的,下面规则都知足的 PV 才能被 PVC 绑定:
知足上述全部须要的 PV 才能够被 PVC 绑定。
若是同时有多个 PV 知足需求,则须要从 PV 中选择一个更合适的进行绑定;一般选择容量最小的,若是容量最小的也有多个,则随机选择。
若是没有知足上述需求的 PV 存储,则 PVC 会处于 Pending 状态,等待有合适的 PV 出现了再进行绑定。
从上面的讨论咱们了解到,PVC 是针对应用服务对存储的二次抽象,具备简洁的存储定义接口。而 PV 是具备繁琐存储细节的存储抽象,通常有专门的集群管理人员定义、维护。
根据 PV 的建立方式能够将存储卷分为动态存储和静态存储卷:
通常先由集群管理员分析集群中存储需求,并预先分配一些存储介质,同时建立对应的 PV 对象,建立好的 PV 对象等待 PVC 来消费。若是负载中定义了 PVC 需求,K8s 会经过相关规则实现 PVC 和匹配的 PV 进行绑定,这样就实现了应用对存储服务的访问能力。
由集群管理员配置好后端的存储池,并建立相应的模板(storageclass),等到有 PVC 须要消费 PV 的时候,根据 PVC 定义的需求,并参考 storageclass 的存储细节,由 Provisioner 插件动态建立一个 PV。
两种卷的比较:
提供动态存储卷的优点:
当用户声明一个 PVC 时,若是在 PVC 中添加了 StorageClassName 字段,其意图为:当 PVC 在集群中找不到匹配的 PV 时,会根据 StorageClassName 的定义触发相应的 Provisioner 插件建立合适的 PV 供绑定,即建立动态数据卷;动态数据卷时由 Provisioner 插件建立的,并经过 StorageClassName 与 PVC 进行关联。
StorageClass 可译为存储类,表示为一个建立 PV 存储卷的模板;在 PVC 触发自动建立PV的过程当中,即便用 StorageClass 对象中的内容进行建立。其内容包括:目标 Provisioner 名字,建立 PV 的详细参数,回收模式等配置。
StorageClasss 模板定义以下:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: alicloud-disk-topology parameters: type: cloud_ssd provisioner: diskplugin.csi.alibabacloud.com reclaimPolicy: Delete allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer
用户建立一个 PVC 声明时,会在集群寻找合适的 PV 进行绑定,若是没有合适的 PV 与之绑定,则触发下面流程:
某种存储(阿里云云盘)在挂载属性上有所限制,只能将相同可用区的数据卷和 Node 节点进行挂载,不在同一个可用区不能够挂载。这种类型的存储卷一般遇到以下问题:
StorageClass 中的 volumeBindingMode 字段正是用来解决此问题,若是将 volumeBindingMode 配置为 WaitForFirstConsumer 值,则表示 Provisioner 在收到 PVC Pending 的时候不会当即进行数据卷建立,而是等待这个 PVC 被 Pod 消费的时候才执行建立流程。
其实现原理是:
经过上述流程可见:延迟绑定会先让应用负载进行调度(肯定有充足的资源供 pod 使用),而后再触发动态卷的建立流程,这样就避免了数据卷所在可用区没有资源的问题,也避免了存储预规划的不许确性问题。
在多可用区集群环境中,更推荐使用延迟绑定的动态卷方案,目前阿里云 ACK 集群已经支持上述配置方案。
下面给出一个 pod 消费 PVC、PV 的例子:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nas-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 50Gi selector: matchLabels: alicloud-pvname: nas-csi-pv --- apiVersion: v1 kind: PersistentVolume metadata: name: nas-csi-pv labels: alicloud-pvname: nas-csi-pv spec: capacity: storage: 50Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain flexVolume: driver: "alicloud/nas" options: server: "***-42ad.cn-shenzhen.extreme.nas.aliyuncs.com" path: "/share/nas" --- apiVersion: apps/v1 kind: Deployment metadata: name: deployment-nas labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx1 image: nginx:1.8 - name: nginx2 image: nginx:1.7.9 volumeMounts: - name: nas-pvc mountPath: "/data" volumes: - name: nas-pvc persistentVolumeClaim: claimName: nas-pvc
模板解析:
根据 PVC、PV 绑定的逻辑,此 PV 符合 PVC 消费要求,则 PVC 会和此 PV 进行绑定,并供 pod 挂载使用。
此篇文章较为详细的讲述了容器存储的总体面貌,包括单机范围的 Docker 数据卷、和集群式的 K8s 数据卷;K8s 数据卷更多关注的时候集群级别的存储编排能力,同时也在节点上实现了具体的数据卷挂载流程。K8s 为了实现上述复杂的存储卷编排能力,其实现架构也较为复杂,下节内容咱们将为您介绍 K8s 的存储架构和实现流程。
为了更多开发者可以享受到 Serverless 带来的红利,这一次,咱们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。
点击便可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless
“ 阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的公众号。”