内网 K8s MetalLB + Traefik


在私有网络中部署k8s,没有公网云中统一的GW,所以需要规范使用统一网关来进行业务发布。

整体架构

内网客户端 → MetalLB 分配固定内网 LBIP → Traefik 网关 → 业务 ClusterIP Service → Pod 业务全程只使用 ClusterIP,无 NodePort 暴露。

前置条件

  1. 裸机 / 虚拟机自建 K8s(无云厂商 LB)
  2. 一段内网空闲 IP 段(如192.168.3.200-192.168.3.220,和集群同网段、未被 DHCP 占用)
  3. 集群内核转发开启、防火墙放开 80/443 端口
  4. kubectl 正常连通集群

一、部署 MetalLB v0.15(L2 二层模式,内网首选)

步骤 1:

修改 kube-proxy 开启 strictARP(必做,否则 MetalLB ARP 异常)

kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl apply -f -
# 重启kube-proxy生效
kubectl rollout restart daemonset kube-proxy -n kube-system
kubectl get daemonset kube-proxy -n kube-system

步骤 2:

一键部署 MetalLB 官方原生 yaml

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml

等待 Pod 全部 Running:

kubectl get pods -n metallb-system -w

预期 2 类 Pod:

  • controller:1 个 Deployment
  • speaker:每个节点 1 个 DaemonSet

步骤 3:

配置 IP 地址池 + L2 二层通告

新建文件 metallb-config.yaml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: lan-lb-pool
  namespace: metallb-system
spec:
  addresses:
    # 替换为你内网空闲IP段,不要和网关/服务器DHCP冲突
    - 192.168.3.200-192.168.3.220
  autoAssign: true
  avoidBuggyIPs: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: lan-l2-advertise
  namespace: metallb-system
spec:
  ipAddressPools:
    - lan-lb-pool

应用配置:

kubectl apply -f metallb-config.yaml
# 查看地址池
kubectl get IPAddressPool -n metallb-system

二、部署 Traefik v2.11(完整 RBAC+CRD+LoadBalancer Service)

步骤 1:

创建 traefik 命名空间

kubectl create ns traefik

步骤 2:

完整 Traefik 部署 yaml traefik-full.yaml

包含:ServiceAccount、ClusterRole、ClusterRoleBinding、Deployment、Service (LoadBalancer)、IngressRoute CRD

# 1. ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik
  namespace: traefik
---
# 2. ClusterRole 权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups: [""]
    resources: ["services", "endpoints", "secrets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses", "ingressclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses/status"]
    verbs: ["update"]
  - apiGroups: ["traefik.containo.us"]
    resources: ["ingressroutes", "ingressroutetcps", "middlewares", "tlsoptions"]
    verbs: ["*"]
---
# 3. 绑定权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik
    namespace: traefik
---
# 4. Traefik Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
  namespace: traefik
spec:
  replicas: 2 # 高可用部署2副本
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik
      containers:
        - name: traefik
          image: traefik:v2.11
          args:
            # 开启API面板
            - --api.insecure=true
            # 监听k8s标准Ingress
            - --providers.kubernetesingress
            # 监听Traefik自定义IngressRoute CRD
            - --providers.kubernetescrd
            # HTTP入口
            - --entrypoints.web.address=:80
            # HTTPS入口
            - --entrypoints.websecure.address=:443
          ports:
            - name: web
              containerPort: 80
            - name: websecure
              containerPort: 443
            - name: dashboard
              containerPort: 8080
---
# 5. 关键:LoadBalancer Service,由MetalLB分配内网固定IP
apiVersion: v1
kind: Service
metadata:
  name: traefik-lb
  namespace: traefik
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
      name: web
    - port: 443
      targetPort: 443
      name: websecure
  selector:
    app: traefik

步骤 3:应用 Traefik 全部资源

kubectl apply -f traefik-full.yaml
# 等待Pod就绪
kubectl get pods -n traefik -w

步骤 4:查看 Traefik 分配的内网 LB 固定 IP

kubectl get svc traefik-lb -n traefik

输出示例:

NAME         TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)        AGE
traefik-lb   LoadBalancer   10.100.120.100  192.168.3.200     80:30000/TCP,443:30001/TCP   2m

EXTERNAL-IP=192.168.3.200 就是内网统一网关入口 IP,全程不用 NodePort 访问业务

三、配置 Traefik Dashboard(内网可视化面板)

新建 traefik-dashboard.yaml

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: traefik
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.lan`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService

应用:

kubectl apply -f traefik-dashboard.yaml -n traefik

内网客户端配置 hosts:

192.168.3.200 traefik.lan

浏览器访问 http://traefik.lan/dashboard 打开 Traefik 管理面板。

四、部署测试业务(仅 ClusterIP,无 NodePort)

1. 业务 yaml demo-whoami.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami
          ports:
            - containerPort: 80
---
# 仅ClusterIP,不使用NodePort
apiVersion: v1
kind: Service
metadata:
  name: whoami-svc
spec:
  selector:
    app: whoami
  ports:
    - port: 80
      targetPort: 80
  type: ClusterIP
kubectl apply -f demo-whoami.yaml

2. Traefik 路由 IngressRoute

新建 whoami-route.yaml

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami-web
  namespace: default
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`demo.lan`)
      kind: Rule
      services:
        - name: whoami-svc
          port: 80
kubectl apply -f whoami-route.yaml

五、内网客户端访问测试

  1. 客户端 hosts 添加解析(小规模内网方案)
192.168.3.200 demo.lan traefik.lan
  1. curl / 浏览器访问
curl http://demo.lan

流量路径:客户端 → MetalLB 固定 IP → Traefik 网关 → ClusterIP Service → Pod,无任何 NodePort 暴露业务

六、常用排错命令

MetalLB 排查

# 查看地址池分配情况
kubectl describe IPAddressPool -n metallb-system lan-lb-pool
# 查看speaker日志(ARP分配IP日志)
kubectl logs -f daemonset/speaker -n metallb-system
# 查看controller日志
kubectl logs -f deployment/controller -n metallb-system

Traefik 排查

# Traefik容器日志
kubectl logs -f deployment/traefik -n traefik
# 查看IngressRoute路由规则
kubectl get IngressRoute
# 查看Traefik LB IP
kubectl get svc traefik-lb -n traefik
# 检查业务service是否正常
kubectl get svc whoami-svc

七、关键注意事项

  1. IP 段规划:MetalLB 地址池不能和 DHCP、服务器静态 IP 重叠,否则 IP 冲突;
  2. strictARP 必须开启,否则多节点 ARP 转发异常,访问时断时续;
  3. 业务 Service 统一type: ClusterIP,禁止 NodePort;
  4. 大规模内网:搭建内网 CoreDNS 统一解析域名,不用手动改 hosts;
  5. 高可用:Traefik 副本≥2,pod 反亲和分散到不同节点;
  6. 防火墙放开节点 80、443、7946(MetalLB speaker 通信端口)。

八、可选:Helm 一键安装 Traefik

如果你习惯 helm,替代上面 yaml 部署 Traefik:

helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik -n traefik --create-namespace \
--set service.type=LoadBalancer \
--set ports.web.port=80 \
--set ports.websecure.port=443 \
--set dashboard.enabled=true \
--set dashboard.insecure=true

安装后同样通过kubectl get svc traefik -n traefik获取 MetalLB 分配的内网 LB IP。