K8s入门实战:从Pod到Service打通容器编排核心链路

发布时间:2026/10/21 4:42:11
K8s入门实战:从Pod到Service打通容器编排核心链路
第一次翻开K8sKubernetes文档的时候我盯着Pod、Deployment、Service、Ingress这一串名词看了很久脑子里只有一个疑惑这些东西到底谁先谁后哪个才是根后来带过几个新人我发现大家卡住的地方出奇一致——不是概念理解不了而是缺一条能从“最小可运行单元”一路走到“真正对外提供服务”的完整路径。这篇就用3个动手案例把这条链路从头到尾走一遍。不管你是刚学完Docker想进阶的开发者还是在公司里接触过一点微服务但没正经碰过K8s的运维同学只要把这条路径跑通一遍你至少能做到三件事看懂绝大多数YAML文件、说清一个应用部署到K8s要经过哪些环节、遇到Pod起不来知道往哪几个方向排查。如果还能动手敲一遍命令那收获会比看十篇教程都大。1. 容器编排到底是什么K8s解决的核心痛点1.1 从“单机跑容器”到“一群容器需要管家”先聊一个最朴素的问题都有Docker了为什么还要K8s假设你手上只有一个容器docker run -p 80:80 nginx一条命令搞定非常爽。但当你开始负责一个项目需要同时跑前端、后端、数据库、消息队列加起来七八个容器时单机Docker就管不过来了。Docker Compose能在单机上统一编排这些容器可是一旦规模上去比如公司里有几十台机器、几十个微服务、每个服务还要多副本问题就开始暴露哪个服务该调度到哪台机器上某个容器挂了谁会把它拉起来还是说只能等人发现再手动重启流量大了想加副本怎么加发布新版本怎么做到不中断服务这些问题归结起来就一句话容器多了需要有个东西来当“管家”。这个管家要负责分配机器、保证副本数量、管理网络、分配存储、处理滚动发布。K8s就是这个管家而“容器编排”指的就是这一整套自动化的调度和管理机制。用大白话类比一下单容器就像你住单间只要管好自己就行到了微服务阶段就像整栋楼有几百户人家需要有物业系统来管理水电、安保、电梯、公共区域。K8s就是这套物业系统Pod是住户Deployment是物业员Service是大门门禁。这套类比虽然粗略但能帮你先把心智模型搭起来。1.2 声明式思维K8s和传统运维最大的不同想要理解K8s必须先理解“声明式”这个词。这是它和传统运维方式在根子上的区别。传统方式是命令式的你告诉系统“先创建这台VM再装软件再启动进程如果不通了就重启”。每一步都要你下指令系统不会主动推断你要什么。K8s反过来你只需要告诉它“我期望有3个nginx副本、每个副本用1核CPU和512M内存”剩下的事情——怎么创建、调度到哪里、一个挂了怎么办——全部由K8s自己处理。这就是声明式的核心描述目标状态而不是描述操作步骤。我见过太多新手在这个地方绕不出来总想用写脚本的思路去理解K8s问“那它到底是先创建Pod还是先创建Deployment”其实K8s不关心你要它“先做什么、后做什么”它只关心“当前状态是不是你期望的状态”。如果不是它就调协reconcile到你期望的状态。理解了这个后面看啥YAML都会顺很多。1.3 三个绕不开的基础对象Pod、Deployment、Service整篇博文会围绕三个核心对象展开先用一张关系脑图把位置摆清楚PodK8s里最小的调度和运行单元。可以理解为一个“逻辑主机”里面可以有一个或多个容器。这些容器共享网络命名空间能用localhost互相访问也可以共享存储卷。Deployment一个控制器负责管理一组Pod的“期望状态”。你告诉它“我要2个副本”它会创建出来并一直盯住少了就补多了就删。Service一个稳定的访问入口。Pod是会换IP的重建一次IP就变Service给一组Pod提供一个固定的虚拟IP和DNS名字并在这些Pod之间做负载均衡。三者的关系像一条流水线Deployment负责生产PodService负责给这堆Pod挂上“门牌号”并分发流量。案例一先接触Pod案例二用Deployment管起来案例三用Service把访问入口打通。这条路径走完你对容器编排的整个主干就基本有数了。2. 案例一创建第一个Pod先跑起来一个最小单元2.1 环境准备本地先搭一个单机K8s动手之前得先有个集群。生产环境一般是多台机器组成的正式集群但学习阶段完全不需要那么重本地就有轻量方案。常用的有两类minikube官方推出的本地单机方案总体思路是帮你启动一个虚拟机或容器环境里面跑一个单节点的K8s。对新手最友好自带一些辅助插件。kind全称是Kubernetes in Docker本质是把K8s节点也跑成容器。启动更快比较省资源但有些网络相关的体验不如minikube贴近真实集群。我个人建议优先试minikube。安装完成后执行minikube start --cpus2 --memory2048启动一个本地集群等看到“Done”提示就说明控制面和节点都起来了。可以用kubectl get nodes验证一下能看到一个状态为Ready的节点就算环境OK。这里提醒一句国内网络环境下拉取kubeadm和镜像可能较慢minikube支持通过参数指定镜像仓库和节点镜像地址。常见的做法是设置--image-mirror-countrycn这类参数或者提前配置好镜像加速器。如果第一次minikube start卡住先别急着重装多半是镜像下载问题查一下网络再试。2.2 用一条命令跑起第一个Pod命令式操作在K8s里可以通过kubectl命令直接操作。最简单的第一条指令是这样kubectl create deployment nginx --imagenginx这条命令做了什么事情它创建了一个DeploymentDeployment又替你创建了一个PodPod里的容器镜像就是nginx。执行完之后看Pod状态kubectl get pod正常情况下你会看到类似下面的输出NAME READY STATUS RESTARTS AGE nginx-9d8f7b6c7-abc12 1/1 Running 0 12sREADME是1/1STATUS是Running说明容器已正常运行。注意这个Pod名字后半段那串随机字符是Deployment下面的ReplicaSet自动拼上去的不用纠结后面案例二会解释。再细看Pod的信息kubectl get pod -o wide kubectl describe pod nginx-9d8f7b6c7-abc12-o wide会额外显示Pod所在的节点、Pod IP等信息describe是K8s排查问题的第一大利器它会列出这个Pod的事件、挂载、条件等后面排障章节还会再聊。这里我想强调一个基础命令模式kubectl 动作(get/describe/logs/exec/delete...) 资源类型 名称。这套模式贯穿始终记住了它后面所有命令都能推断出个大概。2.3 用YAML定义Pod声明式写法的门道命令操作适合临时验证但正式场景更推荐用YAML文件。因为YAML能版本化、能评审、能签入Git别人看代码就能知道你部署了什么。这也是“基础设施即代码”思想的具体落地。下面是一个最基础的Pod清单文件apiVersion: v1 kind: Pod metadata: name: my-nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80保存成pod-nginx.yaml然后执行kubectl apply -f pod-nginx.yaml kubectl get pod my-nginx几个关键字段逐个拆一下apiVersionAPI版本。不同资源版本不同Pod是v1Deployment是apps/v1后面记不住就查文档。metadata.name资源名称在同一命名空间内必须唯一。metadata.labels标签。这是K8s组织资源的纽带Service靠标签选择PodDeployment靠标签管理Pod后面会反复用到。spec.containers容器列表。每个容器必须有名字、镜像其他如端口、资源配额、环境变量都是可选但重要。spec.containers.ports.containerPort声明容器监听的端口。注意它更多是“信息性”的不写也不会限制端口监听但建议写方便别人理解也让Service选择时更明确。这里要特别强调 labels 不是摆设。K8s里几乎所有关联关系都靠“标签选择器”来维护。如果你建的Pod写错了labels后面Service根本找不到它流量自然不通。新手写YAML最容易翻车的地方就在这里。2.4 进容器看日志验证Pod真的活着Pod跑起来之后怎么确认它真的在正常工作最常用的两个操作看日志、进容器。kubectl logs my-nginx kubectl exec -it my-nginx -- /bin/bash第一条会打印nginx的访问日志和启动日志能看到“start worker process”之类的内容说明服务进程起来了。第二条是进入容器内部你可以执行ls、curl localhost之类的命令直接感受容器里的文件系统和网络。注意--后面的内容是要在容器内执行的命令。如果镜像里没有bash部分精简镜像只有sh可以把/bin/bash换成/bin/sh这个坑我踩过好多次。走到这里你已经成功创建、查看、进入了第一个Pod。这个过程虽然基础但把“Pod是最小调度单元”这回事落到了实处。3. 案例二用Deployment管理Pod体验自愈与扩缩容3.1 裸Pod的问题为什么不能只靠Pod案例一你创建了一个Pod假设上面跑的是公司核心API服务。某个凌晨它挂了你睡得正香第二天上班一看业务挂了怎么办如果用的是裸PodK8s只会告诉你“这个Pod已经挂了”不会帮你拉起一个新的。裸Pod没有自愈能力这是它最大的短板。其次裸Pod没有“副本”的概念。你想让这个服务跑3份就得手动创建3个Pod而且这3个Pod也不知道彼此的存在。流量怎么分摊谁负责协调都不存在。Deployment就是来解决这两个问题的。它像一个“监工”你告诉它“我要2个一模一样的Pod”它负责创建并持续保证刚好有2个。这2个Pod哪个挂了它就立刻新起一个补上你想改成5个它也立刻调整。Deployment和Pod、ReplicaSet的关系是这样的Deployment管理ReplicaSetReplicaSet负责确保指定数量的Pod存在Deployment是更上层的概念它额外带滚动更新、回滚、版本记录能力。ReplicaSet更像“计数器和副本管理器”平时你不用直接操作它。3.2 创建Deployment并观察副本管理还是用nginx这次用Deployment方式创建并指定2个副本apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80仔细看这个YAML和Pod文件对比有两点关键区别。第一多了spec.replicas它声明期望副本数是2。第二spec.template里嵌了一个Pod的定义。Deployment不管三七二十一先把这个template复制若干份然后让ReplicaSet去盯着数量。另外spec.selector.matchLabels必须和spec.template.metadata.labels匹配这个匹配关系是Deployment能找到“自己管辖的Pod”的依据。这段写错了Deployment会立刻报错。创建并观察kubectl apply -f deploy-nginx.yaml kubectl get deploy nginx-deploy kubectl get rs kubectl get pod你会看到2个Pod分别被调度到节点上。想看它们在哪个节点、Pod IP是多少kubectl get pod -o wide3.3 模拟故障体验K8s的自愈能力接下来做一个实验手动删掉其中一个Podkubectl delete pod nginx-deploy-xxxx-yyyy然后立刻再看Podkubectl get pod到这一步你会发现K8s在极短时间内通常几秒就自动新建了一个Pod名字后缀是新的一串随机字符状态迅速变成Running。因为Deployment发现“Pod数量少于期望值2个”于是通知ReplicaSet补了一个。这里我想让你刻意留意一下时间点。你可以先kubectl delete pod xxx马上执行kubectl get pod -w-w是watch模式持续监听变化。你会看到Pod生命周期的变化被删的那个Terminating新PodContainerCreating然后Running。这种能力在生产里就叫“自愈”。你不需要写任何重启脚本不需要接告警后半夜起床手动删除K8s会把状态调协到期望状态。这也是很多人说“K8s是声明式系统”的最佳实证。3.4 滚动更新与回滚换个版本不宕机自愈是Deployment的基本功滚动更新是它的另一项核心能力。现在把镜像从nginx:1.25升级到nginx:1.26。有两种方式一种是改YAML再apply一种是直接命令行设置kubectl set image deployment/nginx-deploy nginxnginx:1.26执行后立刻看更新过程kubectl rollout status deployment/nginx-deploy滚动更新的本质不是把2个Pod全删了再建而是逐个交替先起一个新的等它Ready后删一个旧的再起另一个新的直到所有节点都变成新版本。这个过程里服务始终有副本在跑不会出现全体停摆的窗口期。如果更新后发现有问题要回滚也很方便kubectl rollout undo deployment/nginx-deployK8s会按之前的版本记录回滚到上一个可用的版本。这个机制靠的是Deployment的历史版本记录默认会保留最近10个版本。有时间的话可以kubectl rollout history deployment/nginx-deploy看看版本记录理解一下“版本管理”这层意思。关于更新节奏K8s还给Deployment提供了两个关键参数maxSurge和maxUnavailable。前者控制“最多能多出几个Pod”后者控制“最多允许几个Pod不可用”。生产环境如果服务有状态或者资源有限这两个参数需要仔细调否则可能因为同时启动太多Pod导致资源竞争。新手先了解这层概念等真正上线时再精细化。3.5 手动扩缩容一行命令加副本流量上来了要临时加副本一条命令搞定kubectl scale deployment nginx-deploy --replicas5 kubectl get pod执行之后Deployment会自动调整ReplicaSet的期望副本数Pod总量从2变成5。如果流量回落再执行一次--replicas2就能缩回去。生产环境还可以配置自动扩缩容HPAHorizontal Pod Autoscaler实现“CPU超过某个阈值就自动加副本”。这块是K8s的进阶内容新手可以先知道有这个东西等真正掌握Deployment再说。这里有个常见的疑问Pod数量变了IP变了客户端怎么找到它们答案是光靠Deployment还不够普通客户端不能直接连Pod IP就得引出第三个案例的主角——Service。4. 案例三用Service暴露服务从集群内到集群外4.1 为什么Pod有IP却不能直接访问你现在有5个nginx副本每个Pod都有自己的IP。理论上访问任何一个Pod的IP加80端口都能访问nginx服务。但现实是没人会直接去连Pod IP。原因有三个。第一Pod IP不稳定。Pod重建后IP就变了你没法在配置里写死。第二IP是集群内部网络地址从集群外基本路由不到。第三多个副本需要负载均衡直接连某个Pod无法分摊流量。Service就是解决这三个问题的稳定层。它给一组Pod提供一个固定的虚拟IP和服务名外界只要访问这个稳定入口Service内部帮你转发到各个Pod。4.2 ClusterIP集群内部访问的默认方案创建Service也有两种方式。先看YAMLapiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx ports: - port: 80 targetPort: 80这里的关键点是selector.app: nginx它会把所有带app: nginx标签的Pod都纳入这个Service后端。这就是前面为什么强调labels重要的原因。将其保存为svc-nginx.yaml执行kubectl apply -f svc-nginx.yaml kubectl get svc你会看到nginx-svc的CLUSTER-IP一栏有一个IP这个IP集群内部可以访问。验证一下kubectl run test-pod --imagenginx --restartNever --rm -it -- curl http://nginx-svc这条命令会创建一个临时Pod在里面通过Service名nginx-svc访问服务等价于在集群内部另一个Pod里访问http://nginx-svc:80。这就是Service的域名机制同一个命名空间内Service名就是可用的DNS名字。这里要理解Service的转发细节。Service本身并不是一个进程或代理容器它更像一个“虚拟服务”入口真正干活的是每个节点上的kube-proxy组件。kube-proxy会维护一组转发规则把发往CLUSTER-IP:port的流量均匀分发到后端Pod。想要查看当前Service关联了哪些后端Podkubectl get endpoints如果Service的selector写错这个列表会显示为空或缺少IP流量自然不通。这也是排障时最该先看的东西之一。4.3 NodePort把端口开到宿主机上ClusterIP只有集群内部能访问那外部怎么办最基础的做法是把Service类型改成NodePort。apiVersion: v1 kind: Service metadata: name: nginx-svc spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080重新apply后查看kubectl get svc注意type变成了NodePort端口段里显示30080/TCP。NodePort的原理是在每个节点上开放一个端口30000-32767之间任何打到节点IP:30080的请求都会被转发到Service的ClusterIP再路由到后端Pod。本地minikube环境下验证可以开另一个终端跑minikube service nginx-svc --url它会返回一个可在宿主机浏览器打开的地址。NodePort的优点是简单把宿主机端口暴露出去就能访问。缺点是端口范围有限30000-32767、所有节点都要占用端口生产环境直接暴露也不够安全。一般只在测试环境或临时联调时用。线上更推荐下面这个。4.4 Ingress更接近生产的暴露方式说完NodePort再说Ingress。上一节那种方式相当于给每个服务开一个宿主端口但生产实践里通常会有很多服务不可能每加一个服务就开一堆端口。Ingress解决的是“七层HTTP路由”的问题。注意一个常见误区Ingress不是一种Service而是独立的一类API对象。它描述“什么样的外部请求路由到集群内哪个Service”要真正生效集群里还必须装一个Ingress Controller组件。这里可以用一句话快速理清两者的关系Ingress是规则Controller是执行规则的引擎。没有ControllerIngress YAML再漂亮也不生效这个顺序请一定记牢。一个最简单的Ingress规则示意apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ing spec: rules: - host: nginx.demo.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80这个Ingress表示当外部请求的Host是nginx.demo.comController就把请求转发给nginx-svc这个Service的80端口。使用Ingress前需要在本机安装Ingress Controller相关组件具体命令因环境而异在minikube里一般是一行命令启用插件kind则需要额外部署Controller的配置。这些步骤每个环境不太一样建议以官方安装文档为准。Ingress的实际优势在于可以按域名、按路径分流可以配合HTTPS证书可以做限流和更细粒度的路由控制。因此生产环境普遍把Ingress作为集群统一的流量入口后端再配合Service解耦。到这里从Pod搭建最小单元到Deployment管副本再到Service和Ingress打通外部访问这条主干链路已经完整走了一遍。5. 新手高频问题排查手册几个我踩过的坑5.1 ImagePullBackOff镜像一直拉不下来新手遇到最多的报错之一Pod状态会显示ImagePullBackOff或ErrImagePull。含义是镜像拉取失败K8s重试几次也拉不下来。排查第一步先看详细事件kubectl describe pod pod-name kubectl get events | grep -i image常见原因有几个镜像名称写错或者tag不存在镜像在私有仓库而集群没有配置对应的镜像拉取凭证imagePullSecrets网络原因导致镜像仓库连接超时本地临时环境用的仓库地址需要替换。如果是网络原因常见的处理是在容器运行时层面配置镜像加速器或者把镜像名换成可达的镜像仓库地址。确认修改后重新apply或删掉Pod让Deployment重建即可。5.2 CrashLoopBackOff容器反复重启Pod能创建但容器起来没几秒就挂K8s会不断重启它状态显示CrashLoopBackOff。Band这个状态名字很直白“崩溃循环退避”。先看日志尤其要看上一次崩溃的日志kubectl logs pod-name kubectl logs pod-name --previous--previous能查看容器上一次退出前的日志这是查CrashLoop最常用的命令。常见诱因包括应用启动需要但缺失的环境变量、配置错误、启动端口被占用、健康检查探针livenessProbe配置太严格导致直接被判定不健康被杀掉。最后一种比较隐蔽如果日志看不出异常但容器一直重启就要检查有没有配探针以及探针的路径和端口是否正确。5.3 PendingPod一直排在队列里Pod始终Pending说明还没有被调度到某个节点上。和新手最相关的两个原因集群节点资源不足比如CPU或内存不够镜像本身需要较大存储或特定条件但没有匹配的节点。关键的排查命令还是kubectl describe pod pod-name在Events部分会看到调度器给出的提示比如 “0/1 nodes are available: 1 Insufficient cpu”这就说明节点CPU不够需要释放资源或减小容器请求的资源量。如果节点不对则要检查标签和污点相关配置。很多新手只盯着get pod看状态其实describe才是真正告诉你“为什么”的工具。任何诡异的K8s问题先把describe和events打开信息量会翻倍。5.4 Service不通先查Selector和外层路由按案例三部署完Service发现通过Service访问不了Pod。这时候查什么按顺序来kubectl get endpoints service-name kubectl describe svc service-name第一个命令看Service后端有没有Pod。若ENDPOINTS为空几乎可以断定是selector和Pod标签不匹配。检查Deployment里Pod模板的labels和Service的selector是否严格一致注意大小写和字段名。如果Endpoints正常再试集群内部访问。我这里遇到过一个典型情况Service的targetPort写成了80但容器实际监听8080端口不一致也会导致请求超时。所以portService对外端口、targetPort容器内端口这两个概念一定要分清。5.5 排障速查表把常用排查命令集中放在这里收藏以后能少走很多弯路现象优先命令关注点Pod一直Pendingkubectl describe pod NAMEEvents里的调度失败原因Pod反复重启kubectl logs NAME --previous上一次崩溃日志镜像拉不下来kubectl describe pod NAME 或 get events镜像名、仓库可达性、密钥Service访问不通kubectl get endpoints SVC_NAMEEndpoints是否为空不知道去哪看问题kubectl get events --sort-by.lastTimestamp整个命名空间近期事件最后多说几句我个人带新人的经验里最容易出效果的学习路径不是先背概念而是照着这套流程动手敲一遍再把每个环节故意弄坏一次体会排障过程。比如故意把selector写错看看Service怎么找不到后端把副本缩到0看看Deployment怎么报错。踩过这几个坑之后对K8s的理解会比看十遍文档都深。再分享一个小技巧平时可以多开一个终端跑kubectl get pod -w这样你的每一次操作创建、删除、更新都能立刻看到Pod状态迁移。这种“状态变化可视化”会帮你把“声明式系统持续调协”这句话真正内化而不是停留在字面理解。下一步可以试着扩展的内容包括ConfigMap和Secret给Pod注入配置、PVC挂载持久化存储、命名空间做多环境隔离、HPA做自动扩缩容。以这条路径为基础再去学这些会顺很多。K8s确实往上爬会觉得内容多但把主干路径走扎实之后剩下的都是往这棵树上挂叶子的事。