【1】、标签选择器 Selector
它是 K8s 最核心的关联逻辑 ,Deployment 管 Pod、Service 转发流量,全靠它!
一、核心定义(大白话)
Selector = 标签选择器
就是一个匹配规则 ,作用是:
通过「标签(Labels)」,精准找到你要关联的 Pod
二、先搞懂两个基础概念
- 标签(Labels) :给 Pod 贴的「身份标识」,格式
key=value
比如你的 Pod 贴了:app: wtt - 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,访问直接报错
四、关键铁律(必背)
-
Selector 和 Pod 标签必须完全一致
不一致 → 彻底失联:
- Deployment 找不到 Pod → 无法自愈
- Service 找不到 Pod → 无法访问
-
一个 Selector 可以匹配多个 Pod
所有带相同标签的 Pod,都会被选中
→ 所以一个 Service 可以代理 N 个 Pod、N 个 Deployment
-
kubectl expose自动继承 Selector你执行
kubectl expose deploy wtt→ Service 会自动复制 Deployment 的 Selector
→ 不用手动写,这就是你之前命令里看不到
wtt的原因!
五、标签原理
问题1:一个 Deployment 可以有多少个标签?
结论:理论无上限,实际够用就好
-
技术限制 :K8s 对单个资源的标签数量没有硬上限,但受两个规则约束:
- 标签键(含可选前缀)最长253字符,值最长63字符
- 单个资源的元数据总大小不能超过256KB(标签太多会占空间)
-
实际使用 :一般10个以内足够,比如:
yamlmetadata: 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→ ❌ 不匹配 - 两个都没有 → ❌ 不匹配
-
matchLabels
只能写键值对等值匹配,全部是与关系,最简单最常用。
-
matchExpressions
可以实现:或、不等于、包含、排除 等复杂逻辑
想要「或关系」必须用它。
不管是 Deployment 选 Pod ,还是 Service 选 Pod
只要写在 matchLabels 里,统一都是:同时满足 = 与。
一句话总结:
Deployment管理的是「所有满足Selector规则的Pod」,这些Pod可以有不同的额外标签,但必须都包含Selector里的标签,本质上是"一类Pod",不是"多种标签的Pod"。
问题3:一个 Pod 可以有多少个标签?
结论:和Deployment一样,理论无上限,实际按需添加
-
技术限制和Deployment完全相同(键值长度、元数据大小)
-
实际使用中,一般5-10个标签足够,用来被不同的控制器/Service筛选:
yamlmetadata: 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
-
创建两个不同版本的Deployment,Pod都带
app: wtt标签:- DeploymentA(wtt-v1):Pod标签
app: wtt, version: v1 - DeploymentB(wtt-v2):Pod标签
app: wtt, version: v2
- DeploymentA(wtt-v1):Pod标签
-
创建一个名字为my-service 的 Service, 通过 --selector=app=wtt 将 Selector设为
app: wtt:bashkubectl expose deploy wtt-v1 --name=my-service --port=80 --selector=app=wtt -
效果:这个Service会同时匹配两个Deployment的所有Pod,流量会被负载均衡到v1和v2的Pod上,相当于同时服务两个Deployment!
【2】、一 Service 管 二 Deployment 的经典YAML示例
一、完整的YAML文件
我们一共需要3个文件:
wtt-v1.yaml:v1版本的Deploymentwtt-v2.yaml:v2版本的Deploymentwtt-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?)
- Deployment的Selector :
app:wtt AND version:v1- 每个Deployment只管理自己版本的Pod,确保不会互相干扰。
- Service的Selector :
app:wtt- 只筛选带
app:wtt标签的Pod,不关心version,所以v1和v2的Pod都会被选中。
- 只筛选带
- 效果 :流量会被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=80,targetPort 默认为 80 |
显式指定 port=80,target-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足够)
操作步骤:
-
给v2版本扩容1个Pod:
bashkubectl scale deploy wtt-v2 --replicas=1 -
等待Pod启动完成:
bashkubectl get pods -l app=wtt # 看到4个Pod(3个v1 + 1个v2)都Running -
验证流量分布:
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一样多,让流量对半分:
-
扩容v2到3个Pod:
bashkubectl scale deploy wtt-v2 --replicas=3 -
等待所有Pod启动:
bashkubectl get pods -l app=wtt # 现在有6个Pod(3个v1 + 3个v2) -
验证流量分布:
bashfor 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:
-
缩容v1版本:
bashkubectl scale deploy wtt-v1 --replicas=0 -
等待v1的Pod全部销毁:
bashkubectl get pods -l app=wtt # 现在只有3个v2的Pod在运行 -
验证全量切换:
bashfor 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%,不用调整副本数:
-
创建两个Ingress:一个主Ingress(100%流量),一个金丝雀Ingress(10%流量)
-
金丝雀Ingress配置示例:
yamlapiVersion: 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,不影响用户)
- 自动熔断、故障注入
六、关键注意事项(新手必看)
-
就绪探针(Readiness Probe) :生产环境一定要给Deployment加上就绪探针,确保新版本Pod完全健康后,才会被Service接收流量,避免把流量打到没启动好的Pod上。
yamlreadinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 -
流量比例是近似的:基于副本数的流量分配是近似的,因为Service的轮询是随机的,不是严格的百分比,测试环境用没问题,生产环境推荐用Ingress或Istio的精准控制。
-
版本隔离:v1和v2的Deployment是独立的,修改其中一个不会影响另一个,避免了滚动更新时的版本耦合。
-
日志和监控:灰度发布时,一定要同时监控v1和v2的日志、指标,比如错误率、响应时间,发现异常立刻回滚。
总结
- 基础灰度发布的核心是:共用Service + 双版本Deployment + 副本数控制流量比例,不用额外工具,直接上手。
- 分阶段发布,从少量流量开始验证,没问题再逐步扩容,风险可控。
- 一键回滚是最后保障,任何阶段出问题都能快速切回稳定版。
如果你想试试Ingress的精准权重控制,我可以给你写一套完整的Ingress YAML配置,直接复制就能用~
【5】、K8s 五大端口 完整详解
先统一标准命名(纠正你口语叫法):
- ContainerPort 容器端口(Pod 内部容器监听端口)
- TargetPort 目标转发端口
- Port = 你说的 ClusterPort(集群内网 Service 端口)
- NodePort 节点暴露端口
- HostPort 宿主机端口直绑
没有 ClusterPort 官方叫法,行业内都叫 Service Port。
1. ContainerPort 容器端口
作用
定义容器内部程序监听的端口,就是程序本身跑起来占用的端口。
- Nginx 默认监听
80→containerPort: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
致命特点
- 一台宿主机同一个端口只能跑一个Pod,端口冲突直接启动失败
- Pod 漂移后端口跟着飘,访问地址直接变
- 不支持负载均衡
最佳使用场景(极少用)
- 底层监控组件、Agent 客户端直连
- 需要独占宿主机端口的老旧程序
生产业务服务严禁使用
示例
yaml
ports:
- containerPort: 80
hostPort: 80
最终最佳使用场景选型(背这一套足够)
- 服务内部互相调用
用:ClusterIP + Port - 测试环境临时外网访问
用:NodePort - 企业正式业务对外发布
用:Ingress 域名路由(首选) - 云服务器公网正式上线
用:LoadBalancer - 底层监控/运维组件
酌情少量用HostPort - 绝对禁止
业务服务大规模使用 HostPort
极简访问链路汇总
- 集群内访问:
ClusterIP:Port→ TargetPort → ContainerPort - 外网节点访问:
节点IP:NodePort→ Port → TargetPort → ContainerPort - 宿主机直连:
节点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
前提:
- 所有服务器防火墙/云平台安全组,放行 30000~32767 端口
- 集群正常运行,已部署 Nginx 业务 Pod
第一步:创建业务 + NodePort 类型 Service
- 部署 Nginx 应用
bash
kubectl create deploy web --image=nginx --replicas=3
- 暴露为
NodePort服务
bash
kubectl expose deploy web --port=80 --target-port=80 --type=NodePort
- 查看服务信息
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 举例)
- 外网用户 →
111.22.33.1:30090 - 节点1 接收 30090 端口流量
- 转发 给集群内
Service 虚拟IP - Service 自动负载均衡,转发到任意一台正常运行的 Nginx Pod
- Pod 返回页面数据,原路回传给用户
关键特性
- 和 Pod 运行在哪台机器无关
哪怕 3 个 Nginx Pod 全部跑在节点3上
你访问111.22.33.1:30090照样能通 - 5个IP地址完全等价,随便用哪个都行
- 其中一台节点宕机,换其他任意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 对外)
-
业务 Service 只做默认
ClusterIP,不开 NodePort -
单独部署一套 Ingress 组件
-
Ingress 绑定 5 台节点,统一监听 80 / 443 标准端口
-
外网通过 域名 直接访问,不用带端口
http://www.xxx.com 自动走80端口
https://www.xxx.com 自动走443端口
- 优势:域名友好、可配HTTPS、路由分发、统一管控
我把你两个例子完全结合起来 ,一步步讲清楚「两个port=80的Service」在「5台节点集群」里,集群内和外网的具体访问地址,还有背后的流量走向👇
【7】、Service 示例升级
一、先统一环境设定(合并两个场景)
我们把你之前的两个例子整合,先明确所有资源状态:
-
5台K8s节点服务器
公网IP:111.22.33.1~111.22.33.5
前提:所有节点防火墙/云安全组,都放行了30000~32767端口段。 -
两个业务Deployment
web1:运行Nginx,首页返回"我是web1服务",Pod标签app=web1web2:运行Nginx,首页返回"我是web2服务",Pod标签app=web2
-
两个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-1:http://svc-1.default.svc.cluster.local:80 - 访问
svc-2:http://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.type为NodePort,保存后再查看:
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=3008180:30082/TCP表示:port=80,自动分配的nodePort=30082- 两个Service的
nodePort是不同的,所以不会冲突。
步骤2:两个Service的「外网具体访问地址」
NodePort的核心特性是:集群内所有节点,都会监听同一个nodePort端口 。也就是说,30081和30082这两个端口,在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为例)
- 外网用户发送请求到
111.22.33.3:30081(节点3的公网IP + svc-1的nodePort) - 节点3的
kube-proxy进程,收到30081端口的请求,知道这个端口对应svc-1服务 - 节点3把请求转发给
svc-1的ClusterIP:80(10.96.100.10:80) svc-1根据负载均衡规则,把请求转发到任意一个健康的web1Pod上(哪怕这个Pod运行在节点1或节点5上,也能正常转发)web1Pod的Nginx容器,收到请求,返回"我是web1服务"的响应,原路返回给用户。
五、补充:为什么两个port=80的Service不会冲突?
很多人会疑惑:两个Service的port都是80,为什么访问地址不冲突?
- 集群内访问 :靠
ClusterIP + port区分,10.96.100.10:80和10.96.200.20:80是完全不同的地址,就像两台不同的电脑都开了80端口,不会冲突。 - 外网访问 :靠
节点IP + nodePort区分,111.22.33.1:30081和111.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,效果和自动分配完全一致。
核心总结
- 两个Service的
port=80(ClusterIP端口)可以共存,因为它们的ClusterIP不同,集群内访问靠ClusterIP:port区分。 - 改成
NodePort后,两个Service会分配不同的nodePort,外网访问靠节点IP:nodePort区分,和port=80无关。 - 不管Pod运行在5台节点中的哪一台,通过任意节点的公网IP + 对应的
nodePort,都能访问到服务,K8s会自动转发流量。
需要我帮你写一套可以直接复制的完整YAML,包含这两个Deployment和Service吗?