Agent Substrate CSI 外部卷实战指南从 CSIDriverConfig 到 ActorTemplate 的完整接入【免费下载链接】substrateAgent Substrate: the core system项目地址: https://gitcode.com/GitHub_Trending/substrate7/substrateAgent Substrate 通过Container Storage InterfaceCSI为 Actor 提供动态供给、按 Actor 生命周期自动挂载/卸载的外部卷。与标准 Kubernetes 的 PV/PVC 异步协调模型不同Substrate 由控制平面ateapi直接通过 gRPC 调用 CSI Controller节点守护进程atelet直接驱动 CSI Node 插件将卷操作与 Actor 的生命周期创建、恢复、销毁强耦合。读完本文你将掌握CSIDriverConfig的资源定义与 TLS/mTLS 配置、ActorTemplate中externalVolumeTemplate的声明方式以及从 StorageClass 到 WorkerPool 的端到端接入流程并了解底层实现原理。1. CSI in Substrate vs. 标准 Kubernetes在标准 Kubernetes 中卷的协调是异步的用户创建PersistentVolumeClaimcsi-provisioner、csi-attacher等 sidecar 监听 etcd 事件并调用本地 Unix socket 上的 CSI 驱动。Agent Substrate 针对 Actor 生命周期操作做了不同的设计没有 PV / PVC 对象外部卷通过ActorTemplate中的externalVolumeTemplate声明式定义并为每个 Actor 实例动态供给。卷操作与 Actor 生命周期直接耦合——Actor 创建时供给并挂载Actor 销毁时卸载并删除。控制平面直连 CSI ControllerSubstrate 控制平面ateapi通过网络TCP 或 DNS 端点可选 TLS/mTLS 加密直接与 CSI Controller 的 gRPC 服务通信绕过了 Kubernetes 侧车容器这一层。从源码结构看这套设计体现在两处核心抽象上internal/volume/csi/client.go 中NewCSIClient建立 gRPC 连接同时初始化 CSI 的 Identity、Controller、Node 三个 service client是控制平面与节点平面共用同一个连接工厂internal/volume/csi/plugin.go 中Plugin同时实现volume.VolumePluginControlPlane与volume.VolumePluginWorkerPlane两个接口把CreateVolume/AttachVolume等控制面操作与MountVolume/UnmountVolume等节点面操作统一封装。2. 动态 CSI 驱动发现CSIDriverConfigSubstrate 通过集群范围的CSIDriverConfig自定义资源CRD来发现并连接 CSI 驱动。它把ActorTemplate中引用的 KubernetesStorageClass桥接到 CSI Controller 服务的网络端点以及 CSI Node 插件的本地 socket 路径。2.1 CSIDriverConfig 资源示例apiVersion: ate.dev/v1alpha1 kind: CSIDriverConfig metadata: name: nfs.csi.k8s.io spec: driverName: nfs.csi.k8s.io controllerEndpoint: tcp://csi-nfs-controller.kube-system.svc.cluster.local:50052 nodeSocketOverride: unix:///var/lib/kubelet/plugins/csi-nfsplugin/csi.sock tls: enabled: true usePodIdentity: true serverName: csi-nfs-controller.kube-system.svc.cluster.local2.2 规格字段CSIDriverConfigSpec字段类型说明driverNamestring必填。标准 CSI 驱动名如nfs.csi.k8s.io、hostpath.csi.k8s.io、pd.csi.storage.gke.io必须与所引用 KubernetesStorageClass的provisioner字段一致。controllerEndpointstring必填。CSI Controller 服务的 gRPC 端点必须是合法 URI以tcp://、dns:///或unix://开头如tcp://csi-controller.kube-system.svc:50051或dns:///csi-svc.default.svc:9000。nodeSocketOverridestring可选。覆盖工作节点上 CSI Node 服务的 Unix 域 socket 路径必须以unix://开头。省略时默认使用unix:///var/lib/kubelet/plugins/driverName/csi.sock。tls*CSIDriverTLSConfig可选。配置到controllerEndpoint的 gRPC 连接的 TLS 或 mTLS。这些约束在 CRD 类型定义中有明确声明pkg/api/v1alpha1/csidriverconfig_types.go 中driverName被限制为 163 个字符的 DNS 样式名称controllerEndpoint必须匹配^(tcp|dns)://.$nodeSocketOverride必须匹配^unix://.$。TLS / mTLS 配置spec.tls字段类型说明enabledbool必填。是否对该 gRPC 连接启用 TLS/mTLS。usePodIdentitybool可选。为true时复用 Substrate 的 SPIFFE Pod Identity 证书进行双向 TLSmTLS并支持动态 CA 信任束校验与轮换。当enabled为true时必须为true。serverNamestring可选。TLS 证书校验时的服务器名称覆盖。[!NOTE] 关于如何在网络上暴露 CSI Controller 端点、以及如何为 CSI Node DaemonSet 配置所需的挂载传播请参阅 CSI 驱动部署指南。2.3 端点的解析与 TLS 的动态轮换源码级解读internal/volume/csi/client.go 中的parseEndpoint只接受unix、tcp、dns三种 schemeunix要求带路径、tcp要求带 host:port、dns原样透传给 gRPC 的 DNS 解析器其他 scheme 一律报错unsupported scheme。TLS 连接的控制面逻辑在 internal/volume/csi/plugin.go 的resolveTLSConfig中实现有几个值得注意的实现细节最低 TLS 1.3ALPN 强制 h2MinVersion: tls.VersionTLS13NextProtos: []string{h2}保证 gRPC over TLS 的 HTTP/2 协商客户端证书动态加载GetClientCertificate: credbundle.ClientLoader(paths.clientCert)其中paths.clientCert默认指向/run/podidentity.podcert.ate.dev/credential-bundle.pem常量DefaultClientCertPath。Pod Identity 证书由 kubelet经 Substrate 的podcertcontroller以文件形式投射进 Pod 并负责轮换credbundle.ClientLoader监控文件变化并在后续握手时自动重载CA 信任束动态刷新代码没有使用静态的tls.Config.RootCAs那会在进程启动时固定而是设置InsecureSkipVerify: true再通过VerifyConnection回调在每次握手时动态校验服务端证书链配套的caPoolCachenewCAPoolCache只在文件发生变更inode、mtime 或 size 变化时才重新读取解析 CA 信任束避免每个握手都读盘服务器证书验证以tlsCfg.ServerName作为x509.VerifyOptions.DNSName校验叶子证书的ExtKeyUsageServerAuth用途中间证书取自state.PeerCertificates[1:]。同时CRD 层面的 CEL 校验规则保证了安全约束tls.usePodIdentity must be true when tls.enabled is true; manual certificates are not yet supported见 csidriverconfig_types.go 的XValidation注解。如果配置违规CSIDriverConfig资源会被 CRD 校验拒绝ateapi也会在运行时返回错误。3. ActorTemplate声明 CSI 卷外部卷声明在ActorTemplate资源上。关于 ActorTemplate 的完整说明参见 API 指南中的 ActorTemplate: The Workload Blueprint。3.1 卷配置字段要为一个 Actor 挂载 CSI 卷需要两步在volumes下用externalVolumeTemplate定义卷在containers[].volumeMounts中把卷挂载进一个或多个容器。volumes[]volumes: - name: my-data-volume externalVolumeTemplate: capacity: 10Gi storageClassName: standard-rwxname符合 DNS-label 规范的唯一卷名。externalVolumeTemplate.capacity请求卷大小的 Quantity 字符串如1Gi、50Gi。externalVolumeTemplate.storageClassName集群中存在的 KubernetesStorageClass名称其provisioner必须匹配某个已注册的CSIDriverConfig。在 proto 定义中ExternalVolumeTemplate只有capacity一个字段见 pkg/proto/ateapipb/ateapi.proto 中的message ExternalVolumeTemplate注释明确说明它是为每个 Actor 供给的外部卷模板容量使用 Kubernetes resource.Quantity 字符串。containers[].volumeMounts[]volumeMounts: - name: my-data-volume mountPath: /var/dataname必须匹配已声明的volumes[].name。mountPath卷在容器沙箱内挂载的 Unix 路径。[!NOTE]volumes中声明的所有卷都必须被至少一个容器挂载。3.2 卷生命周期与控制平面状态机源码级解读控制平面的卷管理逻辑集中在 cmd/ateapi/internal/controlapi/volumes.go供给前的校验与初始化initialActorVolumes遍历模板中的externalVolumeTemplate通过 StorageClass lister 校验storageClassName是否存在并把卷初始化为STATUS_PENDING状态VolumeType记录为该 StorageClass 的provisioner创建createActorVolumes按状态推进——PENDING才真正创建CREATED直接跳过DELETING则报FailedPrecondition创建时再次校验StorageClass.provisioner与卷类型一致然后以actorVolumeID(actorUID, volName)格式为substrate-actorUID-volumeName作为 CSI 卷名调用plugin.CreateVolume成功后卷状态变为CREATED并记录StorageVolumeId与VolumeContext删除deleteActorVolumes优先使用已记录的StorageVolumeId若卷从未成功创建无 ID则回退到请求时生成的actorVolumeID遇到NotFound视为已删除并继续卸载/分离detachActorVolumes通过 Actor 的workerAssignment定位节点模板存在时只分离模板中仍被挂载的卷模板缺失时则回退到分离 Actor 上记录的全部外部卷避免在节点上遗留挂载的磁盘。CSI 调用层面internal/volume/csi/plugin.go 中CreateVolume用resource.ParseQuantity解析容量字符串得到字节数写入CapacityRange.RequiredBytes携带getStandardCapabilities()当前硬编码为SINGLE_NODE_WRITER的 Mount 型能力源码注释提到多访问模式支持是待办项AttachVolume/DetachVolume映射 CSI 的ControllerPublishVolume/ControllerUnpublishVolume对codes.Unimplemented的驱动仅告警跳过源码注释说明这是为了避免调用未实现方法产生刷屏日志MountVolume先os.MkdirAll创建 staging 目录前缀来自ateompath.StagingDirPrefix()并调用NodeStageVolume驱动不支持则跳过 staging再调用NodePublishVolume完成挂载UnmountVolume反向执行NodeUnpublishVolume与NodeUnstageVolume并清理 staging 目录插件建立后还会通过GetPluginInfo校验驱动自报名称与请求的driverName一致否则拒绝使用。4. 端到端示例NFS CSI 卷接入以下示例演示为 Substrate 配置 NFS CSI 驱动并部署一个挂载外部 NFS 卷的ActorTemplate。Step 1: 创建 StorageClassapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: csi-nfs-sc provisioner: nfs.csi.k8s.io parameters: server: nfs-server.default.svc.cluster.local share: / reclaimPolicy: Delete volumeBindingMode: Immediate mountOptions: - nfsvers4.1provisioner: nfs.csi.k8s.io必须与后续CSIDriverConfig.spec.driverName保持一致这是控制平面把 StorageClass 与驱动配置关联起来的桥梁。Step 2: 注册 CSIDriverConfigapiVersion: ate.dev/v1alpha1 kind: CSIDriverConfig metadata: name: nfs.csi.k8s.io spec: driverName: nfs.csi.k8s.io controllerEndpoint: tcp://csi-nfs-controller.kube-system.svc.cluster.local:50052 nodeSocketOverride: unix:///var/lib/kubelet/plugins/csi-nfsplugin/csi.sock tls: enabled: true usePodIdentity: true serverName: csi-nfs-controller.kube-system.svc.cluster.localStep 3: 定义 WorkerPool 与 ActorTemplateWorkerPool是 Kubernetes 资源直接使用kubectl apply提交apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: agent-pool namespace: ate-demo labels: workload: stateful-agent spec: replicas: 5 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisorActorTemplate是 protojson 形态的ateapipb.ActorTemplate通过 ate API 创建使用kubectl ate create actor-template -f -ate-demoatespace 必须已存在metadata: atespace: ate-demo name: stateful-agent-template workerSelector: matchLabels: workload: stateful-agent containers: - name: agent image: gcr.io/my-project/agent-appsha256:7f28ab0... volumeMounts: - name: shared-storage mountPath: /mnt/shared readyz: httpGet: path: /readyz port: 8080 sandboxConfig: sandboxClass: SANDBOX_CLASS_GVISOR configName: gvisor-default snapshotsConfig: storageLocation: gs://my-snapshots-bucket/stateful-agent volumes: - name: shared-storage externalVolumeTemplate: capacity: 5Gi storageClassName: csi-nfs-sc整个流程中卷的生命周期完全由 Actor 生命周期驱动Actor 启动时ateapi调用CreateVolume/ControllerPublishVolume完成供给与附着恢复resume时节点上的atelet经 CSI Node 插件执行NodeStageVolume/NodePublishVolume把卷挂进ateom-gvisor沙箱Actor 终止时反向执行卸载与删除。5. 配套部署要点结合 CSI 部署指南要让上面的端到端示例真正跑通CSI 驱动的部署形态也与标准 Kubernetes 不同完整方案见 CSI 驱动部署指南核心差异与要求如下组件标准 Kubernetes CSI 部署Agent Substrate CSI 部署CSI Controller 通信Controller 仅监听 Pod 内 Unix socket/csi/csi.socksidecarcsi-provisioner、csi-attacherwatch etcd 后本地调用驱动控制平面ateapi通过集群网络 gRPC 直连 CSI ControllerController 必须经 KubernetesService以 TCP 暴露Controller 认证无仅 Pod 内 socket可选但推荐基于 Substrate SPIFFE Pod Identity 证书的 mTLS支持动态 CA 轮换CSI Node 通信kubelet 与节点插件 Unix socket 通信节点守护进程atelet直连 CSI Node 插件 Unix socket经CSIDriverConfig发现节点挂载传播kubelet 管理/var/lib/kubelet/pods下的挂载CSI Node 插件必须以mountPropagation: Bidirectional挂载 Substrate 宿主机目标目录如/var/lib/ateom-gvisor使atelet能将其 bind-mount 进 Actor 沙箱5.1 暴露 Controller 的两种方式Envoy TLS 代理 sidecar推荐在 CSI Controller Deployment/StatefulSet 中加一个 Envoy 反向代理 sidecar终结来自ateapi的 TLS/mTLS gRPC 连接并通过 HTTP/2 转发到本地 Unix socket/csi/csi.sock原生 TCP 端点若 CSI 驱动二进制原生支持网络监听如--endpointtcp://0.0.0.0:port直接让驱动容器绑定网络端口。随后创建指向 Controller Pod 的 KubernetesService提供稳定 DNS 名与副本间负载均衡。5.2 用 mTLS 与 Pod Identity 加固在CSIDriverConfig.spec.tls中enabled: true开启ateapi与 CSI Controller 服务间的 TLS/mTLS为false或省略则回退为明文 gRPCusePodIdentity: true让ateapi使用 Substrate SPIFFE Pod Identity 客户端证书/run/podidentity.podcert.ate.dev/credential-bundle.pem认证并用动态 Service DNS CA 信任束/run/servicedns.podcert.ate.dev/trust-bundle.pem校验 Controller 的服务端证书。[!IMPORTANT] 当spec.tls.enabled为true时Substrate 目前强制要求usePodIdentity: true。若设置为false或省略CSIDriverConfig会被 CRD 校验拒绝错误信息为tls.usePodIdentity must be true when tls.enabled is true; manual certificates are not yet supportedateapi也会在运行时返回错误。5.3 限制 Controller 通信仅对 ateapi 开放由于 CSI Controller 执行创建/删除卷等特权存储操作网络访问应限制给控制平面ateapi。推荐在 Envoy 代理 sidecar 中开启客户端证书校验require_client_certificate: true校验上下文使用 Substrate Pod Identity CAsignerName: podidentity.podcert.ate.dev/identity并在match_typed_subject_alt_names中指定ateapi的精确 SPIFFE SANmatch_typed_subject_alt_names: - san_type: URI matcher: exact: spiffe://cluster.local/ns/ate-system/sa/ate-api-serverEnvoy 会拒绝任何未持有ateapi签发证书的客户端连接。5.4 Node DaemonSet 的挂载传播要求CSI Node 插件以 DaemonSet 形式运行在每个工作节点上Actor 恢复时负责NodeStageVolume与NodePublishVolume。当 CSI Node 插件在容器内挂载外部卷NFS 共享或格式化块设备后宿主侧的atelet与 worker 沙箱ateom-gvisor必须能看到该挂载因此要求mountPropagation: BidirectionalCSI Node 插件容器中挂载宿主 Substrate 目标目录如/var/lib/ateom-gvisor的卷必须设置双向挂载传播使插件在容器内创建的挂载能传播回宿主文件系统Unix socket 可达CSI Node 插件必须把 Unix socket 放在/var/lib/kubelet/plugins/driverName/或CSIDriverConfig.spec.nodeSocketOverride指定的路径下与atelet共享。csi-nfs驱动的完整示例部署清单位于仓库的 hack/third_party/csi-driver-nfs/deploy 目录csi-nfs-node.yaml、csi-nfs-controller.yaml、csi-nfs-driverinfo.yaml、rbac-csi-nfs.yaml一键部署脚本见 hack/setup-csi-nfs-kind.shkind 环境下的 NFS 服务端模板见 hack/csi/nfs-server.yaml。此外仓库的端到端测试 internal/e2e/suites/combinedvolumes/combinedvolumes_test.go 也覆盖了外部卷与容器内卷的组合场景可作为验证接入正确性的参考。小结Agent Substrate 的 CSI 集成把外部持久化存储变成 Actor 生命周期的一部分用CSIDriverConfig完成驱动发现与安全连接支持基于 Pod Identity 的动态轮换 mTLS用ActorTemplate.externalVolumeTemplate声明按 Actor 供给的卷由ateapi直连 CSI Controller 完成供给与附着、atelet驱动 CSI Node 插件完成挂载。只要按本文步骤配置 StorageClass、注册CSIDriverConfig、暴露并加固 Controller 端点、为 Node DaemonSet 设置双向挂载传播即可在 gVisor/MicroVM 沙箱中为有状态 Agent 接入 NFS、云盘等各类 CSI 存储。【免费下载链接】substrateAgent Substrate: the core system项目地址: https://gitcode.com/GitHub_Trending/substrate7/substrate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
