K8s 核心知识 讲解

【1】、标签选择器 Selector

它是 K8s 最核心的关联逻辑 ,Deployment 管 Pod、Service 转发流量,全靠它


一、核心定义(大白话)

Selector = 标签选择器

就是一个匹配规则 ,作用是:
通过「标签(Labels)」,精准找到你要关联的 Pod

二、先搞懂两个基础概念

  1. 标签(Labels) :给 Pod 贴的「身份标识」,格式 key=value
    比如你的 Pod 贴了:app: wtt
  2. Selector :就是筛选条件
    比如:匹配所有 app=wtt 的 Pod

三、分 2 个场景讲作用(你天天用的)

场景 1:Deployment 里的 Selector → 找自己管理的 Pod

你之前的 Deployment YAML:

yaml 复制代码
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wtt   # 👈 这就是 Deployment 的选择器
  template:
    metadata:
      labels:
        app: wtt # 👈 这是 Pod 的标签
作用:

Deployment 说:
我只管理「标签 = app:wtt」的 Pod,别的 Pod 我不管

  • 标签匹配 → Deployment 负责创建、自愈、扩容 Pod
  • 标签不匹配 → Deployment 看不见这个 Pod

场景 2:Service 里的 Selector → 找要转发流量的 Pod(最重要)

你创建的 Service:

yaml 复制代码
spec:
  selector:
    app: wtt  # 👈 这就是 Service 的选择器
作用:

Service 说:
我只把流量,转发给「标签 = app:wtt」的 Pod

  • 匹配 → 流量能进到 Pod
  • 不匹配 → Service 没有后端 Pod,访问直接报错

四、关键铁律(必背)

  1. Selector 和 Pod 标签必须完全一致

    不一致 → 彻底失联:

    • Deployment 找不到 Pod → 无法自愈
    • Service 找不到 Pod → 无法访问
  2. 一个 Selector 可以匹配多个 Pod

    所有带相同标签的 Pod,都会被选中

    → 所以一个 Service 可以代理 N 个 Pod、N 个 Deployment

  3. kubectl expose 自动继承 Selector

    你执行 kubectl expose deploy wtt

    → Service 会自动复制 Deployment 的 Selector

    → 不用手动写,这就是你之前命令里看不到 wtt 的原因!


五、标签原理

问题1:一个 Deployment 可以有多少个标签?

结论:理论无上限,实际够用就好
  • 技术限制 :K8s 对单个资源的标签数量没有硬上限,但受两个规则约束:

    1. 标签键(含可选前缀)最长253字符,值最长63字符
    2. 单个资源的元数据总大小不能超过256KB(标签太多会占空间)
  • 实际使用 :一般10个以内足够,比如:

    yaml 复制代码
    metadata:
      name: wtt
      labels:
        app: wtt          # 核心标识
        env: prod        # 环境
        version: v1      # 版本
        team: backend    # 所属团队
        tier: frontend   # 层级
        region: beijing  # 地域
  • 关键提醒:Deployment自己的标签,和它管理的Pod的标签是两回事!前者是给Deployment本身分类用的,后者才是Selector匹配的对象。


问题2:一个 Deployment 可以管理多少种标签的 Pod?

结论:只能管理「满足Selector规则的一类Pod」,不是"多种标签"

核心逻辑:Deployment的管理完全靠spec.selector.matchLabels,它是一个AND条件的匹配规则

我只管理「同时包含Selector里所有标签」的Pod,不管它们有没有额外标签。

举个你的例子:

假设你的Deployment是这样的:

yaml 复制代码
spec:
  selector:
    matchLabels:
      app: wtt  # 核心规则:只管理带app:wtt标签的Pod
  template:
    metadata:
      labels:
        app: wtt       # 必须包含Selector的标签
        env: prod      # 额外标签,不影响管理
        version: v1    # 额外标签,不影响管理
  • ✅ 会被管理的Pod:
    • app: wtt, env: prod(包含核心标签)
    • app: wtt, version: v2(包含核心标签)
  • ❌ 不会被管理的Pod:
    • app: other, env: prod(没有核心标签app:wtt
举个你的例子2:

假设你的Deployment是这样的:

yaml 复制代码
selector:
  matchLabels:
    app: nginx
    env: dev

规则:

Pod 必须同时拥有
app=nginx 并且 env=dev

才会被这个 Deployment 管理。

匹配情况

  • app=nginx + env=dev → ✅ 匹配
  • 只有 app=nginx,无 env=dev → ❌ 不匹配
  • 只有 env=dev,无 app=nginx → ❌ 不匹配
  • 两个都没有 → ❌ 不匹配

  1. matchLabels

    只能写键值对等值匹配,全部是与关系,最简单最常用。

  2. matchExpressions

    可以实现:或、不等于、包含、排除 等复杂逻辑

    想要「或关系」必须用它。

不管是 Deployment 选 Pod ,还是 Service 选 Pod

只要写在 matchLabels 里,统一都是:同时满足 = 与

一句话总结:

Deployment管理的是「所有满足Selector规则的Pod」,这些Pod可以有不同的额外标签,但必须都包含Selector里的标签,本质上是"一类Pod",不是"多种标签的Pod"。


问题3:一个 Pod 可以有多少个标签?

结论:和Deployment一样,理论无上限,实际按需添加
  • 技术限制和Deployment完全相同(键值长度、元数据大小)

  • 实际使用中,一般5-10个标签足够,用来被不同的控制器/Service筛选:

    yaml 复制代码
    metadata:
      labels:
        app: wtt          # 被Deployment管理
        env: prod         # 被监控系统筛选
        tier: frontend    # 被Service匹配
        version: v1       # 用于灰度发布
        region: beijing   # 用于地域调度
  • 好处:一个Pod可以被多个不同的Selector筛选,比如Deployment管它、Service转发流量给它、监控系统收集它的指标。


问题4:一个 Service 可以服务多个标签的 Deployment 吗?

结论:可以!但关键是「所有Deployment的Pod都包含Service的Selector标签」

Service的spec.selector也是 AND 条件的匹配规则,它会把流量转发给所有包含Selector标签的Pod,不管这些Pod来自哪个Deployment。

举个例子:
bash 复制代码
# 1. 创建v1版本Deployment(带app:wtt标签)
kubectl create deploy wtt-v1 --image=nginx:1.20 --replicas=2
# 为名为 wtt-v1 的 Deployment 下的 Pod 添加、更新 标签
kubectl patch deploy wtt-v1 -p '{"spec":{"template":{"metadata":{"labels":{"app":"wtt","version":"v1"}}}}}'

# 2. 创建v2版本Deployment(带app:wtt标签)
kubectl create deploy wtt-v2 --image=nginx:1.25 --replicas=2
kubectl patch deploy wtt-v2 -p '{"spec":{"template":{"metadata":{"labels":{"app":"wtt","version":"v2"}}}}}'

# 3. 创建Service,Selector设为app:wtt
kubectl expose deploy wtt-v1 --port=80 --selector=app=wtt --name=wtt-service
  1. 创建两个不同版本的Deployment,Pod都带app: wtt标签:

    • DeploymentA(wtt-v1):Pod标签 app: wtt, version: v1
    • DeploymentB(wtt-v2):Pod标签 app: wtt, version: v2
  2. 创建一个名字为my-service 的 Service, 通过 --selector=app=wtt 将 Selector设为app: wtt

    bash 复制代码
    kubectl expose deploy wtt-v1 --name=my-service --port=80 --selector=app=wtt
  3. 效果:这个Service会同时匹配两个Deployment的所有Pod,流量会被负载均衡到v1和v2的Pod上,相当于同时服务两个Deployment!


【2】、一 Service 管 二 Deployment 的经典YAML示例

一、完整的YAML文件

我们一共需要3个文件:

  • wtt-v1.yaml:v1版本的Deployment
  • wtt-v2.yaml:v2版本的Deployment
  • wtt-service.yaml:匹配两个版本的Service

1. v1版本Deployment(wtt-v1.yaml)

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wtt-v1
  labels:
    app: wtt
    version: v1
spec:
  replicas: 2  # 启动2个v1版本的Pod
  selector:
    matchLabels:
      # 👇 Deployment的Selector:只管理同时满足这两个标签的Pod
      app: wtt
      version: v1
  template:
    metadata:
      labels:
        # 👇 Pod的标签,必须和Deployment的Selector完全匹配
        app: wtt       # 核心标签,Service会匹配这个
        version: v1    # 版本标签,区分不同Deployment
    spec:
      containers:
      - name: nginx
        image: nginx:1.20  # v1版本用Nginx 1.20
        ports:
        - containerPort: 80

2. v2版本Deployment(wtt-v2.yaml)

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wtt-v2
  labels:
    app: wtt
    version: v2
spec:
  replicas: 2  # 启动2个v2版本的Pod
  selector:
    matchLabels:
      app: wtt
      version: v2
  template:
    metadata:
      labels:
        app: wtt       # 同样带app:wtt标签,Service能匹配到
        version: v2    # 版本标签,和v1区分开
    spec:
      containers:
      - name: nginx
        image: nginx:1.25  # v2版本用Nginx 1.25(和v1区分)
        ports:
        - containerPort: 80

3. 共用的Service(wtt-service.yaml)

关键就在这里!Service的Selector只写了app: wtt,没有限制version,所以会同时匹配两个Deployment的Pod:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: wtt-service
spec:
  selector:
    # 👇 核心规则:只匹配带app:wtt标签的Pod,不管version是v1还是v2
    app: wtt
  ports:
    - protocol: TCP
      port: 80          # Service的端口(集群内访问用)
      targetPort: 80   # Pod容器的端口,和Nginx的containerPort一致
  type: ClusterIP  # 可以改成NodePort,方便外部测试

二、一步创建所有资源

把上面3个文件放在同一个目录下,执行下面的命令:

bash 复制代码
# 应用所有YAML文件
kubectl apply -f .

三、验证效果(关键步骤)

1. 查看所有Pod

bash 复制代码
kubectl get pods -o wide

你会看到4个Pod:

  • 2个来自wtt-v1,标签是app:wtt, version:v1
  • 2个来自wtt-v2,标签是app:wtt, version:v2

2. 查看Service的Endpoints(最关键!)

bash 复制代码
kubectl describe svc wtt-service

在输出里找到Endpoints字段,你会看到4个Pod的IP地址,说明Service已经把两个Deployment的所有Pod都纳入了后端池!

3. 测试负载均衡(可选)

如果你想直观看到流量被分到两个版本,可以在集群内用curl访问Service:

bash 复制代码
# 先拿到Service的ClusterIP
kubectl get svc wtt-service

# 用curl访问,多执行几次,会轮询到v1和v2的Pod
curl http://<你的ClusterIP>:80

小技巧:可以修改v1和v2的Nginx首页,让它们返回不同内容,这样curl的时候能明显看到效果:

bash 复制代码
# 给v1的Pod修改首页(替换成你的Pod名字)
kubectl exec <wtt-v1的Pod名> -- sh -c "echo 'I am v1' > /usr/share/nginx/html/index.html"

# 给v2的Pod修改首页
kubectl exec <wtt-v2的Pod名> -- sh -c "echo 'I am v2' > /usr/share/nginx/html/index.html"

四、核心原理解析(为什么能同时服务两个Deployment?)

  1. Deployment的Selectorapp:wtt AND version:v1
    • 每个Deployment只管理自己版本的Pod,确保不会互相干扰。
  2. Service的Selectorapp:wtt
    • 只筛选带app:wtt标签的Pod,不关心version,所以v1和v2的Pod都会被选中。
  3. 效果 :流量会被K8s的Service自动负载均衡到所有符合条件的Pod上,实现了"一个服务入口,同时服务多个版本"的效果,这就是灰度发布/金丝雀发布的基础!


【3】、Service 创建方式

一、先搞懂两种创建方式的定位

1. kubectl expose:命令式快捷创建

它的核心作用是:用一行命令,快速给已有的Deployment/Pod创建一个简单的Service ,不用写完整YAML,K8s会自动帮你补全很多配置(比如自动匹配Deployment的标签作为Selector、自动生成端口映射)。

举个例子,你给 wtt 部署创建Service:

bash 复制代码
# 简写版
kubectl expose deploy wtt --port=80

# 效果 等同于 下面的 完成版
kubectl expose deploy wtt --port=80 --target-port=80 --selector=app=wtt --name=wtt

简写版 它会自动帮你:

  • 生成一个和Deployment同名的 Service wtt
  • 自动把Selector设置为 和Deployment 的标签 一致的 app: wtt
  • 自动创建ClusterIP类型的Service
  • 自动 设置 --target-port=80 和 --port=80 保持一直, --port=80 是 Service 的端口, 必须写。
对比维度 第一行命令 (简写) 第二行命令 (全写) 是否等同
Service 名称 默认继承 Deployment 名 → wtt 显式指定 --name=wtt ✅ 等同
标签选择器 自动提取 Pod 模板标签 → app=wtt 显式指定 --selector=app=wtt ✅ 等同
端口转发 port=80targetPort 默认为 80 显式指定 port=80target-port=80 ✅ 等同

2. YAML + kubectl apply:声明式创建(生产首选)

这才是创建Service的"正统方式",也是生产环境的标准做法。你可以通过完整的YAML文件,定义Service的所有细节,包括类型、端口、Selector、会话保持、外部流量策略等,然后用 kubectl apply -f xxx.yaml 创建。

给你举一个和上面 kubectl expose 效果完全等价的YAML例子,对应你的 wtt 部署:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: wtt-svc  # Service的名字,自定义即可
spec:
  selector:
    app: wtt     # 关键:必须和Deployment中Pod模板的标签完全一致,才能绑定到你的Pod
  ports:
    - protocol: TCP
      port: 80       # Service自身的端口(集群内访问用)
      targetPort: 80 # 容器的端口(和Nginx的containerPort一致)
  type: ClusterIP   # 服务类型,默认是ClusterIP,也可以改成NodePort/LoadBalancer

创建命令:

bash 复制代码
kubectl apply -f wtt-svc.yaml

执行后,效果和 kubectl expose 完全一样,你可以用 kubectl get svc 看到新的Service。

这种方式的优点是:

  • 配置完整,支持所有Service的复杂特性
  • 可以把YAML文件纳入Git版本管理,方便团队协作和回滚
  • 可复用,比如在多个环境部署时,只需要修改少量参数

补充一个NodePort类型的Service YAML例子

如果你想创建一个可以外部访问的NodePort类型Service,YAML方式会更灵活,比如你可以指定固定的NodePort端口(比如30080):

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: wtt-svc
spec:
  selector:
    app: wtt
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080 # 自定义NodePort端口(必须在30000-32767之间)
  type: NodePort

kubectl apply -f wtt-svc.yaml 创建后,就可以通过 节点IP:30080 访问你的Nginx服务了,而用 kubectl expose 只能让K8s随机分配一个NodePort端口,无法自定义。

【4】、灰度发布

下面讲一套从零到一的灰度发布(金丝雀发布)完整流程,包含「准备环境 → 分阶段切流量 → 验证效果 → 一键回滚」,全程用K8s原生资源就能实现,不用额外工具,直接复制命令就能跑👇


一、先搞懂:什么是灰度发布?

灰度发布(也叫金丝雀发布),就是先把一小部分用户流量切到新版本,验证没问题后,再逐步把所有流量切过去

  • 好处:避免全量升级出问题,影响所有用户;一旦新版本有bug,只影响一小部分用户,能快速回滚。
  • 核心原理:利用你之前创建的共用Service(wtt-service ,它的Selector是app:wtt,会同时匹配v1和v2的Pod,流量会自动轮询到所有Pod上。

二、先准备好你的环境(和之前的例子完全对应)

先确认你已经有这些资源,如果没有,先执行下面的命令创建:

1. 基础环境准备(如果还没创建,直接复制)

bash 复制代码
# 1. 创建v1版本(稳定版,Nginx 1.20)
kubectl create deploy wtt-v1 --image=nginx:1.20 --replicas=3
kubectl patch deploy wtt-v1 -p '{"spec":{"template":{"metadata":{"labels":{"app":"wtt","version":"v1"}}}}}'

# 2. 创建v2版本(新版,Nginx 1.25)
kubectl create deploy wtt-v2 --image=nginx:1.25 --replicas=0  # 初始副本数设为0,先不跑Pod
kubectl patch deploy wtt-v2 -p '{"spec":{"template":{"metadata":{"labels":{"app":"wtt","version":"v2"}}}}}'

# 3. 创建共用Service(Selector只匹配app:wtt,不区分版本)
kubectl expose deploy wtt-v1 --port=80 --selector=app=wtt --name=wtt-service

2. 关键一步:修改Nginx首页,方便区分版本

这样你访问的时候,能直接看到是v1还是v2的响应,验证流量分布更直观:

bash 复制代码
# 给v1的所有Pod修改首页,标记为"稳定版 v1"
kubectl exec $(kubectl get pod -l app=wtt,version=v1 -o name | head -n1) -- sh -c "echo '✅ 稳定版 v1 - Nginx 1.20' > /usr/share/nginx/html/index.html"

# 给v2的所有Pod修改首页,标记为"测试版 v2"
kubectl exec $(kubectl get pod -l app=wtt,version=v2 -o name | head -n1) -- sh -c "echo '🚀 测试版 v2 - Nginx 1.25' > /usr/share/nginx/html/index.html"

3. 验证初始状态

bash 复制代码
# 查看Pod状态(初始只有v1的3个Pod在运行,v2是0个)
kubectl get pods -l app=wtt

# 查看Service的ClusterIP,后面要用来测试访问
kubectl get svc wtt-service

初始状态下,Service的所有流量都会打到v1的Pod上,你用curl访问只会看到✅ 稳定版 v1的响应。


三、灰度发布完整流程(分4个阶段)

我们的目标是:从100%流量走v1 → 先切10%到v2 → 切50%到v2 → 最后全量切换到v2,全程可控,随时能回滚。

阶段1:初始状态(100%流量走v1)

  • v1副本数:3个

  • v2副本数:0个

  • 流量分布:100% → v1

  • 验证:

    bash 复制代码
    # 循环访问10次,看所有响应都是v1
    for i in {1..10}; do curl -s http://$(kubectl get svc wtt-service -o jsonpath='{.spec.clusterIP}'); echo ""; done

    输出全是✅ 稳定版 v1,说明初始状态正常。


阶段2:金丝雀发布(10%流量切到v2)

我们要让10%的用户流量打到v2,怎么实现?
核心逻辑:Service的流量是轮询所有匹配的Pod,所以流量比例≈两个版本的Pod数量比例。

  • 我们现在v1有3个Pod,要让v2占10%左右,v2的副本数设为1个(总Pod数4个,v2占25%,如果要更接近10%,可以v1设9个,v2设1个,测试环境用3:1足够)
操作步骤:
  1. 给v2版本扩容1个Pod:

    bash 复制代码
    kubectl scale deploy wtt-v2 --replicas=1
  2. 等待Pod启动完成:

    bash 复制代码
    kubectl get pods -l app=wtt  # 看到4个Pod(3个v1 + 1个v2)都Running
  3. 验证流量分布:

    bash 复制代码
    # 循环访问20次,统计v1和v2的响应次数
    for i in {1..20}; do curl -s http://$(kubectl get svc wtt-service -o jsonpath='{.spec.clusterIP}'); echo ""; done | sort | uniq -c

    你会看到类似这样的输出:

    复制代码
      15 ✅ 稳定版 v1 - Nginx 1.20
       5 🚀 测试版 v2 - Nginx 1.25

    说明大概25%的流量已经打到v2了,和我们的Pod数量比例一致,灰度生效!


阶段3:逐步扩容(50%流量切到v2)

验证v2没有bug后,我们把v2的副本数扩容到和v1一样多,让流量对半分:

  1. 扩容v2到3个Pod:

    bash 复制代码
    kubectl scale deploy wtt-v2 --replicas=3
  2. 等待所有Pod启动:

    bash 复制代码
    kubectl get pods -l app=wtt  # 现在有6个Pod(3个v1 + 3个v2)
  3. 验证流量分布:

    bash 复制代码
    for i in {1..20}; do curl -s http://$(kubectl get svc wtt-service -o jsonpath='{.spec.clusterIP}'); echo ""; done | sort | uniq -c

    输出大概是10次v1 + 10次v2,说明流量已经对半分了,新版本压力上来了,再观察一会儿有没有问题。


阶段4:全量切换(100%流量切到v2)

确认v2完全稳定后,把v1的副本数缩为0,让所有流量都走v2:

  1. 缩容v1版本:

    bash 复制代码
    kubectl scale deploy wtt-v1 --replicas=0
  2. 等待v1的Pod全部销毁:

    bash 复制代码
    kubectl get pods -l app=wtt  # 现在只有3个v2的Pod在运行
  3. 验证全量切换:

    bash 复制代码
    for i in {1..10}; do curl -s http://$(kubectl get svc wtt-service -o jsonpath='{.spec.clusterIP}'); echo ""; done

    所有响应都是🚀 测试版 v2,说明全量切换完成!


四、关键保障:一键回滚操作

如果在任何阶段发现v2有bug,不用慌,直接回滚,分两种情况:

情况1:刚切了少量流量(比如阶段2),发现v2有问题

直接把v2的副本数缩为0,所有流量立刻切回v1:

bash 复制代码
kubectl scale deploy wtt-v2 --replicas=0

等待Pod销毁后,流量就100%回到v1了,用户几乎感知不到问题。

情况2:已经全量切换到v2,发现严重bug

直接把v1的副本数恢复,同时缩容v2:

bash 复制代码
# 恢复v1的副本数,缩容v2
kubectl scale deploy wtt-v1 --replicas=3
kubectl scale deploy wtt-v2 --replicas=0

流量会立刻切回v1,快速止损。


五、进阶:更精准的灰度方式(适合生产环境)

上面的方式是基于「副本数比例」的流量分配,简单但不够精准(比如想严格控制10%流量到v2,需要调整副本数比例,不够灵活)。

如果是生产环境,推荐用下面两种更精准的方式:

方式1:用Nginx Ingress Controller控制流量权重

通过Ingress的nginx.ingress.kubernetes.io/canary注解,直接给v2设置固定的流量权重,比如10%,不用调整副本数:

  1. 创建两个Ingress:一个主Ingress(100%流量),一个金丝雀Ingress(10%流量)

  2. 金丝雀Ingress配置示例:

    yaml 复制代码
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: wtt-canary
      annotations:
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-weight: "10"  # 只分配10%流量到v2
    spec:
      rules:
      - host: wtt.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wtt-service-v2  # 单独指向v2的Service
                port:
                  number: 80

这种方式能实现精准的百分比流量控制,适合生产环境的灰度发布。

方式2:用Istio实现高级流量管理

如果你的集群装了Istio,可以用它的VirtualService实现更复杂的灰度策略:

  • 按用户、地域、设备类型切流量
  • 固定百分比流量
  • 流量镜像(把复制的流量打到v2,不影响用户)
  • 自动熔断、故障注入

六、关键注意事项(新手必看)

  1. 就绪探针(Readiness Probe) :生产环境一定要给Deployment加上就绪探针,确保新版本Pod完全健康后,才会被Service接收流量,避免把流量打到没启动好的Pod上。

    yaml 复制代码
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
  2. 流量比例是近似的:基于副本数的流量分配是近似的,因为Service的轮询是随机的,不是严格的百分比,测试环境用没问题,生产环境推荐用Ingress或Istio的精准控制。

  3. 版本隔离:v1和v2的Deployment是独立的,修改其中一个不会影响另一个,避免了滚动更新时的版本耦合。

  4. 日志和监控:灰度发布时,一定要同时监控v1和v2的日志、指标,比如错误率、响应时间,发现异常立刻回滚。


总结

  1. 基础灰度发布的核心是:共用Service + 双版本Deployment + 副本数控制流量比例,不用额外工具,直接上手。
  2. 分阶段发布,从少量流量开始验证,没问题再逐步扩容,风险可控。
  3. 一键回滚是最后保障,任何阶段出问题都能快速切回稳定版。

如果你想试试Ingress的精准权重控制,我可以给你写一套完整的Ingress YAML配置,直接复制就能用~

【5】、K8s 五大端口 完整详解

先统一标准命名(纠正你口语叫法):

  1. ContainerPort 容器端口(Pod 内部容器监听端口)
  2. TargetPort 目标转发端口
  3. Port = 你说的 ClusterPort(集群内网 Service 端口)
  4. NodePort 节点暴露端口
  5. HostPort 宿主机端口直绑

没有 ClusterPort 官方叫法,行业内都叫 Service Port


1. ContainerPort 容器端口

作用

定义容器内部程序监听的端口,就是程序本身跑起来占用的端口。

  • Nginx 默认监听 80containerPort:80
  • Tomcat 默认 8080

特点

  • 只在Pod 网络空间内生效
  • 同 Pod 多个容器不能同端口,不同 Pod 互不冲突
  • 不对外暴露任何访问入口

场景

所有业务容器必配置,声明程序监听端口。

yaml 复制代码
containers:
- name: nginx
  image: nginx
  ports:
  - containerPort: 80

2. TargetPort 目标端口

作用

Service 转发的终点端口

流量到达 Service 后,转发到 Pod 里哪个 ContainerPort

关系

TargetPort 必须等于 容器的 ContainerPort

示例

容器监听 80,TargetPort 就写 80

yaml 复制代码
ports:
- port: 8080       # 集群内网端口
  targetPort: 80   # 转到容器80

3. Port(ClusterPort 集群内网端口)

作用

ClusterIP 类型 Service 专属端口

集群内部所有 Pod 互相访问时,使用这个端口。

访问路径

集群内 Pod → ClusterIP:Port → 转发到 TargetPort

特点

  • 只能集群内部访问,外网无法直接访问
  • 端口范围:1~65535 随便用,无限制
  • 最安全、最常用

最佳场景

微服务之间互相调用(后端调数据库、网关调业务服务)


4. NodePort 节点端口

作用

把 Service 暴露到所有集群节点物理机端口,外网/本地电脑可直接访问。

访问路径

外网客户端 → 任意节点IP:NodePort → Service → Pod

强制规则

  • 端口固定区间:30000 ~ 32767(不能自定义随意写)
  • 集群所有节点都会监听这个端口

最佳场景

  • 测试环境快速外网调试
  • 小型项目直接对外暴露服务
  • 临时快速上线

缺点

端口号不优雅、不安全、生产不主推

yaml 复制代码
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

5. HostPort 宿主机直连端口

作用

Pod 直接绑定宿主机物理端口

Pod 在哪台节点运行,就占用哪台机器的端口。

访问方式

客户端 → 运行Pod的节点IP:HostPort

致命特点

  1. 一台宿主机同一个端口只能跑一个Pod,端口冲突直接启动失败
  2. Pod 漂移后端口跟着飘,访问地址直接变
  3. 不支持负载均衡

最佳使用场景(极少用)

  • 底层监控组件、Agent 客户端直连
  • 需要独占宿主机端口的老旧程序
    生产业务服务严禁使用

示例

yaml 复制代码
ports:
- containerPort: 80
  hostPort: 80

最终最佳使用场景选型(背这一套足够)

  1. 服务内部互相调用
    用:ClusterIP + Port
  2. 测试环境临时外网访问
    用:NodePort
  3. 企业正式业务对外发布
    用:Ingress 域名路由(首选)
  4. 云服务器公网正式上线
    用:LoadBalancer
  5. 底层监控/运维组件
    酌情少量用 HostPort
  6. 绝对禁止
    业务服务大规模使用 HostPort

极简访问链路汇总

  1. 集群内访问:ClusterIP:Port → TargetPort → ContainerPort
  2. 外网节点访问:节点IP:NodePort → Port → TargetPort → ContainerPort
  3. 宿主机直连:节点IP:HostPort → ContainerPort

【6】、Service 对外提供服务 示例

实战举例:5台集群节点 + NodePort 外网完整访问

环境设定

集群共 5 台 K8s 节点服务器,全部带公网 IP

复制代码
节点1公网IP:111.22.33.1
节点2公网IP:111.22.33.2
节点3公网IP:111.22.33.3
节点4公网IP:111.22.33.4
节点5公网IP:111.22.33.5

前提:

  1. 所有服务器防火墙/云平台安全组,放行 30000~32767 端口
  2. 集群正常运行,已部署 Nginx 业务 Pod

第一步:创建业务 + NodePort 类型 Service

  1. 部署 Nginx 应用
bash 复制代码
kubectl create deploy web --image=nginx --replicas=3
  1. 暴露为 NodePort 服务
bash 复制代码
kubectl expose deploy web --port=80 --target-port=80 --type=NodePort
  1. 查看服务信息
bash 复制代码
kubectl get svc web

输出示例:

复制代码
NAME   TYPE       CLUSTER-IP      PORT(S)        AGE
web    NodePort   10.98.12.34     80:30090/TCP   1m

解析:

  • port=80:集群内部访问端口
  • targetPort=80:容器 Nginx 监听端口
  • 30090自动分配的 NodePort 外网端口

核心重点:NodePort 机制

一旦创建 NodePort=30090

👉 集群里 5 台所有节点,都会统一监听 30090 端口

所有外网可直接访问地址(全部生效)

任意一个公网IP + 30090 都能打开服务

复制代码
http://111.22.33.1:30090
http://111.22.33.2:30090
http://111.22.33.3:30090
http://111.22.33.4:30090
http://111.22.33.5:30090

流量走向(以访问 111.22.33.1:30090 举例)

  1. 外网用户 → 111.22.33.1:30090
  2. 节点1 接收 30090 端口流量
  3. 转发 给集群内 Service 虚拟IP
  4. Service 自动负载均衡,转发到任意一台正常运行的 Nginx Pod
  5. Pod 返回页面数据,原路回传给用户

关键特性

  1. 和 Pod 运行在哪台机器无关
    哪怕 3 个 Nginx Pod 全部跑在节点3上
    你访问 111.22.33.1:30090 照样能通
  2. 5个IP地址完全等价,随便用哪个都行
  3. 其中一台节点宕机,换其他任意IP依旧正常访问

手动固定 NodePort 端口(不用随机)

写 YAML 指定固定端口 30080

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080  # 手动指定
  type: NodePort

应用后,外网统一访问:
所有5个公网IP:30080


使用 Ingress模式 对比

模式1:只使用 NodePort(不需要 Ingress)

  • 优点:最简单,开箱即用
  • 访问形式:公网IP:高位端口
  • 适合:测试、临时项目、小型服务

模式2:生产正式架构(搭配 Ingress,抛弃 NodePort 对外)

  1. 业务 Service 只做默认 ClusterIP不开 NodePort

  2. 单独部署一套 Ingress 组件

  3. Ingress 绑定 5 台节点,统一监听 80 / 443 标准端口

  4. 外网通过 域名 直接访问,不用带端口

    http://www.xxx.com 自动走80端口
    https://www.xxx.com 自动走443端口

  • 优势:域名友好、可配HTTPS、路由分发、统一管控

我把你两个例子完全结合起来 ,一步步讲清楚「两个port=80的Service」在「5台节点集群」里,集群内和外网的具体访问地址,还有背后的流量走向👇


【7】、Service 示例升级

一、先统一环境设定(合并两个场景)

我们把你之前的两个例子整合,先明确所有资源状态:

  1. 5台K8s节点服务器
    公网IP:111.22.33.1 ~ 111.22.33.5
    前提:所有节点防火墙/云安全组,都放行了30000~32767端口段。

  2. 两个业务Deployment

    • web1:运行Nginx,首页返回"我是web1服务",Pod标签app=web1
    • web2:运行Nginx,首页返回"我是web2服务",Pod标签app=web2
  3. 两个Service(对应你给的命令)

    bash 复制代码
    # 第一个Service:对应web1
    kubectl expose deploy web1 --port=80 --target-port=80 --name=svc-1
    # 第二个Service:对应web2(同样用--port=80)
    kubectl expose deploy web2 --port=80 --target-port=80 --name=svc-2

执行完后,用kubectl get svc查看,会得到类似这样的结果:

bash 复制代码
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
svc-1   ClusterIP   10.96.100.10    <none>        80/TCP    1m
svc-2   ClusterIP   10.96.200.20    <none>        80/TCP    1m
  • 两个Service的port(集群端口)都是80,但它们的ClusterIP(虚拟IP)是完全独立的,不会冲突。
  • target-port=80,对应Nginx容器的监听端口。

二、情况1:默认ClusterIP模式 → 仅集群内可访问

这种模式下,Service没有绑定节点公网IP,只能被集群内部的Pod、节点服务器访问,外网用户无法直接访问。

两个Service的「集群内具体访问地址」

1. 用「ClusterIP地址」直接访问

因为两个Service的ClusterIP不同,即使端口都是80,访问地址也完全不冲突:

  • 访问svc-1(web1服务)http://10.96.100.10:80
  • 访问svc-2(web2服务)http://10.96.200.20:80

✅ 验证方式:在任意一台集群节点(比如111.22.33.1)上执行curl:

bash 复制代码
curl http://10.96.100.10:80  # 返回:我是web1服务
curl http://10.96.200.20:80  # 返回:我是web2服务
2. 用「Service名称」访问(推荐,内置DNS解析)

K8s会自动给Service生成DNS域名,格式为服务名.命名空间.svc.cluster.local,默认命名空间是default,所以访问地址是:

  • 访问svc-1http://svc-1.default.svc.cluster.local:80
  • 访问svc-2http://svc-2.default.svc.cluster.local:80

✅ 优势:不用记动态变化的ClusterIP,只要Service名称不变,访问地址永远有效。


三、情况2:改成NodePort模式 → 外网可直接访问(结合5台节点IP)

如果把两个Service改成NodePort类型,就能通过5台节点的公网IP直接访问了。这里有个关键规则:

NodePort是节点物理端口,整个集群内所有Service的nodePort必须唯一,不能重复 。即使port(ClusterIP端口)相同,K8s也会自动分配不同的nodePort

步骤1:修改Service类型为NodePort

我们直接编辑两个Service,改成NodePort类型:

bash 复制代码
kubectl edit svc svc-1
kubectl edit svc svc-2

在YAML里修改spec.typeNodePort,保存后再查看:

bash 复制代码
NAME    TYPE       CLUSTER-IP      PORT(S)        AGE
svc-1   NodePort   10.96.100.10    80:30081/TCP   5m
svc-2   NodePort   10.96.200.20    80:30082/TCP   5m
  • 80:30081/TCP表示:port=80(ClusterIP端口),自动分配的nodePort=30081
  • 80:30082/TCP表示:port=80,自动分配的nodePort=30082
  • 两个Service的nodePort是不同的,所以不会冲突。

步骤2:两个Service的「外网具体访问地址」

NodePort的核心特性是:集群内所有节点,都会监听同一个nodePort端口 。也就是说,3008130082这两个端口,在5台节点上都会被监听。

1. 访问svc-1(web1服务)的所有外网地址

任意一台节点的公网IP + 30081端口,都能访问到web1服务:

复制代码
http://111.22.33.1:30081
http://111.22.33.2:30081
http://111.22.33.3:30081
http://111.22.33.4:30081
http://111.22.33.5:30081

✅ 这些地址完全等价,访问任意一个,都会被K8s转发到svc-1对应的web1 Pod上,不管Pod实际运行在哪台节点上。

2. 访问svc-2(web2服务)的所有外网地址

同理,任意一台节点的公网IP + 30082端口,都能访问到web2服务:

复制代码
http://111.22.33.1:30082
http://111.22.33.2:30082
http://111.22.33.3:30082
http://111.22.33.4:30082
http://111.22.33.5:30082

四、流量走向拆解(以访问http://111.22.33.3:30081为例)

  1. 外网用户发送请求到111.22.33.3:30081(节点3的公网IP + svc-1的nodePort)
  2. 节点3的kube-proxy进程,收到30081端口的请求,知道这个端口对应svc-1服务
  3. 节点3把请求转发给svc-1ClusterIP:8010.96.100.10:80
  4. svc-1根据负载均衡规则,把请求转发到任意一个健康的web1 Pod上(哪怕这个Pod运行在节点1或节点5上,也能正常转发)
  5. web1 Pod的Nginx容器,收到请求,返回"我是web1服务"的响应,原路返回给用户。

五、补充:为什么两个port=80的Service不会冲突?

很多人会疑惑:两个Service的port都是80,为什么访问地址不冲突?

  • 集群内访问 :靠ClusterIP + port区分,10.96.100.10:8010.96.200.20:80是完全不同的地址,就像两台不同的电脑都开了80端口,不会冲突。
  • 外网访问 :靠节点IP + nodePort区分,111.22.33.1:30081111.22.33.1:30082是不同的端口,也不会冲突。

六、手动指定NodePort的方式(可选)

如果你不想用自动分配的端口,想手动指定nodePort,可以写YAML文件创建Service:

yaml 复制代码
# svc-1的YAML,手动指定nodePort=30081
apiVersion: v1
kind: Service
metadata:
  name: svc-1
spec:
  type: NodePort
  selector:
    app: web1
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30081  # 手动指定,必须在30000~32767之间,且集群内唯一

同样,svc-2手动指定nodePort=30082,效果和自动分配完全一致。


核心总结

  1. 两个Service的port=80(ClusterIP端口)可以共存,因为它们的ClusterIP不同,集群内访问靠ClusterIP:port区分。
  2. 改成NodePort后,两个Service会分配不同的nodePort,外网访问靠节点IP:nodePort区分,和port=80无关。
  3. 不管Pod运行在5台节点中的哪一台,通过任意节点的公网IP + 对应的nodePort,都能访问到服务,K8s会自动转发流量。

需要我帮你写一套可以直接复制的完整YAML,包含这两个Deployment和Service吗?

相关推荐
杨云龙UP1 小时前
MySQL主库高峰期备份引发504故障:从库手动切换接管 + 主从恢复同步 + Docker版DB2重启实战_2026-05-17
linux·运维·数据库·mysql·docker·容器·centos
My_Java_Life2 小时前
windows中使用docker部署Milvus和Autt
windows·docker·milvus
jran-2 小时前
Docker 架构&命令
运维·docker·容器
不吃香菜kkk、3 小时前
SonarQube安装配置使用
ci/cd·kubernetes·云计算
小义_3 小时前
【Kubernetes】(二十)ETCD 备份与恢复
容器·kubernetes·etcd
汪汪大队u3 小时前
基于 K8s 的物联网平台运维体系:Ansible+Zabbix 自动化监控与故障自愈(一)—— 环境准备与 Zabbix Server 部署
运维·kubernetes·自动化·ansible·zabbix
learning-striving4 小时前
华为云欧拉操作系统的服务器实例中手工部署 Docker
linux·运维·服务器·docker·容器·华为云
修己xj13 小时前
别再让Docker占满你的硬盘!一篇搞定docker system所有命令
docker
布吉岛的石头15 小时前
Docker Compose编排实战:多容器应用从开发到生产
运维·docker·容器