用 kubeadm 在裸金属 / VPS 上起一个最小 Kubernetes 集群

云厂商的 K8s(EKS / GKE / AKS)按月几十美元。自己买几台便宜 VPS
也能跑 K8s——一台 control plane + 2 台 worker,每月几美元。
适合自学 / 个人项目 / 小规模生产。

下面用 kubeadm 起 v1.30 集群。

准备(每台机器都做)

# 关 swap(K8s 要求)
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab

# 内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay 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

装 containerd

sudo apt update
sudo apt install -y containerd

sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# 启 SystemdCgroup(K8s 要求)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd

装 kubeadm / kubelet / kubectl

sudo apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

初始化 control plane(只在 master 上)

sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=<MASTER_IP>

完成后会打印一段 kubeadm join ... 命令——保存下来,下面 worker 用。

让普通用户用 kubectl:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes
# 状态会是 NotReady —— 因为没装 CNI

装 CNI(这里用 Flannel)

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# 等 1-2 分钟
kubectl get pods -A
kubectl get nodes
# Ready 了

其它 CNI 选择:
- Calico:网络策略 + BGP,生产更常用
- Cilium:eBPF,性能 + 观测最好
- Flannel:最简单,仅 overlay 网络

Worker 加入

每台 worker 上:

sudo kubeadm join <MASTER_IP>:6443 \
  --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<HASH>

Master 上:

kubectl get nodes
# NAME       STATUS   ROLES           AGE   VERSION
# master     Ready    control-plane   5m    v1.30.0
# worker-1   Ready    <none>          1m    v1.30.0
# worker-2   Ready    <none>          1m    v1.30.0

部署个 demo

# nginx-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels: { app: nginx }
  template:
    metadata: { labels: { app: nginx } }
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: NodePort
  selector: { app: nginx }
  ports:
    - port: 80
      nodePort: 30080
kubectl apply -f nginx-demo.yaml
kubectl get pods -o wide
curl http://<任意-Node-IP>:30080

Ingress(暴露 80/443 到公网)

NodePort 30000+ 端口不友好,用 Ingress:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: nginx, port: { number: 80 } }

<MASTER_IP> 或 worker IP DNS 指到 app.example.com
80/443 流量进 ingress-nginx,再分发到 service。

cert-manager 自动 HTTPS

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: { name: letsencrypt-prod }
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef: { name: letsencrypt-prod }
    solvers:
      - http01:
          ingress: { class: nginx }

Ingress 加 annotation cert-manager.io/cluster-issuer: letsencrypt-prod
自动签发 + 续期证书。

备份 etcd

# 在 master 上
sudo ETCDCTL_API=3 etcdctl \
  --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 \
  snapshot save /tmp/etcd-$(date +%F).db

定时备份这个文件到对象存储是单 master K8s 集群唯一的 DR 手段。

升级

sudo apt-mark unhold kubeadm
sudo apt install -y kubeadm=1.30.5-*
sudo apt-mark hold kubeadm

sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.30.5

# 然后升 kubelet / kubectl

踩过的坑

  • swap 没关 kubelet 启动失败:v1.22 之前默认要求;v1.22+ 可以保留 swap
    但要在 kubelet 配置里显式 failSwapOn: false
  • 防火墙:control plane 需要开 6443、10250、2379-2380,CNI 用的端口
    各异(Flannel UDP 8472)。最简单:把 master / worker 之间的网络
    完全开放(内网或 VPC)。
  • Token 24 小时过期:worker 加入时 kubeadm token create --print-join-command
    生成新 token。
  • 单 master 没冗余:宕机就完蛋。生产多 master + HA load balancer
  • 外部 etcd cluster。kubeadm 也支持。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。