☰
Kubernetes入门:Ubuntu环境下k8s集群搭建实操与避坑指南
2026/10/20 22:20:39 网站建设 项目流程

有朋友让我聊聊 k8s 到底是个啥,顺便给一套能在 Ubuntu 上把环境跑起来的实操流程。这话题我前前后后折腾了不少次,从最早只会跑 docker 容器,到后来维护几个节点的集群,中间踩的坑不算少。这篇就结合我自己的实操经验,从“是什么”讲到“怎么搭”,再聊几个新手最容易掉进去的深坑。照着一步步来,通常能把一套能用的环境搭起来。

1. 先弄清楚 k8s 是什么,以及它到底解决了什么问题

k8s 是 Kubernetes 的简称,名字太长,大家习惯叫 k8s(中间 8 个字母省了)。它本质上是一套容器编排系统,解决的是“容器多了以后怎么管理”的问题。

举个生活化的类比:你要是自己在家做饭,一个锅一个灶,自己盯着就行,不需要什么管理系统。但如果是开一个后厨,几十个灶同时开工,有切菜的、有炒菜的、有装盘的,你就需要一个“后厨调度总管”,负责安排谁先做、哪个灶空闲、食材够不够、出菜顺序怎么排。

k8s 就是这个总管,只不过它管的不是菜,而是容器。它管的事情包括:

  • 部署:告诉系统“我要跑 3 个 Nginx 容器”,它会自动帮你拉起 3 个,并且分布在不同机器上。
  • 自动重启:某个容器挂了,它会自动重新拉起一个,保证对外服务的数量不减少。
  • 服务发现与负载均衡:容器跑起来后 IP 会变,k8s 提供稳定的访问入口,并把请求分发到健康的容器上。
  • 扩缩容:流量大了,可以手动或者自动增加容器数量;流量小了,再缩回去。
  • 滚动更新:发布新版本时,逐步替换旧容器,期间服务不中断。

在没有 k8s 的年代,我们要管理十几台服务器上的容器,基本靠脚本和人工,发布一次版本需要半夜起来操作。有了 k8s 之后,很多事情变成了声明式的:你告诉它“最终状态是什么样”,它自己去想办法达成。

再说得直白一点,很多时候你看到公司招聘要求里写着“熟悉 k8s”,实际意思是:你能不能在 k8s 上部署应用、排查问题、调优资源。它已经成了现代后端基础设施的事实标准,和当年“会不会 Linux”差不多是一个地位。

2. 理解 k8s 的整体架构,才知道搭建时每个组件是干嘛的

说完是什么,得说说它由哪些部分组成。很多人搭建环境时一股脑照着文档敲命令,结果出了问题完全不知道哪一环坏了,就是因为对架构没有整体概念。

2.1 控制平面(Control Plane)

控制平面是整个集群的“大脑”,负责决策和调度。通常由以下几个组件组成:

API Server:所有操作的入口,kubectl 命令实际上都是发给它的。其他组件之间不直接通信,都通过它中转。

etcd:集群的数据库,保存所有配置信息和状态。可以理解成“账本”——集群里发生了什么、现在应该是什么样、实际是什么样,全部记录在这里。生产环境里 etcd 挂了,整个集群基本就瘫痪了。

Scheduler:调度器,决定一个新的 Pod 应该放在哪台机器上运行。它会综合考虑资源够不够、有没有特殊要求等因素。

Controller Manager:控制器管理器,负责维持集群的实际状态。比如你声明“要 3 个副本”,它发现现在只有 2 个,就会触发创建第 3 个。可以理解成纠偏员。

2.2 工作节点(Node)

真正跑应用的地方,每台工作节点上都有:

kubelet:每个节点上的代理,负责接收控制平面的指令,管理本机上的容器,并把状态汇报上去。

kube-proxy:负责网络规则,实现 Service 的负载均衡和转发。不同节点上的 Pod 要互通、外部要访问服务,都靠它维护规则。

容器运行时:真正运行容器的地方,常见的是 containerd,也可以理解成 docker 的替代品。k8s 本身不直接运行容器,它通过容器运行时接口(CRI)和 containerd 打交道。

2.3 核心对象:Pod、Deployment、Service

你实际使用 k8s 时,打交道最多的是这几个对象。

Pod是 k8s 的最小调度单元,一个 Pod 里可以有一个或几个容器,这些容器共享网络和存储。通常会有一个业务容器加一两个辅助容器。

Deployment是管理无状态应用的对象。你写一个 YAML 描述“镜像是什么、副本数多少、更新策略怎么定”,Deployment 就会帮你管好这一切。

Service是稳定的访问入口。Pod 会随时被销毁重建,IP 不稳定,Service 提供固定的虚拟 IP 和 DNS 名字。你可以用 Service 名直接访问后端服务,不需要关心具体是哪个 Pod 在处理。

理解这几个对象之后,搭建环境时就不会晕了:kubeadm 初始化后,系统组件本身就是以 Pod 的方式跑在集群里的,比如 kube-apiserver、etcd 都作为静态 Pod 运行在控制节点上。

3. 搭建前的准备工作和环境规划

这一节重点说实操。我这里的操作环境是 Ubuntu 22.04 LTS,用的 k8s 版本是 1.28 系列。版本选择上提一句:如果不是特殊原因,尽量用官方还在支持维护的版本,别用太老的也没必要追最新,1.28 属于比较稳定成熟的版本。

3.1 硬件和系统要求

先说硬件:

  • 控制节点:2 核 CPU、2GB 内存以上,推荐 4GB 以上。如果内存不够 2GB,初始化大概率会失败。
  • 工作节点:同样建议至少 2 核 2GB。
  • 磁盘:每个节点 20GB 以上可用空间,镜像多的话再放大些。

系统要求:

  • Ubuntu 22.04 或 20.04 都行,内核版本别太旧。
  • 所有节点之间网络互通,主机名不能重复。
  • 确保能访问外网,至少能访问到拉取镜像的仓库。

3.2 修改主机名和 hosts

搭建之前先把主机名设置好,方便后面识别节点。比如控制节点叫 master-01,工作节点叫 worker-01。修改主机名后建议把节点 IP 加到 /etc/hosts 里,避免后续互相解析出问题。

# 控制节点 sudo hostnamectl set-hostname master-01 # 工作节点 sudo hostnamectl set-hostname worker-01

3.3 关闭交换分区

这一步是硬性要求。k8s 默认不允许在开启了 swap 的节点上运行,kubelet 会直接报错不启动。原因涉及到内存管理:swap 会导致内存资源统计不准确,影响 Pod 的 QoS 判断。

sudo swapoff -a

注意这只是临时关闭,重启后 swap 会恢复。要永久关闭,需要注释掉 /etc/fstab 里 swap 那一行。

3.4 加载内核模块和调整系统参数

k8s 网络组件需要一些内核模块支持,主要是 overlay 和 br_netfilter。

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter

然后调整网络参数,让 iptables 能正确处理桥接流量:

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

这套参数是让集群网络正常工作的重要前置条件。之前我遇到过 Pod 之间网络不通的问题,最后排查下来就是网桥流量没放行导致的。

4. 安装容器运行时 containerd 并完成基础配置

k8s 1.24 版本之后已经移除了对 Docker 的直接支持,转而使用 containerd 作为默认容器运行时。当然 Docker 仍然可以作为一个独立的容器工具使用,但 k8s 运行容器时不再直接依赖它了。

4.1 安装 containerd

sudo apt update sudo apt install -y containerd

安装完成后先别急着用,containerd 的默认配置需要调整。

4.2 生成默认配置并修改关键两项

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

生成配置文件之后,有两个地方必须改:

第一处:SystemdCgroup 改为 true

找到[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]这一段,把SystemdCgroup = false改成SystemdCgroup = true。

为什么要改?因为 Ubuntu 系统默认的 init 系统是 systemd,容器如果不用 systemd cgroup 驱动,会和 kubelet 的 cgroup 配置不一致,导致节点状态一直 NotReady。这个坑我踩过不止一次,初始化成功了但节点就是不 Ready,排查半天就是这里的问题。

可以用 sed 直接替换:

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

第二处:配置镜像加速

因为网络环境原因,默认的沙箱镜像地址经常拉取超时,而且拉取公共镜像速度慢,建议配置镜像仓库加速地址。

找到[plugins."io.containerd.grpc.v1.cri".registry]这一段,修改config_path或者直接加[plugins."io.containerd.grpc.v1.cri".registry.mirrors]。

如果你拿到了可用的镜像加速地址(常见的有 docker 官方源、国内各大云厂商提供的加速服务,具体以自己可用为准),可以配置成:

[plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://你的加速地址"]

我自己实操时还改过 sandbox_image 的地址,这一步也是为了解决镜像拉取问题。

改完配置后重启 containerd:

sudo systemctl restart containerd sudo systemctl enable containerd

5. 安装 kubeadm、kubelet、kubectl

这三个工具的分工是:

  • kubeadm:负责初始化集群和添加节点,相当于一个引导工具。
  • kubelet:节点上的核心代理组件,以 systemd 服务方式运行。
  • kubectl:命令行工具,用来和 API Server 交互,发布应用、查看状态全靠它。

5.1 添加 k8s 的 apt 源

这里有一个版本差异要提醒:k8s 1.28 及更早版本,官方源在apt.kubernetes.io;1.29 开始迁移到了pkgs.k8s.io。我用的是 1.28,所以按旧源来配:

sudo apt update sudo apt install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

提示:这里的源地址我用的是国内可访问的镜像源,因为官方源在某些网络环境下面非常慢。如果你所在网络访问官方源没问题,直接用官方文档里的地址就行。

5.2 安装指定版本

sudo apt update sudo apt install -y kubelet kubeadm kubectl

如果不指定版本号,默认安装的是当前最新版本。为了稳定起见,建议指定版本:

sudo apt install -y kubelet=1.28.2-00 kubeadm=1.28.2-00 kubectl=1.28.2-00

安装完成后设置版本锁定,防止 apt 升级时把组件版本改了,导致集群不一致:

sudo apt-mark hold kubelet kubeadm kubectl

6. 初始化控制平面节点

到这里,准备工作就全部完成了。下一步是初始化集群的控制节点。

6.1 拉取组件镜像

初始化前可以先手动拉取需要的镜像,避免初始化时因为网络问题卡在拉镜像步骤:

sudo kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers

--image-repository参数很关键,如果直接使用默认的registry.k8s.io,在国内环境大概率会卡住。

6.2 执行 kubeadm init

初始化命令如下:

sudo kubeadm init \ --apiserver-advertise-address=192.168.1.100 \ --image-repository=registry.aliyuncs.com/google_containers \ --kubernetes-version=v1.28.2 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12

参数说明:

  • --apiserver-advertise-address:控制节点的内网 IP,写上你的实际 IP。
  • --image-repository:镜像源,换成可访问的仓库地址。
  • --kubernetes-version:指定版本,要和安装的 kubeadm 版本对应。
  • --pod-network-cidr:Pod 网段,这里用 10.244.0.0/16 是为后续网络插件准备的。比如后面用 Flannel,这个网段就是它的默认网段;用 Calico 的话,通常默认是 192.168.0.0/16,两者别混。
  • --service-cidr:Service 网段,一般用默认值就行,不要和 Pod 网段、物理网络网段重叠。

初始化过程大约需要两到三分钟,期间主要在做的事是:拉镜像、生成证书、配置 etcd、启动 API Server 等系统组件。看到类似下面的输出就说明成功了:

Your Kubernetes control-plane has been initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config

初始化成功后 kubeadm 会打印一条加入集群的命令,类似:

kubeadm join 192.168.1.100:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx

这条命令要保存好,工作节点加入集群时需要用到。如果丢了也没关系,后面可以重新生成。

6.3 配置 kubectl

按初始化输出的提示,配置 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,因为还没有装网络插件。

6.4 安装 CNI 网络插件

这一步不行跳过,否则节点永远无法变成 Ready。我这边选的是 Calico,功能更完整,支持网络策略。在网络插件选择上我多说两句:Flannel 更轻量,配置简单;Calico 功能更多,性能也不错,但配置复杂一些。单机测试或者学习环境用 Flannel 完全够,真想在生产上跑,建议认真评估 Calico。

Calico 的安装方式通常是直接应用官方提供的 YAML:

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml

如果你网络无法访问 GitHub 源,可以先把 YAML 文件下载到本地再 apply,或者找可访问的镜像地址。

应用完之后等待 Pod 启动:

kubectl get pods -n kube-system -w

等所有 Pod 都变成 Running,节点状态也会变成 Ready:

kubectl get nodes

我这里选用 Calico 时,需要确认 Pod 网段和 Calico 默认网段一致。如果你初始化时用的--pod-network-cidr=10.244.0.0/16,而 Calico 默认使用的是 192.168.0.0/16,就需要去改 Calico 的 ConfigMap。这一点网上很多教程没提,实际操作中容易踩坑。

7. 工作节点加入集群

控制节点 Ready 之后,工作节点加入集群。

7.1 在工作节点上重复系统配置

所有工作节点也要做前面第 3、4、5 节的操作:关闭 swap、加载内核模块、设置系统参数、安装 containerd、安装 kubeadm 和 kubelet。工作节点上不用装 kubectl,装了也暂时用不上,但装了也没坏处。

7.2 执行 join 命令

在控制节点上重新生成 token 和 join 命令,因为之前那条不一定还在有效期。默认 token 有效期是 24 小时:

sudo kubeadm token create --print-join-command

输出的命令拿到工作节点上执行:

sudo kubeadm join 192.168.1.100:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx

如果控制节点的 CA 证书哈希值不记得了,可以用下面的命令获取:

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

回到控制节点验证:

kubectl get nodes

看到新节点状态变成 Ready 就完成了。

8. 部署第一个应用验证环境

集群搭好了,不实际跑一个应用验证一下总觉得不放心。我用 Nginx 来做个冒烟测试。

8.1 创建 Deployment

kubectl create deployment nginx-test --image=nginx:latest

查看状态:

kubectl get pods

Pod 状态变成 Running,说明部署成功。

8.2 暴露 Service

kubectl expose deployment nginx-test --type=NodePort --port=80 --target-port=80

查看 Service:

kubectl get svc

会看到类似这样的输出:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-test NodePort 10.98.74.122 <none> 80:31234/TCP 10s

然后用任意节点的 IP 加端口访问:

curl http://192.168.1.100:31234

看到 Nginx 的欢迎页说明整套环境已经通了。

8.3 配置 kubectl 命令自动补全

这里补充一个提高效率的小技巧。kubectl 命令比较长,配置自动补全之后用起来会舒服很多:

echo "source <(kubectl completion bash)" >> ~/.bashrc source ~/.bashrc

9. 常见问题与排查技巧实录

实践过程中总会遇到各种问题,这里把最常见的几个列出来并分享我的排查思路。

9.1 节点一直 NotReady,kubelet 起不来

最常见的两个原因:

  • cgroup 驱动不一致:确认 containerd 配置里SystemdCgroup = true,这是最常见的。
  • 镜像拉不下来:查看 kubelet 日志。

排查方法:

sudo journalctl -u kubelet -f

用 systemd 的 journal 直接看 kubelet 日志,这是排查 k8s 节点问题最常用的手段。

9.2 Pod 一直 Pending 或 ContainerCreating

Pending 通常意味着没有节点能满足调度条件,常见的是资源不足。我之前遇到过一次,控制节点只有 1GB 内存,初始化的系统组件就占掉了大半,普通 Pod 根本没有资源分配。

ContainerCreating 大概率是镜像拉取慢或者镜像源有问题:

kubectl describe pod <pod-name>

看 Events 部分,能看到拉取镜像的具体报错。

9.3 token 过期或者 join 报错

如果之前 join 命令失败,重新生成 token 时,--print-join-command会直接给出新命令。也可以加上--ttl=0让 token 永不过期(测试环境可以用,生产环境不建议)。

还有一种情况是 CA 证书 hash 不匹配,需要重新获取正确值。

9.4 Service 无法访问

先确认 Pod 是否正常运行,再确认 Service 的后端是否有 Endpoints:

kubectl get endpoints nginx-test

如果 Endpoints 为空,通常是 Service 的 selector 和 Pod 的 label 对不上。查看 Service 的 selector 和 Deployment 的 label 对不对得上。

9.5 Pod 之间网络不通

网络插件安装有问题是最常见的原因。先确认网络插件 Pod 是否正常运行:

kubectl get pods -n kube-system -o wide

再用kubectl exec进入容器实际测试连通性,比如从业务 Pod ping Service 的 ClusterIP。

9.6 containerd 拉取镜像超时

这个问题在部分网络环境下非常突出。我的做法是:

  1. 配置镜像加速地址。
  2. 把 sandbox_image 地址也改掉。
  3. 适当调大timeout参数,在 /etc/containerd/config.toml 里能找到相关配置。

10. 写在最后的几点体会

环境搭好只是第一步。我在实际用下来最大的感受是,k8s 的概念和使用习惯和传统运维思路有很大不同。传统方式是你命令机器“给我跑一个 Nginx”,k8s 是你告诉它“我要确保集群里始终有 3 个 Nginx 副本”,至于怎么达成,它自己想办法。思路转换过来之后,再看什么 Deployment、ReplicaSet、StatefulSet 就不会觉得绕了。

学习路径上,我个人的建议是先搞清楚 Pod、Deployment、Service 这三个核心对象,把应用发布和访问跑通,然后逐步去接触 ConfigMap、Secret、Ingress、持久化存储这些更深入的内容。不要一开始就照着大而全的文档啃,容易越看越晕。

还有一点:多describe,少猜。k8s 的问题排查入口基本都是kubectl describe xxx xxx,它会把事件、调度记录、镜像拉取状态等信息都展示出来。大多数问题看 Events 就能定位到原因。

这套流程跑通之后,你已经在用和很多生产环境相同的技术栈了。下一步可以试试在上面部署一个有价值的应用,而不是停留在跑个 Nginx 冒烟测试。比如搭个简单的 Web 服务,配合 ConfigMap 管理配置,给自己立个小目标——把某个应用完整地用 k8s 管理起来。那才是真正开始体会到 k8s 的好处。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询