Docker与Kubernetes关系本质:容器运行时与编排系统的分工 1. 这不是“选哪个”的问题而是“谁在什么位置干活”的问题刚入行那会儿我常被客户问“我们该用 Kubernetes 还是 Docker”——问得特别认真眼神里带着技术选型的沉重感。后来我才明白这问题本身就像在问“该用扳手还是螺丝刀”听起来合理实则错位。Docker 是个容器运行时它负责把一个应用连同它的依赖、配置、文件系统一起打包成一个可移植的镜像然后在宿主机上拉起一个隔离的进程环境来运行它。说白了Docker 就是那个拧螺丝的工人干的是单点执行的活。Kubernetes 则完全不同它压根不碰具体的应用怎么启动、怎么读配置它只管调度、编排、扩缩容、故障自愈、服务发现和流量治理——它是整个工地的项目经理兼调度中心手里攥着几十上百台服务器的资源池盯着成百上千个 Docker 容器或其他兼容 OCI 的运行时在哪儿跑、跑得稳不稳、要不要加人手、坏了谁来顶上。所以“Kubernetes vs. Docker”这个标题本质上是个伪命题。真正该问的是我的应用规模、团队结构、发布节奏和稳定性要求是否已经超出了单机或小集群手动管理 Docker 容器的能力边界如果你还在用docker run启动三个服务配个docker-compose.yml拉起来那 Kubernetes 不仅是杀鸡用牛刀更是给厨房装了一套核电站控制系统。但如果你每天要发布 20 次、服务间调用链超过 15 层、峰值流量是平日的 8 倍、要求 99.99% 的可用性那 Docker 就只是你交付物的“出厂包装”而 Kubernetes 才是你生产环境的“操作系统”。关键词Kubernetes、Docker、容器编排、微服务运维、云原生基础设施全在这层分工逻辑里扎了根。这篇文章不是教你怎么二选一而是带你亲手拆开这两个工具的“工作界面”看清它们各自在哪条流水线上拧哪颗螺丝以及当你的业务从手工作坊升级为智能工厂时这条流水线该怎么重新布局。2. 核心设计哲学与能力边界的硬核拆解2.1 Docker专注“单体交付”的极致封装者Docker 的设计哲学可以用一句话概括让软件运行环境从“描述文档”变成“可执行文件”。在 Docker 出现之前部署一个 Python Web 应用你需要写一份《部署手册》里面写着“请安装 Python 3.9、pip install -r requirements.txt、配置 Nginx 反向代理到 8000 端口、确保 /var/log/myapp 目录有写权限……”这份文档在不同人的电脑上执行十次有八次会出错。Docker 把所有这些“环境要求”全部固化进一个分层的、只读的镜像文件里。它的核心机制是Union File System联合文件系统比如 Overlay2 驱动它把基础镜像如 ubuntu:22.04、运行时依赖如 python3.9、pip、应用代码、配置文件一层层叠在一起最上层是可写的容器层。这种分层设计带来了两个关键优势一是镜像复用率极高100 个基于同一基础镜像的微服务宿主机上只需存一份基础层二是构建速度快修改代码只重做最上层下层缓存直接复用。我实测过一个中等规模的 Java 应用从源码构建镜像使用 Docker BuildKit 的 cache 机制增量构建时间能从 6 分钟压到 42 秒。但 Docker 的能力边界也极其清晰它不关心容器 A 和容器 B 之间怎么通信。docker run --link是早期的临时方案早已废弃docker network create能建一个网桥让同网络下的容器通过容器名互相 ping 通但这只是 L2/L3 连通性没有服务发现、没有负载均衡、没有健康检查。你无法告诉 Docker“当用户访问 /api/orders 时请把请求轮询转发给所有正在运行的 order-service 容器”它没这个模块。它的 API 里也没有 “scale to 5 replicas” 这个命令。这就是为什么docker-compose up -d --scale web3看似能扩缩容但它本质是启动 3 个独立的、彼此无感知的容器实例一旦其中一台宿主机宕机这 3 个实例就全挂了Docker 自己不会去另一台机器上补一个。它的可靠性模型是单机维度的。2.2 Kubernetes面向“大规模协同”的分布式操作系统如果说 Docker 解决了“软件怎么打包”那么 Kubernetes 解决的就是“打包好的软件在成百上千台机器上怎么活着、怎么协作、怎么自我修复”。它的设计哲学是声明式 API 控制循环Control Loop。你不需要告诉 Kubernetes “先创建 Pod A再创建 Service B再更新 Ingress C”你只需要提交一个 YAML 文件声明你想要的终态“我需要 5 个 nginx 实例暴露 80 端口通过域名 myapp.com 访问”。Kubernetes 的各个控制器Controller——比如 ReplicaSet Controller、EndpointSlice Controller、Ingress Controller——会持续地“观察”集群当前状态并与你声明的目标状态比对。如果发现只有 3 个 Pod 在运行ReplicaSet Controller 就会立刻创建 2 个新的如果某个 Pod 的健康探针失败它会被自动驱逐并重建如果新 Pod 启动成功EndpointSlice Controller 就会把它的 IP 加入后端列表。这个“观测-比对-干预”的闭环每秒都在集群内数以千计地并行发生。Kubernetes 的核心抽象对象就是围绕这个闭环设计的Pod最小的调度单元不是容器而是一个或多个紧密耦合的容器的集合比如主应用容器 一个 sidecar 日志收集容器共享网络命名空间和存储卷。这是它区别于 Docker 单容器模型的根本。Service一个稳定的网络端点ClusterIP 或 NodePort背后是一组动态变化的 Pod IP。它内置了 kube-proxy 组件通过 iptables 或 IPVS 规则实现集群内部的服务发现和四层负载均衡。你永远不用记住 Pod 的 IP只认 Service 名。Deployment声明式地管理 Pod 的副本集和滚动更新策略。你可以定义 maxSurge1, maxUnavailable0这意味着更新时最多多启 1 个新 Pod且保证旧 Pod 一个都不能少直到新 Pod 完全就绪——这是零停机发布的底层保障。Ingress七层HTTP/HTTPS的流量入口网关支持基于 host 和 path 的路由规则、TLS 终止、重写等把外部流量精准导入到不同的 Service。提示Kubernetes 本身不运行容器它需要一个容器运行时Container Runtime。Docker Engine 曾是默认选择但自 v1.20 起Kubernetes 宣布弃用 dockershim转而拥抱更轻量、更符合 OCIOpen Container Initiative标准的运行时如 containerdDocker 自身也已将其作为底层运行时或 CRI-O。这意味着你在 Kubernetes 集群里看到的“容器”其底层可能是 containerd 拉起的Docker CLI 只是开发者本地的构建和调试工具。这个演进恰恰印证了二者关系的本质Docker 是构建和分发的“前端”Kubernetes 是运行和编排的“后端”。2.3 关键能力对比一张表看懂谁管什么下表不是功能罗列而是按“生产环境真实痛点”归类告诉你每个能力在哪个环节起作用生产环境典型需求Docker (单机/小集群) 是否原生支持Kubernetes 是否原生支持关键说明与实操影响单机快速启动与调试✅ 原生强大⚠️ 过重需 minikube/k3sdocker run -p 8080:80 nginx5 秒搞定K8s 需先kubectl apply -f pod.yaml再kubectl port-forward至少 30 秒起步。镜像构建与分发✅ 核心能力Dockerfile Registry❌ 无此模块K8s 不管你怎么造轮子只管轮子运过来后怎么装车。镜像构建仍是 Docker Build 或 BuildKit 的主场。跨主机容器网络互通❌ 需第三方插件Weave, Flannel✅ 原生CNI 插件Docker 默认 bridge 网络只限本机K8s 通过 CNI如 Calico自动为每个 Pod 分配集群唯一 IP跨节点直通。服务发现与 DNS❌ 仅限同网络容器名解析✅ 原生CoreDNS在 K8s 里curl http://orders-service:8080在任何 Pod 里都有效Docker 需手动维护 hosts 或用 Consul。自动扩缩容HPA❌ 无✅ 原生Horizontal Pod Autoscaler基于 CPU/内存或自定义指标如 QPS自动调整 Deployment 的副本数。Docker Compose 的 scale 是静态指令。滚动更新与回滚⚠️ Compose 支持但无健康检查保障✅ 原生Deployment 策略K8s 更新时新 Pod 必须通过 readinessProbe 才加入流量livenessProbe 失败则重启回滚kubectl rollout undo一键完成。存储卷生命周期管理⚠️docker volume本地持久化✅ 原生PV/PVC/StorageClassK8s 抽象出持久卷PV和申领PVC可对接 NFS、AWS EBS、Ceph 等生命周期独立于 Pod数据不随 Pod 消亡而丢失。多租户与资源配额❌ 无✅ 原生Namespace ResourceQuota一个集群可划分为 dev/test/prod Namespace为每个 NS 设置 CPU/Memory 上限避免一个项目吃光整台机器。这张表的核心启示是Docker 的价值在“交付链前端”Kubernetes 的价值在“运行时后端”。它们不是竞争对手而是上下游的协作者。一个健康的云原生交付流水线必然是开发者用 Docker 构建和测试镜像 → 镜像推送到私有 Registry → CI/CD 流水线触发 Kubernetes 的kubectl apply→ K8s 调度运行。试图用 Docker 替代 K8s 去管百台机器上的千个服务就像用 Excel 表格去管理一个上市公司的财务总账——不是不能记而是每次打开表格都要卡死而且根本没法审计、没法预警、没法自动化。3. 从零搭建一个可验证的对比实验环境3.1 环境准备轻量级但真实的双轨对照为了让你亲手触摸两者的差异我推荐一个零成本、零污染的本地实验方案k3s Docker Desktop。k3s 是 Rancher 开源的轻量级 Kubernetes 发行版专为边缘、CI/CD 和开发测试设计二进制只有 50MB内存占用 512MBcurl -sfL https://get.k3s.io | sh -一条命令即可安装1 分钟内启动一个单节点集群。Docker Desktop 则自带一个嵌入式的 k3s 集群在设置里开启同时保留完整的 Docker CLI 功能。这样你能在同一台 Mac/Windows 电脑上左手docker run右手kubectl apply实时对比。注意不要用 Minikube它基于 VirtualBox/Vmware 创建完整 VM启动慢、资源占用高、网络调试复杂。k3s 直接运行在宿主机 Linux 内核上Docker Desktop 内部也是 LinuxKit网络透明localhost:30000就能访问 NodePort省去所有端口映射烦恼。安装步骤精简如下下载并安装 Docker Desktop 最新版已内置 k3s。启动 Docker Desktop在右下角鲸鱼图标上右键 → “Settings” → “Kubernetes” → 勾选 “Enable Kubernetes”点击 “Apply Restart”。等待状态变为绿色 “Kubernetes is running”。打开终端验证# 检查 Docker 是否就绪 docker --version # 应输出 Docker version 24.x.x docker run hello-world # 第一次会下载镜像看到欢迎信息即成功 # 检查 Kubernetes 是否就绪 kubectl version --short # 应输出 Client Server 版本 kubectl get nodes # 应看到一个 Ready 状态的节点 kubectl get pods -A # 应看到 coredns、local-path-provisioner 等系统 Pod Running此时你的电脑就是一个微型的“混合云”Docker 引擎负责构建和运行单个容器k3s 集群负责编排和管理一组容器。接下来我们用同一个应用——一个极简的 Python Flask API —— 来走通两条路径。3.2 实验一Docker 单机模式——从构建到运行的全流程我们创建一个名为simple-api的应用它只有一个/health接口返回 JSON。创建项目目录mkdir simple-api cd simple-api编写app.pyfrom flask import Flask import os app Flask(__name__) app.route(/health) def health(): return {status: ok, host: os.getenv(HOSTNAME, unknown)} if __name__ __main__: app.run(host0.0.0.0:5000)编写requirements.txtFlask2.3.3编写DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD [python, app.py]构建并运行# 构建镜像打标签为 simple-api:latest docker build -t simple-api:latest . # 启动容器映射宿主机 5000 端口到容器 5000 端口 docker run -d -p 5000:5000 --name api-v1 simple-api:latest # 验证 curl http://localhost:5000/health # 返回 {status: ok, host: a1b2c3d4e5} docker ps # 查看容器 ID 和状态这就是 Docker 的全部构建Build、推送Push此处省略、运行Run。现在你有了一个运行中的服务。但问题来了如果这个容器意外退出比如kill -9它的 PID它不会自动重启。你得手动docker start api-v1。如果想扩容到 3 个实例得运行三次docker run并手动管理三个不同的端口5000, 5001, 5002再自己搭个 Nginx 做负载均衡。这就是单机 Docker 的“天花板”。3.3 实验二Kubernetes 模式——声明式编排的威力现在我们用 Kubernetes 来管理同一个simple-api镜像体验声明式的力量。首先确保镜像在 k3s 节点上可用。由于 k3s 和 Docker Desktop 共享同一个镜像仓库/var/lib/rancher/k3s/agent/images我们直接将本地构建的镜像加载进去# 将镜像保存为 tar 文件 docker save simple-api:latest simple-api.tar # 加载到 k3s 的镜像仓库Docker Desktop 内置 k3s 的路径 sudo k3s ctr images import simple-api.tar实操心得在生产环境你绝不会手动ctr import。正确的流程是docker build后docker push到私有 Registry如 Harbor然后在 K8s 的 Deployment YAML 中指定image: harbor.example.com/myproject/simple-api:v1.0。这里手动加载只为实验快速验证。编写deployment.yaml定义应用的“终态”apiVersion: apps/v1 kind: Deployment metadata: name: simple-api labels: app: simple-api spec: replicas: 3 # 我要 3 个副本 selector: matchLabels: app: simple-api template: metadata: labels: app: simple-api spec: containers: - name: api image: simple-api:latest # 使用我们刚加载的镜像 ports: - containerPort: 5000 livenessProbe: # 存活探针每 10 秒检查一次 /health httpGet: path: /health port: 5000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: # 就绪探针启动后立即检查通过才加入流量 httpGet: path: /health port: 5000 initialDelaySeconds: 2 periodSeconds: 5 --- # 定义 Service为这 3 个 Pod 提供稳定入口 apiVersion: v1 kind: Service metadata: name: simple-api-service spec: selector: app: simple-api ports: - protocol: TCP port: 80 targetPort: 5000 type: NodePort # 暴露到宿主机端口便于本地访问这个 YAML 文件就是你对 Kubernetes 的“下单”。它没有一句“启动”、“停止”、“重启”的命令只有“我要 3 个它们必须健康必须就绪必须通过 80 端口提供服务”。应用配置并验证# 应用 YAML kubectl apply -f deployment.yaml # 查看 Deployment 状态 kubectl get deployments # 应显示 simple-api 的 READY 3/3 # 查看 Pod 状态会看到 3 个 Running 的 Pod kubectl get pods -l appsimple-api # 查看 Service找到分配的 NodePort通常是 30000-32767 之间 kubectl get service simple-api-service # 通过 NodePort 访问假设 NodePort 是 31234 curl http://localhost:31234/health你会得到类似{status: ok, host: simple-api-7c8d9b4f5-abcde}的响应host字段是 Pod 的名字证明流量确实打到了后端的某个 Pod 上。亲手破坏见证自愈找到一个 Pod 的名字kubectl get pods -l appsimple-api删除它kubectl delete pod simple-api-7c8d9b4f5-abcde立刻执行kubectl get pods -l appsimple-api你会发现旧 Pod 状态变为Terminating几秒后一个新的 Pod名字不同以ContainerCreating状态出现很快变成Running。整个过程无需人工干预ReplicaSet Controller 自动补足了副本数。再次curl http://localhost:31234/health服务依然可用。这就是 Kubernetes 的“韧性”。3.4 实验三横向对比——一次发布两种体验现在我们模拟一次真实发布将应用升级到 v2 版本返回不同的健康状态。修改app.py添加版本标识app.route(/health) def health(): return {status: ok, version: v2, host: os.getenv(HOSTNAME, unknown)}重新构建镜像并打新标签docker build -t simple-api:v2 . sudo k3s ctr images import simple-api.tar # 再次加载Docker 方式升级停止旧容器docker stop api-v1删除旧容器docker rm api-v1启动新容器docker run -d -p 5000:5000 --name api-v2 simple-api:v2验证curl http://localhost:5000/health→ 看到version: v2问题这期间有服务中断stop 到 run 的间隙且旧容器的v1镜像还占着磁盘空间需手动docker rmi清理。Kubernetes 方式升级滚动更新修改deployment.yaml中的image: simple-api:v2然后kubectl apply -f deployment.yaml实时观察滚动过程kubectl rollout status deployment/simple-api # 等待 deployment \simple-api\ successfully rolled out kubectl get pods -l appsimple-api # 会看到旧 Pod 逐步 Terminating新 Pod 逐步 Running关键验证在整个滚动过程中持续执行curl http://localhost:31234/health你会发现响应始终成功只是version字段从v1逐渐变为v2。这是因为 K8s 严格遵循maxSurge和maxUnavailable策略默认值确保流量不中断。一键回滚如果 v2 版本上线后发现问题kubectl rollout undo deployment/simple-api瞬间回到 v1 版本所有 Pod 重新拉起。这个实验的价值在于它剥离了所有云厂商的包装让你在自己电脑上用最原始的命令亲手触摸到两种范式的温度。Docker 给你的是“确定性”——你敲下run它就启动Kubernetes 给你的是“确定性之上的弹性”——你声明“我要 3 个”它就给你 3 个无论机器重启、网络抖动、进程崩溃它都默默守护着这个数字。选择哪一个从来不是技术优劣的辩论而是你当前业务水位线的诚实映射。4. 真实世界中的选型决策树与避坑指南4.1 什么时候坚决用 Docker而不是 Kubernetes我见过太多团队因为“Kubernetes 很火”就盲目上马结果半年后 DevOps 工程师离职整个集群没人敢动CI/CD 流水线瘫痪最后降级回docker-compose。这不是技术倒退而是回归理性。以下场景Docker或docker-compose是更优解个人开发者或小团队 3 人的日常开发与测试你正在写一个博客系统本地用docker-compose up启动 MySQL、Redis、Nginx 和你的 PHP 应用所有服务通过docker-compose.yml的networks和depends_on关联。此时Kubernetes 的 YAML 文件、kubectl命令、Namespace 概念只会增加认知负担毫无收益。docker-compose的build、up、logs、exec命令简洁高效就是为你量身定做的。CI/CD 流水线中的构建与测试环节在 GitHub Actions 或 GitLab CI 中你用docker build构建镜像用docker run启动一个临时数据库容器来跑单元测试。这里的容器是“一次性的、短暂的、隔离的”Kubernetes 的调度、持久化、服务发现全是冗余。Docker 的轻量和快速启动是 CI 环境的生命线。嵌入式设备或边缘计算节点资源极度受限一个运行在树莓派上的家庭自动化网关只需要启动 2-3 个容器MQTT Broker、Home Assistant、Node-RED。k3s 虽然轻量但其 etcd、kube-apiserver、controller-manager 等组件仍需至少 512MB 内存和稳定的存储。而一个dockerd进程256MB 内存就能稳稳运行。在这种场景下追求“Kubernetes 兼容性”是舍本逐末。遗留系统容器化改造的过渡期一个单体 Java 应用你把它打包成 Docker 镜像用docker run -d --restartalways启动配上docker logs -f查看日志。这已经比以前java -jar app.jar 启动强太多了。此时强行拆分成十几个微服务再上 Kubernetes只会把一个简单问题复杂化。Docker 是容器化的第一步也是最坚实的基础。实操心得判断是否需要 Kubernetes有一个朴素的“三问法”我的服务实例数是否经常需要动态增减非手动如果答案是“否”K8s 的 HPA 就是摆设。我的服务是否部署在 3 台以上的物理/虚拟机上如果所有容器都在一台机器上K8s 的跨节点调度、故障转移能力就无从谈起。我的团队是否有专人或至少一人能理解并维护kubectl、YAML、Helm、Prometheus 这套生态如果没有K8s 不是加速器而是定时炸弹。宁可先用docker-composesystemd做好服务管理等团队能力跟上再平滑迁移。4.2 什么时候 Kubernetes 成为唯一选择当你的业务规模和复杂度已经让 Docker 的“单点管理”模式不堪重负时Kubernetes 就不再是选项而是必需品。以下是几个典型的临界点信号服务网格Service Mesh成为刚需当你的微服务数量超过 50 个服务间调用链路复杂需要统一的可观测性Tracing、流量控制Canary Release、安全策略mTLS。Istio、Linkerd 这些服务网格其数据平面Envoy Proxy必须注入到每个 Pod 中控制平面则深度集成在 Kubernetes 的 CRDCustom Resource Definition之上。你无法脱离 K8s 的声明式 API 和强大的 Admission Control准入控制来部署和管理它们。此时Docker 的--network参数连入门门槛都够不到。多云/混合云架构落地你的业务既要跑在 AWS 上也要跑在阿里云上还要在客户私有数据中心部署。Kubernetes 的核心价值之一就是提供了统一的、标准化的 API 抽象层。你写一套deployment.yaml在 AWS EKS、阿里云 ACK、本地 k3s 上都能运行只需更换底层的 CNI网络和 CSI存储插件。而 Docker 的run命令在不同云厂商的 VPC 网络、安全组、负载均衡配置下需要写无数份适配脚本维护成本指数级上升。Serverless 架构的底层支撑如今流行的 Knative、OpenFaaS、KEDA 等 FaaS函数即服务平台其核心就是 Kubernetes 的事件驱动扩展。它们监听 Kafka 主题、S3 对象上传、HTTP 请求等事件动态创建 Pod 来执行函数执行完自动销毁。这个“按需启停、毫秒级伸缩”的能力完全建立在 K8s 的调度器Scheduler和控制器Controller之上。Docker 本身不具备事件监听和自动扩缩的机制。AI/ML 训练任务的批量调度一个 PyTorch 分布式训练任务需要同时启动 8 个 GPU Pod它们之间通过 RDMA 网络高速通信。Kubernetes 的 Device Plugin 机制可以精确地将 GPU 设备分配给 Pod而Job和CronJob对象可以声明式地定义任务的并行度、重试策略和超时时间。你无法用docker run来协调 8 个容器的启动顺序、网络拓扑和资源绑定。4.3 从 Docker 到 Kubernetes 的平滑迁移路径很多团队最大的误区是把迁移当成一场“大换血”。正确的做法是把它看作一次“渐进式升级”。我亲历过三个成功案例总结出一条黄金路径阶段一容器化先行Docker Only目标所有应用包括数据库、中间件都打包成 Docker 镜像通过docker-compose在预发环境运行。关键动作建立公司级的 Dockerfile 最佳实践多阶段构建、非 root 用户、固定 UID/GID、私有镜像仓库Harbor、镜像扫描Trivy。成果交付物标准化环境一致性问题消失 80%这是后续一切的基础。阶段二Kubernetes 试点K8s for Stateful Apps目标选择一个无状态、流量不大、但对稳定性要求极高的核心服务如公司内部的 SSO 认证服务迁移到 Kubernetes。关键动作用 Helm Chart 封装该服务的 Deployment、Service、Ingress、ConfigMap接入 Prometheus Grafana 做基础监控配置livenessProbe和readinessProbe。成果团队熟悉了kubectl、YAML、Helm验证了 K8s 的自愈能力建立了第一个“成功样板”。阶段三平台化赋能K8s as a Platform目标将 Kubernetes 抽象为一个内部 PaaS 平台为所有业务线提供自助服务。关键动作开发一个简单的 Web 控制台或 CLI 工具业务方只需填写应用名、Git 仓库地址、端口号后台自动生成 Helm Chart 并helm install集成 CI/CDgit push自动触发构建、镜像推送、K8s 部署。成果DevOps 团队从“救火队员”转变为“平台建设者”业务迭代速度提升 3 倍以上。常见问题速查表来自我踩过的坑问题现象根本原因解决方案我的教训Pod 一直 Pendingkubectl describe pod显示0/1 nodes are available: 1 node(s) had taints that the pod didnt tolerate.节点被打上了NoSchedule污点如node-role.kubernetes.io/control-plane:NoSchedule而你的 Pod 没有容忍toleration该污点。在 Pod 的spec中添加tolerations字段或使用kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-移除污点仅限单节点测试。k3s 默认会给 master 节点打污点防止普通 Pod 调度上去。这是安全设计不是 bug。别急着删先学会容忍。Service 的 ClusterIP 无法从 Pod 内部访问curl http://my-service超时CoreDNS 解析失败或 Service 的selector标签与 Pod 的labels不匹配。kubectl get endpoints my-service看后端列表是否为空kubectl get pods --show-labels确认标签kubectl exec -it a-pod -- nslookup my-service测试 DNS。标签label是 K8s 的“胶水”90% 的网络问题都源于标签对不上。养成习惯kubectl get pods -l appmyapp和kubectl get svc -l appmyapp一起看。Ingress 404kubectl get ingress显示 ADDRESS 为空Ingress Controller如 nginx-ingress未安装或未正确关联到你的 Ingress 资源。kubectl get pods -n ingress-nginx确认 Controller Pod Running检查 Ingress 的ingressClassName是否与 Controller 的ingressclass名称一致。Ingress 是一个“接口”不是“实现”。你必须先部署一个具体的 Controller如 nginx、traefik它才会监听并处理你的 Ingress 资源。kubectl logs查不到日志显示Error from server: no such file or directoryk3s 默认使用containerd作为运行时其日志路径与 Docker 不同且kubectl logs依赖cri-o或containerd的日志插件。确保 k3s 启动时启用了--disable-agent不适用或检查containerd配置更可靠的方式是kubectl exec -it pod-name -- cat /proc/1/fd/1直接读取 stdout。不要迷信kubectl logs。在 k3s 环境crictl logsk3s 自带的 CRI 工具往往更准确。5. 总结工具