ARTICLE DETAIL

资讯详情

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

Kubernetes虚拟化编排工具实战:从容器调度到Nginx部署全解析

Kubernetes虚拟化编排工具实战:从容器调度到Nginx部署全解析 项目标题是“知识点8---虚拟化编排工具Kubernetes”说真的这个标题放在课程大纲里略显平淡但放到真实的生产环境中它可能是运维和开发之间最难跨越的一道坎。如果你已经会写Dockerfile、能在单机跑起容器那下一步面对的就是这台“单机”被几十台上百台机器取代之后容器到底要怎么调度、怎么互相通信、怎么自动恢复。Kubernetes解决的就是这一整摊事所以它才被叫作“虚拟化编排工具”——本质上它管的不是虚拟化本身而是运行在虚拟化层之上、以容器为单位的整个应用生命周期。这篇内容适合刚接触Kubernetes的开发者也适合已经部署过简单服务但对Pod、Service、Deployment之间关系还含糊的运维同学。我会从“为什么需要编排工具”开始讲清底层逻辑再完整走一遍用Kubernetes部署Nginx的流程最后分享几个我实际踩过的坑。保证你照着做完之后再回头看官方文档不会觉得每个词都认识但连起来不知道在说什么。1. 从单机容器到集群编排Kubernetes到底做了什么1.1 你手里的容器为什么管不了一堆机器先回忆一下Docker时代的爽感一条docker run -d -p 8080:80 nginx服务起来了写个docker-compose.yml数据库、缓存、应用一次拉起。这套东西在单机上非常顺手因为所有容器共享同一个内核、同一块网卡、同一套存储目录Docker Daemon天然知道哪个端口被谁占用哪个容器死了该怎么重启。但你一旦把规模扩大到三台、十台、五十台机器问题就接连出现用户请求进来负载均衡器该把流量转发到哪台机器上的哪个容器某个容器把CPU吃满我是杀掉重启还是迁移到别的机器凌晨三点一个节点宕机上面的十个容器谁来负责重新拉起新版本要上线怎么做到不停机、不手动登录每台机器去改配置这些问题本质上是“调度”和“状态管理”问题。单个Docker Daemon的视野只局限在自己这台机器上它看不见全局的资源水位也不知道别的节点是不是更空闲。Kubernetes的作用就是把自己包装成一个“集群级操作系统”把几十台机器的CPU、内存、存储抽象成一份大的资源池再按照你声明的期望状态比如“我要三个Nginx副本”不断调谐实际状态去靠近它。1.2 Kubernetes不是虚拟化技术的替代品而是它的上一级很多人第一次接触Kubernetes容易被“虚拟化编排工具”这个说法误导觉得它像VMware或OpenStack那样管理虚拟机。更准确的理解是Kubernetes站在容器引擎比如Docker、containerd之上它本身不运行容器而是通过调用容器运行时接口CRI来创建和销毁容器。你写一个Deployment告诉它“我想要三个Nginx”它做的事情是通过API Server接收你的声明把任务下发到调度器kube-scheduler调度器找到一台有足够资源的节点该节点上的kubelet调用containerd真正把容器跑起来控制循环kube-controller-manager持续监控如果发现只有两个副本了马上再创建一个补上。所以你可以把Kubernetes理解成一个大型公司的老板它不亲自干活的但所有员工容器的状态它都盯着谁跑了、谁状态不对立刻有人补位。1.3 为什么说“编排”这个词很精准编排Orchestration最早来自音乐领域指多个乐器按乐谱协同演奏。Kubernetes做的事情高度类似你提供一份“乐谱”YAML清单它指挥各个模块协同工作。API Server是指挥家手里的总谱etcd是挂在墙上的乐谱副本集群状态kubelet是各乐手kube-proxy是音响调音台负责网络规则。这个类比能帮你理解后面所有对象Deployment管副本数量Service管访问入口ConfigMap管配置Namespace管隔间。每个对象各司其职组合起来才形成一套完整编排。2. 核心概念拆解必须先搞明白这几个“乐手”2.1 PodKubernetes里最小的调度单位刚学Kubernetes时最容易犯的错是把容器当成最小单位。实际上Kubernetes不直接操作容器而是操作Pod。一个Pod可以包含一个或多个容器这些容器共享同一个网络命名空间也就是共用IP和端口、共享存储卷通过localhost就能互相访问。为什么要包这么一层因为现实中经常有两三个进程必须部署在同一台机器上比如一个日志采集容器sidecar挨着业务容器跑才能直接读它的日志文件再比如一个反向代理容器和业务容器共享网络栈丝滑转发请求。把它们塞进同一个PodKubernetes就能保证这两个容器总是被调度到同一节点一起启动、一起消亡。我自己的经验是如果一个Pod里跑多个容器尽量分清楚主容器和辅助容器sidecar。主容器承载核心业务sidecar做日志、监控、网络代理这类附属功能而且sidecar最好做成独立镜像方便复用。2.2 Deployment声明你要的“未来状态”Deployment是Kubernetes中最常用的控制器对象专门管理无状态应用。它最核心的价值是“声明式更新”你不需要告诉它“帮我停止旧Pod、创建新Pod”你只需要声明“我要这个镜像版本、要3个副本”Deployment控制器会自动完成滚动更新。实际操作中Deployment的YAML结构固定重点理解四个字段replicas期望的Pod副本数等于你希望同时跑几个实例selector.matchLabelsDeployment通过标签选择器管理哪些Pod归自己管这个必须和Pod模板里的labels对应上templatePod模板描述每个Pod内部长什么样包括容器镜像、端口、环境变量strategy更新策略默认是RollingUpdate可以控制滚动过程中最多超出几个副本、最少不可用几个副本。一个特别容易踩坑的地方selector一旦创建大部分字段就不能改了。如果改了标签选择器Deployment会放弃管理旧Pod导致一堆“孤儿Pod”继续运行白白占着资源。所以一开始设计标签就尽量用稳定的业务维度比如app: nginx、tier: frontend别把版本号写进标签。2.3 Service稳定的访问入口Pod在Kubernetes世界里是“短命”的它可能因为崩溃被重建因为更新被替换因为节点故障被重新调度。每次重建都会拿到新的IP如果让客户端直接记Pod IP那运维就没法干了。Service就是用来解决这个问题的它提供一组Pod的稳定访问入口不管底层Pod IP怎么变Service的IP和DNS名字都不变。Service通过标签选择器关联Pod工作原理可以理解为用户创建Service时指定selector比如app: nginxKubernetes通过API实时watch匹配这些标签的Pod列表Service得到所有匹配Pod的IP列表把它们写进Endpointskube-proxy在每台节点上维护转发规则访问Service IP的流量会被转发到某个Endpoints。Service的类型常见有四种ClusterIP集群内访问、NodePort节点端口暴露、LoadBalancer云负载均衡、ExternalNameDNS别名。刚上手时用ClusterIP和NodePort就够后面需要对外暴露公网流量再上Ingress网关。2.4 Namespace逻辑隔离的“小区”Namespace解决的是多团队、多环境共用集群时的资源划分问题。你可以把Namespace理解成Linux里的cgroup命名空间或者办公室里的隔断工位大家都在同一栋楼同一个集群但每个Team坐在自己的区域互相看不到对面桌上的文件。默认情况下Kubernetes自带几个Namespacedefault默认、kube-system系统组件、kube-public公共资源。我强烈建议你从第一天起就别把所有资源堆在default里至少按环境拆成dev、test、prod三个Namespace再配合ResourceQuota给每个Namespace限制CPU和内存总量。否则等扩容起来整个集群资源被某个测试任务吃光生产服务全部Pending那种感觉真的酸爽。2.5 ConfigMap与Secret把配置移出镜像好的镜像应该是“一次构建到处运行”但现实中每个环境开发、测试、生产的数据库地址、日志级别、域名都不一样。如果把这些写死在镜像里每次环境变更都要重新构建镜像太蠢。Kubernetes提供的标准方案是ConfigMap明文配置和Secret敏感信息。ConfigMap最大的价值是让镜像与配置解耦。比如Nginx的nginx.conf可以做成ConfigMap挂载进容器修改配置时只需要更新ConfigMap并触发滚动重启不用重新打镜像。Secret原理类似只是存储时会做base64编码注意这只是编码不是加密真正加密要用KMS并且支持权限控制。有一个经验必须分享ConfigMap更新后已经运行的Pod不会自动拿到新配置。你必须手动滚动重启或者用kubectl rollout restart deployment/xxx否则容器里还是老配置。很多新手改了ConfigMap发现服务没变排查半天其实只是忘了重启Pod。3. 实战用Kubernetes部署Nginx的完整过程3.1 准备环境本地体验用minikube还是k3s如果你手头没有现成的Kubernetes集群想先在自己电脑上练手我个人建议按机器配置选笔记本内存小于8GB用k3s。它是一个轻量级发行版单二进制文件搞定内存占用只有几百MB拿来学核心概念完全够笔记本内存大于8GB用minikube。它自带驱动能一键创建单节点集群且对Ingress、Dashboard、StorageClass这些附加组件支持比较友好如果你有云服务器可以考虑用云厂商的托管Kubernetes服务省去控制面维护成本但第一次学习不建议直接用因为很多底层细节被屏蔽出了问题看不懂。下面我用minikube为例因为它在本地还原度最高。装好minikube后执行minikube start --driverdocker --cpus2 --memory4096 kubectl get nodes看到节点处于Ready状态说明集群起来了。注意minikube start时指定--driverdocker意思是用Docker容器来跑Kubernetes节点这在Windows和macOS上最省事不用额外装虚拟机。3.2 编写第一个Deployment YAMLKubernetes一切皆对象对象用YAML描述。下面这份是我实际用过的Nginx部署清单注释写得很详细apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25.3 imagePullPolicy: IfNotPresent ports: - containerPort: 80 protocol: TCP resources: requests: memory: 128Mi cpu: 100m limits: memory: 256Mi cpu: 500m逐个解释关键点apiVersion必填不同的Kubernetes版本支持不同API版本Deployment用的就是apps/v1。如果你从网上抄老教程看到extensions/v1beta1那基本都是古董级写法新版集群会直接报错imagePullPolicy有三个值Always总是拉镜像、IfNotPresent本地没有才拉、Never只用本地镜像。本地调试的时候用IfNotPresent最舒服因为频繁构建相同标签的镜像时不会每次都被强制重新拉取。但如果你推到生产环境建议改成Always或直接用带唯一tag的镜像避免每台节点缓存了旧镜像resources.requests和limits这是新手最容易忽略的字段。requests告诉调度器“这个Pod最少要多少资源”调度器会用它做节点选择依据limits限制容器最多能用多少资源防止某个容器把整台机器打爆。上面例子中CPU的100m表示0.1个CPU核心500m表示0.5个核心内存按字节的倍数写Mi就是Mebibyte1Mi1024Ki。第一次写时建议先不设limits观察线上用量几天后根据真实监控再设置否则一不小心limits给太小容器频繁被OOMKilled烦死人。创建资源kubectl apply -f nginx-deployment.yaml这里提醒一下kubectl apply和kubectl create虽然都能创建资源但行为差异很大。create是命令式操作叫“给我建一个”apply是声明式操作叫“确保当前状态变成这样”。后者的优势是可以反复执行Kubernetes会自动计算差异所以生产环境统一习惯用apply。3.3 检查Pod状态与滚动更新创建完成后用下面几组命令观察状态kubectl get pods kubectl get deployment kubectl describe deployment nginx-deploymentkubectl get pods看到三个Pod处于Running状态说明部署成功。如果某个Pod状态不是Running用kubectl describe pod pod-name查看详细事件表格里会写清楚卡在哪一步比如镜像拉取失败、资源不足、探针失败等。现在模拟一次发布把Nginx镜像从1.25.3升级到1.27.0kubectl set image deployment/nginx-deployment nginxnginx:1.27.0你也可以直接改YAML文件再apply两种方式等价。执行后立刻用kubectl rollout status deployment/nginx-deployment观察滚动过程能看到它先创建新Pod、等新Pod Ready后再删旧Pod全程服务不中断。这里值得多说一句滚动更新的速度由几个参数控制默认maxUnavailable最大不可用数是25%maxSurge最大超额数也是25%。意思是更新3个副本时Kubernetes会允许新旧Pod总数最多到4个超出1个同时最多允许1个旧Pod先停掉。如果你希望发布时尽量保留全部旧副本直到新副本全部Ready可以把maxSurge设为100%、maxUnavailable设为0%代价是发布期间需要双倍的Pod资源。3.4 创建Service打通集群内访问Pod已经跑起来了但你还没法访问它因为Pod IP只在集群内部可见而且随时会变。创建ServiceapiVersion: v1 kind: Service metadata: name: nginx-service spec: type: ClusterIP selector: app: nginx ports: - port: 80 targetPort: 80 protocol: TCP这个Service的意思很直白把标签为app: nginx的所有Pod聚合起来提供一个虚拟IP端口80。外部流量访问Service的80端口时转发到Pod的80端口也就是Nginx监听端口。应用后验证kubectl apply -f nginx-service.yaml kubectl get svc nginx-service kubectl get endpoints nginx-servicekubectl get endpoints特别重要它显示Service后端真正关联到的Pod IP列表。如果这个列表是空的说明Service的selector没写对或者Pod标签不匹配流量转不出去。在minikube环境里你可以在宿主机直接访问ClusterIP测试因为Pod和Service都在同一个Docker网络里curl http://ClusterIP如果返回Nginx欢迎页说明整个集群内链路通了。但注意这个ClusterIP只有集群内能访问想从外面访问需要换NodePort或Ingress。3.5 用NodePort把Nginx暴露到宿主机对于本地实验来说NodePort是最快捷的暴露方式。改一下Service类型apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080NodePort的原理是在集群的每一台节点上都开一个端口范围默认是30000-32767访问任意节点IP:nodePort时流量会经过kube-proxy转发到后端Pod。nodePort字段如果不写Kubernetes会自动分配一个随机端口。minikube下更简单的做法是直接用一行命令把端口映射出来minikube service nginx-service --url它会返回类似http://127.0.0.1:xxxxx的地址浏览器打开就能看到Nginx页面。我实际测试中NodePort是最容易排查的问题源之一防火墙没放行端口、nodePort超出范围、多Service端口冲突都是常见状况。4. 深入一层从“能跑”到“好用”的进阶配置4.1 探针配置让Kubernetes知道Pod是不是真的活了部署Nginx跑起来容易但生产环境必须考虑一个问题如果容器进程还在但应用已经不响应了怎么办比如Java应用发生线程死锁进程还在但端口不接受连接比如Nginx的worker进程全部挂掉但master进程没退出。这时候如果只看容器运行状态Kubernetes会以为一切正常。所以就有了探针Probe机制livenessProbe探活。如果探针失败Kubernetes会杀掉容器重新创建readinessProbe就绪。如果探针失败Kubernetes会把这个Pod从Service的Endpoints里摘掉流量不再打进来但Pod不会被杀startupProbe启动。用于处理启动特别慢的应用防止liveness在启动窗口期误杀。给Nginx加一个最简单的HTTP探针livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5注意一点/healthz路径需要Nginx容器里真实存在才能返回200。默认Nginx镜像是没有这个页面的你可以通过ConfigMap挂一个自定义server块加一个location /healthz { return 200; }。千万别不测试直接把探针配上去否则容器一直被认为不健康一直被重启页面看起来就是CrashLoopBackOff加超时非常迷惑。4.2 优雅停机别让用户请求断在半路默认情况下当Pod要被删除时Kubernetes会先发SIGTERM给容器主进程。Nginx收到SIGTERM后会立刻退出正在处理的请求可能直接断掉。为此要在Nginx配置里加上优雅停机逻辑# nginx.conf 片段 worker_processes 1; events { worker_connections 1024; } http { server { listen 80; location / { root /usr/share/nginx/html; } } }同时容器里需要设置preStop钩子让Pod被删除时先执行一条命令等待几秒给Nginx时间处理完存量请求lifecycle: preStop: exec: command: [/bin/sh, -c, sleep 10]配合Pod的terminationGracePeriodSeconds默认30秒可以大大降低滚动发布时的请求错误率。我在生产环境遇到过一个问题明明探针都正常滚动更新时监控还是出现零星5xx排查后就是Nginx直接被SIGTERM杀掉加了这个preStop等待后错误率直接归零。4.3 三种对外暴露方式怎么选很多新手在NodePort、LoadBalancer、Ingress之间纠结其实它们不是互相替代的关系而是层层递进的关系方式适用场景缺点ClusterIP仅集群内部访问比如微服务之间调用外部完全访问不到NodePort测试环境、临时暴露端口给外部端口范围有限需要用节点IP不适合公网大规模分发LoadBalancer云环境直接创建云负载均衡器每个Service一个LB成本高Ingress多个Service共用一个入口按域名和路径路由需要额外部署Ingress Controller一句话总结对外提供HTTP/HTTPS服务生产上几乎都用IngressTCP/UDP四层服务用LoadBalancer或者NodePort集群内部微服务调用用ClusterIP就够。Ingress的典型配置长这样apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: nginx.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80注意创建Ingress对象之前集群里必须已经部署了Ingress Controller比如Nginx Ingress Controller、Traefik。jìn rù minikube环境可以直接用minikube addons enable ingress启用否则Ingress对象会一直显示Pending很多新手就在这里卡住。5. 实操中遇到的常见问题与排查思路5.1 Pod一直处于Pending状态看到Pod卡在Pending第一反应不是看代码而是看资源。用kubectl describe pod pod-name查看Events最常见的原因是节点CPU/内存不足调度器找不到合适的节点PVC持久卷声明Bound不上存储卷无法挂载节点存在污点Taint而Pod没有对应的容忍Toleration。排查命令kubectl top nodes kubectl describe node node-name kubectl get pvc -A对于本地minikube最常见的Pending原因是内存不足。如果你是4GB内存的机器minikube本身占1GB剩余内存不够跑3个带requests的Pod那就把副本数降到1或者把requests调低。5.2 ImagePullBackOff镜像拉取失败这个错误提示很直接镜像拉不下来。导致原因有网络问题、镜像不存在、镜像tag写错、私有仓库没有配置imagePullSecret。排查步骤kubectl get pods kubectl describe pod pod-name事件里会显示具体的失败原因比如manifest for nginx:1.26 not found或者connection refused。如果只是本地测试可以改用docker先手动拉一遍镜像docker pull nginx:1.25.3本地能拉下来但Pod拉不下来多半是节点上有代理限制或者镜像源不稳定。可以给Docker配置国内镜像源加速或者直接把镜像传到集群内私有仓库。5.3 CrashLoopBackOff容器起来了又退出CrashLoopBackOff的意思是容器启动后立刻崩溃Kubernetes用指数退避策略不断重启。要定位原因核心是看日志kubectl logs pod-name --previous加--previous参数可以查看上一次容器的日志。常见的崩溃原因有启动命令路径不对、依赖的ConfigMap挂载了错误的键名、环境变量没配上、权限不足等。一个让我印象深刻的例子有次给Spring Boot应用挂载了一个ConfigMap里面的配置文件用了Tab缩进导致YAML解析失败应用起来就报错退出。YAML对缩进极其敏感写配置文件时建议全程用空格、统一两个空格缩进别混用Tab。5.4 部署升级后Service访问仍报503升级Deployment后如果Service立刻返回503大概率是readinessProbe还没通过。Kubernetes滚动更新时只有新Pod Ready了才会摘掉旧Pod但如果你没配readinessProbe新Pod只要容器Running就会被当作Ready其实内部服务还没监听端口流量打进去自然失败。解决办法给每个重点服务都配上readinessProbe并且确保探针路径足够轻量。用kubectl rollout status deployment/xxx观察滚动过程看到类似“waiting for deployment spec update to be observed”说明还在等探针通过。5.5 集群网络不通或者DNS解析失败如果你发现Pod之间通过Service名访问不通先检查CoreDNS组件是否正常kubectl get pods -n kube-system -l k8s-appkube-dns如果CoreDNS处于CrashLoopBackOff那么集群内所有Service的域名解析都会失败。在minikube里如果遇到这类问题最简单粗暴的恢复办法是minikube delete再minikube start重来一次反正本地环境数据不重要。但在正式环境千万不能这么干优先看CoreDNS的日志找原因。5.6 我的总结好的排查习惯比命令更重要排查Kubernetes问题我的固定套路是“先看事件、再看日志、最后看配置”。kubectl describe是最高频的命令它能把你从“盲猜状态”直接带到“出事现场”。另外给每个Pod加标准化的labels和annotations能让kubectl get pods -l appnginx这类过滤命令发挥最大价值。再有就是所有YAML文件都要进Git仓库每次变更留痕出了事故能快速查到是谁、在什么时候、改了什么。别不信我见过太多人直接kubectl edit线上资源改完之后也不知道自己改了啥出问题无法复盘。6. 日常使用Kubernetes的一些小技巧最后分享几个我实际用着很顺手的小技巧都是文档里不常写但实操很有用的。第一个善用kubectl explain。比如你忘了Deployment的字段含义直接执行kubectl explain deployment.spec.template.spec.containers.resources它会把这个字段的用途、类型、默认值一五一十告诉你。这个命令本质上是查询API文档不用切浏览器效率极高。第二个查看Pod运行时的实际状态变化不要只盯着kubectl get pods。用kubectl get events --watch能实时看到集群里发生了什么比如节点掉了、镜像在拉、探针失败。这个命令在排障时比kubectl describe更直观因为它是持续输出的。第三个学会用kubectl port-forward临时调试。有时候你不想暴露Service只想本地看一下某个Pod的页面可以执行kubectl port-forward pod/nginx-deployment-xxxxx 8080:80然后浏览器访问localhost:8080就行。这个命令适合排查问题时不修改集群资源状态用完直接CtrlC退出不留任何垃圾。说到底Kubernetes的学习曲线确实陡峭但只要抓住“期望状态”和“调谐循环”这两条主线再跑通一个Nginx部署实验后面的路就顺了。希望这篇内容能帮你跨过第一道坎。
返回列表