learn-tech/专栏/周志明的架构课/55_谈谈Kubernetes的存储设计理念.md
2024-10-16 06:37:41 +08:00

320 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
55 _ 谈谈Kubernetes的存储设计理念
你好我是周志明。从这节课起我会用三讲带你学习容器编排系统存储方面的知识点。今天这节课我们先来探讨下Kubernetes的存储设计理念。
Kubernetes的存储设计考量
在开始之前我想先表明一下我对Kubernetes存储能力的态度。Kubernetes在规划持久化存储能力的时候依然遵循着它的一贯设计哲学用户负责以资源和声明式API来描述自己的意图Kubernetes负责根据用户意图来完成具体的操作。不过我认为就算只是描述清楚用户的存储意图也不是一件容易的事情相比Kubernetes提供的其他能力的资源它内置的存储资源其实格外地复杂甚至可以说是有些繁琐的。
如果你是Kubernetes的拥趸不能认同我对Kubernetes的批评那不妨来看一看下列围绕着“Volume”所衍生出的概念它们仅仅是与Kubernetes存储相关概念的一个子集而已你在看的时候也可以来思考一下这些概念是否全都是必须的、是否还有整合的空间、是否有化繁为简的可能性
概念Volume、PersistentVolume、PersistentVolumeClaim、Provisioner、StorageClass、Volume Snapshot、Volume Snapshot Class、Ephemeral Volumes、FlexVolume Driver、Container Storage Interface、CSI Volume Cloning、Volume Limits、Volume Mode、Access Modes、Storage Capacity……-
操作Mount、Bind、Use、Provision、Claim、Reclaim、Reserve、Expand、Clone、Schedule、Reschedule……
其实啊Kubernetes之所以有如此多关于存储的术语概念最重要的原因是存储技术本来就有很多种类为了尽可能多地兼容各种存储Kubernetes不得不预置了很多In-Tree意思是在Kubernetes的代码树里插件来对接让用户根据自己的业务按需选择。
同时为了兼容那些不在预置范围内的需求场景Kubernetes也支持用户使用FlexVolume或者CSI来定制Out-of-Tree意思是在Kubernetes的代码树之外的插件实现更加丰富多样的存储能力。下表中列出了Kubernetes目前提供的一部分存储与扩展的插件
事实上迫使Kubernetes存储设计得如此复杂的原因除了是要扩大兼容范畴之外还有一个非技术层面的因素就是Kubernetes是一个工业级的、面向生产应用的容器编排系统。
而这就意味着即使Kubernetes发现了某些已存在的功能有更好的实现方式但直到旧版本被淘汰出生产环境以前原本已支持的功能都不允许突然间被移除或者替换掉。否则当生产系统更新版本时已有的功能就会出现异常那就会极大威胁到产品的信誉。
当然在一定程度上我们可以原谅Kubernetes为了实现兼容而导致的繁琐但这样的设计确实会让Kubernetes的学习曲线变得更加陡峭。
Kubernetes提供的官方文档的主要作用是为实际开发提供参考它并不会告诉你Kubernetes中各种概念的演化历程、版本发布新功能的时间线、改动的缘由与背景等信息只会以“平坦”的方式来陈述所有目前可用的功能这可能有利于熟练的管理员快速查询到关键信息却不利于初学者去理解Kubernetes的设计思想。
如此一来因为很难理解那些概念和操作的本意初学者往往就只能死记硬背很难分辨出它们应该如何被“更正确”地使用。而介绍Kubernetes设计理念的职责只能由Kubernetes官方的Blog 这类信息渠道,或者其他非官方资料去完成。
所以接下来我会从Volume的概念开始以操作系统到Docker再到Kubernetes的演进历程为主线带你去梳理前面提到的那些概念与操作以此帮你更好地理解Kubernetes的存储设计。
首先我们来看看Mount和Volume这两个概念。
Mount和Volume
Mount和Volume都是来源于操作系统的常用术语Mount是动词表示将某个外部存储挂载到系统中Volume是名词表示物理存储的逻辑抽象目的是为物理存储提供有弹性的分割方式。
而我们知道容器是源于对操作系统层的虚拟化为了满足容器内生成数据的外部存储需求我们也很自然地会把Mount和Volume的概念延至容器中。因此要想了解容器存储的发展我们不妨就以Docker的Mount操作为起始点。
目前Docker内建支持了三种挂载类型分别是Bind--mount type=bind、Volume--mount type=volume和tmpfs--mount type=tmpfs如下图所示。其中tmpfs主要用于在内存中读写临时数据跟我们这个小章节要讨论的对象“持久化存储”并不相符所以后面我们只着重关注Bind和Volume两种挂载类型就可以了。
图片来自Docker官网文档
我们先来聊聊Bind。
Bind Mount是Docker最早提供的发布时就支持挂载类型作用是把宿主机的某个目录或文件挂载到容器的指定目录或文件比如下面命令中参数-v表达的意思就是把外部的HTML文档挂到Nginx容器的默认网站根目录下
docker run -v /icyfenix/html:/usr/share/nginx/html nginx:latest
请注意,虽然命令中-v参数是--volume的缩写但-v最初只是用来创建Bind Mount而不是创建Volume Mount的。
这种迷惑的行为其实也并不是Docker的本意只是因为Docker刚发布的时候考虑得不够周全随随便便就在参数中占用了“Volume”这个词到后来真的需要扩展Volume的概念来支持Volume Mount的时候前面的-v已经被用户广泛使用了所以也就只能如此将就着继续用。
从Docker 17.06版本开始Bind就在Docker Swarm中借用了--mount参数过来这个参数默认创建的是Volume Mount用户可以通过明确的type子参数来指定另外两种挂载类型。比如说前面给到的命令就可以等价于下面所示的--mount版本
docker run --mount type=bind,source=/icyfenix/html,destination=/usr/share/nginx/html nginx:latest
从Bind Mount到Volume Mount实质上是容器发展过程中对存储抽象能力提升的外在表现。我们根据“Bind”这个名字以及Bind Mount的实际功能其实可以合理地推测Docker最初认为“Volume”就只是一种“外部宿主机的磁盘存储到内部容器存储的映射关系”但后来它眉头一皱发现事情并没有那么简单存储的位置并不局限只在外部宿主机存储的介质并不局限只是物理磁盘存储的管理也并不局限只有映射关系。
我给你举几个例子。
比如Bind Mount只能让容器与本地宿主机之间建立某个目录的映射那么如果想要在不同宿主机上的容器共享同一份存储就必须先把共享存储挂载到每一台宿主机操作系统的某个目录下然后才能逐个挂载到容器内使用这种跨宿主机共享存储的场景如下图所示
图片来自Docker官网文档
这种存储范围超越了宿主机的共享存储,配置过程却要涉及到大量与宿主机环境相关的操作,只能由管理员人工地去完成,不仅繁琐,而且由于每台宿主机环境的差异,还会导致主机很难实现自动化。
再比如即使只考虑单台宿主机的情况基于可管理性的需求Docker也完全有支持Volume Mount的必要。为什么这么说呢
实际上在Bind Mount的设计里Docker只有容器的控制权存放容器生产数据的主机目录是完全独立的与Docker没有任何关系它既不受Docker保护也不受Docker管理。所以这就使得数据很容易被其他进程访问到甚至是被修改和删除。如果用户想对挂载的目录进行备份、迁移等管理运维操作也只能在Docker之外靠管理员人工进行而这些都增加了数据安全与操作意外的风险。
因此Docker希望能有一种抽象的资源来代表在宿主机或网络中存储的区域以便让Docker能管理这些资源这样就很自然地联想到了操作系统里的Volume。
提出Volume最核心的一个目的是为了提升Docker对不同存储介质的支撑能力这同时也是为了减轻Docker本身的工作量。
要知道存储并不是只有挂载在宿主机上的物理存储这一种介质。在云计算时代网络存储逐渐成为了数据中心的主流选择不同的网络存储都有各自的协议和交互接口。而且并不是所有的存储系统都适合先挂载到操作系统然后再挂载到容器的如果Docker想要越过操作系统去支持挂载某种存储系统首先必须要知道该如何访问它然后才能把容器中的读写操作自动转移到该位置。
Docker把解决如何访问存储的功能模块叫做存储驱动Storage Driver。通过docker info命令你能查看到当前Docker所支持的存储驱动。虽然Docker已经内置了市面上主流的OverlayFS驱动比如Overlay、Overlay2、AUFS、BTRFS、ZFS等等但面对云计算的快速迭代只靠Docker自己来支持全部云计算厂商的存储系统是完全不现实的。
为此Docker就提出了与Storage Driver相对应的Volume Driver卷驱动的概念。
我们可以通过docker plugin install命令安装外部的卷驱动并在创建Volume时指定一个与其存储系统相匹配的卷驱动。比如我们希望数据存储在AWS Elastic Block Store上就找一个AWS EBS的驱动如果想存储在Azure File Storage上也是找一个对应的Azure File Storage驱动即可。
而如果在创建Volume时不指定卷驱动那默认就是local类型在Volume中存放的数据就会存储在宿主机的/var/lib/docker/volumes/目录之中。
Static Provisioning
好了了解了Mount和Volume的概念含义之后现在我们把讨论主角转回容器编排系统上。
这里我们会从存储如何分配、持久存储与非持久存储的差异出发来具体学习下Static Provisioning的设计。
首先我们可以明确一件事即Kubernetes同样是把操作系统和Docker的Volume概念延续了下来并对其进行了进一步的细化。
Kubernetes把Volume分为了持久化的PersistentVolume和非持久化的普通Volume两类这里为了不跟我前面定义的Volume这个概念产生混淆后面课程我提到的Kubernetes中非持久化的Volume时都会带着“普通”这个前缀。
普通Volume的设计目标并不是为了持久地保存数据而是为同一个Pod中多个容器提供可共享的存储资源所以普通Volume的生命周期非常明确也就是与挂载它的Pod有着相同的生命周期。
这样就意味着尽管普通Volume不具备持久化的存储能力但至少比Pod中运行的任何容器的存活期都更长Pod中不同的容器能共享相同的普通Volume当容器重新启动时普通Volume中的数据也能够得到保留。
当然一旦整个Pod被销毁普通Volume也就不复存在了数据在逻辑上也会被销毁掉。至于实际中是否会真正删除数据就取决于存储驱动具体是如何实现Unmount、Detach、Delete接口的这个小章节的主题是“持久化存储”所以关于无持久化能力的普通Volume我就不再展开了
如此一来从操作系统里传承下来的Volume概念就在Docker和Kubernetes中继续按照一致的逻辑延伸拓展了只不过Kubernetes为了把它跟普通Volume区别开来专门取了PersistentVolume这个名字。你可以从下图中直观地看出普通Volume、PersistentVolume和Pod之间的关系差异
其实我们从Persistent这个单词的意思就能大致了解PersistentVolume的含义它是指能够将数据进行持久化存储的一种资源对象。
PersistentVolume可以独立于Pod存在生命周期与Pod无关所以也就决定了PersistentVolume不应该依附于任何一个宿主机节点否则必然会对Pod调度产生干扰限制。我们在前面“Docker的三种挂载类型”图例中可以看到“Persistent”一列里都是网络存储这便是很好的印证。
额外知识Local PersistentVolume-
对于部署在云端数据中心的系统,通过网络访问同一个可用区中的远程存储,速度是完全可以接受的。但对于私有部署的系统来说,基于性能考虑,使用本地存储往往会更加常见。-
因此考虑到这样的实际需求从1.10版起Kubernetes开始支持Local PersistentVolume这是一种将一整块本地磁盘作为PersistentVolume供容器使用的专用方案。-
所谓的“专用方案”就是字面意思它并不适用于全部应用Local PersistentVolume只是针对以磁盘I/O为瓶颈的特定场景的解决方案因而它的副作用就很明显由于不能保证这种本地磁盘在每个节点中都一定存在所以Kubernetes在调度时就必须考虑到PersistentVolume分布情况只能把使用了Local PersistentVolume的Pod调度到有这种PersistentVolume的节点上。-
尽管调度器中专门有个Volume Binding Mode模式来支持这项处理但是一旦使用了Local PersistentVolume还是会限制Pod的可调度范围。
那么在把PersistentVolume与Pod分离后就需要专门考虑PersistentVolume该如何被Pod所引用的问题了。
实际上原本在Pod中引用其他资源是常有的事要么是通过资源名称直接引用要么是通过标签选择器Selectors间接引用。但是类似的方法在这里却都不太妥当至于原因你可以先思考一下“Pod该使用何种存储”这件事情应该是系统管理员运维人员说的算还是由用户开发人员说的算
要我看最合理的答案是他们一起说的才算因为只有开发能准确评估Pod运行需要消耗多大的存储空间只有运维能清楚地知道当前系统可以使用的存储设备状况。
所以为了让这二者能够各自提供自己擅长的信息Kubernetes又额外设计出了PersistentVolumeClaim资源。
其实在Kubernetes官方给出的概念定义中也特别强调了PersistentVolume是由管理员运维人员负责维护的用户开发人员通过PersistentVolumeClaim来匹配到合乎需求的PersistentVolume。
PersistentVolume & PersistentVolumeClaim-
A PersistentVolume PV is a piece of storage in the cluster that has been provisioned by an administrator.-
A PersistentVolumeClaim PVC is a request for storage by a user.-
PersistentVolume是由管理员负责提供的集群存储。-
PersistentVolumeClaim是由用户负责提供的存储请求。-
—— Kubernetes Reference DocumentationPersistent Volumes
PersistentVolume是Volume这个抽象概念的具象化表现通俗点儿说即它是已经被管理员分配好的具体的存储。
这里的“具体”是指有明确的存储系统地址有明确的容量、访问模式、存储位置等信息而PersistentVolumeClaim是Pod对其所需存储能力的声明通俗地说就是“如果要满足这个Pod正常运行需要满足怎样的条件”比如要消耗多大的存储空间、要支持怎样的访问方式。
所以,实际上管理员和用户并不是谁引用谁的固定关系,而是根据实际情况动态匹配的。
下面我们就来看看这两者配合工作的具体过程:
管理员准备好要使用的存储系统它应该是某种网络文件系统NFS或者云储存系统一般来说应该具备跨主机共享的能力。
管理员会根据存储系统的实际情况手工预先分配好若干个PersistentVolume并定义好每个PersistentVolume可以提供的具体能力。如下面例子所示
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-html
spec:
capacity:
storage: 5Gi # 最大容量为5GB
accessModes:
- ReadWriteOnce # 访问模式为RXO
persistentVolumeReclaimPolicy: Retain # 回收策略是Retain
nfs: # 存储驱动是NFS
path: /html
server: 172.17.0.2
这里我们来简单分析下以上YAML中定义的存储能力
存储的最大容量是5GB。
存储的访问模式是“只能被一个节点读写挂载”ReadWriteOnceRWO另外两种可选的访问模式是“可以被多个节点以只读方式挂载”ReadOnlyManyROX和“可以被多个节点读写挂载”ReadWriteManyRWX
存储的回收策略是Retain即在Pod被销毁时并不会删除数据。另外两种可选的回收策略分别是Recycle 即在Pod被销毁时由Kubernetes自动执行rm -rf /volume/*这样的命令来自动删除资料以及Delete它让Kubernetes自动调用AWS EBS、GCE PersistentDisk、OpenStack Cinder这些云存储的删除指令。
存储驱动是NFS其他常见的存储驱动还有AWS EBS、GCE PD、iSCSI、RBDCeph Block Device、GlusterFS、HostPath等等。
用户根据业务系统的实际情况创建PersistentVolumeClaim声明Pod运行所需的存储能力。如下面例子所示
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nginx-html-claim
spec:
accessModes:
- ReadWriteOnce # 支持RXO访问模式
resources:
requests:
storage: 5Gi # 最小容量5GB
可以看到在以上YAML中声明了要求容量不得小于5GB必须支持RWO的访问模式。
Kubernetes在创建Pod的过程中会根据系统中PersistentVolume与PersistentVolumeClaim的供需关系对两者进行撮合如果系统中存在满足PersistentVolumeClaim声明中要求能力的PersistentVolume就表示撮合成功它们将会被绑定。而如果撮合不成功Pod就不会被继续创建直到系统中出现新的、或让出空闲的PersistentVolume资源。
以上几步都顺利完成的话意味着Pod的存储需求得到满足进而继续Pod的创建过程。
以上的整个运作过程如下图所示:
图片来自《Kubernetes in Action》
Kubernetes对PersistentVolumeClaim与PersistentVolume撮合的结果是产生一对一的绑定关系“一对一”的意思是PersistentVolume一旦绑定在某个PersistentVolumeClaim上直到释放以前都会被这个PersistentVolumeClaim所独占不能再与其他PersistentVolumeClaim进行绑定。
这意味着即使PersistentVolumeClaim申请的存储空间比PersistentVolume能够提供的要少依然要求整个存储空间都为该PersistentVolumeClaim所用这有可能会造成资源的浪费。
比如某个PersistentVolumeClaim要求3GB的存储容量当前Kubernetes手上只剩下一个5GB的PersistentVolume了此时Kubernetes只好将这个PersistentVolume与申请资源的PersistentVolumeClaim进行绑定平白浪费了2GB空间。
假设后续有另一个PersistentVolumeClaim申请2GB的存储空间那它也只能等待管理员分配新的PersistentVolume或者有其他PersistentVolume被回收之后才被能成功分配。
Dynamic Provisioning
对于中小规模的Kubernetes集群PersistentVolume已经能够满足有状态应用的存储需求。PersistentVolume依靠人工介入来分配空间的设计虽然简单直观却算不上是先进一旦应用规模增大PersistentVolume很难被自动化的问题就会凸显出来。
这是由于Pod创建过程中需要去挂载某个Volume时都要求该Volume必须是真实存在的否则Pod启动可能依赖的数据如一些配置、数据、外部资源等都将无从读取。Kubernetes虽然有能力随着流量压力和硬件资源状况自动扩缩Pod的数量但是当Kubernetes自动扩展出一个新的Pod后并没有办法让Pod去自动挂载一个还未被分配资源的PersistentVolume。
想解决这个问题要么允许多个不同的Pod都共用相同的PersistentVolumeClaim这种方案确实只靠PersistentVolume就能解决却损失了隔离性难以通用要么就要求每个Pod用到的PersistentVolume都是已经被预先建立并分配好的这种方案靠管理员提前手工分配好大量的存储也可以实现却损失了自动化能力。
无论哪种情况都难以符合Kubernetes工业级编排系统的产品定位对于大型集群面对成百上千来自成千上万的Pod靠管理员手工分配存储肯定是无法完成的。在2017年Kubernetes发布1.6版本后终于提供了今天被称为Dynamic Provisioning的动态存储解决方案让系统管理员摆脱了人工分配的PersistentVolume的窘境并把此前的分配方式称为Static Provisioning。
那Dynamic Provisioning方案是如何解放系统管理员的呢我们先来看概念Dynamic Provisioning方案是指在用户声明存储能力的需求时不是期望通过Kubernetes撮合来获得一个管理员人工预置的PersistentVolume而是由特定的资源分配器Provisioner自动地在存储资源池或者云存储系统中分配符合用户存储需要的PersistentVolume然后挂载到Pod中使用完成这项工作的资源被命名为StorageClass它的具体工作过程如下
管理员根据储系统的实际情况先准备好对应的Provisioner。Kubernetes官方已经提供了一系列预置的In-Tree Provisioner放置在kubernetes.io的API组之下。其中部分Provisioner已经有了官方的CSI驱动如vSphere的Kubernetes自带驱动为kubernetes.io/vsphere-volumeVMware的官方驱动为csi.vsphere.vmware.com。
管理员不再是手工去分配PersistentVolume而是根据存储去配置StorageClass。Pod是可以动态扩缩的而存储则是相对固定的哪怕使用的是具有扩展能力的云存储也会将它们视为存储容量、IOPS等参数可变的固定存储来看待比如你可以将来自不同云存储提供商、不同性能、支持不同访问模式的存储配置为各种类型的StorageClass这也是它名字中“Class”类型的由来如下面这个例子
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs #AWS EBS的Provisioner
parameters:
type: gp2
reclaimPolicy: Retain
用户依然通过PersistentVolumeClaim来声明所需的存储但是应在声明中明确指出该由哪个StorageClass来代替Kubernetes处理该PersistentVolumeClaim的请求如下面这个例子
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: standard-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard #明确指出该由哪个StorageClass来处理该PersistentVolumeClaim的请求
resource:
requests:
storage: 5Gi
如果PersistentVolumeClaim中要求的StorageClass及它用到的Provisioner均是可用的话那这个StorageClass就会接管掉原本由Kubernetes撮合的PersistentVolume和PersistentVolumeClaim的操作按照PersistentVolumeClaim中声明的存储需求自动产生出满足该需求的PersistentVolume描述信息并发送给Provisioner处理。
Provisioner接收到StorageClass发来的创建PersistentVolume请求后会操作其背后存储系统去分配空间如果分配成功就生成并返回符合要求的PersistentVolume给Pod使用。
前面这几步都顺利完成的话就意味着Pod的存储需求得到了满足会继续Pod的创建过程整个过程如下图所示。
图片来自《Kubernetes in Action》
好了通过刚刚的讲述相信你可以看出Dynamic Provisioning与Static Provisioning并不是各有用途的互补设计而是对同一个问题先后出现的两种解决方案。你完全可以只用Dynamic Provisioning来实现所有的Static Provisioning能够实现的存储需求包括那些不需要动态分配的场景甚至之前例子里使用HostPath在本地静态分配存储都可以指定no-provisioner作为Provisioner的StorageClass以Local Persistent Volume来代替比如下面这个例子
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
所以说相较于Static Provisioning使用Dynamic Provisioning来分配存储无疑是更合理的设计不仅省去了管理员的人工操作的中间层也不再需要将PersistentVolume这样的概念暴露给最终用户因为Dynamic Provisioning里的PersistentVolume只是处理过程的中间产物用户不再需要接触和理解它只需要知道由PersistentVolumeClaim去描述存储需求由StorageClass去满足存储需求即可。只描述意图而不关心中间具体的处理过程是声明式编程的精髓也是流程自动化的必要基础。
除此之外由Dynamic Provisioning来分配存储还能获得更高的可管理性。如前面提到的回收策略当希望PersistentVolume跟随Pod一同被销毁时以前经常会配置回收策略为Recycle来回收空间即让系统自动执行rm -rf /volume/*命令。
但是这种方式往往过于粗暴要是遇到更精细的管理需求如“删除到回收站”或者“敏感信息粉碎式彻底删除”这样的功能实现起来就很麻烦。而Dynamic Provisioning中由于有Provisioner的存在如何创建、如何回收都是由Provisioner的代码所管理的这就带来了更高的灵活性。所以现在Kubernetes官方已经明确建议废弃掉Recycle策略如果有这类需求就改由Dynamic Provisioning去实现了。
另外相较于Dynamic ProvisioningStatic Provisioning的主要使用场景就局限于管理员能够手工管理存储的小型集群它符合很多小型系统尤其是私有化部署系统的现状但并不符合当今运维自动化所提倡的思路。Static Provisioning的存在某种意义上也可以视为是对历史的一种兼容在可见的将来Kubernetes肯定还是会把Static Provisioning作为用户分配存储的一种主要方案来供用户选用。
小结
容器是镜像的运行时实例,为了保证镜像能够重复地产生出具备一致性的运行时实例,必须要求镜像本身是持久而稳定的,这就决定了在容器中发生的一切数据变动操作,都不能真正写入到镜像当中,否则必然会破坏镜像稳定不变的性质。
为此容器中的数据修改操作大多是基于写入时复制Copy-on-Write策略来实现的容器会利用叠加式文件系统OverlayFS的特性在用户意图对镜像进行修改时自动将变更的内容写入到独立区域再与原有数据叠加到一起使其外观上看起来像是“覆盖”了原有内容。这种改动通常都是临时的一旦容器终止运行这些存储于独立区域中的变动信息也将被一并移除不复存在。所以可见如果不去进行额外的处理容器默认是不具备持久化存储能力的。
而另一方面容器作为信息系统的运行载体必定会产生出有价值的、应该被持久保存的信息比如扮演数据库角色的容器大概没有什么系统能够接受数据库像缓存服务一样重启之后会丢失全部数据多个容器之间也经常需要通过共享存储来实现某些交互操作比如我在第48讲中曾经举过的例子Nginx容器产生日志、Filebeat容器收集日志两者就需要共享同一块日志存储区域才能协同工作。
而正因为镜像的稳定性与生产数据持久性存在矛盾,所以我们才需要去重点了解这个问题:如何实现容器的持久化存储。
一课一思
不知你是否察觉这节课里还埋藏了一条暗线的逻辑以Kubernetes的存储为样例讨论当新的更好的解决方案出来之后系统对既有旧方案和旧功能的兼容。这是很多场景中都会遇到的问题系统设计必须考虑现实情况必须有所妥协很难单纯去追求理论上的最优解。越大规模的应用通常都带着更大的现实牵绊。如果你也有这样的经历不妨留言与我分享一下。
如果你觉得有收获,也欢迎把今天的内容分享给更多的朋友。感谢你的阅读,我们下一讲再见。