首页 / 资讯中心 / 文章详情

K8s集群架构到Deployment滚动更新与Pod配额排障课件

K8s集群架构到Deployment滚动更新与Pod配额排障课件 ★ FEATURED ARTICLE
简介面向 Kubernetes 初学者与云原生运维人员的这套课件围绕集群架构、核心对象和日常管理展开帮助建立系统化的 k8s 知识框架。资源包为 1 个 pptx 文件约 1.25MB篇幅集中便于按章节浏览和课堂演示。内容先说明 apiserver、kubelet、kube-proxy 等组件职责再梳理 Pod、Controllers、Service、Label、Namespace 等对象。结合 kubectl 命令与 YAML 清单展示集群状态查看、资源监控、日志排查并串联 Deployment 的创建、发布、升级、扩缩容和回滚流程同时延伸调度、存储与集群安全。目前已有 2191 人学习适合入门梳理与面试前复盘。1. 一份把 K8s 从概念讲到配额边界的课件适合谁啃刚拿到 kubeconfig 的人最常遇到的问题不是 YAML 缩进写错而是 Pod 起不来时只会反复kubectl get pods刷新说不清 apiserver、etcd、kubelet 各自卡在链路哪一段。这份 K8s 课件针对的正是这个薄弱点从集群架构与组件职责入手接着是监控与日志、应用生命周期管理、调度、存储、集群安全六块内容按「先知道谁在干活再知道怎么让它干活」的顺序排布。它不适合已经能顺手写 Operator 的人但对刚接手集群、需要独立把一个 Deployment 从创建推到升级再回滚的运维和后端更合适。下面我按自己复盘的顺序拆开重心放在命令背后的参数含义和几个容易出错的边界上。2. 集群架构拆开看apiserver、etcd、kubelet 各自守哪一段2.1 控制面四个组件的职责边界很多人把控制面当成一个整体其实它内部的分工很明确边界一旦搞混排查方向就会偏。kube-apiserver 是集群唯一入口所有组件彼此不直接通信只跟 apiserver 通信apiserver 也是唯一能读写 etcd 的进程。一个请求进来要过三道关认证证书、Token 或 ServiceAccount、鉴权RBAC 规则、准入控制各种 Admission 插件全过了才落盘。这套设计的好处是审计点集中坏处是 apiserver 一旦不可用kubectl全线报错但已经在跑的 Pod 不受影响因为 kubelet 还在按本地缓存的期望状态干活。kube-controller-manager 是一堆控制器的集合体Deployment、ReplicaSet、Node、Endpoint、ServiceAccount 各有一个控制器在跑。每个控制器本质是个死循环读期望状态spec读实际状态status算差值发指令。这里有个容易被忽略的嵌套关系Deployment 控制器并不直接管 Pod它管的是 ReplicaSetReplicaSet 再管 Pod。正因为老版本的 ReplicaSet 会被保留并缩到 0 而不是删掉回滚才有历史可退。kube-scheduler 只做一件事给新创建、还没绑定节点的 Pod 选一个 Node然后写一个 Binding 对象把spec.nodeName填上。它不负责把容器启动起来启动是 kubelet 的活。调度分两阶段Filter 先过滤掉不满足条件的节点资源不够、污点不匹配、端口冲突、亲和性不符Score 再对剩下的节点打分。所以看到 Pod 处于 Pending 且 Events 里写着FailedScheduling问题一定在调度器这一侧不用去翻容器日志。etcd 是分布式键值存储默认监听 2379/2380正常配置下只允许 apiserver 访问。集群里所有对象状态都在它里面某种意义上删掉 etcd 数据目录等于删掉整个集群。组件位置核心职责挂掉后的典型表现kube-apiserver控制面唯一入口认证鉴权准入读写 etcdkubectl 全部报错已运行 Pod 不受影响kube-controller-manager控制面控制器循环把实际状态拉向期望状态副本数不收敛Service 没有 Endpointskube-scheduler控制面为新 Pod 选择节点并绑定新 Pod 一直 Pendingetcd控制面保存全部集群状态apiserver 起不来集群事实性丢失kubelet节点管理本机 Pod 生命周期该节点上的 Pod 状态不再更新kube-proxy节点维护 Service 的四层转发规则Service 通但后端访问不到2.2 节点侧kubelet 和 kube-proxy 不是一回事kubelet 是节点上的代理它做的是向 apiserver 注册节点、watch 到绑定给自己的 Pod、通过 CRI 调用容器运行时创建容器、挂载数据卷、拉取 Secret 和 ConfigMap、执行存活与就绪探针、定时上报节点和 Pod 状态。kubelet 把每个 Pod 翻译成一组容器操作这个翻译过程出错Pod 就卡在 ContainerCreating 或者一直重启。kube-proxy 完全不参与容器创建它 watch Service 和 Endpoints 的变化在节点上写 iptables 或 ipvs 规则实现四层负载均衡。区分方法很简单Pod 起不来看 kubelet 和容器运行时Pod 起来了但 Service 访问不通先看 Endpoints 有没有后端再看 kube-proxy 的规则。2.3 用几条命令把组件状态验证一遍课件里给了kubectl cluster-info但单靠这一条信息量太少。我一般按下面这个顺序过一遍每条命令看什么、返回什么算正常心里要有数。# 集群入口信息apiserver 地址和集群 DNS 服务地址 kubectl cluster-info # 控制面组件是否都是 Running以及各自落在哪个节点 kubectl get pods -n kube-system -o wide # 直接打 apiserver 的健康端点返回 ok 说明 apiserver 自身活着 kubectl get --raw /healthz # 看单个节点的容量、已分配资源、污点和最近事件 kubectl describe node node-name第一条命令的返回值里如果 DNS 那行是10.96.0.10:53这类地址说明集群服务网段正常。第二条要重点看kube-controller-manager、kube-scheduler、etcd这几个静态 Pod 的 RESTARTS 次数短时间反复重启通常意味着证书过期或者磁盘压力。第三条--raw是直接向 apiserver 发原始请求绕开 kubectl 的客户端逻辑用来判断「是 apiserver 挂了还是 kubectl 配置错了」特别快。注意网上很多老教程会写kubectl get componentstatuses这个接口在较新版本里已经废弃输出的警告比内容还长。用kubectl get --raw /healthz配合kubectl get pods -n kube-system替代更靠谱也不用记一堆缩写。2.4 etcd 快照唯一的后悔药集群层面最该养成的习惯是给 etcd 做快照。误删一个 Namespace 会连带删掉里面所有对象控制器不会帮你恢复因为对它来说那就是期望状态变了。# 在控制面节点执行证书路径按实际安装位置修改 ETCDCTL_API3 etcdctl \ --endpointshttps://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 \ snapshot save /var/backups/etcd-$(date %F-%H%M).db # 校验快照完整性输出里 hash 和 revision 都要有值 etcdctl snapshot status /var/backups/etcd-20240101-1200.db --write-outtable--endpoints指向本机 etcd 客户端端口三个证书分别对应 CA、客户端证书和私钥缺一个都会报context deadline exceeded这个报错很有迷惑性看起来像网络不通实际多数是证书路径写错。snapshot save是热备份不需要停 etcd但恢复时必须停掉 apiserver 和 etcd 再执行snapshot restore恢复动作不要在业务时段做。备份文件放本地磁盘意义有限节点挂了就一起没了我一般会再同步到集群外的存储。3. 从 kubectl 到 YAML把一个 Deployment 推到回滚的完整闭环3.1 命令式创建先用两条命令跑通初学阶段别急着写 YAML先用命令式把对象关系跑通看到 Deployment、ReplicaSet、Pod 三层是怎么长出来的比背字段有用得多。# 创建一个 Deployment镜像用内网仓库地址 kubectl create deployment web --imageregistry.local/demo/java-web:1.0 # 查看 Deployment 和 Pod-o wide 能看到 Pod 落在哪个节点 kubectl get deploy,pods -o wide # 暴露服务Service 80 端口转发到容器 8080 kubectl expose deployment web \ --port80 \ --target-port8080 \ --typeNodePort \ --nameweb kubectl get svc web--port是 Service 自身监听的端口集群内其他 Pod 通过web:80访问--target-port是容器实际监听的端口写错这个是最常见的 502 来源--typeNodePort会在 30000–32767 区间随机分配一个节点端口--name不写的话默认与 Deployment 同名。创建完立刻kubectl get rs会看到一个名字形如web-7d9f8c6b5的 ReplicaSetPod 名字再在后面加随机后缀。这个三层结构不是冗余设计Deployment 负责版本和滚动策略ReplicaSet 负责某一版本的副本数Pod 才是真正被调度的单元。理解了这一层后面rollout history为什么能列出多条记录就顺理成章了。3.2 YAML 逐字段拆apiVersion 到 containers命令式适合验证生产必须落到 YAML否则改一个副本数都要记参数。下面这份是课件里 Deployment 的加厚版把滚动更新策略和资源配额一起带上。apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx # 必须与 template.metadata.labels 完全匹配 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 滚动时最多超出期望副本数 1 个 maxUnavailable: 0 # 滚动时不允许有副本不可用 revisionHistoryLimit: 10 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 250m memory: 64Mi limits: cpu: 500m memory: 128MiapiVersion和kind是配对的写错组合会直接报no matches for kind不确定时用kubectl api-resources | grep deployment查当前集群支持哪个版本。selector.matchLabels与template.metadata.labels不匹配时 apply 会被拒绝报错信息是selector does not match template labels这条报错很直白照着改就行。replicas是期望副本数但控制器实际收敛需要几秒刚 apply 完立刻查可能只有 1 个 Pod属于正常现象。revisionHistoryLimit默认就是 10意味着最多保留 10 个历史 ReplicaSet调小能省 etcd 空间但会让可回滚的版本变少。3.3 升级、扩缩容与回滚的操作序列# 升级镜像改的是 Pod 模板会触发滚动更新 kubectl set image deployment/web nginxnginx:1.25 # 阻塞等待滚动完成超时退出码非 0适合放进脚本 kubectl rollout status deployment/web --timeout120s # 查看历史版本REVISION 列就是回滚时要用的编号 kubectl rollout history deployment/web # 回到指定版本 kubectl rollout undo deployment/web --to-revision2 # 手动扩缩容 kubectl scale deployment web --replicas10 # 配置自动扩缩容CPU 超过 80% 时最多扩到 15 个 kubectl autoscale deployment web --min10 --max15 --cpu-percent80rollout status是我最推荐放进 CI 的一条命令它比sleep 30靠谱得多滚动失败时会立刻返回而不是傻等。--to-revision有一个容易踩的点回滚本身会产生一个新的 revision 编号比如当前是 3回滚到 2 之后历史里会多出一个 4内容与 2 一致。所以脚本里写死 revision 号迟早出问题更稳的做法是记镜像 tag。scale和autoscale同时存在会打架。HPA 接管之后会周期性改写spec.replicas手动 scale 上去的数字过一会儿就被覆盖回来排查时看到副本数自己变先查kubectl get hpa有没有对应的规则。3.4 滚动更新参数maxSurge 与 maxUnavailable 的实际影响这两个参数决定了滚动更新的节奏也是「升级时服务到底会不会抖」的直接原因。maxSurge: 1加maxUnavailable: 0是最保守的组合先起一个新 Pod等它就绪再停一个旧的全程副本数只多不少。代价是需要集群有富余资源如果节点资源刚好卡满新 Pod 调度不上去滚动就会永远停在中间状态。maxSurge: 0加maxUnavailable: 1则相反先停旧的再起新的省资源但会短暂少一个副本对流量敏感的服务不要这么配。两者不能同时为 0否则滚动无法推进apply 时会直接被拒。滚动卡住的排查路径很固定先kubectl rollout status看卡在哪一步再kubectl describe rs 新 RS 名看 Events。如果是FailedScheduling就是资源或亲和性问题如果是Readiness probe failed就是应用起来了但没准备好接收流量这时候要去查就绪探针的路径和端口而不是怀疑镜像。4. Pod 为什么不是容器的简单包装infra container、拉取策略与配额4.1 共享网络是怎么做到的Pod 是 K8s 的最小调度单元但它本身是个逻辑概念节点上并没有一个叫 Pod 的东西。实现方式是每个 Pod 先起一个极小的 infra container也叫 pause 容器由它持有网络命名空间然后把业务容器 join 进这个命名空间。于是同一 Pod 内的容器看到的网卡、IP、路由表完全一致可以直接用127.0.0.1通信。这解释了几个日常现象同 Pod 内容器不能监听同一个端口因为共享同一张网卡nginx 加 php-fpm 这种组合放同一个 Pod 里nginx 直接fastcgi_pass 127.0.0.1:9000就行不用配 ServicePod 重建后 IP 会变因为它拿到的是新的网络命名空间。存储共享走的是另一条路靠 volume 挂载而不是命名空间。多个容器把同一个 emptyDir 挂到各自路径下就能看到同样的文件。课件里那个 nginx 日志的例子就是这个思路把容器内的日志目录挂到宿主机方便采集。apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: web image: registry.local/demo/nginx-php:1.0 volumeMounts: - name: logs mountPath: /usr/local/nginx/logs volumes: - name: logs hostPath: path: /tmp/logs type: DirectoryOrCreatetype: DirectoryOrCreate比Directory安全后者要求宿主机目录必须已存在不存在时 Pod 会卡在 ContainerCreating 并报FailedMount。这一点在批量部署时格外明显十台节点里只要有一台没建目录那台机器上的 Pod 就永远起不来。4.2 imagePullPolicy 三个值与 :latest 的隐式规则三个取值的含义分别是IfNotPresent本地没有才拉Always每次创建都拉Never只用手动导入的镜像。真正需要记住的是默认值规则它跟标签有关而不是固定的当镜像标签是latest或者干脆不写标签时默认是Always写了具体标签时默认是IfNotPresent。所以生产环境用latest有两个隐患一是每次 Pod 重建都要打仓库仓库抖动就直接ImagePullBackOff二是同一个标签对应的镜像内容可能已经变了回滚时你以为回到了旧版本实际拉的是新内容。我的做法是标签写死到小版本重要服务用 digest 固定形如nginxsha256:...这样镜像内容不可变回滚才有确定性。4.3 requests 与 limitsCPU 单位、OOMKilled 与 QoS 分类这两个字段经常被混着写其实职责完全不同。requests是调度依据调度器按它计算节点还剩多少可分配资源limits是运行时上限由容器运行时强制执行。CPU 超过 limits 会被节流表现为响应变慢但进程还在内存超过 limits 会直接触发 OOMKilled容器被杀掉重启。这个差异决定了排查方向CPU 问题看监控里的 throttling 指标内存问题看kubectl describe pod里的Last State: Terminated, Reason: OOMKilled。单位方面CPU 的m是千分之一核250m就是 0.25 核1000m等于 1。内存的Mi、Gi是二进制单位1024 进制M、G是十进制两者差 4.8% 左右配额算得紧的时候这点差异会变成事故。# 查看 Pod 的 QoS 等级 kubectl get pod web-7d9f8c6b5-abcde -o jsonpath{.status.qosClass} # 查看容器上次退出的原因OOMKilled 会在这里体现 kubectl describe pod web-7d9f8c6b5-abcde | grep -A3 Last StateQoS 分三档所有容器都设置了 requests 且等于 limits 是 Guaranteed部分设置是 Burstable一个都没设是 BestEffort。节点资源紧张触发驱逐时顺序是 BestEffort 优先被赶走Burstable 次之Guaranteed 最后。所以核心服务把 requests 和 limits 设成相等不只是为了稳定也是为了在驱逐排序里靠后。不设 requests 的后果比想象中严重。调度器会认为这个 Pod 不需要资源把它塞到已经快满的节点上实际跑起来内存一涨节点整体进入压力状态最后被驱逐的可能是隔壁那个规规矩矩设了配额的服务。这就是为什么我坚持新服务上线前先把 requests 填上哪怕估值不准也比空着强。4.4 私有仓库拉取imagePullSecrets 的创建与挂载内网仓库基本都要认证报错表现为ErrImagePull或ImagePullBackOffdescribe的 Events 里会明确写no basic auth credentials或者unauthorized。# 创建 docker-registry 类型的 Secret kubectl create secret docker-registry regcred \ --docker-serverregistry.local \ --docker-usernamedevops \ --docker-password密码 \ --docker-emaildevexample.com # 挂到命名空间的默认 ServiceAccount 上该空间内 Pod 自动继承 kubectl patch serviceaccount default \ -p {imagePullSecrets:[{name:regcred}]}挂载位置有两种选择。写进 Pod 的spec.imagePullSecrets只对当前 Pod 生效适合跨团队共享集群但仓库权限不同的场景patch 到 ServiceAccount 上则整个命名空间生效省事但权限范围大。密码里有特殊字符时记得用单引号包住$和!在双引号里会被 shell 展开这个坑我在不同环境里见过好几次。5. 监控与日志排查kubectl top 不出数、日志抓不到的五个坑5.1 metrics-server 的数据从哪来kubectl top背后依赖的是 metrics-server它本身不采集数据而是定期向每个节点的 kubelet 请求资源用量kubelet 再从 cAdvisor 拿容器级指标。metrics-server 把结果聚合成 Metrics API通过 APIService 注册到 apiserver 上kubectl 和 HPA 都是从这条路读数据。这个链路决定了排查顺序先看 APIService 有没有注册成功再看 metrics-server Pod 是否健康最后看它能不能连通 kubelet 的 10250 端口。# APIService 的 AVAILABLE 必须是 True kubectl get apiservice v1beta1.metrics.k8s.io -o wide # 看节点和 Pod 的实时资源占用按内存排序 kubectl top node kubectl top pod -A --sort-bymemory # metrics-server 自身日志证书和连接错误都在这里 kubectl logs -n kube-system deploy/metrics-server --tail50需要清楚一点metrics-server 只保留最近几分钟的数据不做长期存储想看过往趋势得另外上 Prometheus 之类的方案。它的定位就是给 HPA 和kubectl top供数不要指望它当监控系统用。5.2 日志的三条路径容器日志一般分三层看。第一层是应用写到标准输出的内容用kubectl logs读这是最常用的。容器重启过要看上次的日志加--previous多容器 Pod 要加-c指定容器名。第二层是节点上的日志文件。容器运行时会把标准输出落到/var/log/pods/namespace_pod_uid/container/0.logPod 已经被删掉但节点上还有残留时直接去这个路径捞。第三层是组件自身的日志。kubelet 通常由 systemd 托管用journalctl -u kubelet看以静态 Pod 形式运行的控制面组件用kubectl logs -n kube-system pod看。# 持续跟踪日志只显示最后 100 行 kubectl logs -f deploy/web --tail100 # 看容器上一次崩溃前的日志 kubectl logs web-7d9f8c6b5-abcde -c nginx --previous # 跟踪节点上 kubelet 的日志只看最近 10 分钟 journalctl -u kubelet -f --since 10 min ago如果应用是把日志写文件而不是标准输出kubectl logs什么都看不到这不是 bug。要么改成输出到标准输出要么用 emptyDir 或 hostPath 把日志目录挂出来再接采集器。5.3 五条踩坑记录现象一kubectl top node报error: Metrics API not available但 Pod 都正常。原因APIService 没注册成功或者 metrics-server 连不上 kubelet 的 10250 端口。自签证书的集群上metrics-server 校验 kubelet 证书失败是高频原因。解决先kubectl get apiservice v1beta1.metrics.k8s.io -o yaml看status.conditions里的 message再去翻 metrics-server 日志。确认是证书问题的话在它的启动参数里加--kubelet-insecure-tls这是测试和生产自签环境都常见的做法代价是放弃对 kubelet 证书的校验。现象二Pod 一直 ContainerCreatingdescribe里 Events 显示FailedMount。原因hostPath 写了type: Directory但宿主机上目录不存在或者挂载的 ConfigMap、Secret 还没创建。解决type改成DirectoryOrCreate或者提前在节点上把目录建好。用 DaemonSet 挂宿主机目录时尤其要注意集群扩容进来的新节点不会自动带上目录得靠初始化脚本或 DaemonSet 自己创建。现象三kubectl logs报previous terminated container xxx not found。原因容器已经重启过多次旧日志被运行时轮转清理掉了--previous也拿不回来。解决短期靠及时抓长期靠把日志落盘到挂载卷或者接采集器。容器日志的轮转策略是运行时管的默认单个文件到 10Mi 左右就会切业务日志写得猛的话几分钟就没。现象四Service 访问不通但后端 Pod 状态是 Running。原因Endpoints 为空通常是 Service 的 selector 与 Pod 的 labels 不匹配或者targetPort写成了 Service 端口。解决kubectl get endpoints svc-name先看有没有后端地址空的话kubectl get svc svc-name -o yaml把 selector 抄出来跟 Pod 的 labels 逐字比对。大小写和拼写错误是主要来源app: nginx和app: Nginx在 YAML 里是两个完全不同的选择器。现象五出问题想查事件kubectl get events是空的。原因事件默认只保留 1 小时apiserver 的--event-ttl参数排查拖久了就没了。解决故障当下立刻执行kubectl get events -A --sort-by.lastTimestamp把输出重定向到文件留档。超过保留期的只能靠提前部署的事件采集组件这个能力在事后复盘时价值很高。6. 调度、存储与安全三个能当场跑通的验证动作课件目录里还有调度、存储、集群安全三块篇幅不多但都是容易「看着懂、上手懵」的部分。我的习惯是每块至少跑一个最小验证确认自己理解的和集群实际行为一致。调度这块验证方式是把 Pod 钉到指定节点再观察结果。给 Pod 加nodeSelector或者直接用nodeName创建后kubectl get pod -o wide看 NODE 列是不是预期的那台。用污点和容忍做反向验证更直观给节点打上kubectl taint node node envtest:NoSchedule不带容忍的 Pod 会 Pendingdescribe里的FailedScheduling会明确写出污点不匹配的原因。这比读十遍文档管用。# 给节点打污点再验证不带容忍的 Pod 是否被挡住 kubectl taint node node-01 envtest:NoSchedule kubectl run probe --imagenginx:1.25 kubectl describe pod probe | grep -A2 FailedScheduling # 验证某个 ServiceAccount 在命名空间里有哪些权限 kubectl auth can-i --list \ --assystem:serviceaccount:default:deployer \ -n default存储这块PVC 一直 Pending 时按三步查kubectl get storageclass看有没有可用的 StorageClasskubectl describe pvc name看 Events 里是no persistent volumes available还是waiting for a volume to be created再比对 PVC 的accessModes和 PV 支持的是不是一致。ReadWriteOnce和ReadOnlyMany写混了不会报语法错只会静静挂起。安全这块RBAC 的验证靠kubectl auth can-i上面那条命令会把这个身份能做的动作全列出来。我在给新同事分配 kubeconfig 之前一定会跑一次确认权限范围跟预期一致避免出现「以为只给了只读实际能删 Deployment」这种事。有个习惯我改不掉任何环境做变更之前先跑一遍kubectl get --raw /healthz加一次 etcd 快照。这个动作花不到一分钟但在我误删过一整个命名空间之后它就成了肌肉记忆。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站