Kubernetes 集群声明式文件 YAML 深度解析:从语法基础到资源对象描述全攻略

摘要:本文从 YAML 语法规范出发,深入剖析 Kubernetes 声明式资源管理的核心机制,涵盖数据类型映射、多文档流、锚点引用、apiVersion 版本协商、GVK/GVR 模型、资源对象四层结构(TypeMeta / ObjectMeta / Spec / Status)以及声明式与命令式的本质差异,并结合生产级示例揭示 kubectl apply 的三路合并(three-way merge)策略与 Server-Side Apply 的演进。适合希望系统性掌握 Kubernetes 资源编排原理的中高级工程师。


一、YAML 语法深度解析

1.1 YAML 本质与设计哲学

YAML(YAML Ain’t Markup Language)是一种以数据为中心的序列化格式,其设计目标是人类可读性最大化。与 JSON 和 XML 不同,YAML 使用缩进而非括号或标签来表达层级结构,这一设计直接影响了 Kubernetes 声明式文件的书写范式。

YAML 1.2 规范(2009 年发布)明确了 YAML 是 JSON 的超集——任何合法的 JSON 文档都是合法的 YAML 文档。Kubernetes 的 API Server 同时接受 JSON 和 YAML 格式,但在人工编写场景下 YAML 凭借其简洁性成为事实标准。

1.2 基础数据类型与映射规则

YAML 定义了三种基本数据结构,它们与 Kubernetes API 对象的 Go 结构体之间存在精确的映射关系:

1.2.1 映射(Mapping)—— 对应 Go struct / map
# 块风格映射(Block Mapping)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: production
  labels:
    app: nginx
    tier: frontend
    version: v1.21.6

# 流风格映射(Flow Mapping)—— 等效写法
metadata: {name: nginx-deploy, namespace: production}

技术细节:YAML 中的 key 遵循以下规则:

  • 同一层级 key 不可重复,重复时后者覆盖前者(这是常见的配置错误来源)
  • key 的类型默认为字符串,无需引号
  • 包含特殊字符(: , #, {, }, [, ])的 key 必须用引号包裹
1.2.2 序列(Sequence)—— 对应 Go slice / array
# 块风格序列(Block Sequence)
spec:
  containers:
    - name: nginx
      image: nginx:1.21.6
      ports:
        - containerPort: 80
          protocol: TCP
        - containerPort: 443
          protocol: TCP
    - name: sidecar-log
      image: fluent-bit:2.1

# 流风格序列(Flow Sequence)
ports: [{containerPort: 80, protocol: TCP}, {containerPort: 443, protocol: TCP}]

关键注意:序列中每个元素以 - 开头,- 后的缩进空格是语法的一部分。混合使用 Tab 和 Space 是 YAML 解析失败的首要原因,Kubernetes 严格要求使用空格缩进

1.2.3 标量(Scalar)—— 对应 Go 基本类型
# 字符串(默认不需要引号)
name: nginx-deploy

# 需要引号的场景
annotation_value: "true"        # 防止被解析为布尔值
port_string: "8080"             # 防止被解析为整数
special: "hello: world"         # 包含冒号

# 多行字符串 —— 在 ConfigMap 中极为常见
data:
  nginx.conf: |                 # 字面量块(Literal Block):保留换行符
    server {
        listen 80;
        server_name localhost;
        location / {
            root /usr/share/nginx/html;
        }
    }
  init-script.sh: |+            # |+ 保留末尾换行
    #!/bin/bash
    echo "initializing..."
    
  one-liner: >                  # 折叠块(Folded Block):换行替换为空格
    this is a very long string
    that spans multiple lines
    but will be folded into one

1.3 YAML 类型自动推断陷阱

YAML 的隐式类型推断是 Kubernetes 配置中最常见的坑之一:

# 类型推断规则表
string_value: hello             # → string
integer_value: 42               # → int
float_value: 3.14               # → float
boolean_true: true              # → bool (true)
boolean_yes: yes                # → bool (true)  ⚠️ 注意!
boolean_on: on                  # → bool (true)  ⚠️ 注意!
null_value: ~                   # → null
null_value2: null               # → null
octal_value: 0o777              # → int (YAML 1.2)
octal_legacy: 0777              # → string (YAML 1.2) / int (YAML 1.1) ⚠️

# 科学计数法
scientific: 1.0e+3              # → float 1000.0

# 日期(YAML 1.1 支持,1.2 中为字符串)
date_value: 2024-01-15          # → date / string(取决于解析器版本)

# 安全写法:明确使用引号
env_value: "yes"                # → string "yes"
port: "8080"                    # → string "8080"
version: "1.0"                  # → string "1.0"(避免被截断为 1)

生产建议:在 env 中的 value 字段、ConfigMapdata 字段、labelsannotations 的值中,一律使用双引号显式标注字符串类型,避免隐式类型转换导致的运行时错误。

1.4 锚点与别名(Anchors & Aliases)

锚点机制允许在 YAML 中实现节点复用,在大型 Kubernetes 配置中可显著减少重复:

# 定义锚点
.default-resources: &default-resources
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

.default-probes: &default-probes
  livenessProbe:
    httpGet:
      path: /healthz
      port: 8080
    initialDelaySeconds: 30
    periodSeconds: 10
  readinessProbe:
    httpGet:
      path: /readyz
      port: 8080
    initialDelaySeconds: 5
    periodSeconds: 5

spec:
  containers:
    - name: app
      image: myapp:v1
      resources: *default-resources     # 引用锚点
      <<: *default-probes               # 合并键(Merge Key):展开映射
    - name: worker
      image: myworker:v1
      resources: *default-resources     # 复用同一资源配置

注意kubectl 在发送到 API Server 之前会完全展开锚点引用,API Server 侧不感知锚点语法。因此锚点仅是客户端层面的语法糖。

1.5 多文档流(Multi-Document Stream)

单个 YAML 文件中可包含多个文档,使用 --- 分隔:

# all-in-one.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: myapp
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: myapp
data:
  DATABASE_URL: "postgres://db:5432/myapp"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  replicas: 3
  # ... 省略
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  namespace: myapp
spec:
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080

执行 kubectl apply -f all-in-one.yaml 时,kubectl 按顺序逐个提交资源。在生产环境中,推荐将资源按依赖顺序排列:Namespace → RBAC → ConfigMap/Secret → Deployment → Service → Ingress。


二、Kubernetes 资源对象描述方法

2.1 声明式 vs 命令式:本质差异

Kubernetes 提供三种资源管理方式,理解它们的本质差异是正确使用 YAML 的前提:

维度 命令式命令 命令式对象配置 声明式对象配置
典型命令 kubectl run / kubectl expose kubectl create -f kubectl apply -f
操作语义 告诉系统"做什么" 告诉系统"创建这个" 告诉系统"确保状态是这样"
幂等性 ❌ 非幂等 ❌ 重复执行报错 ✅ 天然幂等
变更追踪 last-applied-configuration 注解
适用场景 调试、一次性任务 初始化部署 生产环境标准

声明式的核心思想:用户只需描述期望状态(Desired State),Kubernetes 的控制器(Controller)负责驱动**当前状态(Current State)**向期望状态收敛。这就是声明式 YAML 存在的根本意义。

2.2 API 版本与 GVK/GVR 模型

每个 Kubernetes 资源对象由 GVK(Group-Version-Kind) 唯一标识,对应 API 路径上的 GVR(Group-Version-Resource)

GVK: apps/v1/Deployment
      │    │  └── Kind(资源类型)
      │    └── Version(API 版本)
      └── Group(API 组)

GVR: apps/v1/deployments
      │    │  └── Resource(复数形式,用于 REST 路径)
      │    └── Version
      └── Group

对应 API 路径: /apis/apps/v1/namespaces/{ns}/deployments/{name}
API 版本成熟度
阶段 格式 含义 稳定性保证
Alpha v1alpha1 实验性功能,默认关闭 无,可能随时移除
Beta v1beta1 功能基本稳定,默认开启 较高,但字段可能变更
Stable v1 正式发布 完全向后兼容

查看集群支持的 API 版本

# 查看所有 API 资源及其版本
kubectl api-resources -o wide

# 查看特定资源支持的 API 版本
kubectl api-versions | grep apps

# 查看资源的详细字段说明(极为实用)
kubectl explain deployment.spec.strategy --api-version=apps/v1
kubectl explain pod.spec.containers --recursive

2.3 资源对象四层结构模型

每个 Kubernetes 资源对象的 YAML 描述遵循统一的四层结构,这是由 API Server 的反序列化机制决定的:

┌─────────────────────────────────────────┐
│         TypeMeta(类型元信息)            │  ← apiVersion + kind
├─────────────────────────────────────────┤
│         ObjectMeta(对象元信息)          │  ← metadata 块
├─────────────────────────────────────────┤
│         Spec(期望状态)                  │  ← 用户定义的期望状态
├─────────────────────────────────────────┤
│         Status(实际状态)                │  ← 控制器填充,用户不应编写
└─────────────────────────────────────────┘
2.3.1 TypeMeta —— 资源的身份证
apiVersion: apps/v1    # API 组/版本,决定了序列化/反序列化使用的 schema
kind: Deployment       # 资源类型,必须与 apiVersion 匹配

apiVersion 的解析规则:

  • 核心组(Core Group):直接写 v1,对应路径 /api/v1/。包括 Pod、Service、ConfigMap、Secret、Namespace 等
  • 命名组(Named Group):写 group/version,对应路径 /apis/{group}/{version}/。如 apps/v1batch/v1networking.k8s.io/v1
2.3.2 ObjectMeta —— 资源的 DNA
metadata:
  # === 核心标识字段 ===
  name: nginx-deploy                     # 资源名称(同 namespace 内唯一)
  namespace: production                  # 命名空间(集群级资源无此字段)
  
  # === 系统生成字段(不应手动填写)===
  uid: a1b2c3d4-e5f6-7890-abcd-ef1234567890  # 全局唯一 ID
  resourceVersion: "12345678"            # etcd 中的 modifiedIndex,乐观并发控制
  generation: 3                          # spec 变更计数器
  creationTimestamp: "2024-01-15T08:30:00Z"
  
  # === 标签(Labels)—— 用于筛选和分组 ===
  labels:
    app.kubernetes.io/name: nginx        # 推荐标签规范(k8s.io 推荐)
    app.kubernetes.io/instance: nginx-prod
    app.kubernetes.io/version: "1.21.6"
    app.kubernetes.io/component: webserver
    app.kubernetes.io/part-of: web-platform
    app.kubernetes.io/managed-by: helm
  
  # === 注解(Annotations)—— 用于存储非筛选元数据 ===
  annotations:
    kubernetes.io/change-cause: "update nginx to 1.21.6"
    prometheus.io/scrape: "true"
    prometheus.io/port: "9113"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",...}
  
  # === 终结器(Finalizers)—— 资源删除保护 ===
  finalizers:
    - kubernetes.io/pvc-protection
  
  # === 属主引用(OwnerReferences)—— 资源间父子关系 ===
  ownerReferences:
    - apiVersion: apps/v1
      kind: ReplicaSet
      name: nginx-deploy-7c8b9d6f4
      uid: xxx-yyy-zzz
      controller: true
      blockOwnerDeletion: true

Labels vs Annotations 的本质区别

维度 Labels Annotations
索引 存入 etcd 索引,支持 selector 查询 不建索引,不支持选择器
长度限制 key ≤ 63 字符,value ≤ 63 字符 value 可达 256KB
用途 资源筛选、Service 路由、调度约束 元数据存储、工具配置、审计信息
性能 高频查询优化 不适合高频查询
2.3.3 Spec —— 声明式的灵魂

Spec 是用户声明期望状态的核心区域,不同资源类型的 Spec 结构差异巨大。以 Deployment 为例进行深度拆解:

spec:
  # === 副本控制 ===
  replicas: 3                            # 期望副本数
  revisionHistoryLimit: 10               # 保留的历史 ReplicaSet 数量
  progressDeadlineSeconds: 600           # 部署进度超时时间
  
  # === 选择器(不可变字段,创建后不可修改)===
  selector:
    matchLabels:
      app: nginx
    matchExpressions:                    # 集合选择器(更灵活)
      - key: tier
        operator: In
        values: [frontend, web]
      - key: environment
        operator: NotIn
        values: [test]
  
  # === 更新策略 ===
  strategy:
    type: RollingUpdate                  # RollingUpdate | Recreate
    rollingUpdate:
      maxSurge: 25%                      # 滚动更新时最大超出副本比例
      maxUnavailable: 25%                # 滚动更新时最大不可用副本比例
  
  # === Pod 模板(核心中的核心)===
  template:
    metadata:
      labels:
        app: nginx                       # 必须匹配 selector
      annotations:
        checksum/config: "sha256:abc123" # 配置变更触发滚动更新技巧
    spec:
      # --- 调度策略 ---
      nodeSelector:
        kubernetes.io/os: linux
        disktype: ssd
      
      affinity:
        podAntiAffinity:                 # Pod 反亲和:分散部署
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: nginx
              topologyKey: kubernetes.io/hostname
      
      tolerations:                       # 容忍污点
        - key: "node-role.kubernetes.io/master"
          operator: "Exists"
          effect: "NoSchedule"
      
      # --- 安全上下文 ---
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 2000
        seccompProfile:
          type: RuntimeDefault
      
      # --- 服务账号 ---
      serviceAccountName: nginx-sa
      automountServiceAccountToken: false
      
      # --- 初始化容器 ---
      initContainers:
        - name: init-config
          image: busybox:1.36
          command: ['sh', '-c', 'cp /config-template/* /config/']
          volumeMounts:
            - name: config-template
              mountPath: /config-template
            - name: config
              mountPath: /config
      
      # --- 主容器 ---
      containers:
        - name: nginx
          image: nginx:1.21.6
          imagePullPolicy: IfNotPresent  # Always | IfNotPresent | Never
          
          # 端口声明(信息性,不做实际限制)
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          
          # 资源配额(QoS 类别的决定因素)
          resources:
            requests:                    # 调度依据,保证分配
              cpu: 100m                  # 100 millicores = 0.1 CPU
              memory: 128Mi
              ephemeral-storage: 1Gi
            limits:                      # 硬上限,超出触发 OOMKill/throttle
              cpu: 500m
              memory: 512Mi
              ephemeral-storage: 2Gi
          
          # 环境变量注入
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name       # Downward API
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: CPU_LIMIT
              valueFrom:
                resourceFieldRef:
                  containerName: nginx
                  resource: limits.cpu
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          
          envFrom:
            - configMapRef:
                name: app-config
              prefix: APP_                       # 添加前缀避免冲突
          
          # 健康检查三件套
          startupProbe:                          # 启动探针(1.20+ 推荐)
            httpGet:
              path: /healthz
              port: 8080
            failureThreshold: 30
            periodSeconds: 10
          
          livenessProbe:                         # 存活探针
            httpGet:
              path: /healthz
              port: 8080
              httpHeaders:
                - name: X-Health-Check
                  value: liveness
            initialDelaySeconds: 0               # startupProbe 存在时可设为 0
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          
          readinessProbe:                        # 就绪探针
            httpGet:
              path: /readyz
              port: 8080
            periodSeconds: 5
            successThreshold: 1
            failureThreshold: 3
          
          # 生命周期钩子
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "echo 'started' > /tmp/started"]
            preStop:                             # 优雅终止关键
              exec:
                command: ["/bin/sh", "-c", "nginx -s quit && sleep 10"]
          
          # 卷挂载
          volumeMounts:
            - name: config
              mountPath: /etc/nginx/conf.d
              readOnly: true
            - name: data
              mountPath: /usr/share/nginx/html
            - name: tmp
              mountPath: /tmp
      
      # --- 卷声明 ---
      volumes:
        - name: config
          configMap:
            name: nginx-config
            items:
              - key: default.conf
                path: default.conf
                mode: 0644
        - name: data
          persistentVolumeClaim:
            claimName: nginx-data-pvc
        - name: tmp
          emptyDir:
            sizeLimit: 100Mi
        - name: config-template
          configMap:
            name: nginx-template
      
      # --- 优雅终止 ---
      terminationGracePeriodSeconds: 60          # 默认 30s,根据业务调整
      
      # --- DNS 策略 ---
      dnsPolicy: ClusterFirst
      dnsConfig:
        options:
          - name: ndots
            value: "2"                           # 减少不必要的 DNS 查询
2.3.4 Status —— 控制平面的反馈

Status 由控制器自动填充,反映资源的实际运行状态:

# kubectl get deployment nginx-deploy -o yaml 查看
status:
  observedGeneration: 3          # 控制器已处理的最新 generation
  replicas: 3                    # 当前总副本数
  readyReplicas: 3               # 就绪副本数
  updatedReplicas: 3             # 已更新副本数
  availableReplicas: 3           # 可用副本数
  conditions:
    - type: Available
      status: "True"
      lastTransitionTime: "2024-01-15T08:35:00Z"
      lastUpdateTime: "2024-01-15T08:35:00Z"
      reason: MinimumReplicasAvailable
      message: "Deployment has minimum availability."
    - type: Progressing
      status: "True"
      lastTransitionTime: "2024-01-15T08:30:00Z"
      lastUpdateTime: "2024-01-15T08:35:00Z"
      reason: NewReplicaSetAvailable
      message: 'ReplicaSet "nginx-deploy-7c8b9d6f4" has successfully progressed.'

重要:用户编写 YAML 时绝不应该包含 status 段。声明式配置只关注期望状态(Spec),实际状态由系统维护。


三、kubectl apply 的三路合并原理

3.1 三路合并(Three-Way Strategic Merge Patch)

kubectl apply 是声明式管理的核心命令,其背后的合并策略远比表面看起来复杂:

┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│  Last Applied    │    │  Live Object     │    │  Local File      │
│  Configuration   │    │  (etcd 中的当前值) │    │  (用户本次提交)   │
│  (annotation 中) │    │                  │    │                  │
└────────┬─────────┘    └────────┬─────────┘    └────────┬─────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 │
                        ┌────────▼─────────┐
                        │  Three-Way Merge │
                        │  计算 Patch       │
                        └────────┬─────────┘
                                 │
                        ┌────────▼─────────┐
                        │  发送 Strategic   │
                        │  Merge Patch 到   │
                        │  API Server       │
                        └──────────────────┘

合并决策矩阵

Last Applied Live Object Local File 决策
A A A 无变更,跳过
A A B 用户修改,更新为 B
A B A 其他人/控制器修改,保留 B
A B B 双方都改为相同值,保留 B
A B C 冲突,以 Local File 为准更新为 C
A 新增字段 A
A A 用户删除,移除字段
A B 用户删除,移除字段(即使 Live 有变更)

3.2 Server-Side Apply(SSA)

Kubernetes 1.22 GA 的 Server-Side Apply 解决了客户端 apply 的多项局限:

# 启用 Server-Side Apply
kubectl apply -f deployment.yaml --server-side --field-manager=my-controller

# 查看字段属主
kubectl get deployment nginx-deploy -o yaml --show-managed-fields

SSA 引入了**字段所有权(Field Ownership)**概念:

managedFields:
  - manager: kubectl-client-side-apply
    operation: Apply
    apiVersion: apps/v1
    time: "2024-01-15T08:30:00Z"
    fieldsType: FieldsV1
    fieldsV1:
      f:spec:
        f:replicas: {}           # kubectl 拥有 replicas 字段
        f:template:
          f:spec:
            f:containers:
              k:{"name":"nginx"}:
                f:image: {}      # kubectl 拥有 image 字段
  - manager: hpa-controller
    operation: Update
    apiVersion: apps/v1
    time: "2024-01-15T09:00:00Z"
    fieldsType: FieldsV1
    fieldsV1:
      f:spec:
        f:replicas: {}           # HPA 也声明了对 replicas 的所有权

当多个 manager 声明同一字段的所有权时,需要通过 --force-conflicts 解决冲突。这在 HPA + Deployment 的 replicas 字段冲突场景中最为常见。


四、核心资源对象 YAML 速查

4.1 工作负载类

# === Pod(最小调度单元,生产中很少直接创建)===
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
spec:
  containers:
    - name: debug
      image: nicolaka/netshoot:latest
      command: ["sleep", "infinity"]

# === StatefulSet(有状态应用)===
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless        # 必须关联 Headless Service
  replicas: 3
  podManagementPolicy: OrderedReady  # OrderedReady | Parallel
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0                   # 分区更新:只更新序号 >= partition 的 Pod
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
  volumeClaimTemplates:              # 自动创建 PVC
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi

# === DaemonSet(每节点一个 Pod)===
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-exporter
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1              # DaemonSet 无 maxSurge
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      hostNetwork: true              # 使用宿主机网络
      hostPID: true
      containers:
        - name: node-exporter
          image: prom/node-exporter:v1.7.0
          ports:
            - containerPort: 9100
              hostPort: 9100
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi

# === Job / CronJob(批处理)===
apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"             # 每天凌晨 2 点
  concurrencyPolicy: Forbid          # Allow | Forbid | Replace
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  startingDeadlineSeconds: 200
  jobTemplate:
    spec:
      backoffLimit: 3                # 失败重试次数
      activeDeadlineSeconds: 3600    # 单次 Job 超时
      ttlSecondsAfterFinished: 86400 # 完成后自动清理
      template:
        spec:
          restartPolicy: OnFailure   # Job 中只能是 OnFailure 或 Never
          containers:
            - name: backup
              image: postgres:15
              command: ["pg_dump", "-h", "postgres-svc", "-U", "admin", "mydb"]

4.2 服务发现与网络

# === Service(四种类型)===
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  type: ClusterIP                    # ClusterIP | NodePort | LoadBalancer | ExternalName
  selector:
    app: myapp
  ports:
    - name: http
      port: 80                       # Service 端口
      targetPort: 8080               # Pod 端口(可使用命名端口)
      protocol: TCP
  sessionAffinity: ClientIP          # None | ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

---
# === Ingress ===
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: myapp-api-svc
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-frontend-svc
                port:
                  number: 80

4.3 配置与存储

# === ConfigMap ===
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:                                # 文本数据
  DATABASE_HOST: "postgres-svc"
  LOG_LEVEL: "info"
  config.yaml: |
    server:
      port: 8080
      timeout: 30s
    database:
      maxConnections: 100
binaryData:                          # 二进制数据(Base64 编码)
  logo.png: iVBORw0KGgoAAAANSUhEUgAA...
immutable: true                      # 不可变 ConfigMap(1.21+ GA,提升性能)

---
# === Secret ===
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque                         # Opaque | kubernetes.io/tls | kubernetes.io/dockerconfigjson
data:                                # Base64 编码
  username: YWRtaW4=                 # echo -n 'admin' | base64
  password: cGFzc3dvcmQxMjM=
stringData:                          # 明文(提交时自动 Base64 编码)
  connection-string: "postgres://admin:password123@db:5432/myapp"

---
# === PersistentVolumeClaim ===
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce                  # RWO | ROX | RWX | RWOP(1.22+)
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi
  volumeMode: Filesystem             # Filesystem | Block

4.4 RBAC 权限控制

# === ServiceAccount ===
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production
  annotations:
    # AWS IRSA
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/app-role"

---
# === Role(命名空间级别)===
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: production
rules:
  - apiGroups: [""]                  # 核心组
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list"]
    resourceNames: ["myapp"]         # 限定特定资源名

---
# === RoleBinding ===
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

五、生产级 YAML 编写规范与最佳实践

5.1 资源配额 QoS 等级

Pod 的 QoS 等级由 resources 配置自动决定,直接影响 OOM 时的驱逐优先级:

QoS 等级 条件 OOM 驱逐优先级
Guaranteed 所有容器均设置 requests = limits(CPU 和 Memory) 最低(最后被驱逐)
Burstable 至少一个容器设置了 requests 或 limits,但不满足 Guaranteed 条件 中等
BestEffort 没有任何容器设置 requests 和 limits 最高(最先被驱逐)

生产铁律:所有生产工作负载必须至少达到 Burstable 级别,核心服务必须达到 Guaranteed 级别。

5.2 安全加固清单

spec:
  # Pod 级别安全
  securityContext:
    runAsNonRoot: true               # 禁止 root 运行
    runAsUser: 65534                  # nobody
    runAsGroup: 65534
    fsGroup: 65534
    seccompProfile:
      type: RuntimeDefault           # 启用 seccomp
  
  # 容器级别安全
  containers:
    - name: app
      securityContext:
        allowPrivilegeEscalation: false  # 禁止提权
        readOnlyRootFilesystem: true     # 只读根文件系统
        capabilities:
          drop: ["ALL"]                  # 丢弃所有 Linux capabilities
          add: ["NET_BIND_SERVICE"]      # 按需添加
        privileged: false                # 禁止特权模式

5.3 镜像策略

containers:
  - name: app
    # ✅ 正确:使用完整的 SHA256 摘要
    image: myregistry.com/myapp@sha256:a1b2c3d4e5f6...
    
    # ✅ 可接受:使用明确的语义化版本标签
    image: myregistry.com/myapp:v1.2.3
    
    # ❌ 避免:使用 latest 标签
    image: myregistry.com/myapp:latest
    
    # 拉取策略
    imagePullPolicy: IfNotPresent    # 配合具体版本标签使用

5.4 标签规范

遵循 Kubernetes 推荐标签 规范:

metadata:
  labels:
    # 推荐标签
    app.kubernetes.io/name: myapp
    app.kubernetes.io/instance: myapp-production
    app.kubernetes.io/version: "1.2.3"
    app.kubernetes.io/component: api-server
    app.kubernetes.io/part-of: ecommerce-platform
    app.kubernetes.io/managed-by: helm
    
    # 自定义业务标签
    team: platform-engineering
    cost-center: "CC-12345"
    environment: production

六、YAML 调试与验证工具链

6.1 客户端验证

# 干运行(不实际提交,仅验证语法和 schema)
kubectl apply -f deployment.yaml --dry-run=client -o yaml

# 服务端干运行(经过 admission webhook 验证)
kubectl apply -f deployment.yaml --dry-run=server -o yaml

# 使用 kubectl diff 预览变更
kubectl diff -f deployment.yaml

6.2 静态分析工具

# kubeval —— 校验 YAML 是否符合 Kubernetes schema
kubeval deployment.yaml --kubernetes-version 1.28.0

# kubeconform —— kubeval 的高性能替代
kubeconform -kubernetes-version 1.28.0 -strict deployment.yaml

# kube-linter —— 安全和最佳实践检查
kube-linter lint deployment.yaml

# pluto —— 检测已废弃的 API 版本
pluto detect-files -d ./k8s/

6.3 explain 命令深度使用

# 递归查看所有字段
kubectl explain pod.spec --recursive | head -100

# 查看特定字段的详细说明
kubectl explain deployment.spec.strategy.rollingUpdate

# 指定 API 版本
kubectl explain ingress.spec --api-version=networking.k8s.io/v1

七、总结

Kubernetes 声明式 YAML 的本质是用结构化数据描述基础设施的期望状态。掌握 YAML 语法只是第一步,真正的技术深度在于理解:

  1. GVK/GVR 模型决定了资源的 API 路径和序列化方式
  2. **四层结构(TypeMeta / ObjectMeta / Spec / Status)**是所有资源对象的统一骨架
  3. 三路合并策略Server-Side Apply 是声明式管理的核心引擎
  4. QoS 等级、安全上下文、资源配额是生产环境 YAML 编写的安全底线
  5. 声明式配置应纳入 GitOps 工作流,实现版本控制、审计追踪和自动化交付

声明式不仅是一种配置格式,更是一种基础设施管理哲学:描述"是什么",而非"做什么"

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐