实验环境:1 Master + 2 Worker + Harbor 私有仓库,Kubernetes 1.28,Flannel 网络
一、理解标签与 Service 中的 Endpoint
1. 建立一个 Service
bash
kubectl create service clusterip --tcp 80:80 testservice --dry-run=client -o yaml >> testservice.yaml
编辑 testservice.yaml,指定 selector 为 app=webserver:
yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: testservice
name: testservice
spec:
ports:
- name: webport
port: 80
protocol: TCP
targetPort: 80
selector:
app: webserver
type: ClusterIP
应用并监控:
bash
kubectl apply -f testservice.yaml
watch -n 1 "kubectl describe svc testservice ; kubectl get pods --show-labels"
此时 Service 已创建,但 Endpoints 为空------因为还没有匹配 selector 的 Pod。

图 1:建立 Service 后 Endpoints 为空
2. 建立 Pod 并测试暴露
标签不匹配时,Pod 不会被暴露:
bash
kubectl run webserver --image myapp:v1 --dry-run=client -o yaml > webserver.yml
Pod 标签为 run=webserver,与 Service 的 app=webserver 不匹配,Endpoints 仍为空。
修改标签后,Endpoint 自动绑定:
bash
kubectl label pods webserver app=webserver
curl 10.100.62.227
输出 Hello MyApp | Version: v1,说明流量已转发到 Pod。此时 Endpoints 显示为 10.244.2.7:80。
二、利用 Service 暴露控制器
1. 生成 Service + Deployment 组合 YAML
bash
kubectl create service clusterip --tcp 80:80 webservice --dry-run=client -o yaml >> webservice.yaml
kubectl create deployment webcluster --image myapp:v1 --dry-run=client -o yaml >> webservice.yaml
编辑组合文件,确保 Deployment 的 matchLabels 与 Service 的 selector 一致:
yaml
---
apiVersion: v1
kind: Service
metadata:
labels:
app: webservice
name: webservice
spec:
ports:
- name: webport
port: 80
protocol: TCP
targetPort: 80
selector:
app: webcluster
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webcluster
name: webcluster
spec:
replicas: 2
selector:
matchLabels:
app: webcluster
template:
metadata:
labels:
app: webcluster
spec:
containers:
- image: myapp:v1
name: myapp
2. 应用与验证负载均衡
bash
kubectl apply -f webservice.yaml
curl 10.98.31.55/hostname.html
多次请求返回不同 Pod 名称,证明 Service 已自动实现后端轮询调度。

图 2:Service 暴露 Deployment 并实现轮询调度
三、Service 的 IPVS 模式
1. 安装 ipvsadm
bash
for name in 100 10 20 200; do
ssh -l root 172.25.254.$name "dnf install ipvsadm -y"
done
2. 切换 kube-proxy 为 IPVS 模式
bash
kubectl -n kube-system edit cm kube-proxy
# 修改 mode: "ipvs"
kubectl -n kube-system delete pods -l k8s-app=kube-proxy
3. 验证 IPVS 规则
bash
ipvsadm -Ln
输出中可见 10.98.31.55:80 对应两条后端 Pod 记录,调度算法为 rr(轮询)。

图 3:IPVS 模式下的 Service 负载均衡规则
四、Service 的 ClusterIP 类型
1. 标准 ClusterIP
默认类型,分配集群内部 VIP,仅集群内可访问:
bash
kubectl describe svc webservice
# IP: 10.109.112.7
2. 集群内部 DNS 解析
bash
dig webservice.default.svc.cluster.local @10.96.0.10
返回 A 记录指向 ClusterIP。集群内部通信应使用 Service 名称而非 IP,因为 IP 可能变化。

图 4:ClusterIP Service 通过 CoreDNS 解析到 VIP
3. Headless 模式
设置 clusterIP: None,Service 不再分配 VIP,DNS 直接返回所有后端 Pod IP:
yaml
spec:
clusterIP: None
bash
dig webservice.default.svc.cluster.local @10.96.0.10
返回两个 A 记录,分别为 10.244.5.99 和 10.244.2.15。适用于需要直接访问 Pod 的场景(如 StatefulSet)。

图 5:Headless Service 直接返回后端 Pod IP
五、Service 的 NodePort 类型
1. 生成 NodePort
yaml
spec:
ports:
- name: webport
port: 80
protocol: TCP
targetPort: 80
nodePort: 30951
selector:
app: webcluster
type: NodePort
不指定 nodePort 时,系统从 30000-32767 随机分配。
2. 查看 IPVS 策略
bash
ipvsadm -Ln
可见 172.25.254.100:30951 和 172.17.0.1:30951 均映射到后端 Pod。
3. 访问验证
bash
curl 172.25.254.100:30951/hostname.html

图 6:NodePort 通过节点端口暴露服务
4. 端口范围破限
如需使用 32767 以上端口,修改 kube-apiserver:
bash
vim /etc/kubernetes/manifests/kube-apiserver.yaml
# 添加 --service-node-port-range=30000-50000
重建 kube-apiserver Pod 后,即可分配 nodePort: 44444。

图 7:修改 apiserver 端口范围后分配 44444 端口
六、LoadBalancer 类型
1. 建立 Service
yaml
type: LoadBalancer
bash
kubectl get svc
# webservice LoadBalancer 10.111.5.103 <pending> 80:31426/TCP
EXTERNAL-IP 为 <pending>,需要外部负载均衡器支持。裸金属集群使用 MetalLB 实现。
2. 部署 MetalLB
上传镜像到 Harbor:
bash
docker load -i metallb-v0.16.1.tar
docker tag quay.io/metallb/controller:v0.16.1 reg.timinglee.org/metallb/controller:v0.16.1
docker tag quay.io/metallb/speaker:v0.16.1 reg.timinglee.org/metallb/speaker:v0.16.1
docker push reg.timinglee.org/metallb/controller:v0.16.1
docker push reg.timinglee.org/metallb/speaker:v0.16.1
配置 kube-proxy:
bash
kubectl edit configmap -n kube-system kube-proxy
# mode: "ipvs"
# ipvs:
# strictARP: true
kubectl -n kube-system delete pods -l k8s-app=kube-proxy
配置 MetalLB IP 池:
yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 172.25.254.50-172.25.254.99
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
3. 验证
bash
kubectl get svc
# webservice LoadBalancer 10.111.5.103 172.25.254.50 80:31426/TCP
EXTERNAL-IP 已分配为 172.25.254.50。

图 8:MetalLB 为 LoadBalancer Service 分配外部 IP
七、ExternalName
将 Service 映射到外部域名:
yaml
apiVersion: v1
kind: Service
metadata:
name: extrnalname
spec:
type: ExternalName
externalName: www.timinglee.org
bash
kubectl get svc extrnalname
# extrnalname ExternalName <none> www.timinglee.org <none>
无 ClusterIP,DNS 查询直接返回 CNAME 记录。

图 9:ExternalName Service 映射到外部域名
八、Ingress 七层代理
1. 部署 Ingress-Nginx
下载官方部署文件:
bash
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/baremetal/deploy.yaml
准备镜像并推送到 Harbor:
bash
docker load -i ingress-v1.15.1.tar
docker tag registry.k8s.io/ingress-nginx/controller:v1.15.1 reg.timinglee.org/ingress-nginx/controller:v1.15.1
docker tag registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9 reg.timinglee.org/ingress-nginx/kube-webhook-certgen:v1.6.9
docker push reg.timinglee.org/ingress-nginx/controller:v1.15.1
docker push reg.timinglee.org/ingress-nginx/kube-webhook-certgen:v1.6.9
修改 deploy.yaml:
- 替换镜像地址为 Harbor 私有仓库
- 将 Service 类型改为
LoadBalancer(配合 MetalLB)
bash
kubectl apply -f deploy.yaml
kubectl -n ingress-nginx get svc
ingress-nginx-controller 获得 EXTERNAL-IP 172.25.254.50。

图 10:Ingress-Nginx Controller 部署完成并获得外部 IP
2. 建立 Ingress 代理的微服务
部署两个版本的业务:
bash
# myapp:v1
kubectl create deployment myappv1 --image myapp:v1 --replicas 2 --dry-run=client -o yaml > myappv1.yaml
kubectl create service clusterip servicev1 --tcp 80:80 --dry-run=client -o yaml >> myappv1.yaml
# myapp:v2
kubectl create deployment myappv2 --image myapp:v2 --replicas 2 --dry-run=client -o yaml > myappv2.yaml
kubectl create service clusterip servicev2 --tcp 80:80 --dry-run=client -o yaml >> myappv2.yaml
验证两个 Service 可正常访问:
bash
curl 10.97.35.20 # Hello MyApp | Version: v1
curl 10.100.116.247 # Hello MyApp | Version: v2

图 11:部署 v1/v2 两个版本的微服务
3. 基于路径的访问
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress1
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: www.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /v1
pathType: Prefix
- backend:
service:
name: servicev2
port:
number: 80
path: /v2
pathType: Prefix
客户端配置 /etc/hosts 后验证:
bash
curl www.timinglee.org/v1 # v1
curl www.timinglee.org/v2 # v2
4. 基于域名的访问
yaml
rules:
- host: myapp1.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /
pathType: Prefix
- host: myapp2.timinglee.org
http:
paths:
- backend:
service:
name: servicev2
port:
number: 80
path: /
pathType: Prefix
bash
curl myapp1.timinglee.org # v1
curl myapp2.timinglee.org # v2

图 12:不同域名路由到不同后端服务
5. 动静分离
利用正则表达式实现路径重写:
yaml
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /index.html
spec:
rules:
- host: www.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /php
pathType: ImplementationSpecific
- backend:
service:
name: servicev2
port:
number: 80
path: /static
pathType: ImplementationSpecific
bash
curl www.timinglee.org/php/ # v1
curl www.timinglee.org/static/ # v2

图 13:基于正则的路径重写实现动静分离
6. TLS 加密访问
生成自签证书:
bash
openssl req -newkey rsa:2048 -nodes -keyout tls.key -x509 -days 365 \
-subj "/CN=nginxsvc/O=nginxsvc" -out tls.crt
kubectl create secret tls web-tls-secret --key tls.key --cert tls.crt
配置 Ingress:
yaml
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- myapp-tls.timinglee.org
secretName: web-tls-secret
rules:
- host: myapp-tls.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /
pathType: Prefix
bash
curl -k https://myapp-tls.timinglee.org

图 14:Ingress 配置 TLS 加密访问
7. Auth 认证
生成认证文件:
bash
dnf install httpd-tools -y
htpasswd -cm auth admin
kubectl create secret generic web-auth --from-file auth
配置 Ingress:
yaml
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: web-auth
nginx.ingress.kubernetes.io/auth-realm: "Please input username and password"
验证:
bash
curl www.timinglee.org/v1 # 401 Authorization Required
curl www.timinglee.org/v1 -u admin:lee # v1

图 15:Ingress Basic Auth 认证拦截与放行
8. 网页重写
默认发布文件:
yaml
annotations:
nginx.ingress.kubernetes.io/app-root: /hostname.html
访问根路径自动 302 跳转到 /hostname.html。
正则表达式重定向:
yaml
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
- host: www.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /
pathType: Prefix
- backend:
service:
name: servicev2
port:
number: 80
path: /lee(/*.*/*|$)(.*)
pathType: ImplementationSpecific
bash
curl -L www.timinglee.org # v1
curl -L www.timinglee.org/lee # v2
curl -L www.timinglee.org/lee/a # v2

图 16:Ingress 路径重写与 302 跳转
九、金丝雀发布(Canary)
1. 基于 Header 的灰度发布
yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress1
spec:
ingressClassName: nginx
rules:
- host: www.timinglee.org
http:
paths:
- backend:
service:
name: servicev1
port:
number: 80
path: /
pathType: Prefix
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress2
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "version"
nginx.ingress.kubernetes.io/canary-by-header-value: "2"
spec:
ingressClassName: nginx
rules:
- host: www.timinglee.org
http:
paths:
- backend:
service:
name: servicev2
port:
number: 80
path: /
pathType: Prefix
bash
curl www.timinglee.org # v1
curl -H "version:2" www.timinglee.org # v2
2. 基于权重的灰度发布
yaml
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
nginx.ingress.kubernetes.io/canary-weight-total: "100"
100 次请求统计:v1 约 95 次,v2 约 5 次,符合 10% 权重分配。

图 17:基于权重的金丝雀发布流量分配
总结
| 类型 | 适用场景 | 特点 |
|---|---|---|
| ClusterIP | 集群内部通信 | 默认类型,分配 VIP |
| Headless | 直接访问 Pod | DNS 返回 Pod IP 列表 |
| NodePort | 开发测试/简单暴露 | 每个节点开放端口,范围 30000-32767 |
| LoadBalancer | 生产环境公网暴露 | 需云厂商 LB 或 MetalLB |
| ExternalName | 外部服务映射 | DNS CNAME 转发 |
| Ingress | 七层路由/域名/HTTPS | 基于 Nginx,支持路径、域名、TLS、认证、灰度 |
Service 是 Kubernetes 网络的核心抽象,理解其底层实现(iptables/IPVS)和各种类型的工作机制,是排查集群网络问题和设计高可用架构的基础。