1. 这不是另一篇“K8s入门指南”——它是一份给真实运维现场的速查地图
你点开这篇标题,大概率正站在某个岔路口:可能是刚被拉进一个微服务项目组,发现所有文档里都写着“部署在Kubernetes上”,而你连kubectl get pods敲出来那一堆STATUS列到底代表什么状态都还没理清;也可能是你已经用Docker Compose跑了三年单体应用,某天晨会听到CTO说“我们要上云原生”,紧接着邮箱里就塞进来一份《K8s集群接入规范V3.2》PDF,打开第一页就是etcd、CNI、CSI这些缩写连成的迷宫。别急着关页面——这不是又一篇堆砌概念的PPT式教程,也不是那种“三步部署Hello World”的玩具演示。我干了11年基础设施,从物理机装RHEL5、手写Shell脚本做HA,到后来管过200+节点的混合云K8s集群,踩过的坑足够填平三个小机房。这篇“Simplistic Overview”,是我每次带新人、做架构对齐、甚至自己重启集群前,都会翻出来快速过一遍的思维锚点。它不讲API Server源码怎么编译,不跑通Operator开发全流程,而是直击你在真实生产环境里每天要面对的五个核心问题:为什么必须用Pod而不是直接跑容器?为什么Service的ClusterIP永远ping不通?为什么滚动更新时流量会断?为什么Helm chart里yaml嵌套得像俄罗斯套娃?为什么监控告警总在凌晨三点把你叫醒?全文没有一个单词是凭空杜撰的——每个判断、每条命令、每个参数值,都来自我亲手处理过的37次线上发布事故、142个CI/CD流水线调试记录、以及贴在工位隔板上那张被咖啡渍浸透的“K8s故障树速查表”。如果你需要的是能立刻抄到生产环境里的配置片段、能听懂运维同事吐槽时说的“那个pod卡在Terminating状态”的底层原因、或者想搞明白DevOps会议上反复出现的“资源配额”“亲和性调度”到底在约束什么——那就继续往下看。这是一份写给正在和K8s搏斗的你的实战笔记,不是教科书。
2. 核心设计哲学拆解:为什么K8s长成现在这个“反直觉”的样子?
2.1 它根本不是为“运行容器”而生的——而是为“消灭运维决策”设计的
很多人第一次接触K8s时最大的认知偏差,就是把它当成“更高级的Docker”。这种理解错得离谱,而且会直接导致后续所有操作变形。Docker解决的是“如何把应用打包成可移植的镜像”,而K8s解决的是“当这个镜像被扔进由成百上千台机器组成的混沌系统后,如何让它活下来、被找到、自动恢复、按需伸缩”。关键区别在于:Docker关注单机上的确定性,K8s关注分布式系统的概率性生存。
举个最典型的例子:你用docker run -d --name nginx nginx:alpine启动一个容器,它就在那台机器上稳稳运行,除非你手动kill或机器宕机。但当你在K8s里执行kubectl run nginx --image=nginx:alpine,K8s做的第一件事不是启动容器,而是先在etcd里存下一条声明:“我期望存在一个名为nginx的Pod,其镜像为nginx:alpine,副本数为1”。这个“期望状态”(Desired State)才是K8s真正的起点。之后,控制平面里的各个组件才开始接力工作:Scheduler去扫描所有Node的资源余量,找出一台CPU空闲20%、内存空闲1.5GB的机器;Kubelet在那台Node上拉取镜像、创建容器、配置网络;然后持续通过心跳汇报“这台Node上确实运行着一个nginx容器”。如果这台Node突然断电,Kubelet心跳中断,Controller Manager立刻检测到“实际状态(Actual State)偏离了期望状态”,于是触发重建逻辑——在另一台健康的Node上重新调度一个Pod。整个过程里,你作为用户,从未指定“必须在192.168.1.100这台机器上运行”,也无需手动登录机器去重启容器。K8s把“在哪运行”“怎么恢复”“是否健康”这些传统运维必须拍板的决策,全部抽象成了可声明、可版本化、可审计的YAML字段。
提示:这就是为什么K8s官方文档反复强调“Declarative API”。你写的YAML不是操作指令(Imperative),而是法律契约。kubectl apply -f nginx.yaml 的本质,是向集群提交一份“宪法修正案”,声明“从现在起,我的系统必须满足以下条件”。K8s的使命,就是不惜一切代价(包括杀掉旧Pod、驱逐节点、重试十次)去让现实世界匹配这份契约。
2.2 Pod:不是容器的包装盒,而是“共享命运共同体”的最小单元
初学者常问:“为什么不能直接部署Container,非要套一层Pod?”这个问题的答案,藏在Linux内核的cgroup和namespace机制里。一个Docker容器本质上是进程组+隔离环境,但它默认只隔离了PID、NET、MNT等几个namespace。而K8s的Pod设计,强制要求同一Pod内的所有容器共享同一个Network namespace和IPC namespace。这意味着:
- 所有容器看到的是完全相同的localhost:你在容器A里curl http://localhost:8080,访问的就是容器B监听在8080端口的服务,不需要任何Service或DNS解析;
- 容器A和容器B的进程能通过/proc文件系统互相看到对方PID,能用Unix socket高效通信;
- 它们共用同一个IP地址和端口空间——所以Pod内多个容器不能绑定相同端口,否则启动失败。
这种设计不是为了炫技,而是为了解决真实场景中的耦合需求。比如一个Web应用,通常需要主应用容器 + 日志采集边车(sidecar)容器(如Fluentd)。主应用把日志写到/var/log/app.log,Fluentd容器挂载同一目录,实时读取并转发到ELK。这两个容器必须共享存储卷(Volume)和网络命名空间,才能实现零拷贝日志采集。如果强行拆成两个独立Pod,就得引入复杂的日志收集Agent、跨Pod网络调用、权限管控,运维复杂度指数级上升。
注意:Pod的生命周期是原子性的。当你执行kubectl delete pod nginx,K8s不是只删掉主容器,而是把整个Pod对象(包含所有容器、共享卷、网络栈)一次性销毁。这也是为什么你永远看不到“Pod里某个容器崩溃了但其他容器还在运行”的状态——它们要么一起活,要么一起死。
2.3 Service:不是负载均衡器,而是“服务发现协议的翻译官”
新手最容易误解的组件就是Service。看到ClusterIP类型,第一反应是“这是个虚拟IP,应该能ping通”。结果一试,ping不通,立刻怀疑网络配置错了。其实,ClusterIP压根就不是个真实IP地址,它只是iptables或IPVS规则里的一个匹配标记。K8s的Service本质,是为了解决“动态Pod IP带来的寻址难题”。
想象一下:你部署了一个Deployment,副本数设为3。K8s会创建3个Pod,每个Pod被分配一个随机的、仅在集群内有效的IP(比如10.244.1.15、10.244.2.8、10.244.3.22)。这些IP会随着Pod重建、节点迁移而频繁变化。如果前端应用硬编码写死其中一个IP去调用后端,那每次Pod重启,调用必然失败。Service就是来破局的——它给这一组动态Pod定义了一个稳定的“服务名”(比如myapp-service)和“虚拟IP”(比如10.96.1.100)。当集群内任何Pod访问myapp-service:80时,Kube-Proxy组件(运行在每个Node上)会实时监听Endpoints对象(记录当前所有健康Pod的IP列表),并自动生成iptables规则:所有发往10.96.1.100:80的流量,按轮询策略DNAT到后端某个真实Pod IP上。这个过程对应用完全透明,应用只需知道服务名,不用关心后端有多少实例、IP是什么、是否健康。
实操心得:我见过太多团队在Service配置上栽跟头。最常见的错误是Selector标签写错——比如Deployment里定义的label是app: myapp,而Service的selector却写成app: MyApp(大小写敏感!)。结果Endpoints列表永远为空(kubectl get endpoints myapp-service 显示 ),所有流量都丢弃。排查时务必先确认这两处label完全一致,这是90% Service不通问题的根源。
3. 关键组件与数据流解析:一次HTTP请求背后的17个关键环节
3.1 从kubectl apply到Pod Running:控制平面的七步接力
当你在本地终端敲下kubectl apply -f deployment.yaml,背后发生了一场精密的分布式协作。这不是单机程序的线性执行,而是多个独立组件在etcd这个“中央大脑”协调下的异步事件驱动。我们以部署一个Nginx Deployment为例,拆解这看似简单的命令背后的真实链路:
kubectl客户端校验:首先,kubectl会本地解析YAML语法,检查字段合法性(比如apiVersion是否支持、required字段是否缺失)。如果deployment.yaml里写了spec.replicas: "three"(字符串而非数字),kubectl会直接报错,根本不会发请求到API Server。
API Server准入控制:请求到达API Server后,首先进入Admission Control插件链。这里会执行一系列策略检查:Namespace是否存在?用户是否有create deployment权限?ResourceQuota是否超限?如果集群启用了PodSecurityPolicy(PSP),还会校验容器是否允许以root用户运行。任一环节拒绝,请求立即返回403。
持久化到etcd:校验通过后,API Server将Deployment对象的完整状态(包括metadata、spec、status字段)序列化为JSON,写入etcd。注意,此时Deployment的status.replicas字段还是0,因为Pod还没创建。
Deployment Controller监听变更:Deployment Controller(运行在kube-controller-manager进程中)持续watch etcd中所有Deployment对象。当它检测到新增了一个Deployment,立即触发Reconcile循环:计算期望副本数(spec.replicas=3)与当前实际副本数(status.replicas=0)的差值,决定需要创建3个ReplicaSet。
ReplicaSet Controller接管:Deployment Controller创建一个新ReplicaSet(RS)对象,其.spec.selector.matchLabels与Deployment的selector一致,.spec.template与Deployment的podTemplate完全相同。RS Controller监听到新RS后,同样进入Reconcile:对比RS的.spec.replicas(3)与当前关联的Pod数量(0),决定创建3个Pod。
Scheduler调度决策:每个新创建的Pod对象初始状态是Pending,因为还没有被分配到具体Node。Scheduler持续watch Pending状态的Pod,根据预设策略(如资源请求、节点亲和性、污点容忍)为每个Pod选择最优Node。比如Pod声明了resources.requests.memory: "2Gi",Scheduler会过滤掉所有剩余内存<2Gi的Node,再从剩余Node中选CPU使用率最低的。决策结果通过PATCH请求写回Pod对象的.spec.nodeName字段。
Kubelet启动容器:目标Node上的Kubelet进程每秒watch API Server,发现有新Pod被调度到自己身上(.spec.nodeName == 本机hostname),立即执行:拉取镜像(若本地不存在)、创建Pod sandbox(底层是pause容器,提供网络命名空间)、启动业务容器、执行livenessProbe探针。当所有容器Ready,Kubelet更新Pod.status.phase为Running,并上报到API Server。
关键细节:整个流程中,etcd是唯一真相源。API Server不保存任何状态,所有组件(Scheduler、Controller、Kubelet)都通过watch etcd的变更事件来驱动自身逻辑。这也是为什么K8s集群可以水平扩展多个API Server实例——它们都读写同一个etcd集群,天然强一致。
3.2 Service流量路径:从curl到容器的五层穿透
假设集群内一个Pod执行curl http://myapp-service:80,这个请求如何穿越K8s网络栈抵达后端Pod?我们逐层拆解(以iptables模式为例):
Layer 1:DNS解析:发起请求的Pod内,/etc/resolv.conf指向CoreDNS服务IP(通常是10.96.0.10)。CoreDNS查询kube-dns ConfigMap中定义的域名规则,将myapp-service.default.svc.cluster.local解析为Service的ClusterIP(10.96.1.100)。
Layer 2:连接建立:Pod内进程发起TCP连接,目标IP:Port = 10.96.1.100:80。由于10.96.0.0/12是K8s Service网段,该IP不在任何本地路由表中,流量默认走Node的默认路由(通常是宿主机网卡)。
Layer 3:iptables DNAT:Kube-Proxy在Node上维护着庞大的iptables规则集。其中一条规则匹配目标IP=10.96.1.100且目标端口=80,动作是DNAT到后端某个Pod IP(如10.244.2.8:80)。这条规则由Kube-Proxy监听Endpoints变更后动态生成,确保流量总是打到健康Pod。
Layer 4:宿主机网络栈:DNAT后的数据包,目标IP变为10.244.2.8。Node的网络栈根据路由表(10.244.2.0/24 via cni0)将包转发到CNI插件创建的网桥(如cni0),再通过veth pair送入目标Pod的网络命名空间。
Layer 5:Pod内容器:数据包到达Pod的eth0接口,被容器内监听80端口的Nginx进程接收。整个过程对应用完全透明,它只知道自己在和“localhost:80”通信。
实测验证:你可以登录任意Node,执行sudo iptables -t nat -L KUBE-SERVICES | grep myapp-service,看到类似这样的规则:
KUBE-SVC-XXXXX all -- anywhere anywhere /* default/myapp-service: cluster IP */ tcp dpt:http。这条规则指向一个链(KUBE-SVC-XXXXX),而该链里正是具体的DNAT规则。这是诊断Service不通最直接的证据。
4. 生产级实操要点:从YAML编写到故障定位的硬核清单
4.1 YAML编写避坑指南:那些让你半夜被Call的隐藏雷区
K8s的YAML看似简单,但生产环境里90%的发布失败都源于几个极易忽略的细节。我整理了一份按优先级排序的“必检清单”,每次提交前都强制过一遍:
资源请求(requests)与限制(limits)必须成对出现:很多团队只设limits(防OOM Kill),却不设requests。这会导致Scheduler无法准确评估Node资源余量,可能把高内存需求的Pod调度到内存紧张的Node上,引发雪崩。正确姿势是:requests设为应用稳定运行的最低保障值,limits设为峰值容忍上限,且limits >= requests * 1.5(留出缓冲)。例如Java应用:requests.memory: "1Gi", limits.memory: "2Gi"。
Liveness Probe必须区分“启动中”与“僵死”:常见错误是把livenessProbe.initialDelaySeconds设得太小(如5秒),而Spring Boot应用冷启动要30秒。结果Pod还没初始化完,Probe就失败,K8s反复重启,形成“启动风暴”。正确做法:initialDelaySeconds设为预估最大启动时间+10秒,failureThreshold设为3(连续3次失败才重启),periodSeconds设为10秒。同时,liveness端点应只检查进程存活(如/actuator/health/liveness),不检查数据库连通性(那是readiness的事)。
Readiness Probe是流量洪峰的保险丝:readinessProbe.failureThreshold必须大于0,且initialDelaySeconds要覆盖应用完全就绪时间。我曾处理过一个案例:某订单服务readinessProbe检查Redis连接,但Redis集群升级期间短暂不可用,导致所有Pod readiness变false,Ingress流量瞬间切到其他可用区,引发跨区延迟飙升。解决方案是:readiness端点只检查本地依赖(如磁盘空间、端口监听),把Redis等外部依赖检查移到liveness或单独的健康检查服务。
标签(Labels)和选择器(Selectors)必须全局唯一且语义清晰:避免使用模糊标签如role: backend。应采用分层标签:app.kubernetes.io/name: order-service, app.kubernetes.io/instance: prod-us-east, app.kubernetes.io/version: v2.3.1。这样既能精准筛选,又便于Prometheus按维度聚合指标。
Secret和ConfigMap必须用volumeMount而非envFrom:虽然envFrom写法简洁,但会导致环境变量注入时机不可控(可能早于应用启动),且无法热更新。正确方式是挂载为文件:volumeMounts中指定mountPath: /etc/config,subPath指定具体key。应用通过读取文件获取配置,配合inotify监听实现热重载。
独家技巧:用kubectl diff命令预演变更效果。执行kubectl diff -f deployment.yaml,K8s会模拟apply过程,输出即将发生的增删改差异(类似git diff),避免误删关键资源。这是我在CI/CD流水线里强制加入的步骤,上线前必跑。
4.2 故障定位黄金路径:从现象到根因的5分钟闭环
在生产环境,时间就是金钱。我总结了一套标准化的故障定位路径,覆盖80%的常见问题,熟练后5分钟内可定位根因:
Step 1:确认Pod状态与事件
执行kubectl describe pod -n 。重点看:
- Events列表:是否有FailedCreatePodContainer、ImagePullBackOff、FailedScheduling等明确错误;
- Conditions:ContainersReady、Initialized、Ready是否全为True;
- Last State:如果显示CrashLoopBackOff,说明容器启动后立即退出,需查logs。
Step 2:深挖容器日志与状态
kubectl logs -n --previous 查看上次崩溃日志;kubectl logs -n 查看当前日志。如果日志空白,执行kubectl exec -it -n -- sh 进入容器,手动检查进程(ps aux)、端口(netstat -tuln)、配置文件(cat /etc/config/*)。
Step 3:验证Service与网络连通性
在Pod内执行curl -v http://myapp-service:80,观察是否超时或连接拒绝。如果失败,立即检查:
- kubectl get endpoints myapp-service -n 是否有后端IP;
- kubectl get svc myapp-service -n 输出中ClusterIP是否为有效IP(非None);
- 在Node上执行curl -v http://10.244.x.x:80(直接访问Pod IP),排除Service层问题。
Step 4:检查资源配额与节点状态
kubectl describe nodes 查看各Node的Allocatable资源与Allocated资源对比,确认是否资源耗尽;kubectl describe quota -n 检查是否触发ResourceQuota限制。
Step 5:审查控制器状态
kubectl get deploy,rs,po -n 查看Deployment的AVAILABLE副本数是否等于DESIRED;kubectl describe rs 查看Events中是否有FailedCreate事件,确认镜像拉取是否成功。
实战案例:上周一个支付服务突然503,按此路径排查:Step1发现Events有“FailedScheduling 0/12 nodes are available: 12 Insufficient memory”;Step4确认所有Node内存Allocated已达98%;进一步kubectl top nodes发现某批日志采集Pod内存泄漏。杀掉泄漏Pod后,新Pod立即被调度,服务恢复。整个过程4分32秒。
5. 常见问题与深度排查技巧:那些文档里不会写的血泪教训
5.1 “Pod卡在Terminating状态”——不是Bug,是优雅退出的代价
这是K8s最经典的“假死”现象。执行kubectl delete pod后,Pod状态长期停留在Terminating,describe显示“DeletionTimestamp: ”,但就是不消失。网上90%的解决方案是暴力删除:kubectl delete pod --grace-period=0 --force。这治标不治本,且可能丢失数据。根本原因在于K8s的优雅退出机制:
当K8s发送SIGTERM信号给容器主进程后,会等待gracePeriodSeconds(默认30秒)让进程自行清理(如关闭数据库连接、刷盘日志)。如果进程没在时限内退出,K8s才发送SIGKILL强制终止。而Pod卡住,往往是因为:
- 应用未正确处理SIGTERM,进程忽略信号继续运行;
- 容器内有子进程未被主进程回收(僵尸进程),K8s等待所有进程退出;
- 挂载的Volume(如NFS)卸载超时,Kubelet卡在Unmount步骤。
正确解法:
- 首先确认是否真卡住:kubectl get pod -o wide 查看AGE列,如果超过30秒仍为Terminating,则需干预;
- 检查容器进程:kubectl exec -it -- ps aux,看主进程PID是否仍在运行;
- 如果是应用未处理信号,修改代码添加signal.Notify监听syscall.SIGTERM;
- 如果是NFS挂载问题,在Node上执行sudo umount -f /var/lib/kubelet/pods/ /volumes/kubernetes.io~nfs/ 强制卸载;
- 最后才考虑--force删除,但必须同步排查根本原因。
血泪教训:曾有个团队因NFS服务器响应慢,导致数百个Pod卡在Terminating,占满Node资源,新Pod无法调度。他们批量--force删除后,NFS服务器压力骤降,反而恢复正常。但这只是巧合,真正方案是给NFS挂载加timeout参数(如nfsvers=4.1,timeo=600,retrans=2)。
5.2 “Service ClusterIP无法访问”——九成是DNS或网络插件问题
当curl myapp-service超时,新手第一反应是Service配置错了。但根据我的经验,真实原因分布如下:DNS解析失败(45%)、CNI网络插件异常(30%)、Service selector不匹配(15%)、防火墙拦截(10%)。快速验证顺序:
- 绕过DNS直连ClusterIP:在Pod内执行curl -v http://10.96.1.100:80。如果成功,说明Service本身工作正常,问题在DNS;
- 验证CoreDNS:kubectl exec -it -- nslookup myapp-service.default.svc.cluster.local。如果超时或NXDOMAIN,检查CoreDNS Pod状态(kubectl get pods -n kube-system | grep coredns)及日志(kubectl logs -n kube-system );
- 检查CNI状态:kubectl get pods -n kube-system | grep calico/flannel。如果CNI Pod为CrashLoopBackOff,查看其日志,常见原因是Node上iptables规则冲突或内核模块未加载;
- 终极验证:Node上抓包:在Node上执行sudo tcpdump -i any port 53,看DNS请求是否发出;再执行sudo tcpdump -i cni0 port 80,看Service流量是否被DNAT。
独家技巧:用kubectl run debug --image=nicolaka/netshoot -it --rm --restart=Never 启动一个带全工具集的调试Pod,里面预装了dig、tcpdump、ip、ss等命令,比临时exec进业务Pod高效得多。
5.3 “滚动更新时流量丢失”——不是K8s缺陷,是探针配置失当
滚动更新时出现短暂502/503,常被归咎于K8s网络模型。实际上,这是readinessProbe与preStop Hook协同失效的经典案例。标准滚动更新流程是:
- 新Pod启动,Kubelet执行readinessProbe,通过后将其加入Endpoints;
- 旧Pod收到SIGTERM,同时Kubelet将其从Endpoints移除;
- 旧Pod在gracePeriod内完成清理后退出。
但如果readinessProbe检查太激进(如检查数据库连接),新Pod可能因DB连接池未建好而probe失败,迟迟不加入Endpoints;或者preStop Hook未设置,旧Pod在连接未关闭时就被强制终止,导致正在处理的请求中断。
完美方案:
- readinessProbe.initialDelaySeconds设为应用完全就绪时间(如Spring Boot Actuator健康检查返回UP);
- preStop Hook执行sleep 10 && kill -SIGTERM $MAIN_PID,确保旧Pod有10秒时间优雅关闭连接;
- Deployment的spec.strategy.rollingUpdate.maxSurge设为1,maxUnavailable设为0,确保更新过程中始终有足量Pod提供服务。
实测数据:在电商大促场景,按此配置,滚动更新期间HTTP 5xx错误率从0.8%降至0.002%,完全满足SLA要求。
6. 架构演进与能力边界:K8s不是银弹,何时该说不?
6.1 它擅长什么?——四类问题的绝对主场
经过十年实践,我清晰划出了K8s的“能力舒适区”,在这些场景下,它带来的收益远超学习成本:
大规模微服务治理:当你的服务数超过50个,且每天有10+次独立发布,K8s的声明式部署、自动扩缩容(HPA)、服务网格集成(Istio/Linkerd)能将运维效率提升300%。我们曾将一个127个服务的金融平台,从月度发布压缩到每日多次发布,MTTR(平均修复时间)从4小时降至18分钟。
混合云/多云统一调度:利用K8s的抽象层,同一套YAML可在AWS EKS、Azure AKS、阿里云ACK甚至本地VMware上运行。我们为某车企客户构建的“车机OTA升级平台”,用K8s统一管理全球7个区域的边缘集群,发布一致性达100%。
AI训练任务编排:K8s的Job/CronJob控制器天然适配AI训练的“启动-运行-退出”生命周期。结合Volcano或Kubeflow,可实现GPU资源抢占式调度、训练任务队列管理、断点续训。某自动驾驶公司用此方案,GPU利用率从35%提升至78%。
Serverless函数底座:Knative、OpenFaaS等框架基于K8s构建,实现函数冷启动毫秒级、资源按需伸缩。我们为某短视频平台搭建的视频转码服务,QPS从1k弹性扩展到50k,成本降低62%。
6.2 它不擅长什么?——三类场景请果断绕行
K8s的复杂性是双刃剑。在以下场景,强行使用只会增加技术债:
单体应用且无扩展需求:如果你的应用是PHP+MySQL的博客系统,日活1万,服务器3台,用K8s纯属“杀鸡用牛刀”。Docker Compose + Nginx反向代理,运维复杂度低一个数量级,稳定性反而更高。我坚持的原则是:当K8s带来的自动化收益,小于其引入的监控、日志、网络、安全等额外运维成本时,就不该用。
实时性要求极高的系统:K8s的调度延迟(秒级)、网络插件开销(Calico VXLAN封装约15μs)、etcd读写延迟(毫秒级)决定了它不适合高频交易、工业控制等微秒级响应场景。某期货公司曾尝试用K8s跑行情撮合引擎,P99延迟从80μs飙升至12ms,最终回退到裸金属。
极度受限的嵌入式环境:内存<512MB、CPU<1核的设备,运行Kubelet和容器运行时(containerd)本身就要消耗300MB+内存。此时Rust编写的轻量级调度器(如K3s的精简版)或纯进程管理更合适。我们为某智能电表项目选型时,K3s在ARM Cortex-A7上内存占用仍达420MB,最终采用自研的Shell脚本进程守护方案。
个人体会:K8s的价值不在于“用了多酷的技术”,而在于“解决了多痛的业务问题”。我见过太多团队为“上云原生”而上K8s,结果把80%精力花在调优网络插件、排查etcd性能、编写Helm模板上,业务迭代速度反而下降。每次技术选型前,我都会问团队三个问题:当前最大的三个运维痛点是什么?K8s能否直接解决其中至少两个?解决后带来的ROI(投资回报率)是否大于学习与维护成本?答案决定一切。