1. 面试官问“高级特性”时,到底在考什么——先摸清出题逻辑
我在做技术面试官的时候发现一个现象:大部分候选人简历上都写着“精通Kubernetes”,但一问到Pod调度策略、驱逐机制、控制器工作原理这类问题时,回答往往停留在“用过”“见过”的层面。Kubernetes高级特性的面试题,表面考的是知识点,实际上考的是两件事:第一,你有没有在真实环境里踩过坑;第二,你能不能把控制面组件之间的协作关系讲清楚。
先说一个最容易被忽略的事实:Kubernetes本身不是一个容器运行时,它是一个容器编排平台。你在面试里说的每一个“高级特性”,本质上都是在回答一个问题——你怎么让一组容器在一个几百台机器的集群里按照你期望的方式运行、通信、扩容、自愈。所以我不建议死记硬背面试题答案,而是先建立一个坐标系:调度、弹性、高可用、网络、存储、安全,这六个维度基本覆盖了K8s高级特性的全部考点。
这篇内容我会结合“部署nginx”这个最经典的实战场景,把散落在各个知识点里的逻辑串起来,顺便把我这些年整理面试题时沉淀下来的一些出题思路和答题技巧一并放进去。无论你是准备跳槽的候选人,还是要组织团队面试的Leader,应该都能从这里捞到点东西。
另外一个建议:不要指望一口气把下面所有东西都记住。Kubernetes的面试题有一个特点,题目和题目之间有很强的关联性。比如面试官问你“Pod是怎么被调度到节点上的”,如果你能把调度器、kubelet、资源请求、亲和性、污点这几个概念串成一个完整的链路,这一题就能带出七八个高分点。这也是我下面这套拆解方式的出发点。
2. 调度与资源管理:Pod从创建到落地的完整决策链
2.1 调度器到底在干嘛:从Pending到Running中间发生了什么
很多人一上来就被“高级特性”四个字带偏,觉得K8s面试肯定要考什么Operator、Webhook、自定义控制器。但实际上,调度这一块才是区分初中级和高级的分水岭。你去面试的时候,面试官问你“Pod创建之后,调度器做了什么”,如果你只能回答“选个合适的节点”,那基本就告别高级岗了。
一个Pod从创建到运行,链路大概是这样的:你提交的YAML经过API Server的准入控制(Admission Control)之后被写入etcd,接着调度器通过List-Watch机制感知到这个新的Pod对象,然后开始为它寻找合适的节点。注意,这里的“合适”不是随便选的,而是一个两阶段的过程:先过滤(Filtering),再打分(Scoring)。
过滤阶段,调度器会把不满足条件的节点全部排除。比如节点资源不够、端口冲突、不满足节点亲和性或污点容忍等,这些都是硬约束。打分阶段则是在剩余节点里按一系列优先级策略算分,比如资源余量、镜像是否已存在、节点亲和性匹配度等,最后选出得分最高的节点。这也是为什么有时候你的Pod会被调度到一台看起来负载很高的机器上——因为综合评分最高。
这里我要补充一个面试里特别爱考的细节:调度器只是决定Pod放到哪个节点,真正把Pod跑起来的是kubelet。调度器把Pod的NodeName字段写进去,然后kubelet通过List-Watch发现这个Pod被分配给自己了,才开始拉镜像、创建容器。很多候选人会把调度器说成“把Pod放到机器上的人”,这个概念是不准确的——调度器只写了一个binding,剩下的事全归kubelet管。
2.2 资源请求与限制:requests和limits的博弈
关于资源,面试题万年不变的问题就是“requests和limits有什么区别,如果不设置会怎样”。这个问题看似基础,但它牵出的陷阱特别多。
requests是调度依据,limits是运行限制。调度器只看requests,不看limits——这一点几乎是必考的点。你设置Pod的limits很大、requests很小,调度器依然会按requests去匹配节点资源,也就是说这个Pod有潜力爆掉节点的实际负载,却因为requests很低而被塞到一台资源紧张的机器上,这就是Node压力问题的源头之一。
还有一个高频场景:状态工作负载(Deployment、StatefulSet)允许设置limits不设置requests,但这样会产生一个后果——Pod的QoS等级会被标记为Burstable,而且如果节点资源耗尽,这类Pod优先被驱逐。所以生产环境里我一般建议requests和limits都显式声明,而且最好不要拍脑袋填,而是结合应用的真实内存画像来定。你可以先用不加limits的方式跑一两周,配合监控看容器实际用量,再回填合理的requests和limits。这就是Kubernetes官方一直提倡的“基于实际使用量配置资源”的思路。
QoS等级这块,面试官如果追问,你要能说清楚三类:Guaranteed(每个容器都设置了相等的requests和limits)、Burstable(至少有一个容器设置了requests或limits,但不满足Guaranteed条件)、BestEffort(完全不设置任何requests和limits)。它们的区别不只是名字,驱逐优先级从低到高是Guaranteed、Burstable、BestEffort。这也就解释了为什么有的人会碰到一种诡异现象:明明自己的Pod没出问题,却被系统杀掉了——大概率是节点内存紧张时BestEffort和Burstable先被优先驱逐。
2.3 亲和性、污点与容忍:把Pod“钉”在合适的节点上
高级特性面试题里,亲和性和污点这一组概念出现的频率非常高,而且经常放在一起考。我给一个比较容易理解的说法:亲和性是Pod主动去挑节点,污点是节点主动拒绝Pod,容忍是Pod戴了一个头盔去扛住拒绝。
节点亲和性(nodeAffinity)有两种,requiredDuringScheduling和preferredDuringScheduling。前者是硬性条件,调度器找不到符合的节点就不调度;后者是软性偏好,加分项,找不到也无所谓。Pod间亲和性(podAffinity)和反亲和性(podAntiAffinity)则用来表达“我想和谁待在一起”或者“我不想和谁待在一起”,典型场景是保证同一个应用的多个副本尽量分散到不同节点,避免单节点故障带走所有副本。
污点(Taint)和容忍(Toleration)这边,大家最熟悉的就是kubectl taint nodes node1 key=value:NoSchedule。这里面有一个很容易踩的坑:添加了污点之后,已经运行在该节点上的Pod不会被立刻驱逐,只有NoExecute类型的污点才会对已存在的Pod生效。很多初学者以为打上NoSchedule污点就能清空节点,结果节点上的Pod还在优雅运行,然后他们就在面试现场翻车了。
另外要注意,master节点初始化之后自带一个node-role.kubernetes.io/master:NoSchedule的污点,所以普通Pod不会调度到master上。你如果在一个单节点集群上创建Pod发现一直在Pending,第一个排查点就是这个。
2.4 横向扩缩容:HPA其实比你想的更简单也更复杂
弹性伸缩是K8s最吸引人的能力之一,也是面试题里几乎必考的一类。HPA(HorizontalPodAutoscaler)的原理并不难:Metrics Server持续采集Pod的CPU、内存等指标,HPA Controller按照你设定的目标值计算期望副本数,然后调整Deployment或StatefulSet的副本数。
计算公式是:期望副本数 = ceil(当前副本数 x 当前指标值 / 目标指标值)。举个例子,你设置目标CPU使用率是50%,当前有4个副本,实际使用率是100%,那期望副本数就是4x100%/50%=8。
但面试题往往不会只考这个公式,而是会追问两个进阶点。第一个是扩容和缩容的速率控制:HPA默认的扩容是允许尽快扩容的(容忍3-5分钟的指标延迟),但缩容有一个冷却时间(默认5分钟),防止指标抖动导致副本数反复横跳。第二个是自定义指标:CPU和内存指标只能反映资源压力,如果你的应用是消息队列消费者,更合理的扩缩容依据是队列长度。这种场景需要配合Prometheus Adapter或KEDA来实现自定义指标HPA。
说实话,HPA在生产里的坑比原理复杂得多。最典型的就是Java应用在启动初期CPU飙升,HPA误判为负载高而触发扩容,还没来得及复用又缩掉了。解决思路有两个:一是给HPA配上 stabilization窗口,二是用带缓冲的指标(比如队列深度)代替瞬时CPU。面试的时候如果能聊到这一层,面试官基本会认为你是有线上经验的人。
3. 高可用与自愈:Pod明明挂了,系统是怎么“装作无事发生”的
3.1 探针的三个层级:存活、就绪、启动
Kubernetes的自愈能力全指望探针(Probe),这也是面试里的高频考点。探针有三个:livenessProbe(存活探针)、readinessProbe(就绪探针)、startupProbe(启动探针)。很多人只记得前两个,但startupProbe是K8s 1.16引入的,专门解决“启动慢的应用被liveness误杀”的问题。
三者的区别要能用一句话讲清楚:liveness决定“要不要杀掉重启”,readiness决定“要不要放流量进来”,startup决定“要不要开始其他探针计时”。
我最想提醒的是liveness和readiness的误用问题。有些同学图省事,把readinessProbe和livenessProbe配成一样的探针,结果应用一出现瞬时过载,readiness失败的同时liveness也失败,Pod直接被杀掉重启,而不是暂时摘除流量。正确做法是:liveness的阈值要比readiness宽松得多——比如readiness可以用3秒间隔、失败3次就摘流量,而liveness可以放到15秒间隔、失败5次才重启。这样应用在抖动的时候只是暂时不接流量,而不是动不动被重启。
还有一个踩坑点:探针执行的命令是写在容器里的,如果容器里没有curl、wget这些工具,你用HTTP探针或TCP探针完全没问题,但用exec探针执行shell命令就会失败。这是一个在面试里可以反客为主的细节——你主动提出“我的镜像里没有bash,所以我用HTTP探针”,面试官会立刻对你的实战经验有印象。
3.2 控制器模式:Deployment、ReplicaSet、StatefulSet的分工逻辑
高可用这个话题绕不开控制器。面试官问你“Deployment怎么实现滚动更新的”,你要是只回答“先起新Pod、再删旧Pod”,那是不够的。准确的回答是:Deployment创建了一个ReplicaSet,ReplicaSet负责维持期望的副本数,滚动更新时Deployment会创建新的ReplicaSet并逐步扩容,同时缩容旧的ReplicaSet。
这里有一个特别经典的追问:**滚动更新过程中Pod版本不一致,流量怎么处理?**答案是:这取决于readinessProbe的配合。新Pod的readiness探针通过后才会被加入Service的Endpoints,否则就算新Pod已经Running,流量还是打到旧Pod上。也就是说,滚动更新真正决定一个副本是否“可用”的核心是ready状态,而不是Running状态。
StatefulSet和Deployment的区别也是必考项。StatefulSet给每个Pod一个稳定的网络标识((如pod-0、pod-1))、稳定的存储(每个Pod绑定独立的PVC),并且Pod的启停顺序是严格有序的。面试里常考的“为什么StatefulSet是顺序部署的”,答案是它要保证分布式应用(如etcd、ZooKeeper)在启动时能按角色顺序加入集群,避免脑裂或写入冲突。
3.3 PodDisruptionBudget与节点维护:优雅驱逐的最后一道保险
高级一点的面试题会问:我要对节点做重启维护,怎么保证存量Pod不出现长时间不可用?这个问题考的就是PDB(PodDisruptionBudget)。
PDB的典型写法是给某个应用设置minAvailable: 2,意思是自愿驱逐(voluntary disruption)过程中至少保持2个副本可用。注意,PDB只对自愿驱逐生效——比如你执行kubectl drain、节点污点变更、滚动更新这类操作。节点宕机、Pod崩溃这种非自愿情况,PDB是管不着的。所以“PDB保证了我的服务永远不挂”这种说法是一个典型的理解误区。
实操层面,kubectl drain之前最好先确认PDB已配置,否则当节点只有一份副本且没设置PDB时,drain会一直阻塞(这是好事),或者把Pod强制驱逐掉(如果加了--force,这是坏事)。我自己踩过一次坑:在某环境里没设置PDB,直接drain了一台承载单副本关键服务的节点,结果业务中断了十几秒。从那之后,我养成了一个习惯:任何有状态的或者单副本的应用,必须配置PDB再考虑节点维护。
3.4 多副本部署就够了吗:Pod反亲和性的真实价值
很多面试题会绕一大圈问“怎么保证高可用”。最常见的老实答案是“部署多个副本”。这个答案本身没问题,但高级的回答必须带上反亲和性。
如果多个副本没有任何调度约束,是有可能挤在同一台物理机上的。一旦那台机器宕了,你的多副本策略形同虚设。解决办法就是podAntiAffinity:让同一个应用的不同副本尽量不落在同一个节点。但这里有个性能代价要说清楚:反亲和性会显著降低调度成功率,尤其是在小集群或节点数量有限的场景里。而且如果你用的是requiredDuringScheduling级别的反亲和性,节点不够时Pod会一直Pending。
所以生产里我通常用preferredDuringScheduling级别的软反亲和性,配合节点数量的监控,既能做基本的高可用分散,又不会把调度逼进死胡同。面试的时候如果能主动区分硬反亲和和软反亲和各自的适用场景,十分加分。
4. Service与Ingress:从“部署nginx”到“访问nginx”的完整链路
4.1 一道看似简单的部署题:nginx在你的集群里怎么跑起来
“Kubernetes 部署nginx”这个热搜词背后对应的其实是一道复合题:一个nginx应用从无到有、从内到外被访问,涉及到的K8s知识点覆盖了Workload、Service、Ingress、ConfigMap、存储等多个层面。
先按最简单的方式走一遍:创建一个Deployment运行nginx镜像,副本数设为3,然后创建一个ClusterIP类型的Service暴露80端口。此时集群内部可以通过服务名:端口访问nginx。但如果要在集群外部访问,就需要NodePort或者Ingress。
这里有一个面试高频陷阱:为什么ClusterIP的Service在集群外访问不了?因为ClusterIP是一个虚拟IP,只在集群内部的网络命名空间里有效。面试官真正想听到的是“Service的底层实现是iptables/IPVS规则”,如果你能进一步说明NodePort就是在每个节点上开了一个端口,把流量转发到ClusterIP,再转发到Pod里的容器端口,那基本就是满分答案。
4.2 Service的类型选择:ClusterIP、NodePort、LoadBalancer怎么选
Service类型的选择是面试常见题,也直接关系到nginx部署的实战效果:
| 类型 | 访问方式 | 适用场景 | 缺点 |
|---|---|---|---|
| ClusterIP | 集群内部虚拟IP | 内部服务间调用 | 外部不可直接访问 |
| NodePort | 每个节点的IP+端口 | 临时对外暴露 | 端口范围有限,多服务容易混乱 |
| LoadBalancer | 云厂商负载均衡器 | 云环境对外暴露 | 每个Service一个LB,成本高 |
| ExternalName | DNS别名 | 访问集群外部服务 | 只做DNS映射,无代理转发 |
实战里我推荐的做法是:nginx作为边缘入口,不直接用NodePort暴露,而是先创建一个ClusterIP的Service,再用Ingress把域名路由到它。这样做的原因是:NodePort直接暴露会占用节点端口,而且没有TLS终止、路径路由这些能力。Ingress才是生产环境下的标准入口方案。
还有一个常被忽略的点:LoadBalancer类型的Service在裸金属集群(bare metal)里其实是无法自动创建云负载均衡器的,需要用MetalLB这类方案来补充实现。如果你在IDC机房面试,提到这一点会非常亮眼。
4.3 Ingress Controller和Ingress资源:别再傻傻分不清
几乎所有K8s学习者都经历过一个困惑:我创建了Ingress,为什么访问不了域名?原因非常简单——Ingress只是一份配置规则,真正干活的是Ingress Controller。集群默认不会安装Ingress Controller,你需要自己部署一个,比如nginx-ingress-controller或traefik。
这个区分是面试题的大热门。Ingress资源本身只是一系列规则:某个域名的某个路径,转发到哪个Service的哪个端口。Ingress Controller则是一个常驻Pod,它不断读取这些规则并把它们翻译成Nginx(或Traefik等)的配置,然后热加载。流量链路是:外部请求 → Ingress Controller(作为反向代理入口)→ Service → Pod。
我在这里强烈建议在练习环境里至少亲手部署一次ingress-nginx。等你看到那个ingress-nginx-controller的Pod创建出来、ConfigMap里自动生成的配置、以及访问日志里的转发记录时,“Ingress资源”和“Ingress Controller”就再也不会搞混了。
4.4 部署nginx的进阶姿势:ConfigMap注入与优雅停机
如果你只是把nginx用Deployment拉起、Service暴露,这个练习只能算入门。想在部署nginx这个场景里体现你对高级特性的掌握,至少还要加上三件事。
第一,用ConfigMap管理nginx.conf。nginx的配置天然适合文件注入,你可以把Nginx的日志格式、gzip开关、代理参数写进ConfigMap里挂载到容器,而不是把配置打进镜像。这样做的好处是:修改配置后只需要重启或reload,不需要重新构建镜像。
第二,处理好优雅停机。K8s删除Pod时会给容器发送SIGTERM信号,但是nginx默认的master进程收到SIGTERM后会直接退出,不等待当前连接处理完。你需要给nginx配置nginx -g 'daemon off;'配合stop_signal: SIGQUIT(nginx的优雅退出信号),或者在preStop钩子脚本里执行nginx -s quit。这个细节是我当年被折腾了一个晚上的地方,也是面试时能讲出“生命周期钩子”加分项的绝佳素材。
第三,加一份readinessProbe。nginx的readiness探针建议用HTTP请求/healthz,并且你需要在nginx配置里专门暴露这个location。探针的意义在于:流量只打到健康副本上,滚动更新时新旧副本平滑切换。否则你在滚动更新时可能会遇到短暂的502。
4.5 面试必问题:Pod访问Service的域名是怎么解析的
如果面试官问“Service的DNS名称怎么用”,你需要引出CoreDNS。Kubernetes集群里每个Service都会被COREDNS注册一个A记录,格式是<service-name>.<namespace>.svc.cluster.local。同一命名空间下可以直接用Service名访问,跨命名空间就要带命名空间名前缀。
这个知识点本身不难,但延伸出来的排查题特别多。比如:在Pod里ping Service的ClusterIP不通,是不是Service有问题?这种题要看情况。ClusterIP是iptables/IPVS转发规则,不是实际存在的网卡,ping很可能被DROP掉,但TCP请求是正常的。因此排查Service问题,你应该用curl或者nc,而不是ping。
还有一道衍生题:Pod的/etc/resolv.conf里的search domain是怎么影响DNS解析的。这个文件里默认带了一串search列表,包含.namespace.svc.cluster.local、.svc.cluster.local、.cluster.local等,所以你在Pod里访问myservice时,系统会先试myservice.default.svc.cluster.local,找到就直接返回结果。理解了这一点,你就明白为什么有些人把Service当成域名写在配置里也能通——不是巧合,是DNS search机制在起作用。
5. 存储与持久化:PV/PVC/StorageClass在面试中的正确打开方式
5.1 从emptyDir到PV/PVC:为什么要绕这么大一圈
nginx本身是无状态的,但你部署一个nginx无非是两种情况:要么前面挂了缓存目录、网页静态文件,要么后面接业务容器。不管哪种,一旦涉及数据持久化,存储的体系就绕不开了。
K8s存储的抽象层次容易把人绕晕:PV(PersistentVolume)是集群里的存储资源,PVC(PersistentVolumeClaim)是使用方发起的资源请求,StorageClass负责动态供给存储。用生活化的类比来解释:PV是仓库里已有的货架,PVC是你要租一个货架的申请单,StorageClass则是接到申请单后现做货架的供货商。
面试最常见的题是“静态PV和动态PVC有什么区别”。静态供给是管理员预先创建好一批PV,PVC按条件去匹配;动态供给是PVC创建时直接指定storageClassName,由StorageClass的后端插件(比如NFS Provisioner、云盘插件)自动创建PV。生产环境几乎都会用动态供给,因为你不可能预知每个应用需要多大空间。
5.2 PVC的容量与访问模式:你以为申请了就一定有空间吗
关于PVC,面试里有两个坑值得说出来。第一个是容量扩容:早期PVC一旦创建,容量就不能改。后来引入了allowVolumeExpansion,但前提是StorageClass支持、存储后端支持在线扩容。而且云盘扩容的步骤往往是“改PVC,继续fdisk、resize2fs”等多步操作,并不是K8s改一个字段就好。第二个是回收策略:PV被释放后,是保留(Retain)、回收(Recycle,已废弃)还是删除(Delete)。如果选择Retain,PVC删除后数据还在但PV状态变成Released,这时候你需要手动清理并重置PV的状态才能复用。
访问模式也是一个高频考点。ReadWriteOnce(RWO)基本对应块存储,只能被一个节点挂载;ReadOnlyMany(ROX)和ReadWriteMany(RWX)需要共享文件系统,比如NFS或CephFS。你有没有想过这样一个场景:Deployment有3个副本挂同一个PVC,结果有一个副本Pending了。原因大概率就是这个PVC是RWO的,只能在一个节点上挂载,而三个副本里的两个被调度到了别的节点。这种题在面试里出现过的概率极高,因为它是真实生产环境里确实会发生的问题。
5.3 一个实战案例:给nginx挂载持久化静态页面
话题回到nginx。如果要把nginx的静态页面目录挂到持久卷上,推荐的做法是:部署一个NFS服务(或者用云厂商的NAS),然后创建StorageClass指向NFS Provisioner。之后写一个PVC,再把Deployment里的nginx容器通过volumeMounts挂载这个PVC。
这是整个链路里最容易卡壳的地方——PVC和容器目录的挂载关系一定要理清楚。PVC是一个“盘”,volumeMounts只是把这个盘挂到容器里的某个路径。如果nfs服务器上对应的目录是空的,那nginx容器里的/usr/share/nginx/html目录就会显示空内容(覆盖掉镜像里的默认页面),而不是镜像里的index.html。很多人挂载后页面404就是被这个“空目录覆盖”坑了。
正确做法是先用一个initContainer把页面文件拷贝到挂载目录里,再把nginx容器正常启动。或者在NFS服务器端预先放入初始文件。这个细节在面试中讲出来,能证明你确实实操过而不是只看过文档。
5.4 有状态应用的存储:StatefulSet为什么需要VolumeClaimTemplate
如果你面试的是偏后台或中间件方向的岗位,StatefulSet绑定存储这题基本逃不掉。StatefulSet里的VolumeClaimTemplate相当于一个PVC模板,每次创建Pod副本时,系统自动为它生成一个独立的PVC。这意味着:pod-0绑定的数据卷和pod-1绑定的数据卷物理上是两块独立的盘,即使Pod被删掉重建,新Pod还是会绑定原来那个PVC,数据不丢。
这个机制让状(StatefulSet)天然适合跑数据库、消息队列这类有状态服务。面试官如果问你“StatefulSet和Deployment最根本的区别是什么”,除了启动顺序、网络标识之外,你还要提这一点:Deployment的Pod共享同一个PVC是可以的(虽然受限于访问模式),但一般不会为副本生成独立存储模板;StatefulSet则是用VolumeClaimTemplate来保证每个副本有独立且稳定的存储,这是两者在设计哲学上的根本差异。
6. 安全与多租户:RBAC、Namespace、NetworkPolicy是送分题还是送命题
6.1 RBAC:为什么大部分集群的安全事故都出在权限上
Kubernetes的安全体系分散在好几个层,但面试题里最高频的是RBAC。RBAC(Role-Based Access Control)的核心对象有四个:Role、ClusterRole、RoleBinding、ClusterRoleBinding。面试常考的一个点是Role和ClusterRole的区别——Role作用范围是某个Namespace,ClusterRole作用于整个集群。
但面试题目真正的杀招是组合拳:能不能跨Namespace授权?答案是RoleBinding只能绑定同Namespace下的Role,如果你想在多个Namespace复用一组权限,只能创建ClusterRole,然后用RoleBinding在不同Namespace分别绑定它。那ClusterRoleBinding和RoleBinding绑定ClusterRole有什么区别?前者让权限真正变成集群级,后者让权限只作用于某个Namespace。
实战里我最想补充的一点是:不要为了省事把cluster-admin直接绑给应用服务账号。我见过太多因为调试方便用cluster-admin跑业务的例子,出了问题之后根本没有审计追责的依据。正确做法是创建一个最小权限的ServiceAccount,只授权它需要调用的资源操作,比如读取Pod列表、查看日志、创建Job。然后通过kubectl auth can-i --list --as=system:serviceaccount:default:xxx来验证权限是否符合预期。这个小工具在面试时提到,会让面试官觉得你对待权限是认真的。
6.2 NetworkPolicy:默认不隔离,才是最大的安全漏洞
Kubernetes的网络隔离是一个很容易被忽略的高级特性。默认情况下,集群的网络是“全通”的——任何一个Pod都能访问其他Pod和所有Service。除非你显式创建NetworkPolicy,否则你的nginx可以被任何命名空间的Pod访问。
NetworkPolicy的难点在于它的“白名单”逻辑:一旦创建了NetworkPolicy,它就只允许符合规则的流量,其余全部拒绝。这个“一旦创建就默认拒绝”的机制是新手最容易踩的坑。如果你给nginx加了一条只允许来自ingress-nginx命名空间流量的策略,那么同命名空间里其他应用直接访问nginx——比如执行kubectl exec进到别的Pod里curl一下——也会被拒绝,因为它的源地址不在允许列表里。
生产环境的NetworkPolicy要特别小心两端都要考虑:入口(ingress)和出口(egress)。如果要限制nginx只能访问数据库Service,出口规则也得同时写上。很多人在面试里只会说入口规则,很少提出口规则,所以我每次听到有人能把egress也纳入讲述,都会在心里默默加分。另外要记住,NetworkPolicy要真正起作用,依赖底层CNI插件的支持。Calico、Cilium这些都支持,但是Flannel不支持NetworkPolicy。你如果在用了Flannel的集群里创建NetworkPolicy,会发现根本没有效果——这个是架构选型层面的坑。
6.3 多租户隔离:Namespace的成本比你想的高
多个团队共用集群时,最基础的隔离单位是Namespace。ResourceQuota和LimitRange这两个资源就是为多租户准备的。ResourceQuota限制整个Namespace的总体资源上限,比如CPU、内存、PVC数量、Service数量。LimitRange限制单个Pod或容器能在Namespace里申请的资源范围,比如强制每个容器必须设置requests。
这两个资源的目标很明确:防止某个团队把一个共享集群的资源吃干榨净。但坑在于,LimitRange设置“默认requests/limits”的行为只对新创建的Pod生效,不回去改老Pod。如果你新增了一个LimitRange,集群里已有的Pod依然保持裸奔状态,除非重建。这算一个小知识点,但面试题往往就是通过这类细节来区分“文档党”和“实战党”。
6.4 镜像安全与准入控制:你的nginx镜像来自哪里
关于容器镜像安全,面试的切入点通常是“你怎么保证线上跑的镜像是可信的”。常见答案有:使用私有镜像仓库、镜像签名、扫描漏洞、避免latest标签。
走到高级层面,面试官会追问“准入控制(Admission Control)”。K8s的Pod创建请求会经过多个准入控制插件的检查,比如NamespaceLifecycle、LimitRanger、ResourceQuota、PodSecurity等等。有的插件是内置默认开启的,有的则需要自己部署。比如如果你想强制所有Pod不能以root用户运行,可以借助Pod Security Standards,或者部署OPA/Gatekeeper这类策略引擎,在请求还投递到etcd之前就把不合规的Pod挡在外面。
这里有一个思维转变值得在面试中体现:K8s的安全不是某一个组件负责的,而是一整条链条——镜像仓库(供应链)→ 准入控制器(策略拦截)→ RBAC(权限管控)→ NetworkPolicy(网络隔离)→ Pod Security(运行时约束)。如果你能按这个维度组织回答,基本就能把安全相关的面试题串成一个体系。
7. 一套nginx实战把所有高级特性串起来
7.1 从零开始的高可用nginx:部署、调度、自愈、访问
现在我们把所有知识点收拢到一个具体的实操路径里,按下面这几步在集群里部署一个生产级可用的nginx。假设你已经有一个K8s集群(单节点也可以,但最好有两个以上节点)。
第一步,创建命名空间:kubectl create namespace web。生产环境里我始终坚持用命名空间把环境或应用区分开,不要什么都怼到default里。
第二步,配置资源清单。Deployment中副本设3个,镜像使用固定版本比如nginx:1.25.3而不是latest。给每个容器设置requests为100m CPU加128Mi内存,limits为500m CPU加256Mi内存,并且加上livenessProbe和readinessProbe。这里用HTTP探针,路径指向nginx自带的/healthz(需要提前在ConfigMap里配置)。调度方面用preferredDuringScheduling的podAntiAffinity,让3个副本尽量分散到不同节点。
第三步,创建Service(ClusterIP类型)暴露80端口,再创建Ingress规则把nginx.example.local路由到刚才的Service。
这套配置跑起来之后,你会在kubectl get pods -o wide里看到3个副本分布在尽量不同的节点上,kubectl get ingress能看到规则正常生成,浏览器或curl也能通过域名访问到nginx页面。这个流程看上去简单,但里面包含的Requests/Limits、探针、反亲和性、Service、Ingress,正好覆盖了前面讲的大半知识点。
7.2 模拟故障:删掉一个Pod,看K8s怎么“自我救赎”
高可用特性的验证不能停留在“配置写了就完事”。我建议你亲自做一次故障演练:kubectl delete pod <nginx-pod-name>——这里如果是Deployment管理的Pod,控制器会立刻重新创建一个新Pod,这正是ReplicaSet维持期望副本数的作用。
再进阶一步:kubectl scale deployment nginx --replicas=5,观察副本数变化;然后kubectl autoscale deployment nginx --cpu-percent=50 --min=3 --max=10,给nginx配置HPA,然后通过压测工具(比如ab或hey)打一波流量,再看HPA是否自动扩容。这个过程特别直观:你先执行kubectl top pods确认Metrics Server正常工作,然后观察kubectl get hpa -w里副本数在压力下被拉上去。看到它真的扩容那一刻,你对HPA的理解会比背十道面试题都有用。
如果你再把节点维护也演练一遍,就会触及PDB。没有PDB的Deployment其实还好——因为3副本分布在多个节点,drain一个节点后控制器会把Pod补到其他节点;但如果你drain的是单副本Pod所在节点,又没有PDB,那么这个Pod会被强制删除,如果控制器的容忍度高,通常也会重建。真正崩溃的场景是没有控制器管理的裸Pod(比如用kubectl run直接创建),drain之后它不会自动重建,服务就真的断了。所以裸Pod在生产里是大忌。
7.3 滚动更新和回滚的不传之秘
nginx部署完,最终绕不开“发版本”。把镜像从nginx:1.25.3改成nginx:1.26.0,执行kubectl set image deployment/nginx nginx=nginx:1.26.0,或者直接改YAML后kubectl apply,Deployment会触发滚动更新。
那“滚动更新失败了”怎么回滚,面试常考。kubectl rollout undo deployment/nginx会回到上一个版本。但如果已经连续发布过多个版本,kubectl rollout history deployment/nginx可以查看历次变更记录,然后--to-revision指定回滚到某个版本。
这里有一个细节很多人没注意到:Deployment默认的spec.revisionHistoryLimit是10,意思是只保留10个历史版本,超出的会被清理。这解释了为什么有时候执行rollout history发现只有最近几条——不是K8s丢了,而是历史被清理了。这也意味着,如果你需要长期保留更久的发布记录,需要显式提高revisionHistoryLimit。
7.4 这组实战里隐藏的面试加分项
我想给你列一下上面这套nginx实战里,每一个步骤对应的可以“主动拿分”的知识点:
- 写Deployment时设置requests/limits → 延伸出QoS等级和驱逐优先级。
- 配置探针时区分liveness和readiness → 延伸出滚动更新时流量切换的细节。
- 加podAntiAffinity → 延伸出多副本高可用的调度约束。
- 创建Service+Ingress → 延伸出Service底层实现(iptables/IPVS)、Ingress Controller原理。
- 配HPA → 延伸出扩缩容公式、冷却窗口、自定义指标。
- 执行rollout undo → 延伸出ReplicaSet版本控制、revisionHistoryLimit。
- ConfigMap管理nginx配置 → 延伸出配置热加载和优雅重载。
每条链路都能再多延展出一层,这就是面试里所谓“举一反三”的来源。掌握知识点只是第一步,把知识点串成一条“因为……所以……”的链条,才是高级工程师和初级工程师的分界。
8. 面试答题的心智模型与常见翻车现场
8.1 不要背答案,要背“推导链”
我筛选候选人这些年,最失望的不是答不上来的,而是把面试当背书现场。问“HPA怎么工作”,他能背出公式;问“HPA扩出来的实例不够怎么办”,他就开始沉默了。原因很简单:他背了结论,但没有建立起推导逻辑。
一个高效的学习方法是把每个知识点变成“我怎么解决这个问题的”。不要问“K8s的Service有哪些类型”,要问“如果我的服务想被外部访问,有哪些方案,各有什么代价”。前者是背书,后者是工程决策。面试官想听到的永远是你做出决策的过程:为什么选Ingress而不是NodePort,为什么给这个Pod配3个副本而不是5个,为什么用StatefulSet而不是Deployment。这些“为什么”背后都有推导链,你平时多问自己几次,面试时就能条件反射。
8.2 几个高频翻车现场:这些坑我真的见过
我总结了几个现实中高频出现的错误答案,分享给你作为“避雷清单”。
第一个:把“Pod崩溃了自动重启”归功于K8s自愈。实际上,Pod崩溃后是否重启由restartPolicy和Pod的管理控制器决定。如果你是裸Pod且restartPolicy=Never,它不会自愈。真正自愈的是Deployment/StatefulSet这套控制器体系。回答时千万不要说“K8s检测到Pod挂掉就重新拉起”,正确的说法是“控制器的Reconcile循环不断对比期望状态和实际状态,发现不一致就修正”。
第二个:“Service的ClusterIP是DNS解析出来的”。这是概念混淆。Service提供了一个虚拟IP,Pod访问Service时,请求先经过DNS解析获得ClusterIP,然后流量被iptables/IPVS规则转发到后端的Pod IP。这里面DNS做的只是把Service名解析成ClusterIP,并没有帮助负载均衡。负载均衡完全交给iptables/IPVS规则去做。
第三个:把探针认为是“监控工具”。探针不是监控工具,它们的任务是配合K8s生命周期管理决定容器是否健康。监控是Prometheus那套的职责。两者层次不同,如果你在面试里把CloudWatch/Prometheus和探针混为一谈,会立刻暴露基本概念的漏洞。
8.3 从面试题到真实排障:怎么证明你“真的用过”
面试最后,面试官往往会让你讲一个线上排障案例。这几乎是决定胜败的环节。我建议你准备两个真实经历,但关键不是讲你怎么看日志、怎么分析监控,而是讲清楚你在解决问题的过程中和哪些K8s概念发生了交互。
比如你以前处理过一个“Pod长时间Pending”的线上问题:第一反应是kubectl describe pod看Events,发现是调度失败;继续查,发现是节点资源不足(requests太高)、或者 nodeSelector 匹配不到任何节点、或者有污点但没加容忍。这样一个排障过程就把调度器、资源请求、节点选择、Taint/Toleration四个知识点串起来了,同时证明了你遇到过、思考过、解决了。
再比如你遇到过“Service偶尔502”的问题:检查后端Pod都正常,但再深入看发现是readinessProbe配置得太宽松,导致Pod在重启的瞬间仍被认为ready而被暴露到Endpoints里,流量持续打过来了。这种Case能让面试官看到你对“可用性”“探针”“Service转发”之间关系理解的意义所在。
8.4 我个人沉淀下来的几条学习建议
如果要在最后给点可操作的建议,我会推荐这几件事。
首先,一定要自己动手把集群搭一遍。无论你用minikube、kind还是云厂商的托管集群,至少要亲手经历一次从创建集群到发布应用的全流程。很多知识点在你亲手操作过一遍之后,就不再是文档里抽象的名词了。
其次,多看集群事件(Events)和kubectl describe的输出。真实环境几乎每个异常都有Events线索,比如FailedScheduling、BackOff、Unhealthy、Evicted。面试时你的回答会不自觉地用上这些具体的事件类型,这是“实战经验”最直接的证据。
第三,养成先写“期望状态”再写“实现步骤”的习惯。K8s面试的核心就是声明式哲学:你告诉系统要什么,而不是怎么达到。这个思维模式听起来很简单,但我面试过很多人,聊到Deep Dive就会发现,他们的没有真正理解“声明式”和“命令式”的区别。你在思考任何K8s问题时,先问自己:“系统里哪个对象在维持期望状态?”这个问题能帮你理清绝大多数控制器相关的面试题。
最后,讲一句我这些年总结出来的经验:Kubernetes的面试题目千变万化,绕来绕去无非就是资源配置怎么定、流量怎么进、数据怎么存、权限怎么管、故障怎么处理。你把一条nginx从部署到被外部访问、再到扩容和故障演练的完整链路亲手跑通,把每条链路里的每一个“为什么”想明白,再去面试,比刷两百道题有用得多。