从传统高可用到 K8s 集群版:技术栈演进实战
2026/9/6 2:01:53 网站建设 项目流程

这篇是「技术演进」总结,讲我一路做下来的两个时代的方案:传统(LVS-DR + Keepalived)K8s 集群版(Deployment / StatefulSet / Service / Ingress / MetalLB)。 不重复上一篇的完整搭建细节(搭建过程见《LVS_DR高可用集群实战》《K8s高可用集群搭建-完整实战》《K8s-MySQL主从集群-实战复盘博客》),这篇聚焦演进逻辑、配置方式、流量转发过程、踩坑对比


一、为什么要演进(先讲动机)

维度传统高可用K8s 集群版
谁管高可用人:写 keepalived.conf、ipvsadm,手工维护平台:控制器自动管,声明式 yaml
故障处理健康检查脚本 + VIP 漂移,人工介入多自愈:容器挂了 kubelet 重启,副本少了控制器补
发布手动改配置、重启服务滚动更新,kubectl apply即可
扩容加机器 + 改 keepalived 规则改副本数 / HPA,自动调度
学习/运维成本每套组件都要单独维护一个 K8s 统一,yaml 即"基础设施即代码"

一句话:传统是靠"配置文件 + 脚本"把高可用"写死";K8s 是把"节点、副本、网络、存储"变成声明式的对象,让平台按你想要的状态去"收敛"。


二、传统技术栈:LVS-DR + Keepalived

2.1 流量转发过程(完整链路:DNS 解析 → VIP 入口 → LVS 转发)

传统这套不是"客户端直接找 LVS",而是一整条链路——域名先过自建的 DNS,DNS 再把域名解析成 Web 的 VIP,最后才到 LVS

客户端 ──访问 www.chengke.com──> DNS 服务(VIP .200,BIND 主从 dns01/.61、dns02/.62) │ ① 解析出 www.chengke.com = 192.168.211.100(Web VIP .100) ▼ 客户端 ──② 请求 Web VIP .100──> LVS Director(.71/.72) ──③ 只改 MAC,不动 IP──> web01(.81) / web02(.82) │ 后端直接用真实 IP 回包给客户端 │ (回包不经过 Director,所以性能最高)

这里 DNS 本身也是高可用的一环(记忆:高可用的 DNS + 高可用的 Web,两条都高可用才叫全程高可用):

  • DNS 高可用:BIND 主从(dns01 主 / dns02 从)+ LVS/Keepalived 给 DNS 服务做 VIP .200。www.chengke.com解析到 web 的 VIP .100。

  • Web 高可用:Keepalived + LVS 给 Web 服务做 VIP .100,转发到 web01/.81、web02/.82。

所以传统高可用要把"入口域名 → DNS 解析 → LVS 转发 → 后端"这一整条链路都做成高可用,客户端全程只需记忆一个域名,其余全部无感知。

关键设计(DR 模式三件套,记忆口诀:藏家门、挂门牌、别抢答)

  1. 挂门牌:后端把 VIP 绑到回环口ip addr add 192.168.211.100/32 dev lo,只用于收包。

  2. 别抢答:后端开 ARP 抑制arp_ignore=1arp_announce=2,防止后端抢答 VIP 的 ARP,外界只认 Director。

  3. 藏家门:VIP 挂lo而不是物理网卡,不参与对外通告。

为什么选 DR?因为回包不经过 Director,Director 只做转发入口,吞吐最高(对比 NAT 模式回包必须绕回 Director,会成瓶颈)。

2.2 配置方式(在 lb-master 的 keepalived.conf 里)

# Web VIP 实例(VRID 51) vrrp_instance VI_WEB { state MASTER # backup 上是 BACKUP interface ens160 virtual_router_id 51 # 同接口上必须唯一,否则脑裂! priority 100 # backup 设 80 virtual_ipaddress { 192.168.211.100 } # VIP 由 Keepalived 管理、负责漂移 } ​ # Web 虚拟服务(wrr 加权轮询) virtual_server 192.168.211.100 80 { lb_algo wrr # 加权轮询:web01=2 web02=1 lb_kind DR # 直接路由 protocol TCP real_server 192.168.211.81 80 { weight 2; TCP_CHECK { connect_timeout 3; retry 3; } } real_server 192.168.211.82 80 { weight 1; TCP_CHECK { connect_timeout 3; retry 3; } } }

这套配置里同时干了两件事

  • Keepalived(VRRP):管 VIP 漂移。MASTER 挂了 → BACKUP 收不到 VRRP 通告 → 自动升 MASTER → VIP 漂过去,客户端无感。

  • LVS / ipvsadm:把virtual_server写进 keepalived.conf,keepalived 会自动加载成 ipvsadm 规则,做四层负载均衡。

健康检查TCP_CHECK(web,探 TCP 端口)和MISC_CHECK(DNS,跑checkdns.shdig查 TXT 记录,见上一篇)。后端挂了自动从 ipvsadm 剔除。

2.3 代表作

LVS-DR 高可用集群(Web + DNS + NFS 共享存储):一套 Director + Keepalived 主备,扛 Web 和 DNS 两个 VIP(.100 / .200),Web 后端挂 NFS 共享一份网页内容。


三、K8s 集群版技术栈(升级后的时代)

3.1 流量转发过程(七层,Ingress 在四层之上又叠了一层)

浏览器 ──域名──> Ingress-nginx(Ingress 按 host/路径路由) │ ▼ Service(ClusterIP,kube-proxy 负载均衡) │ ▼ Endpoints(后端 Pod 列表)──> 真实 Pod

和传统的本质区别

传统(LVS-DR)K8s
转发四层(只认 IP:端口)七层(能按域名 / 路径路由)
负载均衡ipvsadm 的 rr/wrrkube-proxy 自动做 Service 的负载均衡
入口暴露Director 绑 VIPMetalLB给 LoadBalancer Service 分配外部 IP
路由一个端口对一个 service一个端口 + 域名路由到多个 Service(一个 ingress 全搞定)

我这次实操的关键:K8s 集群里没有云厂商 LB,直接用MetalLBingress-nginx-controller这个 LoadBalancer 类型的 Service 分配了一个内网 IP(192.168.211.220),于是http://grafana.k8s.local这种无端口域名就能直接访问了(在此之前只能靠 NodePort 的:31196兜底)。

3.2 配置方式(声明式 yaml,从 master 上kubectl apply

先说清搭这套的三个先后顺序(我实际就是这么做的):

  1. 先装 ingress-nginx(入口控制器,不然业务访问不进来)

  2. 再装 MetalLB(给 LoadBalancer 分配外部 IP,否则入口只走 NodePort)

  3. 最后装 kube-prometheus-stack(监控站本体)

① ingress-nginx 入口控制器(helm 装,关键点:镜像走 harbor、关闭镜像 digest 校验

# 关键:镜像是从 daocloud 代理站拉下来,再推到 harbor,helm 里指向 harbor docker pull m.daocloud.io/registry.k8s.io/ingress-nginx/controller:v1.11.3 docker tag m.daocloud.io/registry.k8s.io/ingress-nginx/controller:v1.11.3 hb.reg.com/k8s/ingress-nginx-controller:v1.11.3 docker push hb.reg.com/k8s/ingress-nginx-controller:v1.11.3 # certgen 同理:docker pull/tag/push hb.reg.com/k8s/ingress-nginx-kube-webhook-certgen:v1.4.4 ​ helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace \ --set controller.image.repository=hb.reg.com/k8s/ingress-nginx-controller \ --set controller.image.tag=v1.11.3 \ --set controller.image.digest="" \ # ⚠️ 关掉 chart 默认 pin 的 digest,否则拉"Harbor 上不存在的 digest"报 not found --set controller.admissionWebhooks.patch.image.repository=hb.reg.com/k8s/ingress-nginx-kube-webhook-certgen \ --set controller.admissionWebhooks.patch.image.tag=v1.4.4 \ --set controller.admissionWebhooks.patch.image.digest="" \ --set controller.replicaCount=1 \ --set controller.resources.requests.cpu=100m --set controller.resources.requests.memory=128Mi \ --set controller.resources.limits.cpu=200m --set controller.resources.limits.memory=256Mi

⚠️ 这里有个必须记住的坑:ingress-nginx chart 默认会给镜像 pin 一个@sha256:...digest。你自己docker pull原镜像 →docker tag→ push 到 harbor 后,Harbor 上那个 tag 的 digest ≠ chart 里 pin 的 digest,kubelet 拉「tag@与你不符的digest」必然not found。所以必须--set controller.image.digest=""让 helm 只用 tag 拉。排障先手:kubectl describe pod -n ingress-nginx <controller>看 Events,Failed to pull image "...@sha256:..."+not found就是 digest 不匹配。

② 高可用入口(MetalLB 给 LoadBalancer 发 IP): 先用metallb-native.yaml装 controller + speaker(我用的 v0.16.1),再 apply IPAddressPool。

# metallb-native.yaml 里引入的两个镜像,speaker 是 DaemonSet 要全节点都有 # quay.io/metallb/controller:v0.16.1 (Deployment) # quay.io/metallb/speaker:v0.16.1 (DaemonSet)
kubectl apply -f metallb-native.yaml # 等 controller Ready 再 apply 地址池(否则 webhook connection refused) kubectl wait --for=condition=ready pod -l app=metallb,component=controller -n metallb-system kubectl apply -f ipaddresspool.yaml

/root/ipaddresspool.yaml(我实际用的,netease 段 220-240):

apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: { name: k8s-pool, namespace: metallb-system } spec: addresses: - 192.168.211.220-192.168.211.240 # ⚠️ 必须避开节点 IP(.202/.203/.204) + Service网段(10.10.0.0/12) + Pod网段(10.244.0.0/16) --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: { name: k8s-l2, namespace: metallb-system } spec: ipAddressPools: [k8s-pool]

装完kubectl get ipaddresspool -n metallb-system看到assignedIPv4: 1,且ingress-nginx-controller的 EXTERNAL-IP 从<pending>变成192.168.211.220= 成功。这时候入口才真正能从「无端口域名」访问(此前只能 NodePort:31196兜底)。

③ 无状态应用(python,Deployment 多副本 + Service + Ingress)

# Deployment:5 副本,容器挂掉 kubelet 自动重启 apiVersion: apps/v1 kind: Deployment metadata: { name: python, namespace: python } spec: replicas: 5 selector: { matchLabels: { app: python } } template: metadata: { labels: { app: python } } spec: containers: - name: python-cicd image: hb.reg.com/project/python-cicd:v1.0.11 # 走内网 harbor,快又稳 ports: [ { containerPort: 5000 } ]
# Service:ClusterIP,把流量负载均衡到 5 个副本 apiVersion: v1 kind: Service metadata: { name: python, namespace: python } spec: type: ClusterIP selector: { app: python } ports: - port: 80 targetPort: 5000 # 对外 80 → 容器 5000
# Ingress:一个入口,按域名路由到 Service apiVersion: networking.k8s.io/v1 kind: Ingress metadata: { name: python-ingress, namespace: python } spec: ingressClassName: nginx rules: - host: python.k8s.local http: paths: - path: / pathType: Prefix backend: service: name: python port: { number: 80 }

④ 有状态应用(MySQL 主从,StatefulSet + 无头服务 + NFS + PV/PVC)

这个走的组件多,但每层解决一个具体问题(见《K8s-MySQL主从集群-实战复盘博客》):

需求K8s 概念解决什么
主从要有固定身份StatefulSet(稳定 Pod 名 mysql-0/1)Deployment 的 Pod 名是随机哈希,无法区分角色
从库要"精确"连主库无头服务 Headless(clusterIP: None)普通 Service 会轮询,破坏主从关系
数据不能丢PV/PVC + NFS数据落共享盘,Pod 没了数据还在
一 Pod 一磁盘volumeClaimTemplates每起一个 Pod 自动建一个 PVC
主从用不同配置ConfigMap + initContainer启动前按序号把 primary.cnf / replica.cnf 放进去

⑤ 监控体系(kube-prometheus-stack,helm 一键)

监控我用kube-prometheus-stack(helm chart)一键装,一个 release 带齐 Prometheus / Grafana / Alertmanager / node-exporter / kube-state-metrics / operator。实际版本:chart 89.2.2 / app v0.93.1

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update kubectl create ns monitoring helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --namespace monitoring

关键点:这 8 个镜像必须能拉到node-exporter是 DaemonSet,3 节点都要有):quay.io/prometheus/node-exporter:v1.12.1quay.io/prometheus/prometheus:v3.14.0-distrolessquay.io/prometheus/alertmanager:v0.34.0quay.io/grafana/grafana:13.2.1-distrolessquay.io/prometheus-operator/prometheus-operator:v0.93.1registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.20.0quay.io/kiwigrid/k8s-sidecar:2.11.2ghcr.io/jkroepke/kube-webhook-certgen:1.8.8

因为registry.k8s.io被你网络环境的墙卡死(manifest 被 307 重定向到 google 欧洲 GCP 存储连不通)、docker hub 的 registry-mirror 加速站缓存又损坏,只有k8s.m.daocloud.io(daocloud 专门代理 k8s.io 镜像的站)能拉 kube-state-metrics,所以它的镜像我单独推到 harbor 再指过去:

docker login hb.reg.com -u admin -p Harbor12345 docker pull k8s.m.daocloud.io/kube-state-metrics/kube-state-metrics:v2.20.0 docker tag k8s.m.daocloud.io/kube-state-metrics/kube-state-metrics:v2.20.0 hb.reg.com/k8s/kube-state-metrics:v2.20.0 docker push hb.reg.com/k8s/kube-state-metrics:v2.20.0 ​ # ⚠️ 改镜像地址必须把 registry 和 repository 拆开(否则拼成 registry.k8s.io/hb.reg.com/...) helm upgrade kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring --reuse-values \ --set kube-state-metrics.image.registry=hb.reg.com \ --set kube-state-metrics.image.repository=k8s/kube-state-metrics \ --set kube-state-metrics.image.tag=v2.20.0 kubectl rollout restart deployment/kube-prometheus-stack-kube-state-metrics -n monitoring

装完kubectl get pods -n monitoring全 Running = 监控体系就位。三个 Web UI(grafana/prometheus/alertmanager)+ python 共用一个 ingress-nginx 入口,靠 Ingress 里不同host域名分流,地址全走 MetalLB 的192.168.211.220


四、流量转发 + 配置方式对比(重点)

4.1 流量转发对比

传统 LVS-DR(完整链路): 客户端 ──访问域名──> DNS服务(高可用 VIP .200, BIND主从) ──解析出域名的IP──> 请求 Web VIP .100 │ ▼ LVS Director(只改MAC) ──> 后端(直接回包,不经过Director) ↑ 两层都要高可用:DNS(.200) + Web(.100);四层、只认IP:端口、一个端口对一个服务 ​ K8s Ingress: 浏览器 ──域名──> Ingress-nginx(按host/path) ──> Service(ClusterIP) ──> Pod ↑ 七层、按域名+路径路由、一个入口多个服务

一个端口多个服务怎么做到的?传统 LVS 是"一个端口 = 一个 virtual_server",想暴露 3 个服务得开 3 个端口;K8s 是一个 ingress-nginx 入口(80),靠 Ingress 里不同的host域名把流量分给不同的 Service——这是我这次体会最深的一点。

4.2 高可用机制对比(服务挂了怎么办 / 升级怎么更)

场景传统(LVS-DR + Keepalived)K8s 集群版
服务实例挂了健康检查剔除(TCP_CHECK / MISC_CHECK)→ 但后端服务本身还得手动重启 / 脚本重启,不会自动拉起故障自愈:容器挂了 kubelet 自动重启,副本少了控制器自动补,实例没就绪自动从 Endpoints 摘掉
全程高可用靠 Keepalived 主备漂移 VIP + 后端脚本兜底,切换要一点时间,且要看脚本健壮性平台兜底,kubectl apply后自动收敛到期望状态,几乎无需人工
升级 / 发布改配置 + 重启服务,需要宕机一段时间(停机窗)滚动更新:逐个替换旧副本,新副本就绪再删旧副本,全程不中断服务

一句话:传统是"挂了等人修"(手动/脚本重启 + 停机窗),K8s 是"挂了平台自愈"(自动重启 + 滚动更新不停机)。

4.3 配置方式对比

传统K8s
/etc/keepalived/keepalived.conf+ipvsadm-save写 yaml +kubectl apply
改完keepalived -t校验 +systemctl restartapply 后控制器自动收敛,无需手动重启
加节点要改 keepalived.conf 加 real_server加副本改replicas,或打 label 扩节点
配置错误可能整台 Direct 起不来(坑)yaml 有 schema 校验 + dry-run,错在 apply 前就能发现

五、踩坑记录(两个时代都踩过,都值得记)

5.1 传统 LVS 时代的坑

现象根因解决
VRID 冲突keepalived 启动失败退出码 2两个vrrp_instance用了同一个virtual_router_id 51改成 52,同接口 VRID 必须唯一
关键字拼写keepalived -tUnknown keywordtrack_scrip应为track_scriptTCP_CHECK里没有nb_get_retry,应是retryMISC_CHECK里应是misc_timeout逐个纠正关键字
后端同时绑 VIP 会不会脑裂web01/web02 的 lo:0 都有 .100DR 模式后端绑 VIP 是正常的(为了收包),只要 ARP 抑制对就不会脑裂判断标准:客户端arp -n看 VIP 的 MAC 必须指向 Director
负载均衡 vs 高可用混为一谈以为开 ipvs 就能外部访问ClusterIP/NodePort 是"对内/外部入口",和 lb_algo 负载均衡是两回事拆开看:LVS 管"转发+负载均衡",Keepalived 管"入口 VIP + 主备"

5.2 K8s 版本的坑

现象根因解决
镜像拉取registry.k8s.iopod ImagePullBackOffregistry.k8s.io的 manifest 被 307 重定向到 google 欧洲 GCP 存储,国内连不通走 daocloud 代理站k8s.m.daocloud.io,或推到本地 harbor 再用
helm 改子 chart 镜像改了地址 pod 还是拉旧的kubeStateMetrics(大驼峰)不生效,正确是kube-state-metrics(子 chart 名,小写连字符);且registryrepository要拆开,否则拼成registry.k8s.io/hb.reg.com/...kube-state-metrics.image.registry=hb.reg.com+repository=k8s/kube-state-metrics
Ingress 域名不匹配访问不到 / 跳到别的页面浏览器敲的域名必须和 Ingress 里rules.host完全一致;hosts 里chengke.com.k8s.local两套域名体系别混用(chengke.com和 LVS 时代那台 .91 冲突)统一用*.k8s.local,hosts 加192.168.211.220 x.k8s.local
MetalLB 分配 IP 后 LoadBalancer 仍是 pendingEXTERNAL-IP 一直<pending>集群没有 LoadBalancer 实现;MetalLB 的 IPAddressPool 网段要和节点/Service/Pod 网段避开装 MetalLB,pool 用 192.168.211.220-240,再 apply
Service 的 selector / namespace 不匹配kubectl get endpoints为空selector 没命中 pod,或 Service 和 Deployment 不在一个 ns(跨 ns selector 不生效)查 endpoints,改 selector / 对齐 ns

六、后续展望(还没做,先记一笔,之后单独展开)

现在这套 K8s 监控用的是kube-prometheus-stack自带的告警规则,已经比较完善。后续要加自定义告警规则和更细的监控时,方向大致是:

  • 自定义告警规则:kube-prometheus-stack 用PrometheusRule(CRD)的spec.groups[].rules[]写告警表达式,yaml 里alert:/expr:/for:/labels.severity,配好会自动被 operator 收集。(对应传统时代想在 Prometheus 里加 rule_files 的思路,但这里用 CRD 声明式,不用改配置文件)

  • 给容器加探针:在 Deployment/StatefulSet 的容器上写livenessProbe(存活探针,挂了重启)和readinessProbe(就绪探针,就绪了才接流量),让"自愈"更及时。


七、一句话总结

传统高可用靠"配置文件 + 脚本"把 VIP 漂移 / 负载均衡写死;K8s 靠"声明式对象 + 控制器"让平台把副本、网络、存储、路由按你想要的收敛,是"从手动运维到平台自愈"的升级。四层(LVS)管"转发 + 负载均衡",七层(Ingress)管"域名 + 路径路由",MetalLB 给外部门开锁——三者合作,一个入口进多服务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询