kubeadm 初始化完成只代表控制平面已经就绪。一个可承载业务的集群,还需要 Pod 网络、指标、动态存储和入口控制器。

系列导航:Kubernetes 重建迁移教程系列。建议先完成“从零搭建 Kubernetes”,再继续本篇。

本文从一个三节点控制平面、若干工作节点的空集群开始。所有镜像和清单必须固定版本并保存到 Git;示例中的 <...> 需要替换。

安装顺序

建议严格按照下面的顺序:

Calico → CoreDNS 验收 → metrics-server → StorageClass → Ingress → 综合冒烟

在 Calico 就绪之前,后续的 Pod 网络都不可信。在存储就绪之前,不要部署任何有状态服务。

第 1 步:核对 Pod CIDR

从 kubeadm 配置和控制器参数中确认 Pod CIDR:

grep -n 'podSubnet' manifests/kubeadm.yaml
kubectl -n kube-system get pod -l component=kube-controller-manager -o yaml \
  | grep -n -- '--cluster-cidr'

再检查 Calico 清单中的地址池:

grep -nE 'CALICO_IPV4POOL_CIDR|cidr:' manifests/calico.yaml

三处必须一致,并且不能与节点网段、Service CIDR、办公网或 VPN 网段重叠。

第 2 步:安装 Calico

先做服务端 dry-run:

kubectl apply --server-side --dry-run=server -f manifests/calico.yaml
kubectl apply -f manifests/calico.yaml

观察核心 Pod:

kubectl -n kube-system get pods -l k8s-app=calico-node -o wide -w
kubectl -n kube-system rollout status daemonset/calico-node --timeout=10m
kubectl -n kube-system rollout status deployment/calico-kube-controllers --timeout=10m
kubectl get nodes

所有节点应为 Ready,每个可调度节点应有一个 calico-node。

若某台节点失败:

kubectl -n kube-system describe pod <calico-pod>
kubectl -n kube-system logs <calico-pod> -c calico-node --tail=200
sudo journalctl -u containerd -u kubelet -n 200 --no-pager

先处理模块、sysctl、路由、MTU 和防火墙,不要靠反复删除 Pod 碰运气。

第 3 步:验证跨节点网络与 DNS

若要创建两个分散到不同节点的测试 Deployment,简单做法是先创建两个 Pod,再用 nodeName 固定到两台测试节点:

apiVersion: v1
kind: Pod
metadata:
  name: net-a
  namespace: default
  labels: { app: net-smoke }
spec:
  nodeName: <worker-a>
  containers:
    - name: web
      image: <approved-nginx-image>
---
apiVersion: v1
kind: Pod
metadata:
  name: net-b
  namespace: default
spec:
  nodeName: <worker-b>
  containers:
    - name: client
      image: <approved-curl-image>
      command: ["sleep", "3600"]

应用后取出 net-a 的 Pod IP,并从 net-b 访问:

kubectl apply -f manifests/smoke/network.yaml
POD_IP="$(kubectl get pod net-a -o jsonpath='{.status.podIP}')"
kubectl exec net-b -- curl -fsS "http://${POD_IP}"

再为 net-a 建立 ClusterIP Service:

kubectl expose pod net-a --name=net-smoke --port=80
kubectl exec net-b -- nslookup net-smoke.default.svc.cluster.local
kubectl exec net-b -- curl -fsS http://net-smoke

这三项分别证明跨节点 Pod 路由、CoreDNS、Service 转发正常。

第 4 步:安装 metrics-server

将固定版本的官方清单保存为 manifests/metrics-server.yaml,镜像同步到内部仓库后再应用:

kubectl apply --server-side --dry-run=server -f manifests/metrics-server.yaml
kubectl apply -f manifests/metrics-server.yaml
kubectl -n kube-system rollout status deployment/metrics-server --timeout=5m

等待聚合 API 可用:

kubectl get apiservice v1beta1.metrics.k8s.io
kubectl top nodes
kubectl top pods -A --sort-by=memory | head

若显示 Metrics API not available:

kubectl describe apiservice v1beta1.metrics.k8s.io
kubectl -n kube-system logs deployment/metrics-server --tail=200

常见原因包括 kubelet 证书 SAN 不匹配、kubelet 端口不通以及地址选择错误。不要把 --kubelet-insecure-tls 当作默认生产方案。

第 5 步:安装动态存储

本文以 NFS 动态制备器为例。先在每台节点上验证存储服务器可达,并确认 NFS 客户端已经安装:

showmount -e <storage-server>

应用经过评审的 RBAC、Deployment 和 StorageClass:

kubectl apply -f manifests/storage/nfs-provisioner.yaml
kubectl -n storage-system rollout status deployment/nfs-provisioner --timeout=5m
kubectl get storageclass

创建一个最小的 PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-smoke
spec:
  storageClassName: <nfs-storage-class>
  accessModes: [ReadWriteMany]
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pvc-smoke
spec:
  containers:
    - name: shell
      image: <approved-busybox-image>
      command: ["sh", "-c", "date > /data/created-at && sleep 3600"]
      volumeMounts:
        - { name: data, mountPath: /data }
  volumes:
    - name: data
      persistentVolumeClaim: { claimName: pvc-smoke }

验证:

kubectl apply -f manifests/smoke/pvc.yaml
kubectl wait --for=condition=Ready pod/pvc-smoke --timeout=5m
kubectl exec pvc-smoke -- cat /data/created-at
kubectl get pvc,pv

只有删除并重建 Pod 后文件仍然存在,才能证明持久化链路正常。

第 6 步:安装 NGINX Ingress

先为入口节点打上标签,并根据设计添加污点:

kubectl label node <ingress-node-a> workload.yj/role=ingress
kubectl label node <ingress-node-b> workload.yj/role=ingress
kubectl label node <ingress-node-c> workload.yj/role=ingress

Ingress Controller 清单中配置对应的 nodeSelector、toleration 和三副本,然后应用:

kubectl apply -f manifests/ingress-nginx/
kubectl -n ingress-nginx rollout status deployment/ingress-nginx-controller --timeout=10m
kubectl -n ingress-nginx get pods -o wide
kubectl get ingressclass

创建一个仅用于内网测试的 Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: net-smoke
spec:
  ingressClassName: nginx
  rules:
    - host: smoke.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: net-smoke
                port: { number: 80 }

无需修改公共 DNS,直接指定入口地址验证:

curl --resolve smoke.example.com:80:<ingress-address> \
  http://smoke.example.com/

第 7 步:清理测试资源并保存报告

kubectl delete -f manifests/smoke/network.yaml
kubectl delete service net-smoke
kubectl delete -f manifests/smoke/pvc.yaml

kubectl get nodes -o wide > reports/nodes.txt
kubectl get pods -A -o wide > reports/pods.txt
kubectl top nodes > reports/node-metrics.txt
kubectl get storageclass,pv,pvc -A > reports/storage.txt
kubectl get ingressclass > reports/ingressclass.txt

完成定义

  • 跨节点 Pod IP 可达。
  • CoreDNS 能解析 Service FQDN。
  • ClusterIP 从不同节点都能访问。
  • kubectl top nodes 和 kubectl top pods 有数据。
  • PVC 可动态绑定,Pod 重建后数据仍存在。
  • Ingress 使用 Host 头能正确路由到测试 Service。
  • 所有清单固定版本、镜像 digest 和回滚版本。

以上验收全部通过后,才适合进入节点池规划和业务迁移。