Ubuntu 22.04 上搭建一主一从 K8s 集群:从零到 Dashboard 的完整指南

发布时间:2026/10/25 4:26:18
Ubuntu 22.04 上搭建一主一从 K8s 集群:从零到 Dashboard 的完整指南 1. 为什么要在 Ubuntu 22.04 上折腾一套一主一从的 K8s先把话说在前头这套一主一从的集群不是给生产环境用的它的定位非常明确——学习、验证、开发联调、CI 前置环境。我在实际工作中带过不少刚接触容器编排的同学发现一个普遍现象文档看了一堆概念背得滚瓜烂熟但真让他从零把集群拉起来卡在镜像拉取、网络插件、证书、kubelet 起不来这些地方能耗掉一整天。所以我一直建议入门阶段别贪多先用两台机器把一主一从跑通把 dashboard 挂上去能登录、能看到节点、能部署一个带 Service 的示例应用这一圈走完你对 K8s 的整体骨架就有了肌肉记忆。Ubuntu 22.04 是目前长期支持版本里比较稳的一个选择内核 5.15 起步对 cgroups v2、overlayfs、iptables-nft 这些容器运行时依赖的东西支持得比较完整社区资料也多。一主一从的意思是一台机器当控制平面control plane跑 API Server、调度器、控制器管理器、etcd另一台当工作节点worker跑 kubelet、容器运行时和实际业务 Pod。这个规模小到不能再小但 K8s 的核心组件一个不少学习价值反而最高——因为你能清楚看到每个组件在哪台机器上、干什么活。这套东西能解决什么问题最直接的是给你一个可反复推倒重来的实验环境。云厂商托管的 K8s 服务确实省事但你看不到底层出了问题只能提工单而自建一主一从从系统内核参数到 CNI 插件到 dashboard 的 token 认证每一层你都能摸到。适合谁来参考我把它分成三类一是刚学 K8s、想搞懂组件关系的开发二是需要本地联调微服务、又不想依赖外部集群的团队三是准备考相关认证、需要动手环境的人。下面我按实际操作的顺序把每一步的意图、参数和踩过的坑都摊开讲。2. 整体设计与方案选型为什么这么搭2.1 组件选型背后的取舍逻辑一套能跑起来的 K8s 集群绕不开几个关键决策容器运行时选什么、CNI 网络插件选什么、安装方式用 kubeadm 还是二进制、dashboard 怎么暴露。我先把结论摆出来再解释为什么。决策项我的选择备选方案选择理由容器运行时containerdDocker Engine cri-dockerdcontainerd 是 CRI 原生实现链路短、依赖少Docker 那套中间层在新版本里反而增加故障点安装工具kubeadm二进制手工部署 / 第三方脚本kubeadm 是官方维护的引导工具证书、静态 Pod、引导令牌全帮你管好学习成本可控CNI 插件CalicoFlannel / CiliumCalico 同时提供网络与网络策略排查工具成熟一主一从规模下资源占用可接受Dashboard 暴露方式NodePortIngress / kubectl proxy实验环境用 NodePort 最直接少一层依赖浏览器直接访问系统Ubuntu 22.04 LTS20.04 / 24.0422.04 生态成熟cgroups v2 默认开启社区踩坑记录最全这里重点说两个容易被忽略的取舍。第一是容器运行时。很多人习惯性装 Docker然后发现 K8s 1.24 之后不再内置 dockershim还得额外装 cri-dockerd 做适配多一层就多一个出错点。containerd 本身就是 CRI 标准实现kubelet 直接跟它对话配置里SystemdCgroup true一开和 systemd 的 cgroup 管理对齐稳定性明显更好。第二是安装方式。二进制手工部署确实能让你彻底理解每个组件但对新手来说光是证书签发和 systemd 单元文件就能劝退。kubeadm 把这些脏活包了你只需要理解它做了什么而不是亲手做一遍——入门阶段这个平衡点很关键。2.2 一主一从的拓扑与网络规划拓扑很简单但网络规划必须提前定死否则后面改起来很痛苦。我习惯用下面这套规划你可以直接抄主节点内网地址假设为192.168.56.10主机名k8s-master从节点内网地址假设为192.168.56.11主机名k8s-node1Pod 网段10.244.0.0/16Calico 默认用这个保持一致省事Service 网段10.96.0.0/12kubeadm 默认值一般不动API Server 端口6443为什么 Pod 网段和 Service 网段不能重叠因为 kube-proxy 会在节点上写大量 iptables 规则做 Service 到 Pod 的转发两个网段一旦交叉路由就会打架表现为Pod 能起但 Service 访问不通这种问题排查起来极其费劲。所以规划阶段就把10.244.0.0/16和10.96.0.0/12分开后面 Calico 的CALICO_IPV4POOL_CIDR也要跟pod-network-cidr严格一致这是硬约束。另外主机名必须唯一且能互相解析。我见过有人两台机器都叫ubuntu结果 kubelet 注册节点时直接冲突。做法很简单hostnamectl set-hostname改好然后在两台机器的/etc/hosts里互相加上对方的 IP 和主机名别依赖外部 DNS实验环境自己闭环最稳。2.3 资源与前置条件的现实考量一主一从对资源的要求其实不高但有几个下限得守住。主节点我建议至少 2 核 4G从节点 2 核 2G 起步。为什么主节点要 4G因为控制平面要跑 etcd、API Server、调度器、控制器管理器再加上 Calico 的若干 Pod2G 内存在启动阶段就容易触发 OOM表现为kube-apiserver反复重启日志里全是内存分配失败。磁盘至少 20G容器镜像和 etcd 数据都吃空间。还有一个前置条件是关闭 swap。K8s 的调度器假设节点内存是可预测的swap 会让内存压力下的行为变得不可控所以 kubelet 默认拒绝在开启 swap 的节点上启动。Ubuntu 22.04 默认可能开了 swap 文件得手动关掉并注释掉/etc/fstab里的相关行否则重启后又回来了。这个细节后面实操部分会具体写。3. 系统层准备那些不起眼但致命的细节3.1 主机名、hosts 与时间同步系统层准备看着琐碎但十有八九的集群起不来都栽在这一步。我按顺序来。先在两台机器上分别设置主机名# 主节点执行 hostnamectl set-hostname k8s-master # 从节点执行 hostnamectl set-hostname k8s-node1然后编辑两台机器的/etc/hosts把对方的解析加进去192.168.56.10 k8s-master 192.168.56.11 k8s-node1为什么要手动写 hosts 而不是靠 DNS因为 kubeadm 初始化时会用主机名去解析 API Server 地址实验环境往往没有内网 DNS靠 hosts 是最可靠的闭环方式。写完用ping k8s-node1从主节点验证一下能通再往下走。时间同步也别跳过。K8s 的证书有效期、token 过期判断、etcd 的租约机制都依赖系统时间一致。两台机器时间差太大会出现token 明明没过期却认证失败的诡异现象。Ubuntu 22.04 自带systemd-timesyncd一般默认就开着用timedatectl看一眼System clock synchronized: yes就行。如果没同步timedatectl set-ntp true打开即可。3.2 关闭 swap 与内核模块加载关闭 swap 是硬性要求操作分两步缺一不可# 临时关闭 swapoff -a # 永久关闭注释掉 /etc/fstab 里 swap 那一行 sed -ri s/.*swap.*/#/ /etc/fstab只做swapoff -a的话机器一重启 swap 又回来了kubelet 照样起不来。所以fstab那一步必须做。验证方式free -h看 Swap 那一行是不是全 0swapon --show输出为空就对了。接着加载内核模块。K8s 的网络转发和流量过滤依赖overlay和br_netfilter两个模块cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilteroverlay是容器分层文件系统的基础br_netfilter让 iptables 能处理桥接流量——没有它Pod 之间的跨节点通信和 Service 转发都会出问题。写进/etc/modules-load.d/是为了开机自动加载别只modprobe一次就完事。3.3 内核参数调优与验证内核参数这块核心是让 iptables 能看见桥接的流量并开启 IP 转发cat EOF | 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 sysctl --system逐条解释一下意图。bridge-nf-call-iptables1让经过网桥的流量也走一遍 iptables 规则链这是 kube-proxy 实现 Service 负载均衡的前提ip_forward1开启 IPv4 转发节点要当路由器给 Pod 转发包不开这个跨节点通信直接断。改完用sysctl net.ipv4.ip_forward确认输出是 1。注意bridge-nf-call-iptables这个参数依赖br_netfilter模块已加载如果sysctl --system报 cannot stat /proc/sys/net/bridge/... 之类的错说明模块没加载成功回头检查上一步。到这里系统层就准备完了。我习惯在两台机器上都做一遍别偷懒只做主节点——从节点同样需要这些配置漏了的话加入集群时会卡在 kubelet 启动阶段。4. 容器运行时与 kubeadm 安装实操4.1 安装 containerd 并配置 SystemdCgroup先装依赖和 containerd。Ubuntu 22.04 的官方源里就有 containerd但版本可能偏旧我一般用 Docker 官方源里的containerd.io版本新且稳定apt-get update apt-get install -y ca-certificates curl gnupg lsb-release install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod ar /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | \ tee /etc/apt/sources.list.d/docker.list /dev/null apt-get update apt-get install -y containerd.io装完 containerd 后必须生成默认配置并改一处关键参数mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml # 把 SystemdCgroup 从 false 改成 true sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd为什么SystemdCgroup一定要改成true因为 Ubuntu 22.04 默认用 systemd 管理 cgroup如果 containerd 还用 cgroupfs 驱动两套 cgroup 管理器会打架表现为节点资源统计不准、Pod 被莫名杀掉。这个参数是 containerd 和 kubelet 协同工作的关键改完重启 containerd 生效。验证一下containerd config dump | grep SystemdCgroup输出true就对了。4.2 配置 kubelet 的 cgroup 驱动对齐containerd 改好了kubelet 这边也得对齐。kubeadm 的配置里要显式指定cgroupDriver: systemd否则 kubelet 默认可能用 cgroupfs又回到刚才那个打架的问题。这个配置在初始化时通过配置文件传入后面 4.4 节会给出完整配置。另外还要处理一个 Ubuntu 22.04 特有的坑/etc/default/kubelet里如果残留旧配置可能干扰启动一般全新安装不会有但如果你之前装过又卸了建议apt-get purge kubelet kubeadm kubectl清干净再装。4.3 安装 kubeadm、kubelet、kubectl这三个组件版本必须一致我一般锁定一个稳定的小版本比如 1.28.x。先加源curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | \ gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo deb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \ https://pkgs.k8s.io/core:/stable:/v1.28/deb/ / | \ tee /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectlapt-mark hold这一步别省。不加 hold 的话某次apt upgrade可能把 kubelet 升到新版本而 kubeadm 还是旧的版本错配会导致节点注册失败。锁住版本升级时手动来可控得多。装完用kubeadm version、kubelet --version、kubectl version --client三个命令确认版本一致。这一步两台机器都要做从节点同样需要这三个组件。4.4 主节点初始化配置文件比命令行参数更靠谱kubeadm 初始化有两种方式一长串命令行参数或者一个 YAML 配置文件。我强烈推荐后者因为参数可读、可版本管理、出错好回看。配置文件kubeadm-config.yaml内容如下apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.56.10 bindPort: 6443 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock name: k8s-master --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.0 controlPlaneEndpoint: 192.168.56.10:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd几个关键点解释一下。advertiseAddress填主节点内网 IPAPI Server 会在这个地址上监听criSocket指向 containerd 的 socket告诉 kubelet 用哪个运行时podSubnet必须和后面 Calico 的网段一致最后那段KubeletConfiguration里的cgroupDriver: systemd就是前面说的对齐。三份配置用---分隔kubeadm 会分别解析。执行初始化kubeadm init --config kubeadm-config.yaml --upload-certs--upload-certs是把证书加密上传到集群的 Secret 里方便后续加节点时自动下发。初始化过程会拉取控制平面镜像网络慢的话会卡一会儿耐心等。成功后会输出一段kubeadm join命令务必复制保存从节点加入就靠它。初始化完成后配置 kubectl 的凭据mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config然后kubectl get nodes应该能看到主节点状态是NotReady——这是正常的因为还没装 CNI 插件。5. 网络插件、从节点加入与 Dashboard 部署5.1 安装 Calico 并验证网络就绪主节点NotReady的根因就是缺 CNI。Calico 的安装用官方 manifest 最省事但要注意版本和网段匹配kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml如果前面podSubnet用的不是10.244.0.0/16得先下载 manifest 改CALICO_IPV4POOL_CIDR再 apply。用默认网段的话直接 apply 即可。装完等一会儿用下面命令观察kubectl get pods -n kube-system -w看到calico-node-xxxx和calico-kube-controllers-xxxx都Running后再kubectl get nodes主节点应该变成Ready。如果一直NotReady八成是 Calico Pod 起不来用kubectl describe pod看事件常见原因是镜像拉取失败或网段配置冲突。5.2 从节点加入集群与常见卡点从节点上执行主节点初始化时输出的 join 命令形如kubeadm join 192.168.56.10:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxtoken 默认 24 小时过期过期了在主节点用kubeadm token create --print-join-command重新生成一条。加入过程如果卡在[preflight] Running pre-flight checks多半是前面系统层准备没做全——swap 没关、内核模块没加载、hosts 解析不通逐个排查。加入成功后回主节点kubectl get nodes两台都Ready就说明集群骨架成了。这时候可以跑个验证kubectl run nginx --imagenginx起个 Podkubectl get pods -o wide看它调度到哪台、拿到什么 IP再kubectl exec进去curl一下另一个 Pod跨节点通信通了网络层就算过关。5.3 Dashboard 部署与 token 登录Dashboard 的 manifest 官方维护在 kubernetes/dashboard 仓库我一般用 v2.7 这个稳定版本kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml默认它创建的是 ClusterIP 类型的 Service集群外访问不了。实验环境最直接的做法是改成 NodePortkubectl -n kubernetes-dashboard patch svc kubernetes-dashboard \ -p {spec:{type:NodePort}} kubectl -n kubernetes-dashboard get svc kubernetes-dashboard拿到映射的 NodePort比如 30000 多浏览器访问https://192.168.56.10:NodePort就能打开登录页。注意是 https证书是自签的浏览器会警告手动信任继续即可。登录需要 token。先创建一个有集群管理员权限的 ServiceAccountapiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboardapply 之后生成 tokenkubectl -n kubernetes-dashboard create token admin-user把输出的长串 token 粘到登录页就能进 dashboard 了。这里有个安全提醒cluster-admin权限极大实验环境图省事可以这么干但别把这套配置原样搬到任何对外暴露的环境token 泄露等于集群裸奔。6. 常见问题排查与实操避坑清单6.1 高频故障速查表这套流程我走过很多遍下面这些坑几乎每次带新人都会遇到整理成表方便对照现象可能原因排查与解决主节点一直 NotReadyCNI 未安装或 Calico Pod 异常kubectl get pods -n kube-system看 calico 状态describe看事件kubelet 启动失败swap 未关 / cgroup 驱动不一致journalctl -u kubelet -n 50看日志确认 swap 和 SystemdCgroup从节点 join 卡住hosts 解析不通 / 防火墙拦截 6443主节点ping从节点检查安全组或 ufw 规则Pod 起不来镜像拉取失败网络问题或镜像仓库不可达kubectl describe pod看 Events必要时配置镜像加速Service 访问不通Pod 网段与 Service 网段重叠检查 kubeadm 配置和 Calico 的 CIDR 是否一致Dashboard 登录 401token 过期或权限不足重新create token确认 ClusterRoleBinding 生效节点时间漂移导致认证失败时间未同步timedatectl确认 NTP 同步状态6.2 几个文档里不会写的实操心得第一个心得初始化失败后别急着kubeadm reset重来。先看/var/log/和journalctl -u kubelet很多问题是一次性的比如镜像拉取超时重试就好。真要 reset记得kubeadm reset -f之后手动清理$HOME/.kube、/etc/cni/net.d、/var/lib/etcd否则残留配置会干扰下一次初始化。第二个心得Calico 的镜像拉取是最大的不确定性。manifest 里引用了好几个镜像网络不好时容易卡在ImagePullBackOff。我的做法是提前kubectl describe看它要哪些镜像用crictl pull在节点上先拉一遍拉通了再 apply能省不少等待。第三个心得dashboard 的 NodePort 别用默认随机分配。默认可能分到 30000 以上的随机端口每次重建都变记不住。可以在 patch 时指定nodePort比如固定 30443方便书签。但要注意端口范围 30000-32767别填超了。第四个心得一主一从的 etcd 是单点。主节点挂了整个集群就不可用这不是 bug 是架构决定的。所以这套环境只适合学习别在上面跑任何你丢不起的数据。想练高可用至少三台控制平面那是另一个话题了。6.3 环境验证的完整闭环集群搭完别急着庆祝跑一遍完整验证才算数。我习惯按这个顺序先kubectl get nodes确认两台 Ready再kubectl get pods -A确认所有系统 Pod 都 Running然后部署一个带 Deployment 和 Service 的示例用kubectl expose暴露成 NodePort从浏览器访问验证 Service 转发链路最后进 dashboard 看一眼节点和 Pod 列表是否正常展示。这一圈走完从网络到认证到 UI 全链路都验证过了后面拿它做实验心里才有底。我个人在实际操作中的体会是这套一主一从的价值不在于搭起来那一刻而在于你把它拆了重装三五遍之后对每个组件的依赖关系形成的那种直觉。第一次可能要折腾大半天第三次基本半小时能搞定到那时候你再看 K8s 的官方文档很多之前看不懂的段落会突然变得清晰——因为你亲手摸过每一层。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询