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-systemK8s系统组件(CoreDNS、kube-proxy等)
kube-public公共可读空间,存放cluster-info
kube-node-lease节点租约对象,用于节点心跳检测
kube-flannelFlannel网络插件(本文集群)

实战中建议按环境或团队划分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=nginxenv=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 logsdescribe排查

排障套路: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-0redis-1redis-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仅集群内
NodePortClusterIP + 每节点开一个30000-32767端口集群外可经任意节点IP:NodePort访问
LoadBalancerNodePort + 云厂商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编写规范与最佳实践

  1. 四大必填字段apiVersionkindmetadata.namespec,先写对再谈优化。

  2. 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

  3. 善用生成器:手写易错,先让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     # 查字段文档,比翻官网快
    
  4. 缩进即生命:YAML对缩进敏感,统一2空格,禁止Tab;列表-后有空格。

  5. 字符串陷阱:端口、on/off/yes/no、含冒号的值要加引号("on"在YAML 1.1中会被解析成布尔true,曾引发著名的"挪威问题")。

  6. 资源清单入Git:所有YAML版本化管理,配合kubectl apply -f(声明式)而非kubectl create(命令式),实现可追溯、可回滚。

  7. 多资源用---分隔,一个文件管理一组关联资源;用kubectl apply -f dir/批量部署。

  8. apply前校验kubectl apply --dry-run=client -f xxx.yaml提前发现语法错误;团队内可引入kubeval/kubeconform做CI校验。


十一、总结与系列导航

本文梳理了K8s最核心的资源对象体系,要点回顾:

  1. Namespace做逻辑隔离,Label做资源关联,是理解一切K8s对象关系的基础。
  2. Pod是最小调度单元,requests/limits决定调度与QoS,生产环境必须设置。
  3. 控制器按场景选型:无状态Deployment、节点守护DaemonSet、有状态StatefulSet、任务用Job/CronJob。
  4. Service四种类型覆盖从内网到公网的所有暴露方式,kube-proxy的IPVS模式适合大规模集群。
  5. ConfigMap/Secret解耦配置与镜像,Volume挂载支持热更新,Secret务必配合etcd加密。
  6. 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的七层路由、蓝绿与金丝雀发布实战。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐