2026/10/13 4:13:26

Kubernetes小白入门:从Kind单节点集群开始的实战避坑指南

Kubernetes小白入门:从Kind单节点集群开始的实战避坑指南 1. 这不是教程是技术小白在Kubernetes门口反复推门的真实记录“技术小白的Kubernetes学习自白”——看到这个标题我笑了。不是笑它土而是笑它太准。去年冬天我在某公司内部做DevOps工具链调研时被临时拉去支援一个容器化迁移项目。当时我的全部K8s认知仅限于“它是个管Docker的”以及“听说比Docker Compose难十倍”。结果第一天就卡在kubectl get nodes返回No resources found上查了两小时才发现——集群根本没起来而我连kubeadm init和kubeadm join的区别都分不清。这根本不是知识断层是认知塌方。Kubernetes不是一门语言也不是一个工具它是一套用声明式API重构整个基础设施交付逻辑的操作系统级抽象。你学Docker是在学怎么打包学K8s是在学怎么让一百台机器像一台机器那样思考、调度、自愈。但市面上90%的入门资料一上来就甩出Pod、Deployment、Service、Ingress四件套再配一张“K8s架构图”仿佛只要背熟名词就能上岗。没人告诉你为什么Service需要ClusterIP为什么NodePort端口范围限定在30000-32767为什么kubectl apply -f和kubectl create -f在生产环境里是生死线我试过三套学习路径官方文档、付费视频课、某知名开源社区的实战营。结果全栽在同一个坑里——所有教程默认你已掌握Linux进程管理、网络命名空间、cgroups资源限制、etcd一致性原理这些“前置操作系统常识”。可现实是很多转行者连systemctl status docker和ps aux | grep docker的区别都说不全。这篇自白不教你怎么写YAML不列命令速查表也不画高大上的架构图。我就坐在你对面泡杯茶把过去11个月里踩过的每一个坑、抄错的每一行配置、重启失败的每一次journalctl -u kubelet日志原原本本摊开给你看。适合谁适合那个刚装完Minikube却连minikube dashboard打不开、看到CrashLoopBackOff就手抖、改完ConfigMap发现应用根本没刷新的人。你不需要懂Go语言但得愿意花三天时间把kubelet启动日志里那串failed to run Kubelet: unable to load bootstrap kubeconfig逐字翻译成中文。提示本文所有操作均基于v1.28.x稳定版所有命令、配置、错误日志均来自真实环境复现。文中所有“我”的操作你都可以在本地VirtualBoxUbuntu 22.04虚拟机中1:1复现无需云厂商账号不依赖任何外部服务。2. 为什么必须从“单节点伪集群”开始——绕开分布式幻觉的底层真相2.1 别碰kubeadm先用kind小白的第一道安全护栏几乎所有教程都从kubeadm init讲起。但这是个巨大陷阱。kubeadm设计初衷是给有经验的运维部署生产集群它要求你手动配置CNI插件、证书有效期、control plane高可用、etcd备份策略……而技术小白第一次执行kubeadm init99%会卡在[preflight] Running pre-flight checks阶段因为/proc/sys/net/bridge/bridge-nf-call-iptables没开启或swap没关或内核模块br_netfilter没加载。这时候你搜到的解决方案全是“执行以下三条命令”却没人解释sudo modprobe br_netfilter是在加载Linux内核的网桥过滤模块让iptables能拦截网桥流量echo 1 | sudo tee /proc/sys/net/bridge/bridge-nf-call-iptables是强制开启网桥对iptables规则的调用否则Calico等CNI插件无法生效sudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstab不只是关swap更是因为K8s调度器默认认为swap存在会导致内存超卖判断失真。这些不是命令是操作系统底层契约。而kindKubernetes IN Docker直接把这些契约封装进Docker容器里。它用一个Docker容器模拟整个K8s control plane连etcd、apiserver、scheduler、controller-manager全塞进去。你执行kind create cluster背后实际运行的是docker run -d --privileged --name kind-control-plane \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /lib/modules:/lib/modules:ro \ kindest/node:v1.28.0sha256:xxx注意kind不是玩具。它跑的是真实K8s二进制所有API行为、RBAC校验、准入控制器逻辑与生产集群完全一致。某公司用kind做CI/CD流水线测试每天跑2000次集成测试稳定性远超VM集群。我实测对比过在4核8G笔记本上kubeadm init平均耗时18分钟含排错kind create cluster平均耗时47秒。更重要的是kind的错误提示极其友好。比如当你忘记安装Docker时它不会报connection refused这种模糊错误而是明确说Docker daemon is not running并附上systemctl start docker命令。这种“错误即文档”的设计才是小白真正的学习加速器。2.2 Minikube的致命温柔为什么它让你学得更慢Minikube曾是我的首选。它支持VirtualBox、Hyper-V、Docker驱动还能一键启Dashboard。但三个月后我把它卸载了。原因很残酷它太“好用了”。Minikube默认启用--driverdocker这意味着它把整个K8s集群塞进一个Docker容器。问题来了当你执行kubectl get nodes看到的是minikube这个节点但当你docker ps却找不到apiserver容器——因为它被Minikube自己用nsenter注入到宿主机命名空间里了。这种“黑盒封装”导致你永远看不到真实的组件拓扑。更隐蔽的坑在日志。minikube logs命令看似方便但它把所有组件日志混在一起输出。而真实生产环境中kubelet日志在/var/log/syslogsystemd-journaldapiserver日志在/var/log/kube-apiserver.logetcd日志独立存储。你习惯了Minikube的日志聚合真到了线上查journalctl -u kubelet --since 2 hours ago | grep -i failed第一反应是“这命令怎么这么长”。我做过对照实验让两个新人分别用Minikube和kind部署Nginx。Minikube组平均用时12分钟但当要求他们手动修改kubelet配置如--max-pods50时80%的人找不到配置文件位置kind组平均用时15分钟多出3分钟是因要学kind export kubeconfig但100%能准确说出/etc/kubernetes/manifests/kubelet-config.yaml路径并理解这是通过ConfigMap挂载进容器的。实操心得Kind的config.yaml文件就是你的K8s启蒙教科书。创建集群时加--config kind-config.yaml文件内容如下kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: criSocket: /run/containerd/containerd.sock extraPortMappings: - containerPort: 80 hostPort: 80 protocol: TCP这段配置暴露了三个关键事实1kind底层用containerd而非dockerd2control-plane节点注册时指定CRI socket路径3端口映射是通过Docker的-p参数实现的。每行都是通往真实世界的钥匙。2.3 为什么跳过Helm——YAML才是K8s的母语所有教程都会说“用Helm管理应用更高效”。但对我这样的小白Helm是第二个认知黑洞。第一次用helm install nginx bitnami/nginx看到输出NAME: nginx、STATUS: deployed以为大功告成。结果curl localhost返回404。查了半天才发现bitnami的nginx chart默认不暴露NodePort需要加--set service.typeNodePort。Helm的本质是YAML模板引擎。它把values.yaml里的变量通过Go template语法注入到templates/deployment.yaml中。但小白根本不知道{{ .Values.service.type }}这种语法更不理解helm template命令能预渲染出最终YAML。你直接helm install等于把编译过程交给黑盒出了问题连错误源头都定位不到。我强制自己禁用Helm三个月。所有应用都手写YAMLNginx Deployment明确写出replicas: 2、image: nginx:1.21、ports[0].containerPort: 80Service区分ClusterIP集群内访问和NodePort宿主机端口映射并记住nodePort: 30080必须在30000-32767范围内ConfigMap用kubectl create configmap nginx-conf --from-filenginx.conf生成再在Deployment里通过volumeMounts挂载这个过程痛苦但有效。当我第7次手写apiVersion: v1、kind: ConfigMap、metadata.name:时突然意识到K8s所有对象都遵循同一套元数据结构。kubectl get返回的NAME、AGE、STATUS对应YAML里的metadata.name、metadata.creationTimestamp、status.phase。这种“所见即所得”的肌肉记忆是Helm模板永远给不了的。注意事项手写YAML时---分隔符必须顶格不能有空格缩进必须用空格不能用TabimagePullPolicy: IfNotPresent在开发环境可省略但生产环境必须显式声明否则每次启动都拉镜像。3. 从“Hello World”到“真·上线”一个Nginx服务的七层通关实录3.1 第一层让Pod跑起来——理解容器生命周期的三重门部署Nginx最简YAML只有11行但每行都是关卡apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80执行kubectl apply -f nginx-pod.yaml后你以为结束了不这只是开始。kubectl get pods显示Pending这是第一重门。Pending状态意味着调度器scheduler还没给Pod分配节点。可能原因有三资源不足kubectl describe pod nginx-pod里Events部分显示0/1 nodes are available: 1 Insufficient memory.——你的节点只有2G内存而Nginx Pod默认请求512Mi但scheduler还要预留系统开销污点Taint阻挡kubectl describe node看到Taints: node-role.kubernetes.io/control-plane:NoSchedule说明这是control-plane节点普通Pod不能调度上去亲和性Affinity冲突YAML里写了affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution但没匹配到任何节点标签。我卡在这里整整两天。最后发现是kind集群默认给control-plane节点打了node-role.kubernetes.io/control-plane:NoSchedule污点而我的Pod没加容忍toleration。解决方案不是删污点破坏集群安全而是加容忍spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule实操心得kubectl describe是你的X光机。Events部分按时间倒序排列最新事件在最上面。看到FailedScheduling立刻kubectl describe node查污点kubectl get nodes -o wide查资源kubectl get events --sort-by.lastTimestamp查全局事件。过了PendingPod变成ContainerCreating这是第二重门。常见原因镜像拉取失败kubectl describe pod看到Failed to pull image nginx:1.21检查kubectl get nodes -o wide确认节点能联网或换国内镜像源image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx:1.21存储卷挂载失败如果YAML里有volumeMounts但volumes没定义会卡在这里。终于变成Running但kubectl exec -it nginx-pod -- curl localhost返回Connection refused这是第三重门。原因很反直觉Nginx容器默认监听0.0.0.0:80但Pod IP是10.244.0.2curl localhost其实是curl容器自己的127.0.0.1而Nginx没监听这个地址。正确命令是kubectl exec -it nginx-pod -- curl 127.0.0.1:80。3.2 第二层让服务被访问——Service的三种类型本质解剖Pod是脆弱的。kubectl delete pod nginx-pod后新Pod的IP必然改变。所以需要Service做稳定入口。但ClusterIP、NodePort、LoadBalancer绝不是三个选项而是三层网络抽象类型本质适用场景小白易错点ClusterIPkube-proxy在每个节点上创建iptables规则将ClusterIP:Port转发到后端Pod的PodIP:Port集群内服务间调用认为ClusterIP能被宿主机访问实际不能除非kubectl port-forwardNodePort在ClusterIP基础上额外在每个节点的30000-32767端口开放映射规则为NodeIP:NodePort → ClusterIP:Port → PodIP:Port本地开发测试无负载均衡器的环境忘记防火墙放行端口或误用localhost:30080应为node-ip:30080LoadBalancer云厂商提供的外部负载均衡器将公网IP映射到NodePort再经kube-proxy转发生产环境对外服务本地用Minikube时kubectl expose生成LoadBalancer实际永远处于pending状态我亲手配置过全部三种。最值得深挖的是NodePort。执行kubectl expose pod nginx-pod --typeNodePort --port80 --namenginx-svc后kubectl get svc显示NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-svc NodePort 10.96.123.45 none 80:30080/TCP 2m这里30080是随机分配的NodePort。但很多人不知道这个端口映射发生在内核netfilter层而非用户态代理。kube-proxy会监听--masterhttps://127.0.0.1:8443实时同步EndpointsPod IP列表然后生成iptables规则# 查看真实规则需root权限 sudo iptables -t nat -L KUBE-NODEPORTS -n # 输出类似 # KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:http */ tcp dpt:30080 # KUBE-SVC-XXXXXX tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:http */ tcp dpt:30080这意味着NodePort性能极高几乎没有代理开销。但代价是端口范围受限30000-32767且所有节点都开放该端口——即使该节点上没有Nginx Pod。这就是为什么kubectl get nodes -o wide看到的INTERNAL-IP才是你应该curl的目标而不是127.0.0.1。常见问题curl node-ip:30080返回Connection refused。排查顺序1kubectl get nodes -o wide确认node-ip正确2sudo ufw status检查防火墙是否放行300803kubectl get endpoints nginx-svc确认Endpoints非空若为空说明Selector没匹配到Pod4kubectl describe svc nginx-svc看Selector字段是否与Pod的labels一致。3.3 第三层让配置可维护——ConfigMap与Secret的生存指南硬编码Nginx配置是自杀行为。kubectl edit configmap nginx-conf应该成为本能。但ConfigMap有两个致命细节第一挂载方式决定热更新能力用volumeMounts挂载整个目录如/etc/nginx/conf.d修改ConfigMap后Pod内文件不会自动更新必须重启Pod用envFrom注入环境变量修改ConfigMap后正在运行的容器环境变量不会变新启动的容器才生效。真正热更新的方案是subPath挂载单个文件volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf volumes: - name: nginx-config configMap: name: nginx-conf这样当你kubectl edit configmap nginx-conf修改default.conf内容K8s会触发inotify事件Nginx主进程收到SIGHUP信号后重载配置——这才是生产级热更新。第二Secret不是加密的ConfigMapkubectl create secret generic db-pass --from-literalpassword123456生成的Secretbase64解码后仍是明文。它的“安全”在于默认权限为400只读Pod内文件不可写可以设置immutable: true防止运行时被篡改RBAC可以精确控制哪些ServiceAccount能读取Secret。我曾因混淆二者付出代价把数据库密码放进ConfigMap结果被kubectl get cm -o yaml全量导出到Git仓库。后来改成Secret并在RBAC中严格限制apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: nginx-reader rules: - apiGroups: [] resources: [secrets] resourceNames: [db-pass] # 精确到名称非通配 verbs: [get]实操心得kubectl create configmap支持三种来源--from-file整个文件、--from-literal键值对、--from-env-file环境变量文件。但--from-file会把文件名作为key内容作为value而--from-literalkeyvalue则key和value都由你定义。新手常把nginx.conf文件直接--from-literal结果value变成乱码。4. 调试即学习那些让我凌晨三点还在kubectl logs的日志战场4.1CrashLoopBackOffK8s最狡猾的错误代码看到kubectl get pods里状态是CrashLoopBackOff别慌。这不是Bug是K8s在说“我尝试启动容器但它启动后立刻退出了我正在指数退避重试。”标准排查三步法kubectl logs nginx-pod --previous查看上次崩溃的容器日志--previous是关键kubectl describe pod nginx-pod看Events里是否有Back-off restarting failed containerkubectl exec -it nginx-pod -- sh如果容器还能进检查/proc/1/cmdline确认启动命令。我遇到的最典型案例Nginx容器CrashLoopBackOff。kubectl logs --previous输出空describe看到Started container nginx后直接Killing container。最后发现是livenessProbe配置错误livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10但Nginx默认不提供/healthz端点Probe持续失败K8s判定容器不健康强制kill重启形成死循环。解决方案要么添加/healthz路由需改Nginx配置要么改用exec探针livenessProbe: exec: command: [sh, -c, nginx -t systemctl is-active --quiet nginx]注意initialDelaySeconds必须大于容器启动时间。Nginx启动很快但Java应用可能需要60秒这时initialDelaySeconds: 120是合理值。periodSeconds太小如1秒会导致Probe风暴拖垮apiserver。4.2ImagePullBackOff镜像拉取失败的七种死法ImagePullBackOff表面是镜像问题实则是网络、权限、策略的混合故障。我整理出真实环境中的七种情况错误现象根本原因解决方案Failed to pull image nginx:1.21: rpc error: code Unknown desc failed to pull and unpack image docker.io/library/nginx:1.21节点DNS解析失败kubectl edit cm coredns -n kube-system在Corefile里加forward . 114.114.114.114Failed to pull image my-registry.com/app:latest: unauthorized: authentication required私有仓库未配置ImagePullSecretkubectl create secret docker-registry regcred --docker-servermy-registry.com --docker-usernameuser --docker-passwordpass再在Pod spec里加imagePullSecrets: [{name: regcred}]Failed to pull image nginx:1.21: context deadline exceeded节点网络超时如代理配置错误kubectl get nodes -o wide确认节点IPcurl -v https://registry-1.docker.io/v2/测试连通性Failed to pull image nginx:1.21: no matching manifest for linux/amd64 in the manifest list entries镜像不支持当前CPU架构ARM节点拉x86镜像kubectl get nodes -o wide看OS-IMAGE用docker build --platform linux/amd64重新构建Failed to pull image nginx:1.21: certificate signed by unknown authority私有仓库使用自签名证书kubectl cp ca.crt node-name:/etc/ssl/certs/ca.crt再kubectl exec -it node-name -- update-ca-certificatesFailed to pull image nginx:1.21: denied: requested access to the resource is deniedDocker Hub免费账户超出拉取限额换用阿里云镜像registry.cn-hangzhou.aliyuncs.com/google_containers/nginx:1.21或登录docker loginFailed to pull image nginx:1.21: failed to register layer: ApplyLayer exit status 1 stdout: stderr: write /usr/share/nginx/html/index.html: no space left on device节点磁盘满kubectl describe node看Conditionsdf -h清理/var/lib/docker实操心得kubectl get events --sort-by.lastTimestamp是全局诊断仪。它把所有节点、Pod、Service的事件按时间排序ImagePullBackOff事件上方往往紧跟着NodeNotReady或NetworkPluginNotReady这才是根因。4.3Pending状态的终极解法从调度器视角重看集群当kubectl get pods长期Pending90%的人只查describe pod。但真正的答案在调度器日志里。kind集群中调度器是kube-scheduler容器日志命令是kubectl logs -n kube-system deploy/kube-scheduler --since1h你会看到类似msg:Unable to schedule pod; no fit: 0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/control-plane:NoSchedule}, that the pod didnt tolerate这句话暴露了调度器的完整决策链获取所有节点列表0/1 nodes说明只找到1个节点过滤掉带NoSchedule污点的节点node-role.kubernetes.io/control-plane:NoSchedule剩余节点数为0调度失败。解决方案不是“删污点”而是理解污点的设计哲学control-plane节点要运行kube-apiserver等核心组件必须保证资源不被普通Pod抢占。正确做法是添加容忍toleration或创建专用worker节点。kind支持多节点集群只需kind-config.yamlkind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker执行kind create cluster --config kind-config.yaml后kubectl get nodes显示3个节点其中2个是worker没有污点普通Pod可自由调度。注意事项kubectl taint nodes node-name node-role.kubernetes.io/control-plane:NoSchedule-这条命令末尾的-是删除操作不是减号。漏掉会报错unknown taint effect。5. 小白避坑清单那些没人告诉你的“常识性”雷区5.1 YAML缩进空格与Tab的血泪战争K8s YAML对缩进极其敏感。service.yaml里spec: selector: app: nginx # 正确2个空格 ports: - port: 80 # 正确2个空格如果写成spec: selector: app: nginx ports: - port: 80 # 错误这里少缩进YAML解析器认为ports是selector的子项kubectl apply会报错error: error parsing service.yaml: error converting YAML to JSON: yaml: line 8: did not find expected key。这种错误不提示具体哪行只说“line 8”而line 8可能是ports下面的任意一行。解决方案VS Code安装YAML插件开启yaml.format.enable: true终端用yamllint service.yaml静态检查养成习惯所有-开头的列表项缩进必须比父级多2空格。实操心得用kubectl create service clusterip nginx-svc --tcp80:80 --dry-runclient -o yaml service.yaml生成骨架再手动修改比从零手写安全十倍。5.2kubectl applyvskubectl create生产环境的生死线新手常混用这两个命令。kubectl create是“创建”如果资源已存在直接报错Error from server (AlreadyExists)kubectl apply是“声明式更新”如果资源存在它会计算差异并执行PATCH操作。但危险在于apply会覆盖你手动修改的字段。比如你kubectl edit deployment nginx-deploy把replicas从2改成5然后kubectl apply -f deploy.yaml里面还是replicas: 2K8s会把副本数强行改回2生产环境黄金法则所有YAML文件必须存入Git仓库用kubectl apply -f部署绝对禁止kubectl edit在线修改所有变更必须走Git PR流程临时调试用kubectl scale deployment nginx-deploy --replicas5它只改replicas字段不影响其他配置。我曾因apply覆盖导致事故在测试环境kubectl edit改了Service的type: LoadBalancer第二天上线时apply旧YAMLService类型被切回ClusterIP所有外部流量中断。从此我的deploy.yaml头部必加注释# WARNING: This file is managed by Git. Do NOT use kubectl edit. # To change replicas, use: kubectl scale deployment nginx-deploy --replicas35.3 网络故障的终极定位从Pod到宿主机的五层穿透当curl service-ip失败按此顺序穿透排查Pod层kubectl exec -it nginx-pod -- curl -v http://localhost:80—— 测试容器内服务是否正常Service层kubectl exec -it nginx-pod -- curl -v http://cluster-ip:80—— 测试ClusterIP是否可达kube-proxy层kubectl get endpoints nginx-svc确认Endpoints非空kubectl logs -n kube-system ds/kube-proxy查日志节点网络层kubectl get nodes -o wide确认节点IPping node-ip测试连通性宿主机防火墙层sudo ufw statusUbuntu或sudo firewall-cmd --stateCentOS放行30000-32767端口。我修复过一个诡异问题curl node-ip:30080超时但ping node-ip通。sudo ufw status显示Status: inactive以为没问题。后来发现ufw只是前端底层是iptables执行sudo iptables -L INPUT | grep 30080发现规则被DOCKER-USER链拦截。最终用sudo iptables -I DOCKER-USER -p tcp --dport 30080 -j ACCEPT解决。常见问题速查表现象可能原因快速验证命令kubectl get pods无输出kubectl配置错误kubectl config view检查current-contextkubectl get nodes返回No resources found集群未启动或kubeconfig未加载kind get clusterskind export kubeconfigkubectl logs返回Error from server (BadRequest): a container name must be specifiedPod有多个容器未指定-c container-namekubectl get pod nginx-pod -o wide看CONTAINERS列kubectl port-forward svc/nginx-svc 8080:80后curl localhost:8080返回connection refused本地8080端口被占用lsof -i :8080或netstat -tulpnkubectl apply报error: the server doesnt have a resource type deploymentAPI版本过旧v1.16已废弃extensions/v1beta1kubectl api-resources6. 我的K8s学习路线图从“不敢删”到“敢重构”的心路历程现在回头看我的学习不是线性的而是螺旋上升的五个阶段。每个阶段都对应一种心理状态和一套必须掌握的动作第一阶段恐惧期第1-2周心理状态kubectl delete命令像核按钮敲下去前要深呼吸三次必须动作每天kind delete cluster kind create cluster重建集群10次直到手指形成肌肉记忆关键突破理解kubectl config current-context和kubectl config use-context学会在多个集群间切换。第二阶段模仿期第3-4周心理状态照着教程敲命令但每个参数都要查文档比如--dry-runclient和--dry-runserver的区别必须动作用kubectl create ... --dry-runclient -o yaml xxx.yaml生成所有资源YAML再手动删减理解每个字段作用关键突破发现kubectl explain pod.spec.containers能查任意字段的官方说明从此告别百度。第三阶段试错期第5-8周心理状态主动制造故障比如kubectl delete pod nginx-pod后观察ReplicaSet如何重建kubectl scale deploy nginx-deploy --replicas0后看Pod如何消失必须动作给每个YAML加labels用kubectl get pods -l appnginx按标签筛选理解标签是K8s的“DNA”关键突破kubectl get all --all-namespaces成为日常发现kube-system命名空间里藏着所有控制平面组件。第四阶段解构期第9-12周心理状态不再满足“能用”开始追问“为什么”比如kubectl get nodes为什么能列出节点答案是kubelet定期向apiserver上报心跳必须动作kubectl get componentstatuses查etcd-0、controller-manager