Skip to content

Kubernetes 集群搭建指南

基于 Dell R730 PVE 虚拟化环境,使用 kubeadm 部署 K8s 集群

版本:K8s v1.34 | 容器运行时:containerd | CNI:Calico | 存储:Local Path Provisioner


一、准备工作

1.1 环境概述

项目说明
硬件平台Dell R730 (PVE 9.1)
K8s 版本v1.34
节点数量1 Master + 2 Worker
网络插件Calico
容器运行时containerd
存储类型NFS (nfs-subdir-external-provisioner)

1.1.1 整体架构图

1.2 节点信息

节点角色主机名vCPU内存系统盘数据盘内网 IP用途
Masterk8s-master64GB32GB64GB192.168.1.165控制平面
Worker-1k8s-worker-186GB32GB100GB192.168.1.166工作节点

1.3 前置要求

操作系统要求

  • Ubuntu 24.04 LTS ✅ (当前环境)
  • Ubuntu 22.04 LTS
  • 最低配置:2核 2GB

系统配置

bash
# 关闭 swap
sudo swapoff -a && sudo sed -i '/ swap / s/^\(.*\)$/#\1/' /etc/fstab

# 加载内核模块
sudo modprobe overlay && sudo modprobe br_netfilter

# 配置 sysctl
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

1.4 网络规划

物理网络 (PVE 网桥 vmbr0)

IP 地址主机名用途
192.168.1.161easytier-gatewayEasyTier 网关
192.168.1.162monitor-storage监控 + NFS 存储
192.168.1.165k8s-master控制平面 / API Server
192.168.1.166k8s-worker-1工作节点

网段:192.168.1.0/24

Kubernetes Pod 网络 (Calico CNI)

网段用途分配给
10.244.0.0/24Master 节点 Podk8s-master
10.244.1.0/24Worker-1 Podk8s-worker-1

网段:10.244.0.0/16 (总计 256 个 /24 子网)

Kubernetes Service 网络

网段用途
10.96.0.0/16ClusterIP、NodePort、LoadBalancer、DNS

二、安装容器运行时 (containerd)

为什么选 containerd? kubeadm 默认推荐、轻量稳定、Ubuntu 24.04 默认集成

2.1 安装步骤

所有节点执行

bash
# 1. 安装 containerd
sudo apt-get update && sudo apt-get install -y containerd

# 2. 生成默认配置
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml

# 3. 配置 systemd cgroup 驱动 (必须!)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 4. 重启并启用
sudo systemctl restart containerd && sudo systemctl enable containerd

# 5. 验证
sudo ctr version

2.2 关键配置说明

配置作用
SystemdCgroup = true与 kubelet cgroupDriver 保持一致

三、安装 kubeadm、kubelet、kubectl

3.1 添加 Kubernetes 仓库 (阿里云镜像源)

所有节点执行

bash
# 1. Ubuntu 24.04 需要创建密钥目录
sudo mkdir -p -m 755 /etc/apt/keyrings

# 2. 获取 GPG 密钥
curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.34/deb/Release.key |
    gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 3. 添加阿里云镜像源 (传统 deb 格式)
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.34/deb/ /" | \
    sudo tee /etc/apt/sources.list.d/kubernetes.list

# 4. 安装并锁定版本
sudo apt-get update && sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# 5. 启用 kubelet
sudo systemctl enable --now kubelet

镜像源https://mirrors.aliyun.com/kubernetes-new/(国内快速)

3.2 版本验证

bash
# 查看已安装版本
kubeadm version
kubelet --version
kubectl version --client

四、节点数据盘配置

重要:数据盘配置必须在初始化集群前完成,否则容器数据会占满系统盘导致 Pod 被驱逐。

4.1 为什么需要独立数据盘

┌─────────────────────────────────────────────────────────────┐
│  kubelet evictionHard 只监控根分区                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  /dev/sda (系统盘 32GB)                                    │
│  └─ / (根分区)  ← kubelet 只监控这里                       │
│       /var/lib/kubelet ← 容器数据全在这里                  │
│                                                             │
│  问题:容器镜像占用系统盘,根分区满了                       │
│  → kubelet 驱逐所有 Pod                                    │
│  → 即使数据盘还有 90GB                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

解决方案:软链接 /var/lib/kubelet → /data/kubelet

4.2 初始化 LVM 数据卷

bash
# 1. 清空签名 → 2. 创建 PV → 3. 创建 VG → 4. 创建 LV → 5. 格式化
sudo wipefs -a /dev/sdb
sudo pvcreate /dev/sdb
sudo vgcreate data-vg /dev/sdb
sudo lvcreate -l 100%FREE -n data-lv data-vg
sudo mkfs.ext4 /dev/data-vg/data-lv

# 6. 挂载到 /data
sudo mkdir -p /data
sudo mount /dev/data-vg/data-lv /data

# 7. 永久挂载(重启后生效)
echo '/dev/mapper/data--vg-data--lv /data ext4 defaults 0 0' | sudo tee -a /etc/fstab

# 8. 验证
df -h /data

4.3 迁移 kubelet 到数据盘

bash
# 1. 停止服务
sudo systemctl stop kubelet

# 2. 迁移数据到数据盘
sudo mkdir -p /data/kubelet
sudo rsync -av /var/lib/kubelet/ /data/kubelet/

# 3. 创建软链接替换原目录
sudo rm -rf /var/lib/kubelet
sudo ln -s /data/kubelet /var/lib/kubelet

# 4. 重启服务
sudo systemctl start kubelet

# 5. 验证
ls -la /var/lib/kubelet

4.4 迁移 containerd 到数据盘

bash
# 1. 停止服务
sudo systemctl stop kubelet
sudo systemctl stop containerd

# 2. 迁移数据到数据盘
sudo mkdir -p /data/containerd
sudo rsync -av /var/lib/containerd/ /data/containerd/

# 3. 创建软链接替换原目录
sudo rm -rf /var/lib/containerd
sudo ln -s /data/containerd /var/lib/containerd

# 4. 启动服务(containerd 必须先启动)
sudo systemctl start containerd
sudo systemctl start kubelet

# 5. 验证
ls -la /var/lib/containerd
sudo ctr version

五、初始化 Master 节点

5.1 创建初始化配置文件

仅在 Master 节点执行

bash
cat > kubeadm-config.yaml << 'EOF'
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "192.168.1.165"
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
  name: k8s-master
  taints: nil
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.34.0"
clusterName: "homelab-k8s"
controlPlaneEndpoint: "192.168.1.165:6443"
networking:
  dnsDomain: "cluster.local"
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/16"
apiServer:
  extraArgs:
    service-node-port-range: "30000-32767"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

5.2 执行初始化

bash
# 初始化 Master 节点
sudo kubeadm init --config=kubeadm-config.yaml --upload-certs

5.3 配置 kubectl

bash
# 当前用户配置
mkdir -p ~/.kube
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

# 验证集群
kubectl get nodes
kubectl get pods -A

六、安装 Calico CNI

6.1 为什么选 Calico?

特性CalicoFlannel说明
网络策略✅ 支持❌ 不支持网络安全必需
性能eBPF 加速
复杂度Flannel 简单
社区活跃都很活跃

6.2 安装 Calico

bash
# 使用 manifest (推荐)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/calico.yaml

Calico 版本:v3.28.2 是 K8s 1.34 的推荐版本

6.3 验证 Calico

bash
# 检查 Pod 运行状态
kubectl get pods -n kube-system -l k8s-app=calico-node

# 检查节点网络
kubectl get nodes -o wide

七、Worker 节点加入集群

7.1 使用 Token 加入

bash
# 查看现有 Token
kubeadm token list

# 创建新 Token (如果过期)
kubeadm token create --print-join-command

7.2 加入 Worker 节点

bash
# 在 Worker 节点上执行
sudo kubeadm join 192.168.1.165:6443 \
    --token <your-token> \
    --discovery-token-ca-cert-hash sha256:<your-hash>

7.3 验证节点状态

bash
# 在 Master 上执行
kubectl get nodes

# 状态应为 Ready
NAME            STATUS   ROLES           AGE    VERSION
k8s-master      Ready    control-plane   10m    v1.34.0
k8s-worker-1    Ready    <none>          2m     v1.34.0

八、集群基础配置

8.1 安装核心插件

bash
# Metrics Server (提供 kubectl top 命令)
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - name: metrics-server
        image: registry.k8s.io/metrics-server/metrics-server:v0.7.2
        args:
          - --kubelet-insecure-tls
          - --kubelet-preferred-address-types=InternalIP
        resources:
          requests:
            cpu: 100m
            memory: 200Mi
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        effect: "NoSchedule"
EOF

8.2 创建命名空间

bash
# 创建业务命名空间
kubectl create namespace network      # 网络组件 (Ingress)
kubectl create namespace ci-cd        # CI/CD 平台
kubectl create namespace monitoring   # 监控系统
kubectl create namespace storage      # 存储组件
kubectl create namespace infra        # 基础设施
kubectl create namespace apps         # 业务应用

# 验证
kubectl get namespaces

九、存储配置 (Local Path Provisioner)

9.1 为什么用 Local Path Provisioner

存储方案优点缺点适用场景
Local Path性能高,配置简单不支持跨节点共享内网 homelab 场景
NFS支持多节点共享需要额外 NFS 服务器跨节点有状态应用

9.2 安装 Local Path Provisioner

bash
# 部署 Local Path Provisioner
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-provisioner.yaml

# 设置为默认 StorageClass
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

9.3 验证存储配置

bash
# 检查 StorageClass
kubectl get storageclass

# 测试 PVC 动态创建
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
  namespace: storage
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
EOF

# 检查 PVC 状态
kubectl get pvc -n storage

# 清理测试资源
kubectl delete pvc test-pvc -n storage

十、Ingress Controller 部署

10.1 部署 NGINX Ingress Controller

为什么用 hostNetwork? 当前资源有限,使用 hostNetwork 简化部署。

bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: network
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/ingress-nginx
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ingress-nginx
  namespace: network
spec:
  selector:
    matchLabels:
      app: ingress-nginx
  template:
    metadata:
      labels:
        app: ingress-nginx
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - name: controller
        image: registry.k8s.io/ingress-nginx/controller:v1.12.0
        args:
        - /nginx-ingress-controller
        - --controller-class=k8s.io/ingress-nginx
        - --ingress-class=nginx
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: https
          containerPort: 443
          hostPort: 443
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
          initialDelaySeconds: 10
          periodSeconds: 10
EOF

10.2 验证 Ingress Controller

bash
# 检查 Pod 运行状态
kubectl get pods -n network

# 测试访问
curl -I http://192.168.1.165

十一、集群验证清单

11.1 节点状态

bash
kubectl get nodes -o wide

预期输出

NAME            STATUS   ROLES           AGE    VERSION   INTERNAL-IP
k8s-master      Ready    control-plane   1h     v1.34.0   192.168.1.165
k8s-worker-1    Ready    <none>          45m    v1.34.0   192.168.1.166

11.2 系统组件

bash
# 检查所有系统 Pod
kubectl get pods -n kube-system

十二、集群管理

12.1 常用管理命令

bash
# 集群状态
kubectl cluster-info

# 节点维护
kubectl drain k8s-worker-1 --ignore-daemonsets --delete-emptydir-data  # 驱逐 Pod
kubectl uncordon k8s-worker-1  # 恢复调度

# 资源查看
kubectl top nodes
kubectl top pods -A

12.2 etcd 备份与恢复

bash
# 备份 etcd
sudo ETCDCTL_API=3 etcdctl snapshot save /tmp/etcd-snapshot.db \
    --endpoints=https://127.0.0.1:2379 \
    --cacert=/etc/kubernetes/pki/etcd/ca.crt \
    --cert=/etc/kubernetes/pki/etcd/server.crt \
    --key=/etc/kubernetes/pki/etcd/server.key

# 验证备份
sudo ETCDCTL_API=3 etcdctl --write-out=table snapshot status /tmp/etcd-snapshot.db

十三、常见问题排查

13.1 节点 NotReady

bash
# 检查 kubelet 状态
sudo systemctl status kubelet
sudo journalctl -u kubelet -n 100

# 检查网络插件
kubectl logs -n kube-system -l k8s-app=calico-node --tail=50

13.2 Pod 无法启动

bash
# 检查 Events
kubectl describe pod <pod-name> -n <namespace>

# 检查调度
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

13.3 PVC 无法绑定

bash
# 检查 StorageClass
kubectl get storageclass

# 检查 Local Path Provisioner
kubectl get pods -n local-path-storage

十四、后续步骤

完成集群搭建后,推荐按以下顺序部署:

顺序内容说明
1监控Prometheus 接入 K8s 指标采集
2日志系统Loki + Promtail
3CI/CD 平台Gitea + Harbor
4业务应用VitePress 博客等

基于开源技术构建