资讯动态

K8s实战指南:从集群初始化到Redis部署与GPU调度

发布时间:2026/10/3 9:26:02 来源:尧图企业网站定制
K8s学习笔记写到第六篇后台收到的提问反而比前几篇加起来还多——有卡在master初始化失败的有纠结K8s和Docker到底选哪个的还有在生产环境被Redis集群搞到半夜的。这篇就把这些高频问题串起来从部署安装到应用编排再到GPU调度和故障排查把自己踩过的坑和验证过的方案一次性摊开。不管你是刚开始看《K8s权威指南》的初学者还是已经在生产环境里救过火的老手这篇内容都应该能让你找到点有用的东西。接下来的行文顺序是一根主线先理解K8s的设计思路然后在一台Rocky Linux上从零初始化集群接着部署一个真正的Redis集群再聊生产环境里那些会影响用户的故障最后说说GPU调度。每一部分都有实际命令和排查思路照着做基本能落地。1. 先把基础盘明白K8s到底在解决什么问题1.1 从Docker到K8s不是替代是编排先说一个被问了无数次的问题K8s和Docker到底什么关系很多人以为K8s要干掉Docker其实不是。Docker解决的是单个容器怎么构建、怎么运行而K8s解决的是几十上百个容器怎么协同、怎么调度、怎么在节点宕掉之后自动恢复。你可以把Docker当成螺丝刀K8s是一条自动化产线产线上要用螺丝刀也会用其他工具。K8s在1.24之后默认使用containerd作为容器运行时而不是Docker。但这不代表Docker就废了——开发环境里docker build、docker push依然好用只是生产集群的容器运行时换成了更轻量的CRI实现。你只要能构建镜像并且镜像能被节点拉取K8s并不关心你用哪种运行时。所以我的建议是别把时间花在争论谁替代谁上把docker基本命令练熟再学kubectl这才是最稳的路径。初次接触K8s的人经常想用Docker部署一个容器后再手动去处理所有的事情。K8s让你声明“我期望的状态”比如“我要3个nginx副本”剩下的副本数控制、滚动更新、故障恢复都由控制面完成。这就是“声明式”和“命令式”的区别。你可以不关心它具体在哪个节点上拉起了Pod只关心业务有没有对外提供服务。如果硬要用表格对比可以这样理解维度DockerKubernetes定位容器构建和运行容器编排与调度单机/多机单机为主多节点集群扩缩容手动自动/声明式自愈仅restart策略自动重建、迁移服务发现需要外部方案内置Service/Ingress这个表格能帮你快速建立体感K8s的价值不在于“能不能跑容器”而在于“容器多了之后还能不能稳定跑”。1.2 Namespace多租户隔离的第一步kubectl get ns你会看到default、kube-system、kube-public等几个命名空间。namespace在K8s里是对集群资源进行逻辑划分的一种方式听起来有点抽象其实就是给资源打上归属标记。不同项目、不同团队可以各占一个namespace配合ResourceQuota和LimitRange就能限制每个团队最多用多少CPU、内存防止一个团队占用整个集群。初学者最常见的坑是直接部署在default命名空间里后面想隔离却发现没有规划。我建议从一开始就建立“一个项目一个namespace”的习惯。比如部署Redis集群可以建一个redis专用命名空间后面查看日志、配额、网络策略都干净很多。实际操作很简单kubectl create namespace redis kubectl label namespace redis purposeredis-cluster加label这件事很多人会忽略等到要用网络策略或者做成本核算时才发现没有标签的资源基本等于失控状态。所以每建一个namespace顺手打上team、env、purpose这组标签后面省很多事。K8s的隔离不是指安全隔离而是资源层面的逻辑隔离真正强隔离要靠节点池和网络策略这个概念也要早一点建立起来。1.3 为什么都在找1.36版本热搜词里有个rocky 安装 k8s 1.36实际只要是Kubernetes 1.20之后的版本核心安装流程都差不多不必过度纠结小版本。我选用1.36是因为它是当前稳定版里比较新的一个API没有大的破坏性变更新功能如Job API、自动扩容都更成熟。选择版本时建议看两件事一是kubeadm支持的Kubernetes版本二是yum/apt源里能不能找到对应rpm包。不要追最新选一个社区维护周期内的稳定版就好。还有一个点是看容器运行时和操作系统的兼容性。Rocky Linux 9配Kubernetes 1.36目前在社区里跑得很稳。反而是那些用老版本K8s强行装新系统、或者新版本K8s配老系统的问题更多。版本确认完接下来最关键的其实是操作系统和容器运行时的选择。Rocky Linux这种RHEL系系统优点是稳定、软件源干净缺点是如果不知道它在防火墙和SELinux上有哪些默认策略很容易踩坑。所以下面这部分会把系统初始化和kubeadm init过程中的每个细节都摆出来。2. 零基础装K8sRocky Linux上的实践记录2.1 环境准备与版本选择我手头是三台Rocky Linux 9虚机一个master两个node。生产环境至少三台master学习阶段一台足够。先关闭防火墙生产环境按需开通端口而不是直接关闭但学习环境先保证能通。再关闭swap因为kubeadm默认要求swap被禁用。启用br_netfilter模块让iptables能处理桥接流量。sudo systemctl disable --now firewalld sudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstab cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --system这里说明几条swapoff -a只是临时关闭所以要改/etc/fstab防止重启后又挂载net.ipv4.ip_forward必须为1否则Pod跨节点通信会失败br_netfilter让桥接流量经过iptables是Flannel/Calico能工作的基础。这三步做完用sysctl -p验证一下不要跳过去。容器运行时我选containerd。对于Rocky Linux可以添加docker官方源也可以用仓库自带的containerd。建议用docker-ce源里的containerd.io版本比较新。安装后需要配置cgroup驱动为systemd因为K8s默认使用systemd cgroup。不配置的话初始化时很有可能会挂掉后面会看到具体报错。接下来安装kubeadm、kubelet、kubectl。先配置官方yum源cat EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://packages.cloud.google.com/yum/repos/kubernetes-el9-x86_64 enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOF sudo yum install -y kubeadm kubelet kubectl sudo systemctl enable --now kubelet这个源如果慢可以换成国内镜像源原理一样。装完别急着init先确认kubelet服务是active的再看一下kubelet的版本和安装的kubeadm版本是否一致kubelet --version和kubeadm version。版本不一致会导致初始化后节点失联这是我见过的另一个隐蔽坑。2.2 初始化Master节点API Server不健康的完整排查执行kubeadm init是第一个大坑。我第一次执行完等了一分钟就看到the api server is not healthy after 4m0.00747357s。网上搜到的说法五花八门其实根本原因就那么几个。如果你也碰到这个报错按照下面的顺序排查基本十分钟内能定位。先看kubelet日志journalctl -u kubelet -f日志里出现failed to run Kubeletis it properly set up? 这种十有八九是cgroup驱动不一致。确认一下cat /etc/containerd/config.toml | grep -i systemd如果显示SystemdCgroup false就要改。先用containerd config default生成默认配置再替换sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml sudo systemctl restart containerd还有一种可能是镜像拉不下来。kubeadm init需要拉取pause、etcd、apiserver等一组镜像如果网络不稳定kubelet会一直等最终报出API server不健康。建议先手动拉取kubeadm config images pull这个命令会把所有需要的镜像准备好看到每个镜像都显示Pulled字样再继续init。网络慢的情况下可以配置国内镜像源但这里不展开重点是先确认镜像都齐了。再检查系统配置cat /proc/sys/net/ipv4/ip_forward free -h | grep -i swapip_forward不是1或swap还有空间kubeadm就会卡住。尤其你明明执行了swapoff -a但fstab没有改重启后又挂载了swap此时需要重新关。还有一种容易被忽略的情况SELinux没有设置成permissive导致kubelet访问文件被拒绝日志里会出现Permission denied。所有检查都正常后再执行sudo kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16--apiserver-advertise-address填master的IP--pod-network-cidr要和网络插件的默认网段保持一致否则后面Pod网段和Flannel网段对不上Pod之间通信会乱。等到看到Your Kubernetes control-plane has been initialized successfully!就代表成功了。然后配置kubectlmkdir -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如果不配置这一步你会一直在local:8080 connection refused的报错里打转。2.3 Node节点加入集群与网络插件安装master初始化成功后会输出kubeadm join命令在node上执行。注意node上也要装好containerd、kubeadm、kubelet并做一样的系统初始化。node的cgroup驱动配置、swap、模块都要一致一个没对上加入后就会一直NotReady。加入命令类似这样sudo kubeadm join 192.168.1.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx如果token过期了可以在master上重新生成kubeadm token create --print-join-command网络插件是另一个高频坑。我习惯用Flannel因为配置简单。Calico功能更强但资源占用多一点学习环境无所谓。网络插件要在master上安装安装前先确认init时指定的--pod-network-cidr和Flannel默认一致不一致就成网失败。Flannel默认网段是10.244.0.0/16所以前面init也用这个。安装命令kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml安装后等待一会儿执行kubectl get nodes看到节点变成Ready就说明网络通了。这一步卡住基本都是节点间的安全组、防火墙没放行UDP/VXLAN端口或者iptables规则被重置了。在Rocky Linux上尤其要检查firewalld是不是关了因为就算你disable了有时某个节点重启后又会自动启动这是RHEL系常见的坑。集群就绪后可以顺便验证一下coreDNS是否正常kubectl get pods -n kube-system如果coreDNS一直Pending大概率是CNI没装好因为它依赖网络插件启动。到这里一个可以用的K8s集群就算立起来了。3. 从“能跑”到“会用”部署一个Redis集群3.1 部署前的规划StorageClass和Headless Service集群装完第一个有代表性的应用我建议部署Redis集群。它涉及StatefulSet、Service、配置、持久化学完就能应对大多数有状态服务。Redis集群不像nginx那样随便启动几个副本就行它需要稳定的Pod标识还需要节点之间互相知道对方IP。先建namespace。然后写headless service因为没有ClusterIPPod可以通过DNS名字直接访问。Redis节点间握手需要互相能通过域名找到对方。如果只是ClusterIPPod的identity不稳定重启后IP变了集群就散了。headless service的clusterIP是NoneDNS记录会自动指向所有Pod的IP。写一个简化版apiVersion: v1 kind: Service metadata: name: redis-hs namespace: redis labels: app: redis spec: clusterIP: None selector: app: redis ports: - port: 6379 targetPort: 6379这只是一个service需要搭配StatefulSet使用。headless service的名称一定要记住因为StatefulSet的serviceName字段要指向这里的metadata.name。3.2 用StatefulSet创建Redis集群StatefulSet保证Pod名字稳定比如redis-0、redis-1而且每个Pod可以有独立的存储卷。这里用hostPath演示生产环境建议用StorageClass因为hostPath只在单个节点上有效Pod被调度到别的节点数据就没了。StatefulSet的关键字段是serviceName必须指向上面那个headless service。每个Pod创建一个PVC名字会自动带上序号。下面是一个可以跑通的最小redis节点模板apiVersion: apps/v1 kind: StatefulSet metadata: name: redis namespace: redis spec: serviceName: redis-hs replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7-alpine command: - redis-server args: - --port 6379 - --cluster-enabled yes - --cluster-config-file nodes.conf - --cluster-node-timeout 5000 - --appendonly yes ports: - containerPort: 6379 name: redis volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 1Gi创建后kubectl apply -f redis-statefulset.yaml kubectl get pods -n redis -o wide三个Pod都Running之后进入其中一个Pod构建Redis集群kubectl exec -it redis-0 -n redis -- redis-cli --cluster create \ redis-0.redis-hs.redis:6379 \ redis-1.redis-hs.redis:6379 \ redis-2.redis-hs.redis:6379 \ --cluster-replicas 0这里用Pod的完整DNS名。如果serviceName和namespace对不上这里就会报Could not connect。这个DNS名的格式是podName.serviceName.namespace.svc.cluster.local其中最后的.svc.cluster.local可以省略但前面不能错。如果还是连不上大概率是headless service的selector没匹配到Pod label。3.3 验证集群与故障转移集群创建成功后进入任意Pod执行redis-cli cluster info redis-cli cluster nodes可以看到三个master没有replica。如果需要高可用可以在创建命令里加--cluster-replicas 1那就要准备6个Pod三个master配三个slave。学习阶段先用3个master理解机制再加上replica并不会引入新的K8s概念。要测试故障转移直接删除一个Podkubectl delete pod redis-0 -n redisStatefulSet会自动重建redis-0。因为有headless service和PVC节点恢复后还能用原来的数据。这就是有状态服务在K8s里的价值。实际生产Redis集群通常用redis-operator来管理自己手写StatefulSet只是学习。不过把原理搞懂后再看operator的CRD就不会怵了。核心要点就是稳定的网络身份、稳定的存储、节点间通过DNS发现。4. 生产环境避坑指南用户可感知的故障在哪里4.1 资源限制缺失引发的雪崩生产环境里最常见的不是K8s崩了而是应用被打死。很多Pod没有设置resources.requests和limits导致一个节点的Pod互相抢资源CPU被打满后节点状态变成NotReady用户跟着超时。有一次线上一个Java应用没有设置limits内存持续膨胀直接把同节点的其他Pod全部杀到OOMKilled最后只能强制重启节点。所以规则要提前定好每个Deployment都要设置resources至少设置requests让调度器知道Pod需要多少资源limits用来限制上限。比如resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi这里500m表示0.5核m代表milli1Gi是1024MiB。requests和limits配成一样的是guaranteed QoS最稳定不一样是burstable有一定风险被驱逐。生产环境尽量用guaranteed尤其是核心用户服务。同时配合HPA按CPU或自定义指标自动扩缩容。HPA的基础是Metrics Server没有装的话kubectl top pod跑不起来。4.2 网络插件与Pod通信故障讲几个我实际遇到的Pod之间访问不通多半是网络插件没选对或者节点防火墙挡了Service ClusterIP不通先看Service selector和后端Pod label是否对得上DNS解析失败看CoreDNS是否running以及Pod是否在默认DNS策略下。排查时用kubectl exec进入Pod然后用curl和dig去试。一个经典的跨命名空间访问问题kubectl exec -it busybox -n default -- curl http://redis-hs.redis.svc.cluster.local:6379如果通不了检查Service的selector是否匹配到redis namespace里的Pod label。其次看Pod到Service的endpointskubectl get endpoints -n redis如果endpoints为空说明selector没匹配上。这里的坑都是粗心但线上影响用户的就是这种粗心。跨namespace访问时DNS名里的namespace不能省略否则只会解析到当前namespace的service结果not found。4.3 证书、镜像和节点状态类问题速查对于生产环境中常见的用户可感知故障我整理了一张速查表真心建议保存到自己的运维手册里。症状可能原因快速排查ImagePullBackOff镜像名写错、私有仓库未认证、仓库不存在kubectl describe pod看事件里具体报错CrashLoopBackOff应用启动失败、配置错误、启动依赖其他服务kubectl logs podNodeNotReady负载过高、kubelet异常、磁盘满登到节点看journalctl -u kubeletAPI Server证书过期集群超过1年未升级默认证书365天kubeadm alpha certs renew all然后重启组件Pending资源不足、PVC无法绑定、污点未容忍kubectl describe pod查看Scheduler事件DNS解析失败CoreDNS异常、Pod自定义DNS错误先查CoreDNS Pod再看Pod的resolv.conf证书过期这个问题很多集群跑了一年多突然开始报各种连接错误用户只看到504。排查时先看kube-apiserver容器日志如果出现x509: certificate has expired or is not yet valid就基本实锤了。更新证书后记得重启控制面组件不然新证书不生效。镜像拉取失败也是一大类。私有仓库里的镜像需要在Pod里配置imagePullSecrets不然Kubelet带着匿名身份去拉返回401就卡在ImagePullBackOff。我用一个小技巧先手工拿凭证登录仓库生成secret然后通过imagePullSecrets挂到Deployment里。命令类似kubectl create secret docker-registry regcred \ --docker-serverregistry.example.com \ --docker-usernameadmin \ --docker-passwordxxxx然后Deployment里加spec: template: spec: imagePullSecrets: - name: regcred我看到不少团队把所有应用放在同一个namespacesecret的用途就会混在一起。给每个应用单独建一个secret或者在secret名称里加应用名能少很多误用。5. 进阶让K8s调度GPU资源5.1 GPU调度的基本原理很多训练任务和推理服务都要GPU但K8s本身不认识GPU。它通过Device Plugin机制把GPU作为可调度资源暴露给集群。NVIDIA提供nvidia-device-plugin在每个GPU节点上以DaemonSet方式运行把显卡数量和显存注册到Kubelet。调度器看到nvidia.com/gpu这个资源后就能把Pod调度到合适的节点上。什么是Device Plugin你可以理解成是一个插件进程监听Kubelet的请求返回节点上有哪些设备资源。Kubelet再把这些资源注册到自己的capacity里。之后调度器在安排Pod时会优先检查目标节点还有没有足够GPU。这个过程和检查CPU、内存没有本质区别只是资源名称变成了nvidia.com/gpu。5.2 安装NVIDIA Device Plugin前提是节点上已经装好NVIDIA驱动并且nvidia-smi能正常输出。没驱动后面全是白搭。安装方式很直接kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/main/nvidia-device-plugin.yml安装后可以查看节点资源kubectl describe node gpu-node | grep -A 5 Capacity正常会看到nvidia.com/gpu: 1或更多。如果Capacity里没有看DaemonSet日志多半是驱动版本不被识别或/var/lib/kubelet/device-plugins目录权限不对。还有一个容易忽略的点Device Plugin是DaemonSet它只会调度到有GPU标签的节点因为模板里会配置nodeSelector。你要是给节点打错了标签DaemonSet就会一直Pending。5.3 在Pod中申请GPU资源在Pod spec里声明resources: limits: nvidia.com/gpu: 1注意GPU资源只能设置limits通常可以和requests写一样的值。调度器会把Pod安排到有GPU的节点上然后Device Plugin负责把设备挂载进容器。容器里的nvidia-smi就能看到对应显卡。踩过的坑忘记安装driverDevice Plugin起来但GPU节点不Ready申请0.5个GPU是无效的K8s的GPU资源只支持整数调度有的镜像需要带CUDA环境和宿主机驱动版本要匹配否则容器起来就报CUDA driver version is insufficient。再就是默认一个GPU卡只能被一个Pod独占如果业务跑不满一张卡需要用MIG或TimeSlicing配置把一张卡切成多份但这需要额外配置。学习阶段先用独占方式理解机制不要一上来就去搞切分。6. 学习资源与我的建议6.1 《K8s权威指南》怎么看电子版别乱找很多人搜《K8s权威指南第五版pdf下载》。我的建议是把这本书当作参考手册不要想着从第一页啃到最后一页。第五版出在K8s生态比较成熟的时期很多命令现在还适用。pdf下载渠道质量参差不齐排版错乱还容易踩到捆绑陷阱不如支持正版。买纸质书或者官方电子版配合kubectl help和官方文档效率高很多。看书的方法上先看前三章理解架构和核心概念再看控制面、工作负载、服务发现最后看调度和运维。每读一章就去集群里做一遍书里的例子。没有环境光看是没有记忆点的。不要一口气读完多数人读到后面前面就忘了所以按刚需章节选择阅读更有效。官方文档也一样遇到问题先看相应的概念页再看任务页不要一上来就翻API参考。6.2 我的学习路径与避坑心得我是按照装一个集群、部署无状态应用、部署有状态应用、解决故障、研究调度的顺序走的。分享几点第一多准备几台低配虚机至少2C4G否则很多实验根本跑不动第二遇到问题先看kubectl describe和journalctl不要盲目重来第三养成记日志的习惯每条命令都记录下来下次排查能省一半时间。如果你只学一个命令我建议学kubectl describe。它会把事件的来龙去脉都展开绝大多数问题光看输出就能定位。kubectl logs只是看应用日志而describe能看到调度器、控制器、节点等多方面信息。比如Pod一直Pendingdescribe里会明确告诉你FailedScheduling并附上原因比猜快得多。最后再分享一个小技巧把kubeadm init生成的join命令和初始化过程保存成一个笔记文件每次搭新的环境直接复制。线上出问题时第一条命令永远是kubectl get events --all-namespaces --sort-by.lastTimestamp把所有异常事件按时间排出来比一个个查快得多。我在实际排查中靠这条命令解决的故障少说也有二十次。K8s的学习曲线确实陡但每一步都做实后面自然会顺。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑