Kubernetes 集群搭建好之后,一般来说一年半载都不会再操作一次。

但每次重新搭建都得折腾半天,是一件相当繁杂的事,所以我把搭建过程写成了脚本:

GitHub :https://github.com/patricklaux/kube-install

这篇文章只是介绍配置项、执行命令和软件安装包,可与自动化脚本相互印证参考。

GitHub 中则包含完整的自动化脚本和软件安装包,如果一切顺利的话,大概十分钟就可完成全部过程。

0. 环境说明

集群搭建工具为 Kubeadm ,示例集群共有 3 个节点:1 个主节点和 2 个工作节点。

操作系统均为 Ubuntu 24.04.3,硬件配置为 4核-8G-200G 虚拟机,详细节点信息如下:

IP主机名角色
192.168.50.130k8s-control-1控制平面
192.168.50.135k8s-worker-1工作节点1
192.168.50.136k8s-worker-2工作节点2

部署的软件版本为 Kubernetes 1.34.1 + Containerd 2.1.4 + Calico 3.30.3。

另,这次部署将尝试 Kubernetes 正式支持的新特性:nftables,此特性在 Calico 的当前版本仅处于技术预览阶段,因此勿用于生产环境。】

另,这些系统均无需提前安装 Docker。

1. 安装要求

每台机器均需安装 Linux 兼容系统。

集群所有节点能通过网络相互连接。

主节点应至少具有 2 个 vCPU2GB RAM

工作节点应至少具有 1 个 vCPU2GB RAM

集群所有节点的主机名、MAC 地址和 product_uuid 不能重复。

2. 配置系统环境

2.1. 网络端口

集群节点之间需要通过网络进行内部通信,同时还需要开放部分端口给外部访问。

如果系统防火墙是开启状态,请参照以下两张表格及网络插件 Calico 的相关要求,开放相应端口。

2.1.1. Control plane

ProtocolDirectionPort RangePurposeUsed By
TCPInbound6443Kubernetes API serverAll
TCPInbound2379-2380etcd server client APIkube-apiserver, etcd
TCPInbound10250Kubelet APISelf, Control plane
TCPInbound10259kube-schedulerSelf
TCPInbound10257kube-controller-managerSelf

2.1.2. Worker node(s)

ProtocolDirectionPort RangePurposeUsed By
TCPInbound10250Kubelet APISelf, Control plane
TCPInbound10256kube-proxySelf, Load balancers
TCPInbound30000-32767NodePort Services†All
UDPInbound30000-32767NodePort Services†All

上述表格来自 K8s 官方文档: Ports and Protocols

另,Calico 还需使用其它一些端口,详见其官方文档: Network requirements

# 如果防火墙启用,请先开放端口
sudo ufw allow 6443/tcp
sudo ufw allow 2379:2380/tcp
……

2.2. 查看内核

uname -r
# 内核版本显示如下
6.8.0-71-generic

注:如 Pod 启用用户命名空间隔离,所需的最低内核版本为 6.5+,见:Linux 内核版本要求

另,如希望了解 Kubeadm 支持的 LTS 内核,见 Linux 内核 Release 列表

2.3. 查看主机信息

根据安装要求,MAC 和 product_uuid 不能重复。

# 查看 MAC 地址,确认每台机器均不相同
ip link

# 查看 product_uuid,确认每台机器均不相同
sudo cat /sys/class/dmi/id/product_uuid

2.4. 设置主机名

根据安装要求,主机名不能重复。

# 设置每台机器的主机名
# 192.168.50.130
sudo hostnamectl set-hostname k8s-control-1
# 192.168.50.135
sudo hostnamectl set-hostname k8s-worker-1
# 192.168.50.136
sudo hostnamectl set-hostname k8s-worker-2

2.5. 关闭交换分区

# 临时关闭交换分区
sudo swapoff -a

# 注释掉交换分区所在的行
sudo sed -ri 's/.*swap.*/#&/' /etc/fstab

2.6. 调整时区

# 查看时区
timedatectl
# 如时区并非 Asia/Shanghai,可通过以下命令调整
sudo timedatectl set-timezone Asia/Shanghai
sudo systemctl restart systemd-timedated
sudo systemctl restart systemd-timesyncd

2.7. 禁用 SELinux

Ubuntu 24.04.3,默认未启用 SELinux,跳过此步骤。

# 临时关闭
sudo setenforce 0
# 永久关闭
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

2.8. 调整内核参数

# 动态加载 overlay 模块
sudo modprobe overlay

# 开机加载 overlay 模块,避免重启失效
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
EOF

# 验证模块已加载
lsmod | grep overlay

# 配置网络参数
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

3. 安装容器运行时

Containerd 相比 Docker 更轻量,所以这次选择它。

Containerd 有三种安装方式:二进制、云源和源码构建,详见官方文档:containerd-getting-started

这里采用二进制方式。

3.1. 安装 containerd

根据系统平台下载相应版本:containerd-releases

# 下载 containerd 2.1.4
wget https://github.com/containerd/containerd/releases/download/v2.1.4/containerd-2.1.4-linux-amd64.tar.gz

# 解压到 /usr/local 目录
sudo tar Cxzvf /usr/local containerd-2.1.4-linux-amd64.tar.gz

# 解压之后,/usr/local/bin/ 目录将会有四个文件
containerd  containerd-shim-runc-v2  containerd-stress  ctr

3.2. 作为服务启动

wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service

sudo mkdir -p /usr/local/lib/systemd/system/
sudo cp containerd.service /usr/local/lib/systemd/system/

sudo systemctl daemon-reload
sudo systemctl enable --now containerd

3.3. 安装 runc

根据系统平台下载对应版本:runc-releases

wget https://github.com/opencontainers/runc/releases/download/v1.4.0-rc.1/runc.amd64

sudo install -m 755 runc.amd64 /usr/local/sbin/runc

3.4. 安装 cni-plugins

根据系统平台下载对应版本:cni-plugins-releases

wget https://github.com/containernetworking/plugins/releases/download/v1.8.0/cni-plugins-linux-amd64-v1.8.0.tgz

sudo mkdir -p /opt/cni/bin
sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.8.0.tgz

3.5. 检查版本

sudo systemctl restart containerd
sudo ctr version

# 显示版本如下,安装成功
Client:
  Version:  v2.1.4
  Revision: 75cb2b7193e4e490e9fbdc236c0e811ccaba3376
  Go version: go1.23.11

Server:
  Version:  v2.1.4
  Revision: 75cb2b7193e4e490e9fbdc236c0e811ccaba3376
  UUID: 39b4558f-dbd2-40fd-bd74-bdcab98bb7d3

3.6. 修改配置

3.6.1. 配置文件

# 如存在旧配置文件,将旧配置文件备份
sudo mv /etc/containerd/config.toml /etc/containerd/config.toml.bak

# 生成新的默认配置文件
sudo containerd config default > config.toml

# 新的默认配置文件移动到指定目录
sudo mv ./config.toml /etc/containerd/

# 修改新配置文件
sudo vim /etc/containerd/config.toml

具体修改如下(只需修改带注释的位置):

version = 3
# 2.X版本默认为空(1.X 版本默认包含 'cri',需删除)
disabled_plugins = []
…………

[plugins]
  [plugins.'io.containerd.cri.v1.images']
    …………
    [plugins.'io.containerd.cri.v1.images'.pinned_images]
      # 修改版本为 3.10.1
      sandbox = 'registry.k8s.io/pause:3.10.1'
    [plugins.'io.containerd.cri.v1.images'.registry]
      # 2.X 版本:每个域名都需配置一个镜像源文件,这里是配置镜像源的根目录
      config_path = '/etc/containerd/certs.d'
      …………
  [plugins.'io.containerd.cri.v1.runtime']
    …………
    [plugins.'io.containerd.cri.v1.runtime'.containerd]
      …………
      [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes]
        [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
		  …………
		  [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
		    …………
		    # 增加配置,使用 SystemdCgroup
		    SystemdCgroup = true
		    …………

注意:

Containerd 2.x 的配置版本为 version = 3,1.5 + 的配置版本为 version = 2

两个版本的配置项并不相同,因此这里的配置项仅适用于 2.x 版本。

3.6.2. 镜像源:docker.io

官方文档:containerd-hosts

主要用于下载 Calico 镜像。

sudo mkdir -p /etc/containerd/certs.d/docker.io/

# 文件内容如下(镜像地址按需修改):
cat <<EOF | sudo tee /etc/containerd/certs.d/docker.io/hosts.toml
server = "https://docker.io"
[host."https://docker.1ms.run"]
  capabilities = ["pull", "resolve"]
[host."https://docker.apiba.cn"]
  capabilities = ["pull", "resolve"]
EOF

3.6.3. 镜像源:registry.k8s.io

用于下载 Kubernetes 官方镜像。

sudo mkdir -p /etc/containerd/certs.d/registry.k8s.io/

# 文件内容如下(镜像地址按需修改):
cat <<EOF | sudo tee /etc/containerd/certs.d/registry.k8s.io/hosts.toml
server = "https://registry.k8s.io"
[host."https://k8s.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

3.6.4. 配置代理

如希望使用代理,则可按如下配置(可选)。

sudo mkdir -p /etc/systemd/system/containerd.service.d/

# 文件内容如下(代理地址按需修改):
cat <<EOF | sudo tee /etc/systemd/system/containerd.service.d/http-proxy.conf
[Service]
  Environment="HTTP_PROXY=http://192.168.50.218:3128/"
  Environment="HTTPS_PROXY=http://192.168.50.218:3128/"
  Environment="NO_PROXY=localhost,127.0.0.1,127.0.1.1,::1,10.0.0.0/8,192.168.0.0/16,172.17.0.0/16,172.30.0.0/16,172.31.0.0/16,.svc,.cluster.local,169.254.169.254"
EOF

3.6.5. 重启 containerd

sudo systemctl daemon-reload && sudo systemctl restart containerd

3.7. 小结

Kubernetes 的集群部署出现问题,主要就两个原因:

一是容器运行时未正确安装或与 Kubernetes 版本不匹配;二是网络不通无法拉取镜像。

总之,要不配置镜像源,要不配置网络代理,否则将因无法下载镜像而导致集群部署失败。

4. 安装部署工具

4.1. 安装 kubelet kubeadm kubectl

经测试,这三大工具无需使用国内 apt 源替代 pkgs.k8s.io 源也可快速安装。

如果因网络问题无法正常安装,可使用阿里云,具体配置见:Kubernetes镜像

# 更新仓库数据
sudo apt-get update
# 安装依赖软件
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

# 2. 下载 Kubernetes 软件仓库的公共签名密钥
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 3. 添加 Kubernetes v1.34 的 apt 仓库
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

# 4. 更新仓库数据,安装 kubelet、kubeadm 和 kubectl
sudo apt-get update
sudo apt-get install -y kubelet=1.34.1 kubeadm=1.34.1 kubectl=1.34.1

# 5. 锁定版本,避免升级破坏集群稳定性
sudo apt-mark hold kubelet kubeadm kubectl

4.2. 启动 kubelet

sudo systemctl enable kubelet && sudo systemctl start kubelet
# 查看 kubelet 状态
systemctl status kubelet

4.3. 配置 crictl

安装 kubeadm 将会自动安装 crictl 镜像命令行工具,其命令语法与 docker 非常类似。

这个工具需进行如下配置:

cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: unix:///var/run/containerd/containerd.sock
timeout: 10
debug: false
EOF

现在可以尝试拉取第一个关键镜像:

# 之前已配置好 Containerd 的代理或镜像源
# 所以,这里直接使用 registry.k8s.io 拉取镜像
sudo crictl pull registry.k8s.io/pause:3.10.1
# 这个镜像非常非常小,只有 320 KB,应该可以很快下载完成
# 如果成功,将显示如下信息:
Image is up to date for sha256:cd073f4c5f6a8e9dc6f3125ba00cf60819cae95c1ec84a1f146ee4a9cf9e803f

注:最好每个节点都尝试一遍,如果都能拉取,那么至少已经成功一大半。

另:这个 pause 镜像是所有 pod 的根容器,没有它将完全无法启动 pod,更无法创建集群。


4.4. 小结

以上步骤,请在所有节点执行。

至此,所有集群部署的相关工具和系统环境都已全部准备完毕。

5. 创建集群

使用 kubeadm 创建集群。

5.1. 下载所需镜像

本节内容仅主节点执行。

# 获取需要下载的镜像
sudo kubeadm config images list --kubernetes-version=v1.34.1 --image-repository=registry.k8s.io
# 需要下载的镜像列表如下
registry.k8s.io/kube-apiserver:v1.34.1
registry.k8s.io/kube-controller-manager:v1.34.1
registry.k8s.io/kube-scheduler:v1.34.1
registry.k8s.io/kube-proxy:v1.34.1
registry.k8s.io/coredns/coredns:v1.12.1
registry.k8s.io/pause:3.10.1
registry.k8s.io/etcd:3.6.4-0

# 提前下载镜像(可选)
sudo kubeadm config images pull --kubernetes-version=v1.34.1 --image-repository=registry.k8s.io

5.2. 初始化主节点

本节内容仅主节点执行。

5.2.1. 指定控制平面节点

# 所有机器都添加主节点域名映射
sudo vim /etc/hosts
# 添加如下行(192.168.50.130 请换成你的主节点 IP):
192.168.50.130 cluster-endpoint

5.2.2. 创建配置文件

相比命令行参数配置,官方更推荐使用配置文件方式,因此这里采用配置文件方式来安装。

生成默认配置

# 通过如下命令生成 kubeadm 的集群部署默认配置
kubeadm config print init-defaults -v=5 --component-configs KubeProxyConfiguration,KubeletConfiguration > kubeadm-init.yaml

修改配置项

根据默认文件进行修改和删减后,仅保留了已修改项(未修改项 Kubeadm 将使用默认配置)。

这里需特别注意的是各个 IP 设置,需根据你的实际网络环境进行调整:

1、podSubnetclusterCIDR 的配置必须相同;

2、serviceSubnet 和 【podSubnet & clusterCIDR】的网络范围不能重叠;

3、所配置的网络范围也不能与其它网络范围(譬如物理网络)重叠,总之就是各个网络范围不能重叠。

apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  # 1.更换为实际的主节点 IP
  advertiseAddress: 192.168.50.130
  bindPort: 6443
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controlPlaneEndpoint: cluster-endpoint
kubernetesVersion: 1.34.1
networking:
  # 2.service 的网络范围
  serviceSubnet: 10.96.0.0/12
  # 3.pod 的网络范围
  podSubnet: 172.30.0.0/16
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
# 4.与 podSubnet 保持一致
clusterCIDR: 172.30.0.0/16
# 5.配置为使用 nftables
mode: nftables
---
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd
kind: KubeletConfiguration

5.3. 主节点初始化

本节内容仅主节点执行。

sudo kubeadm init --config=kubeadm-init.yaml

这一步执行完成后,控制台将会打印一些信息,其中包含了重要的加入集群的命令信息。

Your Kubernetes control-plane has initialized successfully!
# 省略 ……
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 省略 ……
kubeadm join cluster-endpoint:6443 --token ihn3xw.gsl769v2649zb33n \
    --discovery-token-ca-cert-hash sha256:2c8223de5e3507f290775cfe1b0da64336afc57470ea9bc7e48ad92d91ebb57d
    --control-plane 
# 省略 ……
kubeadm join cluster-endpoint:6443 --token ihn3xw.gsl769v2649zb33n \
    --discovery-token-ca-cert-hash sha256:2c8223de5e3507f290775cfe1b0da64336afc57470ea9bc7e48ad92d91ebb57d

请手动执行以下命令复制证书和配置到用户目录:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

此时,如果执行命令:

sudo crictl info

将会输出类似的如下错误状态信息,这是因为还未安装网络插件:

{
    ……
    "lastCNILoadStatus": "cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config",
    "lastCNILoadStatus.default": "cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config",
    "status": {
        "conditions": [
            ……
            {
                "message": "Network plugin returns error: cni plugin not initialized",
                "reason": "NetworkPluginNotReady",
                "status": false,
                "type": "NetworkReady"
            },
            ……
        ]
    }
}

5.3. 网络插件 Calico

本节内容仅主节点执行。

Linux 系统下,服务代理有 iptablesip_vsntables 三种模式,见:Virtual IPs and Service Proxies

iptables:时间复杂度 O(n),其性能不适用于大规模集群。

ip_vs:时间复杂度 O(1),但无法满足精细化控制的特性要求,未来可能被 Kubernetes 弃用。

nftables:Kubernetes 1.29 引入,1.33 正式发布,见:kubernetes-1-29-feature & nftables-kube-proxy

网络插件 Calico 3.30.3 的 nftables 模式则是预览特性,见:calico-nftables

如开篇所述,这次部署将使用 nftables 模式体验新特性。

5.3.1. 安装网络插件

下载配置文件

wget https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/operator-crds.yaml

wget https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/tigera-operator.yaml

wget https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/custom-resources.yaml

只需要修改 custom-resources.yaml

# This section includes base Calico installation configuration.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.Installation
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    # 1.设置为使用 Nftables
    linuxDataplane: Nftables
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
      # 2.设置为与 podSubnet 一致
      cidr: 172.30.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()

---

# This section configures the Calico API server.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.APIServer
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}

---

# Configures the Calico Goldmane flow aggregator.
apiVersion: operator.tigera.io/v1
kind: Goldmane
metadata:
  name: default

---

# Configures the Calico Whisker observability UI.
apiVersion: operator.tigera.io/v1
kind: Whisker
metadata:
  name: default

安装网络插件

kubectl create -f operator-crds.yaml
kubectl create -f tigera-operator.yaml
kubectl create -f custom-resources.yaml

5.3.2. 安装 calicoctl

calicoctl 是命令行工具,可以方便地查看网络信息:

wget https://github.com/projectcalico/calico/releases/download/v3.30.3/calicoctl-linux-amd64

# 1. 增加执行权限
sudo chmod +x ./calicoctl-linux-amd64

# 2. 作为独立工具安装
sudo mv calicoctl-linux-amd64 /usr/local/bin/calicoctl

# 2. 还可作为 kubectl 插件安装
sudo mv calicoctl-linux-amd64 /usr/local/bin/kubectl-calico

# 获取节点状态和协议
sudo calicoctl node status

5.4. 添加工作节点

本节内容仅工作节点执行。

执行主节点初始化成功时打印的命令信息:

sudo kubeadm join cluster-endpoint:6443 --token ihn3xw.gsl769v2649zb33n \
    --discovery-token-ca-cert-hash sha256:2c8223de5e3507f290775cfe1b0da64336afc57470ea9bc7e48ad92d91ebb57d

如果遗失了加入集群的命令信息,可以在主节点执行以下命令重新生成:

kubeadm token create --print-join-command

至此,集群基础搭建的所有工作都已完成。

6. 检查集群状态

主节点执行命令,检查节点状态:

watch -n 2 kubectl get nodes

幸运的话,稍等片刻,将会看到所有节点都变成 Ready 状态:

NAME            STATUS   ROLES           AGE   VERSION
k8s-control-1   Ready    control-plane   22h   v1.34.1
k8s-worker-1    Ready    <none>          21h   v1.34.1
k8s-worker-2    Ready    <none>          21h   v1.34.1

使用如下命令检查 pod 状态:

watch -n 2 kubectl get pod -A

幸运的话,稍等片刻,将会看到所有 pod 都是 Ready 状态。

7. 常用命令

# 查看配置
kubectl describe configmaps kubeadm-config -n kube-system

# 查看 pod 日志
kubectl logs <pod-name> -n kube-system -f

某些情况下,工作节点出问题,但又无法使用 kubectl 查看日志,那么可使用如下命令:

# 查看运行日志(特别是工作节点出问题)
journalctl -u kubelet -f

8. 常见异常

异常 1

failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
error: error execution phase preflight: preflight checks failed
To see the stack trace of this error execute with --v=5 or higher

可能情况

1、容器运行时版本不匹配;

2、未正确配置 crictl,见 4.3 节。

异常 2

W0914 10:04:12.719180   25964 checks.go:830] detected that the sandbox image "registry.k8s.io/pause:3.8" of the container runtime is inconsistent with that used by kubeadm. It is recommended to use "registry.k8s.io/pause:3.10.1" as the CRI sandbox image.

3.6.1. 配置文件,调整 pause镜像版本即可。

9. 结语

这只是搭建了一个最基础的集群,

接下来还需要安装 Metrics Server、Dashboard、Helm、Ingress 之类的组件,后续有时间再慢慢添加脚本。

最后,祝大伙的集群搭建过程一切顺利!