Kubernetes核心概念深度解析:Pod/Service/Deployment/存储与配置全攻略
Kubernetes核心概念深度解析:Pod/Service/Deployment/存储与配置全攻略
内容摘要:本文是K8s核心系列的第六篇,系统讲解Kubernetes最核心的资源对象体系——Namespace/Label、Pod生命周期与资源管理、六大工作负载控制器、Service四种类型与kube-proxy代理模式、CoreDNS解析流程、ConfigMap/Secret、RBAC权限控制、PV/PVC/StorageClass存储体系、亲和性调度与YAML编写规范。文中所有示例均在本系列第五篇搭建的K8s 1.29集群(1 Master + 3 Worker)中实际创建验证,输出为真实kubectl结果。
实验环境:Kubernetes v1.29(kubeadm搭建,containerd + Flannel,Pod网段10.244.0.0/16)
目录
一、Namespace与Label:资源管理的两大基石
1.1 Namespace:逻辑隔离的"虚拟集群"
Namespace是K8s的多租户隔离手段,把物理集群划分成多个逻辑空间。注意它是逻辑隔离而非强隔离——不同Namespace的Pod默认网络互通,真正的隔离要配合NetworkPolicy和RBAC。
kubectl get ns # 查看系统默认命名空间
集群默认内置几个Namespace:
| Namespace | 用途 |
|---|---|
| default | 默认空间,不指定namespace时资源都创建在这里 |
| kube-system | K8s系统组件(CoreDNS、kube-proxy等) |
| kube-public | 公共可读空间,存放cluster-info |
| kube-node-lease | 节点租约对象,用于节点心跳检测 |
| kube-flannel | Flannel网络插件(本文集群) |
实战中建议按环境或团队划分:dev/staging/prod,或者team-a/team-b:
kubectl create namespace dev
kubectl -n dev get pods # 指定namespace操作
kubectl config set-context --current --namespace=dev # 切换默认namespace
1.2 Label:K8s的"万能标签"
Label是附加在资源对象上的键值对(如app=nginx、env=prod),是K8s中关联资源的纽带:Service通过Label Selector找到Pod,Deployment通过Label管理ReplicaSet,调度器通过Label做节点亲和。
# 打标签
kubectl label pod mypod env=prod tier=frontend
# 查看标签
kubectl get pods --show-labels
# 按标签筛选(这是日常运维最高频的操作)
kubectl get pods -l app=nginx
kubectl get pods -l 'app=nginx,env=prod' # 与
kubectl get pods -l 'env in (prod,staging)' # 集合匹配
Label设计建议:遵循K8s推荐的通用标签约定——app.kubernetes.io/name(应用名)、app.kubernetes.io/instance(实例)、app.kubernetes.io/version(版本)、app.kubernetes.io/component(组件)、app.kubernetes.io/managed-by(管理工具)。
与Label互补的是Annotation(注解):Label用于筛选和关联,Annotation用于存放非标识性的元数据(描述、负责人、构建信息等),值可以更长且不受字符限制。
二、Pod详解:最小调度单元
2.1 为什么K8s不直接调度容器
Pod是一组共享网络和存储的容器集合,是K8s调度的最小单位。同一个Pod内的容器:
-
共享Network Namespace:通过
localhost互相通信,共享同一个IP -
共享Volume:可通过emptyDir等卷交换数据
这个设计解决的是"强耦合进程组"的调度问题——比如主容器+日志采集Sidecar、应用+配置热更新Agent,它们必须同生共死、同机部署,用Pod抽象就顺理成章。
2.2 Pod生命周期
Pending → Running → Succeeded(正常退出,如Job)
│
└──────→ Failed(异常退出)
│
└──────→ Unknown(节点失联,状态未知)
-
Pending:已创建但未就绪,卡在调度(无可用节点)或镜像拉取阶段
-
Running:至少一个容器在运行
-
Succeeded/Failed:所有容器退出后的终态
-
CrashLoopBackOff:不是生命周期状态,而是容器反复崩溃重启的表现,用
kubectl logs和describe排查
排障套路:Pending看调度事件,ContainerCreating看镜像和网络,CrashLoopBackOff看容器日志。
2.3 镜像拉取策略(imagePullPolicy)
| 策略 | 行为 | 适用场景 |
|---|---|---|
| IfNotPresent | 本地有就不拉(默认,当tag非latest时) | 生产环境推荐,启动快 |
| Always | 每次都拉(默认,当tag=latest时) | 开发环境 |
| Never | 只用本地镜像,没有就失败 | 离线环境 |
生产建议:永远不要使用latest tag,用明确的版本号+IfNotPresent,配合镜像摘要(digest)可获得最强的一致性保证。
2.4 重启策略(restartPolicy)
-
Always(默认):容器退出就重启,配合指数退避(10s→20s→40s,上限5分钟) -
OnFailure:只有非零退出才重启(用于Job) -
Never:从不重启(用于一次性任务)
2.5 资源管理:requests与limits
这是生产环境Pod规范中最重要的一节:
resources:
requests: # 调度依据:节点剩余可分配资源 ≥ requests才会调度上来
cpu: "250m" # 0.25核
memory: "256Mi"
limits: # 运行上限:CPU超限被节流(throttle),内存超限被OOMKill
cpu: "500m"
memory: "512Mi"
由requests/limits衍生出QoS等级(优先级从高到低):
| QoS等级 | 条件 | 驱逐顺序 |
|---|---|---|
| Guaranteed | 每个容器requests==limits且都设置 | 最后被驱逐 |
| Burstable | 至少一个容器设置了requests | 中间 |
| BestEffort | 什么都没设 | 节点资源紧张时最先被驱逐 |
生产规范:所有Pod必须设置requests;CPU是否设limits有争议(设了会throttle影响延迟敏感业务),内存必须设limits防泄漏打爆节点。
本文集群实测:创建一个带requests/limits的Pod并查看其QoS等级。
$ cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-demo
labels:
app: nginx-pod
env: demo
spec:
containers:
- name: nginx
image: nginx:1.25
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
EOF
pod/nginx-pod-demo created
$ kubectl get pod nginx-pod-demo -o jsonpath='{.status.qosClass}'; echo
Burstable
requests≠limits,所以QoS是Burstable,与上表结论一致。
三、控制器详解:从Deployment到CronJob
直接创建Pod是"裸奔"——挂了没人管。控制器(Controller)通过控制循环保证实际状态=期望状态。六大工作负载对号入座:
| 控制器 | 适用场景 | 一句话特征 |
|---|---|---|
| Deployment | 无状态应用 | 副本+滚动更新+回滚 |
| ReplicaSet | (一般不直接用) | 保证副本数,由Deployment管理 |
| DaemonSet | 节点级守护进程 | 每节点一个副本(日志/监控Agent) |
| StatefulSet | 有状态应用 | 稳定身份+稳定存储+有序部署 |
| Job | 一次性任务 | 跑完即止 |
| CronJob | 定时任务 | 按cron表达式调度Job |
3.1 Deployment:无状态应用的标配
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
labels:
app: nginx
spec:
replicas: 3 # 期望副本数
revisionHistoryLimit: 5 # 保留的历史版本数(用于回滚)
strategy:
type: RollingUpdate # 滚动更新(另一个是Recreate先删后建)
rollingUpdate:
maxSurge: 1 # 更新时最多多出1个Pod
maxUnavailable: 0 # 更新时最少可用副本(0=不允许中断)
selector:
matchLabels:
app: nginx # 必须与template.labels一致,创建后不可改
template: # Pod模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
readinessProbe: # 就绪探针:未通过不接流量
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe: # 存活探针:未通过重启容器
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
Deployment的本质是三级对象链:Deployment → ReplicaSet → Pod。滚动更新就是创建新ReplicaSet并逐步扩容/缩容的过程,每次更新留下一个历史ReplicaSet,所以能秒级回滚。
常用运维命令:
kubectl rollout status deployment/nginx-deploy # 观察滚动更新
kubectl rollout history deployment/nginx-deploy # 历史版本
kubectl rollout undo deployment/nginx-deploy # 回滚上一版
kubectl rollout undo deployment/nginx-deploy --to-revision=2 # 回滚到指定版
kubectl scale deployment/nginx-deploy --replicas=5 # 扩缩容
3.2 ReplicaSet:理解即可
ReplicaSet只负责"保证N个Pod在跑",不直接管理更新策略。日常永远不要手动创建ReplicaSet,通过Deployment间接使用。认识它主要是为了看懂kubectl get rs的输出和排障。
3.3 DaemonSet:每个节点跑一份
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
tolerations: # 关键:容忍master污点才能在控制平面也部署
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: prom/node-exporter:v1.7.0
ports:
- containerPort: 9100
hostPort: 9100
典型场景:日志采集(Fluentd/Filebeat)、监控Agent(node-exporter)、网络插件(Flannel本身就是DaemonSet)。调度逻辑是"每个匹配节点一个Pod",节点加入集群自动扩容,移除自动回收。
3.4 StatefulSet:有状态应用的归宿
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: redis-headless # 必须关联Headless Service
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.2
ports:
- containerPort: 6379
volumeClaimTemplates: # 存储卷模板:每个Pod自动创建独立PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
与Deployment的本质区别:
-
稳定网络标识:Pod名为
redis-0、redis-1、redis-2,删了重建名字不变,配合Headless Service得到稳定DNS:redis-0.redis-headless -
有序部署与伸缩:按0→1→2顺序创建,按2→1→0逆序删除
-
独立存储:volumeClaimTemplates为每个副本创建专属PVC,Pod重建后数据不丢
适用:MySQL主从、Redis Cluster、ZooKeeper、Kafka等。
3.5 Job:一次性任务
apiVersion: batch/v1
kind: Job
metadata:
name: pi-calc
spec:
completions: 1 # 成功完成1次就算Job完成
parallelism: 1 # 并行度
backoffLimit: 3 # 失败重试上限(默认6,生产建议调小)
activeDeadlineSeconds: 300 # 总超时时间,超时强制终止
ttlSecondsAfterFinished: 600 # 完成后600秒自动清理(需要开启TTL控制器)
template:
spec:
restartPolicy: Never # Job只支持Never或OnFailure
containers:
- name: pi
image: perl:5.36
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(1000)"]
3.6 CronJob:K8s原生的crontab
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cron
spec:
schedule: "*/1 * * * *" # 标准cron表达式,每分钟一次
concurrencyPolicy: Forbid # 上一次没跑完怎么办:Allow/Forbid/Replace
successfulJobsHistoryLimit: 3 # 保留历史Job数,防止堆积
failedJobsHistoryLimit: 1
startingDeadlineSeconds: 60 # 错过调度时间60秒内还补跑
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.36
command:
- sh
- -c
- echo "[$(date)] hello from cronjob"
concurrencyPolicy是生产中最容易忽略的配置:默认Allow会导致任务堆积(比如每分钟的备份任务某次跑了10分钟,就叠了10个并发实例),定时任务通常应设Forbid。
本文集群实测:创建上面的hello-cron(每分钟执行一次),几分钟后观察它自动产生的Job和日志。
$ kubectl apply -f hello-cron.yaml
cronjob.batch/hello-cron created
$ kubectl get cronjob hello-cron
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-cron */1 * * * * False 1 7s 54s
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
hello-cron-29808912 1/1 3s 2m28s
hello-cron-29808913 1/1 3s 95s
hello-cron-29808914 1/1 3s 35s
$ kubectl logs job.batch/hello-cron-29808914
[Fri Sep 4 15:13:00 UTC 2026] hello from cronjob
每分钟准时产生一个Job,3秒跑完,历史保留3个——和successfulJobsHistoryLimit: 3配置完全吻合。
再看一眼Deployment及其托管的ReplicaSet(Deployment本身不直接管Pod,而是通过RS):
$ kubectl get deployment nginx-deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 3/3 3 3 4m20s
$ kubectl get rs -l app=nginx
NAME DESIRED CURRENT READY AGE
nginx-deploy-5d45497bc9 3 3 3 51s
四、Service:服务发现与负载均衡
Pod的IP是短暂的(重建就变),Service为一组Pod提供稳定访问入口(固定ClusterIP+DNS名)和负载均衡。
4.1 四种Service类型
| 类型 | 说明 | 访问范围 |
|---|---|---|
| ClusterIP(默认) | 分配集群内部虚拟IP | 仅集群内 |
| NodePort | ClusterIP + 每节点开一个30000-32767端口 | 集群外可经任意节点IP:NodePort访问 |
| LoadBalancer | NodePort + 云厂商LB(自建集群一般用MetalLB实现) | 集群外经LB入口 |
| ExternalName | 返回CNAME记录,把集群内名字映射到外部域名 | DNS别名 |
# ClusterIP示例
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
type: ClusterIP
selector:
app: nginx # 关键:通过label选中后端Pod
ports:
- port: 80 # Service端口
targetPort: 80 # 容器端口
---
# NodePort示例
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080 # 不指定则随机分配30000+
Service背后的数据链路:Service创建后,Endpoints控制器根据selector找到健康Pod的IP写入EndpointSlice,kube-proxy监听EndpointSlice变化并刷新本机的iptables/IPVS规则。
Headless Service(clusterIP: None)不分配虚拟IP、不做负载均衡,DNS直接返回所有后端Pod IP——StatefulSet的稳定DNS和客户端自负载均衡场景(如gRPC)都靠它。
4.2 kube-proxy两种代理模式
iptables模式(默认):
-
每个Service的每个端口生成多条iptables规则,随机选择后端(无会话亲和时是概率均分)
-
优点:内核态转发,性能好;缺点:规则数随Service规模线性增长(万级Service时规则刷新慢),且iptables不支持真正负载均衡算法
IPVS模式:
-
基于内核的LVS(Linux Virtual Server),哈希表查找O(1),支持rr/lc/dh/sh等调度算法,天然支持会话保持
-
万级Service规模下规则同步和转发性能远优于iptables
切换IPVS模式:
kubectl -n kube-system edit configmap kube-proxy
# mode: "" 改为 mode: "ipvs"
kubectl -n kube-system rollout restart daemonset kube-proxy
# 验证
ipvsadm -Ln | head -20
4.3 externalTrafficPolicy:保留源IP的关键
NodePort/LoadBalancer流量默认会做SNAT(后端Pod看到的是节点IP),设置externalTrafficPolicy: Local后流量只转发给本机Pod、不做SNAT,保留客户端真实源IP,代价是节点间流量不均。记录访问日志、做IP白名单的业务必须了解这个参数。
本文集群实测:给3副本的nginx-deploy同时创建ClusterIP和NodePort两个Service,并逐一验证。
$ kubectl get svc nginx-svc nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.99.39.225 <none> 80/TCP 51s
nginx-nodeport NodePort 10.106.199.44 <none> 80:30080/TCP 51s
$ kubectl get endpoints nginx-svc
NAME ENDPOINTS AGE
nginx-svc 10.244.0.4:80,10.244.1.3:80,10.244.1.4:80 4m19s
Endpoints里是3个真实Pod IP,kube-proxy正是根据这个列表生成iptables规则的。分别访问三种入口:
# 1. ClusterIP(集群内任意节点上执行)
$ curl -s -o /dev/null -w 'ClusterIP http_code: %{http_code}\n' http://10.99.39.225
ClusterIP http_code: 200
# 2. NodePort(任意节点IP + 30080)
$ curl -s -o /dev/null -w '%{http_code}\n' http://192.168.0.34:30080
200
$ curl -s -o /dev/null -w '%{http_code}\n' http://192.168.0.136:30080
200
# 3. 直连Pod IP(Flannel打通的跨节点Pod网络)
$ curl -s -o /dev/null -w '%{http_code}\n' http://10.244.1.4
200
三种入口全部返回200,Service的发现与负载均衡链路验证通过。
五、CoreDNS:集群内的域名解析
5.1 解析流程
集群内每个Pod的/etc/resolv.conf被kubelet指向CoreDNS的Service IP(默认10.96.0.10):
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
Service的完整域名格式:<service>.<namespace>.svc.cluster.local。同namespace内直接写服务名即可,跨namespace用svc名.namespace。
一次nginx-svc.default.svc.cluster.local的解析流程:
Pod发出查询(ndots:5 → 名字中点数<5时先拼接search域依次尝试)
→ CoreDNS(匹配kubernetes插件,查内存中的Service/Endpoints缓存)
→ 命中:返回ClusterIP(或Headless时返回Pod IP列表)
→ 未命中(外部域名):转发到forward插件指定的上游DNS
ndots:5的坑:查询www.baidu.com(2个点,<5)会先依次尝试www.baidu.com.default.svc.cluster.local等4个search域,全部NXDOMAIN后才查真域名——外部域名访问多的服务建议FQDN结尾加点(www.baidu.com.)跳过search域,或在Pod spec中配置dnsConfig.options调整ndots。
5.2 常用排障命令
# 在临时Pod里测试DNS
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- nslookup nginx-svc
kubectl -n kube-system logs -l k8s-app=kube-dns # CoreDNS日志
六、ConfigMap与Secret:配置管理
6.1 ConfigMap:非敏感配置
创建方式:
# 字面值
kubectl create configmap app-config \
--from-literal=APP_ENV=prod \
--from-literal=LOG_LEVEL=info
# 文件(key=文件名,value=文件内容)
kubectl create configmap nginx-conf --from-file=nginx.conf
# 目录
kubectl create configmap confdir --from-file=/path/to/dir/
对应的YAML:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "prod"
LOG_LEVEL: "info"
app.yaml: | # 多行配置文件直接内嵌
server:
port: 8080
readTimeout: 30s
6.2 三种消费方式
方式一:环境变量注入
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
# 或整体导入:envFrom: [{configMapRef: {name: app-config}}]
方式二:命令行参数引用(先注入env再在command中使用)
command: ["/app/start.sh", "--env=$(APP_ENV)", "--log=$(LOG_LEVEL)"]
方式三:Volume挂载(生产推荐)
volumes:
- name: config-vol
configMap:
name: nginx-conf
items: # 可选:只挂载指定key
- key: nginx.conf
path: nginx.conf
containers:
- volumeMounts:
- name: config-vol
mountPath: /etc/nginx/conf.d
关键差异:环境变量方式注入后不会热更新(改ConfigMap要重启Pod);Volume挂载方式ConfigMap更新后约60秒内自动同步到容器内文件(kubelet定期同步),配合应用自身watch文件可实现配置热加载。
6.3 Secret:敏感信息
Secret与ConfigMap用法几乎一致,区别是value需要base64编码(stringData字段可免编码自动处理),且存储和传输时K8s做了额外处理(可配合etcd加密、内存tmpfs挂载)。
# 创建
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='S3cret@2026'
kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key # TLS证书
kubectl create secret docker-registry regcred --docker-server=... --docker-username=... --docker-password=... # 镜像仓库凭据
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData: # 免base64,apply时自动编码
username: admin
password: S3cret@2026
# Pod中使用
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
安全须知:base64只是编码不是加密!生产环境必须开启etcd静态加密(EncryptionConfiguration)、限制Secret的RBAC访问、避免以env方式注入(env在kubectl describe pod和crash dump中可见,Volume挂载更安全),或接入Vault/外部密钥管理。
本文集群实测:创建一对ConfigMap和Secret,验证存取。
$ kubectl create configmap app-config \
--from-literal=APP_ENV=prod --from-literal=LOG_LEVEL=info
configmap/app-config created
$ kubectl get configmap app-config -o yaml | head -8
apiVersion: v1
data:
APP_ENV: prod
LOG_LEVEL: info
kind: ConfigMap
metadata:
name: app-config
namespace: default
$ kubectl create secret generic db-secret \
--from-literal=username=admin --from-literal=password='S3cret@2026'
secret/db-secret created
# Secret在etcd中以base64存储,取值时解码
$ kubectl get secret db-secret -o jsonpath='{.data.username}' | base64 -d; echo
admin
RBAC章节的pod-viewer实战也是在本集群真实执行的,验证结果如下:
$ kubectl auth can-i list pods --as=system:serviceaccount:default:pod-viewer
yes
$ kubectl auth can-i delete pods --as=system:serviceaccount:default:pod-viewer
no
只读权限精确生效——能list不能delete,最小权限原则落地。
七、RBAC权限控制实战
K8s的RBAC模型四个核心对象:ServiceAccount(身份)、Role/ClusterRole(权限集合)、RoleBinding/ClusterRoleBinding(把权限绑给身份)。Role/RoleBinding是namespace级,ClusterRole/ClusterRoleBinding是集群级。
设计原则是最小权限:每个应用用自己的ServiceAccount,只授予必需的权限。
实战:给default命名空间创建一个只能"看Pod"的账号。
# 1. ServiceAccount:Pod在集群内的身份
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-viewer
namespace: default
---
# 2. Role:定义权限(动词+资源)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""] # 核心API组(Pod/Service等),apps组写"apps"
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
# 3. RoleBinding:把Role绑给ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: pod-viewer
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
验证权限(kubectl auth can-i是RBAC排障神器):
kubectl auth can-i list pods --as=system:serviceaccount:default:pod-viewer
# yes
kubectl auth can-i delete pods --as=system:serviceaccount:default:pod-viewer
# no
kubectl auth can-i list deployments --as=system:serviceaccount:default:pod-viewer -n kube-system
# no(Role只在default命名空间生效)
Pod使用指定ServiceAccount:spec.serviceAccountName: pod-viewer。若 Pod 需要访问API Server(如自研Operator、CI系统),就通过这个链条鉴权。
八、存储体系:Volume/PV/PVC/StorageClass
8.1 Volume:Pod级存储卷
常用类型速查:
| 类型 | 生命周期 | 用途 |
|---|---|---|
| emptyDir | 随Pod | 容器间临时共享、缓存(设medium: Memory变tmpfs内存盘) |
| hostPath | 节点本地 | 访问节点文件(日志、Docker socket),DaemonSet常用,有数据安全隐患 |
| nfs | 独立于Pod | 多节点共享读写 |
| configMap/secret | 随Pod | 配置注入 |
| persistentVolumeClaim | 独立于Pod | 对接持久化存储的标准方式 |
8.2 PV/PVC:存储的"供需分离"
PV(PersistentVolume)是集群管理员准备的存储资源(供应侧),PVC(PersistentVolumeClaim)是用户的存储申请(需求侧)。K8s按容量、访问模式(RWO/ROX/RWX)、StorageClass自动撮合绑定,用户无需关心底层是NFS还是云盘。
hostPath PV实战(单节点测试用):
apiVersion: v1
kind: PersistentVolume
metadata:
name: hostpath-pv
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain # PVC删除后保留数据(还有Delete/Recycle)
storageClassName: manual
hostPath:
path: /data/hostpath-pv
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hostpath-pvc
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: manual
resources:
requests:
storage: 500Mi
NFS PV实战(多节点共享):
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteMany"] # NFS天然支持多节点读写
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.0.34
path: /data/nfs-share
Pod中引用PVC:
volumes:
- name: data
persistentVolumeClaim:
claimName: hostpath-pvc
containers:
- volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
PV/PVC的生命周期状态:Available(可用)→ Bound(已绑定)→ Released(PVC已删但回收策略为Retain)→ 管理员处理后重新Available。
8.3 StorageClass:动态供给
静态PV需要管理员手工逐个创建,规模上来后不可维护。StorageClass定义"存储模板",配合Provisioner插件(如云厂商CSI、nfs-subdir-external-provisioner)实现创建PVC时自动制备PV:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-dynamic
provisioner: nfs.csi.k8s.io # 或云厂商provisioner
parameters:
server: 192.168.0.34
share: /data/nfs-dynamic
reclaimPolicy: Delete # PVC删除时自动删除后端数据
volumeBindingMode: WaitForFirstConsumer # 延迟绑定:等Pod调度后再制备,避免存储与节点不匹配
标记为默认StorageClass后(annotation storageclass.kubernetes.io/is-default-class: "true"),PVC不写storageClassName也会自动动态供给。
本文集群实测:创建一个hostPath类型的PV和一个500Mi的PVC,观察绑定过程。
$ cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: hostpath-pv
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /data/hostpath-pv
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hostpath-pvc
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: manual
resources:
requests:
storage: 500Mi
EOF
persistentvolume/hostpath-pv created
persistentvolumeclaim/hostpath-pvc created
$ kubectl get pv hostpath-pv; kubectl get pvc hostpath-pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
hostpath-pv 1Gi RWO Retain Bound default/hostpath-pvc manual 50s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hostpath-pvc Bound hostpath-pv 1Gi RWO manual 50s
注意PV的CLAIM列已经指向default/hostpath-pvc,状态双双变为Bound——绑定是自动且即时完成的(静态供给模式下控制器按storageClassName+accessModes+容量匹配)。PVC虽然只要500Mi,但绑定的PV是1Gi,因为PV是最小绑定单位,不能切割。
九、亲和性调度:Affinity/Taint/Toleration
K8s调度三件套:nodeSelector(最简单,按节点label筛选)、Affinity(丰富表达)、Taint/Toleration(从节点侧拒绝)。
9.1 nodeAffinity:Pod选节点
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬约束:不满足就Pending
nodeSelectorTerms:
- matchExpressions:
- key: disk-type
operator: In
values: ["ssd"]
preferredDuringSchedulingIgnoredDuringExecution: # 软约束:尽量满足
- weight: 100
preference:
matchExpressions:
- key: zone
operator: In
values: ["zone-a"]
记忆口诀:required是必须,preferred是尽量;In/NotIn/Exists/DoesNotExist/Gt/Lt六种操作符。
9.2 podAffinity / podAntiAffinity:Pod选Pod
affinity:
podAntiAffinity: # 反亲和:副本打散到不同节点,防单点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: nginx
topologyKey: kubernetes.io/hostname # 拓扑域:节点级
topologyKey是精髓:kubernetes.io/hostname表示"每个节点最多一个同类Pod",换成topology.kubernetes.io/zone就是"每个可用区打散"。podAffinity则相反(把相关Pod调度到一起,如缓存与应用同机房降延迟),生产上反亲和远比正亲和常用。
9.3 Taint与Toleration:节点驱逐与专用
Taint打在节点上(拒绝),Toleration打在Pod上(容忍)。三种效果:
| Effect | 含义 |
|---|---|
| NoSchedule | 不容忍的Pod不调度上来 |
| PreferNoSchedule | 尽量不调度(软约束) |
| NoExecute | 不容忍的存量Pod也会被驱逐 |
# 把node3设为GPU专用节点
kubectl taint nodes k8s-node3 gpu=true:NoSchedule
# 允许某Pod使用该节点(YAML中)
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# 取消污点
kubectl taint nodes k8s-node3 gpu=true:NoSchedule-
Master节点默认带node-role.kubernetes.io/control-plane:NoSchedule污点,所以业务Pod不会被调度到Master——这也是前面DaemonSet示例要加tolerations的原因。另外kubectl drain的本质就是打unschedulable:NoSchedule污点+驱逐Pod。
十、YAML编写规范与最佳实践
-
四大必填字段:
apiVersion、kind、metadata.name、spec,先写对再谈优化。 -
apiVersion速查:Pod/Service/ConfigMap/Secret/PV/PVC →
v1;Deployment/DaemonSet/StatefulSet →apps/v1;Job/CronJob →batch/v1;RBAC →rbac.authorization.k8s.io/v1;Ingress →networking.k8s.io/v1。 -
善用生成器:手写易错,先让kubectl生成骨架再改:
kubectl create deployment web --image=nginx --dry-run=client -o yaml > web.yaml kubectl expose deployment web --port=80 --dry-run=client -o yaml >> web.yaml kubectl explain deployment.spec.strategy # 查字段文档,比翻官网快 -
缩进即生命:YAML对缩进敏感,统一2空格,禁止Tab;列表
-后有空格。 -
字符串陷阱:端口、
on/off/yes/no、含冒号的值要加引号("on"在YAML 1.1中会被解析成布尔true,曾引发著名的"挪威问题")。 -
资源清单入Git:所有YAML版本化管理,配合
kubectl apply -f(声明式)而非kubectl create(命令式),实现可追溯、可回滚。 -
多资源用
---分隔,一个文件管理一组关联资源;用kubectl apply -f dir/批量部署。 -
apply前校验:
kubectl apply --dry-run=client -f xxx.yaml提前发现语法错误;团队内可引入kubeval/kubeconform做CI校验。
十一、总结与系列导航
本文梳理了K8s最核心的资源对象体系,要点回顾:
- Namespace做逻辑隔离,Label做资源关联,是理解一切K8s对象关系的基础。
- Pod是最小调度单元,requests/limits决定调度与QoS,生产环境必须设置。
- 控制器按场景选型:无状态Deployment、节点守护DaemonSet、有状态StatefulSet、任务用Job/CronJob。
- Service四种类型覆盖从内网到公网的所有暴露方式,kube-proxy的IPVS模式适合大规模集群。
- ConfigMap/Secret解耦配置与镜像,Volume挂载支持热更新,Secret务必配合etcd加密。
- RBAC最小权限、PV/PVC供需分离、亲和性三件套,是走向生产的必修课。
本系列导航:
-
blog-01《Docker基础入门与核心命令实战》
-
blog-02《Docker镜像与网络深度解析》
-
blog-03《Dockerfile最佳实践与私有镜像仓库搭建》
-
blog-04《Containerd深度解析:从Docker到Containerd的演进与实战迁移》
-
blog-05《Kubernetes生产级集群部署实战:kubeadm搭建K8s 1.29全流程指南》
-
blog-06《Kubernetes核心概念深度解析:Pod/Service/Deployment/存储与配置全攻略》(本篇)
-
blog-07《Kubernetes网络深度剖析:Flannel/Calico原理与Traefik Ingress流量路由实战》
-
blog-08《K8s全栈监控体系搭建:Prometheus+Grafana+AlertManager生产实战》
-
blog-09《K8s日志体系实战:EFK/Loki日志采集与分析》
-
blog-10《Spring Cloud微服务上K8s实战》
-
blog-11《云原生CI/CD与GitOps落地实践》
下一篇我们将深入K8s网络——Flannel/Calico原理对比、Service流量链路,以及Traefik Ingress的七层路由、蓝绿与金丝雀发布实战。
更多推荐



所有评论(0)