服务器运维

k8s部署教程:用 kubeadm 从零搭建 Kubernetes 集群

在自有服务器上做 k8s部署,最直接的方式是官方工具 kubeadm:每台服务器装好 containerd 和 kubeadm、kubelet、kubectl,在一台上执行 kubeadm init 生成控制平面,其余节点用 kubeadm join 加入,再装一个网络插件,节点变成 Ready 就完成了。动手前先定好版本和 Pod、Service 网段,网段定下之后很难再改。

作者 顶联产品团队发布于 6 分钟阅读

k8s部署(Kubernetes 部署)在自有服务器上,用官方工具 kubeadm 最直接,完整流程分六步:所有节点做系统准备(关闭 swap、加载内核模块、开启转发)→ 安装 containerd 并改用 systemd cgroup → 安装 kubeadm、kubelet、kubectl → 在控制平面节点执行 kubeadm init → 安装 Calico 或 Flannel 网络插件 → 在 worker 上执行 kubeadm join 并验证。下面以 3 台 Ubuntu 22.04 或 24.04 服务器、Kubernetes 1.37 为例,每一步都给出可以直接执行的命令,Rocky、AlmaLinux 的差异在相应位置说明。

部署前先定三件事:节点、版本和网段

节点规划

主机名 角色 内网 IP 最低配置
k8s-cp1 控制平面 10.10.20.11 2 核 CPU、2 GB 内存,建议 4 GB 以上
k8s-node1 worker 10.10.20.12 2 GB 内存起,按业务定
k8s-node2 worker 10.10.20.13 同上

每个节点的主机名、MAC 地址和 product_uuid 必须唯一。用同一个模板克隆的虚拟机、用同一个镜像批量装的物理机,都要逐台执行 sudo cat /sys/class/dmi/id/product_uuid 核对。节点之间要互通的端口:控制平面 TCP 6443、2379–2380、10250、10257、10259,worker TCP 10250、10256 和 NodePort 范围 30000–32767,另加网络插件用的端口(见后文)。测试环境里节点在同一内网段时,可以先放通节点之间的全部流量。

版本:k8s 最新版本怎么选

Kubernetes 每年发布三个左右的小版本,每个小版本发布后约有 14 个月的补丁支持,期间大约每月出一个补丁版本。按官方发布页(kubernetes.io/releases)的数据,仍在支持期内的小版本如下:

小版本 发布日期 支持结束
1.37 2026-08-26 2027-10-28
1.36 2026-04-22 2027-06-28
1.35 2025-12-17 2027-02-28

1.34 的支持已于 2026 年 10 月 27 日结束;1.38 按发布计划在 2026 年 12 月 16 日发布。补丁号每个月都在变,安装前用 curl -sL https://dl.k8s.io/release/stable.txt 查当前最新的稳定版,或者直接看官方发布页。本文示例用 1.37,选版本按三条规则:

  • 新集群选最新的、已经出过一两个补丁的小版本;刚发布的 .0 版先在测试集群里跑。
  • kubeadm、kubelet、kubectl 装同一个版本。kubelet 可以比 API Server 旧,最多旧三个小版本,但不能比它新;kubectl 与 API Server 相差不超过一个小版本。
  • 升级只能逐个小版本进行,1.35 要到 1.37,必须先升 1.36。所以别装即将停止支持的版本,否则刚上线就要排升级。

网段:Pod 和 Service 不能与机房已有网段重叠

网段 用途 常见默认值 本例取值
节点网络 服务器网卡地址 机房分配 10.10.20.0/24
Pod 网段 每个 Pod 一个 IP Calico 清单 192.168.0.0/16,Flannel 10.244.0.0/16 172.20.0.0/16
Service 网段 ClusterIP 虚拟地址 kubeadm 默认 10.96.0.0/12 172.21.0.0/16

机房环境里,默认值很容易撞车:10.96.0.0/12 覆盖 10.96.0.0 到 10.111.255.255,机房业务网如果用的是 10.100.x.x,就正好落在里面;192.168.0.0/16 则经常和办公网、BMC 管理网重叠。一旦重叠,Pod 访问这些内网机器时可能被当成 Service 地址处理,或者路由走错,表现为集群内访问某几台机器不通、集群外却正常,非常难查。本例选 172.20.0.0/16 和 172.21.0.0/16,同时避开了 Docker 默认的 172.17.0.0/16。

容量也要算一下:kubeadm 默认给每个节点切一个 /24 的 Pod 子网(254 个可用地址,而 kubelet 默认每个节点最多运行 110 个 Pod),/16 的 Pod 网段最多容纳 256 个节点。Flannel 按这个划分给节点分配地址;Calico 默认不用它,而是按 /26(64 个地址)的块按需分给各节点。

所有节点:系统准备、containerd 与 kubeadm

以下命令在三台服务器上都要执行。

系统参数

# 1. 主机名和解析,每台把 hostname 改成自己的名字
sudo hostnamectl set-hostname k8s-cp1
cat <<EOF | sudo tee -a /etc/hosts
10.10.20.11 k8s-cp1 k8s-api.example.internal
10.10.20.12 k8s-node1
10.10.20.13 k8s-node2
EOF

# 2. 关闭 swap,并注释 /etc/fstab 里的 swap 行,防止重启后恢复
sudo swapoff -a
sudo sed -ri 's/^([^#].*\sswap\s.*)$/#\1/' /etc/fstab

# 3. 开机加载内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

# 4. 内核参数:开启 IPv4 转发,让网桥上的流量经过 iptables
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

# 5. 时间同步,证书校验依赖准确的时间
sudo apt-get update && sudo apt-get install -y chrony
sudo systemctl enable --now chrony

kubelet 默认检测到 swap 就拒绝启动。新版本可以配置成容忍 swap,但入门部署直接关掉最省事。Rocky、AlmaLinux 还要把 SELinux 设为 permissive(sudo setenforce 0,并修改 /etc/selinux/config),并在 firewalld 里放行上面列出的端口。

安装 containerd

sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
containerd --version

# containerd 1.x 的默认配置里有 SystemdCgroup = false,改成 true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 国内网络:pause 镜像改从阿里云的镜像仓库拉取
sudo sed -i 's#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#' /etc/containerd/config.toml
grep -nE 'SystemdCgroup|pause' /etc/containerd/config.toml

sudo systemctl restart containerd
sudo systemctl enable containerd

grep 应该输出 SystemdCgroup = true 和改过地址的 pause 镜像。如果 containerd --version 显示 2.x,默认配置里没有 SystemdCgroup 这一行,grep 也就找不到它,这时在 [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options] 这一段下面手动加一行 SystemdCgroup = true。kubeadm 默认让 kubelet 使用 systemd 管理 cgroup,containerd 必须保持一致,否则控制平面的容器会反复重启。

安装 kubeadm、kubelet、kubectl

Kubernetes 的软件源按小版本分开,K8S_MINOR 写到小版本即可:

K8S_MINOR=v1.37
REPO=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb
# 国内网络改用阿里云镜像(路径里没有冒号):
# REPO=https://mirrors.aliyun.com/kubernetes-new/core/stable/${K8S_MINOR}/deb

sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL ${REPO}/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] ${REPO}/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet

apt-mark hold 防止日常 apt upgrade 把三个组件意外升级,集群升级要按 kubeadm 的流程来。装好后 kubelet 每隔几秒重启一次是正常的,它在等 kubeadm init 生成配置。Rocky、AlmaLinux 按官方文档写 /etc/yum.repos.d/kubernetes.repo,地址把末尾的 deb 换成 rpm,再用 dnf 安装。

初始化控制平面并安装网络插件

在 k8s-cp1 上执行 kubeadm init

# 先单独拉镜像,网络问题在这一步暴露,不会卡在 init 中途
sudo kubeadm config images pull \
  --kubernetes-version $(kubeadm version -o short) \
  --image-repository registry.aliyuncs.com/google_containers

sudo kubeadm init \
  --kubernetes-version $(kubeadm version -o short) \
  --apiserver-advertise-address 10.10.20.11 \
  --control-plane-endpoint k8s-api.example.internal:6443 \
  --pod-network-cidr 172.20.0.0/16 \
  --service-cidr 172.21.0.0/16 \
  --image-repository registry.aliyuncs.com/google_containers

# 让当前用户能用 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

参数说明:

  • --image-repository 只在拉不到 registry.k8s.io 时才需要;执行 kubeadm config images list --kubernetes-version $(kubeadm version -o short) 可以看到这个版本要用的 pause 标签,和 containerd 配置里的不一致时,把配置改成同一个标签再重启 containerd。
  • --control-plane-endpoint 写一个域名而不是 IP。现在它解析到 k8s-cp1,以后扩成 3 台控制平面时改指向负载均衡的虚拟 IP 即可;不加这个参数建好的单控制平面集群,官方不支持再改造成高可用,只能重建。
  • init 成功后会打印一条 kubeadm join ... 命令,先复制保存。此时 kubectl get nodes 显示 NotReady,装完网络插件才会变成 Ready。

网络插件:Calico 还是 Flannel

对比项 Calico Flannel
NetworkPolicy 网络策略 支持 不支持
默认转发方式 清单默认 IPIP 封装,可改 VXLAN 或不封装的 BGP 路由 VXLAN
与机房交换机 BGP 互通 可以,Pod 网段直接宣告给接入交换机 不支持
节点间需放行 BGP 用 TCP 179,IPIP 是 IP 协议号 4,VXLAN 用 UDP 4789 VXLAN 用 UDP 8472
复杂度 组件和概念较多 简单,几乎不用配置
适合 生产集群、多租户、需要访问控制 测试和小集群

两者只装一个。装 Calico(3.33 官方测试过 Kubernetes 1.35 到 1.37):

CALICO_VERSION=v3.33.0
curl -LO https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/calico.yaml
# 编辑 calico.yaml:找到 CALICO_IPV4POOL_CIDR,去掉两行的注释,改成 init 时的 Pod 网段
kubectl apply -f calico.yaml
kubectl -n kube-system get pods -l k8s-app=calico-node -w

改好后的那两行是这样的,缩进要和上下文对齐:

            - name: CALICO_IPV4POOL_CIDR
              value: "172.20.0.0/16"

按 Calico 官方文档,用 kubeadm 且 init 时指定了 --pod-network-cidr 的集群,Calico 能自动识别 Pod 网段,这一步不改也行;显式写上更直观,但必须在 apply 之前改,清单里的注释写明了安装后再改不会生效。Calico 的镜像在 quay.io 上,拉取失败时先导入机房的私有镜像仓库,再修改清单里的 image 字段。官方对新集群更推荐用 Tigera Operator 安装,清单方式步骤少,适合入门和小集群。

装 Flannel 则下载 https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml,把 net-conf.json 里的 "Network": "10.244.0.0/16" 改成 172.20.0.0/16 再 apply,镜像在 ghcr.io 上。

加入 worker 节点并验证

在 k8s-node1、k8s-node2 上执行 init 打印的那条命令:

sudo kubeadm join k8s-api.example.internal:6443 \
  --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:<init 输出里的哈希值>

token 默认 24 小时过期,过期或忘了保存,在控制平面上执行 kubeadm token create --print-join-command 重新生成。然后在 k8s-cp1 上验证:

kubectl get nodes -o wide          # 三个节点都应为 Ready
kubectl get pods -A -o wide        # coredns、calico-node、kube-proxy 都应为 Running
kubectl create deployment web --image=nginx --replicas=2
kubectl expose deployment web --port=80 --type=NodePort
kubectl get pods -l app=web -o wide
kubectl get svc web                # PORT(S) 一栏形如 80:31234/TCP,冒号后是 NodePort
curl http://10.10.20.12:<NodePort>

看三个地方:两个 web Pod 分布在不同节点,IP 落在 172.20.0.0/16 里;Service 的 CLUSTER-IP 落在 172.21.0.0/16 里;从任意节点 IP 加 NodePort 都能打开 nginx 页面。三项都对,说明网段规划和网络插件都生效了。nginx 镜像来自 Docker Hub,国内拉不下来时换成私有仓库里的镜像。

只有一台机器、想让普通 Pod 也调度到控制平面上,执行 kubectl taint nodes k8s-cp1 node-role.kubernetes.io/control-plane:NoSchedule-。

部署失败时按报错排查

现象 常见原因 处理
preflight 报 /proc/sys/net/ipv4/ip_forward contents are not set to 1 内核参数没生效 检查 /etc/sysctl.d/k8s.conf,执行 sysctl --system
preflight 或 kubelet 日志提示 swap swap 没关,或 fstab 没改、重启后又开了 swapoff -a 并注释 fstab 里的 swap 行
init 停在 waiting for the kubelet to boot up the control plane,最后超时 镜像拉不下来;containerd 与 kubelet 的 cgroup 驱动不一致 看 kubelet 日志和容器状态,见下方命令
节点一直 NotReady,coredns 一直 Pending 网络插件没装,或插件的 Pod 起不来 kubectl -n kube-system get pods,对异常 Pod 执行 describe
calico-node 显示 0/1,日志提示 BGP 未建立 节点间 TCP 179 或 IPIP 被防火墙拦截;多网卡服务器识别错了网卡 放行对应流量;kubectl -n kube-system set env daemonset/calico-node IP_AUTODETECTION_METHOD=cidr=10.10.20.0/24
join 报 token 无效 token 过期 重新生成 join 命令
Pod IP 不在规划的网段里 CALICO_IPV4POOL_CIDR 或 Flannel 的 Network 与 init 时的 --pod-network-cidr 不一致 Calico 已创建的 IP 池不会自动改变,要新建正确的池再删除旧池,测试集群直接重装更快
sudo journalctl -u kubelet -f
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps -a
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock logs <容器ID>

# 推倒重来:清掉 kubeadm 生成的配置和网络插件配置
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d $HOME/.kube/config

kubeadm reset 不会清理 iptables 规则和 IPVS 表,重装前按它的提示手动清理,或者直接重启节点。

从测试集群到生产还差什么

照上面的步骤建出来的是单控制平面集群,跑测试足够,上生产还要补四件事:

  1. 控制平面高可用:3 台控制平面节点,前面用 keepalived 加 HAProxy 提供一个虚拟 IP,k8s-api.example.internal 解析到这个虚拟 IP;第一台 kubeadm init 时加 --upload-certs,其余控制平面节点 join 时加 --control-plane 和 init 输出里的 --certificate-key。上传的证书两小时后自动删除,过期了用 sudo kubeadm init phase upload-certs --upload-certs 重新生成密钥。
  2. etcd 定期备份:集群的全部状态都在 etcd 里。主机上装好 etcdctl 后,用 kubeadm 生成的证书做快照,并把文件拷到集群之外:
sudo 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 /backup/etcd-$(date +%F).db
  1. 证书到期:kubeadm 签发的组件证书有效期一年,每次 kubeadm upgrade 会自动续期。长期不升级的集群用 sudo kubeadm certs check-expiration 定期检查,到期前执行 kubeadm certs renew all 并重启控制平面组件。
  2. 对外暴露服务:物理机上没有云厂商的负载均衡,LoadBalancer 类型的 Service 会一直处于 Pending。常见做法是部署 MetalLB,从机房分配的一段地址里给 Service 分配 IP,L2 模式用 ARP 宣告,BGP 模式与交换机建立邻居。要注意:交换机端口开了 IP 与 MAC 绑定或源地址校验时,L2 模式下在节点间漂移的虚拟 IP 会被丢弃,要么把这些地址的绑定一并放开,要么改用 BGP 模式。

在管理系统里怎么落地

k8s 节点最好装同一个系统版本、同样的分区和内核参数。用 Toplink DCIM 的 IPMI 远程管理可以让一批节点统一从 PXE 引导、排进任务队列批量重装系统,几台节点的底子从一开始就一致。网段这件事最怕“只记在部署文档里”:把 Pod 网段、Service 网段和 MetalLB 用的地址段录入 IP 地址管理,类型标为内网 IP 或公网 IP 并设为锁定,它们就不会在开通新服务器时被自动分配出去。节点接在哪台交换机的哪个端口、端口在哪个 VLAN,在交换机管理里能直接看到,排查 MetalLB 地址不通时先从这里确认节点的接入端口。

常见问题

k8s 最少需要几台服务器?

一台也能跑:kubeadm init 之后去掉控制平面节点的污点,普通 Pod 就能调度到这台机器上,适合学习。生产环境的控制平面建议 3 台做高可用,worker 按业务量另算;只有 1 台控制平面时,它一坏集群就无法管理。

还能用 Docker 作为 k8s 的容器运行时吗?

Kubernetes 1.24 起移除了内置的 dockershim,不能直接对接 Docker Engine,坚持用 Docker 要额外安装 cri-dockerd。新集群直接用 containerd 更简单;用 Docker 构建的镜像遵循 OCI 标准,在 containerd 上照样能运行。

k8s 集群可以跨机房部署吗?

不建议把一个集群的控制平面拆到多个机房。etcd 对节点间延迟很敏感,跨机房链路一抖动就可能选主失败、API 变慢。多机房更常见的做法是每个机房一套集群,在上层做流量调度和统一发布。

想看看在你的机房里怎么用?

从一次产品演示开始,梳理你的业务链路。

查看价格
服务热线 400-112-2951

让我们聊聊你的 IDC 业务

扫码添加企业微信,预约产品演示或申请试用。

Toplink 企业微信二维码

长按保存或使用企业微信 / 微信扫描

也可致电
400-112-2951
Telegram
@TopLink88