一文读懂Layer 2:Layer 2指基于底层区块链...
Posted 沉睡者IT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文读懂Layer 2:Layer 2指基于底层区块链...相关的知识,希望对你有一定的参考价值。
Layer 2指基于底层区块链(注:通常也称为“Layer 1网络”)的链下网络、系统或技术,目的是为了扩展底层区块链网络。Layer 2网络可以提升任何底层区块链的吞吐量以及其他性能。
Layer 2网络、系统或技术的核心价值是能够利用底层区块链的安全性。其交易数据必须以某种形式被底层区块链网络验证并确认。按照这个标准来看,侧链就不属于Layer 2,因为侧链通常会部署自己的共识机制和验证节点,因此拥有独立于底层区块链的安全机制。
一些区块链为了保障去中心化水平和安全性而牺牲了可扩展性,这类区块链可以利用Layer 2来提高交易吞吐量,并降低交易成本。Layer 2是解决可扩展性问题的方案之一,无需牺牲去中心化水平或安全性就能快速执行交易并实现可扩展性。
对Layer 2的需求
区块链技术诞生于2008年。自那以来,上千名研究者和开发者一直致力于解决区块链可扩展性的瓶颈,以满足不断增长的应用需求。这些瓶颈导致交易成本居高不下,执行速度慢,并成为了区块链技术主流化的绊脚石。
以太坊联合创始人Vitalik Buterin首次提出了“区块链不可能三角”的概念,认为区块链无法同时兼顾可扩展性、安全性和去中心化三个维度。开发者不得不在这三者之间进行取舍。如今的区块链网络最多只能同时满足其中两个维度。
Layer 2是一种新兴技术,其主张区块链之所以会在可扩展性方面存在限制,是因为区块链需要完成的任务过多。当前区块链有三个核心功能,即:执行交易、数据可用性以及达成共识。
-
执行交易——处理并完成交易。衡量指标是区块链每秒可以完成的计算次数(其中包含交易数量)。
-
数据可用性——网络中的节点和验证者需要储存交易、状态以及其他数据。衡量指标是标准存储单位,比如MB和GB等。
-
达成共识——节点和验证者需要针对网络状态和交易排序达成共识。衡量指标是去中心化水平和终局速度,或所有节点针对某一状态变更达成一致意见所需的时间。
注:本文将聚焦交易执行层。若想全面了解区块链的扩容方案,请阅读这篇关于区块链可扩展性的博客文章。
L2解决方案的运行机制
注:大多数Layer 2解决方案仍处于早期阶段,其中许多要素还有待验证。
Layer 2解决方案大致可以分为两个部分:一个是负责处理交易的网络;另一个是部署在底层区块链上的智能合约,负责解决任何分歧,并将Layer 2网络达成的共识传输到底层区块链进行验证。
Layer 2网络上可以快速执行交易并开展计算。不同网络会通过完全不同的方式提升交易吞吐量。但这些Layer 2网络有一个共同点,那就是在结算时都会向底层区块链提交某种可验证的加密证明,以证明状态变更的真实性。有些Layer 2会提前创建证明并提交至底层区块链,而另一些则是之后再创建追溯性的证明。
另外,不同Layer 2网络在底层区块链上的智能合约实现方式也有所不同,但智能合约的核心功能是一样的,即:
-
保存并释放资金,转账至Layer 2;
-
收到Layer 2提交的证明,进行验证,解决分歧,并最终确认交易。
为了更好地理解这两种机制,我们可以探讨目前的两种Layer 2实现方案,即:支付通道和rollups。
支付通道
两个或以上用户可以预先向通道中充值资金,然后在链下进行通证转账。
Alice和Bob分别在智能合约中锁定一笔资金,创建支付通道,并通过加密签名技术约定双方可以使用多少资金。比如,双方各锁定50美元,因此支付通道里的资金总额为100美元。那么他们很可能会协议每一方可以使用50美元。
一旦建立了支付通道,Alice和Bob都可以通过签名消息在链下进行交易,无需向底层区块链提交交易。Alice可以向Bob付款,Bob也可以向Alice付款,无需任何成本也不存在延时。在这个双向支付通道中,Alice和Bob的交易不会发送到底层区块链。只有当双方都决定关闭通道时,最终交易结果才会被发送至链上进行结算。
因此,最终Bob和Alice只需要在创建和关闭支付通道时支付两笔链上交易费即可。当支付通道开通时,几百万美元的转账可以在不到一秒的时间内完成,而且无需任何成本。这就是解决区块链扩容问题的经典案例。
支付通道可以实现零成本的高速交易。
Rollups
Optimistic rollups和zk-rollups可以在链下执行智能合约状态变更,并在链上进行验证,以此提升区块链的吞吐量并降低成本。Rollups可以通过以下三种方式实现扩容:
1.Rollups在链下执行交易,底层区块链只需计算轻量化的交易证明,验证网络活动并储存原始交易数据。
2.Rollups将交易数据打包,并提交至区块链,链上gas费可以分摊到各个交易中。
3.Rollups最少只需要一个诚实的验证节点,就可以向底层区块链证明交易的有效性,因此对验证节点的数量要求较低,而对节点硬件要求较高,同时不会对安全性有很大影响。
链下执行
rollups的一个关键特质就是在链下执行交易。这意味着Layer 2网络可以代替底层区块链处理与其他用户或智能合约的交易。由于验证节点数量较少且硬件更强大,这将大幅提升Layer 2网络相较于底层区块链的交易吞吐量。
底层区块链只需要计算提交至智能合约的证明,就可以验证Layer 2网络中的活动(注:如果是optimistic rollups的话,只有当分歧出现时才需要验证),并将未执行的原始交易数据作为calldata储存起来。简而言之,区块链不再需要执行那么多计算任务,也不需要储存那么多Layer 2上的交易数据,因此可以降低每笔交易的成本。
打包交易
另外一种降低成本的方式就是将交易打包。你可以把这想象成是送礼物。在区块链上,每笔交易都是一个单独的礼物,被放在不同的盒子里。这样一来运费就会非常高,因为每寄出一个礼物就需要支付一笔运费。而rollups则是将许多礼物放到一个大盒子里,只需付一次运费即可,每个礼物可以分摊运费。
如果要用更技术性的语言来解释,那就是rollups将原始交易数据打包成calldata。Rollup交易的验证方式与链上交易不同,交易数据被打包只是为了将数据储存在底层区块链上,验证节点或rollup参与者如果有需要的话随时可以重建Layer 2网络的状态。然而,核心逻辑仍然没有改变,即:主链上的一个打包交易中储存了多笔rollup交易。
这样可以有效降低交易成本,并为区块链扩容扫清障碍。而将数据压缩还可以进一步降低交易执行成本。
Zk-rollups和optimistic rollups使用同样的方式打包交易。
减少验证节点数量
正如上文所述,Layer 2可以利用底层区块链的安全保障和去中心化水平,我们下文中会详细阐述。但这里的核心概念是,Layer 2必须向底层区块链提交某种证明,以证明其发起的状态变更是有效的。
因此,rollups就可以减少Layer 2网络中负责执行交易的验证节点数量。验证节点可以是获得许可的实体,这些节点通常拥有更强大的硬件,以更快的速度和更低的成本来计算交易。之所以能做到这一点,是因为验证节点不负责达成共识,共识是由底层区块链达成的,底层区块链上的智能合约需要验证Layer 2提交的证明。
证明对于Layer 2的安全性至关重要
“证明”对于Layer 2来说至关重要,因为Layer 2需要通过提交证明来利用底层区块链的安全保障。在现实世界中,证明可以是多种形式的,比如武器上的指纹或者监控录像都可以是证明。
加密证明也差不多,只是内容比法庭上的证据更客观。利用加密技术来证明某事,可以保障其客观性、可验证性以及防篡改性。如果证明有效,那就可以百分之百保证真实性。比如上文提到的支付通道,对交易附上加密签名就可以为智能合约提供确定性的权威事实,解决任何分歧。
每个Layer 2网络都需要提供某种加密证明,以解决底层区块链上的分歧。如今最主流的两种证明方式是错误性证明(fault proof)以及有效性证明(validity proof,也称“零知识证明”)。这两种证明方式是optimistic rollups和zk-rollups的底层技术。
支付通道签名消息
假设在关闭支付通道时,Bob或Alice发起恶意攻击,并试图取走不属于自己的资金。他们中任何一个人都可以向智能合约发送一个之前版本的支付通道“账本”。
支付通道的关键是每笔交易都必须附上加密签名。这可以为底层区块链上的智能合约提供证明,以解决分歧。比如,如果Alice提交了过期的账本副本,拿走了不属于她的那部分钱,那么Bob就可以对结算交易发起挑战,并提交最新的账本副本。然后,智能合约就可以判定正确的金额。而且Alice还会因为不诚实的行为被罚款。
由于底层区块链智能合约会进行裁决,因此支付通道也是一种Layer 2方案。一个简单的评判方法就是看有没有“证明”。支付通道创建后,Bob和Alice都必须对他们发起的每一笔交易附上签名,并储存一份对方的签名。
这就是支付通道中交易的证明。但光有证明还不够,还需要执行事实,这很像法官在得到证据和陪审团审议结果后做出裁决的过程。同样地,智能合约也会执行判决结果,将正确的金额打到各方在底层区块链上的钱包中,以进行结算。
这就是上文中提到的“利用底层区块链的去中心化水平和安全保障”。支付通道将大部分计算和执行都放到链下处理,但在最后结算时,还是会利用底层区块链强大的共识层来作出最终裁决。
Optimistic Rollups的错误性证明
错误性证明其实很简单。采用这种证明方式的Layer 2网络会默认所有交易都是有效的。然而,网络会设置一个挑战期,任何参与者都可以发起挑战,并向智能合约提交证明,表明交易数据或状态变更是错误的。当错误性证明发布后,rollup交易会部分或全部在链上重新计算一遍,最终的状态变更会跟原来的结果进行比较。如果重新计算的结果不一样,原来的结果将被认为是无效的,并被撤回。
这就是目前optimistic rollups采用的机制。“optimistic”中文意思是“乐观的”,其代表了一种乐观的哲学视角,即:智能合约“乐观地”默认所有交易都是有效的,除非有人提出异议(疑罪从无)。回到刚刚说的那个不诚实验证节点的例子。任何网络参与者只需要在挑战期内向智能合约提交有效的错误性证明即可,一旦证明无误,验证节点的行为就会作废。
Optimistic rollups采用了错误性证明来保证终局的交易可以正确地反应Layer 2网络中的交易活动。
zk-rollup的有效性证明
有效性证明则是另一种截然不同的哲学视角,默认所有计算结果都是存疑的,必须先被证明才能接受。
简而言之,有效性证明的作用是证明某件事是正确的。对于zk-rollup来说,证明的就是Layer 2网络中的交易和计算。因此,底层区块链上的智能合约可以验证Layer 2网络提交的有效性证明,以批准状态变更。对于验证节点来说,功能完备的zk-rollup将不允许任何错误的交易在底层区块链上结算,因为每一批交易都必须附上相应的有效性证明。
有效性证明可以主动证明Layer 2网络交易的有效性
区块链扩容方案
支付通道、rollups和Layer 2都属于区块链扩容方案,都具有长远潜力,可以为Web3应用发展提供助力,并提升用户体验。
Layer 1和Layer 2等大多数Web3技术栈都才刚刚起步,还需要不断发展。这些技术目前还没有达到爆发点,仍需要经历一系列实战检验来证明自己。然而,无数开发者和研究者都在通过不懈努力扩展区块链生态并开发DAG和Layer 2方案,以推动Web3实现主流应用。
一文读懂容器存储接口 CSI
简介: 在《一文读懂 K8s 持久化存储流程》一文我们重点介绍了 K8s 内部的存储流程,以及 PV、PVC、StorageClass、Kubelet 等之间的调用关系。接下来本文将将重点放在 CSI(Container Storage Interface)容器存储接口上,探究什么是 CSI 及其内部工作原理。
头图.png
作者 | 惠志
来源 | 阿里巴巴云原生公众号
导读:在《一文读懂 K8s 持久化存储流程》一文我们重点介绍了 K8s 内部的存储流程,以及 PV、PVC、StorageClass、Kubelet 等之间的调用关系。接下来本文将将重点放在 CSI(Container Storage Interface)容器存储接口上,探究什么是 CSI 及其内部工作原理。
背景
K8s 原生支持一些存储类型的 PV,如 iSCSI、NFS、CephFS 等等(详见链接),这些 in-tree 类型的存储代码放在 Kubernetes 代码仓库中。这里带来的问题是 K8s 代码与三方存储厂商的代码强耦合:
更改 in-tree 类型的存储代码,用户必须更新 K8s 组件,成本较高
in-tree 存储代码中的 bug 会引发 K8s 组件不稳定
K8s 社区需要负责维护及测试 in-tree 类型的存储功能
in-tree 存储插件享有与 K8s 核心组件同等的特权,存在安全隐患
三方存储开发者必须遵循 K8s 社区的规则开发 in-tree 类型存储代码
CSI 容器存储接口标准的出现解决了上述问题,将三方存储代码与 K8s 代码解耦,使得三方存储厂商研发人员只需实现 CSI 接口(无需关注容器平台是 K8s 还是 Swarm 等)。
CSI 核心流程介绍
在详细介绍 CSI 组件及其接口之前,我们先对 K8s 中 CSI 存储流程进行一个介绍。《一文读懂 K8s 持久化存储流程》一文介绍了 K8s 中的 Pod 在挂载存储卷时需经历三个的阶段:Provision/Delete(创盘/删盘)、Attach/Detach(挂接/摘除)和 Mount/Unmount(挂载/卸载),下面以图文的方式讲解 K8s 在这三个阶段使用 CSI 的流程。
- Provisioning Volumes
1.jpg
1.集群管理员创建 StorageClass 资源,该 StorageClass 中包含 CSI 插件名称(provisioner:pangu.csi.alibabacloud.com)以及存储类必须的参数(parameters: type=cloud_ssd)。sc.yaml 文件如下:
2.png
2.用户创建 PersistentVolumeClaim 资源,PVC 指定存储大小及 StorageClass(如上)。pvc.yaml 文件如下:
3.png
3.卷控制器(PersistentVolumeController)观察到集群中新创建的 PVC 没有与之匹配的 PV,且其使用的存储类型为 out-of-tree,于是为 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名称](本例中即为 provisioner:pangu.csi.alibabacloud.com)。
4.External Provisioner 组件观察到 PVC 的 annotation 中包含 "volume.beta.kubernetes.io/storage-provisioner" 且其 value 是自己,于是开始创盘流程。
获取相关 StorageClass 资源并从中获取参数(本例中 parameters 为 type=cloud_ssd),用于后面 CSI 函数调用。
通过 unix domain socket 调用外部 CSI 插件的CreateVolume 函数。
5.外部 CSI 插件返回成功后表示盘创建完成,此时External Provisioner 组件会在集群创建一个 PersistentVolume 资源。
6.卷控制器会将 PV 与 PVC 进行绑定。
4.png
- Attaching Volumes
5.jpg
1.AD 控制器(AttachDetachController)观察到使用 CSI 类型 PV 的 Pod 被调度到某一节点,此时AD 控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Attach 函数。
2.内部 in-tree CSI 插件(csiAttacher)会创建一个 VolumeAttachment 对象到集群中。
3.External Attacher 观察到该 VolumeAttachment 对象,并调用外部 CSI插件的ControllerPublish 函数以将卷挂接到对应节点上。外部 CSI 插件挂载成功后,External Attacher会更新相关 VolumeAttachment 对象的 .Status.Attached 为 true。
6.png
4.AD 控制器内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象的 .Status.Attached 设置为 true,于是更新AD 控制器内部状态(ActualStateOfWorld),该状态会显示在 Node 资源的 .Status.VolumesAttached 上。
7.png
- Mounting Volumes
8.jpg
1.Volume Manager(Kubelet 组件)观察到有新的使用 CSI 类型 PV 的 Pod 调度到本节点上,于是调用内部 in-tree CSI 插件(csiAttacher)的 WaitForAttach 函数。
2.内部 in-tree CSI 插件(csiAttacher)等待集群中 VolumeAttachment 对象状态 .Status.Attached 变为 true。
3.in-tree CSI 插件(csiAttacher)调用 MountDevice 函数,该函数内部通过 unix domain socket 调用外部 CSI 插件的NodeStageVolume 函数;之后插件(csiAttacher)调用内部 in-tree CSI 插件(csiMountMgr)的 SetUp 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的NodePublishVolume 函数。
- Unmounting Volumes
9.jpg
1.用户删除相关 Pod。
2.Volume Manager(Kubelet 组件)观察到包含 CSI 存储卷的 Pod 被删除,于是调用内部 in-tree CSI 插件(csiMountMgr)的 TearDown 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数。
3.Volume Manager(Kubelet 组件)调用内部 in-tree CSI 插件(csiAttacher)的 UnmountDevice 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数。
- Detaching Volumes
10.jpg
1.AD 控制器观察到包含 CSI 存储卷的 Pod 被删除,此时该控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Detach 函数。
2.csiAttacher会删除集群中相关 VolumeAttachment 对象(但由于存在 finalizer,va 对象不会立即删除)。
3.External Attacher观察到集群中 VolumeAttachment 对象的 DeletionTimestamp 非空,于是调用外部 CSI 插件的ControllerUnpublish 函数以将卷从对应节点上摘除。外部 CSI 插件摘除成功后,External Attacher会移除相关 VolumeAttachment 对象的 finalizer 字段,此时 VolumeAttachment 对象被彻底删除。
4.AD 控制器中内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象已删除,于是更新AD 控制器中的内部状态;同时AD 控制器更新 Node 资源,此时 Node 资源的 .Status.VolumesAttached 上已没有相关挂接信息。
- Deleting Volumes
11.jpg
1.用户删除相关 PVC。
2.External Provisioner 组件观察到 PVC 删除事件,根据 PVC 的回收策略(Reclaim)执行不同操作:
Delete:调用外部 CSI 插件的DeleteVolume 函数以删除卷;一旦卷成功删除,Provisioner会删除集群中对应 PV 对象。
Retain:Provisioner不执行卷删除操作。
CSI Sidecar 组件介绍
为使 K8s 适配 CSI 标准,社区将与 K8s 相关的存储流程逻辑放在了 CSI Sidecar 组件中。
- Node Driver Registrar
1)功能
Node-Driver-Registrar 组件会将外部 CSI 插件注册到Kubelet,从而使Kubelet通过特定的 Unix Domain Socket 来调用外部 CSI 插件函数(Kubelet 会调用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函数)。
2)原理
Node-Driver-Registrar 组件通过Kubelet 外部插件注册机制实现注册,注册成功后:
Kubelet为本节点 Node 资源打 annotation:Kubelet调用外部 CSI 插件的NodeGetInfo 函数,其返回值 [nodeID]、[driverName] 将作为值用于 "csi.volume.kubernetes.io/nodeid" 键。
Kubelet更新 Node Label:将NodeGetInfo 函数返回的 [AccessibleTopology] 值用于节点的 Label。
Kubelet更新 Node Status:将NodeGetInfo 函数返回的 maxAttachLimit(节点最大可挂载卷数量)更新到 Node 资源的 Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]。
12.png
Kubelet更新 CSINode 资源(没有则创建):将 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓扑仅保留 Key 值)。
- External Provisioner
1)功能
创建/删除实际的存储卷,以及代表存储卷的 PV 资源。
2)原理
External-Provisioner在启动时需指定参数 -- provisioner,该参数指定 Provisioner 名称,与 StorageClass 中的 provisioner 字段对应。
External-Provisioner启动后会 watch 集群中的 PVC 和 PV 资源。
对于集群中的 PVC 资源:
判断 PVC 是否需要动态创建存储卷,标准如下:
PVC 的 annotation 中是否包含 "volume.beta.kubernetes.io/storage-provisioner" 键(由卷控制器创建),并且其值是否与 Provisioner 名称相等。
PVC 对应 StorageClass 的 VolumeBindingMode 字段若为 WaitForFirstConsumer,则 PVC 的 annotation 中必须包含 "volume.kubernetes.io/selected-node" 键(详见调度器如何处理 WaitForFirstConsumer),且其值不为空;若为 Immediate 则表示需要 Provisioner 立即提供动态存储卷。
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 CreateVolume 函数。
创建 PV 资源,PV 名称为 [Provisioner 指定的 PV 前缀] - [PVC uuid]。
对于集群中的 PV 资源:
判断 PV 是否需要删除,标准如下:
判断其 .Status.Phase 是否为 Release。
判断其 .Spec.PersistentVolumeReclaimPolicy 是否为 Delete。
判断其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否为自己。
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 DeleteVolume 接口。
删除集群中的 PV 资源。
- External Attacher
1)功能
挂接/摘除存储卷。
2)原理
External-Attacher 内部会时刻 watch 集群中的 VolumeAttachment 资源和 PersistentVolume 资源。
对于 VolumeAttachment 资源:
从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等。
判断 VolumeAttachment 的 DeletionTimestamp 字段是否为空来判断其为卷挂接或卷摘除:若为卷挂接则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerPublishVolume 接口;若为卷摘除则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerUnpublishVolume 接口。
对于 PersistentVolume 资源:
在挂接时为相关 PV 打上 Finalizer:external-attacher/[driver 名称]。
当 PV 处于删除状态时(DeletionTimestamp 非空),删除 Finalizer:external-attacher/[driver 名称]。
- External Resizer
1)功能
扩容存储卷。
2)原理
External-Resizer内部会 watch 集群中的 PersistentVolumeClaim 资源。
对于 PersistentVolumeClaim 资源:
判断 PersistentVolumeClaim 资源是否需要扩容:PVC 状态需要是 Bound 且 .Status.Capacity 与 .Spec.Resources.Requests 不等。
更新 PVC 的 .Status.Conditions,表明此时处于 Resizing 状态。
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 ControllerExpandVolume 接口。
更新 PV 的 .Spec.Capacity。
若 CSI 支持文件系统在线扩容,ControllerExpandVolume 接口返回值中 NodeExpansionRequired 字段为 true,External-Resizer更新 PVC 的 .Status.Conditions 为 FileSystemResizePending 状态;若不支持则扩容成功,External-Resizer更新 PVC 的 .Status.Conditions 为空,且更新 PVC 的 .Status.Capacity。
Volume Manager(Kubelet 组件)观察到存储卷需在线扩容,于是通过特定的 Unix Domain Socket 调用外部 CSI 插件的NodeExpandVolume 接口实现文件系统扩容。
- livenessprobe
1)功能
检查 CSI 插件是否正常。
2)原理
通过对外暴露一个 / healthz HTTP 端口以服务 kubelet 的探针探测器,内部是通过特定的 Unix Domain Socket 调用外部 CSI 插件的 Probe 接口。
CSI 接口介绍
三方存储厂商需实现 CSI 插件的三大接口:IdentityServer、ControllerServer、NodeServer。
- IdentityServer
IdentityServer 主要用于认证 CSI 插件的身份信息。
// IdentityServer is the server API for Identity service.
type IdentityServer interface {
// 获取CSI插件的信息,比如名称、版本号
GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
// 获取CSI插件提供的能力,比如是否提供ControllerService能力
GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)
// 获取CSI插件健康状况
Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)
}
- ControllerServer
ControllerServer 主要负责存储卷及快照的创建/删除以及挂接/摘除操作。
// ControllerServer is the server API for Controller service.
type ControllerServer interface {
// 创建存储卷
CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
// 删除存储卷
DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)
// 挂接存储卷到特定节点
ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)
// 从特定节点摘除存储卷
ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)
// 验证存储卷能力是否满足要求,比如是否支持跨节点多读多写
ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)
// 列举全部存储卷信息
ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
// 获取存储资源池可用空间大小
GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)
// 获取ControllerServer支持功能点,比如是否支持快照能力
ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error)
// 创建快照
CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)
// 删除快照
DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error)
// 获取所有快照信息
ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)
// 扩容存储卷
ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)
}
- NodeServer
NodeServer 主要负责存储卷挂载/卸载操作。
// NodeServer is the server API for Node service.
type NodeServer interface {
// 将存储卷格式化并挂载至临时全局目录
NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)
// 将存储卷从临时全局目录卸载
NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)
// 将存储卷从临时目录bind-mount到目标目录
NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
// 将存储卷从目标目录卸载
NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
// 获取存储卷的容量信息
NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)
// 存储卷扩容
NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)
// 获取NodeServer支持功能点,比如是否支持获取存储卷容量信息
NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
// 获取CSI节点信息,比如最大支持卷个数
NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)
}
K8s CSI API 对象
K8s 为支持 CSI 标准,包含如下 API 对象:
CSINode
CSIDriver
VolumeAttachment
CSINode
apiVersion: storage.k8s.io/v1beta1
kind: CSINode
metadata:
name: node-10.212.101.210
spec:
drivers:name: yodaplugin.csi.alibabacloud.com
nodeID: node-10.212.101.210
topologyKeys:- kubernetes.io/hostname
name: pangu.csi.alibabacloud.com
nodeID: a5441fd9013042ee8104a674e4a9666a
topologyKeys:- topology.pangu.csi.alibabacloud.com/zone
作用:
判断外部 CSI 插件是否注册成功。在 Node Driver Registrar 组件向 Kubelet 注册完毕后,Kubelet 会创建该资源,故不需要显式创建 CSINode 资源。
将 Kubernetes 中 Node 资源名称与三方存储系统中节点名称(nodeID)一一对应。此处Kubelet会调用外部 CSI 插件NodeServer 的 GetNodeInfo 函数获取 nodeID。
显示卷拓扑信息。CSINode 中 topologyKeys 用来表示存储节点的拓扑信息,卷拓扑信息会使得Scheduler在 Pod 调度时选择合适的存储节点。
CSIDriver
apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
name: pangu.csi.alibabacloud.com
spec:# 插件是否支持卷挂接(VolumeAttach)
attachRequired: true
# Mount阶段是否CSI插件需要Pod信息
podInfoOnMount: true
# 指定CSI支持的卷模式
volumeLifecycleModes:- Persistent
作用:
简化外部 CSI 插件的发现。由集群管理员创建,通过 kubectl get csidriver 即可得知环境上有哪些 CSI 插件。
自定 义Kubernetes 行为,如一些外部 CSI 插件不需要执行卷挂接(VolumeAttach)操作,则可以设置 .spec.attachRequired 为 false。
VolumeAttachment
apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
annotations:csi.alpha.kubernetes.io/node-id: 21481ae252a2457f9abcb86a3d02ba05
finalizers:
- external-attacher/pangu-csi-alibabacloud-com
name: csi-0996e5e9459e1ccc1b3a7aba07df4ef7301c8e283d99eabc1b69626b119ce750
spec:
attacher: pangu.csi.alibabacloud.com
nodeName: node-10.212.101.241
source:persistentVolumeName: pangu-39aa24e7-8877-11eb-b02f-021234350de1
status:
attached: true
作用:VolumeAttachment 记录了存储卷的挂接/摘除信息以及节点信息。- external-attacher/pangu-csi-alibabacloud-com
支持特性
- 拓扑支持
在 StorageClass 中有 AllowedTopologies 字段:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-pangu
provisioner: pangu.csi.alibabacloud.com
parameters:
type: cloud_ssd
volumeBindingMode: Immediate
allowedTopologies:
matchLabelExpressions:
key: topology.pangu.csi.alibabacloud.com/zone
values:- zone-1
- zone-2
外部 CSI 插件部署后会为每个节点打标,打标内容NodeGetInfo 函数返回的 [AccessibleTopology] 值(详见 Node Driver Registrar 部分)。
External Provisioner在调用 CSI 插件的 CreateVolume 接口之前,会在请求参数设置 AccessibilityRequirements:
对于 WaitForFirstConsumer
当 PVC 的 anno 中包含 "volume.kubernetes.io/selected-node" 且不为空,则先获取对应节点 CSINode 的 TopologyKeys,然后根据该 TopologyKeys 键从 Node 资源的 Label 获取 Values 值,最后拿该 Values 值与 StorageClass 的 AllowedTopologies 比对,判断其是否包含于其中;若不包含则报错。
对于 Immediately
将 StorageClass 的 AllowedTopologies 的值填进来,若 StorageClass 没有设置 AllowedTopologies 则将所有包含 TopologyKeys 键的节点 Value 添进来。
Scheduler 如何处理使用存储卷调度
基于社区 1.18 版本调度器
调度器的调度过程主要有如下三步:
预选(Filter):筛选满足 Pod 调度要求的节点列表。
优选(Score):通过内部的优选算法为节点打分,获得最高分数的节点即为选中的节点。
绑定(Bind):调度器将调度结果通知给 kube-apiserver,更新 Pod 的 .spec.nodeName 字段。
调度器预选阶段:处理 Pod 的 PVC/PV 绑定关系以及动态供应 PV(Dynamic Provisioning),同时使调度器调度时考虑 Pod 所使用 PV 的节点亲和性。详细调度过程如下:
Pod 不包含 PVC 直接跳过。
FindPodVolumes
获取 Pod 的 boundClaims、claimsToBind 以及 unboundClaimsImmediate。
boundClaims:已 Bound 的 PVC
claimsToBind:PVC 对应 StorageClass 的 VolumeBindingMode 为 VolumeBindingWaitForFirstConsumer
unboundClaimsImmediate:PVC 对应 StorageClass 的 VolumeBindingMode 为 VolumeBindingImmediate
若 len(unboundClaimsImmediate) 不为空,表示这种 PVC 需要立即绑定 PV(即存 PVC 创建后,立刻动态创建 PV 并将其绑定到 PVC,该过程不走调度),若 PVC 处于 unbound 阶段则报错。
若 len(boundClaims) 不为空,则检查 PVC 对应 PV 的节点亲和性与当前节点的 Label 是否冲突,若冲突则报错(可检查 Immediate 类型的 PV 拓扑)。
若 len(claimsToBind) 不为空
先检查环境中已有的 PV 能否与该 PVC 匹配(findMatchingVolumes),将能够匹配 PVC 的 PV 记录在调度器的 cache 中。
未匹配到 PV 的 PVC 走动态调度流程,动态调度主要通过 StorageClass 的 AllowedTopologies 字段判断当前调度节点是否满足拓扑要求(针对 WaitForFirstConsumer 类型的 PVC)。
调度器优选阶段不讨论。
调度器 Assume 阶段
调度器会先 Assume PV/PVC,再 Assume Pod。
将当前待调度的 Pod 进行深拷贝。
AssumePodVolumes(针对 WaitForFirstConsumer 类型的 PVC)
更改调度器 cache 中已经 Match 的 PV 信息:设置 annotation:pv.kubernetes.io/bound-by-controller="yes"。
更改调度器 cache 中未匹配到 PV 的 PVC,设置 annotation:volume.kubernetes.io/selected-node=【所选节点】。
Assume Pod 完毕
更改调度器 cache 中 Pod 的 .Spec.NodeName 为【所选节点】。
调度器 Bind 阶段
BindPodVolumes:
调用 Kubernetes 的 API 更新集群中 PV/PVC 资源,使其与调度器 Cache 中的 PV/PVC 一致。
检查 PV/PVC 状态:
检查所有 PVC 是否已处于 Bound 状态。
检查所有 PV 的 NodeAffinity 是否与节点 Label 冲突。
调度器执行 Bind 操作:调用 Kubernetes 的 API 更新 Pod 的 .Spec.NodeName 字段。
- 存储卷扩容
存储卷扩容部分在 External Resizer 部分已提到,故不再赘述。用户只需要编辑 PVC 的 .Spec.Resources.Requests.Storage 字段即可,注意只可扩容不可缩容。
若 PV 扩容失败,此时 PVC 无法重新编辑 spec 字段的 storage 为原来的值(只可扩容不可缩容)。参考 K8s 官网提供的 PVC 还原方法:
https://kubernetes.io/docs/co...
- 单节点卷数量限制
卷数量限制在 Node Driver Registrar 部分已提到,故不再赘述。 - 存储卷监控
存储商需实现 CSI 插件的 NodeGetVolumeStats 接口,Kubelet 会调用该函数,并反映在其 metrics上:
kubelet_volume_stats_capacity_bytes:存储卷容量
kubelet_volume_stats_used_bytes:存储卷已使用容量
kubelet_volume_stats_available_bytes:存储卷可使用容量
kubelet_volume_stats_inodes:存储卷 inode 总量
kubelet_volume_stats_inodes_used:存储卷 inode 使用量
kubelet_volume_stats_inodes_free:存储卷 inode 剩余量
- Secret
CSI 存储卷支持传入 Secret 来处理不同流程中所需要的私密数据,目前 StorageClass 支持如下 Parameter:
csi.storage.k8s.io/provisioner-secret-name
csi.storage.k8s.io/provisioner-secret-namespace
csi.storage.k8s.io/controller-publish-secret-name
csi.storage.k8s.io/controller-publish-secret-namespace
csi.storage.k8s.io/node-stage-secret-name
csi.storage.k8s.io/node-stage-secret-namespace
csi.storage.k8s.io/node-publish-secret-name
csi.storage.k8s.io/node-publish-secret-namespace
csi.storage.k8s.io/controller-expand-secret-name
csi.storage.k8s.io/controller-expand-secret-namespace
Secret 会包含在对应 CSI 接口的参数中,如对于 CreateVolume 接口而言则包含在 CreateVolumeRequest.Secrets 中。
块设备
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-example
spec:
selector:matchLabels: app: nginx
serviceName: "nginx"
volumeClaimTemplates:metadata:
name: html
spec:
accessModes: - ReadWriteOnce volumeMode: Block storageClassName: csi-pangu resources: requests: storage: 40Gi
template:
metadata:labels: app: nginx
spec:
containers: - name: nginx image: nginx volumeDevices: - devicePath: "/dev/vdb" name: html
三方存储厂商需实现 NodePublishVolume 接口。Kubernetes 提供了针对块设备的工具包("k8s.io/kubernetes/pkg/util/mount"),在 NodePublishVolume 阶段可调用该工具的 EnsureBlock 和 MountBlock 函数。
- 卷快照/卷克隆能力
鉴于本文篇幅,此处不做过多原理性介绍。读者感兴趣见官方介绍:卷快照、卷克隆。
总结
本文首先对 CSI 核心流程进行了大体介绍,并结合 CSI Sidecar 组件、CSI 接口、API 对象对 CSI 标准进行了深度解析。在 K8s 上,使用任何一种 CSI 存储卷都离不开上面的流程,环境上的容器存储问题也一定是其中某个环节出现了问题。本文对其流程进行梳理,以便于广大程序猿(媛)排查环境问题。
原文链接
本文为阿里云原创内容,未经允许不得转载。
以上是关于一文读懂Layer 2:Layer 2指基于底层区块链...的主要内容,如果未能解决你的问题,请参考以下文章