ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kubernetes Namespace与Pod实战:资源配额、调度与故障排查全解析

Kubernetes Namespace与Pod实战:资源配额、调度与故障排查全解析 很多刚开始接触 Kubernetes 的朋友都会在 Namespace 和 Pod 这两个对象上绕弯子。一个是看似简单的“逻辑分区”另一个是天天挂在嘴边的“最小调度单元”但真正用起来配额怎么算、Pod 里为什么能塞多个容器、kubectl describe一大堆输出到底看哪行这些问题不实践几次根本拎不清。这篇文章我就结合自己带团队、搭环境、救故障的真实经验把 Namespace 和 Pod 从概念到实操完整拆一遍适合刚过完 k8s 安装、准备认真用起来的同学也适合那些已经在用但遇到 Pod 起不来、资源不够用等问题的运维和开发。1. 为什么新人总卡在 Namespace 和 Pod——先弄懂“资源”和“对象”的关系1.1 两个最常见的误解我面试过不少人也带过不少新人对 Namespace 和 Pod 的误解基本集中在两个地方。第一个误解是“Namespace 就是个文件夹”。这句话对不对对了一半。Namespace 确实起到归类、隔离的作用但它是 API 层面的隔离不是文件系统层面。你在一个 Namespace 里创建的资源对另一个 Namespace 默认不可见这种“逻辑边界”是通过 etcd 的存储前缀和 API Server 的校验来保证的。更关键的是Namespace 不仅仅是命名空间它还能绑定资源配额、绑定 RBAC 权限、绑定网络策略这些东西是“文件夹”三个字完全无法涵盖的。第二个误解是“Pod 就是容器”。这句话错得比较离谱偏偏流传最广。Pod 是 k8s 里调度的最小单位容器只是 Pod 里的一个运行实体。一个 Pod 可以只装一个容器也可以装多个容器这几个容器共享同一个网络命名空间、共享同一批存储卷生命周期绑定。你如果只是把 Pod 理解成“包了一层壳的容器”后面看 Service、看网络策略、看 Sidecar 模式都会觉得别扭。1.2 从“资源”到“对象”k8s 到底在管理什么Kubernetes 里几乎所有东西都是“API 对象”Namespace 和 Pod 也不例外。所谓对象就是一段结构化数据它描述了“我希望系统处于什么状态”。你写一份 YAML 提交给 API ServerAPI Server 把它存进 etcd然后控制器和调度器不断工作让实际状态向期望状态收敛。画个简单的链路感受一下你kubectl apply一个 Pod 的 YAML请求打到 API ServerAPI Server 做认证、鉴权、校验把 Pod 对象写入 etcdScheduler 监听 API Server发现一个没有被调度的 Pod开始打分选节点选中的节点上的 Kubelet 监听到调度结果通过容器运行时把 Pod 真正拉起来这里有几个关键点声明式而非命令式你告诉 k8s“我要一个 Nginx Pod”而不是“给我运行这个命令启动 Nginx”。所有信息都通过 API 传递Kubelet 不是直接和 Scheduler 通信而是都通过 API Server 和 etcd 中转。这个设计保证了组件解耦但也意味着 API Server 是绝对的核心它挂了一切都停摆。资源配额、权限控制、网络策略都是围绕对象打交道的Namespace 是对象Pod 也是对象它们之间通过metadata.namespace字段建立归属关系。理解这层之后你再看 k8s 的文档或者排查问题思路会顺很多。因为所有的kubectl get、kubectl describe、kubectl logs本质都是在对 API 对象做查询和操作。2. Namespace 不只是“隔离环境”配额、权限、多租户都靠它2.1 默认的四个 Namespace 和它们的脾气刚装完集群你用kubectl get namespace会看到四个 Namespace很多人第一次看到是懵的不知道它们有什么区别。我挨个讲一下Namespace作用使用注意default默认空间不指定 Namespace 时资源都进这里生产环境尽量避免直接往 default 里丢资源kube-system系统组件栖息地kube-proxy、CoreDNS、calico 等都在这里面没事别乱动也别把业务资源放进来kube-public公开数据所有用户都可读通常放集群公共信息kube-node-lease节点心跳专用每个节点在这里有一个 Lease 对象用于节点健康检查你不需要手动操作从经验上讲kube-system里的东西能不能动能但要有心理准备。比如升级集群、换网络插件都得跟它打交道。初学阶段最好只看不改尤其是别在这里面删东西。2.2 创建 Namespace 的几种方式创建 Namespace 很简单方法至少有三种# 方式一命令行直接创建 kubectl create namespace dev # 方式二YAML 声明式创建 cat EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: dev EOF # 方式三如果用了 Helm通常 values 里配一个 namespace我个人的习惯是尽量用第二种因为 YAML 可以纳入 Git 仓库方便审计和回滚。kubectl create适合临时验证不适合长期管理。2.3 ResourceQuota配额不是摆设配错了 Pod 会被卡死只创建 Namespace 不配配额等于没做隔离。因为同一个集群里的节点资源是共享的没有配额约束一个 Namespace 可以把整个集群的内存吃光。生产环境里这个问题尤其严重我之前就见过一个开发环境因为有人部署了一个内存炸弹式的任务把整台节点打到 NotReady所有人都跟着遭殃。ResourceQuota 是 Namespace 级别的资源总量限制。举个例子apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 4 requests.memory: 8Gi limits.cpu: 8 limits.memory: 16Gi pods: 20 services: 10这个配额的含义是该 Namespace 下所有 Pod 的requests.cpu总和不能超过 4 核requests.memory总和不能超过 8Gi所有 Pod 的limits.cpu总和不能超过 8 核limits.memory总和不能超过 16Gi最多只能有 20 个 Pod10 个 Service这里有一个非常值得注意的坑一旦设置了 ResourceQuota再创建 Pod 时如果 Pod 没有写明 requests 和 limits创建会直接失败。因为配额系统无法判断这个 Pod 到底占用多少资源。报错信息类似Error creating: pods xxx is forbidden: failed quota: dev-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory很多人第一次遇到这个报错会一脸懵以为是配额不够其实是 Spec 字段不完整。解决办法有两个一是所有 Pod 都规范地写 requests 和 limits二是配合 LimitRange 设置默认值。比如apiVersion: v1 kind: LimitRange metadata: name: dev-limit-range namespace: dev spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 200m memory: 256Mi type: Container设置之后如果 Pod 没写资源请求就会自动带上默认值不会再被配额系统拒绝。我强烈建议这两个对象一起使用否则你的团队会频繁踩到“忘了写 requests 导致创建失败”的坑。2.4 Namespace 和 RBAC 的联动Namespace 隔离的不只是资源还有权限。RBAC 里 Role 和 RoleBinding 都是 Namespace 级别的ClusterRole 和 ClusterRoleBinding 才是集群级别的。这意味着你可以做到“A 组的人只能操作 dev 命名空间B 组的人只能操作 prod 命名空间”。我通常的做法是每个环境一个 Namespacedev、staging、prod每个团队一个 ServiceAccount绑定对应 Namespace 的 Role生产 Namespace 单独配一个只读 Role普通开发默认没写权限这样即使有人误操作影响范围也被限制在单个 Namespace 内不会一把梭把整个集群搞挂。3. 看懂 Pod 的本质容器只是过程Pod 才是 k8s 的最小运行单元3.1 为什么 Pod 里可以放多个容器如果你只用 Docker你的思维习惯是“一个容器就是一个服务”。但到了 k8s 这里逻辑要变一下一个 Pod 才是一个服务实例。Pod 里的多个容器共享同一个网络命名空间也就是说它们共用同一个 IP、同一组端口、共享存储卷、共享资源配额。举一个最常见的 Sidecar 例子主容器是 NginxSidecar 容器是一个日志收集器它把 Nginx 的访问日志从共享卷里读出来转发到日志平台。这两个容器放在同一个 Pod 里意味着它们天生就是“同生共死”的调度到同一台节点一起启动一起停止。如果拆成两个独立 Pod你就要额外处理网络互通、日志共享、生命周期同步等一系列问题非常痛苦。Pod 内部还有一个看不见的“基础设施容器”也就是 pause 容器有时候也叫 infra 容器。它的唯一职责是创建并持有 Pod 的网络命名空间其他容器启动时直接加入这个命名空间。这样设计有个好处就算业务容器崩溃重启Pod 的 IP 也不会变因为 IP 是挂在 pause 容器上的。这也是为什么你在节点上docker ps会看到一堆名字很奇怪、状态是 Exited 的 pause 容器不用管它们那是正常现象。3.2 一份最简单的 Pod YAML 逐行拆解下面这个 YAML 是很多人的第一个 PodapiVersion: v1 kind: Pod metadata: name: nginx-pod namespace: dev labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi拆开看apiVersion: v1Pod 属于 v1 核心 API 组这个版本非常稳定放心用metadata.namePod 名字同一个 Namespace 内必须唯一metadata.namespace指定归属的 Namespace不写就走当前 kubeconfig 里的 context默认是defaultmetadata.labels标签是 k8s 的“索引系统”Service、Deployment 都是通过 label selector 找到对应 Pod 的spec.containers容器列表可以写多个resources.requests调度器要用它来决定把 Pod 放在哪个节点上resources.limits运行时用的资源上限超过会被限制或杀掉3.3 requests 和 limits 搞不清楚线上必出事故我见过不少线上事故根源就是 requests 和 limits 理解不到位。这里我尽量用大白话讲透。requests是“至少要多少”调度器看的是它。假设节点有 4 核 8G上面已有一些 Pod 占了 2 核 4G新 Pod 的 requests 是 2 核 4G调度器发现剩余 2 核 4G 够用就把它调过去。调度只看 requests不会看 limits。limits是“最多能用多少”运行时限制靠的是这个。CPU 是压缩型资源超过 limits 会被限流应用表现为变慢但不会挂。内存是非压缩型资源超过 limits 时容器会被内核 OOM Killer 杀掉表现就是 Pod 突然重启或 CrashLoopBackOff。如果只设 limits 不设 requests会怎样调度器认为这个 Pod 占用的资源是 0很容易把多个大 Pod 调度到同一台节点上结果节点内存被打爆所有 Pod 一起遭殃。反过来只设 requests 不设 limits节点资源会被超额分配某个 Pod 内存爆炸时其他 Pod 也可能被误杀。我的建议是大多数场景下 requests 和 limits 都写两者比例控制在 1.5 到 2 倍以内。这样既能保证调度合理又能防止某个业务把节点资源吃干抹净。还有一些人会用resources.requests做“资源预留”故意把 requests 调高留一部分节点资源给突发流量和系统守护进程缓冲。这也是合理玩法但要注意别预估得太离谱否则节点看起来会用得很浪费。4. Namespace 和 Pod 是怎么配合的调度、网络、故障排查一条线4.1 metadata.namespace 和 DNS 的“隐形约定”Pod 归属到哪个 Namespace由metadata.namespace决定。你在 YAML 里不写它kubectl apply时用的就是当前 context 的 Namespace。所以用kubectl config current-context看一下当前语境很重要不然你以为部署到了 staging实际落在 default。Namespace 还会影响 Pod 之间的访问方式。同一个 Namespace 里Pod 之间可以直接用服务名访问比如http://backend:8080跨 Namespace 就得写完整 DNS 名http://backend.staging.svc.cluster.local:8080格式是service-name.namespace.svc.cluster.local。很多人排查跨服务调用超时半天找不到原因最后发现是 Namespace 写错了服务名解析到了错误环境这种坑我踩过不止一次。4.2 用 Deployment 管理 Pod为什么我不直接创建 Pod直接创建 Pod 意味着你管理的是“裸 Pod”它不会自愈。Pod 所在节点挂了Pod 就没了不会在其他节点重新创建。生产环境没人这么玩通常都是通过 Deployment、StatefulSet 这些高级对象来管理 Pod。Deployment 做的事情是描述期望状态比如“我要 3 个 Nginx Pod镜像版本 1.25”然后由 ReplicaSet 控制器负责维护副本数。Pod 挂了它重新拉起节点故障了它把 Pod 调度到其他可用节点。这也是为什么你在排查问题时看kubectl get pod看到的名字都带着一串随机后缀比如nginx-deploy-7c8b9d6b8b-abcde——前面是 Deployment 和 ReplicaSet 的名字后面是 Pod 的随机标识。下面是一个完整的例子把 Namespace、Deployment、Service 串起来apiVersion: v1 kind: Namespace metadata: name: dev --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: dev spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi --- apiVersion: v1 kind: Service metadata: name: nginx-service namespace: dev spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80这里最关键的是spec.selector.matchLabels和 Service 的spec.selector它们靠app: nginx这个标签找到对应的 Pod。标签不一致Service 后面就没有 Endpoint访问就报错。4.3 集群初始化时的那个“大师级报错”API Server 不健康热搜词里有一条很扎心的“k8s控制节点master初始化显示the api server is not healthy after 4m0.00747357s”。这个报错几乎每个自己搭集群的人都见过我第一次装的时候也卡了整整一个晚上。这个报错发生在kubeadm init阶段意思是kubeadm等了 4 分钟发现 API Server 没有进入健康状态。导致它的原因通常是下面几个容器运行时没配置好kubeadm 需要访问 CRI容器运行时接口如果你用的 containerd 还在用默认配置没把systemd作为 cgroup 驱动API Server 容器反复重启健康检查永远过不了。swap 没关kubeadm 要求关闭 swap虽然现在版本报错提示已经很明显但很多人刚开始都会忽略。网络插件没装kubeadm init成功之后还需要装 CNI 插件Calico、Flannel 等如果没有正常安装节点状态会一直是 NotReady。端口被占用或防火墙挡了6443 端口不通健康检查结果就是访问不到 API Server。排查路径也很固定先看容器运行时状态systemctl status containerd crictl ps -a再看 API Server 容器日志crictl logs api-server-container-id大多数时候问题都集中在 containerd 的 sandbox 镜像拉取失败或者 cgroup 驱动不一致上。你按这个顺序排查比一个个猜要高效得多。5. 部署实战与踩坑记录从 Pod Pending 到 OOM 的完整排查链路5.1 故障现场Pod 一直 Pendingdescribe 输出我却看不懂有一次我们部署一个新的微服务kubectl get pods一直显示 Pending。我第一次排查时习惯性看kubectl logs结果当然是空的因为 Pod 还没启动根本没有日志可看。后来才学会一个铁律Pod 没 Running 之前别先看 logs要看 describe。kubectl describe pod pod-name -n namespace在输出里找Events一段重点关注这几类内容FailedScheduling调度失败可能是节点资源不足、有 taint 没被容忍、或者 PVC 没绑定FailedCreatePodSandBox容器运行时创建沙箱失败网络插件问题居多ImagePullBackOff/ErrImagePull镜像拉不下来检查镜像地址、credential、仓库网络OOMKilled内存超限被内核杀了回来说那次故障。Events 里明确写着0/4 nodes are available: 2 Insufficient cpu, 2 Insufficient memory。一看就明白了requests 设置得太大集群剩余资源不足调度器找不到可用的节点。为什么明明节点看起来挺闲的却还是资源不足因为一个节点上跑了很多其他 Pod虽然它们的实际使用量可能不高但 requests 已经把资源“预定”出去了。调度器看的是预留量不是实时用量。这就是 requests 的威力——它可以保护节点但也会造成“看起来闲、实际满”的假象。5.2 快速定位资源问题的命令组合发生资源问题时我一般用下面这套组合拳效率比较高# 看节点整体资源压力 kubectl top nodes # 看某个 Namespace 下所有 Pod 的资源占用 kubectl top pods -n namespace # 看节点上已分配的 requests/limits 总量 kubectl describe node node-name | grep -A 20 Non-terminated Pods # 看资源相关事件按时间排序 kubectl get events --sort-by.lastTimestamp | grep -i failkubectl describe node输出里有一个Allocated resources区块里面有每个节点 CPU 和内存的Requests和Limits汇总。这个信息非常有用你可以快速判断某台节点是不是已经被“虚拟占满”了。还有一个小技巧如果你想临时把某个 Pod 从节点上挪走可以给节点打上不可调度的标记比如kubectl cordon node-name kubectl drain node-name --ignore-daemonsets处理完问题后记得kubectl uncordon node-name这几个操作在缩容、节点维护、故障处置的时候很常用我建议每个 k8s 使用者都记牢。5.3 ResourceQuota 与 LimitRange 的混合使用案例前面提到 ResourceQuota 配上 LimitRange 才能避免“必须手动写 requests”的尴尬。这里给一个更完整的例子展示在生产环境里我通常怎么搭配使用。假设我要为“数据团队”建一个名为>apiVersion: v1 kind: ResourceQuota metadata: name:># 看上次退出原因 kubectl describe pod pod-name -n namespace在Last State这一帧如果写着TerminatedReason是OOMKilled基本就实锤了。然后确认一下内存 limits 是多少再看应用实际占用可以用kubectl exec -it pod-name -n namespace -- cat /sys/fs/cgroup/memory/memory.peak_usage_in_bytes如果这个数字接近 limits说明应用内存涨上去了要么是内存泄漏要么是配置的堆内存或缓存超过了 limits。解决办法不是一味上调 limits而是先看应用本身有没有问题否则调多大都能给你涨爆。另一个我常用的辅助检查直接上节点看内核日志journalctl -k -f | grep -i oom如果内核日志里同时出现了被杀的进程和对应的 cgroup 路径那就能精确知道是哪个容器、哪个进程吃了内存。把这些信息拉齐了再和开发一起看代码里的内存管理问题效率会高很多。5.5 我对 Namespace 划分和 Pod 配置的习惯总结前前后后被线上故障教育了很多次我现在总结出一套比较稳妥的默认策略不一定适合所有人但可以参考环境与团队双维度建 Namespace比如dev-user、dev-order、staging-payment、prod-core。命名带有明确语义方便权限控制和账单拆分。给每个 Namespace 同时配 ResourceQuota 和 LimitRange没有配额开发环境就会变成“公地悲剧”。有配额后资源问题会在创建资源那一刻暴露而不是等到节点被打爆才后悔。Pod 里的容器尽量只放一个主进程除了 Sidecar 等特殊场景不要动不动把一堆东西塞进一个 Pod。多容器共享网络和存储听着方便但也意味着排障时更难区分问题源头。标签规范要提前定至少包含app、env和version例如appnginx、envprod、versionv1.2.3。标签不仅用于 Service 选择器还用于监控告警、故障隔离、发布策略前期定好规范能省很多事。写到最后顺便分享两个亲身攒下来的经验排查了太多 Namespace 和 Pod 相关的问题后我发现新手最容易犯的错其实不是命令不熟而是习惯用 Docker 的思维去套 k8s。Docker 世界里你关心的是“这个容器跑没跑”k8s 世界里你关心的是“这个对象符不符合期望状态”。Pod 重启了不代表异常这恰恰是控制器在努力把你的期望状态拉回来。理解了这一层你会发现很多“故障”其实是正常机制在工作。第二个经验是任何约束类配置ResourceQuota、LimitRange、NetworkPolicy在加到生产环境之前一定要先在测试环境验证一个完整流程。配额设小了 Pod 起不来设漏了字段创建被拒绝LimitRange 和 Quota 冲突导致 Deployment 一直无法滚动更新——这些问题如果不提前演练上线窗口那几分钟根本来不及查文档。最后给你们一个实用小建议平时多用kubectl explain resourcequota.spec、kubectl explain pod.spec这类命令查官方字段说明比在搜索引擎里找过时博客靠谱得多。k8s 版本迭代快网上很多文章讲的还是几年前的行为只有官方自带的解释和当前集群版本是完全对齐的。祝你们早日把 Namespace 和 Pod 用得滚瓜烂熟少踩几个我踩过的坑。
返回列表