
最近群里好几个朋友在问Debian 12上怎么部署K8s有的是刚入行想搭一套本地环境练手有的是想把旧服务器从CentOS换到Debian系。这东西说难不算难但坑是真不少尤其在国内网络环境下镜像源、沙箱镜像、cgroup驱动这几个问题要是没处理好光是一个节点NotReady就能卡你一整天。我花了大概两三天时间从裸机到把K8s集群跑起来中间踩了各种稀奇古怪的坑最后整理成这篇完整的实操记录。这篇文章会从Debian 12系统初始化开始到容器运行时的安装配置、K8s核心组件安装、集群初始化、CNI网络插件部署、第一个应用验证再到单节点场景下的存储方案和压测注意事项全程给出可复制的命令和配置。适合想快速搭一套可用K8s的运维、开发也适合想搞明白K8s部署原理、准备面试的朋友。1. 环境准备与整体方案选型1.1 为什么用Debian 12跑K8s单节点够不够用Debian 12代号Bookworm是当前的稳定版本内核版本6.1默认启用了cgroup v2对containerd和Kubernetes的支持非常友好。相比Ubuntu系发行版Debian更干净没有那么多预装的服务跑容器化平台时资源占用更低、干扰更少。如果你之前用CentOS 7玩过K8s会发现在Debian上部署反而更顺手——因为没有那些老旧的iptables兼容层和SELinux缠着你。单节点K8s到底够不够用要看你的需求。如果是学习K8s命令、理解Pod/Deployment/Service这些概念、跑一跑个人项目单节点完全够了。如果是要做微服务整套环境的验证比如像网上流行的单节点K8s跑若依微服务整套环境那种玩法只要能接受所有Pod挤在一台机器上单节点也可以扛得住。真要上生产至少三节点起步这是后话。本文先解决在本机把集群跑起来这件事。1.2 硬件要求与系统规划K8s对硬件的最低要求是2核CPU、2GB内存、20GB磁盘但这只是能跑的门槛。我个人建议至少4核8G起步因为一个完整的K8s集群光是系统组件就要吃掉不少资源etcd、kube-apiserver、kube-controller-manager、kube-scheduler、CoreDNS、CNI插件再加上你后面要部署的业务Pod2G内存跑起来会非常痛苦经常莫名其妙OOM。部署之前先把系统规划清楚。以下是我推荐的规划方式操作系统Debian 12 x86_64最小化安装不用装桌面环境主机名建议设置成有意义的名称比如k8s-master后面统一管理方便IP地址用静态IP别用DHCP否则重启后IP变了集群直接废掉磁盘根分区最好单独给大一些/var/lib/containerd容器数据和/var/lib/kubeletPod数据需要不少空间网络确保能访问外网至少能访问你选择的软件源设置主机名和静态IP的操作可以在安装系统的时候做也可以在装完后改。我习惯在安装时就配好省得后面折腾。注意/etc/hosts里一定要有本机主机名到IP的解析这个漏掉的话kubeadm init时经常报错。1.3 版本选型思路K8s版本迭代非常快每三个月出一个新版本。选版本不能光看新还要看稳定性和生态兼容性。这里有个重要原则kubelet、kubeadm、kubectl这三个组件必须用同一个版本不能混着装否则集群初始化大概率失败。我这次用的是Kubernetes 1.29系列。选它的理由功能稳定、资料多包含热词里提到的《K8s权威指南》虽然出到第五版但讲的还是比较早的版本网上大把案例都是基于1.20~1.30之间的版本做的、对containerd的兼容性最好。containerd我用的是1.7系列这是当下最稳妥的版本线。CNI插件选了Flannel因为配置最简单单节点场景完全够用后面会详细对比。K8s和Docker的关系这里顺便梳理一下K8s从1.24版本开始就不再直接使用Docker作为容器运行时而是通过containerd来管理和运行容器。Docker本身依然可以用来构建镜像、打tag、做镜像仓库的客户端但真正跑Pod的是containerd。很多人把Docker和K8s二选一理解成对立关系其实不是。我在生产里常见的组合是开发用Docker构建镜像推到镜像仓库K8s集群里的containerd负责拉取并运行这些镜像。这套流程非常主流本文后面的实践也是按这个思路走的。2. 系统初始化与容器运行时安装2.1 Debian 12基础配置系统装完之后第一步先把软件源换成国内源如果你在国外服务器上操作这一步可以跳过。Debian 12的源配置文件是/etc/apt/sources.list我把里面的内容替换成阿里云镜像源# 先备份原有的源 cp /etc/apt/sources.list /etc/apt/sources.list.bak cat /etc/apt/sources.list EOF deb https://mirrors.aliyun.com/debian/ bookworm main non-free contrib deb https://mirrors.aliyun.com/debian/ bookworm-updates main non-free contrib deb https://mirrors.aliyun.com/debian-security bookworm-security main non-free contrib EOF apt update然后装一些最基本的工具这些工具后面都会用得上apt install -y curl wget vim apt-transport-https ca-certificates gnupg lsb-release接下来是K8s节点初始化必做的三个操作关swap、加载内核模块、配置sysctl。先关swap。Kubelet的默认行为是检测到swap开启就报错虽然新版K8s支持某些情况下的swap配置但为了省事直接把swap干掉swapoff -a sed -i / swap / s/^/#/ /etc/fstab然后加载K8s需要的内核模块cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilteroverlay模块提供overlayfs文件系统支持容器镜像的层式存储就靠它。br_netfilter让网桥流量也能经过iptables规则K8s的Service转发依赖这个。配置sysctl让内核参数在重启后依然生效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做完之后用sysctl net.ipv4.ip_forward确认一下输出是1。2.2 安装containerd并正确配置K8s是直接对接containerd来管理容器的所以这一步是整个部署中最关键的环节之一。Debian 12的软件源里其实自带containerd但版本偏老。建议下载官方最新稳定版我装的是containerd 1.7.x系列。这里推荐用Docker官方源来装containerd因为Docker源里的containerd版本维护得比较勤install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/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/debian bookworm stable | tee /etc/apt/sources.list.d/docker.list /dev/null apt update apt install -y containerd.io装完containerd先不要急着启动。必须修改配置文件否则后面K8s集群根本起不来。两个必改项一个是SystemdCgroup要设成true另一个是sandbox_image要换成国内能拉到的镜像地址。先生成默认配置mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml然后修改两个地方。第一处是cgroup驱动。K8s的kubelet默认使用systemd作为cgroup driver而containerd的默认配置是cgroupfs这俩不一致会导致Kubelet报错、节点保持NotReady状态。改成systemd[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true第二处是pause镜像地址。containerd默认的sandbox_image是k8s.gcr.io/pause:3.6这个地址国内访问不了必须换成国内镜像。我用的是阿里云的[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.9改完后重启containerdsystemctl daemon-reload systemctl restart containerd systemctl enable containerd systemctl status containerd这里要提醒一句很多初学者照网上老教程操作在containerd里面折腾crictl的配置越弄越乱。其实K8s 1.29配合containerd 1.7这条线只要SystemdCgroup配对了、镜像源配好了后面就非常顺。cgroup驱动怎么理解你可以把cgroup想象成容器资源的预算表systemd和cgroupfs是两套不同的记账方式kubelet用哪套记账containerd就必须跟着用哪套账对不上就会出问题。这是K8s部署中最经典的坑之一我在最后的问题排查部分还会再讲。2.3 Docker装不装什么时候需要装完containerdDocker可选装。有人说K8s不用Docker了还装它干嘛这话不准确。K8s是不用Docker运行时了但Docker依然是最好用的镜像构建工具而且Docker CLI的体验比crictl友好太多。我的建议是只要你需要自己构建镜像并推送到仓库就装上Docker。装Docker CE的方法和装containerd类似还是走Docker官方源apt install -y docker-ce docker-ce-cli systemctl enable --now docker这里有个细节很多人不知道装了Docker之后Docker本身会带一个它自己的containerd实例路径在/var/lib/docker/containerd。但这不影响我们给K8s用的那个独立containerd服务配套的containerd.io独立安装两者是互相独立的进程数据目录也不冲突。如果你之前用Docker构建过镜像想直接让K8s用这些镜像可以执行docker save导出tar包再用ctr -nk8s.io images import导入或者更优雅的方式是推到镜像仓库再拉取。3. Kubernetes核心组件安装与集群初始化3.1 安装kubelet、kubeadm、kubectl这三个必须同版本安装。我用的是阿里云的K8s软件源国内访问速度快仓库路径也比较顺畅curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.29.1/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg cat /etc/apt/sources.list.d/kubernetes.list EOF deb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.29.1/deb/ / EOF apt update apt install -y kubelet1.29.1-1.1 kubeadm1.29.1-1.1 kubectl1.29.1-1.1装完后立刻锁定版本防止apt upgrade时被升级到其他版本导致集群崩溃apt-mark hold kubelet kubeadm kubectl锁定版本不是小事。我之前在一台服务器上没加锁过了几个月系统自动updatekubelet被升到新版本跟集群里的apiserver版本对不上整个节点直接脱离集群。这是个耗时耗力的教训。3.2 kubeadm init初始化集群初始化之前先想清楚两个网段Pod网段和Service网段。这两个网段不能跟宿主机网段冲突。我习惯用10.244.0.0/16做Pod网段10.96.0.0/12做Service网段。前者是Flannel网络的默认网段后者是K8s Service的默认网段用默认值最省事。这里还是要强调一下K8s的安装部署教程网上五花八门有的让你直接kubeadm init一把梭有的让你写复杂的配置文件。我的习惯是先用一个精简的配置文件把参数写清楚后面排错也有据可查。创建配置文件kubeadm-config.yamlapiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.100 # 改成你机器的实际IP bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.29.1 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 imageRepository: registry.aliyuncs.com/google_containers注意imageRepository这一行这是关键。kubeadm默认从registry.k8s.io拉镜像国内访问不上替换成阿里云镜像仓库后初始化时拉取镜像会顺利很多。执行初始化kubeadm init --config kubeadm-config.yaml整个初始化过程会做几件事检查环境preflight、拉取K8s系统组件镜像apiserver、controller-manager、scheduler、etcd等、生成证书、启动静态Pod、生成kubeconfig和join token。看到类似下面的输出就说明成功了Your Kubernetes control-plane has initialized successfully!然后按提示配置kubectlmkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config如果你是单节点接下来把这个节点上的不可调度污点去掉不然业务Pod永远不会调度到这个节点上kubectl taint nodes --all node-role.kubernetes.io/control-plane-这是单节点K8s上跑任意应用必须的一步很多教程没说结果初始化完发现Pod一直Pending。3.3 安装CNI网络插件CNI插件是Pod之间互相通信的快递系统。K8s只管调度Pod的网络IP分配、容器之间的互通、跨节点的网络隧道全靠CNI插件来完成。没有CNIPod能创建出来但网络是不通的最典型的表现是CoreDNS一直处于ContainerCreating状态。Flannel和Calico是两大主流选择区别我直接用表格列出来对比项FlannelCalico网络模式VXLAN/UDP覆盖网络BGP三层路由性能一般有封装开销较好跨节点走路由网络策略不支持支持精细化NetworkPolicy配置复杂度极简一条命令中等有CRD概念适用场景学习、小型环境生产、多租户、安全要求高资源占用低中等本次实验环境我选了Flannel理由就是简单够用。但需要强调Flannel安装时的Pod网段参数必须跟kubeadm init时的podSubnet一致默认都是10.244.0.0/16如果改了Pod网段Flannel的配置文件也要对应修改。安装Flannelkubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml国内网络环境下GitHub可能访问不稳定。如果卡住了可以用国内加速的镜像地址下载或者先wget下载文件再applywget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml # 如果网速太慢用代理或镜像下载后上传到服务器 kubectl apply -f kube-flannel.yml安装完等一两分钟看Pod运行状态kubectl get pods -n kube-flannel kubectl get pods -n kube-system如果CoreDNS进入Running状态说明网络插件工作正常。这里我踩过一个坑Flannel要求每台节点都能访问彼此的UDP/8285和VXLAN端口如果机器有防火墙需要放行对应的端口和Pod网段流量。4. 验证集群与部署第一个应用4.1 集群健康检查与镜像加速集群初始化完成、CNI装好之后先跑一遍健康检查kubectl get nodes kubectl get pods -A kubectl cluster-info正常情况下kubectl get nodes显示STATUS为Readykube-system命名空间下的CoreDNS和etcd都是Running状态。如果节点不是Ready先别急用这个顺序查systemctl status kubelet journalctl -u kubelet -f --no-pager kubectl describe node [节点名]kubelet日志是最直接的排错入口。我在实际操作中遇到的NotReady原因九成是这两类一是containerd的SystemdCgroup没配好二是镜像拉不下来导致系统组件起不来。前者按照本文前面的配置修改即可后者需要检查镜像地址和网络。检查容器运行时的crictl命令也需要配一下不然crictl ps会报错。配置/etc/crictl.yamlruntime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false4.2 部署Nginx作为第一个工作负载跑通集群最简单的方式就是部署一个Nginx并访问它。很多人第一次接触K8s时会被Pod、Deployment、Service这三个概念绕晕我用个简单类比Deployment是控制器告诉你我要跑3个nginx如果挂了就自动再拉起Pod是集装箱里面至少一个容器K8s最小调度单元Service是门牌号给你的Nginx提供一个稳定的访问入口接下来实际操作kubectl create deployment nginx --imagenginx:latest kubectl get pod -wPod STATUS显示Running之后把Service暴露出去kubectl expose deployment nginx --port80 --target-port80 --typeNodePort kubectl get service nginx输出里会有个类似31234的端口这是NodePortK8s会在所有节点上监听这个端口。然后访问curl http://127.0.0.1:31234能看到Nginx欢迎页就说明这条链路全通了Kubernetes调度Pod - containerd通过镜像仓库拉取镜像 - CNI网络分配IP - kube-proxy把Service流量转发到Pod。4.3 镜像准备与本地私有仓库单节点K8s上面的应用最大的问题是怎么把镜像搬到集群里。如果你只有一台机器每次构建完镜像再走Docker Hub推拉一圈速度慢也不方便。我实际使用的方案是在宿主机起一个本地私有镜像仓库registry:2这个仓库跑在K8s集群之外用Docker跑专门存本机项目的镜像。启动registrydocker run -d --restartalways --name registry -p 5000:5000 -v /opt/registry/data:/var/lib/registry registry:2构建完镜像后打上本地仓库的tag并推送docker tag myapp:v1.0 127.0.0.1:5000/myapp:v1.0 docker push 127.0.0.1:5000/myapp:v1.0关键点来了Docker往127.0.0.1:5000推镜像是非加密HTTP的默认是不允许的。需要修改/etc/docker/daemon.json{ insecure-registries: [127.0.0.1:5000] }然后重启Dockersystemctl restart dockerK8s集群里的containerd要能拉取这个默认的HTTP仓库也需要配置。在/etc/containerd/config.toml里加[plugins.io.containerd.grpc.v1.cri.registry.configs.127.0.0.1:5000] [plugins.io.containerd.grpc.v1.cri.registry.configs.127.0.0.1:5000.tls] insecure_skip_verify true改完重启containerdsystemctl restart containerd这样整套本机构建镜像 - 推送本地仓库 - K8s调度Pod拉镜像的流水线就通了。在单节点环境里这个方案比用什么阿里云容器镜像服务省心得多也是跑若依那种多模块微服务整套环境时最顺手的方式。5. 数据持久化、迁移与压测场景经验5.1 单节点K8s的数据持久化方案跑无状态应用比如Nginx、普通的Java后端服务Pod随便调度都无所谓数据丢了重建就好。但如果跑数据库、消息队列这类有状态应用数据必须持久化。单节点K8s环境没有分布式存储我常用的方案有两种一种是hostPath直接把宿主机目录挂载给Pod。配置简单但要明确指定Pod调度到哪台节点上而且Pod换了节点数据就找不到了。单节点环境用hostPath问题不大因为只有一台机器。写法示例apiVersion: v1 kind: PersistentVolume metadata: name: local-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce hostPath: path: /data/k8s-pv另一种是NFS在宿主机上装一个NFS Server然后把NFS目录作为PV给K8s用。好处是Pod可以漂移虽然单节点没有漂移问题后续如果扩成多节点这套NFS方案也能沿用。K8s部署教程里很多涉及有状态应用的场景默认都假设有共享存储所以如果你打算以后不停服迁移到云ECS建议现在就按NFS的模型来设计。NFS的配置不是K8s侧的活先在宿主机上装好apt install -y nfs-kernel-server mkdir -p /data/nfs echo /data/nfs *(rw,sync,no_subtree_check,no_root_squash) /etc/exports exportfs -ra systemctl restart nfs-kernel-server然后在K8s里装NFS Provisioner或者手动创建PV和PVC。我实测下来手动创建PV/PVC最可控也最能帮你理解K8s存储的概念。5.2 迁移到云ECS的场景注意事项很多人搭完单节点K8s之后后续要做的事就是把整套环境从本地搬上云。这个过程看起来是拷一份镜像数据实际操作时踩坑点非常多。先说镜像迁移。如果业务镜像已经推到本地registry的/opt/registry/data目录迁移时可以直接打包整个目录或者用docker save/load的方式逐镜像打包。到了目标ECS上把registry容器重新起起来然后把镜像重新推送或导入K8s即可。这里容易忽略的是镜像版本标签管理本地用的是127.0.0.1:5000/myapp:v1.0到了云上就要改用云上镜像仓库的地址重新打tag否则YAML里的image字段不匹配Pod拉不下来镜像。再说数据迁移。如果数据在本地NFS或hostPath里用rsync做增量同步是最稳妥的方式rsync -avz --progress /data/nfs/ root云服务器IP:/data/nfs/先做一次全量同步应用停服前再做一次增量同步把数据差异补上这样停服窗口可以压到很小。很多准不停服、不丢数据迁移的核心就是靠rsync增量同步控制停服窗口来实现的不是什么玄学。5.3 高并发压测前必做的资源规划单节点K8s上如果后续要做JMeter压测、验证高并发承载能力有几件事必须在压测之前做完否则压测过程大概率会把节点搞挂。首要是给每个工作负载设置资源requests和limits。requests是最低保障limits是最高上限。K8s调度时只看requests运行时严格限制limits。很多人在本地实验环境图省事不写资源限制一压测内存直接打爆触发OOMkubelet开始疯狂杀Pod集群雪崩。一个典型的Java微服务Deployment资源段resources: requests: memory: 512Mi cpu: 500m limits: memory: 1Gi cpu: 1第二压测期间不要用kubectl exec进Pod抓日志这本身会消耗Pod资源。把日志输出到stdout用K8s原生的kubectl logs看或者把应用日志打到文件再通过hostPath/emptyDir挂出来采集。我在压测遇到Pod重启时一般用这个命令快速定位是资源问题还是应用问题kubectl describe pod [pod名称] # 关注Last State和Exit CodeOOMKilled基本就是资源问题第三单节点压测时要盯紧几个指标节点内存使用率free -h、磁盘IOiostat、网络带宽ifstat。如果节点CPU长时间超过90%再强行提高并发数没有意义反而是资源争抢让所有请求都变慢。这种场景下压测的重点应该是找到当前配置下的最大稳定吞吐量而不是盲目追高。5.4 常见问题速查与排查技巧把这段时间实际踩过的坑和解决方案整理成下表遇到问题直接对号入座现象可能原因排查命令解决办法节点NotReadycontainerd的SystemdCgroup未开启journalctl -u kubelet -f修改config.tomlSystemdCgroup设为trueCoreDNS一直ContainerCreatingCNI网络未安装或参数不对kubectl describe pod -n kube-system coredns-xxx检查Flannel是否RunningPod网段是否一致Pod一直Pending节点有污点或资源不足kubectl describe pod单节点执行taint解除命令检查requests是否超出节点容量ImagePullBackOff镜像地址错误或仓库不可达kubectl describe pod检查image字段HTTP仓库需配置insecure_skip_verifykubeadm init失败端口占用、镜像拉取失败kubeadm reset 重新执行检查6443/10250端口确认imageRepository配置kubectl get nodes显示No resourceskubeconfig未配置好cat ~/.kube/config从/etc/kubernetes/admin.conf重新拷贝容器启动后立刻退出镜像入口命令异常或OOMkubectl logs [pod名称]查看日志调大limits内存再分享两个独家的排查技巧。第一个Pod网络不通的时候最快的排查方式是进入Pod里面看路由和DNSkubectl run -it --rm debug --imagenicolaka/netshoot -- /bin/bashnetshoot镜像里带了curl、dig、nc、tcpdump等网络调试工具比在业务容器里装包干净利落得多。第二个APIServer压力过大导致kubectl命令卡住时不只是看集群资源还要看etcd的性能kubectl get --raw /healthz如果像etcdserver: request timed out这种报错多半是etcd磁盘IO或者网络延迟问题。单节点环境下常见的是磁盘性能不行可以考虑把etcd数据目录迁移到SSD上。从CentOS切换到Debian 12跑K8s开始我以为会有一堆兼容性问题实际跑下来反而比预想省心软件源更新、内核模块加载、containerd配置每一步都有明确的文档可查没有SELinux这类额外阻力。这套环境现在我日常还在用跑几个微服务模块、做点压测实验都没问题。如果后续要扩成多节点生产集群只需要在新节点上装好kubelet、kubeadm、containerd再把当前节点的join token拿出来执行一遍就行。希望这篇记录能帮你少踩几个坑顺利把Debian 12上的K8s跑起来。