简介本资源是《Kubernetes in Action》中文版完整电子书PDF面向容器技术初学者与云原生工程师系统解决Kubernetes入门与实战落地难题。全书从Docker与Kubernetes发展脉络切入通过逐步部署、扩展、监控一个真实应用深入讲解集群架构、服务编排、伸缩调试等核心能力特别适合希望将K8S作为数据中心操作系统来理解与使用的开发者。资源为单文件PDF格式共1个文件大小15.62MB内容覆盖基础概念、实操演练及高阶运维主题排版规范、译文精准由七牛云容器团队翻译、电子工业出版社出版。目前已有1204人学习下载读者可直接获取权威、体系化、带中文注解的K8S实战指南快速建立容器编排全局认知并掌握生产环境部署与问题排查的关键路径。1. 这不是一本“K8S入门书”而是一份能让你在真实生产环境里少踩三个月坑的 Kubernetes 实战地图你手头这份《Kubernetes in Action 中文版.pdf》不是那种翻两页就劝你“先装 Minikube 跑个 Hello World”的速成手册。它从 Docker 镜像构建开始用一个真实应用前端后端数据库为线索一章一章加功能加健康检查、加服务发现、加 ConfigMap、加 StatefulSet、加 HorizontalPodAutoscaler……直到最后把整套监控链路Prometheus Grafana和集群扩缩容逻辑都跑通。全书 18 章没有一章是纯理论——第 3 章讲 Pod就立刻带你写 YAML 并kubectl apply -f第 9 章讲 Deployment就同步对比 RollingUpdate 和 Recreate 策略在灰度发布时的真实日志输出第 14 章讲资源限制直接给出requests/limits设置不当导致 OOMKilled 的kubectl describe pod截图。它不教你怎么从零搭高可用集群那是运维的事但会告诉你当你的 Pod 在节点上被莫名驱逐时该看kubectl get events还是kubectl top node当你发现 Service 始终 503该查 Endpoints 是否为空还是检查 kube-proxy 日志里有没有 iptables 规则加载失败。这本书的作者 Marko Lukša 是 Red Hat Cloud Enablement 团队核心成员2014 年 Kubernetes 还在 v0.4 阶段时他就已经在 OpenShift 上硬刚 WildFly 集群部署了。中文译者是七牛容器云团队KIRK 团队他们不是学院派翻译而是把 QCOS 自研系统砍掉、全面转向 Kubernetes 的实战派——书里所有 YAML 示例都经过 GKE v1.8 和 Minikube 实测GitHub 仓库https://github.com/luksa/kubernetes-in-action里连curl -X POST测试脚本都给你写好了。如果你正在用 K8S 跑业务但还在靠kubectl edit临时改配置、靠kubectl logs -f盯日志、靠删 Pod 硬重启来“解决”问题这本书就是你缺的那张作战地图。2. 从单 Pod 到多副本服务用真实 YAML 拆解 Kubernetes 最小可运行单元2.1 Pod 不是容器而是容器的“运行时上下文”为什么必须用 YAML 而非docker run很多人初学 K8S 时有个根深蒂固的误解Pod 就是“一组容器”。这没错但漏掉了最关键的部分——Pod 是 Kubernetes 调度的最小原子单位它封装的不仅是容器镜像更是一组共享 Linux Namespace网络、IPC、UTS和 Volume 的进程集合。这意味着同一个 Pod 内的容器共享同一个 IP 地址和端口空间localhost:8080对两个容器来说指向的是彼此它们挂载同一个 Volume 时看到的是完全一致的文件系统视图无需 NFS 或对象存储中转它们共用同一个 Network Namespace所以netstat -tuln输出对所有容器都一样。这种设计不是为了炫技而是为了解决“紧耦合辅助进程”问题。比如一个 Web 应用容器需要日志轮转传统做法是让应用自己实现 logrotate或在宿主机上跑 cron。而在 K8S 里你可以起一个 sidecar 容器专门做日志收集如 fluentd它和主应用共享/var/log/app目录主应用只管写文件fluentd 只管读并转发到 ES。这种协作模式只有 Pod 提供的共享上下文才能低成本实现。提示docker run启动的容器是孤立的而kubectl run创建的 Pod 是受控对象——它有 OwnerReference、有 Status 字段、会被 kube-scheduler 分配节点、被 kubelet 拉起、被 kube-controller-manager 持续 reconcile。这就是为什么你永远不该用docker run替代kubectl apply -f pod.yaml。下面是一个最简 Pod YAML来自书中第 2 章我们逐行拆解其生产级含义# ch02/hello-world-pod.yaml apiVersion: v1 kind: Pod metadata: name: hello-world labels: app: hello-world spec: containers: - name: nginx image: nginx:1.19 ports: - containerPort: 80 protocol: TCP livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /readyz port: 80 initialDelaySeconds: 5 periodSeconds: 5 restartPolicy: AlwaysapiVersion: v1声明使用 core/v1 API 组这是 Pod、Service、ConfigMap 等基础资源的稳定版本。注意不要写v1beta1或v1alpha1那些是实验性 APIK8S v1.25 已废弃。labels: {app: hello-world}标签不是装饰品。它是 K8S 所有选择器Selector的基石——Service 用它找后端 PodDeployment 用它管理副本kubectl get pods -l apphello-world用它过滤。生产环境必须打标且建议遵循 Kubernetes Labels and Annotations 官方约定如app.kubernetes.io/name,app.kubernetes.io/version。containerPort: 80这个字段不开放端口它只是文档化声明“此容器监听 80 端口”供人阅读和工具校验。真正控制网络暴露的是 Service。livenessProbe和readinessProbe这是本书第 4 章重点讲的“健康检查双保险”。livenessProbe失败 → kubelet 杀死容器并重启防僵死readinessProbe失败 → 从 Service 的 Endpoints 中移除该 Pod IP防流量打入。initialDelaySeconds必须设够——Nginx 启动要时间若设太小Pod 还没起来 probe 就失败导致无限重启循环。restartPolicy: AlwaysPod 级别的重启策略。注意它只对kubectl run创建的裸 Pod 有效对于由 Deployment 管理的 Pod此字段被忽略实际由控制器决定重启行为。执行这条命令你就把一个带健康检查的 Pod 部署进集群了kubectl apply -f ch02/hello-world-pod.yaml然后立刻验证# 查看 Pod 状态应为 Running kubectl get pod hello-world -o wide # 查看详细事件确认是否拉取镜像成功、是否通过 readinessProbe kubectl describe pod hello-world # 实时查看容器日志注意这里指定的是容器名 nginx不是 Pod 名 kubectl logs -f hello-world -c nginx2.2 从单 Pod 到 ReplicaSet为什么不能只靠kubectl scale单 Pod 是脆弱的。节点宕机、磁盘故障、内核 panic 都会导致它消失。K8S 的解决方案不是“备份 Pod”而是“声明期望状态”——你告诉集群“我要 3 个hello-worldPod”控制器就会持续确保这个数字成立。这个控制器就是 ReplicaSetRS而 Deployment 是它的“增强封装”。书中第 4 章给出了一个典型 RS YAML简化版# ch04/hello-world-rs.yaml apiVersion: apps/v1 kind: ReplicaSet metadata: name: hello-world-rs labels: app: hello-world spec: replicas: 3 selector: matchLabels: app: hello-world template: metadata: labels: app: hello-world spec: containers: - name: nginx image: nginx:1.19 ports: - containerPort: 80关键点解析replicas: 3声明期望副本数。RS 控制器会不断比对kubectl get pods -l apphello-world | wc -l是否等于 3不等就创建或删除。selector.matchLabels定义“哪些 Pod 归我管”。它必须和template.metadata.labels完全一致否则 RS 找不到自己的 Pod会疯狂创建新 Pod血泪经验漏写一个 label集群瞬间生成 100 个孤儿 Pod。templatePod 模板。每次创建新 Pod 时RS 都会用这个模板“克隆”一个新实例。注意template里的metadata.labels是给新 Pod 打标的spec.selector是用来匹配已有 Pod 的二者逻辑不同但值必须相同。但生产环境绝不该直接操作 ReplicaSet。原因有三滚动更新缺失RS 本身不支持滚动更新。你想把 nginx 从 1.19 升到 1.20只能先删旧 RS再建新 RS——这会造成服务中断。历史版本不可追溯RS 没有版本号概念你无法回滚到上一个镜像版本。升级过程不可控无法设置maxSurge最多多几个 Pod、maxUnavailable最多少几个 Pod等精细参数。所以Deployment 才是正解。它在 RS 之上加了一层“发布编排”能力。书中第 9 章的 Deployment YAML 长这样# ch09/hello-world-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello-world spec: replicas: 3 selector: matchLabels: app: hello-world strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: hello-world spec: containers: - name: nginx image: nginx:1.19 # ← 这里改镜像就能触发滚动更新 ports: - containerPort: 80执行更新只需一行# 方式1直接改 YAML 文件再 apply kubectl apply -f ch09/hello-world-deployment.yaml # 方式2在线修改镜像更常用 kubectl set image deployment/hello-world nginxnginx:1.20此时Deployment 会创建一个新的 RS比如hello-world-7d8b9c4567按maxSurge1先起 1 个新 Pod再按maxUnavailable1删 1 个旧 Pod如此交替直到所有 Pod 都是新镜像。全程kubectl get pods始终显示 3 个 Running Pod服务零中断。2.3 Service让 Pod 不再是“IP 黑匣子”用 ClusterIP 实现内部服务发现Pod 是临时的——IP 会变、生命周期短、数量动态伸缩。如果前端 Pod 直接写死后端 Pod 的 IP那每次后端扩缩容都要改前端代码这显然不可行。K8S 的答案是 Service一个稳定的虚拟 IPClusterIP背后代理一组动态变化的 Pod。书中第 5 章的 Service YAML 如下# ch05/hello-world-service.yaml apiVersion: v1 kind: Service metadata: name: hello-world-svc spec: selector: app: hello-world ports: - port: 80 targetPort: 80 protocol: TCPselector: {app: hello-world}Service 通过这个标签选择器自动关联所有带app: hello-world标签的 Pod。它会实时监听 Pod 变化动态更新 Endpoints。port: 80Service 暴露的端口集群内其他 Pod 访问hello-world-svc:80。targetPort: 80转发到后端 Pod 的哪个端口即容器实际监听的端口。可以是数字80也可以是字符串http后者需在 Pod 的containerPort中定义name: http。执行后你会看到kubectl apply -f ch05/hello-world-service.yaml kubectl get svc hello-world-svc # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # hello-world-svc ClusterIP 10.96.123.45 none 80/TCP 10s这个10.96.123.45就是 Service 的虚拟 IP。集群内任何 Pod 都可以通过curl http://hello-world-svc:80访问它。K8S 是怎么做到的kube-proxy组件在每个节点上运行监听 Service 变化当 Service 创建时kube-proxy 会在节点上生成 iptables 规则或 IPVS 规则将发往10.96.123.45:80的流量随机转发到后端某个 Pod 的 IP:80同时它维护一个 Endpoints 对象记录当前所有健康 Pod 的 IP:Port 列表kubectl get endpoints hello-world-svc # NAME ENDPOINTS AGE # hello-world-svc 10.244.1.3:80,10.244.1.4:80,10.244.1.5:80 2m注意Service 的selector必须和 Pod 的labels严格匹配。常见翻车点Pod YAML 里写了labels: {app: hello-world}但 Service YAML 里写成了selector: {app: helloworld}少了个短横线结果 Endpoints 为空curl直接超时。务必用kubectl get endpoints验证3. 让配置与敏感信息脱离镜像ConfigMap 与 Secret 的生产级用法3.1 ConfigMap不只是“键值对”而是配置的“版本快照”Docker 镜像一旦构建完成里面的配置文件如application.properties就固化了。但在 K8S 环境中同一镜像可能要部署在测试、预发、生产三个环境每个环境的数据库地址、Redis 密码都不同。如果为每个环境构建一个镜像CI/CD 流水线会爆炸式增长。ConfigMap 就是为此而生——它把配置数据从镜像中剥离作为独立的 K8S 对象存在Pod 启动时按需挂载。书中第 7 章展示了三种挂载方式我们重点讲最实用的两种方式一挂载为文件推荐用于结构化配置假设你有一个config.yaml# config.yaml database: url: jdbc:mysql://mysql-prod:3306/myapp username: prod_user redis: host: redis-prod port: 6379先创建 ConfigMapkubectl create configmap app-config --from-fileconfig.yaml再在 Pod 中挂载# ch07/pod-with-configmap.yaml apiVersion: v1 kind: Pod metadata: name: app-pod spec: containers: - name: app image: myapp:1.0 volumeMounts: - name: config-volume mountPath: /etc/app/config.yaml subPath: config.yaml # ← 关键指定挂载 ConfigMap 中的哪个 key volumes: - name: config-volume configMap: name: app-config这样容器内/etc/app/config.yaml就是config.yaml的内容。应用启动时读这个文件即可。优势在于配置变更只需kubectl create configmap --from-filenew-config.yaml再kubectl rollout restart deployment/app-deployment无需重新构建镜像ConfigMap 有resourceVersion每次更新都会生成新版本便于审计和回滚kubectl get configmap app-config -o yaml查看。方式二注入为环境变量适合简单键值对于少量配置如LOG_LEVELdebug用环境变量更轻量# ch07/pod-with-env.yaml apiVersion: v1 kind: Pod metadata: name: app-pod-env spec: containers: - name: app image: myapp:1.0 envFrom: - configMapRef: name: app-config-env # ← 这个 ConfigMap 的 data 字段是 key-value 形式对应的 ConfigMapkubectl create configmap app-config-env \ --from-literalLOG_LEVELdebug \ --from-literalAPP_NAMEmyapp-prod避坑 / 常见问题 / 排查现象Pod 挂载 ConfigMap 后容器内文件内容是空的或报错No such file or directory。原因subPath拼写错误或 ConfigMap 中不存在该 key或者挂载路径/etc/app/不存在容器启动失败。解决先kubectl describe pod app-pod查看 Events确认是否有MountVolume.SetUp failed再kubectl get configmap app-config -o yaml确认 key 名称最后在容器内ls -l /etc/app/看目录是否存在不存在则在volumeMounts下加readOnly: true并确保容器启动命令不依赖该目录初始存在。现象修改 ConfigMap 后已运行的 Pod 中的挂载文件没有更新。原因ConfigMap 挂载为文件时默认是只读且不会热更新。K8S 不会主动通知容器文件变了。解决方案一kubectl rollout restart deployment/app-deployment强制重建 Pod方案二用kubectl set env注入环境变量环境变量会随 ConfigMap 更新而更新方案三在应用内监听文件变化如 Spring Boot 的ConfigurationProperties支持 reload。现象envFrom注入的环境变量在容器内printenv | grep LOG_LEVEL查不到。原因ConfigMap 的data字段必须是字符串不能是嵌套 JSON/YAML。--from-literal是安全的但--from-file如果文件内容是{log:debug}K8S 会把它当字符串存环境变量值就是整个 JSON 字符串而非解析后的 key。解决--from-file只用于挂载文件环境变量一律用--from-literal或手动写 YAML 的data字段确保每行是一个key: value。3.2 Secret别再把密码写进 YAML用 base64 编码只是障眼法Secret 用于存储敏感数据如数据库密码、API Token、TLS 证书。很多人误以为kubectl create secret generic my-secret --from-literalpassword123456很安全因为kubectl get secret my-secret -o yaml显示的是 base64 编码。但这是严重误区——base64 不是加密只是编码echo MTIzNDU2 | base64 -d一秒就解出来。Secret 的真正安全机制是权限隔离Secret 默认只有get、list、watch权限普通用户无法describe查看明文除非 RBAC 显式授权挂载保护挂载到 Pod 的 Secret 文件Linux 权限是644但 kubelet 会确保只有容器内 root 用户可读/var/run/secrets/kubernetes.io/serviceaccount/下的 token 除外etcd 加密K8S 1.13 支持对 etcd 中的 Secret 数据进行静态加密需配置--encryption-provider-config。书中第 7 章强调永远不要在 YAML 中硬编码密码。正确流程是用kubectl create secret创建 Secret在 Pod YAML 中通过secretKeyRef引用应用从环境变量或文件中读取。示例挂载为文件# 创建 Secret密码会被 base64 编码存储 kubectl create secret generic db-secret \ --from-literalusernameadmin \ --from-literalpasswordPssw0rd!2024Pod 中引用# ch07/pod-with-secret.yaml apiVersion: v1 kind: Pod metadata: name: app-pod-secret spec: containers: - name: app image: myapp:1.0 env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-secret这样容器内DB_USERNAME环境变量值是admin/etc/secrets/password文件内容是Pssw0rd!2024。注意Secret 的data字段存储的是 base64 编码后的字节流stringData字段则允许你直接写明文K8S 会自动编码。但stringData仅用于创建kubectl get secret永远只显示data。所以 CI/CD 脚本中用--from-literal最安全。4. 有状态应用的破局之道StatefulSet 与 Headless Service 的黄金组合4.1 为什么 Deployment 不适合数据库StatefulSet 的三大不可替代性无状态应用如 Nginx、API Server可以用 Deployment 随意扩缩容因为每个实例都一样删哪个都无所谓。但有状态应用如 MySQL、Redis Cluster、ZooKeeper不行——它们需要稳定的网络标识每个实例必须有固定 DNS 名如mysql-0.mysql.default.svc.cluster.local客户端才能直连特定节点稳定的存储卷Pod 重建后必须挂载原来的 PVCPersistentVolumeClaim否则数据丢了有序部署与扩缩容启动时必须mysql-0先起来并初始化mysql-1才能加入集群缩容时必须先删mysql-2再删mysql-1不能乱序。Deployment 无法满足这三点。StatefulSet 就是为此而生。书中第 10 章用一个 Redis Cluster 示例完整演示了如何用 StatefulSet 管理 3 主 3 从的集群。一个典型的 StatefulSet YAML简化# ch10/redis-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: redis-headless # ← 关键关联的 Headless Service 名 replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7.0 ports: - containerPort: 6379 volumeMounts: - name: redis-data mountPath: /data volumeClaimTemplates: # ← StatefulSet 特有为每个 Pod 创建独立 PVC - metadata: name: redis-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10Gi关键字段解析serviceName: redis-headless必须指向一个 Headless Service即clusterIP: None的 Service。这是 StatefulSet 获取稳定 DNS 的前提。volumeClaimTemplates模板为每个 Podredis-0,redis-1,redis-2生成一个独立的 PVC。PVC 名为redis-data-redis-0绑定到 PV 后即使redis-0Pod 被删重建新 Pod 仍会挂载同一个 PV数据不丢。Pod 名固定kubectl get pods显示redis-0,redis-1,redis-2顺序严格不可乱。4.2 Headless Service没有 ClusterIP 的 Service只为 DNS 而生Headless Service无头服务是 StatefulSet 的“另一半”。它不分配 ClusterIP而是直接将 DNS 查询解析为后端 Pod 的 IP 列表。创建 Headless Service# ch10/redis-headless-svc.yaml apiVersion: v1 kind: Service metadata: name: redis-headless spec: clusterIP: None # ← 关键设为 None selector: app: redis ports: - port: 6379 targetPort: 6379效果kubectl get svc redis-headless显示CLUSTER-IP为nonenslookup redis-headless返回所有 Pod 的 IPName: redis-headless.default.svc.cluster.local Address 1: 10.244.1.10 redis-0.redis-headless.default.svc.cluster.local Address 2: 10.244.1.11 redis-1.redis-headless.default.svc.cluster.local Address 3: 10.244.1.12 redis-2.redis-headless.default.svc.cluster.localnslookup redis-0.redis-headless返回10.244.1.10—— 这就是redis-0的稳定 DNS 名正是这个 DNS 名让 Redis Cluster 的redis-cli --cluster create命令能准确识别每个节点的地址构建出正确的集群拓扑。避坑 / 常见问题 / 排查现象StatefulSet 的 Pod 一直卡在Pending状态kubectl describe pod redis-0显示0/1 nodes are available: 1 node(s) had volume node affinity conflict.原因volumeClaimTemplates创建的 PVC其 StorageClass 默认是standard但你的集群可能没有standard类型的 StorageClass或该类 PV 已耗尽。解决kubectl get storageclass查看可用类型在volumeClaimTemplates.spec中显式指定storageClassName: my-sc或用kubectl patch pvc redis-data-redis-0 -p {spec:{storageClassName:my-sc}}临时修复。现象nslookup redis-0.redis-headless解析失败返回server cant find redis-0.redis-headless: NXDOMAIN。原因Headless Service 的selector和 Pod 的labels不匹配或 StatefulSet 的serviceName字段没指向这个 Headless Service。解决kubectl get svc redis-headless -o yaml确认selectorkubectl get statefulset redis -o yaml确认serviceNamekubectl get pods -l appredis确认 Pod 标签。现象StatefulSet 缩容到 2 个副本后redis-2Pod 被删但它的 PVCredis-data-redis-2还在占着 10Gi 空间。原因PVC 的persistentVolumeReclaimPolicy默认是Retain即 PV 不会自动回收。这是设计使然——防止误删数据。解决手动清理kubectl delete pvc redis-data-redis-2→ PV 进入Released状态 →kubectl patch pv pv-name -p {spec:{persistentVolumeReclaimPolicy:Delete}}→ PV 被删。生产环境建议用ReclaimPolicy: Delete的 StorageClass。5. 生产环境必守红线资源请求requests与限制limits的精确计算5.1 为什么limits: memory: 2Gi可能导致 OOMKilledQoS 类别决定生死K8S 调度器不是“看菜下饭”而是“按需分配”。你给 Pod 设置resources.requests.memory: 1Gi调度器就确保目标节点有至少 1Gi 的可分配内存即allocatable减去已分配。但limits.memory: 2Gi是另一回事——它告诉 kubelet“这个容器最多能用 2Gi 内存超了就杀”。问题来了如果应用实际用了 1.5Gi但节点总内存只剩 500Mikubelet 会根据 QoSQuality of Service优先级杀掉低优先级的 Pod 来腾内存。K8S 定义了三种 QoS 类别Guaranteedrequests limits且必须设置 CPU 和内存。最高优先级最不容易被杀。Burstablerequests limits或只设了requests。中等优先级。BestEffort没设requests和limits。最低优先级OOM 时第一个被杀。书中第 14 章用一张表说清了后果QoS 类别内存压力下行为CPU 使用率上限Guaranteed仅当自身用量 limits时被杀严格限制在limits内Burstable当节点内存不足时可能被杀按requests排序requests 小的先杀可短暂超过limits取决于节点负载BestEffort第一个被杀无限制所以limits: memory: 2Gi本身没问题但如果你的requests设得太低比如requests: 256Mi这个 Pod 就是 Burstable内存紧张时极易被 OOMKilled。5.2 如何科学设置 requests/limits用kubectl top和压测数据说话凭空猜requests是玄学。正确方法是基线测量用kubectl top pod pod-name查看 Pod 运行时的真实内存/CPU 使用量需部署 metrics-server压测验证用hey -z 5m -q 10 -c 50 http://your-service模拟 50 并发观察kubectl top pod峰值留足余量requests设为压测峰值的 1.2~1.5 倍limits设为峰值的 2~3 倍防突发。示例书中第 14 章的 Nginx 压测结果峰值内存850Mi峰值 CPU350m即 0.35 核则合理设置resources: requests: memory: 1024Mi # 1Gi留 20% 余量 cpu: 400m # 0.4 核 limits: memory: 2048Mi # 2Gi防突发 cpu: 800m # 0.8 核执行后用kubectl describe pod验证kubectl describe pod nginx-pod # ... # Containers: # nginx: # Limits: # cpu: 800m # memory: 2Gi # Requests: # cpu: 400m # memory: 1Gi # ... # QoS Class: Burstable # ← 确认类别注意CPUlimits会影响调度公平性。K8S 使用 CFSCompletely Fair Scheduler配额limits.cpu: 100m表示该容器每 100ms 周期最多用 10ms CPU 时间。如果应用是 CPU 密集型如 FFmpeg 转码设太低会导致性能骤降。此时应设requests limits进入 Guaranteed 类别获得稳定 CPU 时间片。6. 从“能跑”到“稳跑”用 HorizontalPodAutoscaler 实现真正的弹性伸缩6.1 HPA 不是魔法而是基于指标的闭环控制理解targetAverageUtilization的真实含义HorizontalPodAutoscalerHPA是 K8S 的自动扩缩容控制器。它监听指标如 CPU 使用率、内存、自定义指标当指标超过阈值时增加 Pod 副本低于阈值时减少副本。书中第 15 章强调**HPA 的核心是 targetAverage本文还有配套的精品资源点击获取