Kubernetes Service 进阶与 Ingress 实战笔记(MetalLB · ExternalName · IPVS · Ingress)

本手册由 master30_2026-08-11_10_01_10.log(8/11 全天实验)整理而成,在 kubeadm v1.30.2 集群(master30 + worker31/32,运行第 6 天)上完成。
一条主线:先把 Service 的流量链路(iptables)彻底看懂 → 用 MetalLB 让 Service 拥有外部 IP → ExternalName 映射集群外域名 → 验证轮询 / 会话保持 / 版本权重 → 把 kube-proxy 从 iptables 切成 IPVS → 最后用 Ingress-nginx 统一对外暴露多域名、多路径、HTTPS。命令提示符保留真实时间戳,报错与修复全部来自实际操作。

📑 目录

#章节核心内容
整体框架与实验总览实验流程图、节点环境、主题速览
Service 流量链路:iptables 分析KUBE-SVC / KUBE-SEP / KUBE-MARK-MASQ、随机概率
MetalLB:LoadBalancer 落地部署控制器、IPAddressPool、L2Advertisement、外部 IP
ExternalName:集群外域名映射CNAME、dig 验证、busybox 容器内访问
Service 轮询与会话保持20 次压测、sessionAffinity: ClientIP
ConfigMap 挂载与版本权重分流副本 8:2、流量占比验证
kube-proxy 切换 IPVS 模式mode 配置、rollout restart、ipvsadm 查看
IPVS 调度算法探究rr 轮询 20/20/20、修改 scheduler 的正确姿势
Ingress-nginx 控制器部署镜像替换、Job immutable 报错与修复
Ingress 多域名路由一个 Ingress 多个 host
十一Ingress 多路径路由//games 分发、/etc/hosts 优先级
十二Ingress 路径重写rewrite-target 与 use-regex
十三Ingress TLS 与强制 HTTPS自签证书、tls secret、308 跳转
十四环境清理与恢复删除 ingress-nginx / ingress 命名空间
十五常见错误与排查(重点)本次实验全部报错:原因 + 修复
十六命令速查表与小结Service / IPVS / Ingress 常用命令

一、整体框架与实验总览

1.1 实验流程图

集群运行第 6 天
master30 + worker31/32

Service 流量链路
iptables 分析

MetalLB
LoadBalancer 外部 IP

ExternalName
集群外域名映射

负载均衡与权重
轮询 / 会话保持 / ConfigMap

kube-proxy
iptables → IPVS

Ingress-nginx
多域名 / 多路径 / 重写 / TLS

错误复盘 + 命令速查

1.2 节点环境

节点主机名IP角色
master30master30.ningcode.cn10.1.8.30控制平面
worker31worker31.ningcode.cn10.1.8.31工作节点
worker32worker32.ningcode.cn10.1.8.32工作节点
  • Pod 网段 10.224.0.0/16;Service 网段 10.96.0.0/12
  • 私有镜像仓库 hub.laoma.cloud(拉取加速用);实验软件包来自 192.168.46.200/class/course-materials/softwares/stage03/
  • 本次会话 kubectl 当前命名空间沿用上次实验的 serviceskubectl config set-context 会持久化),Ingress 实验单独使用 ingress 命名空间

1.3 主题速览

主题核心知识点日志时间
Service 链路KUBE-SVC / KUBE-SEP / MARK-MASQ、random 概率 0.510:04–10:19
MetalLBmetallb-native、IPAddressPool、L2Advertisement11:04–11:15
ExternalNameCNAME 映射、dig 验证、容器内 wget11:16–12:29
Service 负载随机概率压测、会话保持 ClientIP12:30–12:33
ConfigMap版本权重 8:2、流量占比验证13:45–13:58
IPVSkube-proxy mode=ipvs、ipvsadm 验证14:17–15:09
Ingress控制器部署、多域名 / 多路径 / 重写 / TLS15:09–16:47

↑ 回到目录


二、Service 流量链路:iptables 分析

2.1 为什么要看 iptables

Service 只是一个"虚拟 IP + 规则",真正干活的是节点上的 kube-proxy。iptables 模式下,kube-proxy 会生成三类链:

  • KUBE-SERVICES:流量总入口,命中 ClusterIP 的包进入对应的 KUBE-SVC-xxx
  • KUBE-SVC-xxx:负载均衡链,把包随机/按概率转发到某个 KUBE-SEP-xxx(后端 Pod)
  • KUBE-SEP-xxx:Endpoint(Pod)链,做 DNAT 到真实 Pod IP
  • KUBE-MARK-MASQ:给来自集群外部的包打上 0x4000 标记,后续统一做 SNAT(源地址转换)

2.2 查看 MASQ 标记链

root@master30 ~ 10:04:40# iptables-save |grep KUBE-MARK-MASQ
:KUBE-MARK-MASQ - [0:0]
-A KUBE-EXT-7D76YWGERGEPC4GC -m comment --comment "masquerade traffic for services/web external destinations" -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-SEP-HUGMKHV43KCB5G6S -s 10.224.195.130/32 -m comment --comment "services/web" -j KUBE-MARK-MASQ
-A KUBE-SEP-N6LLPMTIQEKB7LJM -s 10.224.83.129/32 -m comment --comment "services/web" -j KUBE-MARK-MASQ
......(kube-dns、kubernetes 等其他 Service 的链省略)
-A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.107.179.7/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
  • --set-xmark 0x4000/0x4000:打标记,告诉 conntrack/iptables 这个包需要被 MASQUERADE
  • ! -s 10.224.0.0/16源地址不是 Pod 网段(即来自集群外部)的包才需要打标记
  • 当时 web Service 端口还是 8080(后续应用 LoadBalancer 清单后才改成 80)

2.3 查看负载均衡链(随机概率)

root@master30 ~ 10:18:38# iptables-save |grep KUBE-SVC-7D76YWGERGEPC4GC
:KUBE-SVC-7D76YWGERGEPC4GC - [0:0]
-A KUBE-EXT-7D76YWGERGEPC4GC -j KUBE-SVC-7D76YWGERGEPC4GC
-A KUBE-SERVICES -d 10.107.179.7/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 8080 -j KUBE-SVC-7D76YWGERGEPC4GC
-A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.107.179.7/32 -p tcp -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.195.130:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-HUGMKHV43KCB5G6S
-A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.83.129:80" -j KUBE-SEP-N6LLPMTIQEKB7LJM
  • 后端 1(10.224.195.130)带 --mode random --probability 0.50000000000只有 50% 的概率被选中
  • 后端 2 没有概率参数:剩下的 50% 走它(隐含默认规则)
  • 实验者批注(日志原文):
root@master30 ~ 10:19:02# #--probability 0.50000000000采用随机模块,所以不能完全轮询

2.4 要点

  • iptables 模式本质是随机概率分流,两个后端时大致 50/50,但不是严格轮询,压测小样本时会看到 11:9、10:10 之类的偏差
  • 链路小结:KUBE-SERVICES → KUBE-SVC(随机选后端)→ KUBE-SEP(DNAT 到 Pod)→ KUBE-MARK-MASQ(外部流量打标记做 SNAT)
  • 这个结论为后面"为什么换 IPVS(严格 rr 轮询)"埋下伏笔

↑ 回到目录


三、MetalLB:LoadBalancer 落地

3.1 概念

云平台上有云 LB 插件给 LoadBalancer 类型的 Service 分配外部 IP;裸金属集群没有,需要 MetalLB。本次部署 v0.14.8,使用 L2 模式:speaker 以 ARP 应答的方式把池子里分配的 VIP 宣告到二层网络,流量先到节点再 DNAT 进集群。

3.2 下载、解压、改镜像

root@master30 ~ 11:04:18# wget http://192.168.46.200/class/course-materials/softwares/stage03/metallb-0.14.8.tar.gz
--2026-08-11 11:04:23--  http://192.168.46.200/class/course-materials/softwares/stage03/metallb-0.14.8.tar.gz
HTTP request sent, awaiting response... 200 OK
metallb-0.14.8.tar.gz          100%[==================================================>]  14.15M  11.2MB/s    in 1.3s

root@master30 ~ 11:04:24# tar -xf metallb-0.14.8.tar.gz

# 查看清单里的镜像(注意:目录名后面要带 .yaml)
root@master30 ~ 11:05:27# grep image metallb-0.14.8/config/manifests/metallb-native.yaml
        image: quay.io/metallb/controller:v0.14.8
        image: quay.io/metallb/speaker:v0.14.8

# 替换成私有仓库,避免走外网拉镜像太慢
root@master30 ~ 11:05:33# sed -i 's/quay.io/hub.laoma.cloud/g' metallb-0.14.8/config/manifests/metallb-native.yaml
root@master30 ~ 11:08:03# #修改镜像,走外网太慢了

3.3 部署控制器与 speaker

root@master30 ~ 11:08:30# kubectl apply -f metallb-0.14.8/config/manifests/metallb-native.yaml
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
......
deployment.apps/controller created
daemonset.apps/speaker created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created

root@master30 ~ 11:09:08# kubectl get all -n metallb-system
NAME                             READY   STATUS    RESTARTS   AGE
pod/controller-d6499775f-68xb9   1/1     Running   0          29s
pod/speaker-8qhf8                0/1     Running   0          29s
pod/speaker-h2m6g                0/1     Running   0          29s
pod/speaker-r5gpn                0/1     Running   0          29s
......
daemonset.apps/speaker   3         3         0       3            0           kubernetes.io/os=linux   29s
deployment.apps/controller   1/1     1            1           29s

speaker 是 DaemonSet,每个节点一个;刚创建时 0/1,等镜像拉取完成后变 Running。

3.4 配置地址池(IPAddressPool)与 L2 通告

root@master30 ~ 11:09:23# cat << 'EOF' > ippool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.1.8.40-10.1.8.80
EOF

root@master30 ~ 11:10:25# kubectl apply -f ippool.yaml
ipaddresspool.metallb.io/first-pool created

root@master30 ~ 11:10:30# cat << 'EOF' > L2.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
EOF

root@master30 ~ 11:10:36# kubectl apply -f L2.yaml
l2advertisement.metallb.io/example created
  • IPAddressPool:可分配的 VIP 范围(本次为 10.1.8.40 ~ 10.1.8.80)
  • L2Advertisement:声明用 L2(ARP)方式通告这些 IP

3.5 把 Service 改成 LoadBalancer

先用 kubectl expose --dry-run=client 生成清单再应用:

root@master30 ~ 11:10:40# kubectl expose deployment web --type LoadBalancer --port=80 --target-port=80 -o yaml --dry-run=client > service-LoadBalancer.yaml

root@master30 ~ 11:15:19# cat service-LoadBalancer.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: web
  type: LoadBalancer
status:
  loadBalancer: {}

root@master30 ~ 11:15:25# kubectl apply -f service-LoadBalancer.yaml
service/web configured

root@master30 ~ 11:15:40# kubectl get service
NAME   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
web    LoadBalancer   10.107.179.7   10.1.8.40     80:30639/TCP   73m

root@master30 ~ 11:15:46# curl -s http://10.1.8.40:80
<html><body><h1>It works!</h1></body></html>
  • EXTERNAL-IP 变为 10.1.8.40,来自地址池的第一个可用 IP,直接 curl 即可访问
  • 该 IP 后来被 web Service 释放,Ingress-nginx 控制器的 LoadBalancer 又复用了它(见第九章)

3.6 要点

  • 裸金属上 LoadBalancer ≠ 云负载均衡,只是 MetalLB 分配的一个 VIP + 二层 ARP 宣告
  • 换镜像用 sed 批量替换,替换后建议 grep image 确认
  • 删除 LoadBalancer Service 会释放 VIP,池子里可重复使用

↑ 回到目录


四、ExternalName:集群外域名映射

4.1 概念

ExternalName 类型的 Service 不建 ClusterIP,也不生成 iptables/ipvs 规则、没有 Endpoints。它只在集群 DNS(CoreDNS)里注册一条 CNAME 记录:集群内访问 my-service.services.svc.cluster.local 时,DNS 会返回配置的 externalName 对应的 A 记录。

4.2 编写 ExternalName

第一次在 YAML 里写了 namespace: prod,但该命名空间不存在,报错:

root@master30 ~ 11:42:44# cat ExternalName.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: www.ningcode.cn

root@master30 ~ 11:42:47# kubectl apply -f ExternalName.yaml
Error from server (NotFound): error when creating "ExternalName.yaml": namespaces "prod" not found

去掉 namespace 字段(落到当前命名空间 services),重新应用:

root@master30 ~ 11:43:17# cat ExternalName.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ExternalName
  externalName: www.ningcode.cn

root@master30 ~ 11:43:20# kubectl apply -f ExternalName.yaml
service/my-service created

root@master30 ~ 11:43:23# kubectl get svc
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)   AGE
my-service   ExternalName   <none>         www.ningcode.cn   <none>    11s
web          LoadBalancer   10.107.179.7   10.1.8.40         80:30639/TCP   101m
  • CLUSTER-IP<none>EXTERNAL-IP 列显示的就是目标域名 www.ningcode.cn

4.3 安装 dig 验证工具

root@master30 ~ 11:43:52# apt install -y bind-dnsutils
E: Package 'bind-dnsutils' has no installation candidate

# Ubuntu 24.04 包名是 bind9-dnsutils
root@master30 ~ 11:44:13# apt install -y bind9-dnsutils
......(安装过程省略)

4.4 用 dig 看集群 DNS 解析

# web 普通 Service:返回 ClusterIP
root@master30 ~ 11:44:55# dig @10.96.0.10 web.services.svc.cluster.local
;; ANSWER SECTION:
web.services.svc.cluster.local.	30 IN	A	10.107.179.7

# my-service(ExternalName):返回 CNAME + 真实 A 记录
root@master30 ~ 11:45:26# dig @10.96.0.10 my-service.services.svc.cluster.local
;; ANSWER SECTION:
my-service.services.svc.cluster.local. 30 IN CNAME www.ningcode.cn.
www.ningcode.cn.	30	IN	A	47.99.246.250
  • @10.96.0.10 指定用 CoreDNS 查询(kube-dns 的 ClusterIP)
  • 普通 Service → A 记录指向 ClusterIP;ExternalName → CNAME 指向外部域名,再递归解析出公网 A 记录(47.99.246.250 是用户云服务器)

4.5 宿主机 vs 容器内访问

# 宿主机直接 curl:解析失败(宿主机的 /etc/resolv.conf 不走集群 DNS)
root@master30 ~ 11:46:26# curl my-service.services.svc.cluster.local
curl: (6) Could not resolve host: my-service.services.svc.cluster.local

进 busybox 容器验证(容器走集群 DNS):

# 第一次写错参数
root@master30 ~ 11:47:09# kubectl run test --rm --it --image busybox -- sh
error: unknown flag: --it

root@master30 ~ 11:47:31# kubectl run test --rm -it --image busybox -- sh
If you don't see a command prompt, try pressing enter.
/ # curl my-service.services.svc.cluster.local
sh: curl: not found          # busybox 里没有 curl,用 wget

/ # wget my-service.services.svc.cluster.local
Connecting to my-service.services.svc.cluster.local (47.99.246.250:80)
saving to 'index.html'
index.html           100% |**********************************************|  1326  0:00:00 ETA
'index.html' saved
/ # exit
pod "test" deleted

4.6 要点

  • ExternalName 适合把集群外服务(老系统、云数据库、公网网站)映射成集群内 DNS 名字,业务代码不用改地址
  • 集群内解析必须用集群 DNS:宿主机 curl 会失败,容器内正常;排障用 dig @10.96.0.10 名称.services.svc.cluster.local
  • kubectl run 交互参数是 -it,写成 --it 会报 unknown flag

↑ 回到目录


五、Service 轮询与会话保持

5.1 重建 web 应用

原 web 资源还在,先删再建,避免同名冲突:

root@master30 ~ 12:30:32# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
error: failed to create deployment: deployments.apps "web" already exists

root@master30 ~ 12:30:41# kubectl delete deployments.apps web
deployment.apps "web" deleted

root@master30 ~ 12:30:50# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/web created

# Service 同名也冲突,先删旧的 LoadBalancer Service 再 expose
root@master30 ~ 12:30:55# kubectl expose deployment web --port 80
Error from server (AlreadyExists): services "web" already exists

root@master30 ~ 12:31:12# kubectl delete service web
service "web" deleted

root@master30 ~ 12:31:32# kubectl expose deployment web --port 80
service/web exposed

5.2 给两个 Pod 写入不同的主页内容

root@master30 ~ 12:31:38# for pod in $(kubectl get pods -o name | awk -F/  '{print $2}'); do    kubectl exec -it $pod -- bash -c "echo $pod > htdocs/index.html"; done

root@master30 ~ 12:32:01# kubectl get svc web
NAME   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.104.183.228   <none>        80/TCP    49s

5.3 压测:iptables 随机概率(11:9)

root@master30 ~ 12:32:37# for i in {1..20};do curl -s 10.104.183.228;done|sort |uniq -c
     11 web-5885dfff8c-nfm6n
      9 web-5885dfff8c-pqwsz

20 次请求不是严格的 10:10,而是 11:9——正是第二章 iptables random 0.5 概率分流的体现。

5.4 开启会话保持

root@master30 ~ 12:33:03# kubectl patch svc web -p '{"spec":{"sessionAffinity":"ClientIP"}}'
service/web patched

root@master30 ~ 12:33:07# for i in {1..20};do curl -s 10.104.183.228;done|sort |uniq -c
     20 web-5885dfff8c-nfm6n
root@master30 ~ 12:33:17# for i in {1..20};do curl -s 10.104.183.228;done|sort |uniq -c
     20 web-5885dfff8c-nfm6n
root@master30 ~ 12:33:22# for i in {1..20};do curl -s 10.104.183.228;done|sort |uniq -c
     20 web-5885dfff8c-nfm6n

5.5 要点

  • 默认 sessionAffinity: None:同源 IP 的请求会被随机分到不同后端
  • sessionAffinity: ClientIP:按来源 IP 哈希固定到同一后端(20/20),适合有状态会话(登录态、购物车)
  • 会话保持粒度是 ClientIP,不是 Session/Cookie;要按 Cookie 做粘性需要 Ingress 的 affinity 或应用层解决
  • 同名资源(Deployment / Service)创建前先确认是否存在,避免 AlreadyExists 报错

↑ 回到目录


六、ConfigMap 挂载与版本权重分流

6.1 准备两个版本的文件并创建 ConfigMap

root@master30 ~ 12:33:23# mkdir web
root@master30 ~ 13:45:26# echo hello nginx 1.28 > web/index28.html
root@master30 ~ 13:45:35# echo hello nginx 1.29 > web/index29.html
root@master30 ~ 13:46:04# kubectl create configmap web --from-file=./web
configmap/web created

root@master30 ~ 13:47:10# kubectl get configmaps web -o yaml |grep ^data -A4
data:
  index28.html: |
    hello nginx 1.28
  index29.html: |
    hello nginx 1.29

6.2 两个版本 Deployment 挂载 ConfigMap(参考清单)

日志中的 YAML 由 vim 编写、未回显,这里给出与实验结果一致的参考写法:两个 Deployment 的 Pod 打相同标签 app: web(这样同一个 Service 都能选中),各自用 subPath 挂载不同版本的 index 文件:

# webapp-1.28.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-28
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
        version: "1.28"
    spec:
      containers:
      - name: httpd
        image: hub.laoma.cloud/library/httpd
        ports:
        - containerPort: 80
        volumeMounts:
        - name: web-cm
          mountPath: /usr/local/apache2/htdocs/index.html
          subPath: index28.html
      volumes:
      - name: web-cm
        configMap:
          name: web
# webapp-1.29.yaml:同上,name: web-29、subPath: index29.html、version: "1.29"
# webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80

6.3 部署并缩放成 8:2

root@master30 ~ 13:48:14# kubectl apply -f webapp-1.28.yaml
deployment.apps/web-28 created
root@master30 ~ 13:48:37# kubectl apply -f webapp-svc.yaml
service/web configured
root@master30 ~ 13:50:38# kubectl apply -f webapp-1.29.yaml
deployment.apps/web-29 created

root@master30 ~ 13:50:48# kubectl scale deployment web-28 --replicas 8
deployment.apps/web-28 scaled
root@master30 ~ 13:51:09# kubectl scale deployment web-29 --replicas 2
deployment.apps/web-29 scaled

6.4 压测观察流量占比

第一轮:扩容完立刻压,50 次全部落在 1.28(刚扩容的 web-29 Pod 还没进 Endpoints / 未就绪):

root@master30 ~ 13:51:14# for i in {1..50}; do curl -s 10.104.183.228; done | sort -n|uniq -c
     50 hello nginx 1.28

第二轮:删掉所有资源(kubectl delete deployments.apps web-28 web-29kubectl delete service web)后重新 apply,等 Pod 全部 Running 再压测,占比与副本比 8:2 基本一致:

root@master30 ~ 13:56:28# kubectl apply -f webapp-1.28.yaml
root@master30 ~ 13:56:36# kubectl apply -f webapp-svc.yaml
service/web created
root@master30 ~ 13:56:54# kubectl apply -f webapp-1.29.yaml
root@master30 ~ 13:57:09# kubectl scale deployment web-28 --replicas 8
root@master30 ~ 13:57:28# kubectl scale deployment web-29 --replicas 2

root@master30 ~ 13:57:34# for i in {1..50}; do curl -s 10.108.166.139; done | sort -n|uniq -c
     44 hello nginx 1.28
      6 hello nginx 1.29
root@master30 ~ 13:57:51# for i in {1..50}; do curl -s 10.108.166.139; done | sort -n|uniq -c
     42 hello nginx 1.28
      8 hello nginx 1.29
root@master30 ~ 13:58:01# for i in {1..50}; do curl -s 10.108.166.139; done | sort -n|uniq -c
     46 hello nginx 1.28
      4 hello nginx 1.29

6.5 要点

  • Service 的负载均衡粒度是 Pod(Endpoint),副本数 8:2 → 流量占比约 8:2,这就是"按副本数做灰度/权重分流"的原理
  • 压测前先 kubectl get pods 确认全部 Runningkubectl get endpoints web 确认 Endpoints 数量=10,否则结果会失真
  • ConfigMap 用 subPath 挂载到已有文件时,改 ConfigMap 内容不会热更新到容器内已挂载文件,需要重建 Pod 才能生效

↑ 回到目录


七、kube-proxy 切换 IPVS 模式

7.1 查看当前模式

root@master30 ~ 14:17:24# kubectl create ns services
namespace/services created
root@master30 ~ 14:17:31# kubectl config set-context --current --namespace services
Context "kubernetes-admin@kubernetes" modified.

root@master30 ~ 14:17:50# kubectl get cm -n kube-system kube-proxy -o yaml|grep mode
    mode: ""            # 空 = 默认 iptables

# 从 kube-proxy 日志确认
root@master30 ~ 14:18:55# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-2c5tb
pod/kube-proxy-5ltr7
pod/kube-proxy-8mg2v

root@master30 ~ 14:18:58# kubectl logs -n kube-system kube-proxy-2c5tb |grep Using
I0811 05:39:05.970175       1 server_linux.go:69] "Using iptables proxy"
I0811 05:39:06.061279       1 server_linux.go:165] "Using iptables Proxier"

7.2 修改配置并重启 kube-proxy

root@master30 ~ 14:19:27# kubectl edit configmaps -n kube-system kube-proxy
# 把 mode: "" 改成 mode: "ipvs",保存退出

root@master30 ~ 14:20:16# kubectl get cm -n kube-system kube-proxy -o yaml|grep mode
    mode: "ipvs"

root@master30 ~ 14:20:20# kubectl rollout restart daemonset -n kube-system kube-proxy
daemonset.apps/kube-proxy restarted

# 新 Pod 起来后看日志
root@master30 ~ 14:20:57# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-dgzk8
pod/kube-proxy-n284c
pod/kube-proxy-ndn2h

root@master30 ~ 14:21:20# kubectl logs -n kube-system kube-proxy-dgzk8 |grep Using
I0811 06:21:01.740336       1 server_linux.go:233] "Using ipvs Proxier"

7.3 建 3 副本应用并压测

root@master30 ~ 14:21:39# kubectl create deployment web --image=nginx --replicas=3
deployment.apps/web created

把每个 Pod 的主页写成自己的名字(注意:nginx 首页路径是 /usr/share/nginx/html/index.html):

root@master30 ~ 14:25:02# kubectl get pods -o wide |awk '{print $1,$6}'
NAME IP
web-7c56dcdb9b-dngnd 10.224.195.146
web-7c56dcdb9b-ngmn5 10.224.83.146
web-7c56dcdb9b-w4fgq 10.224.83.147

root@master30 ~ 14:25:24# curl http://10.224.195.146
web-7c56dcdb9b-dngnd
root@master30 ~ 14:25:41# curl http://10.224.83.146
web-7c56dcdb9b-ngmn5
root@master30 ~ 14:25:54# curl http://10.224.83.147
web-7c56dcdb9b-w4fgq

root@master30 ~ 14:25:59# kubectl expose deployment web --port=80
service/web exposed

root@master30 ~ 14:26:21# kubectl get svc web
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.96.153.122   <none>        80/TCP    5s

7.4 ipvsadm 查看虚拟服务与后端

root@master30 ~ 14:26:26# ipvsadm -Lnt 10.96.153.122:80
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.96.153.122:80 rr
  -> 10.224.83.146:80             Masq    1      0          0
  -> 10.224.83.147:80             Masq    1      0          0
  -> 10.224.195.146:80            Masq    1      0          0
  • Scheduler 列为 rr(Round Robin 轮询),Masq 表示转发方式为 NAT 伪装

7.5 压测:严格轮询 20/20/20

root@master30 ~ 14:26:52# for i in {1..60}; do curl -s 10.96.153.122; done| sort | uniq -c
     20 web-7c56dcdb9b-dngnd
     20 web-7c56dcdb9b-ngmn5
     20 web-7c56dcdb9b-w4fgq

60 次请求严格 20/20/20——与 iptables 的随机概率形成鲜明对比。

7.6 要点

  • 切换三步:kubectl edit cm -n kube-system kube-proxy 改 mode → kubectl rollout restart daemonset -n kube-system kube-proxy → 看日志确认 Using ipvs Proxier
  • ipvs 模式用内核的 LVS 能力,默认 rr 轮询、性能更好、规则更直观(ipvsadm -Lnt 一眼看到后端列表)
  • 建完 Deployment 后 Pod 还没 Ready 时立刻 exec 会报 container not found,等 Pod Running 再操作

↑ 回到目录


八、IPVS 调度算法探究

8.1 实验过程

日志里想改调度算法,但 kubectl edit 没有保存就退出了,所以本次没有真正改成功(这也是一个很好的反面教材):

root@master30 ~ 14:49:40# #更改调度算法:
root@master30 ~ 15:08:53# kubectl edit configmap kube-proxy -n kube-system
Edit cancelled, no changes made.

8.2 正确姿势(按实验意图补齐)

IPVS 调度算法由 kube-proxy 的 ConfigMap 控制,默认 rr,可改成 wrr(加权轮询)、lc(最少连接)、sh(源地址哈希,天然会话保持)等:

# 1. 编辑 ConfigMap,把 ipvs.scheduler(或顶层 scheduler)字段改成目标算法
kubectl edit configmap kube-proxy -n kube-system
#    scheduler: "rr"   →   scheduler: "wrr"

# 2. 重启 DaemonSet 使配置生效
kubectl rollout restart daemonset -n kube-system kube-proxy

# 3. 验证:Scheduler 列变为 wrr
ipvsadm -Lnt 10.96.153.122:80

8.3 要点

  • kubectl edit 不保存退出会提示 Edit cancelled, no changes made.,配置不会变
  • 改完 ConfigMap 必须重启 kube-proxy 才生效
  • rr 适合后端能力相同的场景;后端性能有差异时用 wrr 配 Weight;长连接 / 有状态场景用 sh(等价 ClientIP 会话保持)

↑ 回到目录


九、Ingress-nginx 控制器部署

9.1 概念

Service 的负载均衡局限在"IP + 端口"层面;Ingress 工作在 HTTP 层,按域名 / 路径把请求路由到不同 Service。Ingress 只是规则,真正干活的是 Ingress Controller(本次用 ingress-nginx v1.11.2,本质是一个反向代理 Pod)。

9.2 下载、解压、替换镜像

root@master30 ~ 15:09:34# kubectl create ns ingress
namespace/ingress created
root@master30 ~ 15:09:53# kubectl config set-context --current --namespace ingress
Context "kubernetes-admin@kubernetes" modified.

root@master30 ~ 15:10:04# wget http://192.168.46.200/class/course-materials/softwares/stage03/ingress-nginx-controller-v1.11.2.tar.gz
--2026-08-11 15:10:16--  http://192.168.46.200/class/course-materials/softwares/stage03/ingress-nginx-controller-v1.11.2.tar.gz
Length: 3902473 (3.7M) [application/octet-stream]
ingress-nginx-controller-v1.11.2. 100%[=====================================>]   3.72M  11.2MB/s    in 0.3s

root@master30 ~ 15:10:16# tar -xf ingress-nginx-controller-v1.11.2.tar.gz

root@master30 ~ 15:10:34# grep image: ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml|uniq
        image: registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
        image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3

# 去掉 @sha256 digest 并换成私有仓库(apply 之前必须改好!)
root@master30 ~ 15:19:33# sed -ir 's#@sha256.*##;s/registry.k8s.io/hub.laoma.cloud/' ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml

root@master30 ~ 15:20:14# grep image: ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml|uniq
        image: hub.laoma.cloud/ingress-nginx/controller:v1.11.2
        image: hub.laoma.cloud/ingress-nginx/kube-webhook-certgen:v1.4.3

9.3 踩坑:Job 的 template 不可变

第一次 apply 时用的是原镜像(registry.k8s.io),Job 已创建;第二次(改完镜像)直接再 apply 想原地更新 Job,报错 field is immutable

root@master30 ~ 15:19:41# kubectl apply -f ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
namespace/ingress-nginx unchanged
......
deployment.apps/ingress-nginx-controller configured
Error from server (Invalid): error when applying patch:
......
Job.batch "ingress-nginx-admission-create" is invalid: spec.template: Invalid value: ... : field is immutable

原因:Job 的 spec.template 创建后不可修改。第一次 apply 用旧镜像创建了 ingress-nginx-admission-create/patch 两个 Job,第二次 apply 想改它们 → 报错。

修复:先删掉两个旧 Job,再重新 apply:

root@master30 ~ 15:20:54# kubectl delete job -n ingress-nginx ingress-nginx-admission-create
root@master30 ~ 15:20:54# kubectl delete job -n ingress-nginx ingress-nginx-admission-patch
job.batch "ingress-nginx-admission-create" deleted
job.batch "ingress-nginx-admission-patch" deleted

root@master30 ~ 15:21:48# kubectl apply -f ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
namespace/ingress-nginx unchanged
......
deployment.apps/ingress-nginx-controller configured
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

9.4 验证控制器就绪

root@master30 ~ 15:21:53# kubectl get all -n ingress-nginx
NAME                                            READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-wcc5z        0/1     Completed   0          11s
pod/ingress-nginx-admission-patch-452n6         0/1     Completed   0          11s
pod/ingress-nginx-controller-8659885ffd-kzdww   1/1     Running     0          2m15s

NAME                                         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.101.176.159   10.1.8.40     80:31920/TCP,443:30883/TCP   10m
service/ingress-nginx-controller-admission   ClusterIP      10.105.228.17    <none>        443/TCP                      10m
......
job.batch/ingress-nginx-admission-create   Complete   1/1           3s         11s
job.batch/ingress-nginx-admission-patch    Complete   1/1           3s         11s
  • 控制器 Service 是 LoadBalancer,MetalLB 分配 10.1.8.40(上一轮 web LoadBalancer 已删除,VIP 被复用)
  • 两个 admission Job Completed 说明 webhook 证书已生成

顺手清理旧镜像的 ReplicaSet(已缩到 0,删掉更干净):

root@master30 ~ 15:23:00# kubectl delete rs ingress-nginx-controller-7d4db76476 -n ingress-nginx
replicaset.apps "ingress-nginx-controller-7d4db76476" deleted

9.5 要点

  • 镜像替换必须在 apply 之前完成;对已创建的资源改 spec 前先 kubectl delete 对应资源
  • sed -ir 's#@sha256.*##;s/registry.k8s.io/hub.laoma.cloud/'# 作分隔符避免与 / 冲突,@sha256.* 去掉 digest
  • 控制器入口是 service/ingress-nginx-controller(LoadBalancer 10.1.8.40,80/443),后面所有 Ingress 规则都从它进入

↑ 回到目录


十、Ingress 多域名路由

10.1 准备两个后端应用

webapp01 / webapp02 各 2 副本(httpd),分别写入不同主页内容,并 expose 成 ClusterIP Service:

root@master30 ~ 15:23:43# kubectl create deployment webapp01 --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/webapp01 created

root@master30 ~ 15:53:08# kubectl exec -it webapp01-587d58c655-4cvxf -- bash -c "echo hello webapp01 pod1 > htdocs/index.html"
root@master30 ~ 15:53:47# kubectl exec -it webapp01-587d58c655-lbfwx -- bash -c "echo hello webapp01 pod2 > htdocs/index.html"
root@master30 ~ 15:54:09# kubectl expose deployment webapp01 --port=80 --target-port=80
service/webapp01 exposed

root@master30 ~ 15:54:35# kubectl create deployment webapp02 --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/webapp02 created
# 给 webapp02 两个 Pod 写主页,并额外建 games 目录
root@master30 ~ 15:57:30# kubectl exec -it webapp02-56cfd678d6-cd4ss -- bash -c "echo hellow webapp02 pod2 > htdocs/index.html;mkdir htdocs/games;echo hello webapp02 game2 > htdocs/games/index.html"
root@master30 ~ 15:58:01# kubectl expose deployment webapp02 --port=80 --target-port=80
service/webapp02 exposed

10.2 多域名 Ingress(一个 Ingress 多个 host)

日志中清单由 vim 编写未回显,describe 显示的实际规则为 webapp01.ningcode.cn /webapp02.ningcode.cn /,等价写法如下:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-host-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: webapp01.ningcode.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp01
            port:
              number: 80
  - host: webapp02.ningcode.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp02
            port:
              number: 80
root@master30 ~ 15:59:25# kubectl apply -f ingress.yaml
ingress.networking.k8s.io/multi-host-ingress created

root@master30 ~ 15:59:31# kubectl describe ingress multi-host-ingress
Name:             multi-host-ingress
Namespace:        ingress
Address:          10.1.8.40
Ingress Class:    nginx
Rules:
  Host                  Path  Backends
  ----                  ----  --------
  webapp01.ningcode.cn
                        /   webapp01:80 (10.224.195.147:80,10.224.83.154:80)
  webapp02.ningcode.cn
                        /   webapp02:80 (10.224.195.148:80,10.224.83.155:80)
Events:
  Normal  Sync    4s  nginx-ingress-controller  Scheduled for sync
  • Address: 10.1.8.40:Ingress 的入口就是 ingress-nginx 控制器 Service 的 LoadBalancer IP
  • 两个域名共用同一个 IP,靠 HTTP 的 Host 头区分后端

10.3 验证后端轮询

# webapp01 的 ClusterIP:pod1/pod2 严格 30/30
root@master30 ~ 16:03:15# for i in {1..60}; do curl -s 10.111.64.166; done| sort | uniq -c
     30 hello webapp01 pod1
     30 hello webapp01 pod2

# webapp02 的 ClusterIP:pod1/pod2 严格 30/30
root@master30 ~ 16:03:31# for i in {1..60}; do curl -s 10.110.47.159; done| sort | uniq -c
     30 hellow webapp02 pod1
     30 hellow webapp02 pod2

提示:域名测试要在客户端改 /etc/hosts,否则 webapp01.ningcode.cn 会去公网解析(详见下一章)。

10.4 要点

  • Ingress 按 Host 精确匹配域名,再按 path 匹配路径;匹配不到走 Default backend(没有则 404)
  • 一个 Ingress 可以写多个 rules(多域名),也可以一个域名下写多个 paths(多路径,见下一章)
  • ingressClassName: nginx 指定由哪个控制器处理(对应集群里的 IngressClass 资源)

↑ 回到目录


十一、Ingress 多路径路由

11.1 一个域名按路径分流

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-path-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: www.ningcode.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp01
            port:
              number: 80
      - path: /games
        pathType: Prefix
        backend:
          service:
            name: webapp02
            port:
              number: 80
root@master30 ~ 16:15:28# kubectl apply -f ingress.yaml
ingress.networking.k8s.io/multi-path-ingress created

root@master30 ~ 16:15:43# kubectl describe ingress multi-path-ingress
Name:             multi-path-ingress
Namespace:        ingress
Rules:
  Host             Path  Backends
  ----             ----  --------
  www.ningcode.cn
                   /        webapp01:80 (10.224.195.147:80,10.224.83.154:80)
                   /games   webapp02:80 (10.224.195.148:80,10.224.83.155:80)
Events:
  Normal  Sync    16s  nginx-ingress-controller  Scheduled for sync

11.2 踩坑:域名解析到了公网

直接 curl,请求跑到了公网云服务器(www.ningcode.cn 解析到 47.99.246.250,返回宝塔默认页"没有找到站点"):

root@master30 ~ 16:17:47# curl www.ningcode.cn
<!doctype html>
<title>没有找到站点</title>
......(公网服务器返回的默认页面)
root@master30 ~ 16:17:57# #这里跑到公网去了,要配etc hosts文件,让优先级更高

11.3 修改 /etc/hosts 让本地优先

# /etc/hosts 追加一行(10.1.8.40 是 ingress-nginx 的 LoadBalancer IP)
10.1.8.40 www.ningcode.cn

改完后验证(第一次改完 IP 不对会 Failed to connect,vim 改对后正常):

root@master30 ~ 16:33:41# curl www.ningcode.cn
curl: (7) Failed to connect to www.ningcode.cn port 80 after 0 ms: Couldn't connect to server

root@master30 ~ 16:33:56# curl www.ningcode.cn
hello webapp01 pod1
root@master30 ~ 16:34:00# curl www.ningcode.cn
hello webapp01 pod2
......(pod1/pod2 交替返回)

root@master30 ~ 16:34:03# curl www.ningcode.cn/games/
hello webapp02 game1
  • / → webapp01(pod1/pod2 轮询)
  • /games/ → webapp02 的 games 子目录

11.4 要点

  • 域名解析优先级:/etc/hosts > DNS 服务器;实验环境记得加 hosts,否则流量不会进集群
  • pathType: Prefix 按前缀匹配,/games 匹配 /games//games/xxx;末尾斜杠访问 /games/ 更规范
  • 测试前先 kubectl describe ingress 确认 Address 已分配、规则已 Sync

↑ 回到目录


十二、Ingress 路径重写(rewrite-target)

12.1 需求

想让 /webapp01/xxx 转发给 webapp01,且后端收到的是裁剪后的路径 /xxx(等价 Nginx 的 proxy_pass /),而不是带着 /webapp01 前缀。需要两个注解:

  • nginx.ingress.kubernetes.io/use-regex: "true":启用正则路径
  • nginx.ingress.kubernetes.io/rewrite-target: /$1:把正则捕获组 $1 作为转发路径
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-path-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  ingressClassName: nginx
  rules:
  - host: www.ningcode.cn
    http:
      paths:
      - path: /webapp01/(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: webapp01
            port:
              number: 80
      - path: /webapp02/(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: webapp02
            port:
              number: 80
root@master30 ~ 16:40:45# kubectl apply -f ingress.yaml
ingress.networking.k8s.io/multi-path-ingress created

root@master30 ~ 16:41:00# kubectl describe ingress multi-path-ingress
Name:             multi-path-ingress
Rules:
  Host             Path  Backends
  ----             ----  --------
  www.ningcode.cn
                   /webapp01/(.*)   webapp01:80 (10.224.195.147:80,10.224.83.154:80)
                   /webapp02/(.*)   webapp02:80 (10.224.195.148:80,10.224.83.155:80)
Annotations:       nginx.ingress.kubernetes.io/rewrite-target: /$1
                   nginx.ingress.kubernetes.io/use-regex: true

12.2 验证

# 根路径没有规则了 → nginx 404(符合预期)
root@master30 ~ 16:41:08# curl www.ningcode.cn
<html><head><title>404 Not Found</title></head>......</html>

# 重写后正常访问(路径前缀被裁剪)
root@master30 ~ 16:42:07# curl www.ningcode.cn/webapp01/
hello webapp01 pod2
root@master30 ~ 16:42:15# curl www.ningcode.cn/webapp01/
hello webapp01 pod1
root@master30 ~ 16:42:18# curl www.ningcode.cn/webapp02/
hellow webapp02 pod2
root@master30 ~ 16:42:21# curl www.ningcode.cn/webapp02/
hellow webapp01 pod1

# 后端 httpd 没有 aaa 页面 → 后端 404
root@master30 ~ 16:42:22# curl www.ningcode.cn/webapp02/aaa
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<title>404 Not Found</title>

12.3 要点

  • rewrite-target: /$1 + use-regex: true + pathType: ImplementationSpecific 三件套缺一不可
  • 加了正则规则后,原 / 规则若已删除,根路径会 404——这是预期行为,不是故障
  • 区分两种 404:控制器 nginx 的 404(路径没匹配到规则)vs 后端应用的 404(规则命中了但后端没有该资源)

↑ 回到目录


十三、Ingress TLS 与强制 HTTPS

13.1 生成自签证书

root@master30 ~ 16:45:03# openssl genrsa -out www.key 2048

root@master30 ~ 16:45:20# openssl req -new -key www.key -out www.csr -subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=www.laoma.cloud/emailAddress=webadmin@laoma.cloud"

root@master30 ~ 16:45:27# openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt
Certificate request self-signature ok
subject=C = CN, ST = JS, L = NJ, O = LM, OU = DEVOPS, CN = www.laoma.cloud, emailAddress = webadmin@laoma.cloud

13.2 创建 TLS Secret 并编写 Ingress

root@master30 ~ 16:45:32# kubectl create secret tls www-tls --cert=./www.crt --key=./www.key
secret/www-tls created
# ingress.yaml(tls-ingress)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - www.laoma.cloud
    secretName: www-tls
  rules:
  - host: www.ningcode.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp01
            port:
              number: 80
root@master30 ~ 16:46:12# kubectl apply -f ingress.yaml
ingress.networking.k8s.io/tls-ingress created

root@master30 ~ 16:46:32# kubectl describe ingress tls-ingress
Name:             tls-ingress
TLS:
  www-tls terminates www.laoma.cloud
Rules:
  Host             Path  Backends
  ----             ----  --------
  www.ningcode.cn
                   /   webapp01:80 (10.224.195.147:80,10.224.83.154:80)
Annotations:       nginx.ingress.kubernetes.io/force-ssl-redirect: true
                   nginx.ingress.kubernetes.io/ssl-redirect: true

13.3 验证:HTTP 强制跳转 HTTPS

# 不带 -L:返回 308 重定向(强制 HTTPS 生效)
root@master30 ~ 16:47:25# curl -k http://www.ningcode.cn/
<html><head><title>308 Permanent Redirect</title></head>......</html>

# 带 -L:跟随跳转后正常访问
root@master30 ~ 16:46:42# curl -Lk http://www.ningcode.cn/
hello webapp01 pod1
root@master30 ~ 16:47:22# curl -Lk http://www.ningcode.cn/
hello webapp01 pod2

# 直接 https 访问(-k 忽略自签证书告警)
root@master30 ~ 16:47:32# curl -k https://www.ningcode.cn/
hello webapp01 pod2
root@master30 ~ 16:47:38# curl -k https://www.ningcode.cn/
hello webapp01 pod1

13.4 要点

  • TLS 三步:openssl 自签 → kubectl create secret tls → Ingress 的 spec.tls 里挂 secretName
  • ssl-redirect(默认开)与 force-ssl-redirect 都是把 HTTP 跳转到 HTTPS;返回码是 308
  • 自签证书访问用 -k 忽略校验;生产环境用证书管理工具(cert-manager)签发受信任证书
  • 注意本实验中证书 CN 是 www.laoma.cloud,而测试域名是 www.ningcode.cn-k 下不受影响;浏览器访问会告警

↑ 回到目录


十四、环境清理与恢复

实验收尾删除测试环境(webapp 应用在 ingress 命名空间,Ingress 控制器在 ingress-nginx 命名空间):

root@master30 ~ 16:48:43# #清理环境
root@master30 ~ 16:48:59# kubectl delete ns ingress-nginx
namespace "ingress-nginx" deleted

删除 ingress 命名空间时卡住了(里面有 webapp01/webapp02 等存量资源,且有 webhook 校验),被 ^C 中断:

root@master30 ~ 16:51:00# ^C
root@master30 ~ 16:51:04# kubectl get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/webapp01-587d58c655-4cvxf   1/1     Running   0          58m
pod/webapp02-56cfd678d6-cd4ss   1/1     Running   0          55m
service/webapp01   ClusterIP   10.111.64.166   <none>        80/TCP    56m
service/webapp02   ClusterIP   10.110.47.159   <none>        80/TCP    52m
......

14.1 清理的正确姿势(按实验意图补齐)

# 1. 先删 Ingress 规则(避免 webhook 校验拦截)
kubectl delete ingress --all -n ingress
# 2. 再删应用
kubectl delete deployment,service --all -n ingress
# 3. 最后删命名空间(此时应为空,秒删)
kubectl delete ns ingress
# 4. 确认
kubectl get ns

14.2 要点

  • 命名空间处于 Terminating 状态卡住时,多半是里面还有资源或 finalizer 没清掉;先删资源再删命名空间
  • ingress-nginx 命名空间删除后,之前创建的 Ingress 规则会全部失效(控制器没了)
  • MetalLB(metallb-system)保留未删:后续实验还要用 LoadBalancer 分配外部 IP

↑ 回到目录


十五、常见错误与排查(重点)

15.1 命令语法类

报错原因修复
kubectl run test --rm --it ...error: unknown flag: --it交互参数写错-it
apt install -y bind-dnsutilsE: Package has no installation candidateUbuntu 24.04 包名变化安装 bind9-dnsutils
kubectl get pods ... -i nameerror: unknown shorthand flag: 'i'输出格式参数写错-o name
kubectl get all -n ingress-nginx -wyou may only specify a single resource type-w 不支持 all 多类型watch kubectl get all -n ingress-nginx
grep image metallb-0.14.8/config/manifests/metallb-nativeNo such file or directory文件名少了 .yaml补全 metallb-native.yaml
`kubectl get configmaps web -o yamlgrep ^data -A4 data: …` → `bash: hello: command not found`输出内容被误粘贴进命令
kubectl exec ... bash -c"echo ..."(少空格)-c 与脚本粘连写成 bash -c "..."

15.2 状态 / 资源类

报错原因修复
Error from server (NotFound): namespaces "prod" not foundExternalName 写了不存在的命名空间删掉 namespace 字段或用已存在的命名空间
deployments.apps "web" already exists / services "web" already exists同名资源已存在kubectl delete 旧资源再创建
curl: (6) Could not resolve host: my-service.services.svc.cluster.local宿主机不走集群 DNSdig @10.96.0.10 验证,或进容器访问
curl: (7) Failed to connect ... Couldn't connect to server/etc/hosts 里 IP 不对或服务未就绪vim 修改 /etc/hosts 后重试
error: Internal error occurred: ... container not found ("nginx")Pod 还在创建,容器没起来等 Pod Runningkubectl exec
curl www.ningcode.cn 返回公网页面域名解析到公网/etc/hosts 加 10.1.8.40 www.ningcode.cn
nginx 的 404 Not Found(无标题)Ingress 没有匹配的路径规则检查 rules / 路径是否匹配(rewrite 后根路径无规则属预期)
httpd 的 404 Not Found(有标题)规则命中但后端应用没有该资源检查后端目录 / 文件是否存在
308 Permanent Redirect强制 HTTPS 生效curl -L 跟随跳转,或直接访问 https

15.3 Ingress / 控制器专项

报错原因修复
Job.batch "ingress-nginx-admission-xxx" is invalid: ... field is immutable改镜像后直接 apply,想原地更新已创建的 Jobkubectl delete job -n ingress-nginx ingress-nginx-admission-create ingress-nginx-admission-patch 后重新 apply
describe ingressAddress 为空控制器还没同步完成稍等再 describe;检查 IngressClass / 控制器是否 Running
describe ingress mulit-path-ingressNotFound拼写错误核对名字(multi-path-ingress)
kubectl edit configmap kube-proxyEdit cancelled, no changes made.编辑器里没保存就退出重新 edit 并 :wq 保存;改完重启 kube-proxy

15.4 概念要点对照

对比项iptables 模式ipvs 模式
负载均衡方式random 概率(0.5+默认)内核 LVS 调度(默认 rr 严格轮询)
查看规则iptables-saveipvsadm -Lnt
会话保持sessionAffinity: ClientIP调度算法 sh / ClientIP

↑ 回到目录


十六、命令速查表与小结

16.1 命令速查表

场景命令
查看 Service 流量链路iptables-save |grep KUBE-SVC-xxx / ipvsadm -Lnt <ClusterIP>:<port>
会话保持kubectl patch svc <name> -p '{"spec":{"sessionAffinity":"ClientIP"}}'
创建 ConfigMapkubectl create configmap <name> --from-file=./dir
版本权重kubectl scale deployment <name> --replicas 8 + 压测 for i in {1..50}; do curl -s <IP>; done|sort|uniq -c
kube-proxy 切 IPVSkubectl edit cm -n kube-system kube-proxy(mode: “ipvs”)→ kubectl rollout restart daemonset -n kube-system kube-proxy
部署 MetalLBapply metallb-native.yaml + IPAddressPool + L2Advertisement
部署 Ingress-nginx改镜像后 kubectl apply -f .../deploy.yaml(Job 报错先 delete job)
查看 Ingress 规则kubectl describe ingress <name>
路径重写注解 use-regex: "true" + rewrite-target: /$1,pathType 用 ImplementationSpecific
TLSopenssl genrsa/req/x509kubectl create secret tls → ingress spec.tls + force-ssl-redirect
集群 DNS 验证dig @10.96.0.10 <name>.services.svc.cluster.local

16.2 小结

  1. Service 的转发不是黑盒:iptables 模式是"随机概率"(两个后端约 50/50),ipvs 模式才是严格轮询(rr),排障时先分清当前模式
  2. 裸金属上 LoadBalancer 靠 MetalLB:IPAddressPool 给 VIP,L2Advertisement 用 ARP 宣告,删 Service 会释放 IP
  3. ExternalName 只动 DNS 不动数据面:适合把外部服务映射进集群,验证走 dig @10.96.0.10
  4. 副本数 = 流量权重:Service 按 Endpoint 转发,8:2 副本就是约 8:2 流量,是灰度发布的基础;压测前确认 Endpoints 就绪
  5. Ingress 是规则、控制器才是执行者:多域名靠 Host、多路径靠 path、裁剪路径靠 rewrite-target,全部收敛到控制器的一个 LoadBalancer IP 上
  6. 镜像替换要在 apply 之前完成,Job 的 template 不可变;kubectl edit 不保存等于没改——这两个坑本次都真实踩过

完。本手册基于 master30_2026-08-11_10_01_10.log 整理,命令时间戳均为日志原始记录;省略了重复命令与长输出,报错、修复、验证结果与日志一致。

更多推荐