ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Kubernetes课件实战:架构、监控、生命周期与安全排障

Kubernetes课件实战:架构、监控、生命周期与安全排障

简介:一套围绕 Kubernetes 基础知识点整理的 PPT 课件,面向刚接触容器编排、需要系统梳理 k8s 核心概念与常用操作的开发者和运维人员。内容从容器集群管理系统的定位讲起,依次覆盖集群架构与 kube-apiserver、controller-manager、scheduler、Etcd、kubelet、kube-proxy 等组件职责,并延伸到 Pod、Controller、Service、Label、Namespace 等基础对象。课件还结合监控与日志查看、Deployment 应用生命周期管理、调度、存储和集群安全等章节,穿插 kubectl 命令与 YAML 示例,便于课堂讲解或自学时对照理解。整套资源含 1 个 pptx 文件,压缩包约 1.25MB,篇幅精炼、目录结构清楚,适合用作入门培训提纲和知识查漏补缺。目前已有 2191 人学习,可作为 k8s 基础阶段的一份轻量参考材料。

1. 一份课件顶不顶用,看它敢不敢从架构讲到安全

拿到这份 Kubernetes 课件,我先翻了目录:基本概念、监控与日志、应用生命周期管理、调度、存储、集群安全,六块排下来,正好压在 k8s 从「装完能跑」到「敢放业务」之间最容易断档的那一段。市面上不少资料要么停在kubectl get pod,要么一上来就是 Operator 和服务网格,中间那层薄得像纸,学完还是不敢动生产集群。这份课件的价值不在深度,而在于它把组件职责、YAML 骨架、上线回滚命令排在同一条线上,前后能对上号,看完就知道每条命令背后是哪个组件在干活。适合三类人:刚接手集群的运维、要做课程设计的同学、需要把 Deployment 摸熟的后端。下面按「架构认知 → 看得见的监控 → 改得动的生命周期 → 五个翻车现场 → 调度存储安全的最小验证」走一遍。

2. 控制面与节点组件:把架构图拆成一条能验证的调用链

架构图谁都会画,难的是画完之后能指着某条命令说「这一步是 apiserver 在处理,那一步是 kubelet 在报状态」。课件把组件分成控制面和节点侧两拨,这个分法是对的,但真正要落地,得先搞清楚一件事:整个集群里只有一个组件会往 etcd 写数据,其余全是读者或者间接写者。想明白这条,后面排查「为什么我改了 YAML 没生效」「为什么 Pod 一直 Pending」就有方向了。

2.1 六个组件的职责边界,以及谁真正在写 etcd

kube-apiserver是唯一入口,所有对象资源的增删改查和 watch 都走它,认证、鉴权、准入控制也在这层做完,然后才落到 etcd。kube-controller-manager里跑的是一堆控制器,每个控制器盯一种资源,用 list-watch 的方式从 apiserver 拿期望状态,再对比实际状态做调谐——Deployment 控制器发现副本数不够就去建 ReplicaSet,ReplicaSet 控制器发现 Pod 少了就去补。它不直接操作容器,只改对象。

kube-scheduler的活儿比想象中窄:它只负责给新建的 Pod 挑一个 Node,然后把结果写回 Pod 的spec.nodeName字段。容器不是它启的,镜像不是它拉的。真正干活的是节点上的kubelet,它监听 apiserver 上绑定到本节点的 Pod,调用容器引擎创建容器、挂载数据卷、拉取 secret,并定期上报节点和 Pod 状态。kube-proxy负责维护 Service 到 Pod 的四层转发规则,iptables 或 ipvs 模式都行,它不碰业务流量之外的东西。

etcd 是这套体系里唯一有状态、且丢了就真没了的组件,其他组件挂了重启就能恢复,etcd 挂了集群状态就没了。所以我一般会把 etcd 的定时快照和异地备份放在所有运维动作之前,这是后悔药,平时想不起来,出事时救命。

2.2 用三条只读命令验证控制面是活的

# 1. 看各核心组件的健康检查聚合结果 kubectl get --raw /readyz?verbose # 2. 看控制面组件是不是以静态 Pod 形式跑着 kubectl -n kube-system get pods -o wide | grep -E 'apiserver|controller|scheduler|etcd' # 3. 看节点注册状态和版本 kubectl get nodes -o wide

第一条命令打的是 apiserver 的/readyz端点,带?verbose会把每一项依赖(etcd 连通性、准入插件、informers 同步)逐条列出来,哪一项 fail 一目了然。老版本课件里可能写的是kubectl get componentstatuses,这个资源从 1.19 起就标记弃用了,新集群上多半打不出东西,别以为是集群坏了。

第二条依赖一个前提:用 kubeadm 起的集群,控制面组件默认以静态 Pod 形式运行,清单放在/etc/kubernetes/manifests下,kubelet 会盯着这个目录,文件一变就重建容器。所以你在kube-system里看到的kube-apiserver-xxxx名字后面那串是节点名,不是随机后缀。

第三条里的-o wide会多打出节点内网 IP、操作系统镜像和容器运行时版本,排查「新节点加不进来」时先看这行,版本对不上、CRI 不一致,在这里就能看出来。

2.3 节点侧:kubelet 才是真正干活的那个

# kubelet 服务状态(systemd 管理的集群) systemctl status kubelet --no-pager # 最近 50 行 kubelet 日志,找出拉镜像、挂卷失败的具体报错 journalctl -u kubelet -n 50 --no-pager # 节点上实际存在的 Pod 目录,能和 kubectl 的结果对照 ls /var/lib/kubelet/pods | head -5 # kube-proxy 是以 DaemonSet 形式跑的,每个节点一个 kubectl -n kube-system get pods -o wide | grep kube-proxy

systemctl status只能告诉你进程活没活,真正的信息在 journal 里。看 kubelet 日志有个技巧:先按关键字过滤,FailedMount、ImagePullBackOff、failed to get sandbox image这三类占了节点侧故障的大半,直接journalctl -u kubelet | grep -i failedmount比从头翻快得多。

/var/lib/kubelet/pods下每个目录名是 Pod 的 UID,里面存着 volumes、containers、plugins 几个子目录。当kubectl describe pod显示挂载失败但你不知道具体卡在哪,进这个目录看对应 UID 下的实际结构,比读事件更直接。这个目录也是磁盘清理的重点,Pod 删了目录没清掉,往往是挂载残留导致的。

kube-proxy的 Pod 名带随机后缀,用grep kube-proxy筛出来后,kubectl -n kube-system logs <pod名>能看到它同步 Service 规则的过程。如果 Service 通但 Pod 直连不通,先看这里有没有Failed to list *v1.EndpointSlice之类的报错——规则没同步,流量自然到不了后端的 Pod。

2.4 对象模型:Label、Namespace、Service 怎么把 Pod 串起来

# 建一个独立命名空间,避免和默认空间里的东西混在一起 kubectl create namespace demo # 起一个两副本的 Deployment kubectl -n demo create deployment web --image=demo/java-app --replicas=2 # 给 Pod 打标签 kubectl -n demo label pod -l app=web tier=frontend # 按标签查 Pod,并显示标签列 kubectl -n demo get pods -l tier=frontend --show-labels # 暴露成集群内服务:Service 端口 80,容器端口 8080 kubectl -n demo expose deployment web --port=80 --target-port=8080 --name=web # 看 Service 后面到底挂了哪几个 Pod IP kubectl -n demo get endpoints web

这段里有几个参数值得单独说。--port是 Service 自己监听的端口,--target-port是容器里应用真正监听的端口,两个写反了,Service 能创建成功但请求会超时,现象和「服务没起来」几乎一样,是新手最容易卡住的地方。--name不写就默认跟 Deployment 同名,显式写出来是为了后面用kubectl get svc时一眼能认出来。

get endpoints是我每次排 Service 不通的第一步。它显示的是 Service 的选择器实际匹配到的 Pod IP 和端口,如果这里是<none>,说明选择器和 Pod 标签对不上,问题不在网络层,在标签层。这一步能省掉大量抓包时间。

还有个细节:kubectl label pod -l app=web是给 Pod 打的标签,Deployment 重建 Pod 之后这个标签就没了,因为模板里没有。标签该写在spec.template.metadata.labels里,而不是事后补。这一点在第 4 章的 YAML 里会体现,也是很多人第一次写 Deployment 时踩的坑。

2.5 kubectl 到底在跟谁说话

# 看当前上下文用的是什么集群、什么用户 kubectl config current-context kubectl config view --minify # 多集群场景下显式指定 kubeconfig 和上下文 kubectl --kubeconfig ~/.kube/prod.yaml --context prod-cluster get nodes

很多「命令打不通」的问题,根子不在集群,在 kubeconfig。current-context指向的可能是本地测试集群,而你以为连的是远端。--minify只输出当前上下文相关的配置,比完整view好读得多。多集群环境里,我习惯给每个环境单独一个 kubeconfig 文件,用--kubeconfig显式指定,不做use-context的临时切换,避免在一个终端里误操作到另一个环境。

3. 监控与日志:metrics-server 加 cAdvisor 把资源利用率摊开看

能登上机器不等于能看见集群在干什么。这一章的两块内容,一块是「资源消耗到底谁在用」,一块是「出问题去哪翻日志」。课件给的命令不多,但每一条背后都对应一条采集链路,链路没通,命令就只会返回冷冰冰的error: Metrics API not available。

3.1 kubectl top 背后的采集链路

kubectl top不是直接从 kubelet 拿数据,它打的是 apiserver 里的metrics.k8s.io聚合 API,请求被转发给metrics-server,metrics-server再去挨个问 kubelet 要Summary API的数据。kubelet 内部的 cAdvisor 从容器运行时采集 CPU 和内存的瞬时用量,聚合成 summary 暴露出来。整条链路上任何一环断掉,top就没数据。

理解了这条链,很多现象就好解释了。metrics-server本身不存历史数据,它没有时序库,所以你看到的永远是最近一次采集点的瞬时值,不能拿来算 P95 或者画趋势图。要做容量规划,得上 Prometheus 那套体系,metrics-server只解决「现在谁在吃 CPU」这个即时问题。另外 cAdvisor 从 1.12 之后已经内嵌进 kubelet,不再单独部署,老资料里那种「先装个 cAdvisor DaemonSet」的步骤在新集群上纯属多余。

3.2 部署 metrics-server 与三个必调参数

# 应用清单(组件的官方发布清单,课件里一般以文件形式附带) kubectl apply -f metrics-server-components.yaml # 确认 Pod 起来了 kubectl -n kube-system get deploy metrics-server kubectl -n kube-system get pods -l k8s-app=metrics-server -o wide # 看日志,卡在证书或者 kubelet 连接问题上在这里最明显 kubectl -n kube-system logs deploy/metrics-server --tail=30 # 验证:节点级和 Pod 级用量 kubectl top node kubectl top pod -A --sort-by=memory | head -10

--sort-by=memory支持cpu和memory两个值,只对单列有效,配合head能在几百个 Pod 里快速捞出吃内存的大户。-A是--all-namespaces的缩写,跨命名空间看总量,排查节点资源紧张时必加。

清单里通常要改两个启动参数。第一个是--kubelet-insecure-tls,当 kubelet 用的是自签证书、又没在metrics-server里配 CA 时,不跳过校验就会连不上,日志里报x509: certificate signed by unknown authority。这个参数只适合实验环境,生产集群应该把 CA 证书挂进去做正经校验,跳过等于默认信任所有节点,安全性上说不过去。第二个是--metric-resolution,默认 60s,想更灵敏可以调到 15s,代价是 apiserver 和 kubelet 的压力都变大,几百个节点的集群我不建议往下压。

还有一个隐藏坑:metrics-server的 Deployment 需要能通过kubelet-preferred-address-types找到节点地址,默认是InternalIP,ExternalIP,Hostname。如果集群的节点只有 Hostname 能解析、内网 DNS 又不通,就得把这个参数改成只留InternalIP,否则日志里会一直刷dial tcp: lookup xxx: no such host。

3.3 组件日志的三个入口

# 入口一:systemd 管理的组件(kubelet 就是典型) journalctl -u kubelet --since "30 min ago" --no-pager # 入口二:以 Pod 形式跑在 kube-system 里的组件 kubectl -n kube-system logs kube-proxy-btz4p --tail=50 kubectl -n kube-system logs -l k8s-app=calico-node --tail=20 --all-containers # 入口三:容器运行时落在磁盘上的日志目录 ls -l /var/log/pods | head ls -l /var/log/containers | head

三个入口对应三种部署形态,别混着找。kubelet、容器运行时的日志走 systemd,journalctl -u是唯一正确的打开方式,/var/log/messages里只有零星片段。控制面组件和网络插件、kube-proxy这些以 Pod 形式运行的,走kubectl logs,-l按标签批量拉、--all-containers把多容器 Pod 里的都打出来,排查 init 容器失败时很有用。

/var/log/pods和/var/log/containers是容器运行时写的落盘日志。前者按命名空间_Pod名_UID组织,后者是一堆软链接,指向pods目录下的实际文件。节点上kubectl用不了(apiserver 挂了、证书过期)的时候,这两个目录是最后的救命通道,直接从节点上tail就能看到应用输出。

3.4 应用日志:标准输出与文件挂载的取舍

apiVersion: v1 kind: Pod metadata: name: web-log-demo namespace: demo spec: containers: - name: web image: demo/nginx-php volumeMounts: - name: app-logs # 这个名字必须和下面 volumes 里的 name 完全一致 mountPath: /usr/local/nginx/logs volumes: - name: app-logs # 卷名,Pod 内容器共享 hostPath: path: /tmp/logs # 节点上的实际路径,目录不存在要先建 type: Directory

这段 YAML 是课件里日志挂载的骨架,我改了两个地方:把镜像换成了自己的示例地址,以及把volumeMounts和volumes里的name对齐。原稿里这两处一个是logs、一个是data,看着不起眼,提交上去 apiserver 会直接拒掉,报错大意是找不到对应的 volume。这个坑第 5 章还会再提一次,因为它是新手复现失败率最高的一条。

用hostPath把日志目录挂到节点上,好处是日志落在固定路径、方便采集器统一收;坏处是 Pod 换节点之后日志就换了地方,同一应用的多副本日志分散在不同机器的同一个路径下,做聚合时必须带节点维度。type: Directory要求路径必须已经存在,不存在会报错;想让它自动创建就写DirectoryOrCreate,但我一般不用后者,自动建出来的目录权限经常不对,应用写不进去,报错还更难定位。

绝大多数场景其实不需要挂载日志目录。容器里的应用把日志打到标准输出,kubectl logs <pod> -f和-c <容器名>就能跟,日志轮转交给 kubelet 管,省事得多。只有当应用死活不肯改配置、必须往文件里写的时候,才上hostPath或者emptyDir这套。

3.5 日志轮转:别等磁盘写满才想起来

kubelet 管着容器日志的轮转,两个参数在/var/lib/kubelet/config.yaml里:containerLogMaxSize默认 10Mi,containerLogMaxFiles默认 5。也就是说单个容器最多占 50Mi 日志空间,超了就从最老的开始删。这个默认值在调试期够用,在流量大的应用上完全不够——一次 GC 日志就能刷满,等你回头查问题时发现关键那段已经被轮掉了。

我一般会把containerLogMaxSize调到50Mi,containerLogMaxFiles调到10,改完重启 kubelet 生效。但要注意,这只是延缓问题,真正的解法是接日志采集侧(节点上的采集 agent 或者 sidecar),把日志送出去之后再让本地轮转,本地只做缓冲。只靠调大参数,迟早还是会把节点磁盘写满。

4. 应用生命周期:Deployment 从创建到回滚的完整参数面

课件这一章给了一串kubectl create、expose、set image、rollout undo,命令本身没什么可讲的,但它们串起来其实是一条完整的状态机:创建 → 发布 → 升级 → 伸缩 → 回滚 → 删除。把这几个动作各自改了哪些字段搞清楚,线上出问题时才知道该看哪个对象的哪个字段。

4.1 命令式起手,声明式收敛

kubectl create deployment web --image=demo/java-app这类命令适合验证和临时试验,敲完就有了对象。但它生成的东西不会进版本库,改了什么没人知道,重建环境还得靠人手记。我一般的做法是:新东西先用命令式快速跑通,确认镜像能拉、端口对、依赖通,然后kubectl get deploy web -o yaml导出,删掉status、metadata.uid、resourceVersion这些运行时字段,补上资源限制和探针,再进 Git。

这个顺序比一上手就写 YAML 快,因为你在不知道镜像对不对、端口开没开的情况下,很难一次写对。反过来,如果所有东西都靠 YAML 硬写,调试成本会翻倍。两段式,先跑通再固化,是我这些年最省时间的习惯。

4.2 一份能直接用的 Deployment YAML 与字段拆解

apiVersion: apps/v1 kind: Deployment metadata: name: web namespace: demo annotations: kubernetes.io/change-cause: "web 1.0 -> 1.1, 修复连接池耗尽" # 回滚历史里能看到这句 spec: replicas: 3 revisionHistoryLimit: 5 # 保留 5 个历史版本,多了占 etcd 空间 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% # 升级时最多超出期望副本数的比例 maxUnavailable: 25% # 升级时最多允许不可用的比例 selector: matchLabels: app: web # 创建后不可改,必须和下面 template 的标签对上 template: metadata: labels: app: web spec: containers: - name: web image: demo/java-app:1.1 ports: - containerPort: 8080 imagePullPolicy: IfNotPresent resources: requests: cpu: 250m memory: 64Mi limits: cpu: 500m memory: 128Mi readinessProbe: # 决定 Pod 什么时候开始接流量 httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 5

apiVersion对 Deployment 是apps/v1,写成extensions/v1beta1在 1.16 之后直接报错,老课件里的这一行必须改。selector.matchLabels是创建后不可变的字段,想改只能删了重建,所以标签命名早期就要定好,别等到几十个服务互相引用之后才想重构。

maxSurge和maxUnavailable两个值不能同时为 0,否则滚动更新会卡住不动。25% 是保守配置,3 副本的时候maxUnavailable向下取整是 0,意味着先起新 Pod 再停旧的,全程不降容量;副本数多的时候才会真的停掉一部分。

readinessProbe是我认为比livenessProbe更该先加的东西。没有就绪探针,Pod 一启动就被加进 Service 的后端列表,应用还没初始化完就开始接请求,用户看到的是 502,而kubectl get pods显示全是 Running,属于典型的黑匣子现象。initialDelaySeconds给 10 秒是个经验值,Java 应用冷启动慢的可以给到 30,别给太大,否则发布窗口会被拉长。

resources那段和 QoS 直接相关,留到 4.4 单独说。imagePullPolicy的三个取值在 4.5 里细讲。

4.3 升级、扩缩容、回滚三个动作的状态机

# 升级镜像,并阻塞等待滚动更新完成 kubectl -n demo set image deployment/web web=demo/java-app:1.1 kubectl -n demo rollout status deployment/web --timeout=120s # 查看历史版本 kubectl -n demo rollout history deployment/web # 回滚到上一个版本,或者指定版本号 kubectl -n demo rollout undo deployment/web kubectl -n demo rollout undo deployment/web --to-revision=1 # 手动伸缩与自动伸缩 kubectl -n demo scale deployment web --replicas=5 kubectl -n demo autoscale deployment web --min=2 --max=8 --cpu-percent=70

rollout status会一直阻塞到更新完成或者超时,--timeout不写默认是无限等。CI 流水线里一定要写这个参数,否则一次卡住的发布会把整个任务挂死。它输出的中间状态比get pods有用得多,能看到新 ReplicaSet 扩到几个、旧的在缩到几个。

rollout history里显示的是revision号,但只有给 Pod 模板加了kubernetes.io/change-cause注解,每一行才会有可读的说明。老版本kubectl有--record参数自动写这个注解,新版本已经移除,得自己在 YAML 里加。不加的话历史里全是deployment.apps/web这样的干巴巴一行,回滚到第几版全靠猜,这是血泪经验。

rollout undo --to-revision的版本号来自history的输出,但要注意revisionHistoryLimit之外的老版本已经被清掉了,只能回滚到保留范围内的。revisionHistoryLimit: 5意味着最多能回退 5 步,再多就找不回来了。

autoscale依赖metrics-server,第 3 章那套没通,HPA 会一直显示<unknown>/70%,不扩容。--cpu-percent的基准是requests.cpu,不是limits.cpu,也不是节点总 CPU,所以没设 requests 的 Deployment 开了 HPA 也不会动。

4.4 requests/limits 怎么定,QoS 三档意味着什么

配置组合QoS 等级节点资源紧张时的命运
requests 和 limits 都设,且两者相等(CPU 可不相等)Guaranteed最后被驱逐,优先级最高
设了 requests,limits 更大或只设 requestsBurstable按偏离 requests 的程度排序驱逐
什么都没设BestEffort最先被驱逐

requests是调度依据,kube-scheduler 拿它去算节点还剩多少可分配资源;limits是运行时上限,CPU 超了会被限流(throttle),内存超了直接 OOMKilled。这两个值搞混,会出现两种典型翻车:requests 设得比实际用量大很多,节点明明还有余量但 Pod 一直 Pending;limits 内存设得和 JVM 堆一样大,堆外内存一涨就触发 OOMKilled,看kubectl describe pod里的Last State: Terminated, Reason: OOMKilled才能确认。

Java 应用我一般把limits.memory设成堆上限的 1.5 倍左右,给元空间、线程栈、直接内存留出空间;requests.cpu按压测出来的稳态用量给,limits.cpu可以适当放宽,因为 CPU 被限流只是变慢,不像内存那样直接被杀。

4.5 Pod 里那个看不见的 pause 容器

课件提到 Pod 里的容器共享网络命名空间,实现方式是每个 Pod 先起一个infra container,业内一般叫 pause 容器。它极小,只做一件事:占住网络命名空间不放。其他业务容器加入这个命名空间,于是看到的是同一套网络设备、同一个 IP、同一个 MAC。这也是为什么同一个 Pod 里的两个容器能直接127.0.0.1互相访问,nginx 和 php-fpm 塞进一个 Pod 就是这个道理。

存储共享则是另一套机制,靠的是挂载同一个 volume 到多个容器的挂载点,而不是命名空间。所以「Pod 内容器共享网络」和「Pod 内容器共享存储」是两条独立的实现路径,前者靠 pause 容器,后者靠卷挂载,别记混。

imagePullPolicy三个值:Always每次创建都重新拉,IfNotPresent本地有就不拉,Never只用本地的。课件的说法是默认IfNotPresent,这不完全对——默认值取决于镜像标签,tag 是latest或者没写 tag 时默认Always,写了具体版本号才默认IfNotPresent。生产环境两个原则:不用latest标签,显式写IfNotPresent。用latest加Always,每次重启都去拉一遍,仓库抖动一下整个发布就卡住。

5. 避坑与排查:五个高频翻车现场

这一章是我把课件照着敲了一遍之后攒下来的,每一条都真实存在,现象和原因对不上号,能白耗半天。

5.1 挂载名对不上,Pod 直接起不来

现象:kubectl apply之后 Pod 一直是ContainerCreating,describe里报MountVolume.SetUp failed或者事件里出现volume "logs" not found。

原因:volumeMounts[].name引用的是volumes[].name,两个字符串必须完全一致。课件原稿里volumeMounts写的是logs,volumes写的是data,差一个词。这不是语法错误,YAML 解析能过,但 apiserver 的校验会拒掉。

解决:逐个字符核对两个 name 字段。更好的做法是统一命名习惯,比如卷名一律带业务前缀web-logs、web-data,容器挂载时照抄,减少手误。改完kubectl apply之后一定要describe pod看事件,光看 Pod 状态是ContainerCreating还是Pending判断不出原因。

5.2 kubectl top 报 metrics not available

现象:kubectl top node返回error: Metrics API not available,或者kubectl top pod提示metrics not available for pod。

原因:三个可能。metrics-server没装;装上了但连不上 kubelet(自签证书、地址类型选错);metrics-server的 APIService 注册失败,kubectl get apiservice v1beta1.metrics.k8s.io显示False。

解决:按链路从后往前查。先kubectl -n kube-system get pods -l k8s-app=metrics-server确认在跑,再logs看有没有x509或者no such host,前者加 CA 证书或者临时用--kubelet-insecure-tls,后者改--kubelet-preferred-address-types。最后查 APIService,状态False的话kubectl describe apiservice v1beta1.metrics.k8s.io会给出具体的连接错误。装完到能查出数据中间有几十秒延迟,别急着判定失败。

5.3 回滚回去了,但回去的不是你以为的版本

现象:rollout undo执行成功,rollout status也显示完成,但服务行为和预期不符,翻镜像标签发现回滚到了错误的版本。

原因:revisionHistoryLimit太小,想回的那个版本早被清掉了,undo实际回退到了保留列表里的某一个;或者历史里没有change-cause注解,凭版本号猜错了目标。

解决:把revisionHistoryLimit从默认的 10 往下调之前先想清楚回滚窗口,我一般留 5 到 10。每次发布前在 YAML 里更新kubernetes.io/change-cause注解,写清楚版本和变更点,rollout history才有可读性。回滚是个需要精确的动作,--to-revision显式指定比默认回上一版更靠谱,尤其是在连续发布之后。

5.4 没设 requests,节点被压垮才收到告警

现象:某个节点突然 NotReady,上面的 Pod 大量被驱逐;或者节点内存看着还有富余,新 Pod 却一直 Pending。

原因:Pod 没写resources.requests,调度器认为它不占资源,把一堆 BestEffort 的 Pod 塞到同一个节点上,实际运行时互相抢内存,节点 OOM。反过来,requests 写得远大于实际用量,调度器按虚高的数字分配,节点明明有空间却调度不上去。

解决:所有上生产的 Deployment 强制写 requests,CPU 和内存都要,可以通过准入策略强制。数值来源别拍脑袋,用kubectl top pod观察一两天的稳态值,取接近 P90 的数。设完之后用kubectl describe node看Allocated resources那一栏,能直观看到每个节点的 requests 水位,超过 80% 就该考虑扩容了。

5.5 ImagePullBackOff:策略、密钥、私有仓库三件事

现象:Pod 卡在ImagePullBackOff或ErrImagePull,describe事件里报pull access denied或manifest unknown。

原因:镜像名拼错、tag 不存在、私有仓库没配imagePullSecrets、imagePullPolicy: Never但节点上没这个镜像。课件的示例用的是公共镜像,换成自己仓库的地址之后,前三种最容易撞上。

解决:先在节点上手动crictl pull <镜像全名>试一次,能拉下来说明是 k8s 配置问题,拉不下来是网络或仓库权限问题。私有仓库要建 secret 并在 Pod 的spec.imagePullSecrets里引用:

kubectl -n demo create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=robot \ --docker-password='******' \ --docker-email=dev@example.com

四个参数里--docker-server要和镜像地址的前缀完全一致,差一个斜杠都可能认证失败。secret 建在哪个命名空间,就只能被哪个命名空间的 Pod 引用,跨命名空间要各建一份。imagePullPolicy: Never只适合离线导入镜像的场景,用之前先确认节点上crictl images真有那个镜像和 tag。

6. 从课件到实验环境:调度、存储、RBAC 三处的最小验证

课件目录里有调度、存储、集群安全三块,正文展开得不多,但恰恰是这三块决定了「能不能上生产」。我给自己的验证习惯是:每块只做一件最小的事,跑通了再往下加,别一上来就搭一整套。

调度这块,先用nodeSelector把 Pod 钉到指定节点,再用taint验证驱逐行为:

# 给节点打标签,Pod 模板里加 nodeSelector: disktype=ssd 就会落到这个节点 kubectl label node node-1 disktype=ssd kubectl -n demo get pods -o wide | grep node-1 # 给节点打污点,看没有容忍的 Pod 会不会被赶走 kubectl taint node node-2 env=test:NoSchedule kubectl -n demo get pods -o wide | awk '$7=="node-2"'

NoSchedule只影响新调度,已经在跑的 Pod 不动;想驱逐现有 Pod 得用NoExecute。这两个效果差很多,验证的时候别用错。

存储这块,最小验证是一条 PVC 从 Pending 到 Bound 的过程。StorageClass配好动态供给之后,PVC 创建几秒内就该变成Bound,一直 Pending 通常是 provisioner 没装、或者storageClassName拼错。安全这块,最小验证是一个只读的 ServiceAccount:

kubectl -n demo create serviceaccount viewer kubectl -n demo create role pod-reader --verb=get,list,watch --resource=pods kubectl -n demo create rolebinding viewer-binding --role=pod-reader --serviceaccount=demo:viewer kubectl auth can-i list pods --as=system:serviceaccount:demo:viewer -n demo

最后那条auth can-i是灵魂,返回yes才算配通。我见过太多 RBAC 配完不敢验证、上线后才发现权限给多了或者给少了的情况。

验证项通过标准常见失败点
调度Pod 落到带标签的节点标签拼写、污点未容忍
存储PVC 状态 BoundStorageClass 名不存在、provisioner 未运行
RBACauth can-i返回 yesServiceAccount 命名空间写错、资源名大小写

从那以后我每次拿到一份新课件,都不急着通读,而是先挑这三块各跑一个最小用例,跑不通的地方才是真正要补的。课件给的是骨架,骨架能不能立住,得自己动手压一遍。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表