在之前的章节中,我们已经通过 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 的存储自动化能力。