Kubernetes落地实战:架构、部署排错与资源限额自检
简介:这是一份面向容器编排初学者、后端开发者及运维人员的 Kubernetes 课件,围绕集群基础概念、核心组件、监控与日志、应用生命周期管理、调度、存储和集群安全等主题展开,可用于课堂讲授、自学梳理或团队内部分享。压缩包内含 1 个 PPTX 文件,大小约 1.25MB,以幻灯片形式系统组织知识点,目录清晰,便于按章节浏览、复习和二次编辑;内容从集群架构中的接口服务、控制器管理、调度器、键值存储、节点代理和网络代理等组件讲起,并解释最小部署单元、控制器、服务、标签和命名空间等核心对象。课件还结合常用命令介绍集群状态查看、资源利用率监控、组件与应用日志管理,以及应用的创建、发布、升级、弹性伸缩和回滚流程,有助于读者建立较完整的知识框架。已有 2191 人学习,适合作为入门阶段的提纲式参考。
1. 从一份 Kubernetes 课件说起:它补的是概念,缺的是落地那一段
第一次翻这份 Kubernetes 课件,我先跳到目录末尾,看到「调度、存储、集群安全」三个词就放心了——它不是那种只讲 Pod 和 Deployment 的入门幻灯片。k8s 这东西概念图谁都能画,难的是从kubectl敲下去到容器真的跑起来这一整条链。课件把内容切得很实在:基本概念、监控与日志、应用生命周期管理、调度、存储、集群安全,基本对应一个运维从「能把服务起起来」到「出事能定位」的六步路。它的问题也明显——课件属性太重,命令给到kubectl rollout undo就停了,参数为什么这么填、填错报什么错,得自己补。下面我按「架构立住 → 跑通一次部署 → Pod 细节 → 监控排错 → 三块自检」的顺序把它拆开,刚接手集群的开发能照着敲,已经能跑但总在回滚和排错上翻车的运维能对上参数。
2. 集群架构与核心对象:apiserver 到 kubelet 这条链谁在管什么
这一章是整份课件的骨架。很多人学 k8s 卡住,不是因为记不住七个组件名字,而是不知道一个kubectl apply之后请求在集群里怎么流转、卡在哪一环该去看谁。把这条链捋顺,后面所有排错都有落点。
2.1 控制面四组件:apiserver 是唯一入口,etcd 是唯一真相
kube-apiserver 是整个集群的统一入口,对外用 RESTful API 提供服务,所有对象资源的增删改查和 watch 监听都先交给它,再由它提交给 etcd 存储。这句话的分量在于「唯一」两个字:kubelet 不直接找 scheduler,controller-manager 也不直接改 etcd,任何组件想动集群状态,都得走 apiserver。所以排查问题的第一现场永远是 apiserver 的日志和它的健康端点。
kube-controller-manager 是一堆控制器的集合体。它的工作模式是「期望状态」和「实际状态」的差量收敛:你声明 Deployment 要 3 个副本,Deployment controller 创建 ReplicaSet,ReplicaSet controller 再去创建或删除 Pod,Node controller 盯着节点心跳,Endpoints controller 维护 Service 和后端 Pod 的对应关系。一个资源对应一个控制器,controller-manager 管的就是这些控制器。
kube-scheduler 负责给新创建、还没绑定节点的 Pod 选一个 Node。它的算法分两段:先过滤掉不满足条件的节点(资源不够、亲和性不匹配、污点不容忍),再对剩下的节点打分排序,选最高分。这里有个常见误解——调度只是「决定」,真正把 Pod 跑起来的是目标节点上的 kubelet。
etcd 是分布式键值存储,保存的是整个集群的状态数据,Pod、Service、Secret、ConfigMap 全在里面。它是唯一可以直接读写 etcd 的组件所依赖的底座。我一般会跟人说:集群挂了可以重建,etcd 数据没了就是真的没了,所以备份是唯一的后悔药。kubeadm 部署的集群,常见做法是这样备一份:
# kubeadm 部署的集群,证书路径一般是下面这几个,按实际环境改 ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key # 校验快照是否完整,别等恢复的时候才发现文件是坏的 ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-xxxx.db -w table--endpoints指向本机 etcd 的 2379 端口,三个证书参数缺一不可,少一个就是context deadline exceeded或者证书校验失败。snapshot status那一步别省,我见过备份脚本跑了一年,实际写出来的是 0 字节文件,恢复时才发现,那才叫血泪经验。
登录到控制面机器上,验证这几件事基本能确认控制面是活的:
# 集群信息和 apiserver 地址 kubectl cluster-info # 控制面组件的 Pod 状态,静态 Pod 一般都在 kube-system 下 kubectl get pods -n kube-system -o wide # apiserver 的逐项健康检查,比 /healthz 信息量大得多 kubectl get --raw '/readyz?verbose'/readyz?verbose会逐条列出 etcd、准入控制、控制器等子检查项的通过情况,哪一项[-]失败就直接指向对应组件,比盲翻日志快很多。
2.2 Node 侧三件套:kubelet 拉容器、kube-proxy 写规则、引擎干活
kubelet 是 Master 放在 Node 上的 Agent,它做几件事:向 apiserver 注册节点、watch 到绑定到本节点的 Pod、管理本机容器的生命周期(创建容器、挂载数据卷、下载 Secret)、周期性上报节点和容器状态。注意 kubelet 本身不直接操作容器运行时,它通过 CRI 接口喊话,底下接的是 containerd 或者 docker。
kube-proxy 在 Node 上实现 Pod 网络的代理,维护 iptables 或 ipvs 规则,完成 Service 的四层负载均衡。它不转发流量本身(ipvs 模式下尤其如此),它做的是「把规则写对」。所以 Service 访问不通时,kubectl get endpoints为空说明是 selector 的问题,Endpoints 有值但访问超时,才轮到怀疑 kube-proxy 的规则。
容器引擎(containerd / docker)是最终真正跑容器的那一层。课件里写的是 docker 或 rocket,现在主流新集群基本是 containerd,但排查思路不变:容器起不来,crictl或docker那一层的日志永远是最后一环。
# kubelet 是不是活着,重启过几次 systemctl status kubelet # 实时跟 kubelet 日志,Pod 调度上来却起不来时最常看的地方 journalctl -u kubelet -f --no-pager # 节点状况,重点看 MemoryPressure / DiskPressure / Ready 三个 Condition kubectl describe node <node-name> | grep -A 8 Conditionsjournalctl -u kubelet里出现failed to pull image就是镜像问题,出现failed to create pod sandbox多半是 CNI 网络插件没就绪,两类错误的处理路径完全不同,别混着查。
2.3 五个对象的咬合关系:Label 是胶水,Endpoints 是证据
课件把 Pod、Controllers、Service、Label、Namespace 列成五个并列的概念,实际它们是咬合在一起的。Pod 是最小部署单元,是一组容器的集合,同一个 Pod 内的容器共享网络命名空间,Pod 本身是短暂的——这四条决定了后面所有的设计取向。
Controllers 决定 Pod 怎么被管。Deployment 管无状态应用,支持滚动更新和回滚;StatefulSet 管有状态应用,给每个副本稳定的网络标识和有序的启停;DaemonSet 保证每个节点跑一个副本,日志采集和网络插件常用它;Job 是一次性任务;CronJob 是定时任务。选错控制器是新手最常见的返工点,比如拿 Deployment 去跑数据库,Pod 一重建存储和网络标识就全乱。
Service 解决的是「Pod 会失联」这个问题。它通过 Label Selector 匹配一组 Pod,给它们一个固定入口和访问策略。Label 是附加到资源上的键值对,用来关联、查询和筛选;Namespace 把对象在逻辑上分开,但要注意——Namespace 只做对象层面的隔离,网络默认并不隔离,要真隔离得靠 NetworkPolicy。
# 看 Pod 带的标签 kubectl get pods --show-labels # 按标签筛选 kubectl get pods -l app=nginx # 最关键的一条:Service 到底有没有匹配到 Pod kubectl get endpoints webkubectl get endpoints web输出的ENDPOINTS列如果显示<none>,说明 selector 和 Pod 标签对不上,这时候不要去查网络,先回去对标签。这个命令我基本上每次排查 Service 都是第一个敲的。
下面这张表是我自己整理的对象职责对照,贴在显示器边上那种:
| 对象 | 解决的问题 | 关键字段 | 出问题先看 |
|---|---|---|---|
| Pod | 最小调度与运行单元 | spec.containers、volumes | describe pod的 Events |
| Deployment | 无状态副本数与滚动更新 | replicas、selector、strategy | rollout status、ReplicaSet |
| StatefulSet | 有状态、稳定标识、有序启停 | serviceName、volumeClaimTemplates | PVC 是否 Pending |
| DaemonSet | 每节点一份 | tolerations、nodeSelector | 节点污点是否容忍 |
| Service | 固定入口与四层负载 | selector、port、targetPort | get endpoints |
3. 用命令行与 YAML 跑通一次部署:从 create 到 NodePort 暴露
架构讲完就得动手。这一章的目标很具体:不打开任何编辑器,纯命令行把一个服务从零跑起来、暴露出去,再用 YAML 复现一遍,最后走完升级、扩缩容、回滚、删除的完整生命周期。这套动作跑顺了,课件第三章的内容就算真的落地了。
3.1 命令行五分钟起服务并暴露
先从最省事的路径开始。用kubectl create deployment直接生成一个 Deployment,再expose把端口放出去:
# 创建 Deployment,指定镜像和副本数(镜像换成你自己的) kubectl create deployment web --image=demo/java-app:1.0 --replicas=2 # 确认 Deployment 和 Pod 都起来了,-o wide 能看到 Pod 落在哪个节点 kubectl get deploy,pods -o wide # 暴露为 NodePort 类型,集群外可通过 节点IP:NodePort 访问 kubectl expose deployment web \ --port=80 \ --target-port=8080 \ --type=NodePort \ --name=web # 拿分配到的 NodePort kubectl get svc web这里四个参数必须分清:--port是 Service 自己监听的端口,--target-port是容器实际监听的端口,--type=NodePort表示在每个节点上开一个高位端口转发进来,--name不给的话默认跟 Deployment 同名但容易和别的资源撞。target-port填错是最常见的翻车点——Pod 明明 Running,Service 也能查到,访问就是 502 或超时,因为流量转到了容器没监听的端口上。
kubectl get svc web输出里PORT(S)列会写成80:31234/TCP这种形式,冒号后面那个 31234 就是 NodePort,默认范围 30000–32767。集群如果开了防火墙或者安全组,这个范围得放行,否则内网其他机器访问不了,本机curl却正常,很容易误判成 Service 有问题。
3.2 YAML 字段拆解:apiVersion、selector 与 template 是强绑定关系
命令行适合试手,真上生产还得写 YAML。课件里给的 Deployment 模板是全篇最有价值的片段之一,我把它补上注释重新贴一遍:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default spec: replicas: 3 # 期望副本数,controller 会持续向这个数字收敛 selector: matchLabels: app: nginx # 必须和下面 template 里的 labels 完全对得上 template: # Pod 模板,改了它才会触发滚动更新 metadata: labels: app: nginx # 这个标签是 selector 匹配的唯一依据 spec: containers: - name: nginx image: nginx:1.15 ports: - containerPort: 80 # 仅声明用途,不做端口限制,但 Service 要靠它对齐三个字段值得单独说。apiVersion: apps/v1是当前 Deployment 的稳定版本,老课件里常见的extensions/v1beta1在新集群上直接报no matches for kind。selector.matchLabels在apps/v1里是必填项且创建后不可修改,它和template.metadata.labels必须严格一致,不一致的话 apiserver 会拒绝创建,报selector does not match template labels。template是唯一能触发滚动更新的部分——你改replicas只是伸缩,改template里的镜像或环境变量才会生成新的 ReplicaSet。
不确定字段怎么写的时候,别去搜博客,直接问集群:
# 逐层展开字段说明,比翻文档准 kubectl explain deployment.spec.strategy kubectl explain deployment.spec.template.spec.containers.resources # 只校验不提交,写完 YAML 先干跑一遍 kubectl apply -f deploy.yaml --dry-run=server--dry-run=server会把请求真的发给 apiserver 做准入校验但不落库,比--dry-run=client靠谱得多,能提前抓到 selector 不匹配、字段拼错这类问题。
3.3 升级、扩缩容、回滚、删除:一条不能颠倒的操作序列
课件第三章给了完整的命令清单,我按实际使用顺序重排一遍,并且补上每步该盯什么:
# 1. 升级镜像,注意这里写的是容器名 nginx,不是 Deployment 名 kubectl set image deployment/web nginx=nginx:1.15 # 2. 阻塞式观察滚动更新,成功返回 0,失败会卡住并给出原因 kubectl rollout status deployment/web --timeout=120s # 3. 看历史版本,CHANGE-CAUSE 列默认是空的,除非加 --record 或写注解 kubectl rollout history deployment/web # 4. 回滚到上一个版本,或指定某个 revision kubectl rollout undo deployment/web kubectl rollout undo deployment/web --to-revision=2 # 5. 手动伸缩(临时验证用) kubectl scale deployment web --replicas=10 # 6. 自动伸缩,CPU 超过 80% 就在 10 到 15 之间调整 kubectl autoscale deployment web --min=10 --max=15 --cpu-percent=80 # 7. 清理,两个资源要分别删 kubectl delete deploy/web kubectl delete svc/web有两个参数需要点出来。滚动更新的节奏由spec.strategy.rollingUpdate下的maxSurge和maxUnavailable控制,默认分别是 25%,意味着 4 副本的 Deployment 在更新期间最多会有 5 个 Pod 同时存在、最少保证 3 个可用;副本数少的时候(比如 2 个)这个比例算下来会变成「先起新的再删旧的」,更新期间资源会短暂超配,节点资源紧张时容易触发驱逐。
另一个是revisionHistoryLimit,默认保留 10 个旧 ReplicaSet。如果你做过十几次发布再想回滚到很早的版本,会发现rollout history里那个 revision 已经没了,--to-revision直接报unable to find。要长期保留就从一开始把这个值调大,或者干脆别依赖 ReplicaSet 历史,靠 Git 里的 YAML 版本回退更可控。
kubectl autoscale建出来的 HPA 依赖 Metrics API,如果集群里没装 metrics-server,HPA 会一直显示<unknown>/80%且不触发伸缩——这个问题我在第 5 章细说。
4. Pod 的共享机制与资源限额:infra container 和 requests/limits 到底怎么算
课件第四章讲 Pod 对象存在的意义和实现机制,这段内容被很多人跳过,但它决定了你对「一个 Pod 里的容器到底共享什么、不共享什么」的判断准不准,也决定了你写resources时是在拍脑袋还是在算账。
4.1 infra container:Pod 里为什么只能有一个网络视图
Pod 本身是个逻辑概念,没有对应的实体进程,它落地靠的是 infra container(也叫 pause 容器)。kubelet 创建 Pod 时,先起一个极小的 pause 容器,由它持有这个 Pod 的 network namespace,然后其他业务容器通过加入这个命名空间的方式「接进来」。结果是:同 Pod 内所有容器看到的网络设备、IP 地址、MAC 地址完全一样,彼此可以通过127.0.0.1通信。
这就是课件里说的「亲密性应用」场景的由来:两个应用之间有文件交互、需要通过 localhost 或 socket 通信(典型组合是 nginx + php-fpm)、或者调用极其频繁不想走网络栈,都适合放进同一个 Pod。nginx 和 php-fpm 走127.0.0.1:9000是最经典的例子。
但共享网络不等于共享文件系统。容器之间的文件系统依然是隔离的,要共享存储必须显式挂同一个 Volume。这一点课件写的是「通过共享容器的挂载实现存储共享」,表述有点绕,实际操作是给两个容器各写一段volumeMounts指向同一个volumes名字:
apiVersion: v1 kind: Pod metadata: name: share-demo spec: containers: - name: web image: nginx:1.15 volumeMounts: - name: logs mountPath: /var/log/nginx # nginx 往这里写日志 - name: tail image: busybox:1.36 command: ["sh", "-c", "tail -f /logs/access.log"] volumeMounts: - name: logs mountPath: /logs # 边车容器从同一个卷读日志 volumes: - name: logs emptyDir: {} # 生命周期跟随 Pod,Pod 删除即清空两个容器的mountPath故意写成不一样,这是有意的:卷共享的是底层目录,挂载点是各自独立的,所以 web 写到/var/log/nginx/access.log,tail 从/logs/access.log就能读到同一份内容。emptyDir适合这种临时中转,如果日志要留到 Pod 删除之后,得换hostPath或者 PVC。
验证共享网络最直接的办法是进容器看网卡:
# 看 Pod 里所有容器的名字,确认 pause 容器不在这里(它是隐式的) kubectl get pod share-demo -o jsonpath='{.spec.containers[*].name}' # 进第一个容器看 IP,再进第二个看,两者应该完全一致 kubectl exec -it share-demo -c web -- ip addr kubectl exec -it share-demo -c tail -- ip addr第二个命令在 busybox 里可能没有ip,换成hostname -i也行,输出的是同一个 Pod IP。
4.2 imagePullPolicy 的三个取值:默认值不是固定的 IfNotPresent
课件列了三个取值,但漏了一个关键细节:默认值取决于镜像标签。规则是——如果镜像标签是latest或者没写标签,默认Always;如果写了具体标签(比如nginx:1.15),默认IfNotPresent。很多人以为默认永远是IfNotPresent,结果用了:latest的镜像,每次重建 Pod 都去拉一遍,在弱网或者仓库限流的环境下就是漫长的ImagePullBackOff。
三个取值的语义很清晰:IfNotPresent是宿主机上没有才拉,Always是每次创建 Pod 都重新拉,Never是永不主动拉、只用在本地已经预置镜像的场景(比如离线环境或者用 DaemonSet 预热过的节点)。私有仓库还要配imagePullSecrets:
apiVersion: v1 kind: Pod metadata: name: my-pod spec: imagePullSecrets: - name: myregistrykey # 这个 secret 必须和 Pod 在同一个 namespace containers: - name: java image: demo/java-app:1.0 imagePullPolicy: IfNotPresentmyregistrykey是用kubectl create secret docker-registry建出来的,类型是kubernetes.io/dockerconfigjson。这里有个高频坑:secret 是 namespace 级别的,Pod 在default、secret 建在kube-system,就会报secret "myregistrykey" not found。另一个更省事的做法是把 secret 挂到 ServiceAccount 的imagePullSecrets上,这样该 namespace 下所有用这个 SA 的 Pod 都不用重复写。
4.3 requests/limits 的算账方式与 QoS 分级
课件这一段把两个概念写反了——它把limits标成「申请配额」、把requests标成「限制配额」,实际正好相反。requests是容器启动时向调度器申请的保底资源,调度器拿它来判断节点还剩多少可分配量;limits是容器运行时的硬上限,超了会动手。这个反转如果照着背,后面配 HPA 和排查 OOM 会一路错下去。
CPU 和内存超限的后果完全不同。CPU 是可压缩资源,超过limits.cpu不会杀容器,只会被 cgroup 限流(throttle),表现是接口变慢、延迟毛刺;内存是不可压缩资源,超过limits.memory直接 OOMKilled,Pod 重启,describe pod里能看到Last State: Terminated, Reason: OOMKilled。
apiVersion: v1 kind: Pod metadata: name: web spec: containers: - name: java image: demo/java-app:1.0 resources: requests: # 调度依据,节点剩余资源必须满足它 memory: "64Mi" cpu: "250m" # 250m = 0.25 核,1000m 才是 1 核 limits: # 运行时上限,内存超了直接 OOMKilled memory: "128Mi" cpu: "500m"这两个值怎么定,我的习惯是先压测再定:Java 应用先给requests等于实际稳态用量的 1.2 倍,limits.memory给稳态峰值的 1.5 到 2 倍留出 GC 和堆外内存的空间,limits.cpu可以给到requests的 2 倍容忍突发。定完跑一轮压测,用kubectl top pod看实际消耗再回调。
这组配置还决定了 Pod 的 QoS 等级,节点资源紧张时按等级驱逐:
| QoS 等级 | 条件 | 驱逐优先级 |
|---|---|---|
| Guaranteed | 每个容器都设了 requests 和 limits,且两者相等(CPU 和内存都要) | 最低,最后被驱逐 |
| Burstable | 至少一个容器设了 requests 或 limits,但不满足 Guaranteed | 中等 |
| BestEffort | 所有容器都没设 requests 和 limits | 最高,优先被驱逐 |
核心业务配成 Guaranteed 不是洁癖,是被驱逐过一次之后的自然反应。反过来,临时任务、批处理配 BestEffort 反而合理,被驱逐了重跑就行。
排查资源问题常敲这几条:
# 看 Pod 实际的 requests/limits 和当前用量 kubectl describe pod web | grep -A 6 -E "Limits|Requests" # 需要 metrics-server 支持,没有会报 Metrics API not available kubectl top pod web --containers5. 监控日志与排查:kubectl top 没数据背后的五个坑
课件第二章给了一串命令,从kubectl cluster-info到kubectl top pod,看着都对,但真到集群里敲,kubectl top大概率直接报错。这一章把监控链路拆开,再把我在实际环境里反复踩到的几个坑按「现象 → 原因 → 解决」列清楚。
5.1 Metrics Server 与 cAdvisor 的数据链路
kubectl top不是 apiserver 自带的能力,它查的是metrics.k8s.io这个聚合 API,背后由 Metrics Server 提供数据。链路是这样:每个节点上的 kubelet 内置了 cAdvisor,负责采集本节点容器的 CPU、内存等指标,通过 Summary API 暴露出来;Metrics Server 周期性向所有 kubelet 拉取,聚合成 Pod 和 Node 两个维度的数据,再注册成 apiserver 的一个 APIService。所以这条链上任何一环断了,kubectl top都会失败。
# 确认聚合 API 注册上了,STATUS 要是 True kubectl get apiservice | grep metrics # metrics-server 的 Pod 是否 Running,重启次数是否在涨 kubectl get pods -n kube-system -l k8s-app=metrics-server # 日志里最常出现的两类错误:x509 证书校验失败、连不上 kubelet 10250 kubectl logs -n kube-system deploy/metrics-server --tail=50 # 数据通了之后,这两条才有意义 kubectl top node kubectl top pod -A --sort-by=cpu | head -20--sort-by=cpu配上head是我每周巡检的固定动作,能一眼看出谁在偷吃资源。注意kubectl top pod默认只查当前 namespace,跨命名空间要加-A,这一点和kubectl get pods的行为一致,但经常有人忘了加然后以为没数据。
5.2 五条踩坑记录:从现象倒推原因
第一条,error: Metrics API not available。现象是kubectl top直接报这句,kubectl get apiservice | grep metrics能看到v1beta1.metrics.k8s.io但 STATUS 是 False。原因通常是 metrics-server 没装,或者装了但起不来。解决是先看 Pod 状态,如果是CrashLoopBackOff,八成是证书问题——metrics-server 访问 kubelet 的 10250 端口时,自签证书校验不过。常见做法是给 Deployment 加一个启动参数--kubelet-insecure-tls(仅限测试环境,生产该配 CA),然后删掉 Pod 让它重建。
第二条,Pod 一直ImagePullBackOff。现象是kubectl get pods显示ImagePullBackOff或ErrImagePull,describe pod的 Events 里能看到Failed to pull image。原因要么是镜像名或标签写错,要么是私有仓库没配imagePullSecrets,要么是节点根本连不上仓库。解决顺序是先kubectl describe pod看 Events 里的完整报错,区分是manifest unknown(镜像不存在)还是unauthorized(认证失败)还是i/o timeout(网络问题),三类错误的处理路径完全不同。
第三条,Pod 反复重启,RESTARTS一直涨。现象是 Pod 状态在Running和CrashLoopBackOff之间跳,describe pod里Last State显示Reason: OOMKilled。原因是limits.memory设小了,应用启动或处理大请求时被内核干掉。解决是先kubectl top pod看实际峰值,把limits.memory调到峰值的 1.5 倍以上;同时检查 JVM 的-Xmx有没有超过容器 limit,容器里-Xmx不设或者按宿主机内存算,是 Java 应用 OOMKilled 的头号原因。
第四条,Service 访问不通但 Pod 是 Running。现象是curlService 的 ClusterIP 超时,但kubectl get pods一切正常。原因大概率是 selector 和 Pod 标签对不上,导致 Endpoints 为空。解决是kubectl get endpoints <svc-name>,输出<none>就回去核对spec.selector和template.metadata.labels;如果 Endpoints 有值但访问还是不通,再查targetPort是否等于容器的containerPort,最后才怀疑 kube-proxy。
第五条,回滚时找不到目标版本。现象是kubectl rollout undo deployment/web --to-revision=2报unable to find revision。原因是revisionHistoryLimit默认只有 10,发布次数超过之后旧 ReplicaSet 被 GC 掉了。解决是把revisionHistoryLimit调到 20 或更大,同时别把kubectl rollout history当成发布记录——它只在加了--record或者给 Pod 模板写了kubernetes.io/change-cause注解时才有 CHANGE-CAUSE 内容,否则只有 revision 号,回滚时根本分不清哪版是哪版。
5.3 三种日志的取法:组件日志、标准输出、落盘日志
课件的日志部分分了三层,这个分层是对的,值得展开。k8s 组件的日志要分两类看:用 systemd 托管的(kubelet 就是),走journalctl -u kubelet;以静态 Pod 形式跑的(kube-proxy、etcd、apiserver 在 kubeadm 集群里都是),走kubectl logs:
# systemd 托管的组件 journalctl -u kubelet --since "30 min ago" --no-pager # 静态 Pod 形式的组件,名字后面带随机后缀,用 labels 筛更稳 kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=100 # 系统层面的日志,节点内核问题看这里 tail -n 200 /var/log/messages应用日志走容器标准输出是最省事的,kubelet 会把 stdout/stderr 落到节点磁盘上并按大小轮转:
# 当前日志 kubectl logs <pod-name> # 实时跟,等价于 tail -f kubectl logs -f <pod-name> # 多容器 Pod 必须用 -c 指定容器,否则默认第一个 kubectl logs -f <pod-name> -c <container-name> # 看上一个容器实例的日志,Pod 重启后查崩溃原因必用 kubectl logs <pod-name> --previous--previous这个参数救过我很多次:Pod 因为启动失败反复重启时,kubectl logs拿到的可能是刚起来还没输出就挂掉的那个实例,加上--previous才能看到真正报错的那一份。
如果应用不往标准输出写,只往文件写,就得挂hostPath把日志落到节点上:
apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: web image: nginx:1.15 volumeMounts: - name: logs mountPath: /usr/local/nginx/logs volumes: - name: logs hostPath: path: /tmp/logs type: DirectoryOrCreate # 目录不存在时自动创建课件里给的type: Directory要求目录必须提前存在,不存在的话 Pod 会一直ContainerCreating并报hostPath type check failed。改成DirectoryOrCreate省事得多,代价是它会自动建目录、权限按默认走,生产环境还是建议提前规划好目录并显式设type: Directory,避免权限问题。
6. 把调度、存储、安全串成一条自检链:课件没展开的三块怎么验
课件目录里列了调度、存储、集群安全,正文却没细讲,这三块恰恰是集群从「能跑」到「跑得稳」的分水岭。我的习惯是准备一份十分钟能跑完的自检清单,接一个新集群或者排查完一次故障都过一遍。
调度这块,Pod卡在Pending时别急着看节点资源,先看 Events:
kubectl describe pod <pod-name> | grep -A 10 Events如果看到0/3 nodes are available: 3 Insufficient cpu,是资源不够,回去核requests;看到node(s) had taint {key: value},是节点有污点没容忍,用kubectl describe node <node> | grep -i taint确认;看到node(s) didn't match Pod's node affinity,是nodeSelector或亲和性写死了不存在的标签。这三种错误的修法完全不同,全靠 Events 那一行区分。
存储这块,PVC 长时间Pending是最常见的表现:
kubectl get pvc -A kubectl describe pvc <pvc-name> -n <ns>Events里说no persistent volumes available for this claim,说明没有匹配的 PV,检查容量、accessModes、storageClassName三个属性是否和已有 PV 对得上——这三个必须全部匹配,差一个都绑不上。如果用了动态供给,就去看 StorageClass 的 provisioner Pod 是不是活着。
安全这块最容易被跳过。课件提到集群安全但没展开,实际最该先验的是 ServiceAccount 的权限边界:
# 以某个 ServiceAccount 的身份试探权限,不改任何实际状态 kubectl auth can-i get pods \ --as=system:serviceaccount:default:demo-sa -n default # 反过来查这个 SA 到底被授了哪些权限 kubectl auth can-i --list \ --as=system:serviceaccount:default:demo-sa -n default第二条命令输出的是完整的权限清单,我一般会顺手扫一眼有没有*.*这种通配授权——有的话基本可以认定是早期图省事留下的,该收就收。
把三块合成一张自检表,贴在工位上比翻文档快:
| 自检项 | 触发命令 | 关键判据 | 常见结论 |
|---|---|---|---|
| 调度 | describe pod看 Events | FailedScheduling的具体原因 | 资源不足 / 污点未容忍 / 亲和性不匹配 |
| 存储 | get pvc+describe pvc | 是否 Pending | 容量、accessModes、StorageClass 三者不匹配 |
| 权限 | auth can-i --list | 是否有通配授权 | ServiceAccount 权限过大需收敛 |
| 网络 | get endpoints | 是否为<none> | selector 与 Pod 标签不一致 |
这套自检我是从一次事故之后固定下来的。当时一个服务突然全量不可用,二十多个人围着查网络插件、查 kube-proxy、查节点状态,最后发现是有人改 Deployment 的时候顺手把template.metadata.labels的app值从web改成了web-v2,而 Service 的 selector 还写着app: web,Endpoints 直接空了。整件事的定位时间不到三十秒——如果第一时间敲了kubectl get endpoints。从那以后我每次改完任何带 selector 的 YAML,都以kubectl get endpoints <svc-name>的输出作为发布完成的最后一道确认,不管前面rollout status显示多绿。希望这个习惯也能帮到你。
本文还有配套的精品资源,点击获取