最近身边又有不少人开始提K8s这个词,但真问起来,大部分人的状态都是"知道它很火、好像跟容器有关、简历里写了会一点,但真问架构就沉默"。我自己当年也是这么过来的,最开始被一堆术语砸懵,Pod、Deployment、Service、Ingress、Namespace……每个词都认识,连起来不知道在说什么。这篇k8s扫盲,我尽量用大白话把这些概念一次讲透,不是给你背概念,而是帮你建立一个能直接上手的心智模型。等你把这篇文章读完,起码能看懂一份常见的部署YAML,能在本地跑起一个真正由K8s管理的应用,也能分清哪些坑是新手必然要踩的。
1. K8s到底解决什么问题:先建立心智模型
1.1 从"单机部署"到"容器编排"的跳跃
理解K8s之前,你需要先理解一个背景:为什么单机Docker不够用。Docker确实解决了"环境一致"的问题,同一份镜像走到哪跑哪,开发、测试、生产不再因为环境差异打架。但当你手里有十几个服务,每个服务要跑两三个副本,分布在好几台机器上的时候,问题就来了:这些容器跑在哪些机器上?谁负责拉起挂掉的容器?容器IP变了怎么通知下游?版本升级怎么做到不停机?
这些问题有个统一的名字——编排。K8s的全称是Kubernetes,它就是一个容器编排平台。你可以把K8s理解成一个"容器调度中心",或者说一个"自动化运维管家"。你告诉它"我要跑三个nginx副本,暴露80端口",它自己去找合适的机器、拉镜像、启动容器、监控状态、自动替换挂掉的实例。你不用再手动SSH到每台机器上去敲docker run,这就是它存在的意义。
下面这个表格能帮你更直观地感受差异:
| 场景 | 没有K8s(裸Docker) | 有K8s |
|---|---|---|
| 启动应用 | 手动在每台机器执行docker run | 一个kubectl apply -f deployment.yaml搞定 |
| 容器挂了 | 收到报警,手动重启 | 自动重启,换一台机器拉起新Pod |
| 流量分发 | 手动配Nginx反向代理 | Service自动绑定Pod IP,随Pod变化更新 |
| 滚动升级 | 手动一批批启停容器 | 滚动更新,逐步替换,失败自动回滚 |
| 水平扩容 | 手动复制容器,改配置 | 修改replicas,或配置HPA自动扩缩容 |
所以如果你只有一个服务、两个副本,跑在一台机器上,K8s对你来说确实是过度设计。但一旦你的微服务数量上来、机器多了、发布频率高了,手动运维的成本会指数级上升。K8s的"不香"之处,恰恰是因为它把运维从"人肉操作"变成了"声明式管理"。
1.2 为什么入门这么难:术语太多,先只管三个词
K8s被称为"术语地狱"不是没道理的。光对象类型就有几十种,很多人打开官方文档直接晕倒。但我给你的建议很明确:刚开始扫盲阶段,你只需要关注三个核心对象——Pod、Deployment、Service。
其他的比如Namespace、ConfigMap、Ingress、PersistentVolume,它们都是在这个基础上的扩展。你先把三根支柱立起来,后面再逐步补充,就不会觉得那么乱了。这就好比学游泳,你先学换气、蹬腿、划手,不要一上来就研究自由泳的转身技术。
很多教程喜欢一上来就把Master节点、Worker节点、etcd、kubelet、kube-proxy全部铺开讲,搞得人头晕。这些东西确实重要,但那是"原理进阶"的内容,不是你第一次接触K8s时最应该先理解的东西。K8s的用法和学习K8s的架构原理,是两条路。扫盲阶段,我们应该先走"用起来"这条路。
2. 把Pod、Deployment、Service这三根支柱立起来
2.1 Pod:一台"最小运行单元"
Pod是K8s中最小的调度和运行单元。你可能会问,为什么K8s不直接调度容器?为什么中间非要套一层Pod?这个设计其实很聪明。一个Pod相当于一台"逻辑主机",Pod里的容器共享网络栈、共享存储卷,它们可以通过localhost互相访问,就像在一台机器上一样。
最常见的用法是一个Pod里只跑一个容器,这时候你就把Pod理解成"容器的壳"就行。但有一种场景必须用多容器Pod:比如一个应用容器需要日志采集边车(sidecar),日志采集器要跟应用容器部署在同一台"逻辑主机"上,共享同一个日志目录,这个时候多容器Pod就是最优解。
Pod还有一个特点:它是"短命的"。K8s创建Pod后,Pod会被调度到某个节点上运行,但如果节点挂了、或者Pod超出资源限制被驱逐,这个Pod就没了,K8s会创建一个新的Pod来代替它。新Pod的IP、名称都可能变。这也就引出了下一个概念——为什么你最好不要直接操作Pod。
2.2 Deployment:你要的不是"一个容器",而是"可声明的运行状态"
Deployment是K8s里管理无状态应用的核心对象。它的神奇之处在于"声明式":你只需要在YAML里写好"我要几个副本、用什么镜像、加什么环境变量",K8s内部的控制器就会不断比对"期望状态"和"实际状态",然后努力让实际状态向期望状态看齐。
举个例子。你在Deployment里写了replicas: 3,如果现在只有2个Pod在跑,控制器就会自动创建一个新Pod来补齐。如果有4个Pod,它会杀掉1个。如果你改了镜像版本,它会启动新版本的Pod,逐个替换旧的,这个过程叫滚动更新;如果更新过程中健康检查失败,它还会自动回滚。这一整套行为,你不需要写任何脚本,K8s替你做了。
所以你在实际工作中,几乎不会直接去创建Pod,而是创建Deployment,让Deployment去间接管理Pod。Deployment就像是一个"靠谱的包工头",你告诉它需要多少人干活,它自己想办法保证人数不少不多。这也是为什么我强调要先理解Deployment——它是你日常部署应用时最常用的对象。
2.3 Service:给随时会变化的Pod一个稳定的门牌号
前面说了Pod是短命的、IP会变。那问题来了:一个服务要调另一个服务,总不能每次都在代码里改对方IP吧?这就轮到Service出场了。Service提供了一个稳定的访问入口,它通过标签选择器(label selector)动态找到一组符合条件的Pod,然后把这些Pod的IP聚合在自己背后。
Service有很多类型,扫盲阶段你只需要分清楚三种:
- ClusterIP:集群内部访问用的默认类型,外部访问不到,只有集群内部可以访问。比如A服务要调用B服务,B的Service用ClusterIP就够了。
- NodePort:在每个节点上开一个随机端口,外部可以通过"节点IP+端口"访问到Service,再转发到后端Pod。适合本地调试、临时演示。
- LoadBalancer:配合云厂商的负载均衡器使用,外部流量直接打到负载均衡器,再由负载均衡器分发到节点的NodePort或Pod上。在生产环境最常见。
我见过很多入门者,一上来就在Deployment里配了端口,然后发现外部访问不了,急得不行。这里要记住一个关键点:Deployment里的containerPort只是声明"容器内监听的端口",它并不负责把端口暴露给外部。如果外部想访问,必须显式创建Service。这是新手最容易翻车的地方之一。
下面这张表可以帮你快速对照这三个核心概念:
| 对象 | 作用层次 | 管理内容 | 类比 |
|---|---|---|---|
| Pod | 运行单元 | 一组容器的生命周期 | 一个装着进程的豆荚 |
| Deployment | 声明管理 | 副本数量、滚动更新、回滚 | 包工头 |
| Service | 访问入口 | 稳定IP、负载均衡、服务发现 | 公司前台总机 |
3. 从零起第一个集群:本地环境与生产部署的差异
3.1 本地开发到底选Minikube还是Kind
聊完概念,你得动动手才能真正理解。但在动手之前,先解决一个选型问题:本地用什么工具搭K8s环境?
市面上的本地方案不少,我挑三个主要的给你说清楚:
| 工具 | 特点 | 适合场景 |
|---|---|---|
| Minikube | 最经典的本地单节点方案,功能完整,自带dashboard | 新手入门,想要最接近真实集群的手感 |
| Kind | 基于Docker,启动速度快,适合CI场景 | 自动化测试、快速起临时集群 |
| K3s | 轻量级发行版,资源占用小 | 低配置机器、边缘场景学习 |
如果你是第一次接触K8s,我建议直接选Minikube。理由很简单:它帮你屏蔽了最复杂的安装步骤,一条命令就能起一个完整的单节点集群,同时保留了K8s的绝大部分功能,你可以在这个环境里练习几乎所有入门操作。Kind也行,但Kind更像一个"临时玩具",有些功能上辈子跟真实集群有差异,不如Minikube稳定。K3s则适合你机器配置真的很差,或者想往边缘场景探索的时候再用。
3.2 用Minikube跑一个Nginx:完整操作记录
假设你已经装好了Minikube,下面我用一个完整的操作记录带你走一遍流程。第一步是启动集群:
minikube start --driver=docker它会根据你本机的Docker环境拉取一个迷你镜像,在Docker里启动一个K8s节点。如果这一步卡住了,大概率是镜像拉取网络问题,你需要检查Docker能不能正常拉取镜像,或者配置合适的镜像仓库地址(registry-mirrors),然后再重试。
等看到Done! kubectl is now configured这行输出,集群就起来了。你可以先看下节点状态:
kubectl get node正常情况下应该能看到一个节点,状态是Ready。然后我们来创建第一个Deployment:
kubectl create deployment nginx-demo --image=nginx这条命令会创建一个名为nginx-demo的Deployment,默认副本数1。我们可以再扩展一下:
kubectl scale deployment nginx-demo --replicas=3现在跑一下kubectl get pod,你会看到三个以nginx-demo开头的Pod在Running状态。这里我要提醒你注意一点:Pod名称后面那串随机字符,说明Pod是由Deployment动态创建的,不是你自己手动起的。
接着我们需要创建一个Service来暴露服务:
kubectl expose deployment nginx-demo --type=NodePort --port=80然后查看Service信息:
kubectl get svcService会显示一个80端口映射到某个NodePort(一般是30000以上的某个端口)。再用Minikube提供的命令打开浏览器访问:
minikube service nginx-demo这时候浏览器应该能打开nginx的默认欢迎页了。恭喜你,你已经完成了"部署一个应用并通过K8s访问"的全流程。这个过程中的每一步,每个对象之间的关系,你都能在上一节建立的心智模型里找到位置。
3.3 生产环境别急着自建集群:托管与自建的账
本地玩明白了之后,你可能会想:那我生产环境怎么搞?是自己用kubeadm搭一个集群,还是直接用云厂商的托管K8s?
直接给结论:99%的中小团队和个人项目,优先选托管K8s。
| 对比项 | 托管K8s | 自建集群(kubeadm) |
|---|---|---|
| 控制面(Master) | 云厂商负责高可用和升级 | 自己得维护多个Master节点 |
| 节点组 | 自动伸缩、自动替换故障节点 | 需要自己写脚本或人工介入 |
| 升级 | 一键升级,或配置自动升级 | 升级容易踩版本兼容坑 |
| 学习成本 | 低,控制台操作简单 | 高,需要懂etcd、证书、网络插件 |
| 成本 | 有管理费,但省人力 | 机器便宜,但运维成本高 |
我见过不少团队,为了"省钱"和"可控",用kubeadm自建集群,结果光是一个控制面高可用、证书过期更新、网络插件选型就折腾了好几周,最后算下来人力成本远超托管服务费。当然,如果你本身是做运维的,想深入理解K8s原理,自建一套是很好的学习路径。但同样一句话:学习和生产,目的不一样,选型就应该不一样。
4. 最容易让人懵的几个经典误区:从翻车现场反着学
4.1 误区:K8s装好了就万事大吉——默认能力很克制
经常有刚接触K8s的人以为,只要把集群搭起来,应用就自动高可用、自动扩容、自动容灾。这个认知要改一改。K8s的默认行为其实非常克制:它保证的是"维持你声明的状态",而不是"让你的应用随时可用"。
举个例子。你Deployment里写了replicas: 2,一个Pod所在节点挂了,控制器确实会在其他节点重新创建一个Pod。但如果你没有配置Pod Disruption Budget,K8s在做节点维护的时候可能把你的Pod全部拿掉,造成服务短暂不可用。再比如水平扩缩容,默认是没有的,你需要安装Metrics Server,然后创建HPA(HorizontalPodAutoscaler),指定CPU或内存阈值,才会自动扩缩容。
所以正确的认知是:K8s提供了自动化的"能力",但要不要用、怎么用,都要你自己通过配置去"激活"。K8s是工具箱,不是免维护的神器。
4.2 误区:看到Pod处于Running就以为一切正常
这个误区我见过太多次了。新手排查应用问题,kubectl get pod一看,状态是Running,就以为应用完全没问题,结果发现服务访问不了、日志狂报错。这里要搞清楚,Pod的Running状态只能说明"容器启动成功了",它不代表"容器里的应用工作正常"。
打个比方,你打开电脑,看到电源灯亮了,不代表系统一定没蓝屏。K8s提供了探针(Probe)机制来解决这个问题,最常见的是两种:存活探针(livenessProbe)和就绪探针(readinessProbe)。存活探针决定"容器要不要重启",如果探针失败,K8s会杀掉容器重新拉起;就绪探针决定"流量要不要打到这个Pod",如果失败,Service就会把流量转到其他Pod。
真正判断一个Pod是否健康,建议按这个顺序排查:
kubectl get pod # 看整体状态 kubectl describe pod <pod-name> # 看事件、启动信息、探针状态 kubectl logs -f <pod-name> # 看应用日志 kubectl exec -it <pod-name> -- /bin/sh # 进容器内部手动排查我印象很深的一次,是自己部署一个Java应用,明明Pod显示Running,但日志一直在报数据库连接失败。一开始以为是网络问题,来回检查Service和Pod的IP,最后才发现数据库密码配置的Secret读错了字段。这个教训就是:不要迷信状态,一定要进到日志和探针层面去验证。
4.3 误区:镜像拉不下来就是K8s的问题
写YAML部署应用,镜像拉取失败是最常见的报错之一。新手往往以为这是"K8s没配好",其实大部分跟K8s本身没关系,是镜像仓库或镜像地址的问题。
K8s在部署时会根据镜像拉取策略(imagePullPolicy)决定是否拉取镜像。默认情况下,如果镜像tag是latest,策略是Always,每次都拉;如果是具体版本号(比如nginx:1.27.2),策略是IfNotPresent,只有本地没有的时候才拉。
如果你在本地用Minikube,但不想每次都从远端仓库拉镜像,可以先在本地Docker里构建镜像,再执行minikube image load <镜像名>,把镜像导入到Minikube的环境里,然后用imagePullPolicy: IfNotPresent避免K8s强制去远端拉取。这套操作在离线环境或本地开发时特别实用。
另外,生产环境拉取失败了,记得用kubectl describe pod看事件详情,错误信息往往会明确告诉你"403""401""manifest unknown"这些具体原因,比瞎猜靠谱得多。
4.4 误区:YAML写错就完了,其实错误提示都在那
每次看到有人把YAML的报错截图发群里问"为什么起不来",我都建议他先好好看一眼报错内容里最关键的一行。绝大多数YAML问题,K8s都会给你明确的提示,比如:
- 缩进错误:会提示
error converting YAML to JSON: yaml: line X - 字段拼错:会提示
field spec.replicas not found in struct - apiVersion不支持:会提示
no matches for kind "Deployment" in version "v1"
我甚至见过有人因为YAML里的kind写成了小写的deployment,结果K8s压根不认识这个对象,报了no matches for kind。所以当你看到报错,第一反应应该是:把报错信息完整读一遍,读懂浏览器报错一样的逻辑——它在哪里、哪个字段、哪种格式,再去针对性修改。而不是把整个文件删了重写。
这里再分享一个实用的调试技巧:在kubectl apply -f之前,先用下面这条命令做一次客户端校验,能过滤掉一大部分语法错误:
kubectl apply -f deployment.yaml --dry-run=client -o yaml有些字段的问题是服务端校验才会发现的(比如某个字段的取值不合法),可以用--dry-run=server再做一次服务端校验。两步走下来,YAML问题基本能暴露大半,排查效率提升明显。
5. 从扫盲到上手:我的务实学习路线与踩坑记录
5.1 按这个顺序学,别一上来就啃源码
K8s扫盲之后,如果你决定真正深入,我强烈不建议直接去看源码或者啃那本官方的大部头文档。我更推荐下面这条务实路线:
- 先补齐容器基础:把Docker镜像、容器生命周期、docker run的常用参数搞明白,K8s本质是"管理容器的平台",不懂容器直接学编排,就像没学会开车就学漂移。
- 玩熟kubectl常用命令:get、describe、logs、exec、apply、delete,这六个命令能覆盖你日常90%的操作。
- 吃透核心三件套:Pod、Deployment、Service,把你上手的项目用这三件套部署一遍。
- 再往上一层:学ConfigMap和Secret管理配置,学Ingress管理HTTP路由,学PersistentVolume管理存储。
- 最后才看原理:这时候再回头啃架构文档,理解控制面有哪些组件、etcd存什么、kubelet怎么干活,就会有一种"原来之前那些现象都是这个原理的结果"的感觉。
这条路线最核心的思路是:用起来,再学原理。很多人学着学着就放弃了,是因为一直在背概念,但代码一行没跑过。反过来,当你真的把应用跑起来了,再遇到问题去查概念,记忆会牢固得多。
5.2 我踩过的几个坑,希望你别再踩
说实话,我也不是一开始就顺利,前面快被K8s劝退过。下面这几个坑都是我实际踩过的,写出来给你避雷。
第一个坑:不设置资源request和limit。刚开始我图省事,写Deployment的时候完全不写resources字段。结果某天某个服务内存泄漏,把节点内存打满,整个节点的Pod全部被驱逐。从那以后我再也不偷懒了,每个部署至少设置CPU和内存的request,有峰值风险再加limit。
第二个坑:忘记指定Namespace,然后"找不到对象"。用kubectl get pod查不到Pod,第一反应是部署失败,后来才发现自己查的是默认namespace,而应用部署在另一个namespace里。排查命令里加-n 命名空间,或者用-A参数跨命名空间查看,就不会再牢骚。
第三个坑:NodePort端口范围记不住。默认NodePort的取值范围是30000-32767,我一开始不知道,直接指定了NodePort: 8080,结果apply直接报错,提示端口超出范围。后来才明白NodePort属于集群层面的保留端口范围,不是随便写的。
第四个坑:删namespace把相关性对象全删了。有次想清理环境,直接kubectl delete namespace demo,结果namespace下的所有Deployment、Pod、ConfigMap、Service全没了。这其实也说明了Namespace的隔离边界是什么能力:它不只是给你分类用的,它还是对象生命周期的一个边界。
第五个坑:误用字段更新导致滚动更新失败。使用kubectl edit deployment xxx改了镜像版本,但忘了同步修改某个环境变量,结果新Pod起来后应用配置跟旧版本不匹配,一直触发就绪探针失败、流量不切换。这个教训让我学会了一个习惯:任何部署变更,都先在测试环境用相同的YAML文件走一遍,不要在生产直接编辑。
5.3 最后聊聊我的个人体会
学K8s这件事,最大的障碍不是技术难度,而是"信息过载带来的挫败感"。我现在回头看,如果当初能先建立"调度、声明式状态、服务发现"这三个心智模型,再动手跑实验,至少能少走一半弯路。你现在看到这篇文章,就等于已经有人帮你划了重点、标了雷区,剩下的就是自己动手。用Minikube起一个集群,部署一个Nginx,再试试改一下副本数,观察Pod的重新调度过程——这个过程中获得的体感,比看十遍教程都值。等你亲手跑通这一遍,再回来看K8s的文档,你会发现那些名词都"活"了起来,不再是浮在纸面上的概念了。