有朋友让我聊聊 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-013.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 containerd5. 安装 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 kubectl6. 初始化控制平面节点
到这里,准备工作就全部完成了。下一步是初始化集群的控制节点。
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 podsPod 状态变成 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 ~/.bashrc9. 常见问题与排查技巧实录
实践过程中总会遇到各种问题,这里把最常见的几个列出来并分享我的排查思路。
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 拉取镜像超时
这个问题在部分网络环境下非常突出。我的做法是:
- 配置镜像加速地址。
- 把 sandbox_image 地址也改掉。
- 适当调大
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 的好处。