(注:您提供的原文已经是简体中文。为了符合您“使用口语化中文”的要求,我对原文进行了润色和重写,去掉了稍微生硬的书面语感,让整体读起来更像是一位资深开发者在跟你面对面“掏心窝子”分享经验,同时严格保留了产品名和技术术语。)
Kubernetes 隐藏玩法揭秘:那些你可能不知道的骚操作
上个月,我们生产环境的一个有状态服务出了个怪毛病。Pod 频繁重启,但一重启数据就没了。排查了大半天,最后发现是存储挂载的配置搞错了——Pod 的生命周期和存储的生命周期没对齐。这事儿让我意识到,虽然我们天天写 Deployment 和 Service,但 K8s 里有些机制,我们顶多算“用过”,根本谈不上“懂”。
今天我想分享几个在实际踩坑中摸索出来的 K8s 玩法。这些不是什么炫酷的新特性,但确实是我一开始没注意、后来却帮了大忙的细节。
Namespace 可不只是用来“分分组”的
刚用 K8s 那会儿,我把 Namespace 纯粹当文件夹用——测试放一个,生产放一个。直到有一次,一个新来的同事在测试环境跑了个有资源泄漏的脚本,差点把整个集群的 CPU 给吃光。
后来我学乖了,用 ResourceQuota 配合 Namespace 做硬隔离:
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: development
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "10"
加上这个之后,development 这个 Namespace 里最多只能跑 10 个 Pod,CPU 请求总量不能超过 4 核。一旦超限直接报错,绝不会让它在那儿默默把整个集群拖垮。
还有一招更绝的,就是配合 LimitRange,防止有人写 Deployment 的时候忘记配资源限制:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: development
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
type: Container
这样一来,任何没显式声明资源的容器,都会被强制套上这层默认值。我之前就踩过这么个坑:没设 limits 的容器遇到 OOM 时,K8s 会直接把进程一枪毙了,根本不会优雅退出。加上 LimitRange 后,这种隐形炸弹算是基本排除了。
Secret 的冷门玩法:当环境变量注入器用
大家都知道 Secret 是存密码的,但很多人不知道它其实还能批量注入环境变量。
假设你有个数据库连接配置:
apiVersion: v1
kind: Secret
metadata:
name: db-config
type: Opaque
stringData:
DB_HOST: postgres.prod.internal
DB_PORT: "5432"
DB_USER: app_reader
DB_PASSWORD: "xK9$mL2pQ"
在 Pod 里你可以这么用:
envFrom:
- secretRef:
name: db-config
一行搞定,所有的键值对直接变成环境变量。比一个个去写 valueFrom.secretKeyRef 爽太多了。
不过这里有个我踩过的坑:stringData 和 data 的区别。data 要求值必须是 base64 编码的,而 stringData 是明文写进去,K8s 存储的时候会自动帮你编码。我之前手写 base64 的时候搞错了一个字符,导致密码一直验不过,硬是排查了两个小时。从那以后我就只用 stringData 了,编码这活儿还是交给 K8s 自己干吧。
Service 和 Pod 生命周期的解耦
这个概念听起来挺简单,但真正理解透了,能解决一大堆诡异问题。
有一次我碰到个现象:Service 能解析出 IP,但请求就是死活不通。最后才发现是 Pod 被重建了,但客户端的 DNS 里还缓存着旧的 Pod IP。
其实 K8s 的设计逻辑是这样的:Service 拿到一个固定的 IP(也就是 ClusterIP),而 Pod 的 IP 是动态变的。Service 通过标签选择器去找后端的 Pod,就算 Pod 被销毁重建了,只要标签能对上,流量就会自动转到新 Pod 上去。
但如果你代码里头直接去访问 Pod IP(比如有些老代码把 IP 写死了),那就肯定要出问题。正确的做法永远是去访问 Service 的 DNS:
my-service.my-namespace.svc.cluster.local
这个 DNS 记录指向的是 Service 的 ClusterIP,而 Service 会自动把流量转发给健康的 Pod。
StatefulSet 的“隐藏身份”
StatefulSet 有个功能特别容易被忽略:它会给每个 Pod 分配一个稳定的网络标识。
比如你声明了一个叫 redis 的 StatefulSet,副本数设为 3,它会创建出:
- redis-0
- redis-1
- redis-2
这些名字是固定死的。就算 redis-1 挂了被拉起来,新的 Pod 还是叫 redis-1,DNS 记录也不会变。这对数据库集群来说简直是救命的——像 Redis Sentinel 或者 PostgreSQL 的节点识别,全靠这种稳定的主机名。
我之前就犯过傻:用 Deployment 去跑 PostgreSQL 主从,结果主节点挂了重建之后 IP 变了,从节点死活连不上,整个集群直接崩了。后来改成 StatefulSet,配合 Headless Service(把 clusterIP 设为 None),每个 Pod 都有了自己独立的 DNS:
redis-0.redis-headless.default.svc.cluster.local
redis-1.redis-headless.default.svc.cluster.local
这下才算真正稳住了。
PersistentVolume 的一个反直觉行为
PV(PersistentVolume)有个特性一开始让我特别懵:它其实是不绑定到特定节点上的。
我以前以为 PV 就是“把某块硬盘挂到某个节点上”,结果 PV 是个集群级别的资源。不管你的 Pod 被调度到哪个节点,K8s 都有办法把那个 PV “弄”过去——可能是网络存储(比如 NFS、EBS),也可能是本地存储(如果你配了 local PV 的话)。
这里有个血泪教训:我有次用 local PV 跑了个有状态服务,结果节点做维护的时候 Pod 被驱逐了。但因为 local PV 是死死绑在那个节点上的,Pod 调度到其他节点后就一直卡在 Pending 状态。最后只能干等原节点恢复。
所以,如果你的存储不是网络共享的,一定要用 nodeAffinity 把 Pod 钉死在那个节点上:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
Ingress 不只是“外部流量的入口”
大家一般都用 Ingress 来暴露 HTTP 服务,但它其实还有个用途:当内部服务的统一网关。
我们有个微服务架构,以前服务间调用都是直接访问 Service DNS。后来上了 gRPC,发现原生 Service 根本不支持 L7 路由。最后的方案是:部署一个内部的 Ingress Controller(不对外暴露),服务间调用全走内部域名:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: internal-grpc
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
rules:
- host: user-service.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: user-service
port:
number: 9090
这么搞的话,其他服务只要访问 user-service.internal:80 就能自动路由到 gRPC 后端,而且还能白嫖 Ingress 自带的负载均衡和重试机制。
几条实在的建议
- Namespace 隔离要趁早:集群刚建好就把 ResourceQuota 和 LimitRange 配上,别等出了事故再补课。
- 管理 Secret 认准 stringData:千万别手写 base64,真的是容易出错。
- 有状态服务必须上 StatefulSet:用 Deployment 跑数据库纯纯是给自己挖坑。
- 用本地存储一定记得配 nodeAffinity:不然 Pod 漂移过去绝对起不来。
- 内部流量也能走 Ingress:需要 L7 路由能力的时候,别硬刚。
K8s 的设计其实非常精妙,很多所谓的“隐藏功能”并不是故意藏起来,纯粹是文档太长咱们没看到而已。我现在养成了个习惯:每用到一种新的资源类型,就把官方文档里关于“行为”的那一节通读一遍,往往能扒出不少以前忽略的细节。