ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes实战指南:从容器编排到生产集群的完整进阶路径

Kubernetes实战指南:从容器编排到生产集群的完整进阶路径 1. Kubernetes到底在解决什么问题从一台机器到一群机器的痛苦跃迁先说个大多数人都经历过的场景。你在一台服务器上装好了Docker写好了Dockerfiledocker run一把梭跑起来端口映射好日志能看一切正常。这时候老板说咱们业务要扩容了再上三台机器。你一下子傻眼了——多出来的机器怎么跟原来那台协同容器IP变了怎么办某个容器挂了谁来拉起来流量怎么在多台机器之间分配这时候你才真正意识到单机Docker解决的是怎么把一个应用装进标准箱子而Kubernetes解决的是怎么把成千上万个标准箱子有序地摆满整个仓库还要保证它们各自该干嘛干嘛。说白了Kubernetes就是一套自动化容器调度和运维系统。它的核心价值可以用三个词概括调度、编排、自愈。调度解决容器该跑在哪台机器上编排解决多个容器之间的配合关系自愈解决容器挂了怎么办。这篇指南的路线也很明确先从零开始搭一个能跑的环境再理解它背后的设计逻辑然后一步步把它扩展成一个多节点的分布式集群最后把应用真正部署上去跑通。很多人学习Kubernetes最大的误区在于一上来就对着架构图背Pod、Deployment、Service这些名词。这就像还没开过车就先背发动机原理图——背得再熟打火那一刻还是不知道踩哪个踏板。所以这篇指南我不打算从架构全景图开始而是带着你亲手把一个最小集群搭起来让容器真正跑起来再回头看概念你会觉得每个名词都是本来就该长这样。2. 入门第一课先建立一个声明式的心智模型2.1 命令式与声明式差的不只是习惯Kubernetes跟Docker最大的思维方式差异在于它是声明式的。Docker是命令式的——你输入docker run nginxDocker就执行了创建并运行一个nginx容器这个动作。Kubernetes正相反你提交的不是我要运行nginx这个命令而是nginx这个应用的期望状态是3个副本每个副本需要1核CPU和512M内存这样一份描述文件Kubernetes负责去实现并维护这个状态。我打个比方。命令式就像你打电话给管理员说去把三号房间的灯打开管理员照做了但灯被风吹灭了他不会主动去开。声明式则是你写了一张纸条三号房间的灯必须长期保持亮着管理员得想各种办法保证这个结果灯泡烧了换灯泡断路器跳了合闸。这张纸条在Kubernetes里就是YAML文件管理员就是集群里的各种控制组件。这个心智模型的建立极其重要。你后续会遇到的所有Kubernetes问题几乎都可以归结为一句话当前状态偏离了期望状态。排查思路也就是反过来说哪些机制负责纠偏为什么它们没生效。命令式思维会让你总想着再去ssh到某台机器上修一下而声明式思维会让你回到修改期望状态描述让系统自己去纠正这条正轨上来。2.2 从零搭一个最简集群把每个组件都跑明白工具选择上如果你只是想快速体验用Docker Desktop自带的Kubernetes就行你也可以理解为一个内置的单节点集群。但要真正理解组件之间的配合我建议用kubeadm在虚拟机或云主机上搭一个三节点集群——一台master控制平面两台node工作节点。规格上不用太高2核4G的机器三台就够了做实验完全够用。基础环境先确认三件事所有节点装好容器运行时推荐containerd别用Docker了后面细说原因所有节点禁用swap所有节点的时间同步正常。这三条少一条kubeadm初始化大概率报错。接下来就是标准三步。第一步在master节点上执行kubeadm init --pod-network-cidr10.244.0.0/16指定一个Pod网段后面装网络插件要用。初始化成功后输出末尾有一段kubeadm join命令和token一定要复制保存好。第二步在其他node节点上执行那串join命令节点就加入了集群。第三步装一个Pod网络插件推荐Calico或者Flannel二选一kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # 或者 kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml等两三分钟kubectl get nodes看到所有节点都是Ready状态一个最小的集群就成立了。这时候零散的组件是什么关系呢简单来说kubectl提交YAML到kube-apiserverkube-scheduler负责决定Pod放到哪个节点kubelet在节点上真正启动容器etcd存储所有状态。你在master上敲的每一个kubectl命令本质上都是在跟kube-apiserver对话你提交的每一份YAML最终状态都会被记录到etcd里。3. 理解集群的正常运转规律不要被分布式三个字吓住3.1 节点的自我管理机制你现在有了一个真实的三节点集群但应该感觉到它跟之前单机Docker的本质区别还没体现出来。让我做个实验随便在某个node节点上systemctl stop kubelet模拟这台机器故障。你观察一下会发生什么等大约一两分钟kubectl get pods -o wide会发现之前跑在这台节点上的Pod被标记为Terminating状态然后控制器自动在其他正常节点上创建了替代的Pod。全程你什么都没有做。这就是Kubernetes自愈能力的直接体现。它背后的逻辑是这样的kubelet定期向kube-apiserver汇报自己的心跳超过一定时间没有心跳控制器就会认为节点不可达然后按照Pod的副本策略在别的节点上重建Pod。这个机制你可能听过但亲手看过一次Pod在不同节点间漂移你对控制器这个词的理解会完全不同——它不是写死的代码而是运行中的控制循环时刻在对比期望状态与实际状态。另一个观察窗口是kubectl describe node。你能看到节点的容量、已分配资源、污点Taints和标签。污点这个概念可以这么理解节点上贴着一些负面标签比如node.kubernetes.io/unreachable意思是这机器联系不上新Pod别往这放。反过来Pod上也可以加容忍度Tolerations表示我就是要往这种节点上跑。比如你要给集群加一台专门跑大模型推理的GPU机器就给这台机器打上污点和标签再让对应的Pod带上容忍度和节点选择器一套调度规则就清清楚楚了。3.2 为什么Pod是一组容器而不是一个容器很多人第一个困惑就是Docker里最小运行单位是容器为什么到了Kubernetes变成了Pod这个设计其实是经历过生产环境毒打之后做出的改造。有些场景单个容器撑不住。比如一个日志采集容器和一个业务容器必须同一台机器上运行共享网络栈日志采集容器才能抓到业务容器的访问日志再比如主容器启动之前需要一个Init容器预热挂载目录、等待依赖服务就绪。Kubernetes里Pod就是这样一个最小的调度单元可以包含多个容器它们共享同一个网络命名空间和存储卷lifecycle紧密绑定要么一起调度到某台机器要么一起消失。真正的生产判断技巧是多个容器是否必须部署在一起是否紧密耦合成一个逻辑主机如果答案是肯定的放进同一个Pod如果不一定应该拆成多个Pod。很多人刚上手时容易把Pod直接等同于容器这会造成两个典型问题一是明明可以把多个进程放一个Pod里共进退却非要多起几个Pod然后手动处理它们之间的通信和依赖二是反过来本来应该独立的Pod因为嫌麻烦被塞进了一个Pod导致无法单独扩缩容。记住一条原则Pod是内聚的Deployment才是横向扩展的外层。4. 第一个实战作业在集群上部署Nginx并理清流量怎么进来的4.1 从kubectl run到YAML一份nginx配置的完整拆解跟着我的思路做第一个作业在集群里部署一个Nginx然后从集群外部访问它。如果你直接用kubectl create deployment nginx --imagenginx:latest命令式创建的虽然快但学不到什么东西。我建议你直接写一份YAML因为声明式才是Kubernetes的核心工作方式。apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi这份文件的信息量比看上去大得多。第一点metadata.labels和selector.matchLabels必须一致这是Deployment找到自己管理的Pod的关键不一致的话Deployment会直接工作异常。第二点spec.template是Pod的模板任何新Pod的创建都以这个模板为准。第三点resources里的requests相当于预订资源调度器据此决定Pod放置到哪台节点limits是硬上限超了就会触发OOM或CPU限流。这组配置在生产环境必须写不写的话Pod可能无限抢占机器资源拖垮同一个节点上的其他应用。kubectl apply -f nginx-deployment.yaml然后kubectl get pods -o wide能看到三个Pod被分散调度到不同节点也不一定这跟集群规模和调度器策略有关。这个结果本身就是观察调度器工作方式的好窗口。4.2 用Service把流量打通ClusterIP、NodePort和Ingress的取舍Deployment保证了Pod存在但Pod是短命的——随时可能被替换、迁移、IP漂移。你不可能让用户去记一个随时变化的Pod IP。Service就是这个稳定的接入层它给一组Pod提供一个稳定的虚拟IP(DNS名字)并做负载均衡转发。再往上一层的经典操作是创建一个Service暴露Nginx服务。写一个service的YAMLapiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80 type: NodePortkubectl apply -f后kubectl get svc会看到nginx-service有类似10.108.x.x:80的ClusterIP和节点IP:31568的NodePort。这个31568端口是随机分配的范围默认30000-32767。现在从浏览器访问集群内任意节点的IP:31568多刷新几次你会发现每次请求都可能被负载均衡到不同的Nginx Pod上。实测中kube-proxy转发带来了一定的随机性而这正是我们想要的负载均衡效果。再往后你会遇到的进阶问题是NodePort的局限性每暴露一个服务就得开一个随机端口服务多了端口管理混乱端口范围上限也摆在那里。生产环境更常见的做法是在所有节点前面加一个外部负载均衡器配合Kubernetes的Ingress资源做7层路由。Ingress相当于一个智能流量入口可以根据域名、URL路径把请求转发到不同Service。在云厂商环境阿里云、腾讯云、AWS里创建LoadBalancer类型的Service或Ingress云厂商会自动创建对应的负载均衡实例并绑上公网IP这是另一个层面的工作了。5. 从1到N的集群进阶存储、配置热更新和细粒度升级5.1 有状态应用的存储怎么办Networks和Deployment解决的是无状态应用。但数据库、消息队列这些有状态应用需要持久化数据Pod一旦被重新调度数据不能丢。这就引入了PVPersistentVolume和PVCPersistentVolumeClaim这套存储抽象。理解它们最简单的方式是PV/PVC类比运维人员准备的硬盘与开发者填写的领用申请单。PV定义了存储的容量、类型本地盘、云盘、NFS、访问模式PVC是使用方提出需求比如我需要5G能读写一次的存储Kubernetes里的存储控制器会把满足条件的PV和PVC进行绑定。Pod里声明引用PVC就能真正使用这块存储。做实际实验最简单的方式是HostPath或local类型的PV把宿主机目录挂载给Pod。但这有个致命限制如果Pod被调度到别的节点数据就留在原节点了跟没存有什么区别。所以生产环境有状态应用要考虑分布式存储方案云上就用云厂商的云盘或文件存储自建机房可以用Ceph、Longhorn、GlusterFS这一类方案。选择存储和选择基础架构一样是一个一旦运行起来就难以重来的决策值得斟酌。5.2 配置管理ConfigMap和Secret的使用边界每个应用都有配置文件和敏感信息。早期大家把配置写死在镜像里导致的后果是每次改配置都必须重新构建镜像、重新发布。Kubernetes的ConfigMap和Secret把配置从应用镜像中剥离出来。ConfigMap存明文配置Secret存敏感内容密码、证书。两者映射到Pod里有两种方式以环境变量的方式注入或者以文件的方式挂载到容器路径里。我强烈推荐后者因为以文件挂载支持热更新ConfigMap变化后挂载的文件内容会同步更新虽然应用自己不一定自动重载需要辅助手段触发。环境变量方式在Pod创建时就固化了改配置后只能重建Pod才生效这点值得注意。围绕Secret有个常见误区它只是把内容存成了base64编码这不算加密。api-server所在主机上拿到etcd数据的人可以直接读出来。生产环境必须开启etcd加密存储或者用外部密钥管理云厂商KMS、VaultSecret的保密价值才能真正成立。ConfigMap和Secret在Deployment里的写法spec: template: spec: containers: - name: nginx image: nginx:1.25 volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d volumes: - name: nginx-config configMap: name: nginx-configmap5.3 渐进式升级滚动更新的速度与安全平衡Deployment之所以是Kubernetes最常用的部署对象除了副本管理还因为它自带滚动更新能力。默认策略是RollingUpdate新Pod创建成功并就绪后旧Pod才被销毁整个过程不停服。这比全部杀掉再重建的Recreate策略优雅太多也是生产环境默认推荐的更新方式。更新的执行也很简单改镜像版本后kubectl set image deployment/nginx-deployment nginxnginx:1.26或者直接改YAML再kubectl apply。如果你想观察滚动更新的过程可以试试这样一个命令组合kubectl rollout status deployment/nginx-deployment它能实时显示更新的进度这在生产环境比较实用。更进阶的场景是金丝雀发布和灰度发布让一小部分流量跑新版本验证稳定后再全量切换。Kubernetes原生Deployment做这个比较费劲目前生产环境更通行的有两条路一是用服务网格Istio、Linkerd等做流量权重的精细控制二是借助Argo Rollouts这类工具来管理渐进式交付。到了这个阶段你基本已经跨过了会用的门槛进入如何发布得更稳的运维深水区了。再提醒一个经验之谈建议在Deployment中设置maxSurge和maxUnavailable参数。默认值虽然没有问题但大流量服务值得更精细地控制避免瞬时CPU打高或Pod全部不可用。我在生产环境一般配maxSurge: 25%和maxUnavailable: 0%保证任何时刻至少有75%的实例在线升级期间系统吞吐完全不受影响。6. 生产环境的真实挑战缩容误伤、驱逐风暴和资源抢占有理有据6.1 节点重启时发生了什么PodDisruptionBudget保护关键业务练到这一步你已经能搭建集群、部署应用、对外暴露流量、做滚动更新了。但真正的生产问题并不是功能性问题——反而是正常维护这件事会引起事故。比如你要给某台节点做内核升级重新启动机器。没有保护的情况下节点上的Pod直接全部被驱逐如果业务没有多副本服务就中断几秒甚至几分钟。Kubernetes给出的对策是PodDisruptionBudgetPDB。PDB的作用是规定自愿中断情况下最少保留多少个Pod。比如业务有3个副本PDB要求最少有2个可用那么节点管理操作同一时间只允许自愿中断1个Pod。运维和开发也能借助这个机制更清晰地沟通。apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: nginx-pdb spec: minAvailable: 2 selector: matchLabels: app: nginx这里要特别区分自愿中断和非自愿中断。节点硬件故障、被OOM杀死属于非自愿中断PDB管不了而是运维主动做的排空节点、升级这类才是PDB发挥作用的场景。很多公司吃了这个亏才知道PDB不是锦上添花而是维护操作的安全带。建议给所有有副本数的生产关键应用都建一份PDB。6.2 资源争抢为什么在Kubernetes里更危险前面说过给容器设置requests和limits很重要。Kubernetes的kubelet在节点上会做两件事一是根据容器的requests做调度决策保证节点上所有容器的requests总和不超过节点容量二是根据limits做运行时限制超内存就OOM超CPU就限流CPU绑核。我曾经踩过一个印象非常深刻的坑。一台8核16G的节点上跑着一些没有设置limits的Java应用和几个定时任务。平时没问题某次业务高峰Java应用突然开始疯狂申请内存直接把节点内存打满。kubelet强制驱逐了它但被驱逐的Pod在其他节点上立刻被重建——因为Deployment的replicas要求就是要保证副本数。于是其他节点的内存也被打满连锁反应之下整个集群多个节点的内存全部告急最终核心业务Pod也受到波及。这就像几个抢座位的人没划座次时每个人都拼命往前挤最后连讲台都被挤塌了。解决办法就是规范每个应用的资源规格。但也要注意limits不是设置得越高越好——如果所有容器都把requests压得很高集群的调度效率就会大幅下降很多Pod会因为没有足够的资源而处于Pending状态。这是一个精细的平衡活先测量应用的常规水位requests设为常规值limits留出几倍的峰谷余量然后通过HPA水平Pod自动扩缩容让副本数随着负载动态伸缩。以Nginx为例kubectl get hpa、按CPU使用率设置minReplicas: 3, maxReplicas: 10负载高时自动扩容降低时自动缩容集群资源利用率一下就上来了。6.3 排障方法论一个Pod从Pending到Running的完整检查链路掌握了上面这些最后的模块是排障。Kubernetes的排障跟传统运维最大的不同在于你不能只盯一台机器而要在期望状态→实际状态的差距里找原因。一个Pod从提交到Running要经过好几个节点每一步都可能出问题。我自己排障时习惯按这个顺序操作第一步kubectl get events --sort-by.lastTimestamp看最近集群发生了什么。70%的问题event里直接给了答案比如镜像拉取失败、探针未通过、配额不足。第二步kubectl describe pod pod-name看这个Pod本身的状态和Condition定位是调度失败还是启动失败。调度失败会提示0/3 nodes are available然后跟踪具体原因是资源不足、有污点不匹配、还是节点有unreachable这种异常状态。第三步如果Pod已经启动了但反复重启进去看日志和探针。kubectl logs -f pod-name --previous能看到上一次退出的日志配合kubectl exec进入容器排查。探针是另一大类常见问题——readinessProbe失败会让Pod从Service的endpoints中被移除livenessProbe失败会让kubelet反复重启容器。日志里业务可能一切正常但探针认为不健康就一直在重启。这套链路多跑几次之后你会发现自己已经能从看到Pod红色就慌变成扫一眼event和describe就基本锁定了问题方向。故障现场不再是一团迷雾而是一张有逻辑的排查地图。7. 后记从入门到真正上生产我说点实在的在写这篇指南的时候我一直有一种感觉Kubernetes难的不是某个具体组件怎么用而是整套思维方式和自己以往的习惯不同。如果你从Docker迁移过来最需要克服的就是手动管理一切的冲动如果你从传统运维过来还需要适应基础设施即代码声明显式运维的节奏如果你是开发者最重要的是分清应用该做什么和平台该做什么的边界别把两者搅在一起。作为一个身边不少团队都栽过跟头的老兵我最后想分享几点习惯养成建议第一所有变更先走YAML别敲裸命令。裸命令无法版本化、无法走审批流线上出问题回滚极其痛苦。所有YAML进Git仓库是Kubernetes运维的最低底线。第二至少把etcd的定期备份做成自动化定时任务。很多人觉得etcd备份不是自己的事直到集群状态损坏、丢失所有Pod和Service定义的时候才追悔莫及。备份做起来并不复杂etcdctl snapshot save走一遍就能跑通。第三每个季度主动做一次故障演习。把某台节点直接关机看看集群能不能自愈、PDB是否生效、监控告警是否正常。不要等真实故障来考验你那时候往往伴随着告警风暴和老板的夺命连环call。Kubernetes里的东西越学越多但总有一条主线贯穿始终系统自始至终都在努力让实际状态向期望状态靠拢。你把这条主线理解透了剩下的就是具体API和工具的熟练度问题需要的是时间和生产环境的反复锤炼。
返回列表