
在之前的章节中,我们已经通过 YAML 文件部署了应用程序。
你可能已经注意到,这些 YAML 文件虽然看起来相似,但各自的字段和结构都有其特定的含义。本章将带你深入探索 Kubernetes YAML 配置文件的奥秘。
我们将不再仅仅是复制代码,而是逐行解析你已经使用过的 K8s 资源(如 Pod、Deployment、StatefulSet、Service、ConfigMap、Secret、PVC)的 YAML 结构,详细解释每个核心属性的含义、作用以及它们如何协同工作,让你真正理解“声明式”管理的精髓。
通过本章的学习,你将能够:
所有 Kubernetes 资源的 YAML 文件都遵循一个通用的顶级结构。理解这四个顶级字段是解析任何 K8s YAML 的起点。
apiVersion (API 版本)
group/version 的形式(例如 apps/v1)。对于属于 core API 组的核心对象(如 Pod, Service, ConfigMap, Secret, PVC, Namespace 等),其组名被省略,直接写作 v1。apiVersion 可能对应不同的字段或行为。v1:用于 Pod, Service, ConfigMap, Secret, PVC, Namespace 等核心对象。apps/v1:用于 Deployment, StatefulSet, DaemonSet, ReplicaSet 等控制器。networking.k8s.io/v1:用于 Ingress 等网络相关对象。kind (对象类型)
kind 字段来调用对应的控制器处理这个对象。Pod, Deployment, Service, StatefulSet, ConfigMap, Secret, PersistentVolumeClaim。metadata (元数据)
metadata 字段。kubernetes.io/change-cause: "Upgraded app to v2.0", checksum/config: "abcdef123"。app: redis-cluster, env: production, tier: frontend。kubectl get pods -l app=redis-cluster。default 命名空间。default, kube-system, my-app-ns。redis-cluster-config, postgres-deployment。name:namespace:labels:annotations:apiVersion: <API版本>
kind:<对象类型>
metadata:
name:<对象名称>
namespace:<命名空间,可选>
labels:
<键>:<值>
annotations:
<键>:<值>
spec:
# 此处是该对象类型特有的配置,定义期望状态
# 例如,Deployment 会有 replicas, selector, template 等
# Pod 会有 containers, volumes 等
# Service 会有 ports, selector, type 等
status:
# 此处是 K8s 自动填充的实际状态,不需手动配置
spec (规范/期望状态)
kind 特有的,并且包含了该资源的大部分配置细节。spec 中定义的期望状态进行比对,并采取行动使其达到一致。Deployment 的 spec 包含 replicas 和 template;Service 的 spec 包含 ports 和 selector;Pod 的 spec 包含 containers 和 volumes 等。status (实际状态)
kubectl describe 命令可以看到 status 字段的详细内容。Pod 是 Kubernetes 中最小的可部署和可调度的计算单元。理解 Pod 的 YAML 配置是理解其他控制器(如 Deployment, StatefulSet)的基础,因为它们都包含一个 Pod 模板。
apiVersion: v1
kind:Pod
metadata:
name:my-app-pod
labels:
app:my-app
spec:
containers:# 容器列表
-name:my-app-container
image:my-app:v1.0
imagePullPolicy:IfNotPresent# 镜像拉取策略
ports:
-containerPort:8080
name:http-port
# hostPort: 8080 # (可选) 将容器端口映射到宿主机端口,不推荐在生产环境使用
env:# 环境变量
-name:ENV_VAR_NAME
value:"env_var_value"
-name:DB_PASSWORD
valueFrom:# 值来源于其他 K8s 资源
secretKeyRef:# 从 Secret 中获取
name:my-secret
key:password
volumeMounts:# 卷挂载点
-name:my-data-volume
mountPath:/app/data
readOnly:false
resources:# 资源请求与限制
requests:# 资源请求,用于调度
memory:"64Mi"
cpu:"250m"# 0.25 核
limits:# 资源限制,防止过度使用
memory:"128Mi"
cpu:"500m"# 0.5 核
livenessProbe:# 活性探针,判断容器是否活着
httpGet:
path:/healthz
port:8080
initialDelaySeconds:5
periodSeconds:5
readinessProbe:# 就绪探针,判断容器是否准备好接收流量
httpGet:
path:/ready
port:8080
initialDelaySeconds:5
periodSeconds:5
volumes:# Pod 可用的卷定义
-name:my-data-volume
persistentVolumeClaim:# 使用 PVC
claimName:my-pvc
-name:my-config-volume
configMap:# 使用 ConfigMap
name:my-app-config
nodeSelector:# 节点选择器
disktype:ssd
tolerations:# 容忍度,允许 Pod 调度到有污点(Taints)的节点
-key:"app"
operator:"Exists"# 匹配存在 app 这个 key 的所有污点
effect:"NoSchedule"
-key:"hardware"
operator:"Equal"# 匹配 key 为 hardware 且 value 为 gpu 的污点
value:"gpu"
effect:"NoExecute"
restartPolicy:Always# Pod 重启策略
spec.containers (容器列表)
name: 容器的名称,在 Pod 内部必须是唯一的。image: 容器使用的镜像名称(例如 nginx:latest, my-app:v1.0)。imagePullPolicy:IfNotPresent (默认值): 如果本地已存在该镜像,则不拉取;否则拉取。Always: 每次启动 Pod 时都尝试拉取最新镜像。Never: 永不拉取镜像,只使用本地已存在的镜像。ports:containerPort: 容器内部应用程序监听的实际端口号。name: 端口的名称,方便在 Service 中引用。hostPort: (可选) 直接将容器的端口映射到 Node 上的对应端口。不推荐在生产环境中使用,因为它可能导致端口冲突并限制 Pod 的调度。env (环境变量):configMapKeyRef: 从 ConfigMap 中获取某个键的值。secretKeyRef: 从 Secret 中获取某个键的值(常用于密码)。fieldRef: 获取 Pod 自身的某个字段值,如 metadata.name (Pod 名称)、status.podIP (Pod IP)。resourceFieldRef: 获取容器的资源请求或限制值,如 limits.cpu。name: ConfigMap 的名称。key: ConfigMap 中键的名称。name: Secret 的名称。key: Secret 中键的名称。name: 环境变量的名称。value: 环境变量的静态值。valueFrom:volumeMounts (卷挂载点):spec.volumes 中定义)在容器文件系统中的挂载位置。name: 必须与 spec.volumes 中定义的卷的 name 匹配。mountPath: 卷在容器内部的绝对路径。readOnly: (可选) 如果为 true,则卷以只读模式挂载。resources (资源请求与限制):cpu: CPU 使用的上限。如果容器尝试使用超过限制的 CPU,可能会被 K8s 限制或扼流。memory: 内存使用的上限。如果容器尝试使用超过限制的内存,K8s 可能会终止该容器(OOMKilled),并可能触发 Pod 重启。cpu: CPU 请求量,单位是 millicores (毫核,1 核 = 1000m)。例如 250m 表示 0.25 个 CPU 核。memory: 内存请求量,单位是字节(例如 Mi - 兆字节,Gi - 吉字节)。例如 64Mi。requests (请求): 容器启动所需的最小资源量。K8s 调度器会根据这些请求将 Pod 放置到有足够资源的 Node 上。limits (限制): 容器可以使用的最大资源量。livenessProbe (活性探针):httpGet: 发送 HTTP GET 请求。tcpSocket: 尝试建立 TCP 连接。exec: 在容器内执行命令。initialDelaySeconds: 容器启动后首次执行探针的延迟时间。periodSeconds: 探针执行的频率。readinessProbe (就绪探针):livenessProbe。spec.volumes (卷定义)
volumeMounts)。name: 卷的名称,用于在 volumeMounts 中引用。Secret 中的数据作为文件挂载到 Pod 内部。secretName: 要引用的 Secret 的名称。ConfigMap 中的数据作为文件挂载到 Pod 内部。name: 要引用的 ConfigMap 的名称。PersistentVolumeClaim,实现持久化存储。claimName: 要引用的 PVC 的名称。persistentVolumeClaim:configMap:secret:emptyDir: 临时目录,随 Pod 生命周期而存在,Pod 删除时数据丢失。hostPath: 将 Node 上的文件或目录挂载到 Pod 中(不推荐生产环境使用,除非特殊需要)。spec.nodeSelector (节点选择器)
disktype: ssd 会将 Pod 调度到带有 disktype=ssd 标签的 Node。spec.tolerations (容忍度)
key=value:NoSchedule,Pod 需要有对应的 toleration 才能被调度到该 Node。spec.restartPolicy (重启策略)
Always (默认值): 容器终止后总是重启。通常用于长期运行的服务(Deployment)。OnFailure: 只有当容器以非零退出码(表示失败)终止时才重启。Never: 容器终止后永不重启。通常用于一次性任务(Job)。Deployment 和 StatefulSet 是 Kubernetes 中最常用的两种工作负载控制器,它们负责管理 Pod 的生命周期,确保运行指定数量的 Pod 副本。虽然它们都是管理 Pod 的,但由于其设计目标不同,在 YAML 配置上也有一些关键区别。
# Deployment 示例 YAML
apiVersion:apps/v1
kind:Deployment
metadata:
name:my-app-deployment
labels:
app:my-app
spec:
replicas:3# 期望的 Pod 副本数量
selector:# Pod 选择器,识别 Deployment 管理的 Pod
matchLabels:
app:my-app
template:# Pod 模板,用于创建新的 Pod
metadata:
labels:
app:my-app
spec:
containers:
-name:my-app-container
image:my-app:v1.0
strategy:# 更新策略
type:RollingUpdate# 滚动更新
rollingUpdate:
maxUnavailable:25%# 更新时允许不可用的 Pod 数量
maxSurge:25% # 更新时允许超出期望数量的 Pod 数量
# StatefulSet 示例 YAML
apiVersion:apps/v1
kind:StatefulSet
metadata:
name:my-stateful-app
labels:
app:my-stateful-app
spec:
serviceName:my-stateful-app-headless# 关联的 Headless Service 名称
replicas:3# 期望的 Pod 副本数量
selector:# Pod 选择器,识别 StatefulSet 管理的 Pod
matchLabels:
app:my-stateful-app
template:# Pod 模板,用于创建新的 Pod
metadata:
labels:
app:my-stateful-app
spec:
containers:
-name:my-stateful-app-container
image:my-stateful-app:v1.0
volumeMounts:
-name:my-data-volume
mountPath:/data
volumeClaimTemplates:# 卷声明模板,为每个 Pod 自动创建 PVC
-metadata:
name:my-data-volume
spec:
accessModes:["ReadWriteOnce"]
resources:
requests:
storage:1Gi
spec.replicas (副本数量)
replicas 值保持一致。如果你将 replicas 从 3 改为 5,K8s 会自动创建两个新 Pod。replicas: 3 表示你希望有 3 个应用程序 Pod 正在运行。spec.selector (选择器)
matchLabels)来匹配 Pod 的标签。matchLabels:spec.template.metadata.labels) 中的标签必须与此处的 matchLabels 完全匹配,否则控制器将无法找到或管理这些 Pod。selector: { matchLabels: { app: my-app } } 意味着该控制器将管理所有带有 app: my-app 标签的 Pod。spec.template (Pod 模板)
metadata 和 spec 部分完全相同。template.metadata.labels:spec.selector.matchLabels 字段中定义的所有标签(可以包含更多标签)。这是控制器识别其管理的 Pod 的关键。template.spec:Deployment 特有属性:
spec.strategy (更新策略)RollingUpdate (默认值):Recreate:maxUnavailable:maxSurge:25% 意味着在更新过程中,最多有 25% 的 Pod 副本可以处于不可用状态。spec.replicas)的 Pod 最大数量(可以是百分比或绝对数量)。25% 意味着在更新期间,K8s 最多可以创建比 replicas 多 25% 的新 Pod。rollingUpdate: 进一步配置滚动更新行为。Deployment 的 Pod 模板(spec.template)发生变化时,K8s 如何更新现有 Pod。type:StatefulSet 特有属性:
spec.serviceName (关联的 Headless Service 名称)redis-0.redis-service.default.svc.cluster.local。这对于有状态应用的发现和互联至关重要。serviceName: my-stateful-app-headless。spec.volumeClaimTemplates (卷声明模板)PersistentVolumeClaim (PVC)。volumeClaimTemplates 中定义的每个 PVC 模板,都必须在 spec.template.spec.containers[*].volumeMounts 中被至少一个容器挂载,StatefulSet 控制器才会为每个 Pod 实例创建对应的 PVC。metadata.name: 在 PVC 模板中,这个 name 会作为实际创建的 PVC 名称的一部分(例如 my-data-volume-my-stateful-app-0)。spec: 此处与 6.6 节将详细解析的 PVC 的 spec 完全相同,定义了每个 Pod 请求的存储容量、访问模式和 StorageClass 等。通过理解 Deployment 和 StatefulSet 的这些核心属性,你就能更好地控制应用程序的部署、更新和扩缩容行为,特别是对于有状态应用,StatefulSet 提供的稳定性和有序性是其独特的优势。
Service 是 Kubernetes 中实现服务发现和负载均衡的核心抽象。它定义了一组 Pod 的逻辑集合以及访问这些 Pod 的策略。无论 Pod 如何创建、销毁或移动,Service 都为客户端提供一个稳定的网络入口。
apiVersion: v1
kind:Service
metadata:
name:my-app-service
labels:
app:my-app
spec:
selector:# 后端 Pod 选择器
app:my-app
ports:# 端口映射列表
-protocol:TCP# 协议类型
port:80 # Service 监听的端口
targetPort:8080# 后端 Pod 容器监听的端口
nodePort:30080# (可选,仅当 type 为 NodePort 时有效) Node 上暴露的端口
name:http # 端口名称
type:ClusterIP# Service 类型
clusterIP:10.96.0.10# (可选,仅当 type 为 ClusterIP 时有效) Service 的虚拟 IP
spec.selector (后端 Pod 选择器)selector 与任何 Pod 的 metadata.labels 都不匹配,那么 Service 将没有后端 Pod,也就无法提供服务。selector: { app: my-app } 表示该 Service 将把所有带有 app: my-app 标签的 Pod 作为其后端。spec.ports (端口映射列表)name: http, name: grpc。NodePort 时,此字段指定 K8s 在集群中每个 Node 上打开的端口号。nodePort 来访问 Service。30000-32767 范围内的端口作为 NodePort。集群管理员可以配置此范围。nodePort: 30080。port 转发到后端 Pod 的 targetPort。targetPort: 8080。ports 字段中定义了 name,这里也可以直接引用端口名称,例如 targetPort: http-server。使用名称的好处是,如果容器实际监听的端口号发生变化,只要名称不变,Service 就无需修改。8080 端口监听 HTTP 请求,那么这里就设置为 targetPort: 8080。port: 80 表示 Service 在端口 80 上监听请求。TCP (默认), UDP, SCTP。protocol:port:targetPort:nodePort (仅当 type: NodePort 时有效)name:spec.type (Service 类型)ClusterIP (默认值):NodePort:LoadBalancer:ExternalName:ClusterIP 功能外,还在集群的每个 Node 上打开一个静态端口。spec.clusterIP (Service 的虚拟 IP,仅当 type: ClusterIP 或 NodePort 时有效)LoadBalancer 类型的 Service,通常不会显式设置 clusterIP,它会自动分配一个,并且外部流量主要通过负载均衡器的 IP 访问。对于 ExternalName Service,此字段始终为空。通过对 Service YAML 属性的深入理解,你现在应该能够根据你的应用程序需求,灵活选择和配置合适的 Service 类型,并精确控制其网络行为。
ConfigMap 和 Secret 都是 Kubernetes 中用于存储和分发配置数据的方式。它们的关键区别在于,ConfigMap 用于存储非敏感的配置数据,而 Secret 则专为存储敏感数据而设计。尽管如此,它们在结构和使用方式上有很多相似之处。
ConfigMap 允许你将配置数据与应用程序镜像解耦,从而提高应用的可移植性和可管理性。
apiVersion: v1
kind:ConfigMap
metadata:
name:my-app-config
data:# 键值对数据
config.yaml:|# 完整文件内容作为值
server:
port:8080
database:
host:postgres-service
name:my_database
log_level:DEBUG# 单个键值对
feature_flag:"true"
data (键值对数据)
key: value。值可以是任何字符串。| (保留换行符) 或 > (折叠换行符) 来表示。这在存储完整的配置文件(如 config.yaml 或 nginx.conf)时非常有用。ConfigMap 中的数据是明文存储的,不应包含任何敏感信息。Secret 用于存储敏感信息,例如密码、OAuth Token、SSH 密钥等。虽然 K8s 会对 Secret 中的数据进行 Base64 编码,但这并非加密,只是一种传输编码。为了真正的安全性,数据在 Etcd 中存储时可能需要额外的加密配置。
apiVersion: v1
kind:Secret
metadata:
name:my-app-secret
type:Opaque# Secret 的类型
data:# Base64 编码的键值对数据
username:dXNlcg==# 'user' 的 Base64 编码
password:cGFzc3dvcmQxMjM=# 'password123' 的 Base64 编码
stringData:# (可选) 方便地直接写入明文数据,K8s 会自动 Base64 编码
db_connection_string:"jdbc:postgresql://postgres-service:5432/mydb"
data (Base64 编码的键值对数据)data 字段时,每个值都必须是 Base64 编码后的字符串。Kubernetes API Server 不会自动对 data 下的值进行编码,如果直接写入明文会导致错误(如 cannot unmarshal string into Go struct field Secret.data of type []uint8)。echo -n 'your_plain_text' | base64 来生成。例如,echo -n 'mypassword' | base64 会输出 bXlwYXNzd29yZA==。value 是 Base64 编码后的结果,否则会遇到 cannot unmarshal number into Go struct field Secret.data of type []uint8 类似的错误。stringData (可选:明文字符串数据)stringData 字段仅用于在创建或更新 Secret 时提供明文输入。一旦 Secret 被创建或更新,stringData 字段的内容会被 Kubernetes API Server 转换为 Base64 编码格式并存储在 data 字段中。随后通过 kubectl get secret -o yaml 或其他 API 查询只能看到 data 字段(包含 Base64 编码值),stringData 字段本身在查询结果中是不可见的。stringData 中的字段在 Secret 创建后不会再显示在 kubectl get secret <name> -o yaml 的输出中,它们会被转换到 data 字段,且 data 字段的值是 Base64 编码的。type (Secret 类型)Opaque (默认值): 通用类型,用于存储任意用户定义的数据。这是最常用的类型。kubernetes.io/dockerconfigjson: 用于存储 Docker 镜像仓库的认证凭据(通常由 kubectl create secret docker-registry 命令创建)。kubernetes.io/service-account-token: Service Account 自动生成的 Token Secret。kubernetes.io/tls: 用于存储 TLS 证书和私钥(通常由 kubectl create secret tls 命令创建)。一旦 ConfigMap 或 Secret 被创建,它们可以通过以下两种主要方式注入到 Pod 的容器中:
spec.containers.env 字段中使用 valueFrom 引用 configMapKeyRef 或 secretKeyRef。spec.volumes 中定义一个 configMap 或 secret 类型的卷。spec.containers.volumeMounts 中将这个卷挂载到容器的特定路径。理解 ConfigMap 和 Secret 的这些属性及其注入方式,是你安全有效地管理应用程序配置和敏感数据的关键。
PersistentVolumeClaim (PVC) 是用户向 Kubernetes 请求持久化存储的一种声明。它抽象了底层存储的细节,允许 Pod 简单地“声明”所需的存储,而不必关心具体的存储实现(如 NFS、云盘、iSCSI 等)。PVC 会与一个 PersistentVolume (PV) 绑定,而 PV 则代表了集群中实际的物理存储资源。
apiVersion: v1
kind:PersistentVolumeClaim
metadata:
name:my-app-pvc
labels:
app:my-app
spec:
accessModes:# 访问模式
-ReadWriteOnce# 只允许单个节点读写
resources:# 资源请求
requests:
storage:5Gi# 请求 5 GB 存储空间
storageClassName:standard-ssd# 引用的 StorageClass 名称
selector:# (可选) 用于选择特定 PV 的标签选择器
matchLabels:
tier:premium
matchExpressions:
-{key:environment,operator:In,values:[dev,prod]}
spec.accessModes (访问模式)ReadWriteOnce (RWO):卷可以被单个节点以读写方式挂载。这是最常见的模式,适用于大多数单 Pod 应用程序(如我们的 PostgreSQL)。ReadOnlyMany (ROX):卷可以被多个节点以只读方式挂载。适用于多个 Pod 同时读取共享数据。ReadWriteMany (RWX):卷可以被多个节点以读写方式挂载。这通常需要特定的共享存储解决方案(如 NFS)。ReadWriteOncePod (RWOP): 卷可以被单个 Pod 以读写方式挂载(Kubernetes 1.22+ Beta, 1.27+ GA)。这比 RWO 更严格,确保即使 Node 上有多个 Pod 也能保证只有一个 Pod 使用该卷。spec.resources.requests.storage (请求的存储空间)StorageClass 动态供应)创建一个指定大小的存储卷。Gi (吉字节), Mi (兆字节) 等。storage: 5Gi 表示请求 5 吉字节的存储空间。spec.storageClassName (引用的 StorageClass 名称)storageClassName 被指定时,K8s 会尝试使用该 StorageClass 定义的规则来动态供应(即按需创建)一个 PV。StorageClass 并且 PVC 未指定 storageClassName,那么 K8s 也会使用默认的 StorageClass 进行动态供应。storageClassName: "" (空字符串),则表示 PVC 不希望使用动态供应,而希望绑定到没有任何 StorageClass 的现有 PV。这通常用于静态供应场景。StorageClass 的名称。StorageClass 是实现 K8s 存储自动化和多样化的关键。它定义了存储的提供者(如云提供商的 EBS、GCE Persistent Disk)、性能层级(如 SSD、HDD)和回收策略等。spec.selector (可选:用于选择特定 PV 的标签选择器)storageClassName 和 selector,那么只有当两者都能满足条件时,PVC 才会绑定。通常在动态供应场景下,selector 不会使用。通过对 PVC YAML 属性的深入理解,你现在能够更清晰地声明你的应用程序对持久化存储的需求,并利用 K8s 的存储自动化能力。