
上一篇【第08篇】下一篇【第10篇】Label和Selector——K8s的贴标签艺术摘要上篇文章我们搞懂了Pod的设计哲学今天咱们进入实战——写Pod YAML。说实话我第一次写K8s YAML时是直接复制别人代码改的结果改了半天Pod还是起不来报错也看不懂崩溃得要死。后来我发现K8s的YAML其实就五段话——apiVersion、kind、metadata、spec、status每段话里有个固定套路掌握了套路手写到飞起。本文从Pod YAML的完整骨架讲起逐个拆解每个字段的含义、取值、以及哪些是必填、哪些是可选。你还会学到kubectl explain这个神器——有了它你再也不用翻文档查字段含义了。看完这篇写Pod YAML就不需要复制粘贴改一改了你心里有谱。一、Pod YAML的五段式骨架任何一个K8s对象的YAML都由五个顶层字段构成Pod也不例外# Pod YAML 的完整骨架apiVersion:v1# ① API版本——这是哪个API组的哪个版本kind:Pod# ② 资源类型——Pod/Service/Deployment等metadata:# ③ 元数据——名字、标签、注解、Namespacename:my-nginxlabels:app:nginxspec:# ④ 规格/期望状态——我想要什么样的Podcontainers:-name:nginximage:nginx:1.25status:# ⑤ 实际状态——Pod现在是什么样phase:Running# 【注意】status是K8s自动填充的你不用写podIP:10.244.1.5要点status字段你永远不用自己写。它是K8s根据集群实际状态自动填充的。你只需要声明spec你期望的样子K8s负责把status调成spec的样子。所以写YAML时只关注前四个字段就够了。【Pod YAML 五段式结构】 ┌──────────────────────────────────────────────────────┐ │ apiVersion: v1 ← 告诉K8s我用的是v1版API │ │ kind: Pod ← 告诉K8s我要创建Pod │ ├──────────────────────────────────────────────────────┤ │ metadata: ← 这个Pod叫什么、贴什么标签 │ │ name: my-pod │ │ namespace: default │ │ labels: │ │ app: nginx │ ├──────────────────────────────────────────────────────┤ │ spec: ← 我想要的Pod长什么样 │ │ containers: │ │ - name: nginx │ │ image: nginx:1.25 │ │ ports: │ │ - containerPort: 80 │ │ volumes: │ │ restartPolicy: Always │ ├──────────────────────────────────────────────────────┤ │ status: ← Pod现在实际长什么样 │ │ phase: Running ← K8s 自动填充别手写 │ │ podIP: 10.244.1.5 │ └──────────────────────────────────────────────────────┘二、apiVersion和kind——“你在跟哪个API说话”apiVersion和kind就是K8s对象的身份证号码。不同资源类型有不同的API版本。2.1 K8s的API分组K8s把所有API分成了不同的组Group不同的资源属于不同的组# 核心组core group也叫 legacy group——apiVersion就是v1# Pod、Service、ConfigMap、Secret、Namespace、Node等核心资源apiVersion:v1kind:Pod# apps组——Deployment、StatefulSet、DaemonSetapiVersion:apps/v1kind:Deployment# networking.k8s.io组——NetworkPolicy、IngressapiVersion:networking.k8s.io/v1kind:Ingress# batch组——Job、CronJobapiVersion:batch/v1kind:Job要点核心组Pod、Service等的apiVersion直接写v1即可没有core/前缀。其他组的资源要写完整路径比如apps/v1、batch/v1。搞错了apiVersionkubectl会直接扔个错误给你Pod压根创建不了。2.2 怎么查一个资源的apiVersion# 方法1用 kubectl api-resources 查kubectl api-resources|grep-ipod# pods Pod true Pod v1 truekubectl api-resources|grep-ideployment# deployments deploy apps/v1 true Deployment# 方法2用 kubectl explain 查后面细讲kubectl explain pod# KIND: Pod# VERSION: v1 ← 这就是 apiVersion三、metadata——“给Pod办身份证”metadata就是Pod的户口本信息——名字、标签、注解、命名空间。metadata:name:my-nginx# Pod的名称必填在同一Namespace里唯一namespace:default# 所属Namespace不写就是defaultlabels:# 标签用于选择和分组KV对app:nginxversion:v1.25tier:frontendannotations:# 注解放附加信息KV对description:This is the main nginx pod for productionprometheus.io/scrape:trueprometheus.io/port:9113字段必填说明注意name是Pod名称同一Namespace唯一小写字母、数字、连字符最长253字符namespace否所属命名空间默认defaultlabels否标签KV对key/value最长63字符key分前缀(可选)和名称annotations否注解KV对可以存任意内容包括JSONgenerateName否名称前缀K8s自动在后面加随机后缀保证唯一# generateName 的使用场景批量创建Pod不想手动起名# 如果 generateName: nginx-# K8s 生成的Pod可能叫nginx-7b5f8c、nginx-9d3a2f要点name必须在同一个Namespace里唯一。别把namespace漏掉了——漏掉就创建到default里去时间长了default里乱七八糟一堆Pod你都不知道哪个是哪个。四、spec——“Pod的肉身”也是今天的重头戏spec是Pod YAML的灵魂定义了Pod里有什么容器、怎么运行、用哪些卷、重启策略是什么。下面逐个拆解核心字段。4.1 containers必填——Pod里跑什么容器每个Pod至少有一个容器用containers数组定义。下面是最小配置spec:containers:-name:nginx# 容器名称必填Pod内唯一image:nginx:1.25# 镜像必填一个完整的容器定义可以复杂到吓人spec:containers:-name:my-appimage:myapp:v2.0imagePullPolicy:IfNotPresent# 镜像拉取策略command:[/bin/sh]# 覆盖容器默认的ENTRYPOINTargs:[-c,echo hello]# 覆盖容器默认的CMDports:# 暴露的端口声明性不实际开端口-name:httpcontainerPort:8080protocol:TCP-name:metricscontainerPort:9113protocol:TCPenv:# 环境变量-name:ENVIRONMENTvalue:production-name:DB_HOSTvalueFrom:# 从ConfigMap/Secret取值configMapKeyRef:name:app-configkey:db.hostresources:# 资源需求关键requests:# 调度时的保底需求memory:256Micpu:250mlimits:# 运行时的上限memory:512Micpu:500mvolumeMounts:# 挂载卷-name:app-datamountPath:/data-name:app-configmountPath:/etc/configreadOnly:truelivenessProbe:# 存活探针容器挂了会自动重启httpGet:path:/healthzport:8080initialDelaySeconds:30periodSeconds:10readinessProbe:# 就绪探针准备好才接入流量httpGet:path:/readyport:8080initialDelaySeconds:5periodSeconds:54.2 imagePullPolicy——“什么时候拉镜像”策略值行为适用场景Always每次都拉最新镜像开发环境、latest标签IfNotPresent本地有了就不拉生产环境推荐省带宽也快Never永远不拉只读本地离线环境、预加载镜像要点如果镜像标签是:latestK8s默认用Always策略。但生产环境请一定用具体版本标签配合IfNotPresent——你不想半夜三点线上因为某个节点重新拉了latest镜像而全挂吧我见过这种血案。4.3 resources——“容器能占多少CPU和内存”这是生产环境中最容易被忽略又最重要的配置。不加resources等于无限——一个容器就能撑爆整个Node。resources:requests:# 我需要至少这么多memory:256Mi# Mi Mebibyte (1024*1024字节)cpu:250m# m millicore, 250m 0.25核limits:# 我最多只能吃这么多memory:512Micpu:500m# 500m 0.5核【requests 和 limits 的区别】 requests请求值: limits限制值: ┌────────────────────┐ ┌────────────────────┐ │ 调度时保证给我的 │ │ 运行时不允许超过的 │ │ │ │ │ │ Scheduler 用这个值 │ │ 超过 CPU limit → │ │ 来决定Pod放哪个节点 │ │ 被限速throttle │ │ │ │ │ │ 0.25核 │ │ 超过 memory limit → │ │ ┌──┐ │ │ OOMKilled被杀 │ │ │ │ │ │ │ │ └──┘ │ │ 0.5核 │ │ 保证有这么多 │ │ ┌────┐ │ │ │ │ │ │ │ │ │ │ └────┘ │ └────────────────────┘ │ 最多吃这么多 │ └────────────────────┘要点CPU超限只是被限速不会杀容器但内存超限会直接OOMKilled——容器被杀、Pod重启。所以内存的requests和limits最好设成一样CPU可以留点弹性。不加resources的习惯不改线上迟早要出事。4.4 ports——“声明我用了哪些端口”ports字段本质上是声明性的文档不真的打开端口。它的价值在于ports:-name:http# 给端口起个名Service可以按名引用containerPort:8080# 容器监听的端口protocol:TCP# TCP或UDP默认TCPhostPort:30080# 【慎用】在宿主机监听通常不设要点hostPort会让Pod绑定到特定Node的端口导致这个Pod只能调度到该Node上端口冲突跟K8s的调度灵活性背道而驰。99%的情况下你应该用Service而不是hostPort。4.5 volumes——“Pod的存储空间”spec:volumes:# 定义卷-name:app-dataemptyDir:{}# 临时卷Pod删了就没了-name:app-configconfigMap:# 从ConfigMap挂载name:my-config-name:secret-volumesecret:# 从Secret挂载secretName:my-secretcontainers:-name:myappimage:myapp:latestvolumeMounts:# 容器内挂载-name:app-datamountPath:/data-name:app-configmountPath:/etc/configreadOnly:true4.6 restartPolicy——“Pod挂了怎么办”策略行为适用场景Always容器退出后总是重启默认值适合常驻服务OnFailure只有非0退出才重启适合Job/批处理Never从不重启适合一次性任务五、kubectl explain——你最好的YAML老师记不住字段不想翻文档kubectl explain就是你的内置文档# 查看Pod的顶层字段kubectl explain pod# 查看Pod.spec的字段kubectl explain pod.spec# 一直深入下去kubectl explain pod.spec.containers kubectl explain pod.spec.containers.resources kubectl explain pod.spec.containers.resources.requests kubectl explain pod.spec.volumes.emptyDir# 递归展开所有字段kubectl explain pod--recursive【kubectl explain 的输出解读】 kubectl explain pod.spec.containers.resources KIND: Pod VERSION: v1 RESOURCE: resources Object DESCRIPTION: Compute Resources required by this container. FIELDS: limits map[string]string Limits describes the maximum amount of compute resources allowed. requests map[string]string Requests describes the minimum amount of compute resources required. # 字段类型说明 # Object 嵌套对象可以继续 explain # string 字符串 # integer 整数 # boolean true/false # []Object 对象数组比如 containers 就是 []Object # map[string]string 键值对映射 # -required- 必填字段要点养成习惯——写任何K8s YAML之前先kubectl explain一下对应的资源类型。比Google快比文档准而且是跟你的集群版本完全一致的文档。我写K8s五年了到现在还在用这个命令。六、一个完整的Pod YAML实战——从零到跑起来把上面的知识串起来写一个生产环境级别的Pod配置apiVersion:v1kind:Podmetadata:name:nginx-productionnamespace:defaultlabels:app:nginxversion:1.25tier:frontendenvironment:productionannotations:prometheus.io/scrape:trueprometheus.io/port:9113description:Production nginx pod with sidecarspec:# 容器列表containers:# 主容器nginx-name:nginximage:nginx:1.25imagePullPolicy:IfNotPresentports:-name:httpcontainerPort:80protocol:TCP-name:httpscontainerPort:443protocol:TCP# 环境变量env:-name:NGINX_HOSTvalue:example.com-name:NGINX_PORTvalue:80# 资源限制建议一定要设resources:requests:memory:128Micpu:100mlimits:memory:256Micpu:500m# 挂载卷volumeMounts:-name:nginx-configmountPath:/etc/nginx/conf.dreadOnly:true-name:nginx-logsmountPath:/var/log/nginx# 存活探针livenessProbe:httpGet:path:/healthzport:80initialDelaySeconds:30periodSeconds:10timeoutSeconds:3failureThreshold:3# 就绪探针readinessProbe:httpGet:path:/readyport:80initialDelaySeconds:5periodSeconds:5timeoutSeconds:3failureThreshold:2# Sidecar容器Prometheus exporter-name:nginx-exporterimage:nginx/nginx-prometheus-exporter:1.0imagePullPolicy:IfNotPresentports:-name:metricscontainerPort:9113args:--nginx.scrape-urihttp://localhost/statusresources:requests:memory:32Micpu:50mlimits:memory:64Micpu:100m# 卷定义volumes:-name:nginx-configconfigMap:name:nginx-conf-name:nginx-logsemptyDir:{}# 重启策略restartPolicy:Always# DNS配置可选dnsPolicy:ClusterFirst# 优雅终止时间可选默认30秒terminationGracePeriodSeconds:30# 应用这个配置kubectl apply-fnginx-production.yaml# 验证Pod状态kubectl get pod nginx-production-owide# 看详细信息kubectl describe pod nginx-production# 如果有问题看日志kubectl logs nginx-production-cnginx kubectl logs nginx-production-cnginx-exporter# 指定容器# 进入容器调试kubectlexec-itnginx-production-cnginx -- /bin/bash要点写完YAML后先用kubectl apply --dry-runclient -f your-pod.yaml做一次干运行只验证不实际创建避免YAML格式错误导致问题。七、必填字段速查表——Pod的最小配置路径必填否如果不填会怎样apiVersion是直接报错kubectl不知道用哪个API版本kind是直接报错kubectl不知道创建什么资源metadata.name是直接报错资源没名字spec.containers是直接报错Pod里至少得有一个容器spec.containers[].name是直接报错spec.containers[].image是直接报错没镜像名K8s不知道拉什么metadata.namespace否默认defaultmetadata.labels否空但不推荐影响后续选择和管理spec.containers[].ports否不声明也能跑但不声明无法被Service发现spec.containers[].resources否强烈建议填——不填等于无限影响调度spec.restartPolicy否默认Always本篇小结Pod YAML看起来字段多但骨架就五段其中你只需要操心前四段apiVersion kind告诉K8s你要创建什么类型的资源用哪个API版本来操作metadata给Pod起名字、贴标签、写注释——管理维度的事归这里spec描述Pod的肉身——什么镜像、多少资源、挂什么卷、什么探针statusK8s自动填的你看就行别动手写重点记住两个习惯写YAML前kubectl explain pod --recursive扫一遍写完后kubectl apply --dry-runclient干跑验证一下下一篇咱们聊Label和Selector——这两个东西看着简单但它们是K8s里灵活编排的根基没有Label你连灰度发布都搞不定。上一篇【第08篇】下一篇【第10篇】Label和Selector——K8s的贴标签艺术