前阵子接手了一套“历史悠久”的K8s环境,集群里跑着几十个应用,开发、测试、生产全混在一个集群里。应用之间没有任何隔离,某个组在调试压测脚本时把CPU打满,同节点的其他Pod直接连不上Redis。我第一反应是查资源限制,结果发现连namespace都没好好规划——所有人都在default里瞎跑。这件事让我把namespace的优先级一下子提了上来。很多人初学K8s会纠结“K8s和Docker到底啥区别”,其实看namespace就特别清楚:Docker的隔离发生在单台宿主机的容器层面,而K8s的Namespace是作用于整个集群层面的逻辑分区,两者解决的不是同一个维度的问题。本文是K8s系列的第六篇,我把实际操作里关于namespace的边界、资源配额、网络策略、生命周期管理等经验整理成文字,偏实战、有例子,适合那些已经能跑通基础环境、但还没理顺多环境多团队共用集群细节的人参考。
1. 前期最容易低估的一道边界:Namespace到底管住了什么
1.1 Namespace隔离的是“名字”,而不是“资源”
先明确一个基础事实:K8s的Namespace把集群里的API对象做了一个逻辑划分。你在一个namespace里创建Deployment、Service、ConfigMap,名字只要在这个namespace里不重复就行,另一个namespace里可以出现完全同名的Deployment。所以它的第一层作用是“命名空间”。
但这个“划分”是有边界的。Namespace不会隔离节点、不会隔离Pod的数量(除非你配置ResourceQuota),更不会默认隔离网络。也就是说,两个不同namespace里的Pod互相访问是默认放行的,只要它们之间的Service能够解析到目标地址。这一点在初学的时候很容易被误解为“namespace = 安全隔离”,实际不是。
我做了一个比喻:Namespace更像写字楼里的楼层分隔,你在三楼贴了个“市场部”,在五楼贴了个“研发部”,但是楼里的电、水、电梯大家还是共享的。你想要真正的隔离,得自己在楼里装门禁——对应到K8s就是NetworkPolicy、ResourceQuota、RBAC这些东西。
还有一个很实际的点:刚接触K8s的人特别喜欢把东西直接放在defaultnamespace里,因为创建资源时不用加-n参数。这在单机玩Kubectl的时候没问题,但一旦多人协作,default就是那个“大家都往门口堆鞋的玄关”,早晚会乱到你连哪个Deployment是谁写的都分不清。我到任何一个新集群,第一件事就是把default里能迁走的工作负载迁走,宁可多花半天,也不留隐患。
1.2 Linux namespace和K8s Namespace不是同一个东西
这里我得多说一句,因为确实太多人绕晕过。Linux内核本身有namespace机制,包括PID namespace、NET namespace、UTS namespace等等,Docker容器就是靠这些来实现进程、网络栈、主机名层面的隔离。而K8s的Namespace是API层面的一个逻辑对象,这两个用同一个英文单词,但层级完全不一样——一个在操作系统内核里,一个在Kubernetes控制面里。
如果你用docker run跑容器,你能看到进程的PID被隔离了、网络栈被隔离了,但你看不到“default namespace”这种概念。只有在K8s集群里,你才会遇到Namespaces这个资源。所以“K8s和Docker区别”这个问题,如果从Namespace切入去讲,答案会清楚很多:Docker解决“容器怎么跑”,K8s解决“一堆容器在多台机器上怎么统一编排、怎么按逻辑边界去管理”。
当然,K8s的Pod里也会用到Linux namespace,Pod就是一组共享某些Linux namespace的容器集合。初学的时候把这两个概念放在一起学,容易越学越乱。我建议是先在kubectl命令行层面把K8s Namespace搞熟练,再去看容器运行时底层的原理。这样一步步来,不容易把API层的Namespace和内核层的Namespace搅成一锅粥。
2. ResourceQuota与LimitRange:给Namespace画财务红线
2.1 ResourceQuota一次性限制CPU、内存和对象数量
Namespace如果不做任何配额,那它只是一个“文件夹”。我前面说的那套混乱环境,最大的问题就是Pod没有配额,某个应用能把整台节点的CPU全部吃掉。配了ResourceQuota之后,事情立刻不一样。
下面是一个典型配置:
apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: "8" requests.memory: "16Gi" limits.cpu: "16" limits.memory: "32Gi" pods: "20" services: "20" configmaps: "30"我习惯把requests和limits分开看,因为很多团队只配limits不配requests,这种习惯在新版本里会带来调度问题。Pod要调度到节点上,看的是requests;Pod运行中是否被限制,看的是limits。如果你只设limits而不设requests,调度器会认为这个Pod占用的资源很少,可能把多个大Pod堆到同一台节点,结果运行起来全部被throttle或者被驱逐。
使用ResourceQuota之后,如果namespace里的总配额已经用满,创建新Pod会直接报错。这里放一个我实际遇到过很多次的报错信息:
Error from server (Forbidden): error when creating "deploy.yaml": deployments.apps "xxx" is forbidden: exceeded quota: dev-quota, requested: pods=1, used: pods=20, limited: pods=20这不是运行时的OOM,而是创建阶段就被API Server拒绝。问题暴露得越早越好——你总不希望业务已经在线上跑着,内存悄悄超了配额然后被一连串驱逐吧。
2.2 LimitRange:给没有显式声明资源的Pod兜底
有Quota还不够。很多开发写Deployment的时候不写resources字段,如果这个namespace里有ResourceQuota,那创建Pod会直接报错,提示你“must specify limits.cpu”之类的。你总不能要求所有开发每次记得这些细节,所以需要LimitRange来兜底。
下面是我常用的一套默认配置:
apiVersion: v1 kind: LimitRange metadata: name: default-limit-range namespace: dev spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 250m memory: 256Mi max: cpu: "2" memory: 2Gi type: Container这个配置的效果是:如果Pod里的容器没写resources,K8s会自动套用default值作为limits、defaultRequest作为requests;任何容器都不得超过max。配合ResourceQuota,namespace的“预算红线”才算完整。
这里有个容易踩的坑:LimitRange的default只对“创建时未指定resources”的Pod生效,对已经存在的Pod不会追溯。你改了LimitRange之后,已经跑着的Pod并不会自动获得默认值,要重建才会应用。所以最好在namespace建好、业务迁入前,就把ResourceQuota和LimitRange一起配好。等到上线后才发现配额不合理,就要经历一轮“滚动重启”来让默认值生效,动静反而大。
2.3 配额与限额冲突时的现象与排查思路
如果ResourceQuota和LimitRange都配了,可能出现一个让人懵的现象:Pod创建一直Pending,但看不到明确报错,只看到Events里一堆“0/4 nodes are available”。排查时要先确认是不是配额问题,用下面两个命令看当前namespace的用量和限额:
kubectl describe resourcequota dev-quota -n dev kubectl describe limitrange default-limit-range -n dev我实际遇到过一个案例:开发在namespace里部署了一个内存要求极高的应用,Pod的limit配置超过了LimitRange的max,资源配额显示剩余内存足够,但Pod就是起不来。看Events才发现是LimitRange限制。这种情况在CI/CD自动化发布时特别容易隐藏,因为发布脚本只看Pod状态,不会主动去翻Events。我建议把quota相关的检查直接写进发布流水线,在apply YAML之后立刻执行一次kubectl get events --field-selector type=Warning,早发现早处理。
另外提醒一下:Quota里的内存单位容易写错,写Mi时候是Mebibyte,写M时候是Megabyte,二者差约4.8%。K8s文档里默认推荐Mi/Gi这类二进制单位,但有些从旧版本迁移过来的配置用的是M/G,混用之后会让人对不上账。统一单位这个细节,建议在集群初始化的时候就定下来。
3. Redis集群部署里的Namespace网络与Service调用细节
3.1 多环境共用集群时Redis集群为什么会互相串
刚才说的那套混乱环境里,还有另一个经典问题:开发环境、测试环境、生产环境共用一套K8s集群,但Redis服务全被部署在同一个namespace里。结果就是测试环境的应用连到了生产Redis——因为Service名字冲突,同一个Namespace里后创建的服务把先创建的覆盖了。
K8s的Service在同一个Namespace内唯一,不同Namespace之间可以存在同名Service。所以当业务系统和服务端都部署在不同namespace时,必须通过“完整DNS名”才能精确访问。跨Namespace访问的格式一般是:
<service-name>.<namespace-name>.svc.cluster.local在同一个namespace里,你用redis这个短名就能访问Service;跨namespace,就必须写redis.cache.svc.cluster.local这种全名。很多Redis连接超时的问题,都是因为代码里用的是短域名,而Service又不在同一个namespace。
还有一种情况,是不同namespace下同名Service并存,导致客户端拿到的是旧ClusterIP。比如测试环境先创建了redisService,生产环境后创建了同名redisService。如果这两个Service都在同一个namespace,第二个创建成功的Service会替换掉DNS记录。你连上之后,可能从测试环境连到了生产环境。这类问题在代码层面基本看不出来,因为不报错,只是数据连错了库。
3.2 NetworkPolicy:真正把Namespace之间的门禁装上
默认网络放行的情况下,Namespace之间的互相访问虽然“能用”,但也埋了不少安全隐患。上面说的测试环境不小心连到生产环境Redis,表面看是配置错误,本质上是网络层面没有任何门禁。
NetworkPolicy是K8s里做Pod级别网络隔离的手段。它和Namespace的关系很直接:你可以定义一个NetworkPolicy,只允许特定namespace来源的流量访问目标Pod。下面这个例子表示只允许来自apps命名空间的Pod访问cache命名空间里带app: redis标签的Pod:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-apps-to-redis namespace: cache spec: podSelector: matchLabels: app: redis policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: apps注意一个细节:NetworkPolicy默认的语义需要结合CNI插件来落实。你使用Flannel时,NetworkPolicy是不生效的,必须换成Calico、Cilium这类支持策略的CNI插件。我遇到过不止一次:团队辛苦写好了NetworkPolicy,结果发现流量照样能通,查了半天才发现集群装的是Flannel,策略根本没被下发到数据面。
还有一种常见情况是NetworkPolicy只允许某几个namespace访问,结果运维要调试Redis时,临时从别的地方curl连不上,又不知道问题出在策略上,白白浪费时间。我会在排查时先看一眼这个namespace下是否只有允许来源的Pod,再去看策略定义,顺序不能反。
3.3 跨Namespace访问时的探针与可用性陷阱
跨Namespace之后,可用性问题会被放大。例如Redis集群的Pod分布在多个namespace,或者Sentinel模式下的监控脚本在不同namespace之间轮询,一旦Namespace的网络策略太严,健康检查也可能被阻断。
我的建议是:对于Redis这类状态型基础组件,尽量单独划分一个infra或cachenamespace,严格控制哪些客户端namespace能访问它。同时把探针(readinessProbe)的端口在NetworkPolicy里显式放行,否则探针失败会导致Pod被摘除,出现“看起来Pod全部Ready,但实际无法服务”的假象。
这里也顺便说一个排查NetworkPolicy影响探针的方法:直接看kubectl describe pod xxx里的事件,如果探针超时,Pod状态会反复处于Ready/NotReady,且事件里通常不会直接写“NetworkPolicy blocked”,而是表现为connection timeout。这时候别急着调探针参数,先检查策略类型是否覆盖了探针来源节点的IP范围。这个坑很容易浪费半天时间,方向错了比不排查还麻烦。
4. namespace删除卡在Terminating:根因与完整处置链路
4.1 现象比预想的常见
用K8s久了,几乎都会遇到kubectl delete namespace xxx之后,namespace一直卡在Terminating状态的情况。我见过有人直接等了一个小时也没反应,最后只能重启集群的kube-apiserver,属于典型的会用刀不会磨刀。
先看一个正常情况下的删除流程:当你执行delete命令时,API Server会把namespace标记为Terminating,然后控制平面开始清理namespace下的资源——Service、Pod、ConfigMap等都会依次删除。正常情况下这个流程几秒到几十秒就结束了。但如果namespace里有某些资源带有finalizer且无法正常清理,删除流程就会挂住。
4.2 Finalizers到底是什么
Finalizers是K8s资源删除前的一个“钩子”。你可以把它理解成一个清理清单,资源要真正被删除前,必须先把所有finalizer对应的清理工作做完。为什么这个机制要存在?因为有些资源在被删除时,还需要做“后事”——比如PVC要通知云厂商释放磁盘、S3挂载点要卸载。如果清理不可用,资源就会留在Terminating状态。
namespace本身也可能带有finalizers,最常见的如kubernetes这个finalizer,它负责清理namespace下的所有K8s资源。如果某个子资源的controller不再响应,或者自定义资源(CRD)的清理逻辑有bug,namespace就会因为finalizer无法移除而卡住。
4.3 排查与强制清理的完整步骤
我遇到卡在Terminating的namespace,会按以下顺序排查:
kubectl get namespace <name> -o json把返回的JSON里.status.conditions和.spec.finalizers看清楚。如果finalizers里有奇怪的CRD相关名字,大概率是某个自定义资源在捣乱。
接下来列出namespace下残留的资源,确认哪些还没删完:
kubectl api-resources --verbs=delete --namespaced=true -o name | xargs -n1 kubectl get -n <name> --ignore-not-found这个命令会列出这个namespace下所有namespace维度的资源。如果发现某类资源一直删不掉,就单独去查对应的controller。
如果是明确的无主finalizer卡住,一种常用方法是先把namespace的spec导出到本地,然后临时清掉finalizers,再用本机代理的方式把更新后的spec回写到API Server:
kubectl get namespace <name> -o json > ns.json vi ns.json # 删除 spec.finalizers kubectl proxy & curl -X PUT http://127.0.0.1:8001/api/v1/namespaces/<name>/finalize -H "Content-Type: application/json" --data-binary @ns.json这里必须要提醒:这是“最后手段”,不是首选项。强制移除finalizers后,namespace下的资源可能变成“没人管的孤儿”,比如PVC对应的云磁盘无法释放、网络策略残留,后面反而更难收拾。只有当你确认这个namespace里的资源已经不再被任何工作负载使用,再执行强制删除。
4.4 如何不踩这个坑
与其等出问题再修复,我更愿意在前期做几件事:
- 删除namespace前,先用
kubectl get all -n <name>看看还有没有工作负载,再通过kubectl get pvc -n <name>确认没有未释放的PVC。 - 避免在namespace里安装带自定义CRD且清理逻辑复杂的控制器,如果要删,先卸controller再删namespace,顺序别反。
- 定期检查集群里有没有长期Terminating的namespace,这条每次做集群巡检时顺手加进去。
不管你是用kubeadm在Rocky还是Ubuntu上搭的集群,这套排查方法都适用,和底层操作系统关系不大。卡在Terminating本质上控制面状态不一致的问题,而不是节点系统配置的问题。
5. 多团队协作时的Namespace规划与RBAC联动
5.1 命名规范比你想的更重要
namespace的命名规范直接决定了集群长期好不好管。我看到过太多“teamA-dev”、“teamA_test”、“project1”、“随便起”这样混用的命名。我一般推荐“环境-应用-序号”或“应用-环境”的统一格式,例如prod-order-v1、dev-user-v2。
为什么要统一?因为CI/CD脚本、监控告警、日志采集都会使用namespace作为筛选维度。命名混乱时,告警分组会跟着乱——你没法用一条PromQL快速统计出“所有生产namespace的CPU平均用量”,只能靠手工维护标签列表。时间一长,维护成本就上去了,而且很容易漏掉某个新加的namespace。
另外一个细节:给namespace打标签很重要。比如:
kubectl label namespace dev-user-v2 env=dev app=user kubectl label namespace prod-order-v1 env=prod app=order标签有什么用?NetworkPolicy的namespaceSelector、监控采集的namespace过滤、权限模板的匹配,全都依赖标签而不是名称字符串。名称可能被改,标签可以灵活扩展。我甚至会把告警接收组也做成标签,比如alert=core,然后监控系统直接按标签报警,这样新增namespace时不用改监控配置,只要打上对应标签。
5.2 RBAC:让不同团队只碰自己的namespace
K8s的RBAC天然支持按namespace做权限划分。Role是作用于某个namespace的,ClusterRole才作用于整个集群。做团队隔离时,我习惯给每个团队建一个ServiceAccount,然后给它绑定某个namespace下的Role。
一个简单的Role示例:
kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: dev-user-v2 name: dev-user-role rules: - apiGroups: [""] resources: ["pods", "services", "configmaps", "deployments"] verbs: ["get", "list", "watch", "create", "update", "patch"]注意这里没有给delete权限——很多团队不愿意轻易让人直接删Deployment或Service,避免一键把生产删没了。高危操作可以走工单流程,由平台管理员执行。
RoleBinding把ServiceAccount绑定到Role上即可:
kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: dev-user-v2 name: dev-user-binding subjects: - kind: ServiceAccount name: ci-bot namespace: dev-user-v2 roleRef: kind: Role name: dev-user-role apiGroup: rbac.authorization.k8s.io这样CI/CD里用到的ci-bot就只能在指定namespace里改指定资源,就算Kubeconfig泄漏了,爆炸半径也被控制在单个namespace内。我见过一些团队把所有CI都绑定到cluster-admin,这是运维事故的温床。不是说不能用cluster-admin,而是这个权限应该只掌握在少数人手里,普通流水线账号给最小权限即可。
5.3 kubens与kubeconfig:不用每次敲-n参数
日常操作多namespace时,我推荐装一个kubectx/kubens工具。它不是必须的,但确实能帮我减少出错概率——人只要少敲一次-n,就少一次把生产当测试操作的机会。
kubens dev-user-v2之后所有kubectl命令自动带着这个namespace上下文,省掉了很多重复参数。我还习惯在shell的提示符里显示当前集群和namespace,避免在多个集群间切换时脑子跟不上。Linux下可以用kube_ps1,macOS和Windows也有对应的方案。或者更简单一点:开一个专门的终端窗口给生产环境namespace用,绝不混用。这个习惯帮我在很多手忙脚乱的时刻守住了底线。
6. 踩过一阵子坑之后的实战心得与常用命令
6.1 关于“Namespace是不是越多越好”的反思
一开始我也有过比较激进的想法:给每个应用、每个环境都建一个namespace,觉得隔离越细越安全。但也遇到了新的成本:Namespace多了,权限模板要按namespace复制,监控告警要按namespace逐条配置,稍微不带-n参数的kubectl命令就会操作到错误的namespace,心智负担其实不小。
现在我更推荐一种折中方案:少量大namespace + 标签细分。比如一个集群只分dev、test、prod三个namespace,应用维度的隔离通过标签和RBAC模板来做。这样既避免了default里“大锅炖”,又不会让集群的namespace数量膨胀到不可控。
这里没有绝对正确答案。如果你有上百人的研发团队、多个独立产品线,那多namespace是必然选择;如果你只是几个人的小团队,硬造一堆namespace只会给日常操作添乱。关键是搞清楚namespace解决的是“逻辑边界与权限边界”问题,而不是“性能隔离”问题。性能隔离得靠节点池、资源配额和优先级别去解决。
6.2 常用排查命令清单
最后把我日常最常用的一组namespace相关命令整理出来,供大家直接参考:
# 查看所有namespace及其标签 kubectl get ns --show-labels # 查看某个namespace的资源配额和当前用量 kubectl describe resourcequota -n <namespace> # 查看某个namespace下的全部资源 kubectl get all -n <namespace> # 查看某个namespace的NetworkPolicy kubectl get networkpolicies -n <namespace> # 杀掉当前namespace下所有非Running状态的Pod kubectl delete pods -n <namespace> --field-selector=status.phase!=Running # 查看集群中所有namespace的Pod数量分布 kubectl get pods -A --no-headers | awk '{print $1}' | sort | uniq -c | sort -rn最后这个命令在集群资源紧张时特别好用,能一眼看出哪个namespace是Pod大户,再结合ResourceQuota决定要不要限制。有一次我就是靠它发现某个测试namespace里躺着300多个没人管的Pod,这些都是之前某次压测脚本留下的,删完之后集群压力立刻小了一截。
6.3 最后一件事:namespace也要纳入日常巡检
namespace看似是K8s里最基础的对象,但恰恰是基础的东西最容易在运维看板里被忽略。我现在的习惯是每两周做一个集群小巡检,固定检查几项:Terminating状态的namespace、namespace配额使用率超过80%的项、以及没有打任何标签的namespace。发现长期Terminating或者关键namespace配额用满,优先处理。
还有一个容易被忽略的点:namespace里的资源清理要及时。很多人用完临时环境、跑完实验,直接就不管了,namespace留着几个月不删。这些残留的ConfigMap、Secret、PVC虽然单个不大,但积少成多,PVC占用的存储和配额都是钱,也是未来排查问题时最烦人的干扰项。定期清理临时namespace,比写一大堆告警规则更省钱省心。
namespace用好了,K8s集群的长期稳定性会明显上一个台阶。它不复杂,但值得认真对待。每次有人问我“K8s入门之后应该学什么”,我给的答案里永远都有这一项:先把namespace、配额、权限这套基础边界理顺,再往上谈服务网格、可观测性,地基才稳。