Kubernetes 标签与选择器实战

一、标签到底是干嘛的

标签(Label)就是挂在 K8s 对象上的键值对,key=value 格式。核心用途两个:组织选择

标签就是给资源贴的"分类标签"——这台 Node 是 GPU 节点、那个 Pod 是前端服务、这组资源属于支付团队。选择器(Selector)就是拿着标签去"捞"资源的工具。

标签不是注解(Annotation)。注解是给人看的元数据,比如构建信息、联系人邮箱,K8s 自己不用它来做任何逻辑判断。标签是用来被系统"消费"的,调度器用它、Service 用它、HPA 也用它。别搞混了。

二、命名规范——别等出事了才后悔

1. 建议用前缀区分来源

无前缀的标签在官方语义上被视为用户私有,Kubernetes 核心组件不会用它们做内部逻辑判断。但这不是硬性限制,用 app: nginx 照样能跑。

不过对于自动化组件(包括 CI/CD 系统)打的标签,个人推荐带前缀,方便溯源:

# 推荐
company.com/team: payments
company.com/environment: production

# 也能用,但人多了容易重名
team: payments
env: prod

kubernetes.io/k8s.io/ 这两个前缀是 K8s 核心组件保留的,别碰。

2. 标签值建议有实际含义

标签值最长 63 个字符,可以为空。空值在技术上是允许的——比如用 critical-service 这个 key 存在来表示"是核心服务",不需要具体值。但个人习惯是尽量给值,查询的时候 environment=productionenvironment 好写筛选条件。

值里面只能用字母、数字、横杠、下划线、点——别搞特殊字符进去。

3. 用官方推荐的那套 app.kubernetes.io/*

K8s 官方推荐了一套通用标签,所有工具都能识别。建议每个资源都打上

标签键 说明 示例
app.kubernetes.io/name 应用名称 payment-gateway
app.kubernetes.io/instance 实例唯一标识 payment-gateway-prod-01
app.kubernetes.io/version 版本号 2.3.1
app.kubernetes.io/component 架构组件 apidatabasecache
app.kubernetes.io/part-of 所属更大应用 trade-system
app.kubernetes.io/managed-by 管理工具 helmargocdterraform

这套标签的好处——Helm 打的标签、ArgoCD 打的标签、自己手写的标签,只要都按这套规范来,所有工具都能互相理解。

三、选择器的两种玩法

等值选择器(Equality-based)——最简单的精确匹配

# 精确匹配
selector:
  app: nginx
  environment: production

# 不等于
selector:
  environment: != staging

集合选择器(Set-based)——更灵活,支持 in / notin / exists

# 匹配 environment 是 production 或 staging 的
selector:
  matchExpressions:
    - key: environment
      operator: In
      values: ["production", "staging"]

# 匹配有 app 这个 key 的所有资源(不管值是什么)
selector:
  matchExpressions:
    - key: app
      operator: Exists

⚠️ Deployment 的 selector 是写死的

Deployment 的 spec.selector 一旦创建就不能改了。改了 selector,Deployment 就认不出自己管理的 Pod 了,新的 Pod 起不来,旧的 Pod 删不掉——直接卡死。想改?只能删了重建。

四、SRE 实战:5 条核心铁律

铁律 1:资源之间的标签选择器必须"对得上"

Service 的 selector 必须能选中 Deployment 里 Pod 的 labels。

# Deployment 里的 Pod 标签
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  template:
    metadata:
      labels:
        app: nginx          # 这俩是关键
        environment: prod
---
# Service 的选择器必须能匹配上
apiVersion: v1
kind: Service
spec:
  selector:
    app: nginx             # 必须匹配!
    environment: prod      # 必须匹配!

检查方法:Service 创建后看 Endpoints 有没有 IP。kubectl get endpoints <service-name> 如果是空的,说明 selector 没匹配上任何 Pod。

铁律 2:节点标签用于调度——用之前搞清楚场景

节点标签最常见的用途就是 nodeSelector——把 Pod 限制到特定节点上。

spec:
  nodeSelector:
    node-type: gpu
    disk-type: ssd

注意事项:
- 节点标签打了之后,要确认节点状态正常。kubectl get nodes --show-labels 看一眼
- 如果 Pod 一直 Pending,报 node(s) didn't match node selector,就是节点标签和 Pod 的 nodeSelector 对不上
- 节点标签别随便改。改了之后,依赖这个标签调度的 Pod 不会自动重新调度——已经跑着的 Pod 不会动,但新 Pod 可能就调度不到合适的地方了

节点隔离,根据场景选方案
- 只想把特定 Pod 调度到特定节点 → nodeSelector 就够了
- 想让节点"拒绝"大部分 Pod、只有带特定 Toleration 的 Pod 能上来 → 用 Taints + Tolerations(比如节点要维护、或者节点是 spot instance 随时可能被回收)
- 两者可以组合使用,但不是必须捆绑。先想清楚你要解决什么问题,再选工具。

铁律 3:标签要能支撑监控和成本归因

如果你用的是云服务商,云上跑 K8s 最大的痛点之一是——钱花哪了不知道。AWS EKS 现在已经支持把 K8s 标签导入成本分配标签(2024 年推出的功能,每个 Pod 最多 50 个标签)。

所以打标签的时候,就要想着"这个标签以后能不能用来分账":

labels:
  company.com/team: payments          # 哪个团队
  company.com/cost-center: fintech    # 哪个成本中心
  company.com/environment: production # 哪个环境
  company.com/project: wallet-v2      # 哪个项目

铁律 4:版本标签的用法要搞清楚

app.kubernetes.io/version 这个标签建议打在资源上用于标识版本信息。

但要注意——不要把这个标签放进 Deployment 的 selector 里,因为 selector 是不可变的,每次发版改版本号就得跟着改 selector,根本行不通。

正确的用法是:版本标签只做"展示"用途——用来查询、监控、审计,不参与流量路由。滚动更新靠镜像 tag 或 Helm chart 的版本号来驱动,跟这个标签没关系。

# 版本标签放在 metadata.labels 上做标识
metadata:
  labels:
    app.kubernetes.io/version: "2.3.1"

# 但 selector 里绝对不要写 version: 2.3.1
spec:
  selector:
    matchLabels:
      app: nginx            # 只写固定的、不会随版本变的选择标签
      environment: prod

铁律 5:别把数组塞进一个标签里

# 错误示范
labels:
  environments: "prod,staging,dev"

标签不支持数组解析——值就是一个字符串,没法用 In 操作符去匹配一个逗号分隔的字符串里的某个元素。真要表达多个维度,拆成多个标签:

# 正确做法:每个维度一个独立的标签
labels:
  environment: production
  env-staging: "true"
  env-dev: "false"

五、常见翻车现场

翻车 1:Service 的 selector 和 Pod 的 labels 对不上

症状:Service 创建成功,但 kubectl get endpoints 是空的。
排查kubectl describe service 看看 selector 是啥,再看看 Pod 的 labels 是不是完全匹配。
解决:要么改 Service 的 selector,要么改 Pod 的 labels——但 Pod 的 labels 改了之后 Pod 要重建。

翻车 2:改了 Deployment 的 selector

症状:Deployment 状态异常,新 Pod 起不来。
原因:Deployment 的 selector 创建后不可修改。
解决:删了 Deployment 重建。没别的办法。

翻车 3:节点标签被覆盖

症状:节点标签莫名其妙变了,Pod 调度失败。
原因:某些自动化工具(比如 logging operator)会覆盖节点标签。
解决:检查是哪个组件在改标签,用 kubectl label nodes <node> <key>=<value> --overwrite 改回来,然后找到罪魁祸首关掉自动覆盖。

翻车 4:kubectl 版本导致标签选择器 Null 值问题

症状:YAML 里显式写了 labels: null 或某个标签值为 null,并且带了 -l 参数执行 kubectl apply -l 时,报错 error: no objects passed to apply
原因:老版本 kubectl(v1.30.5 及更早)对显式 Null 值标签处理有问题。大多数人是无意中产生 null 的——比如 Helm 模板里某个值没传导致渲染出 null。
解决:升级 kubectl 到 v1.33.0 或更高(v1.33.0 的修复逻辑是:客户端 apply 忽略 null 值标签,服务端 apply 把 null 转成空字符串)。另外,检查一下 Helm 模板或 YAML 源文件,避免渲染出 null。

六、个人偏好(供参考)

  • 用 Kustomize 或 Helm 的模板机制统一注入标签,比在每个文件里手写靠谱得多
  • 节点标签习惯用 node-role.kubernetes.io/<role> 格式,跟 K8s 官方风格保持一致。自定义的用公司域名做前缀,比如 mycompany.com/node-pool: high-memory
  • 标签别打太多——虽然没有硬性数量上限,但 etcd 对单个对象有大小限制(默认约 1.5 MiB),标签打太多会撑爆 metadata。建议控制在 20-30 个以内,否则查询性能会受影响

七、总结

  1. 标签是 K8s 资源组织和调度的核心机制,命名规范、前后一致,能省 80% 的运维麻烦
  2. 命名用 app.kubernetes.io/* 官方推荐标签,再加公司自定义前缀的标签做补充
  3. Deployment 的 selector 是"一次定型"的,创建前想清楚,创建后别乱动
  4. app.kubernetes.io/version 只做展示用途,别放进 selector 里——selector 不可变,放进去就是给自己挖坑
  5. 节点隔离选对工具:nodeSelector 和 Taints/Tolerations 各自有适用场景,不是必须组合使用
  6. 标签要考虑未来的监控和成本归因——打的时候多花 5 分钟想清楚,月底能省 5 小时对账