3. 多租户模型设计:租户定义、生命周期与资源隔离策略
多租户是云原生平台的核心能力之一。在 Operator 控制面中,租户不仅仅是逻辑上的组织单位,更是资源配额、网络策略、安全边界和幂等控制策略的载体。本章将围绕 Tenant CRD 的定义、租户从创建到销毁的完整生命周期管理、命名空间策略的自动编排,以及资源配额与限制范围的设计展开,构建一套可复用的多租户隔离模型。
3.1 租户定义:Tenant CRD 设计
Tenant CRD 是整个多租户模型的根资源。每个 Tenant 实例代表一个独立的租户,Operator 根据 Tenant 的 spec 自动生成对应的命名空间、RBAC 规则、网络策略和资源配额。以下是一个精简但完整的 Tenant CRD 定义示例:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: tenants.tenant.example.com
spec:
group: tenant.example.com
names:
kind: Tenant
listKind: TenantList
plural: tenants
singular: tenant
shortNames:
- tn
scope: Cluster
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- displayName
- namespacePrefix
properties:
displayName:
type: string
maxLength: 63
namespacePrefix:
type: string
pattern: '^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$'
maxLength: 20
adminUsers:
type: array
items:
type: string
resourceQuota:
type: object
properties:
hard:
type: object
additionalProperties:
type: string
limitRange:
type: object
properties:
limits:
type: array
items:
type: object
networkPolicy:
type: object
properties:
ingressIsolation:
type: boolean
default: true
egressIsolation:
type: boolean
default: false
labels:
type: object
additionalProperties:
type: string
status:
type: object
properties:
phase:
type: string
enum:
- Pending
- Active
- Degraded
- Terminating
namespaces:
type: array
items:
type: string
observedGeneration:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
reason:
type: string
message:
type: string
lastTransitionTime:
type: string
format: date-time
Cluster 作用域,因为租户需要跨命名空间管理资源。namespacePrefix 字段用于自动生成命名空间名称(如 tn-prod-001),确保命名空间可预测且不冲突。observedGeneration 用于幂等控制,避免重复处理。
3.2 租户生命周期管理
租户的生命周期分为四个阶段:创建(Pending → Active)、运行(Active)、降级(Degraded) 和 销毁(Terminating → 删除)。Operator 的 Reconciler 根据 Tenant 对象的 metadata.deletionTimestamp 和 status.phase 决定当前执行的动作。
| 阶段 | 触发条件 | Operator 动作 | 幂等保证 |
|---|---|---|---|
| Pending | Tenant 创建,无 deletionTimestamp | 创建命名空间、RBAC、NetworkPolicy、ResourceQuota、LimitRange | 检查命名空间是否已存在,跳过已创建的资源 |
| Active | 所有子资源就绪 | 定期校验子资源状态,更新 conditions | 对比 spec 与当前状态,仅更新差异部分 |
| Degraded | 子资源缺失或配置不一致 | 重新创建或修复子资源,记录错误原因 | 使用 resourceVersion 避免并发覆盖 |
| Terminating | deletionTimestamp 不为空 | 清理命名空间及关联资源,移除 finalizer | 幂等删除:忽略已不存在的资源 |
deletionTimestamp,若不为空则进入 Terminating 分支;否则根据 status.phase 和子资源状态决定是创建还是修复。所有写操作前都执行 Get 检查,避免重复创建。
3.3 命名空间策略:自动编排与隔离
每个租户可以拥有一个或多个命名空间。Operator 根据 Tenant CRD 的 namespacePrefix 和 labels 自动生成命名空间,并附加以下策略:
- 命名空间命名规则:
{namespacePrefix}-{environment},例如tn-prod-001、tn-staging-001。environment 由 Operator 根据 Tenant 的spec.environments字段生成。 - 标签继承: 自动为命名空间添加
tenant-id: {tenant.Name}、tenant-display-name: {spec.displayName}标签,便于后续资源查询和审计。 - 网络隔离: 默认创建
DenyAllIngress的 NetworkPolicy,仅允许同命名空间内的流量;若spec.networkPolicy.ingressIsolation为 false,则创建允许所有入站流量的策略。 - RBAC 绑定: 为
spec.adminUsers中的每个用户创建RoleBinding,绑定到admin角色,确保租户管理员拥有命名空间内的管理权限。
// Reconciler 中创建命名空间的伪代码
func (r *TenantReconciler) ensureNamespace(ctx context.Context, tenant *tenantv1.Tenant) error {
nsName := fmt.Sprintf("%s-%s", tenant.Spec.NamespacePrefix, "default")
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: nsName,
Labels: map[string]string{
"tenant-id": tenant.Name,
"tenant-display-name": tenant.Spec.DisplayName,
"managed-by": "tenant-operator",
},
},
}
// 幂等创建:先 Get,不存在则 Create
existing := &corev1.Namespace{}
err := r.Get(ctx, types.NamespacedName{Name: nsName}, existing)
if err != nil && apierrors.IsNotFound(err) {
return r.Create(ctx, ns)
}
return err
}
3.4 资源配额与限制范围设计
资源配额(ResourceQuota)和限制范围(LimitRange)是防止租户资源滥用、保障集群稳定性的关键手段。Operator 根据 Tenant CRD 的 spec.resourceQuota 和 spec.limitRange 自动生成对应的 Kubernetes 资源。
3.4.1 ResourceQuota 设计
每个租户命名空间自动创建一个 ResourceQuota,默认配置如下:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: tn-prod-001
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
persistentvolumeclaims: "10"
pods: "50"
services: "20"
secrets: "100"
configmaps: "100"
hard 字段必须与集群实际容量匹配。建议在 Tenant CRD 中提供默认值,同时允许租户管理员通过 spec.resourceQuota.hard 覆盖。Operator 在更新配额时,需校验新值是否超过集群剩余容量,避免超卖。
3.4.2 LimitRange 设计
LimitRange 用于约束单个 Pod 或容器的资源上下限,防止某个 Pod 占用过多资源。默认配置如下:
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-limits
namespace: tn-prod-001
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "10m"
memory: "32Mi"
- type: PersistentVolumeClaim
max:
storage: "100Gi"
min:
storage: "1Gi"
3.4.3 配额与限制的联动策略
| 维度 | ResourceQuota | LimitRange | 联动效果 |
|---|---|---|---|
| CPU | 限制命名空间总 CPU 请求/限制 | 限制单个容器 CPU 上下限 | 防止单个 Pod 占满整个命名空间配额 |
| 内存 | 限制命名空间总内存请求/限制 | 限制单个容器内存上下限 | 避免内存泄漏 Pod 影响其他租户 |
| 存储 | 限制 PVC 总数 | 限制单个 PVC 大小 | 防止大量小 PVC 或超大 PVC |
| 对象数量 | 限制 Pod/Service/Secret 数量 | 不适用 | 防止 etcd 压力过大 |
spec.resourceQuota 或 spec.limitRange 发生变化时,Operator 先获取当前 ResourceQuota/LimitRange 对象,对比 spec 字段,仅在有差异时执行 Update。更新前使用 resourceVersion 确保乐观锁,避免并发覆盖。
3.5 租户状态与条件管理
Tenant 的 status 字段记录了当前阶段和详细条件,便于用户和运维人员了解租户健康状态。Operator 在每次 Reconcile 结束时更新 status.conditions,包含以下类型:
- NamespaceReady: 所有命名空间已创建且状态正常。
- QuotaApplied: ResourceQuota 和 LimitRange 已创建或更新成功。
- NetworkPolicyApplied: NetworkPolicy 已就绪。
- RBACReady: 所有 RoleBinding 已创建。
当所有条件均为 True 时,status.phase 切换为 Active;若任一条件为 False,则切换为 Degraded,并在 status.conditions[].message 中记录错误原因。
3.6 幂等控制与错误恢复
在多租户场景中,Operator 必须处理网络抖动、API Server 限流、资源冲突等异常情况。以下是幂等控制的核心策略:
- 先读后写: 所有创建/更新操作前先执行 Get,若资源已存在且 spec 一致则跳过。
- 使用 resourceVersion: 更新操作携带 resourceVersion,避免并发覆盖。
- Finalizer 机制: 在 Tenant 创建时添加
tenant-cleanupfinalizer,删除时先清理子资源再移除 finalizer,确保资源不会残留。 - 重试与退避: 当 API 返回 429 或 500 时,Reconciler 自动重试,重试间隔指数退避(1s、2s、4s…最大 30s)。
- 状态持久化: 每次 Reconcile 结束时更新
status.observedGeneration,避免重复处理相同版本的 Tenant。
status.conditions 中记录每次 Reconcile 的详细错误信息,便于运维人员快速定位问题。同时,建议为 Operator 添加 Prometheus 指标(如 tenant_reconcile_total、tenant_reconcile_errors_total),实现可观测性。