4. gRPC 基础:通信模型、Protocol Buffers 语法与 Service 定义
gRPC 是 CSI(Container Storage Interface)驱动与 Kubernetes 组件(如 kubelet、external-provisioner)之间的核心通信协议。理解 gRPC 的通信模型、接口定义语言(IDL)以及代码生成流程,是开发自定义存储接入层的前提。本章将围绕这三个关键点展开,为后续实现 CSI Identity、Controller、Node 三大 gRPC 服务奠定基础。
4.1 gRPC 通信模型
gRPC 基于 HTTP/2 协议,使用 Protocol Buffers(protobuf)作为序列化与接口定义工具。与传统的 REST/JSON 相比,gRPC 在 Kubernetes 生态中具有以下优势:强类型约束、双向流支持、低延迟与高吞吐。
4.1.1 四种通信模式
| 模式 | 描述 | CSI 典型场景 |
|---|---|---|
| Unary RPC | 客户端发送单个请求,服务端返回单个响应 | CreateVolume、DeleteVolume、ControllerPublishVolume |
| Server Streaming RPC | 客户端发送单个请求,服务端返回多个消息流 | ListVolumes(分页返回)、ControllerGetCapabilities |
| Client Streaming RPC | 客户端发送多个消息流,服务端返回单个响应 | 批量快照创建请求(较少使用) |
| Bidirectional Streaming RPC | 双方独立发送消息流,实时交互 | NodeExpandVolume 过程中的进度上报(扩展场景) |
4.1.2 gRPC 通信流程(内联 SVG)
下图展示了 gRPC Unary RPC 的完整调用链路,从客户端发起请求到服务端返回响应的核心步骤:
4.2 Protocol Buffers 语法
Protocol Buffers(protobuf)是 gRPC 的接口定义语言(IDL)。CSI 规范使用 proto3 语法定义所有消息结构和 RPC 方法。掌握 proto3 的核心语法是阅读和扩展 CSI 规范的基础。
4.2.1 消息结构定义
以下是一个简化的 CSI CreateVolumeRequest 消息定义示例:
syntax = "proto3";
package csi.v1;
option go_package = "github.com/container-storage-interface/spec/lib/go/csi";
message CreateVolumeRequest {
// 卷名称,必填
string name = 1;
// 容量范围,必填
CapacityRange capacity_range = 2;
// 卷能力,可选
repeated VolumeCapability volume_capabilities = 3;
// 参数键值对,可选
map<string, string> parameters = 4;
// 密封内容,用于传递敏感信息
SealedSecrets secrets = 5;
}
message CapacityRange {
int64 required_bytes = 1;
int64 limit_bytes = 2;
}
4.2.2 字段类型与编号规则
| 类型 | proto 关键字 | Go 映射类型 | 说明 |
|---|---|---|---|
| 标量类型 | int32 / int64 / string / bool |
int32 / int64 / string / bool |
可变长编码,小正数效率高 |
| 枚举 | enum |
int32 常量 |
必须从 0 开始,0 为默认值 |
| 嵌套消息 | message |
struct |
支持多层嵌套 |
| 重复字段 | repeated |
slice |
相当于数组,可包含 0 个元素 |
| 映射 | map<K,V> |
map[K]V |
键类型必须是标量或字符串 |
| 可选字段 | optional |
*T 或 wrapper |
proto3 中默认所有字段都是可选的 |
name、volume_id)通常分配 1-15 的编号以提升性能。一旦发布,字段编号不可修改,否则会导致兼容性问题。
4.2.3 枚举与 oneof
CSI 规范中大量使用枚举和 oneof 来表达状态和互斥选项:
enum VolumeCapability.AccessMode.Mode {
UNKNOWN = 0;
SINGLE_NODE_WRITER = 1;
SINGLE_NODE_READER_ONLY = 2;
MULTI_NODE_READER_ONLY = 3;
MULTI_NODE_SINGLE_WRITER = 4;
MULTI_NODE_MULTI_WRITER = 5;
}
message VolumeContentSource {
oneof type {
VolumeContentSource.SnapshotSource snapshot = 1;
VolumeContentSource.VolumeSource volume = 2;
}
}
4.3 Service 定义与代码生成
CSI 规范将存储操作抽象为三个 gRPC Service:Identity、Controller、Node。每个 Service 包含一组相关的 RPC 方法。开发者通过 protoc 编译器从 .proto 文件生成 Go 代码,然后实现生成的接口。
4.3.1 CSI 三大 Service 定义
// Identity Service:驱动身份与能力声明
service Identity {
rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse) {}
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest) returns (GetPluginCapabilitiesResponse) {}
rpc Probe(ProbeRequest) returns (ProbeResponse) {}
}
// Controller Service:控制面卷管理
service Controller {
rpc CreateVolume(CreateVolumeRequest) returns (CreateVolumeResponse) {}
rpc DeleteVolume(DeleteVolumeRequest) returns (DeleteVolumeResponse) {}
rpc ControllerPublishVolume(ControllerPublishVolumeRequest) returns (ControllerPublishVolumeResponse) {}
rpc ControllerUnpublishVolume(ControllerUnpublishVolumeRequest) returns (ControllerUnpublishVolumeResponse) {}
rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse) {}
rpc GetCapacity(GetCapacityRequest) returns (GetCapacityResponse) {}
// ... 其他可选 RPC
}
// Node Service:节点面挂载操作
service Node {
rpc NodeStageVolume(NodeStageVolumeRequest) returns (NodeStageVolumeResponse) {}
rpc NodeUnstageVolume(NodeUnstageVolumeRequest) returns (NodeUnstageVolumeResponse) {}
rpc NodePublishVolume(NodePublishVolumeRequest) returns (NodePublishVolumeResponse) {}
rpc NodeUnpublishVolume(NodeUnpublishVolumeRequest) returns (NodeUnpublishVolumeResponse) {}
rpc NodeGetVolumeStats(NodeGetVolumeStatsRequest) returns (NodeGetVolumeStatsResponse) {}
rpc NodeGetCapabilities(NodeGetCapabilitiesRequest) returns (NodeGetCapabilitiesResponse) {}
}
4.3.2 代码生成流程(内联 SVG)
从 .proto 文件到可运行的 gRPC 服务端代码,需要经过以下步骤:
4.3.3 编译命令与 Go 代码示例
使用以下命令从 CSI 规范生成 Go 代码:
# 安装 protoc 和 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 生成代码
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
csi.proto
生成后,开发者实现服务端接口的典型模式:
package driver
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi"
)
type myControllerServer struct {
csi.UnimplementedControllerServer // 嵌入未实现桩,确保向后兼容
}
func (s *myControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
// 1. 验证请求参数
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "volume name is required")
}
// 2. 调用存储后端 API 创建卷
volumeID, err := backend.CreateVolume(req.GetName(), req.GetCapacityRange())
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create volume: %v", err)
}
// 3. 构造响应
return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: volumeID,
CapacityBytes: req.GetCapacityRange().GetRequiredBytes(),
},
}, nil
}
Unimplemented*Server 结构体。当 CSI 规范新增 RPC 方法时,你的驱动不会因为未实现新方法而编译失败,而是返回 Unimplemented 状态码,符合 gRPC 的优雅降级原则。
4.4 本章小结
- gRPC 通信模型:CSI 驱动主要使用 Unary RPC,流式模式用于分页和扩展场景。HTTP/2 的多路复用和头部压缩为存储控制面提供了低延迟通信基础。
- Protocol Buffers 语法:proto3 的消息结构、字段编号、枚举和 oneof 是定义 CSI 接口的核心工具。字段编号 1-15 应留给高频字段以优化性能。
- Service 定义与代码生成:CSI 规范定义了 Identity、Controller、Node 三个 Service。通过
protoc生成 Go 桩代码后,开发者只需实现对应的 Server 接口,并嵌入Unimplemented*Server确保兼容性。