kube-vip 部署指南(control-plane VIP,仅 Master 节点)

avatar 2026年9月2日18:20:14 评论 5 次浏览

场景:本集群(测试环境 192.168.6.36,k8s v1.29.2)使用 kube-vip 作为 API Server 的高可用入口,VIP 192.168.6.249 绑定 eth0,端口 6443。kube-vip 通过 ARP + Leader Election 在 master 节点间漂移 VIP,任意一台 master 宕机 VIP 自动切换。

部署范围:5 台 master(k8s-master / master01 / master02 / master03 / master04)。worker 节点不部署,它们不提供 API 入口。


一、两种部署方式选型

DaemonSet 方式

  • 支持 imagePullSecrets(普通 pod,无限制)

  • kubectl 完全可管理,支持滚动更新

  • 新节点加入自动调度

  • 依赖 API Server 可用(控制器需要调度)

  • 适合集群运行稳定后的管理场景

Static Pod 方式

  • 不支持 imagePullSecrets(mirror pod 被 API Server 拒绝,详见排障一节)

  • 不依赖 API Server,kubelet 直接读 manifests 文件拉起

  • 适合集群 bootstrap 阶段,或 API 不可用时兜底

  • 每台 master 手动放文件,新节点要手动补

结论:集群已经在运行、API 可用,用 DaemonSet 最省心。如果是"从零搭集群、API 还没起来"的场景,用 Static Pod。


二、DaemonSet 方式部署(推荐)

1. 确认镜像

crictl images | grep kube-vip
# 期望输出 harbor.wulaoer.org/ops/kube-vip  v0.6.2

镜像和 secret 依赖 harbor,如果 harbor 密码轮换过,先确认 kube-system 的 eft-image-auth 凭据是否仍有效(见排障一节)。

2. manifest 文件

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-vip
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: kube-vip
  template:
    metadata:
      labels:
        app.kubernetes.io/name: kube-vip
    spec:
      # 关键:只调度到 control-plane 节点,worker 不会跑
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role.kubernetes.io/control-plane
                operator: Exists
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      containers:
      - name: kube-vip
        image: harbor.wulaoer.org/ops/kube-vip:v0.6.2
        imagePullPolicy: IfNotPresent
        imagePullSecrets:
        - name: eft-image-auth
        env:
        - name: vip_arp
          value: "true"
        - name: port
          value: "6443"
        - name: vip_interface
          value: eth0
        - name: vip_cidr
          value: "32"
        - name: cp_enable
          value: "true"
        - name: cp_namespace
          value: kube-system
        - name: svc_enable
          value: "true"
        - name: vip_leaderelection
          value: "true"
        - name: vip_leasename
          value: plndr-cp-lock
        - name: vip_leaseduration
          value: "5"
        - name: vip_renewdeadline
          value: "3"
        - name: vip_retryperiod
          value: "1"
        - name: vip_address
          value: 192.168.6.249
        - name: prometheus_server
          value: :2112
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
            - NET_RAW
        volumeMounts:
        - mountPath: /etc/kubernetes/admin.conf
          name: kubeconfig
      hostNetwork: true
      volumes:
      - hostPath:
          path: /etc/kubernetes/admin.conf
          name: kubeconfig

几个要点:

  • nodeAffinity 限制 node-role.kubernetes.io/control-plane,DaemonSet 只会调度到 master

  • tolerations 容忍 master 的 NoSchedule 污点,否则调度不上去

  • hostNetwork 必须为 true,否则 VIP 绑不到宿主机网卡

  • NET_ADMIN / NET_RAW 权限必须有,否则无法操作网卡和 ARP

  • imagePullSecrets 在 DaemonSet 里合法,直接用 kube-system 的 eft-image-auth

3. 应用

kubectl apply -f kube-vip-ds.yaml

4. 验证

# 确认只出现在 master 上,共 5 个,worker 上不应有
kubectl get pods -n kube-system -o wide | grep kube-vip

# 确认全部 Running
kubectl get pods -n kube-system | grep kube-vip

# 查看当前 leader(谁持有 VIP)
kubectl get lease -n kube-system plndr-cp-lock -o jsonpath='{.spec.holderIdentity}'

# 测试 VIP 入口
curl -k https://192.168.6.249:6443/healthz

三、Static Pod 方式(备选)

1. manifest 文件

放到每台 master 的 /etc/kubernetes/manifests/kube-vip.yaml,kubelet 自动拉起:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: kube-vip
  namespace: kube-system
spec:
  containers:
  - args:
    - manager
    env:
    - name: vip_arp
      value: "true"
    - name: port
      value: "6443"
    - name: vip_interface
      value: eth0
    - name: vip_cidr
      value: "32"
    - name: cp_enable
      value: "true"
    - name: cp_namespace
      value: kube-system
    - name: svc_enable
      value: "true"
    - name: vip_leaderelection
      value: "true"
    - name: vip_leasename
      value: plndr-cp-lock
    - name: vip_leaseduration
      value: "5"
    - name: vip_renewdeadline
      value: "3"
    - name: vip_retryperiod
      value: "1"
    - name: vip_address
      value: 192.168.6.249
    - name: prometheus_server
      value: :2112
    image: harbor.wulaoer.org/ops/kube-vip:v0.6.2
    imagePullPolicy: IfNotPresent
    name: kube-vip
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
        - NET_RAW
    volumeMounts:
    - mountPath: /etc/kubernetes/admin.conf
      name: kubeconfig
  hostNetwork: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/admin.conf
    name: kubeconfig

注意:Static Pod 的 yaml 里不能写 imagePullSecrets,写了会导致 mirror pod 创建失败,kubectl 看不到这个 pod(详见排障一节)。

2. 分发脚本

for ip in 192.168.6.36 192.168.6.41 192.168.6.42 192.168.6.236 192.168.6.237; do
  scp /etc/kubernetes/manifests/kube-vip.yaml root@$ip:/etc/kubernetes/manifests/kube-vip.yaml
done

四、两种方式互相迁移

Static Pod 迁到 DaemonSet

  1. 先 apply DaemonSet,等 pod 起来

  2. 两者共用同一个 lease(plndr-cp-lock),leader election 互斥,不会出现双 VIP

  3. 确认 DaemonSet 正常后,删除所有 master 的 manifests 文件

  4. 确认最终只剩 DaemonSet 的 5 个 pod

kubectl apply -f kube-vip-ds.yaml
# 验证 Running 和 VIP 正常后:
for ip in 192.168.6.36 192.168.6.41 192.168.6.42 192.168.6.236 192.168.6.237; do
  ssh root@$ip "rm -f /etc/kubernetes/manifests/kube-vip.yaml"
done

DaemonSet 迁到 Static Pod

顺序反过来:先放 static pod 文件到所有 master,验证正常后再 kubectl delete ds。

迁移期间两套并存是安全的,但不要长期并存。


五、排障记录(2026-08-28 事故复盘)

1. 镜像拉取 401 Unauthorized

现象:kubelet 报 Failed to pull image "harbor.wulaoer.org/ops/kube-vip:v0.6.2" ... 401 Unauthorized。

原因:harbor 密码被轮换,k8s 里的 eft-image-auth secret 没有同步更新。

排查:

kubectl get secret eft-image-auth -n kube-system -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d
kubectl get secret -A | grep eft-image-auth

修复:更新各 namespace 的 secret;临时应急可以节点本地预置镜像 + imagePullPolicy 改 IfNotPresent。

2. kubectl 看不到 kube-vip 的 pod(Static Pod 方式)

现象:kubelet 日志持续报 Failed creating a mirror pod ... forbidden: a mirror pod may not reference secrets。

原因:static pod 的 yaml 里写了 imagePullSecrets。Kubernetes 不允许 mirror pod(static pod 在 API Server 上的影子对象)引用 secret,这是安全限制,不是 bug。

注意:这个限制只影响"kubectl 能不能看到",不影响本地容器运行。容器真正起不来是 401 拉不到镜像导致的。

修复:删除 yaml 里的 imagePullSecrets 块,或改用 DaemonSet 方式。

3. 改了 IfNotPresent 还是拉镜像失败

现象:imagePullPolicy 已改 IfNotPresent,仍然报 401 拉取失败。

原因:该节点 containerd 本地没有 harbor tag 的镜像缓存。本次事故中 master04 的 kube-vip.yaml 是新加的,从未成功拉过 harbor 镜像,本地只有 ghcr.io 的 tag,IfNotPresent 找不到本地镜像才会去拉。

排查和修复:

# 看本地有哪些 tag
crictl images | grep kube-vip

# 本地有同 digest 的 ghcr 镜像时,直接打 tag 复用
ctr -n k8s.io images tag ghcr.io/kube-vip/kube-vip:v0.6.2 harbor.wulaoer.org/ops/kube-vip:v0.6.2

# 或从其他节点导出导入
# ctr -n k8s.io images export /tmp/kube-vip.tar harbor.wulaoer.org/ops/kube-vip:v0.6.2
# ctr -n k8s.io images import /tmp/kube-vip.tar

4. 节点 NotReady

现象:节点 Ready 条件变 Unknown,Kubelet stopped posting node status。

排查:先 ping / SSH 节点本身,确认是节点宕机、网络断还是 kubelet 故障,与 kube-vip 无关时单独处理。

5. 本集群其他待办隐患

  • containerd 1.6.21 与 1.6.33 混用 + CentOS7 内核,存在 netns 删除失败(remove netns: unlinkat ... device or resource busy),导致 pod 卡 Terminating

  • k8s-node18 上有 3 个长期 RunContainerError 的 pod

  • control-plane 组件(kube-apiserver / kube-controller-manager / calico-node)存在频繁重启记录,建议单独排查


六、速查命令

# 部署(DaemonSet)
kubectl apply -f kube-vip-ds.yaml

# 查看分布(应只有 master)
kubectl get pods -n kube-system -o wide | grep kube-vip

# 查看 leader
kubectl get lease -n kube-system plndr-cp-lock -o jsonpath='{.spec.holderIdentity}'

# 测试入口
curl -k https://192.168.6.249:6443/healthz

# 本地镜像检查
crictl images | grep kube-vip

# 本地打 tag(镜像缺失时)
ctr -n k8s.io images tag ghcr.io/kube-vip/kube-vip:v0.6.2 harbor.wulaoer.org/ops/kube-vip:v0.6.2

avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: