上一篇 下一篇 分享链接 返回 返回顶部

香港服务器如何通过 Kubernetes 统一管理多操作系统节点,解决跨平台调度难题?

发布人:Minchunlin 发布时间:2025-08-21 09:36 阅读量:677


夜里 1:40,香港荃湾机房的冷风从地板孔吹上来,脚底发凉,同事Zabbix 刚推了个告警:新上架的 Windows 节点没有按期接管 .NET 任务,Linux 节点上排队长度飙到 312。屏幕上是被拒的 Pod 事件:node(s) didn't match node selector。

我抬头看了眼机柜 18U 的蓝色标签,心里骂了一句:跨操作系统调度,还是得用体系化的办法来治,不是补补 YAML 就能好。于是,我把咖啡放到 PDU 上,开始把这件事一次性做对——用 Kubernetes 把 Linux(CentOS 7 / Ubuntu 22.04) 和 Windows Server 2019/2022 纳入同一个集群,做“跨平台有序调度”。

目标与边界

目标:在同一 K8s 集群中,统一纳管 Linux 与 Windows 节点,保证跨平台工作负载(.NET、Java、Go、前端 Node 等)按策略稳定调度,网络、存储、监控、CI/CD 统一。

边界:

  • 使用 kubeadm 初始化控制面。
  • CNI 选用 Calico(VXLAN 模式 + Windows 支持)。
  • CRI 使用 containerd(Linux/Windows 统一)。
  • 存储:Linux 侧 Longhorn(或 Ceph RBD),Windows 侧 SMB CSI,对业务暴露统一的 StorageClass 名称。
  • 机房:香港本地两柜(HK-1/HK-2)25GbE 互联,跨大陆链路 10GbE(高时延,受限 MTU)。

现场硬件与系统版本(真实可落地)

物理与操作系统清单

角色 机柜 机型 CPU 内存 系统盘 数据盘 网卡 操作系统 用途
控制面-1 HK-1 Dell R650xs Xeon Gold 6330 28C 256G 2×480G SATA SSD (RAID1) 2×1.92T NVMe 2×25GbE (Mellanox CX4-Lx) Ubuntu 22.04.4 LTS 控制面/etcd
控制面-2 HK-2 Dell R650xs Xeon Gold 6330 28C 256G 同上 同上 同上 Ubuntu 22.04.4 LTS 控制面/etcd
控制面-3 HK-1 Supermicro 1029U Xeon Gold 5218 16C 192G 同上 同上 Intel X710-DA2 2×10GbE Ubuntu 22.04.4 LTS 控制面/etcd
计算-Linux-A HK-1 Dell R650 Xeon Gold 6330 28C 256G 480G 2×3.84T NVMe 2×25GbE CentOS 7.9 Java/Go/Node
计算-Linux-B HK-2 HPE DL360 G10 Xeon Gold 6248R 24C 256G 480G 2×3.84T NVMe 2×25GbE CentOS 7.9 批处理/数据
计算-Win-A HK-1 HPE DL360 G10 Xeon Gold 6248R 24C 256G 480G 2×1.92T NVMe 2×25GbE Windows Server 2019 Datacenter .NET 旧栈
计算-Win-B HK-2 HPE DL360 G10 Xeon Gold 6248R 24C 256G 480G 2×1.92T NVMe 2×25GbE Windows Server 2022 Datacenter .NET 6/8

网络核心:ToR 交换机 25GbE,上联聚合 100GbE;跨柜延迟 0.120.18ms;跨大陆专线 3555ms。VLAN 隔离业务/管理/存储 3 个平面。

网络与版本矩阵

Kubernetes / CNI / CRI 兼容表(实测)

组件 版本 Linux Windows
Kubernetes v1.28.7(示例) ✔️ ✔️(Win 2019/2022 受支持)
Calico v3.26+ ✔️(VXLAN, BGP) ✔️(Windows 代理,VXLAN)
containerd 1.7.x ✔️ ✔️
kube-proxy 同 K8s ipvs winkernel
CoreDNS 1.10+ ✔️ ✔️

选择 VXLAN:香港多运营商混合网络,三层路由可控性差,VXLAN 封装更稳。

MTU:实测跨运营商链路有效 MTU 1472,VXLAN 叠加后建议 CalicoMTU=1450。

规划参数(强烈建议先落纸)

ClusterName: hk-prod
PodCIDR:     10.244.0.0/16
ServiceCIDR: 10.96.0.0/12
DNSDomain:   cluster.local
CalicoEncap: VXLAN, MTU=1450
Zones:       hk-1, hk-2
Node Labels:
  kubernetes.io/os: linux | windows
  topology.kubernetes.io/zone: hk-1 | hk-2
  node.os.distro: centos7 | ubuntu22 | win2019 | win2022
Taints (Windows):
  node-role.kubernetes.io/windows:NoSchedule

部署步骤(一步步复刻)

1)控制面初始化(Ubuntu 22.04)

containerd 配置(systemd cgroup)

sudo apt-get update && sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl enable --now containerd

kubeadm 配置文件

# kubeadm-controlplane.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.7
clusterName: hk-prod
networking:
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
  dnsDomain: cluster.local
controllerManager:
  extraArgs:
    node-cidr-mask-size: "24"
apiServer:
  extraArgs:
    service-node-port-range: "30000-32767"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
  kubeletExtraArgs:
    cgroup-driver: "systemd"

sudo kubeadm init --config kubeadm-controlplane.yaml
mkdir -p ~/.kube && sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config && sudo chown $(id -u):$(id -g) ~/.kube/config

2)安装 Calico(Linux 先行)

先部署 Linux 侧 Calico,再加 Windows 组件。

# 下载官方 manifest 后,按需改 MTU=1450 与 VXLAN
kubectl apply -f calico.yaml
kubectl -n kube-system set env ds/calico-node FELIX_VXLANMTU=1450
kubectl -n kube-system set env ds/calico-node FELIX_VXLANENABLED=true

为 Windows 加载 Calico 组件(简化示例)

kubectl apply -f https://docs.projectcalico.org/manifests/calico-windows.yaml
# 或将 windows-only 组件拆分成单独清单,便于灰度

3)加入 Linux 工作节点(CentOS 7)

内核与网络准备

swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab
yum install -y containerd iptables-services
systemctl enable --now iptables

# containerd
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd

# 关闭 firewalld或切换为 iptables-legacy(CentOS7 默认 iptables)
modprobe br_netfilter
cat <<EOF >/etc/sysctl.d/99-k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.ipv4.ip_forward=1
EOF
sysctl --system

kubelet/kubeadm 安装并加入

kubeadm join <API_SERVER_IP>:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --node-name linux-a \
  --cri-socket /run/containerd/containerd.sock

4)加入 Windows 工作节点(2019/2022)

在 Windows PowerShell(提升权限):

# 开启容器功能
Enable-WindowsOptionalFeature -Online -FeatureName containers -All
# 可选:启用 Hyper-V 以支持 Hyper-V 隔离
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

# 安装 containerd
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest https://github.com/containerd/containerd/releases/download/v1.7.18/containerd-1.7.18-windows-amd64.tar.gz -OutFile c:\containerd.tar.gz
mkdir C:\containerd; tar -xzf c:\containerd.tar.gz -C C:\containerd
c:\containerd\containerd.exe --register-service
Start-Service containerd

# CNI (Calico Windows 会包含 CNI,可统一到 C:\cni\bin)
mkdir C:\cni\bin

# kubelet/kubeadm/kubectl
choco install -y kubernetes-cli --version=1.28.7
choco install -y kubernetes-kubelet --version=1.28.7
choco install -y kubernetes-kubeadm --version=1.28.7

# HNS 清理(如反复测试)
Get-HnsNetwork | Remove-HnsNetwork -Force

# kubeadm join(容器运行时指定)
kubeadm.exe join <API_SERVER_IP>:6443 --token <token> `
  --discovery-token-ca-cert-hash sha256:<hash> `
  --node-name win-a `
  --cri-socket npipe:////./pipe/containerd-containerd

注意:Windows 节点加入后会自动打上 kubernetes.io/os=windows。建议我们再补充自定义标签与污点,确保非 Windows 工作负载不会误落:

kubectl label nodes win-a topology.kubernetes.io/zone=hk-1 node.os.distro=win2019
kubectl taint nodes win-a node-role.kubernetes.io/windows=true:NoSchedule

存储统一:Linux/Windows 两套实现,一个名字暴露

方案对比与选择

诉求 Linux Windows
有状态数据库/队列 Longhorn / Ceph RBD(RWO) 不建议(缺驱动/成熟度),改为跨区主从或容器外托管
共享文件(RWX) NFS/Longhorn RWX SMB CSI
容器临时盘 emptyDir + local NVMe same

StorageClass 与 PVC(统一名称 shared-files)

Windows:SMB CSI

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: shared-files
provisioner: smb.csi.k8s.io
parameters:
  source: "\\\\10.10.10.20\\k8s-share"
  csi.storage.k8s.io/node-publish-secret-name: smb-cred
  csi.storage.k8s.io/node-publish-secret-namespace: kube-system
reclaimPolicy: Retain
mountOptions:
  - dir_mode=0777
  - file_mode=0777
  - vers=3.0

Linux:NFS(与上面同名备用,按命名空间挑选)

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: shared-files
provisioner: nfs.csi.k8s.io
parameters:
  server: 10.10.10.21
  share: /export/k8s-share
reclaimPolicy: Retain
mountOptions:
  - nfsvers=4.1

实践里我们按命名空间拆分:win-* 命名空间默认指向 SMB 的 shared-files;linux-* 默认指向 NFS 的 shared-files。名字统一,使用方无感。

跨平台调度:标签、污点、亲和性一次到位

统一标签/污点初始化脚本(可直接跑)

#!/usr/bin/env bash
set -euo pipefail
for n in $(kubectl get nodes -o name | sed 's#node/##'); do
  os=$(kubectl get node "$n" -o jsonpath='{.status.nodeInfo.operatingSystem}')
  zone=$(kubectl get node "$n" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
  [[ -z "$zone" ]] && kubectl label node "$n" topology.kubernetes.io/zone=hk-1 --overwrite
  if [[ "$os" == "windows" ]]; then
    kubectl taint node "$n" node-role.kubernetes.io/windows=true:NoSchedule --overwrite
  else
    kubectl label node "$n" node.os.distro=centos7 --overwrite || true
  fi
done

跨平台 Deployment 示例(同一业务,两套容器)

关键点:

1)使用 topologySpreadConstraints 保证跨柜均衡。
2)用 nodeAffinity / nodeSelector 指定 OS。
3)Windows 节点有污点,工作负载需显式容忍。

.NET on Windows

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-win
  namespace: win-prod
spec:
  replicas: 4
  selector:
    matchLabels: app: api-win
  template:
    metadata:
      labels:
        app: api-win
    spec:
      tolerations:
        - key: "node-role.kubernetes.io/windows"
          operator: "Exists"
          effect: "NoSchedule"
      nodeSelector:
        kubernetes.io/os: windows
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: topology.kubernetes.io/zone
                    operator: In
                    values: ["hk-1","hk-2"]
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-win
      containers:
        - name: api
          image: mcr.microsoft.com/dotnet/aspnet:6.0
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: shared
              mountPath: C:\data
      volumes:
        - name: shared
          persistentVolumeClaim:
            claimName: shared-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-pvc
  namespace: win-prod
spec:
  accessModes: ["ReadWriteMany"]
  storageClassName: shared-files
  resources:
    requests:
      storage: 100Gi

Java on Linux

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-linux
  namespace: linux-prod
spec:
  replicas: 6
  selector:
    matchLabels: app: api-linux
  template:
    metadata:
      labels:
        app: api-linux
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: node.os.distro
                    operator: In
                    values: ["centos7","ubuntu22"]
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-linux
      containers:
        - name: api
          image: eclipse-temurin:17-jre
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
            limits:
              cpu: "2"
              memory: "2Gi"

日志与监控:一套栈,两端采集

指标:Prometheus Operator +

  • Linux:node-exporter(DaemonSet, kubernetes.io/os=linux)
  • Windows:windows-exporter(DaemonSet, kubernetes.io/os=windows,HostProcess 模式)

日志:Fluent Bit

  • Linux:tail /var/log/containers/*.log
  • Windows:读取 C:\var\log\containers\*.log 或 ETW,经 Sidecar 转发

Windows HostProcess DaemonSet 样例

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: windows-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels: app: windows-exporter
  template:
    metadata:
      labels:
        app: windows-exporter
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      hostNetwork: true
      containers:
        - name: exporter
          image: ghcr.io/prometheus-community/windows-exporter:0.25.1
          args: ["--telemetry.addr=:9182"]
      securityContext:
        windowsOptions:
          hostProcess: true
          runAsUserName: "NT AUTHORITY\\SYSTEM"

典型坑与当场修复记录(机房里真遇到的)

CentOS 7 cgroup 驱动不匹配

现象:cgroup driver mismatch: cgroupfs vs systemd,kubelet 报错。

解决:统一 containerd SystemdCgroup=true,kubeletExtraArgs: cgroup-driver=systemd,重启后恢复。

VXLAN MTU 错配导致跨柜丢包

现象:跨柜流量偶发丢包,抓包显示 frag needed。

解决:将 FELIX_VXLANMTU=1450,并在 Pod 启动参数里避免强制大包(如 gRPC max_receive_message_length 合理设置)。

Windows kube-proxy 端口冲突(NodePort vs Ephemeral)

现象:暴露 NodePort 服务后,Periodically 无法访问。

诊断:netsh int ipv4 show dynamicport tcp 显示动态端口范围覆盖到 30000+。

解决:收紧动态端口,或调整 NodePort 范围。

netsh int ipv4 set dynamicport tcp start=10000 num=20000
netsh int ipv4 set dynamicport udp start=10000 num=20000

同时确保 service-node-port-range: "30000-32767"。

Windows DNS 解析异常

现象:Windows Pod 偶发 Name does not exist。

根因:HNS 网络创建过早,DNS Suffix 未继承。

解决:在 Calico Windows 清单中显式设置 KUBE_DNS_NAME= kube-dns.kube-system.svc.cluster.local,并确保 kubelet --cluster-dns 与 --cluster-domain 一致。

SMB 凭据轮换

现象:月度轮换后,Windows 容器挂载失败。

解决:凭据以 Secret 挂载,改为 短期 Secret + Deployment 滚动,并对持久挂载的 StatefulSet 设置 podManagementPolicy: Parallel,缩短恢复时间。

跨大陆链路时延引起的镜像拉取超时

解决:在香港落地 Harbor 镜像仓库,所有节点优先从本地仓拉取;给 containerd 配置镜像加速与 Registry 镜像。

Windows HostProcess 权限

现象:Windows DaemonSet 无法触达系统指标。

解决:必须 windowsOptions.hostProcess=true 且 runAsUserName=NT AUTHORITY\SYSTEM,并打开 hostNetwork。

压测与观测(上线前我们做了什么)

项目 变更前 变更后 备注
跨平台交付时长(从 Issue 到上线) 3~5 天 1 天内 CI/CD + 模板化部署
调度拒绝率(因 OS 不匹配) 2.3% 0.1% 统一标签+污点
跨柜流量丢包(p99) 0.05% <0.005% MTU 调优
.NET API P99 延迟 210ms 168ms 本地镜像仓+Node 亲和
故障恢复(Windows 节点重启) 20~30 分钟 8~12 分钟 DaemonSet/Sidecar 优化

CI/CD 与配置治理

模板化:用 Helm/Kustomize 两套 overlay:overlay/linux 与 overlay/windows。

准入控制:可用 OPA Gatekeeper 编写策略,禁止未声明 kubernetes.io/os 的工作负载进入生产命名空间;Windows 命名空间强制容忍污点。

命名空间隔离:win-*/linux-* 前缀策略 + 资源配额 + LimitRange。

示例 Gatekeeper 约束(禁止未声明 OS 的 Pod):

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredNodeSelector
metadata:
  name: require-os-selector
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces: ["prod", "win-prod", "linux-prod"]
  parameters:
    selectors:
      - key: "kubernetes.io/os"
        allowedRegex: "linux|windows"

运行手册片段(遇到问题先敲它)

查看节点 OS 与标签

kubectl get nodes -L kubernetes.io/os -L topology.kubernetes.io/zone -L node.os.distro

检查被拒 Pod 原因

kubectl describe pod <pod> | egrep -i "taint|NoSchedule|node affinity|didn't match"

Windows HNS 网络

Get-HnsNetwork; Get-HnsEndpoint

调整 Calico MTU(滚动)

kubectl -n kube-system set env ds/calico-node FELIX_VXLANMTU=1450

强制在某区调度

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
      - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values: ["hk-2"]

成本与收益(给老板看的两行字)

一次性投入:Windows 节点容器化适配(1~2 人周),CI/CD overlay、监控/日志加固(1 人周)。

长期收益:交付周期缩短 50%+;夜间故障恢复时间下降 40%+;人力值班压力明显下降。

FAQ:几个常见“坑点”的取舍

为什么不用单独 Windows 集群?
我们试过,运维成本翻倍、网络与权限策略难以统一。混部 + 策略治理成本更优。

数据库能跑在 Windows 容器里吗?
理论可行,实践不建议。Linux 存储生态成熟,主从/集群架构选择更多。

CNI 选 Flannel 行不行?
Flannel Windows 支持相对有限,Calico Windows 在策略与可观测性上明显更完整。

总结:凌晨 2:35 的那一刻

Calico 的滚动更新刚完成,新的 Windows 节点成功接管了 .NET 服务,api-win 四个副本在两柜均衡分布,Prometheus 图上红色报警线缓缓回落。
我关掉机房的头灯,屏幕上最后一条事件是 Scheduled。
做运维这些年我越来越笃定:跨平台不是靠人肉记忆,是靠“标签、污点、亲和、网络、存储、监控”一整套工程化作业。Kubernetes 不是银弹,但它给了我们把混乱变成秩序的锤子。
走出机房时,清晨 3 点的荃湾街口还有两家便利店开着。我买了罐冰可乐,心想:这次,终于能睡个踏实觉了。

附:可直接复用的清单与命令(归档版)

  • kubeadm-controlplane.yaml(见上)
  • Calico:Linux + Windows(记得 MTU=1450,VXLAN)
  • Windows:Enable-WindowsOptionalFeature、containerd 安装、kubeadm join(npipe 套接字)
  • 统一标签/污点脚本(见上)
  • Deployment 样例:api-win / api-linux
  • SMB/NFS StorageClass(同名 shared-files)
  • HostProcess DaemonSet(windows-exporter)
  • netsh 动态端口收紧命令

如果你也在香港、或任一多运营商、跨柜、跨 OS 的环境里奋战,希望这份“深夜机房笔记”能让你少走几步弯路。祝你每个滚动更新都平安、每次集群扩容都顺利。

目录结构
全文