Kubernetes 基础体系 · 第 7/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes Namespace:作用域、资源配额、权限、多租户和删除

Namespace(命名空间)是 Kubernetes API 中用于组织对象的逻辑边界。它最直接的作用是给一组命名空间作用域资源增加名称前缀,并让 RBAC、ResourceQuota、LimitRange 等机制能够以 Namespace 为治理单位。

Namespace 不是一台虚拟集群,也不是天然的安全边界。它能提供名称隔离、权限隔离和资源治理的基础,但无法单独完成网络、节点、内核、存储或控制面层面的强隔离。

本文示例使用当前稳定的 Kubernetes API:

  • v1:Namespace、Pod、Service、ResourceQuota、LimitRange
  • rbac.authorization.k8s.io/v1:Role、RoleBinding、ClusterRole、ClusterRoleBinding
  • networking.k8s.io/v1:NetworkPolicy

云厂商托管集群可能额外提供租户、项目、节点池或网络策略实现,但这些不属于 Namespace API 本身。

Namespace 到底解决了什么问题

在 Kubernetes 中,对象的完整身份通常由以下信息共同决定:

Identity=(API Group,Resource,Namespace,Name)\text{Identity} = (\text{API Group}, \text{Resource}, \text{Namespace}, \text{Name})

例如:

("", "pods", "team-a", "web")
("", "pods", "team-b", "web")

这两个 Pod 的名称都叫 web,但因为 Namespace 不同,所以可以同时存在。

对命名空间作用域资源,API 路径也体现了 Namespace:

/api/v1/namespaces/team-a/pods/web
/api/v1/namespaces/team-b/pods/web

而节点是集群作用域资源:

/api/v1/nodes/node-1

它没有 Namespace 部分。

命名空间作用域与集群作用域

常见资源可以分为两类。

类型 示例 是否属于某个 Namespace
命名空间作用域 Pod、Deployment、Service、ConfigMap、Secret、PVC、Role、RoleBinding、ResourceQuota、LimitRange
集群作用域 Node、Namespace、PersistentVolume、StorageClass、ClusterRole、ClusterRoleBinding、CustomResourceDefinition、PriorityClass

可以用 API 发现信息确认资源是否 namespaced:

kubectl api-resources --verbs=list --namespaced
kubectl api-resources --verbs=list --namespaced=false

例如:

kubectl api-resources | grep -E 'pods|nodes|persistentvolumes|roles'

这比凭记忆判断更可靠,因为 CRD 是否 namespaced 由 CRD 定义决定。

Namespace 不会自动隔离资源引用

Namespace 主要约束对象的身份和权限范围,并不意味着所有引用都只能指向同一 Namespace。

例如:

  • Pod 可以通过 DNS 访问另一个 Namespace 中的 Service;
  • RoleBinding 可以把一个 Namespace 中的 ServiceAccount 授予另一个 Namespace 中的 Role;
  • PVC 是 namespaced,但它绑定的 PersistentVolume 是 cluster-scoped;
  • StorageClass 是 cluster-scoped;
  • Node 是 cluster-scoped,Pod 可以被调度到任意符合条件的节点;
  • Image、RuntimeClass、PriorityClass 等资源通常不是 Namespace 私有的。

Service 的 DNS 名称体现了 Namespace:

web.team-a.svc.cluster.local

同一个 Namespace 内通常可以使用短名称:

web

但这只是集群 DNS 的解析约定,不是安全控制。NetworkPolicy 和 RBAC 才分别承担网络访问控制与 API 操作授权。

创建 Namespace 与选择默认作用域

最简单的 Namespace 定义如下:

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    tenant: team-a
    environment: production

创建并验证:

kubectl apply -f namespace.yaml
kubectl get namespace team-a
kubectl describe namespace team-a

预期可看到:

NAME     STATUS   AGE
team-a   Active   ...

Namespace 名称需要符合 Kubernetes 对资源名称的约束,通常使用小写字母、数字和连字符,并且不能随意修改。需要改名时,通常是创建新 Namespace、迁移对象,再删除旧 Namespace,而不是修改 metadata.name

命令行中可以通过 -n 指定 Namespace:

kubectl get pods -n team-a
kubectl apply -f deployment.yaml -n team-a

也可以为当前 kubeconfig 上下文设置默认 Namespace:

kubectl config set-context --current --namespace=team-a
kubectl config view --minify --output='jsonpath={..namespace}{"\n"}'

没有显式指定 Namespace 的 namespaced 对象,kubectl 通常使用当前上下文的 Namespace;如果上下文没有设置,则常见默认值是 default。这不是 API 要求对象必须属于 default,而是客户端的默认行为。

下面的命令可能产生误解:

kubectl get pods

它通常只列出当前 Namespace 的 Pod,而不是整个集群。查看所有 Namespace 需要显式指定:

kubectl get pods -A

但命令能否成功还取决于当前用户是否有跨 Namespace 列表权限。

Namespace 与资源配额

ResourceQuota 的作用

ResourceQuota(资源配额)是在一个 Namespace 内对资源总量设置上限的准入控制机制。它可以限制:

  1. 计算资源总量,例如 CPU 和内存;
  2. 某些对象的数量,例如 Pod、Service、Secret;
  3. 某些类别的 Pod,例如带有特定优先级或 QoS 分类的 Pod。

ResourceQuota 不是实时监控器。它主要在对象创建或更新进入 API Server 准入流程时,计算该操作是否会使 Namespace 超过配额。

以 CPU 请求为例,如果 Namespace 当前累计请求量为 CC,本次操作新增请求为 ΔC\Delta C,配额上限为 CmaxC_{\max},则准入条件是:

C+ΔCCmaxC + \Delta C \leq C_{\max}

如果条件不成立,API Server 拒绝请求,Pod 不会被成功创建。

配额的计算通常基于对象声明的 requests、limits 或对象数量,而不是节点上瞬时的实际 CPU 使用率。一个 Pod 即使实际只使用很少 CPU,只要它声明了较大的 requests.cpu,就会消耗相应的配额。

一个完整的配额示例

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    pods: "10"
    services: "5"
    count/secrets: "20"
    requests.cpu: "2"
    limits.cpu: "4"
    requests.memory: 4Gi
    limits.memory: 8Gi

应用并查看:

kubectl apply -f quota.yaml
kubectl describe resourcequota team-a-quota -n team-a

输出中的核心信息类似:

Resource           Used   Hard
--------           ----   ----
limits.cpu         1500m  4
limits.memory      1536Mi 8Gi
pods               3      10
requests.cpu       300m   2
requests.memory    384Mi  4Gi
services           1      5
count/secrets      2      20

Used 是当前 Namespace 中已经被配额统计的使用量,Hard 是上限。这里的 services: "5" 是资源短名数量配额;count/secrets: "20" 使用了通用对象计数语法。

可以进一步按 API 资源限制数量:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
  namespace: team-a
spec:
  hard:
    count/deployments.apps: "20"
    count/jobs.batch: "50"
    count/configmaps: "100"
    count/secrets: "100"

具体资源是否支持计数,以及资源名称如何写,应以当前集群的 API 发现和 ResourceQuota 文档为准。对 CRD 对象进行数量治理时,也要确认集群版本和该资源是否支持对象计数配额。

requests、limits 与配额的关系

容器资源字段含义不同:

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"
  • requests 主要用于调度器判断节点是否有足够的可分配资源;
  • limits 主要用于运行时限制容器的资源使用;
  • ResourceQuota 可以分别统计 requests.*limits.*
  • quota 不会把实际 CPU 利用率当作 requests.cpu
  • CPU limit 通常通过 cgroup 限制,内存 limit 超出时可能触发 OOM,具体行为还受运行时和内核影响。

例如,Namespace 配置:

requests.cpu: "2"

有三个 Pod,每个 Pod 中一个容器声明:

requests:
  cpu: "500m"

则累计 CPU 请求为:

3×500m=1500m=1.53 \times 500m = 1500m = 1.5

再创建一个 600m 请求的 Pod 时:

1.5+0.6=2.1>21.5 + 0.6 = 2.1 > 2

API Server 会拒绝该创建请求,常见错误类似:

exceeded quota: team-a-quota, requested: requests.cpu=600m,
used: requests.cpu=1500m, limited: requests.cpu=2

这里的失败发生在对象创建阶段,不是 Pod 先创建、再由调度器发现节点不足。

Pod 副本数与配额计算

Deployment 的副本数会间接产生 Pod,但 ResourceQuota 默认统计的是实际被创建的 Pod,而不是 Deployment 的 spec.replicas 字段本身。

例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: team-a
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx:stable
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi

创建后,配额中的 Pod 数量通常增加 3,CPU 请求增加:

3×100m=300m3 \times 100m = 300m

如果还设置了:

count/deployments.apps: "20"

则 Deployment 对象本身也会消耗一个 Deployment 数量配额。

滚动更新时,旧 ReplicaSet 和新 ReplicaSet 可能短时间同时存在,Pod 数量可能暂时高于稳定副本数。因此,配额只设置为“刚好等于期望副本数”会使滚动更新失败。需要为更新过程预留临时容量,或者采用适合配额的滚动更新参数。

ResourceQuota 的边界

ResourceQuota 不等同于完整的资源隔离:

  • 它限制的是 Namespace 的声明量,不保证租户实际获得独占 CPU;
  • 一个 Namespace 中的 Pod 仍可能和其他 Namespace 的 Pod 运行在同一节点;
  • CPU limit 不是 CPU 独占;
  • 内存 limit 不能防止节点整体内存压力;
  • 配额不自动限制节点数量、磁盘 I/O、网络带宽或 API 请求速率;
  • ResourceQuota 本身不提供权限控制;
  • 如果配额依赖 requests 或 limits,而工作负载没有填写这些字段,就需要配合 LimitRange 或准入策略补齐。

LimitRange:让配额能够被正确使用

ResourceQuota 负责 Namespace 级别的总量,LimitRange 负责 Namespace 内单个对象或容器的默认值与边界。两者解决的是不同问题:

机制 主要问题
ResourceQuota 这个 Namespace 总共可以使用多少
LimitRange 单个容器或 Pod 至少、最多、默认使用多少

示例:

apiVersion: v1
kind: LimitRange
metadata:
  name: team-a-limits
  namespace: team-a
spec:
  limits:
  - type: Container
    defaultRequest:
      cpu: 100m
      memory: 128Mi
    default:
      cpu: 500m
      memory: 512Mi
    min:
      cpu: 10m
      memory: 16Mi
    max:
      cpu: "2"
      memory: 2Gi

其效果是:

  1. 容器没有填写 requests 时,可能被补上 100m CPU 和 128Mi 内存;
  2. 容器没有填写 limits 时,可能被补上 500m CPU 和 512Mi 内存;
  3. 低于 min 或高于 max 的容器请求会被拒绝;
  4. LimitRange 不会把整个 Namespace 的总量限制为某个数值。

因此,前面的 Deployment 如果省略资源字段,LimitRange 可以使它获得默认资源值。三个副本会按默认值消耗约:

requests.cpu    = 3 × 100m  = 300m
requests.memory = 3 × 128Mi = 384Mi
limits.cpu      = 3 × 500m  = 1500m
limits.memory   = 3 × 512Mi = 1536Mi

实际准入结果还应通过对象查询确认:

kubectl get deployment web -n team-a -o yaml
kubectl get pod -n team-a -o yaml

需要注意,LimitRange 的默认值是准入时写入对象或影响准入判断的默认行为,不是调度器在运行时临时猜测的值。修改 LimitRange 通常不会自动重写已经存在的 Pod;要让旧 Pod 使用新默认值,通常需要重新创建或通过控制器滚动更新。

Namespace 与 RBAC 权限

RBAC(Role-Based Access Control,基于角色的访问控制)回答的是:

某个主体能否对某个 API 资源执行某个动作?

它不是 Namespace 的附属属性,而是单独的授权机制。

Role 与 RoleBinding

Role 是 Namespace 作用域资源,描述该 Namespace 内允许的 API 操作:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: workload-reader
  namespace: team-a
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]

RoleBinding 将角色授予主体:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: alice-workload-reader
  namespace: team-a
subjects:
- kind: User
  name: alice@example.com
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: workload-reader
  apiGroup: rbac.authorization.k8s.io

验证权限:

kubectl auth can-i get pods \
  --as=alice@example.com \
  -n team-a

kubectl auth can-i get pods \
  --as=alice@example.com \
  -n team-b

第一个命令预期为 yes,第二个通常为 no,因为 RoleBinding 只存在于 team-a

RBAC 默认是允许规则累加模型:

  • 没有显式拒绝规则;
  • 多个 RoleBinding 的权限会合并;
  • 删除一个 Binding 后,主体可能仍通过另一个 Binding 保有权限。

ServiceAccount 的 Namespace 归属

工作负载通常以 ServiceAccount 身份访问 Kubernetes API:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: deployer
  namespace: team-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployer-binding
  namespace: team-a
subjects:
- kind: ServiceAccount
  name: deployer
  namespace: team-a
roleRef:
  kind: Role
  name: workload-reader
  apiGroup: rbac.authorization.k8s.io

ServiceAccount 的完整主体名是:

system:serviceaccount:team-a:deployer

一个 Namespace 中的 RoleBinding 可以引用另一个 Namespace 中的 ServiceAccount,但这会形成跨租户授权,必须显式审查。

ClusterRole 不一定意味着集群级权限

ClusterRole 是集群作用域的角色对象,但它可以通过 RoleBinding 在某一个 Namespace 中授予命名空间资源权限:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-a-view
  namespace: team-a
subjects:
- kind: Group
  name: team-a-developers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

此时 view 中对 Pod、Service 等 namespaced 资源的权限只在 team-a 生效。

相反,ClusterRoleBinding 会把角色授予整个集群范围。例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: team-a-admin
subjects:
- kind: Group
  name: team-a-developers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io

这类绑定可能使主体获得所有 Namespace 的管理权限,不能因为角色名称中没有 cluster 就认为它是租户内权限。

Role 不能授权访问集群作用域资源。例如,Role 中写入 nodes 并不能让主体访问节点;访问 Node 必须使用适当的 ClusterRole 和 ClusterRoleBinding。Namespace 本身也是集群作用域资源,删除 Namespace 通常需要对 Namespace 资源拥有集群级权限。

Namespace 与多租户

多租户(multi-tenancy)是多个相互独立的团队、项目或客户共享一个 Kubernetes 集群,同时限制彼此影响和访问的架构问题。

Namespace 是多租户的常见组织单位,但一个可用的租户边界至少包含以下维度:

维度 主要机制 解决的问题
API 权限 RBAC 谁能读写哪些对象
资源预算 ResourceQuota 租户最多声明多少资源或对象
单对象约束 LimitRange、准入策略 单个容器的默认值和上下限
网络访问 NetworkPolicy Pod 之间允许哪些网络连接
调度边界 taint、toleration、nodeAffinity、节点池 工作负载能运行在哪些节点
凭据与配置 Secret、外部密钥系统、RBAC 谁能读取敏感数据
API 治理 Admission webhook、Pod Security Admission 哪些对象可以被创建
存储边界 StorageClass、CSI、云厂商策略 数据卷的生命周期和访问范围
故障边界 独立集群、节点池、控制面 租户之间能否互相造成基础设施故障

网络默认行为不是租户隔离

如果集群没有 NetworkPolicy,许多常见网络插件的默认行为是允许 Pod 之间通信。此时下面两个 Pod 即使属于不同 Namespace,也可能互相访问:

team-a/web  ->  team-b/database

要改变这个行为,需要网络插件支持并正确实现 networking.k8s.io/v1 的 NetworkPolicy。例如,在 team-a 中启用入口默认拒绝:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: team-a
spec:
  podSelector: {}
  policyTypes:
  - Ingress

这表示选择 team-a 中所有 Pod,并对入口流量启用隔离。它不自动限制出口流量,也不自动允许同 Namespace 的通信。

允许 team-a 中的 Pod 访问 team-b 中带有标签的服务端 Pod,可以在 team-b 配置:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-team-a
  namespace: team-b
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          tenant: team-a

这里的 namespaceSelector 选择 Namespace 标签,而不是 Namespace 名称本身。team-b Namespace 必须确实带有:

metadata:
  labels:
    tenant: team-b

NetworkPolicy 的实际能力依赖网络插件。API 对象创建成功,不代表底层网络实现一定支持所有行为;应通过实际连接测试验证。NetworkPolicy 通常也不等同于节点级防火墙,不能替代云网络安全组或主机安全策略。

Namespace 不是强安全边界

以下情况会突破“只划分 Namespace 就完成隔离”的假设:

  • 用户拥有创建 privileged Pod 的权限;
  • Pod 使用 hostNetworkhostPIDhostPath
  • 容器拥有过高 Linux capabilities;
  • 用户可以创建或修改跨 Namespace 的 RoleBinding;
  • 用户可以读取其他 Namespace 的 Secret;
  • 节点内核、容器运行时或 CSI 驱动存在漏洞;
  • 租户共享节点导致噪声、侧信道或节点级资源争用;
  • 用户可以创建影响全局的 CRD、Webhook、ClusterRole 或 StorageClass;
  • Admission 策略没有限制危险的 Pod Security 设置。

Pod Security Admission 可以按 Namespace 标签启用 Pod 安全级别。例如:

kubectl label namespace team-a \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

restricted 会拒绝不符合 Pod Security Standards 的 Pod,但它仍不提供完整的租户隔离;应用还需要合理的 RBAC、NetworkPolicy、Quota 和节点治理。

如果租户之间存在强安全、强合规或高风险需求,独立集群通常比共享 Namespace 更容易验证隔离边界。Namespace 多租户适合逻辑隔离和组织治理,但不能被描述为“等价于虚拟机”或“等价于独立集群”。

Namespace 删除的状态机

Namespace 删除不是简单删除一行元数据。它大致经历以下状态:

stateDiagram-v2
    [*] --> Active
    Active --> Terminating: DELETE Namespace
    Terminating --> Terminating: 清理对象或 finalizer 未完成
    Terminating --> [*]: finalizers 完成且对象清理完成

删除请求被 API Server 接受后,Namespace 通常不再保持 Active,而进入:

Terminating

此时 Namespace 的删除时间戳已经写入对象。Namespace Controller 会尝试:

  1. 发现当前集群中可用的 API 资源;
  2. 枚举该 Namespace 中的 namespaced 对象;
  3. 删除这些对象;
  4. 等待对象上的 finalizer 完成;
  5. 在清理完成后移除 Namespace 自身的最终 finalizer。

典型删除命令:

kubectl delete namespace team-a

这是高风险操作。它会触发 team-a 中 Deployment、Pod、Service、ConfigMap、Secret、PVC、RoleBinding、ResourceQuota 等 namespaced 对象的清理。PV 是 cluster-scoped,不会因为 Namespace 删除而自动删除;但 PVC 删除可能触发底层存储供应商根据 StorageClass 回收策略处理对应的 PV 和云盘。

finalizer 为什么会阻塞删除

Finalizer 是对象元数据中的一个字符串列表,用于表达:

在真正删除对象前,某个控制器必须先完成外部清理。

删除对象时,API Server 通常先写入 deletionTimestamp,对象进入删除中状态,而不是立即从存储中消失。只有相关 finalizer 被控制器移除后,对象才会完成删除。

Namespace 卡在 Terminating 时,常见原因包括:

  • Namespace 中存在带 finalizer 的对象;
  • 对应的控制器已经停止运行;
  • 某个 APIService 不可用,Namespace Controller 无法发现资源;
  • CRD 被删除,但残留对象或控制器清理逻辑不完整;
  • Webhook、聚合 API 或控制器持续返回错误。

诊断步骤:

kubectl get namespace team-a -o yaml
kubectl describe namespace team-a
kubectl get apiservice | grep -v True

重点查看 Namespace 的状态条件:

kubectl get namespace team-a \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status} reason={.reason} message={.message}{"\n"}{end}'

也可以查看 Namespace 中的资源:

kubectl api-resources --verbs=list --namespaced -o name | \
while read resource; do
  kubectl get "$resource" -n team-a --ignore-not-found
done

该方法会受权限、API 聚合服务和资源数量影响;如果某类资源的 API 不可用,命令本身也可能报错,错误信息就是诊断线索之一。

强制移除 Namespace finalizer 的风险

在确认阻塞原因并完成必要清理后,才考虑移除 Namespace 的 finalizer。常见的低风险路径是先修复对应控制器或删除阻塞对象,而不是直接修改 finalizer。

强制 finalize 的操作示例:

kubectl get namespace team-a -o json > team-a.json

编辑 team-a.json,只在确认风险后移除 Namespace 对象中的 spec.finalizers,然后通过 finalize API 提交。不同客户端版本对 kubectl replace --raw 的用法可能存在差异,执行前应检查当前客户端帮助信息:

kubectl replace --raw \
  "/api/v1/namespaces/team-a/finalize" \
  -f team-a.json

该操作的危险在于:

  • 它可能使 Namespace 对象消失,但某些对象没有完成控制器清理;
  • 外部云资源、DNS、负载均衡器或存储资源可能残留;
  • 某些资源可能成为无法通过正常 Namespace 路径访问的孤儿对象;
  • 重新创建同名 Namespace 后,残留资源或旧控制器可能产生混淆。

因此,强制移除 finalizer 不是“修复删除”的通用按钮,而是放弃一部分由控制器提供的清理保证。执行后应检查:

kubectl get namespace team-a
kubectl get pv
kubectl get service --all-namespaces
kubectl get events --all-namespaces --sort-by=.lastTimestamp

还需要到云厂商控制台或 CSI、Ingress、LoadBalancer 控制器的日志中确认外部资源是否已经删除。

常见误解与失败路径

误解一:Namespace 名称不同,Pod 就不能互相访问

错误。Namespace 主要影响对象作用域和权限,不会自动创建网络隔离。需要 NetworkPolicy,并且网络插件必须支持和执行这些策略。

误解二:给 Namespace 设置 CPU 配额,就获得了独占 CPU

错误。配额限制的是声明量和准入量。不同 Namespace 的 Pod 仍可能共享节点 CPU。要实现更强的节点级隔离,需要节点池、taint、toleration、亲和性和底层基础设施配合。

误解三:ResourceQuota 会替用户补齐资源字段

错误。ResourceQuota 负责总量限制,LimitRange 才负责默认 requests 和 limits。生产治理中常把二者一起配置,但它们不是同一个机制。

误解四:删除 Deployment 后,Namespace 配额立即归零

通常会下降,但不一定立即归零。控制器删除 Pod、ReplicaSet、Job 或其他对象是异步过程;带 finalizer 的对象也可能延迟消失。应通过以下命令观察实际状态:

kubectl describe resourcequota -n team-a
kubectl get all -n team-a
kubectl get events -n team-a --sort-by=.lastTimestamp

误解五:Namespace 删除成功就代表云资源全部删除

不一定。Kubernetes 对象和外部资源的删除依赖控制器、finalizer、CSI 驱动以及云厂商实现。尤其是 LoadBalancer、云盘、DNS 记录和外部数据库,必须检查实际云资源状态。

误解六:能读取 Namespace 就能读取其中所有对象

错误。get namespacesget pods -n team-a 是不同资源、不同授权检查。主体即使能看到 Namespace 名称,也可能没有读取其中 Secret 或 Pod 的权限。

一个可验证的租户基础配置

下面的配置把 Namespace、LimitRange、ResourceQuota 和最小 RBAC 组合起来:

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    tenant: team-a
---
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: team-a
spec:
  limits:
  - type: Container
    defaultRequest:
      cpu: 100m
      memory: 128Mi
    default:
      cpu: 500m
      memory: 512Mi
    min:
      cpu: 10m
      memory: 16Mi
    max:
      cpu: "2"
      memory: 2Gi
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: budget
  namespace: team-a
spec:
  hard:
    pods: "10"
    requests.cpu: "2"
    limits.cpu: "4"
    requests.memory: 4Gi
    limits.memory: 8Gi
    services: "5"
    count/secrets: "20"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-reader
  namespace: team-a
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-a-readers
  namespace: team-a
subjects:
- kind: Group
  name: team-a-developers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: app-reader
  apiGroup: rbac.authorization.k8s.io

应用后验证:

kubectl apply -f team-a-governance.yaml

kubectl get namespace team-a
kubectl describe resourcequota -n team-a
kubectl describe limitrange -n team-a

kubectl auth can-i list pods \
  --as-group=team-a-developers \
  --as=alice@example.com \
  -n team-a

然后创建一个不声明资源字段的 Pod 或 Deployment,检查 API Server 是否写入了 LimitRange 默认值:

kubectl get pod -n team-a -o yaml

再创建超过配额的工作负载,观察 API Server 返回的 exceeded quota 错误。这个验证顺序很重要:

  1. 先确认 Namespace 存在且为 Active
  2. 确认 LimitRange 和 ResourceQuota 已被 API Server 接受;
  3. 确认 RBAC 主体确实使用预期身份;
  4. 检查生成对象中的实际 requests 和 limits;
  5. 最后验证超额请求是否被拒绝;
  6. 如果网络隔离也在要求范围内,再使用实际连接测试验证 NetworkPolicy,而不能只检查策略对象存在。

结语:Namespace 的准确定位

Namespace 是 Kubernetes 中连接多个机制的核心作用域:

  • 它让 namespaced 对象可以在不同团队之间重用名称;
  • ResourceQuota 用它计算租户预算和对象数量;
  • LimitRange 用它提供单容器默认值与边界;
  • Role 和 RoleBinding 用它限制 API 权限范围;
  • NetworkPolicy 用它表达一部分租户网络边界;
  • Namespace Controller 在删除时以它为单位清理 namespaced 对象。

但 Namespace 本身不提供完整多租户隔离,也不保证独占计算资源、网络、节点、存储或内核。可靠的租户治理必须明确区分:

Namespace 作用域RBAC 权限资源配额网络隔离强安全边界\text{Namespace 作用域} \neq \text{RBAC 权限} \neq \text{资源配额} \neq \text{网络隔离} \neq \text{强安全边界}

删除 Namespace 也不是瞬时操作,而是由 API Server、Namespace Controller、各类资源控制器和 finalizer 共同完成的异步生命周期。理解这些边界,才能在配额拒绝、权限不足、跨租户访问和 Namespace 卡在 Terminating 时,沿着正确的组件和状态路径进行诊断。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。