简介:本资源是《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,那些是实验性 API,K8S v1.25+ 已废弃。labels: {app: hello-world}:标签不是装饰品。它是 K8S 所有选择器(Selector)的基石——Service 用它找后端 Pod,Deployment 用它管理副本,kubectl get pods -l app=hello-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: Always:Pod 级别的重启策略。注意:它只对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”,控制器就会持续确保这个数字成立。这个控制器就是 ReplicaSet(RS),而 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 app=hello-world | wc -l是否等于 3,不等就创建或删除。selector.matchLabels:定义“哪些 Pod 归我管”。它必须和template.metadata.labels完全一致,否则 RS 找不到自己的 Pod,会疯狂创建新 Pod(血泪经验:漏写一个 label,集群瞬间生成 100+ 个孤儿 Pod)。template:Pod 模板。每次创建新 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 nginx=nginx:1.20此时,Deployment 会创建一个新的 RS(比如hello-world-7d8b9c4567),按maxSurge=1先起 1 个新 Pod,再按maxUnavailable=1删 1 个旧 Pod,如此交替,直到所有 Pod 都是新镜像。全程kubectl get pods始终显示 3 个 Running Pod,服务零中断。
2.3 Service:让 Pod 不再是“IP 黑匣子”,用 ClusterIP 实现内部服务发现
Pod 是临时的——IP 会变、生命周期短、数量动态伸缩。如果前端 Pod 直接写死后端 Pod 的 IP,那每次后端扩缩容都要改前端代码,这显然不可行。K8S 的答案是 Service:一个稳定的虚拟 IP(ClusterIP),背后代理一组动态变化的 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: 80:Service 暴露的端口(集群内其他 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先创建 ConfigMap:
kubectl create configmap app-config --from-file=config.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-file=new-config.yaml,再kubectl rollout restart deployment/app-deployment,无需重新构建镜像; - ConfigMap 有
resourceVersion,每次更新都会生成新版本,便于审计和回滚(kubectl get configmap app-config -o yaml查看)。
方式二:注入为环境变量(适合简单键值)
对于少量配置,如LOG_LEVEL=debug,用环境变量更轻量:
# 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 形式对应的 ConfigMap:
kubectl create configmap app-config-env \ --from-literal=LOG_LEVEL=debug \ --from-literal=APP_NAME=myapp-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-literal=password=123456很安全,因为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-literal=username=admin \ --from-literal=password='P@ssw0rd!2024'Pod 中引用:
# 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文件内容是P@ssw0rd!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 重建后,必须挂载原来的 PVC(PersistentVolumeClaim),否则数据丢了;
- 有序部署与扩缩容:启动时必须
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:模板,为每个 Pod(redis-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为<none>;nslookup redis-headless返回所有 Pod 的 IP:Name: 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 can't find redis-0.redis-headless: NXDOMAIN。
原因:Headless Service 的selector和 Pod 的labels不匹配;或 StatefulSet 的serviceName字段没指向这个 Headless Service。
解决:kubectl get svc redis-headless -o yaml确认selector;kubectl get statefulset redis -o yaml确认serviceName;kubectl get pods -l app=redis确认 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可能导致 OOMKilled?QoS 类别决定生死
K8S 调度器不是“看菜下饭”,而是“按需分配”。你给 Pod 设置resources.requests.memory: 1Gi,调度器就确保目标节点有至少 1Gi 的可分配内存(即allocatable减去已分配)。但limits.memory: 2Gi是另一回事——它告诉 kubelet:“这个容器最多能用 2Gi 内存,超了就杀”。问题来了:如果应用实际用了 1.5Gi,但节点总内存只剩 500Mi,kubelet 会根据 QoS(Quality of Service)优先级,杀掉低优先级的 Pod 来腾内存。
K8S 定义了三种 QoS 类别:
- Guaranteed:
requests == limits(且必须设置 CPU 和内存)。最高优先级,最不容易被杀。 - Burstable:
requests < 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
- 峰值 CPU:350m(即 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 # ← 确认类别注意:CPU
limits会影响调度公平性。K8S 使用 CFS(Completely Fair Scheduler)配额,limits.cpu: 100m表示该容器每 100ms 周期最多用 10ms CPU 时间。如果应用是 CPU 密集型(如 FFmpeg 转码),设太低会导致性能骤降。此时应设requests == limits,进入 Guaranteed 类别,获得稳定 CPU 时间片。
6. 从“能跑”到“稳跑”:用 HorizontalPodAutoscaler 实现真正的弹性伸缩
6.1 HPA 不是魔法,而是基于指标的闭环控制:理解targetAverageUtilization的真实含义
HorizontalPodAutoscaler(HPA)是 K8S 的自动扩缩容控制器。它监听指标(如 CPU 使用率、内存、自定义指标),当指标超过阈值时,增加 Pod 副本;低于阈值时,减少副本。书中第 15 章强调:**HPA 的核心是 `targetAverage
本文还有配套的精品资源,点击获取