1. 为什么选择这个组合?聊聊我的实战选型思路

最近有不少朋友问我,想自己搭个Kubernetes集群玩玩,或者给团队搞个轻量级的开发测试环境,到底该怎么选组件、怎么配才最省心。我折腾K8s集群少说也有五六年了,从最早的Docker搭桥,到后来各种CRI(容器运行时接口)轮番上阵,踩过的坑真不少。今天我就结合最近一次在Ubuntu 22.04上部署Kubernetes 1.32集群的实际经历,跟你详细聊聊为什么我最终敲定了 Kubeadm + Containerd 这个组合,以及怎么一步步把它搭起来,让它既轻快又稳定。

先说Ubuntu 22.04 LTS,这是个长期支持版本,官方维护到2027年,系统稳定,社区支持好,软件包也够新。对于生产或者长期使用的测试环境,LTS版本是首选,能避免频繁升级带来的麻烦。内核方面,22.04默认的5.15内核已经包含了对K8s各种特性的良好支持,比如cgroup v2、ebpf等,开箱即用,不用我们再去折腾内核编译。

然后是容器运行时。早几年大家基本都用Docker,但它毕竟是个“全家桶”,包含了守护进程、API、CLI、镜像构建等等,有点重。后来K8s推出了CRI标准,把运行时接口标准化了。Containerd 就是从Docker里剥离出来的那个纯粹的、高性能的容器运行时,它实现了CRI,直接被kubelet调用,路径更短,架构更清晰。我实测下来,用Containerd后,容器启动速度确实有感知的提升,资源占用也更少,特别是对于追求效率和轻量的场景,优势明显。而且从Kubernetes 1.24开始,社区就逐渐淡化了对Docker的直接支持,转向Containerd和CRI-O,这算是顺应技术趋势。

最后是部署工具 Kubeadm。你可能听说过kops、kubespray这些,它们功能更强大,但复杂度也高。Kubeadm是K8s官方出的“一键安装”工具,它的目标很纯粹:帮你快速初始化一个符合最佳实践的集群。它把证书生成、组件配置、etcd集群部署这些繁琐的活都干了,给我们留出的是一个干净、标准的集群骨架。对于我们这种想要理解底层细节,又想快速上手的场景,Kubeadm是绝佳的选择。它就像乐高说明书,告诉你核心部件怎么拼,剩下的网络、存储、监控这些“装饰件”,你可以自由发挥。

所以,Ubuntu 22.04提供稳定的地基,Containerd提供高效的动力引擎,Kubeadm提供清晰的组装图纸,这三者结合,就是一个现代化、轻量且易于维护的Kubernetes集群的黄金起点。下面,我就带你从零开始,亲手把这个组合搭建起来。

2. 实战第一步:给Ubuntu 22.04做个“体检”与优化

在真正动手安装K8s之前,我们必须先把操作系统这个“地基”打牢。很多部署失败的问题,追根溯源都是系统层配置没到位。这一步千万别图快,耐心配好,后面能省下大量排错的时间。

2.1 基础环境检查与网络配置

首先,登录你的服务器,确认下系统版本和内核。虽然Ubuntu 22.04很常见,但确保一下没坏处。

lsb_release -a
uname -r

输出应该能看到 Ubuntu 22.04.x LTS 和类似 5.15.0-xx-generic 的内核信息。接下来是网络,这是集群通信的命脉。我建议使用 netplan 来管理网络,这是Ubuntu 17.10以后默认的网络配置工具。假设你的网卡是 ens160(可以用 ip a 命令查看),编辑配置文件:

sudo vim /etc/netplan/00-installer-config.yaml

我的配置大概长这样,你需要根据你的实际网络环境修改IP地址、网关和DNS:

network:
  version: 2
  ethernets:
    ens160:
      addresses:
        - 10.62.1.181/24
      nameservers:
        addresses: [114.114.114.114, 8.8.8.8]
      routes:
        - to: default
          via: 10.62.1.254

保存后,应用配置:sudo netplan apply。这里有个小坑,如果你是用虚拟机模板克隆出来的机器,网卡的UUID可能会重复,这可能导致网络服务启动异常。一个检查方法是看MAC地址:ifconfig ens160 | grep ether。如果几台机器MAC一样,那最好在netplan配置里为每台机器生成一个新的UUID,或者直接忽略这个配置项。

然后是主机名和 hosts 文件。集群节点之间要靠主机名互相识别,所以必须配好。我习惯用 hostnamectl 设置永久主机名:

sudo hostnamectl set-hostname k8s-master01

接着,编辑 /etc/hosts 文件,把所有集群节点的IP和主机名对应关系加进去。注意,这个操作需要在集群的每一台机器上都执行!

10.62.1.181 k8s-master01
10.62.1.182 k8s-master02
10.62.1.183 k8s-master03
10.62.1.184 k8s-node01

配完之后,一定要用 ping 命令测试一下所有节点之间是否能通过主机名互相访问,比如在 master01 上 ping k8s-master02。网络不通,后面全是白搭。

2.2 系统参数调优:为K8s铺平道路

Kubernetes对Linux内核有一些硬性要求,我们必须提前配置好。第一件事是关闭Swap。K8s的设计理念是,Pod的内存使用由它自己来调度和管理,如果允许使用Swap,内存不足时性能会急剧下降,而且调度器也无法准确感知真实的内存压力,所以必须关掉。

# 临时关闭当前已启用的swap
sudo swapoff -a
# 永久关闭,注释掉fstab里所有包含swap的行
sudo sed -ri 's/^([^#].*swap.*)$/#\1/' /etc/fstab

你可以用 free -h 命令确认Swap是否已经为0。接下来,Ubuntu自带的防火墙 ufwapparmor 安全模块可能会和K8s的网络插件、容器网络产生冲突。为了简化初期部署,我建议先停用它们(生产环境请根据具体安全策略调整)。

sudo systemctl stop ufw && sudo systemctl disable ufw
sudo systemctl stop apparmor && sudo systemctl disable apparmor

时区和时间同步是分布式系统的基石。集群内所有节点时间必须高度一致,否则证书验证、事件日志都会出问题。设置时区并安装NTP客户端:

sudo timedatectl set-timezone Asia/Shanghai
sudo apt install -y chrony

编辑 /etc/chrony/chrony.conf,配置NTP服务器。你可以用公共的 pool.ntp.org,如果公司内有自己的NTP服务器更好。配置完后重启服务并设为开机自启:

sudo systemctl restart chronyd
sudo systemctl enable chronyd

chronyc sources -v 可以查看时间同步状态,确保状态是 ^*

最后,也是最重要的一步,调整内核参数。K8s需要内核支持一些网络特性,比如IP转发、桥接流量过滤等。我们创建一个配置文件:

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

执行 sudo sysctl --system 让配置生效,并用 sysctl net.ipv4.ip_forward 验证 ip_forward 是否已设为1。这些参数确保了节点上的容器网络流量能被正确路由和过滤。

3. 容器引擎核心:Containerd的安装与深度配置

地基打好了,现在来安装容器运行时。前面说了,我们选Containerd。它的安装其实很简单,但配置上有几个关键点,直接关系到后面K8s能否正常拉取镜像和运行容器。

3.1 安装与基础配置

在Ubuntu上安装Containerd就是一条命令的事:

sudo apt update
sudo apt install -y containerd

安装完成后,检查一下版本:containerd -v,我写这篇文章时默认安装的是1.7.x版本,完全兼容K8s 1.32。安装完Containerd后,它并没有默认的配置文件,我们需要生成一个:

sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml

这个命令会生成一个包含所有默认选项的TOML格式配置文件。接下来要修改两个关键地方。第一,找到 SystemdCgroup 这个参数,把它从 false 改成 true。这是因为K8s默认使用systemd来管理cgroup,保持两者一致能避免资源管理上的混乱。用sed命令可以快速修改:

sudo sed -ri 's#(SystemdCgroup = )false#\1true#' /etc/containerd/config.toml

第二,修改 sandbox_image,也就是K8s的Pause容器镜像。默认的 registry.k8s.io/pause:3.8 在国内可能拉取很慢甚至失败,我们需要把它换成国内能访问的镜像地址,比如阿里云的:

sudo sed -i 's#registry.k8s.io/pause:3.8#registry.aliyuncs.com/google_containers/pause:3.10#' /etc/containerd/config.toml

改完后,可以用 grep 命令确认一下修改是否成功:

grep -E "SystemdCgroup|sandbox_image" /etc/containerd/config.toml

3.2 配置镜像加速与私有仓库(解决拉取慢的痛点)

这是Containerd配置里最体现技巧的部分,也是很多新手会卡住的地方。Docker有 daemon.json 来配镜像仓库,Containerd的机制不太一样,它通过 hosts.toml 文件来为每个仓库域名进行配置。

首先,在Containerd的主配置文件里,告诉它去哪里找这些仓库配置。找到 [plugins."io.containerd.grpc.v1.cri".registry] 这个段落,确保下面有 config_path = "/etc/containerd/certs.d" 这一行。如果没有,就手动加上。这个路径就是存放各个仓库配置的目录。

然后,我们开始为不同的仓库创建配置。假设你需要配置 Docker Hub 的国内加速,以及一个公司内部的私有仓库 myregistry.local:5000

# 为docker.io创建配置目录
sudo mkdir -p /etc/containerd/certs.d/docker.io
# 为私有仓库创建配置目录
sudo mkdir -p /etc/containerd/certs.d/myregistry.local:5000

接下来,为 docker.io 创建 hosts.toml 文件,配置多个镜像加速器。Containerd会按顺序尝试,哪个能用用哪个。

sudo tee /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.io"

[host."https://dockerproxy.com"]
  capabilities = ["pull", "resolve"]
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
[host."https://reg-mirror.qiniu.com"]
  capabilities = ["pull", "resolve"]
EOF

对于HTTP协议的私有仓库(不安全,但内网常用),需要特别注明 http://,并设置 skip_verify = true 跳过TLS证书验证:

sudo tee /etc/containerd/certs.d/myregistry.local:5000/hosts.toml <<EOF
server = "http://myregistry.local:5000"

[host."http://myregistry.local:5000"]
  capabilities = ["pull", "resolve", "push"]
  skip_verify = true
EOF

所有配置完成后,重启Containerd服务让配置生效:sudo systemctl restart containerd。可以用 sudo systemctl status containerd 查看状态,确保是 active (running)

最后,我们配置一下 crictl,这是K8s社区推荐的容器运行时命令行工具,可以用来调试和查看容器、镜像。创建它的配置文件:

sudo tee /etc/crictl.yaml <<EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 2
debug: false
pull-image-on-create: false
EOF

现在,你可以用 sudo crictl images 命令测试一下,虽然现在还没镜像,但能正常列出(空列表),说明Containerd服务和我们配置的crictl工具都工作正常了。

4. 安装Kubernetes核心三剑客:kubeadm, kubelet, kubectl

容器运行时Ready了,接下来就该请出Kubernetes的核心组件了。我们将使用K8s官方提供的APT仓库来安装,这样方便后续升级和管理。

4.1 添加仓库并安装指定版本

首先,信任Kubernetes的APT仓库GPG密钥。这里注意,K8s 1.32的稳定仓库路径是 v1.32/deb/

# 安装一些必要的工具
sudo apt install -y apt-transport-https ca-certificates curl gpg

# 下载并添加GPG密钥
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 添加阿里云镜像的Kubernetes APT源(国内加速)
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/deb/ /" | \
sudo tee /etc/apt/sources.list.d/kubernetes.list

更新软件包列表并安装 kubelet, kubeadm, kubectl。这里有个重要操作:安装完成后,用 apt-mark hold 锁定这三个包的版本,防止它们被系统自动升级,导致集群版本不一致。

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

安装完成后,设置 kubelet 开机自启(虽然现在它还跑不起来,要等集群初始化后才行):sudo systemctl enable kubelet。你可以用 kubeadm versionkubectl version --client 来验证安装是否成功。

4.2 高可用基石:用HAProxy和Keepalived搭建负载均衡

如果你只部署一个Master节点(单控制平面),那么可以跳过这一步。但为了生产环境的高可用,或者你想体验多Master架构,那么为API Server配置一个负载均衡器是必须的。我选择的是经典组合:HAProxy做负载均衡,Keepalived做VIP(虚拟IP)漂移,实现高可用。

首先在所有计划作为控制平面的节点上安装这两个软件:

sudo apt install -y keepalived haproxy

配置HAProxy:编辑 /etc/haproxy/haproxy.cfg。关键是把后端指向所有Master节点的6443端口(K8s API Server端口)。下面是一个精简配置示例,同时开启了一个状态监控页面(在2080端口)。

sudo tee /etc/haproxy/haproxy.cfg <<EOF
global
    log 127.0.0.1 local3
    chroot /var/lib/haproxy
    pidfile /var/run/haproxy.pid
    maxconn 4000
    user haproxy
    group haproxy
    daemon

defaults
    mode tcp
    log global
    option tcplog
    timeout connect 5s
    timeout client 50s
    timeout server 50s

# 监控页面,可以通过 http://你的IP:2080/status 访问,账号admin,密码123456
listen admin_stats
    bind 0.0.0.0:2080
    mode http
    stats enable
    stats uri /status
    stats realm HAProxy
    stats auth admin:123456

# 这是核心配置,监听8443端口,将流量转发到三个Master的6443端口
listen kube-apiserver
    bind 0.0.0.0:8443
    mode tcp
    balance roundrobin
    server k8s-master01 10.62.1.181:6443 check inter 2000 rise 2 fall 3
    server k8s-master02 10.62.1.182:6443 check inter 2000 rise 2 fall 3
    server k8s-master03 10.62.1.183:6443 check inter 2000 rise 2 fall 3
EOF

配置Keepalived:这个配置稍微复杂点,因为它要决定哪个节点持有虚拟IP(VIP)。假设我们规划VIP是 10.62.1.180。在三台Master节点上,配置文件略有不同,主要是 state(主/备)和 priority(优先级)以及 unicast_peer(单播对端地址)的区别。

以第一台Master(10.62.1.181)为例,配置为MASTER,优先级最高(100):

sudo tee /etc/keepalived/keepalived.conf <<EOF
global_defs {
    router_id K8S_MASTER_01
}

vrrp_script check_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -5
}

vrrp_instance VI_1 {
    state MASTER
    interface ens160
    virtual_router_id 12
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    unicast_src_ip 10.62.1.181
    unicast_peer {
        10.62.1.182
        10.62.1.183
    }
    virtual_ipaddress {
        10.62.1.180/24 dev ens160
    }
    track_script {
        check_haproxy
    }
}
EOF

在第二台Master(10.62.1.182)上,state 改为 BACKUPpriority 设为 95unicast_src_ipunicast_peer 也要相应调整。第三台同理,优先级设为 90

健康检查脚本:Keepalived需要监控HAProxy进程是否存活。创建检查脚本:

sudo tee /etc/keepalived/check_haproxy.sh <<EOF
#!/bin/bash
COUNT=\$(ps -C haproxy --no-header | wc -l)
if [ \$COUNT -eq 0 ]; then
    systemctl start haproxy
    sleep 2
    COUNT=\$(ps -C haproxy --no-header | wc -l)
    if [ \$COUNT -eq 0 ]; then
        systemctl stop keepalived
    fi
fi
EOF
sudo chmod +x /etc/keepalived/check_haproxy.sh

最后,在所有节点上启动服务并设置开机自启:

sudo systemctl enable --now haproxy
sudo systemctl enable --now keepalived

ip a 命令在配置为MASTER的节点上查看,应该能看到虚拟IP 10.62.1.180 已经绑定到了 ens160 网卡上。这个VIP 10.62.1.180:8443 就是我们后续初始化集群时要用的 control-plane-endpoint

5. 临门一脚:使用Kubeadm初始化你的第一个集群

所有准备工作就绪,最激动人心的时刻到了——初始化集群。这里我强烈推荐使用配置文件的方式,而不是一长串命令行参数,因为配置文件更清晰,也方便以后回顾和版本管理。

5.1 拉取镜像与初始化配置

首先,我们可以让kubeadm先预拉取所需的镜像,这样能提前发现镜像拉取问题。如果你配置了Containerd的镜像加速,这里会快很多。指定我们之前配置的私有仓库或者阿里云镜像地址:

sudo kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers --kubernetes-version=v1.32.2

看到所有镜像 pull 完成后,就可以开始初始化了。创建一个初始化配置文件,比如 /opt/kubeadm-init.yaml

apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.32.2
imageRepository: registry.aliyuncs.com/google_containers
controlPlaneEndpoint: "10.62.1.180:8443" # 这里填你的HAProxy VIP和端口
networking:
  podSubnet: "10.244.0.0/16" # 设置Pod网络CIDR,需要和后续的网络插件匹配
  serviceSubnet: "10.96.0.0/16"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
nodeRegistration:
  criSocket: "unix:///var/run/containerd/containerd.sock" # 关键!指定Containerd的socket
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs" # 使用ipvs模式,性能优于默认的iptables

这个配置文件做了几件重要的事:1. 指定了K8s版本和镜像仓库。2. 定义了控制平面的访问端点(就是我们HAProxy的VIP)。3. 指定了Pod和Service的网段。4. 明确告诉kubelet使用Containerd。5. 将kube-proxy的模式设为ipvs。

5.2 执行初始化并处理输出

万事俱备,在第一台Master节点(我这里是k8s-master01)上,执行初始化命令:

sudo kubeadm init --config=/opt/kubeadm-init.yaml --upload-certs

--upload-certs 参数会将生成的证书加密后上传,方便其他控制平面节点加入时自动获取。这个过程可能需要一两分钟,期间会生成各种证书、启动静态Pod等。如果一切顺利,你会看到令人振奋的成功信息,其中包含了两条非常重要的 kubeadm join 命令。

一条是给其他控制平面节点(Master) 加入集群用的,它包含了 --control-plane--certificate-key 参数。另一条是给工作节点(Node) 加入集群用的。务必把这两条命令完整地保存下来! 尤其是那个token和discovery-token-ca-cert-hash,默认24小时失效,如果忘了可以用 kubeadm token createopenssl 命令重新生成,但直接保存最省事。

初始化成功后,按照提示,以普通用户身份配置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,因为还没有安装网络插件。

5.3 让其他节点加入集群

回到另外两台规划为Master的服务器(k8s-master02, k8s-master03)。确保它们已经完成了第2、3、4步的所有系统初始化、Containerd安装和配置、以及Kubernetes组件安装。

然后,在每台机器上,执行初始化成功后输出的那条给控制平面节点用的 kubeadm join 命令(需要sudo权限)。加入过程会自动从集群下载证书并启动控制平面组件。

对于工作节点(如k8s-node01),则执行那条给工作节点用的 kubeadm join 命令

所有节点加入后,回到第一个Master节点,再次执行 kubectl get nodes,你应该能看到所有节点,但状态可能还是 NotReady

6. 打通集群网络:安装Calico网络插件

节点都齐了,但彼此还不认识,因为缺少一个东西:Pod网络。K8s本身不负责网络,需要第三方插件来实现Pod之间的通信。这里我选择 Calico,它功能强大,支持网络策略,性能也很好,而且安装简单。

6.1 安装与验证

Calico的安装现在推荐使用Operator模式。我们直接使用官方提供的manifest文件。

# 下载Operator和自定义资源文件
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/tigera-operator.yaml
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/custom-resources.yaml

重要:在应用 custom-resources.yaml 之前,需要修改里面的 cidr 字段,确保和我们在 kubeadm-init.yaml 里设置的 podSubnet10.244.0.0/16)一致。用编辑器打开 custom-resources.yaml,找到 spec.calicoNetwork.ipPools.cidr 进行修改。

然后按顺序应用这两个文件:

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

安装过程需要拉取镜像,稍等一两分钟。你可以通过以下命令观察安装进度:

# 查看Operator Pod状态
kubectl get pods -n tigera-operator
# 查看Calico系统Pod状态
kubectl get pods -n calico-system -w

calico-system 命名空间下的所有Pod都变成 Running 状态后,再查看节点状态:

kubectl get nodes

这时,节点的状态应该从 NotReady 变成了 Ready。恭喜你,一个多节点的Kubernetes集群已经搭建完成了!

6.2 验证集群功能与后续步骤

集群Ready了,我们跑个最简单的测试来验证一下。部署一个Nginx的Deployment:

kubectl create deployment nginx-test --image=nginx:alpine
kubectl expose deployment nginx-test --port=80 --type=NodePort

然后查看Pod和Service:

kubectl get pods,svc -o wide

你应该能看到一个 nginx-test 的Pod在运行,并且有一个对应的Service,有一个 NodePort 类型的端口(比如 30080)。这时,你可以在任意节点的浏览器里访问 http://<节点IP>:<NodePort>,应该能看到Nginx的欢迎页面。

到这里,一个基于Ubuntu 22.04、Containerd和Kubeadm的Kubernetes 1.32轻量级集群就部署成功了。它具备了多Master高可用的基础架构。当然,这只是一个开始,后续你还需要根据实际需求,配置存储类(StorageClass)、安装Ingress控制器(如Nginx Ingress或Traefik)、部署监控系统(如Prometheus+Grafana)以及日志收集方案,才能构成一个完整可用的应用平台。不过,有了这个稳定、标准的底层集群,上面的那些组件安装和配置,就都是按部就班的“填空”工作了。

更多推荐