说实话,我见过太多人一上来就敲kubeadm init,结果卡在各种报错里怀疑人生。Kubernetes 这东西,如果脑子里没有一个清晰的概念地图,你连报错信息都看不懂。就拿最近常被问到的“控制节点初始化显示 the api server is not healthy after 4m0.00747357s”来说,这个报错看着像时间超时,其实背后可能是网络插件没装、cgroup 驱动不一致、或者镜像没拉全。如果你只知道“哦这是 api server 没起来”,那你还是不会排查。我一直觉得,先把 k8s 的核心概念吃透,再上手操作,效率至少翻一倍。这篇文章就是写给准备学 k8s、或者刚被初始化报错折磨过的朋友,我会把 Pod、Deployment、Service、Namespace 这些核心对象讲明白,再结合 k8s 和 Docker 的区别、控制平面架构、集群初始化的常见坑,帮你把“概念”和“实操”串成一条线。文章里没有教科书式的废话,都是我在实际环境里试过、踩过、验证过的经验。
1. 为什么要先搞懂k8s核心概念:先给大脑装一张地图
1.1 从一个真实的困惑说起:k8s到底在解决什么问题?
很多人学 k8s 的第一反应是“这不就是个容器管理工具吗?和 Docker 有什么区别?”这个想法不算错,但格局太小。Docker 解决的是“怎么把一个应用打包成镜像、怎么运行容器”,而 k8s 解决的是“成百上千个容器怎么调度、怎么保证不挂、怎么对外提供稳定服务、怎么滚动升级不中断”。你可以把 Docker 想象成一个个集装箱,集装箱很好用,但如果你有一万个集装箱散落在码头上,你需要的是港口调度系统——谁来卸货、谁先走、哪个集装箱坏了怎么替换,这套系统就是 Kubernetes。
所以第一个要明确的概念是:k8s 的核心价值不是“运行容器”,而是“管理容器的生命周期”。它会持续监控你声明好的期望状态,比如“我要 3 个 Nginx 副本”,它就会保证集群里始终有 3 个 Nginx 在跑,少一个就自动补一个,多一个就杀掉一个。这个过程叫“声明式管理”,你只需要告诉它“最终要什么”,它会自己去协调。这种思路和传统运维“登录服务器手动执行命令”是完全不同的,你需要转变思维。
如果你之前只玩过docker run,那么在学习 k8s 时最痛苦的点就是:不能直接跑容器,而是要写一堆 YAML 声明资源对象。这个门槛确实存在,但也正因为这样,集群才能自愈、才能扩展。先接受这个思维转变,后面的路才走得顺。
1.2 k8s和Docker的本质区别:别再“容器=K8s”了
这是一个被问烂了但又必须说清楚的问题。简单总结:Docker 是容器运行时,它负责“把镜像跑成容器”;Kubernetes 是容器编排平台,它负责“管理一堆容器”。两者不在一个层面。
更准确地说,现在 k8s 不一定非要配 Docker 不可,它甚至推出了自己的 CRI(Container Runtime Interface)标准,现在很多集群用的是 containerd 作为运行时。containerd 本身就是从 Docker 里拆出来的、专注于运行容器的组件,体积更小、更稳定。所以你去看新版 k8s 集群节点上的进程,可能根本找不到 docker 进程,只有 containerd。
那为什么大家总把它俩放一块儿说?因为早期 k8s 确实主要支持 Docker,而且你平时用docker build打的镜像,在 k8s 里也能直接跑,k8s 不关心镜像怎么构建的,只关心镜像能不能拉取、能不能运行。我的建议是:学习时先把 Docker 的基础操作搞定,尤其是镜像构建和仓库推送,但不要沉迷于docker run的各种参数。进入 k8s 之后,你会发现很多参数都被抽象成 YAML 里的字段了。
还有一点容易混淆:kubectl和docker命令的区别。kubectl是操作 k8s 集群的客户端工具,你的所有操作都通过它发给 API Server,再由 API Server 调度到各节点。而docker命令只是操作本地单机容器,两者完全不是一个控制面。如果你习惯在每台机器上docker ps看容器,那在 k8s 里标准做法是kubectl get pods -o wide,一切以集群视角为准。
1.3 Kubernetes的“全身骨架”:控制平面与工作节点
理解 k8s 架构,千万不要一上来就背组件名。你先记住两个角色:控制平面(Control Plane)和工作节点(Node)。控制平面是整个集群的“大脑”,负责决策;工作节点是“手脚”,负责跑真实的应用。
控制平面核心组件有这么几个:API Server、etcd、Scheduler、Controller Manager。API Server 是所有请求的唯一入口,你用kubectl发出的每个命令都会打到它;etcd 是一个分布式键值数据库,存集群所有状态——谁在跑、谁挂了、期望状态是什么,全在 etcd 里;Scheduler 负责决定一个新 Pod 放到哪个节点上;Controller Manager 负责跑各种控制器,比如 Deployment 控制器、Node 控制器,它确保实际状态收敛到期望状态。这四个组件一挂,集群基本就瘫痪了。
工作节点上的组件相对简单:kubelet、kube-proxy、容器运行时。kubelet 是每个节点上的“代理”,它接收控制平面下发的指令,负责启动容器;kube-proxy 负责实现 Service 的网络代理和负载均衡;容器运行时就是 containerd 或 Docker 这类东西。节点上不需要装 API Server,也不需要 etcd,它只要听话干活就行。
这张架构图里最需要“为什么”的环节是:为什么 API Server 是唯一入口?因为所有组件都通过 API Server 通信,彼此不直接连接,这样整个集群的解耦性非常好。比如 Scheduler 并不直接跑到节点上启动容器,它只是把“Pod 应该分配到哪个节点”这个决定写回 etcd,kubelet 通过 watch API Server 发现这个变化,然后自己动手启动容器。这种设计让每个组件都可以独立升级、独立扩展,代价就是链路变长,排查问题时要习惯“看事件、看日志、看状态”。
2. 核心对象逐个拆解:Pod、Deployment、Service、Namespace
2.1 Pod:最小调度单元,而不是容器
这个概念是新手最容易绕晕的。Kubernetes 里最小的调度单元不是容器,而是 Pod。Pod 里可以包含一个或多个容器,这些容器共享同一个网络命名空间和存储卷,可以互相通过 localhost 访问。为什么要把容器再包一层?因为有些应用场景下,多个进程必须放在一起部署、一起调度,比如一个日志边车容器在旁收集主容器的日志,或者一个代理容器帮主容器处理网络流量。
你平时部署 Nginx,可能一个 Pod 里就一个 Nginx 容器,这没问题。但你要明白,Pod 这个对象是有生命周期的,它会被创建、销毁、重新调度。Pod 里的容器不是常驻进程,Pod 本身也不具备自愈能力。那么谁负责创建和销毁 Pod?答案是上层控制器,比如 Deployment。所以你在生产环境里很少直接创建 Pod,而是创建 Deployment,由它来管理 Pod 的副本和生命周期。
实际操作中,kubectl get pods看到的状态一般有 Running、Pending、ContainerCreating、CrashLoopBackOff、Terminating 等。Pending 意味着调度还没完成,可能资源不足;CrashLoopBackOff 说明容器启动就崩、崩了又重启,通常是应用本身报错。排查时先用kubectl describe pod <pod-name>看事件,再用kubectl logs <pod-name>看容器日志。记住这句话:Pod 不是一个“稳固的东西”,它随时可能被干掉、被迁移,你的应用必须设计成无状态的,或者通过持久卷存储做有状态处理。
2.2 Deployment:你要的“副本数”和自愈
Deployment 是实际部署应用时最常用的控制器。它的核心是“副本数”(replicas)。你可以在 YAML 里写replicas: 3,Deployment 控制器就会保证集群里始终运行 3 个 Pod 副本。如果某节点挂了导致一个 Pod 没了,它会立刻在可用节点上再建一个;如果你想升级镜像版本,它支持滚动更新,先起一个新 Pod,等新 Pod Ready 后再杀掉旧 Pod,保证服务不中断。
Deployment 创建 Pod 的方式不是直接创建,而是通过 ReplicaSet。ReplicaSet 负责维护 Pod 副本数量,Deployment 负责管理 ReplicaSet 的版本迭代。这个嵌套关系新手容易忽略,但排查版本回滚时就有用了。你可以先知道一个大致的层级:Deployment -> ReplicaSet -> Pod。每次镜像或配置更新,Deployment 都会生成一个新的 ReplicaSet,并逐步把流量切过去。
写 Deployment YAML 时有几个关键字段:spec.replicas、spec.selector.matchLabels、spec.template.metadata.labels、spec.template.spec.containers。selector 的 labels 必须和 template 里的 labels 匹配,否则 Deployment 无法管理对应的 Pod。这个不匹配的问题我在新手群里见过不止一次——YAML 校验通过了,但 Pod 创建出来后一直不被控制器管理,结果越搞越多。
还有一个实际经验:如果你要快速创建一个 Deployment,可以用命令kubectl create deployment nginx --image=nginx:1.25,它会自动生成 YAML 模板,然后再用kubectl edit deployment nginx去调整副本数和资源限制。比手写 YAML 快很多,也减少缩进错误。
2.3 Service:稳定的访问入口
Pod 是会被杀掉的,它的 IP 地址也是会变的。如果客户端直接访问 Pod IP,那 Pod 一重建,客户端就断。Service 就是解决这个问题的抽象层。它给一组 Pod 提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名称,客户端只需要访问 Service,Service 再把流量转发到后面实际的 Pod 上。
Service 通过 selector 匹配 Pod 的 labels,只要 labels 对得上,它就知道把流量转到哪几个 Pod 上。这种机制叫“标签选择器”,是整个 k8s 里最核心的设计模式之一。你用 Deployment 管理 Pod,用 Service 暴露 Pod,两者通过 labels 解耦。
Service 的类型主要有三种:ClusterIP(默认,集群内访问)、NodePort(把服务映射到每个节点的某个端口上,集群外可以通过节点IP:端口访问)、LoadBalancer(云服务商提供的负载均衡器,会把流量送到 NodePort,再转发到 ClusterIP)。如果你在自己电脑上用 minikube 或 kind,通常会配合kubectl port-forward来本地访问服务,这个工具排错时特别好用。
我认为学习时一定要搞明白的一条链路是:客户端请求 -> 节点的kube-proxy规则 -> ClusterIP -> Service 后端 Pod。实际流量不一定经过 Service 这个进程,它只是 iptables 或 IPVS 规则。这也是为什么 Service 本身没有进程在监听端口,但它又能转发流量。排查 Service 不通时,先看看有没有 Endpoints:kubectl get endpoints <service>。如果 Endpoints 为空,说明 selector 没匹配到 Pod,这是最常见的 Service 故障原因。
2.4 Namespace:多租户的“虚拟集群”
Namespace 是 k8s 里非常重要的一个概念。你可以把它理解成集群内部的“虚拟子集群”。同一个集群里可以划分多个 Namespace,每个 Namespace 里的资源(Pod、Service、Deployment 等)相互隔离,但这种隔离是逻辑上的,说白了就是名字分组和权限控制用的。默认情况下,网络不一定隔离,Pod 还是可以跨 Namespace 访问的。
之所以很多初学者忽略 Namespace,是因为默认操作都在default这个 Namespace 里。但真正生产环境,一定要按照业务、环境区分 Namespace,比如dev、prod、ops。这么做的好处是:第一,资源管理清晰,kubectl get pods -n dev和-n prod互不干扰;第二,可以通过 RBAC 控制每个 Namespace 的权限;第三,配合 ResourceQuota 可以限制每个 Namespace 的 CPU、内存总量,防止一个业务吃光整个集群。
我见过不少人创建资源的时候忘加-n,结果全堆在 default 里,后面排查特别痛苦。建议你在初始化集群后做的第一件事是创建几个常用 Namespace,并且养成写-n的习惯。另外注意,有些资源是集群级别的,不归 Namespace 管,比如 Node、PersistentVolume、ClusterRole 这些,它们天然是全局的。用kubectl api-resources可以查看哪些资源是 namespaced 的,哪些不是,这个方法比死记硬背高效。
2.5 其他高频对象:ConfigMap、Secret、Ingress
除了上面四个,还有三个对象你很快会碰到。第一个是 ConfigMap,用来存配置,比如nginx.conf、环境变量、URL 参数。把配置从镜像里抽出来,好处是改配置不用重新打镜像,只要更新 ConfigMap 再重启 Pod 就能生效。第二个是 Secret,和 ConfigMap 类似,但专门存敏感信息,比如密码、Token、证书。Secret 在 etcd 里默认是 base64 编码存储的(严格说是 base64 而不是加密),所以不要把 Secret 当成绝对安全的东西,要配合 RBAC 限制访问。第三个是 Ingress,它不是 Service 那种四层转发,而是工作在七层 HTTP/HTTPS,可以根据域名和路径路由到不同 Service。比如你有一个域名 api.example.com 和 www.example.com,你能让它们分别路由到不同的后端服务,这就是 Ingress 的典型用途。
这几个对象有一个共性:它们都是通过 labels/selector 或名称引用去关联到其他资源的。ConfigMap 和 Secret 通过spec.template.spec.containers.env.valueFrom或 volume 挂载使用;Ingress 通过spec.rules.http.paths.backend.service.name指向 Service。理解关联关系,比背字段有用。因为你写 YAML 时只要搞明白“谁引用谁”,就不会频繁报错。
3. 从“会看概念”到“能上手操作”:集群初始化与常见坑
3.1 用kubeadm初始化集群的合理姿势(Rocky / CentOS系注意点)
概念看了一大堆,最终还是要落地跑集群。很多教程会让你用 minikube 或 kind,但我个人建议如果想认真学,就用 kubeadm 自己搭一个一主一从的小集群,对理解架构帮助非常大。我也注意到现在很多人在 Rocky Linux 上装 k8s,这里就说一下主要注意点。
选用 Rocky 或 CentOS 做节点时,要先确保操作系统干净,内核版本尽量新。k8s 对系统要求不算苛刻,但在初始化之前有几件必做的事:关闭 swap,swapoff -a并且注释 /etc/fstab 里的 swap 行;加载内核模块br_netfilter;调整 sysctl 参数,让 iptables 能处理桥接流量。这些步骤如果你漏掉,后面 kubelet 大概率没法正常工作,报各种奇怪的错。
安装 kubeadm、kubelet、kubectl 时,版本要固定,不要默认装最新版。例如你想部署 k8s 1.36,那么三个组件都要装 1.36.x(注意这个版本号最近在社区讨论比较多,具体以官方仓库为准)。因为 kubelet 和 kubeadm 的版本必须保持在同一 minor 版本,否则控制平面初始化时会校验失败。在 Rocky 上配置 yum 源时,记得用阿里的镜像源或者官方源,国内网络环境一定要提前准备镜像加速,否则拉取 k8s 自带组件镜像的时候会卡死。
还有一点很多人忽略:容器运行时建议使用 containerd,不要再去装 Docker 了。k8s 1.24 之后就彻底移除了 dockershim,你要是还配置 Docker 作为运行时,就要额外装 cri-dockerd 适配器,纯属自找麻烦。用 containerd 的话,kubelet 直接对接 CRI 接口,省心。
3.2 控制节点初始化报错“the api server is not healthy after ...”:排查实录
这个报错可以说是 kubeadm 初始化最常见的“拦路虎”之一。错误长这样:[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from ...,最后提示the api server is not healthy after 4m0.00747357s。看到这个报错,很多人的第一反应是重跑kubeadm init,大概率没用。你要去查到底为什么 api server 起不来。
api server 在初始化时是以静态 Pod 的形式跑在控制节点上的,配置文件在/etc/kubernetes/manifests/kube-apiserver.yaml。你可以先检查 containerd 里有没有对应的容器,用crictl ps -a | grep kube-apiserver。如果容器没起来,就用crictl logs查看容器日志。常见原因有这么几个:
第一,镜像拉不下来。kubeadm init 默认使用的是 k8s.gcr.io 或 registry.k8s.io 的镜像,国内环境经常拉不动。解决办法是提前用kubeadm config images pull或者配置 containerd 的 registry mirror,把镜像源换成可访问的地址。我那次遇到这个问题时,就是先crictl images一看,发现 kube-apiserver 的镜像根本没有,然后去配 containerd 的 mirror,重新 pull,再kubeadm reset重新 init。
第二,etcd 没就绪。api server 启动时会先连 etcd,如果 etcd 一直没起来,api server 也会反复 crash。etcd 同样是静态 Pod,检查/etc/kubernetes/manifests/etcd.yaml和对应日志。etcd 起不来很可能是数据目录权限、磁盘空间不足或者证书问题。kubeadm init之前如果跑过多次失败,最好先kubeadm reset清干净旧数据,否则 etcd 可能会因为残留数据而拒绝启动。
第三,API Server 启动参数有误。比如 certificate 目录不对、Service CIDR 和 Pod CIDR 冲突等。这类错误日志里会写得比较清楚,你要耐心看日志,不要只看“not healthy”就慌了。
第四,端口被占用。如果 6443 端口被别的进程占了,api server 绑定失败,也会一直不健康。用ss -lntp | grep 6443查看。这里我要重点说一句:不要一看到报错就盲目执行kubeadm reset,那是最后手段。第一次出现时先看日志、看资源,能精准定位就少走弯路。
还有一个很容易被忽略的原因:cgroup 驱动不一致。kubelet 默认的 cgroup 驱动是 systemd,而 containerd 默认可能是 systemd,但要确认没有错。如果你之前手动改过 containerd 配置,或者用了 Docker 作为运行时,cgroup 驱动是 cgroupfs,两边不一致时,kubelet 会报 “failed to run Kubelet” 一类的错误,控制平面自然不会健康。这种事我在实验环境里碰到过一次,docker 的 cgroup 驱动和 k8s 要求不一致,折腾了快一个小时。解决办法是统一 containerd 和 kubelet 的 cgroup driver,一般建议都设成 systemd。
3.3 保证init成功的三个关键细节(网络、cgroup驱动、镜像拉取)
基于我踩过的坑,如果你准备在 Rocky 或者类似 Linux 发行版上用 kubeadm 初始化集群,记住这三个细节,能避免八成问题。
细节一:务必提前加载内核模块并开通防火墙规则。br_netfilter不加载,可能会导致 Pod 网络不通;net.bridge.bridge-nf-call-iptables不设为 1,集群网络插件(比如 Calico)会异常。另外,如果你开着 firewalld,记得放行控制平面的端口:6443(API Server)、2379-2380(etcd)、10250(kubelet)、10259(kube-scheduler)、10257(kube-controller-manager)。很多人初期没放行,kubelet 报权限错误,然后 coredns 一直 Pending。
细节二:把集群 Pod 网段和 Service 网段想清楚再动手。--pod-network-cidr和--service-cidr不能和宿主机网段冲突。默认的 pod CIDR 是 10.244.0.0/16,可以在 init 时显式指定。如果你是多个网卡的服务器,scheduler 可能会选错 IP,最好在kubeadm init时用--apiserver-advertise-address指定控制节点内网 IP。这个参数平时没人提,但多网卡环境下就是成败手。
细节三:镜像提前拉取,确认无误后再 init。强烈建议先执行kubeadm config images pull --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers这类的命令(网上有很多可用的镜像源,自行选择可访问的),把控制平面组件镜像全拉下来,再用crictl images确认。这一步看起来啰嗦,但能让你在 init 时避开大部分网络超时问题。初始化完成后,接下来要安装 CNI 网络插件,比如 Calico 或 Flannel。网络插件没装好,节点会一直是 NotReady 状态,这是最常见的“初始化成功但集群不可用”的原因,你要学会用kubectl get nodes看状态,用kubectl describe node <node>看 Condition 里的报错。
4. 概念点亮之后:如何规划你的K8s学习路径
4.1 学习顺序建议:从单机到集群
我经常被问“K8s 学习路线怎么走”。我的回答是:不要上来就折腾高可用、不要上来就配这配那。先老老实实把一个单控制节点的小集群跑起来,把 Pod、Deployment、Service、Namespace 这些核心概念全部亲手过一遍。
对没有真实多节点服务器的朋友,建议先用 minikube 或 kind 在本地跑一个模拟集群。minikube 更接近真实集群,kind 更快也更省资源。我个人喜欢用 kind,因为它在 Docker 里跑多个节点,很方便做 Node 异常测试。你可以在 kind 里创建“一个控制节点加两个工作节点”的集群,然后练习把 Pod 调度到不同节点、模拟节点宕机、滚动更新等场景。虽然 kind 不是生产环境,但核心 API 行为是一致的。
等你在本地熟练了,再上 kubeadm 搭一个 Rocky 虚拟机集群,重点体验初始化过程中那些坑。这时候你会真正理解“静态 Pod”“etcd 数据目录”“cgroup 驱动”这些名词的份量。我认为最理想的学习闭环是:本地模拟理解 API 行为 -> 真机集群理解组件交互 -> 回到 YAML 写一个完整应用(比如 Web + 数据库)加深资源依赖理解。
4.2 用Redis集群练手:K8s编排的真实场景
很多人搜“k8s redis 集群”,说明大家都在拿 Redis 当练习项目。这个方向不错,因为 Redis 是有状态应用的典型代表,比单纯的 Nginx 无状态部署更能锻炼你对 StatefulSet、PersistentVolume、Service 的理解。
先从简单的开始:部署一个单实例 Redis,使用 Deployment + PVC(持久卷声明)+ Service。这样你能熟悉 ConfigMap 存 redis.conf、Secret 存密码、PVC 存数据,理解有状态应用在容器里的正确姿势。然后第二步,部署 Redis 主从复制,这涉及到多个 Pod 之间的角色配置,需要用 Service 让从节点能访问主节点。你可以通过环境变量注入主节点地址,再用 ConfigMap 区分主从配置。最后再考虑 Redis Cluster 模式,这比较复杂,需要配置多个节点、集群初始化命令,还要开启集群总线端口。有人用 StatefulSet + headless Service 来做固定网络标识,让每个 Redis 节点都能通过稳定 DNS 名称互相发现。这个练习做完,你对 k8s 的“有状态应用编排”基本就入门了。
我建议做 Redis 练手时,一定要刻意制造故障:删除一个 Redis Pod,观察它的重建过程和数据是否丢失;把节点关机,观察 Pod 是否被调度到另一节点;改一下 Redis 密码的 Secret,重启 Pod,看看应用配置有没有及时更新。这些操作比背 YAML 字段更能让你理解 k8s 的设计哲学。
4.3 运维必看的排查工具箱:kubectl get/describe/logs/events
概念学完了,最后还是要落到命令上。kubectl的常用命令我建议按“三层排查法”记忆。
第一层:kubectl get看状态。kubectl get nodes看节点是否 Ready;kubectl get pods -n <namespace> -o wide看 Pod 状态和所在节点;kubectl get svc看 Service;kubectl get endpoints看 Service 后端是否有 Pod;kubectl get events --sort-by=.metadata.creationTimestamp看集群事件。这一层解决“现在到底是什么状态”。
第二层:kubectl describe看详情。kubectl describe pod <pod-name>会显示 Pod 的事件记录,比如镜像拉取失败、探针失败、调度失败等;kubectl describe node <node>能看节点资源压力;kubectl describe svc能看 Service 的 Endpoints 是否正常。这一层的价值是“为什么是这个状态”。
第三层:kubectl logs看日志。kubectl logs <pod-name> -f跟踪日志;如果 Pod 里多个容器,加-c <container-name>;如果 Pod 已经崩了,可以加--previous查看上一次崩溃容器的日志。这一层是“应用内部到底发生了什么”。
把这三层记熟,你可以覆盖绝大多数日常问题。再配合crictl命令在节点上查容器,比如crictl ps -a、crictl logs,就能穿透 “一层层抽象” 直达容器本身。
我个人在排查问题时的习惯是:先看事件(events),再看描述(describe),最后才看应用日志。因为很多问题在调度和启动阶段就已经暴露了,没必要先进容器看应用日志。比如镜像拉取失败,你kubectl describe pod一眼就能看到Failed to pull image,这时候看应用日志根本没意义。学会按这个顺序排查,你的效率会比翻来覆去logs高很多。
最后再分享一点我做实验的体会
如果让我给刚开始学 k8s 的人一句建议,那就是:概念要反复看,但一定要动手验证。你可以把文章里提到的核心对象都写成 YAML,一个个部署上去,然后用kubectl get观察它。哪怕现在写的 YAML 不规范,哪怕报错,都是宝贵的经验。我记得第一次自己搭集群成功的时候,看到kubectl get nodes全部 Ready,那种感觉真的是小有成就。后来遇到 Master 初始化报错,我学会了用crictl logs看静态 Pod 日志,才发现所谓“核心概念”其实早就藏在报错信息里——你不知道 api server、etcd、cgroup 是什么,你就不知道该去哪儿找答案。所以别嫌概念枯燥,它们是排查问题的导航图。以后你再搜“k8s安装部署”“k8s学习笔记”这类内容时,希望能想起这篇文章说的:先建地图,再上路,路就不歪了。