k8s 笔记

好的,我们来详细整理一下关于 Kubernetes Endpoints 资源的笔记。这份笔记将严格按照你的大纲,并力求概念通俗易懂、案例详尽、代码完整且包含输出解释。


Kubernetes Endpoints 资源深度解析

一、什么是 Endpoints 资源?

你可以把 Endpoints 资源理解为 Kubernetes 中的“服务地址簿”或“DNS 白名单”。它是一个核心的服务发现组件,充当了 Service (svc)Pod 之间的桥梁。

具体来说:

  1. Endpoints 是一个 Kubernetes 资源对象:就像 Pod, Service, Deployment 一样,它也有自己的 YAML 定义和生命周期。
  2. 核心功能是存储地址:它的主要数据是一个 IP 地址列表和对应的端口号列表。这些 IP 地址正是后端提供服务的 Pod 的 IP。
  3. Service 管理和引用:每个 Service 资源都会自动关联一个同名的 Endpoints 资源(在大多数情况下)。Service 收到请求后,会查看自己关联的 Endpoints 列表,然后将请求转发到列表中的一个或多个 IP 上。

工作流程简图:

外部请求  ->  Service (例如:my-service)  ->  Endpoints (my-service) ->  [Pod IP 1, Pod IP 2, ...]
                                                              |
                                                              v
                                                      后端应用 Pods

通俗比喻:

  • Service 就像是一家公司的总机电话。
  • Endpoints 就是这家公司的员工通讯录,上面有各个部门员工的分机号(Pod IP)。
  • 当客户(外部请求)拨打总机(Service),接线员(kube-proxy)会查看通讯录(Endpoints),然后把电话转接到具体的员工(Pod)那里。

二、核心案例:Endpoints 的自动与手动管理

Endpoints 的创建和维护主要有两种模式:自动模式手动模式

案例 1:自动创建 Endpoints (最常见)

当你创建一个 Service 并为其指定 标签选择器(selector) 时,Kubernetes 会自动为你完成以下工作:

  1. 创建一个与 Service 同名的 Endpoints 资源。
  2. 持续监控集群中所有 Pod 的标签。
  3. 当发现有 Pod 的标签与 Serviceselector 匹配时,就把这个 Pod 的 IP 地址和端口(从 PodcontainerPort 获取)添加到 Endpoints 列表中。
  4. Pod 被删除、更新或标签改变时,Kubernetes 会自动更新 Endpoints 列表。

详细实现过程:

第 1 步:创建一个 Deployment 来管理 Pod

我们先创建一些带有特定标签的 Pod。这里我们用 Deployment 来批量管理。

my-app-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3  # 运行 3 个 Pod 副本
  selector:
    matchLabels:
      app: my-app # Deployment 管理带有此标签的 Pod
  template:
    metadata:
      labels:
        app: my-app # 给 Pod 打上标签
        env: production
    spec:
      containers:
      - name: my-app-container
        image: nginx:alpine # 使用一个简单的 nginx 镜像作为示例
        ports:
        - containerPort: 80 # 容器在 80 端口提供服务

执行命令创建:

kubectl apply -f my-app-deployment.yaml

查看创建的 Pod

kubectl get pods -o wide --show-labels

输出示例:

NAME                                READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES   LABELS
my-app-deployment-7f98d7c6b4-2xqzk   1/1     Running   0          10s   10.244.1.10   minikube   <none>           <none>            app=my-app,env=production,pod-template-hash=7f98d7c6b4
my-app-deployment-7f98d7c6b4-5bmd7   1/1     Running   0          10s   10.244.1.11   minikube   <none>           <none>            app=my-app,env=production,pod-template-hash=7f98d7c6b4
my-app-deployment-7f98d7c6b4-8vgrx   1/1     Running   0          10s   10.244.1.12   minikube   <none>           <none>            app=my-app,env=production,pod-template-hash=7f98d7c6b4

你可以看到 3 个 Pod 已经运行,并且都带有 app=my-app 标签,它们的 IP 地址分别是 10.244.1.10, 10.244.1.11, 10.244.1.12

第 2 步:创建一个带有 selectorService

现在,我们创建一个 Service,让它通过标签选择器找到上面的 Pod

my-app-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app # 关键!Service 将选择所有带有 app=my-app 标签的 Pod
  ports:
  - protocol: TCP
    port: 80        # Service 暴露的端口
    targetPort: 80  # 转发到 Pod 的端口
  type: ClusterIP   # 这是默认类型,只在集群内部可访问

执行命令创建:

kubectl apply -f my-app-service.yaml

查看 Service

kubectl get svc my-app-service

输出示例:

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
my-app-service   ClusterIP   10.100.235.147   <none>        80/TCP    5s

Service 被分配了一个集群内部的虚拟 IP 地址(CLUSTER-IP)。

第 3 步:验证自动创建的 Endpoints

此时,Kubernetes 已经自动为我们创建了一个名为 my-app-serviceEndpoints 资源。

查看 Endpoints

kubectl get endpoints my-app-service -o wide

输出示例:

NAME            ENDPOINTS                                                      AGE
my-app-service   10.244.1.10:80,10.244.1.11:80,10.244.1.12:80   15s

奇迹发生了! Endpoints 列表中自动包含了我们刚才创建的 3 个 Pod 的 IP 地址和端口 80

第 4 步:验证服务发现

我们可以在集群内的另一个 Pod 中(比如一个临时的 busybox Pod)来访问这个 Service,以验证它是否能正常工作。

kubectl run -it --rm --image=busybox:1.28 --restart=Never busybox-test sh

这会打开一个 busybox Pod 的 shell 终端。在终端中执行:

# 使用 wget 或 curl 访问 Service 的名字
wget -qO- my-app-service

输出示例(简化):

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>

你成功地通过 Service 名称 my-app-service 访问到了后端的 nginx Pod 提供的服务。这背后正是 Endpoints 在默默地工作。


案例 2:手动创建 Endpoints

Service 没有 selector 时,Kubernetes 不会自动创建和管理 Endpoints。这种情况通常用于:

  • 服务集群外部的应用:例如,你有一个数据库运行在 Kubernetes 集群之外的物理机上,你想让集群内的 Pod 也能通过 Service 的方式访问它。
  • 精细控制服务 endpoints:在某些特殊场景下,你需要手动决定哪些 IP 被包含。

详细实现过程:

假设我们有一个数据库运行在集群外部,IP 地址是 192.168.1.100,端口是 5432

第 1 步:创建一个不带 selectorService

my-external-db-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-external-db-service
spec:
  ports:
  - protocol: TCP
    port: 5432        # Service 暴露的端口
    targetPort: 5432  # 转发到外部服务的端口
  # 注意:这里没有 selector 字段

执行命令创建:

kubectl apply -f my-external-db-service.yaml

查看 Service

kubectl get svc my-external-db-service

输出示例:

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
my-external-db-service   ClusterIP   10.100.156.78   <none>        5432/TCP   10s

Service 创建成功,但此时如果你查看它的 Endpoints,会发现是空的。

kubectl get endpoints my-external-db-service

输出示例:

NAME                   ENDPOINTS   AGE
my-external-db-service   <none>      20s

第 2 步:手动创建对应的 Endpoints 资源

现在,我们需要手动创建一个 Endpoints 资源,将 Service 指向外部的数据库 IP。

my-external-db-endpoints.yaml:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-external-db-service # 名称必须和 Service 完全一致
subsets:
- addresses:
  - ip: 192.168.1.100  # 外部数据库的 IP 地址
  ports:
  - port: 5432         # 外部数据库的端口

关键点

  • metadata.name 必须与 Service 的名字相同,这样 Service 才能找到并使用它。
  • subsets.addresses.ip 可以是任何可访问的 IP,不一定是集群内的 Pod IP。
  • 你可以在 addresses 列表中添加多个 IP,实现对多个外部服务的负载均衡。

执行命令创建:

kubectl apply -f my-external-db-endpoints.yaml

第 3 步:验证手动创建的 Endpoints

再次查看 Endpoints

kubectl get endpoints my-external-db-service

输出示例:

NAME                   ENDPOINTS             AGE
my-external-db-service   192.168.1.100:5432   5s

现在 Endpoints 已经有值了!

第 4 步:验证服务访问

现在,集群内的 Pod 就可以通过 my-external-db-service:5432 这个地址来访问外部的数据库了。

# 再次使用 busybox 进行测试
kubectl run -it --rm --image=busybox:1.28 --restart=Never busybox-test sh

在 shell 中:

# 尝试 telnet 到 Service 地址和端口,验证连通性
telnet my-external-db-service 5432

如果外部数据库可达,你应该会看到类似下面的输出,表示连接成功:

Trying 10.100.156.78...
Connected to my-external-db-service.default.svc.cluster.local.
Escape character is '^]'.

(注意:10.100.156.78ServiceCLUSTER-IP


三、Endpoints 的特殊行为与配置

默认行为:Service 如何筛选 Pod

在案例 1 的自动模式下,Service 并不是把所有标签匹配的 Pod 都加入 Endpoints。它有一套默认的健康检查机制:

一个 Pod 必须满足以下所有条件,才会被 Service 选中并加入到 Endpoints 列表中:

  1. 标签匹配Pod 的标签必须与 Serviceselector 完全匹配。
  2. 状态为 "Running"Podstatus.phase 必须是 Running。处于 Pending, Succeeded, Failed 状态的 Pod 会被忽略。
  3. 就绪状态为 "Ready"Pod 必须通过所有的就绪探针(Readiness Probe)检查。Podstatus.conditions 中必须有一个类型为 Ready,且 statusTrue 的条件。这表示 Pod 内部的应用已经启动并准备好接收请求。

示例:一个未就绪的 Pod

假设我们有一个 Pod,它的就绪探针检查一个需要 30 秒才能启动的服务。在启动后的前 30 秒内,kubectl describe pod <pod-name> 会显示:

Conditions:
  Type              Status
  Ready             False 
  ...

在这 30 秒内,这个 Pod 虽然标签匹配且状态是 Running,但因为 Ready 状态是 False,所以它不会出现在 Endpoints 列表中。只有当 Ready 变为 True 后,kube-proxy 才会将其加入 Endpoints,并开始转发流量。

特殊配置:publishNotReadyAddresses

有时候,你可能希望 Service 也能将那些未就绪(Not Ready)的 Pod 暴露出来。例如:

  • 进行健康检查或监控:你可能有一个监控系统,需要访问所有 Pod(包括未就绪的)来收集启动时的日志或指标。
  • 服务网格(Service Mesh)场景:一些服务网格代理需要在 Pod 完全就绪前就与之通信,以注入sidecar代理。

要实现这个需求,你可以在 Service 的定义中设置 publishNotReadyAddresses: true

详细实现过程:

我们修改案例 1 中的 Service YAML。

my-app-service-include-not-ready.yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service-include-not-ready
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  publishNotReadyAddresses: true  # 关键配置!

执行命令创建或更新:

kubectl apply -f my-app-service-include-not-ready.yaml

效果:

现在,假设我们有一个 Pod 因为就绪探针失败而处于 Not Ready 状态。

kubectl get pods

输出示例:

NAME                                READY   STATUS    RESTARTS   AGE
my-app-deployment-7f98d7c6b4-2xqzk   1/1     Running   0          5m
my-app-deployment-7f98d7c6b4-5bmd7   0/1     Running   0          30s  # 这个 Pod 未就绪
my-app-deployment-7f98d7c6b4-8vgrx   1/1     Running   0          5m

查看新 Service 对应的 Endpoints

kubectl get endpoints my-app-service-include-not-ready

输出示例:

NAME                               ENDPOINTS                                                                        AGE
my-app-service-include-not-ready   10.244.1.10:80,10.244.1.11:80,10.244.1.12:80   1m

(假设 10.244.1.11 是那个未就绪 Pod 的 IP)

你会发现,即使 Pod my-app-deployment-7f98d7c6b4-5bmd7READY 状态是 0/1,它的 IP 地址依然被包含在了 Endpoints 列表中。

警告:启用 publishNotReadyAddresses 后,Service 会将流量转发到所有标签匹配且状态为 RunningPod,无论其是否就绪。这意味着客户端可能会访问到一个无法正常提供服务的 Pod,导致请求失败。因此,在生产环境中使用此配置时,必须确保你的应用程序或客户端有足够的容错能力(如重试机制)。


总结

  • Endpoints 是服务发现的基石,它存储了 Service 背后真实的 Pod (或外部服务) 的网络地址。
  • 自动模式是常态:通过 Serviceselector 自动管理 Endpoints,是 Kubernetes 服务发现的标准用法。
  • 手动模式用于特殊场景:当需要将 Service 指向集群外部服务或进行精细控制时,可以手动创建 Endpoints
  • 默认行为是安全的Service 只将流量转发到 RunningReadyPod,确保了服务的可用性。
  • publishNotReadyAddresses 是一个强大的逃逸阀:它允许你打破默认的安全检查,在特定场景下非常有用,但使用时需谨慎。

K8s存储笔记

存储分类

K8s中的存储主要用于解决容器数据持久化、配置管理、敏感信息存储等问题,常见的存储类型包括ConfigMap(配置存储)、Secret(敏感信息存储)、PV/PVC(持久化存储)、Downward API(Pod元数据注入)等。不同存储类型针对不同场景设计,满足多样化的存储需求。

ConfigMap

概念

ConfigMap是K8s专门用来存储非敏感配置信息的资源,比如应用的配置参数、环境变量配置、配置文件内容等。你可以把它理解成一个“配置字典”,里面装着键值对形式的配置数据。

  • 它能被挂载到Pod里,要么当成环境变量用,要么当成配置文件用;
  • 多个Pod可以共享同一个ConfigMap,实现配置的统一管理和复用;
  • 注意:ConfigMap是在Pod第一次启动时注入的,后续如果ConfigMap改了,Pod不会自动更新,除非删掉Pod重新创建(不过用Volume挂载的方式支持热更新,后面会讲)。

创建

1. 通过YAML文件创建

新建一个my-config.yaml文件,内容如下:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config  # ConfigMap的名字
  namespace: default  # 所属命名空间,默认default
data:
  app.conf: |  # 配置文件形式的键值对
    server.port=8080
    log.level=info
  env: "prod"  # 简单键值对
  max_connections: "1000"

执行创建命令:

kubectl apply -f my-config.yaml

输出:configmap/my-config created

2. 从文件创建

先创建一个本地配置文件app.properties,内容为:

db.url=jdbc:mysql://localhost:3306/mydb
db.username=root

然后执行命令创建ConfigMap:

kubectl create configmap my-config-from-file --from-file=app.properties

输出:configmap/my-config-from-file created
如果有多个文件,可多次指定--from-file,比如--from-file=file1 --from-file=file2

3. 从字面量创建

直接通过命令行指定键值对创建:

kubectl create configmap my-config-from-literal --from-literal=key1=value1 --from-literal=key2=value2

输出:configmap/my-config-from-literal created

Pod如何使用ConfigMap?

1. 作为环境变量

创建Pod的YAML文件pod-env-config.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-env-config
spec:
  containers:
  - name: nginx-container
    image: nginx:alpine
    env:
    - name: ENV_CONFIG  # 容器内的环境变量名
      valueFrom:
        configMapKeyRef:
          name: my-config  # 引用的ConfigMap名称
          key: env  # 取ConfigMap里的env键
    - name: MAX_CONN  # 另一个环境变量
      valueFrom:
        configMapKeyRef:
          name: my-config
          key: max_connections

创建Pod:

kubectl apply -f pod-env-config.yaml

进入Pod查看环境变量:

kubectl exec -it pod-env-config -- sh
# 执行env命令查看
env | grep -E "ENV_CONFIG|MAX_CONN"

输出:

ENV_CONFIG=prod
MAX_CONN=1000

2. 作为启动参数

创建Pod的YAML文件pod-args-config.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-args-config
spec:
  containers:
  - name: busybox-container
    image: busybox:latest
    command: ["/bin/sh", "-c", "echo $(ENV_VAL) $(MAX_VAL)"]  # 启动命令中使用环境变量
    env:
    - name: ENV_VAL
      valueFrom:
        configMapKeyRef:
          name: my-config
          key: env
    - name: MAX_VAL
      valueFrom:
        configMapKeyRef:
          name: my-config
          key: max_connections

创建Pod后查看日志:

kubectl apply -f pod-args-config.yaml
kubectl logs pod-args-config

输出:prod 1000

3. 作为配置文件(Volume挂载)

创建Pod的YAML文件pod-volume-config.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-config
spec:
  containers:
  - name: nginx-container
    image: nginx:alpine
    volumeMounts:
    - name: config-volume  # 卷名,和下面volumes里的name对应
      mountPath: /etc/nginx/conf.d  # 挂载到容器内的路径
      readOnly: true  # 只读,ConfigMap挂载默认只读
  volumes:
  - name: config-volume
    configMap:
      name: my-config  # 引用的ConfigMap名称
      items:  # 可选,指定要挂载的键,不指定则挂载所有键
      - key: app.conf  # ConfigMap里的app.conf键
        path: app.conf  # 挂载到容器内的文件名

创建Pod:

kubectl apply -f pod-volume-config.yaml

进入Pod查看挂载的文件:

kubectl exec -it pod-volume-config -- sh
cat /etc/nginx/conf.d/app.conf

输出:

server.port=8080
log.level=info

再查看目录下的文件:

ls -lh /etc/nginx/conf.d/

输出(类似):

total 0
lrwxrwxrwx 1 root root 13 Mar 20 10:00 app.conf -> ..data/app.conf

可以看到是符号链接,指向..data目录下的实际文件,这是为了支持热更新。

热更新

只有通过Volume挂载为文件的ConfigMap才支持热更新,环境变量方式不支持。下面用Nginx示例演示:

1. 准备Nginx配置文件和ConfigMap

创建nginx.conf文件:

server {
    listen 80;
    server_name localhost;

    location / {
        default_type text/plain;
        return 200 'Hello from original config!';
    }
}

创建ConfigMap:

kubectl create configmap nginx-config --from-file=nginx.conf

2. 创建Deployment挂载ConfigMap

创建nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-config-volume
          mountPath: /etc/nginx/conf.d/default.conf  # 直接挂载为Nginx默认配置文件
          subPath: nginx.conf  # 指定挂载的文件,避免覆盖整个目录
      volumes:
      - name: nginx-config-volume
        configMap:
          name: nginx-config

创建Deployment:

kubectl apply -f nginx-deployment.yaml

查看Pod:

kubectl get pods

输出(类似):

NAME                           READY   STATUS    RESTARTS   AGE
nginx-deploy-7f9d6c8b7d-2xqzk   1/1     Running   0          10s
nginx-deploy-7f9d6c8b7d-5m7tl   1/1     Running   0          10s
nginx-deploy-7f9d6c8b7d-8k4zp   1/1     Running   0          10s

3. 测试初始配置

访问其中一个Pod:

kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost

输出:Hello from original config!

4. 修改ConfigMap

编辑ConfigMap:

kubectl edit configmap nginx-config

nginx.conf内容改成:

server {
    listen 80;
    server_name localhost;

    location / {
        default_type text/plain;
        return 200 'Hello from updated config!';
    }
}

保存退出。

5. 查看ConfigMap更新后的Pod文件

等待10秒左右,进入Pod查看配置文件:

kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- cat /etc/nginx/conf.d/default.conf

输出:

server {
    listen 80;
    server_name localhost;

    location / {
        default_type text/plain;
        return 200 'Hello from updated config!';
    }
}

说明ConfigMap已经热更新到Pod的文件里了。

6. 重启Nginx使配置生效

但此时访问Pod还是返回旧内容,因为Nginx没重新加载配置:

kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost

输出:Hello from original config!
需要进入Pod重启Nginx:

kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- nginx -s reload

再访问:

kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost

输出:Hello from updated config!

7. 滚动重启Deployment(推荐)

手动重启每个Pod太麻烦,可通过修改Deployment的Annotation触发滚动重启:

kubectl patch deployment nginx-deploy -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$(date +%Y-%m-%dT%H:%M:%S)\"}}}}}"

查看Pod,会发现旧Pod被删除,新Pod创建:

kubectl get pods

新Pod会自动加载最新的ConfigMap配置,无需手动重启Nginx。

不可改变

1. 配置ConfigMap为不可改变

创建ConfigMap时指定immutable: true,或编辑现有ConfigMap添加该字段:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-immutable-config
data:
  key1: value1
immutable: true  # 设为不可改变

执行创建:

kubectl apply -f immutable-config.yaml

2. 特点

  • 设为不可改变后,无法修改ConfigMap的内容,也不能删除后重建同名的ConfigMap(除非先删除这个不可改变的ConfigMap);
  • 这个设置不允许回退,一旦设为immutable: true,就不能改回false,只能删除重建;
  • 优点是能提高性能(K8s无需监控其变化),并防止误修改配置。

3. 验证不可改变

尝试编辑:

kubectl edit configmap my-immutable-config

修改key1的值后保存,会报错:

error: configmaps "my-immutable-config" is immutable

Secret

概念

Secret是K8s用来存储敏感信息的资源,比如密码、密钥、证书、Token等。它和ConfigMap结构类似,但会对数据进行Base64编码(注意:Base64不是加密,只是编码,仍需通过权限控制保证安全),且只有Pod内的容器能解密访问。

  • 同样支持挂载为环境变量或文件;
  • 可被多个Pod共享,统一管理敏感信息;
  • 相比ConfigMap,Secret的访问权限更严格,默认不会被非授权用户查看。

类型

类型 用途
Opaque 默认类型,存储任意键值对的敏感信息(如密码、密钥)
kubernetes.io/dockerconfigjson 存储Docker镜像仓库的认证信息(拉取私有镜像用)
kubernetes.io/service-account-token 存储服务账号的Token,用于Pod访问K8s API Server
kubernetes.io/tls 存储TLS证书和私钥(如HTTPS证书)
bootstrap.kubernetes.io/token 用于K8s集群节点引导的Token

Opaque类型Secret

Opaque是最常用的Secret类型,数据以Base64编码存储,下面详细讲创建和使用。

创建

1. 通过YAML文件创建

首先把要存储的内容转成Base64编码:

echo -n "my-password" | base64  # 输出:bXktcGFzc3dvcmQ=
echo -n "admin" | base64        # 输出:YWRtaW4=

创建my-secret.yaml文件:

apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  username: YWRtaW4=  # Base64编码后的admin
  password: bXktcGFzc3dvcmQ=  # Base64编码后的my-password

执行创建:

kubectl apply -f my-secret.yaml

输出:secret/my-secret created

2. 从文件创建

创建db-pass.txt文件,内容为db-password-123,然后执行:

kubectl create secret generic my-secret-from-file --from-file=db-pass=db-pass.txt

输出:secret/my-secret-from-file created
这里db-pass是Secret里的键,db-pass.txt是本地文件。

3. 从字面量创建

直接指定键值对(K8s会自动转Base64):

kubectl create secret generic my-secret-from-literal --from-literal=redis-pass=redis-123456

输出:secret/my-secret-from-literal created

查看Secret

1. 查看Secret列表
kubectl get secrets

输出(类似):

NAME                      TYPE                                  DATA   AGE
my-secret                 Opaque                                2      20s
my-secret-from-file       Opaque                                1      10s
my-secret-from-literal    Opaque                                1      5s
default-token-xxxx        kubernetes.io/service-account-token   3      1d
2. 查看Secret详情
kubectl describe secret my-secret

输出(类似):

Name:         my-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password:  10 bytes
username:  5 bytes

注意:describe不会显示具体的Base64编码值,保证敏感信息不泄露。

3. 查看Secret的原始数据

如果需要查看具体内容,可执行:

kubectl get secret my-secret -o jsonpath='{.data}'

输出:{"password":"bXktcGFzc3dvcmQ=","username":"YWRtaW4="}
解码查看:

echo "bXktcGFzc3dvcmQ=" | base64 -d  # 输出:my-password
echo "YWRtaW4=" | base64 -d          # 输出:admin

使用

1. 作为环境变量

创建pod-secret-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-env
spec:
  containers:
  - name: busybox-container
    image: busybox:latest
    command: ["/bin/sh", "-c", "env | grep DB_"]
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: my-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: my-secret
          key: password

创建Pod并查看日志:

kubectl apply -f pod-secret-env.yaml
kubectl logs pod-secret-env

输出:

DB_USERNAME=admin
DB_PASSWORD=my-password
2. 作为配置文件(Volume挂载)

创建pod-secret-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-volume
spec:
  containers:
  - name: nginx-container
    image: nginx:alpine
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets  # 挂载到容器内的目录
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: my-secret  # 引用的Secret名称

创建Pod并查看挂载的文件:

kubectl apply -f pod-secret-volume.yaml
kubectl exec -it pod-secret-volume -- sh
ls /etc/secrets/

输出:password username
查看文件内容:

cat /etc/secrets/username  # 输出:admin
cat /etc/secrets/password  # 输出:my-password

注意:挂载的文件内容是Base64解码后的原始值,容器可直接使用。

3. 自定义文件权限

默认挂载的Secret文件权限是0644,可通过defaultMode修改:

volumes:
- name: secret-volume
  secret:
    secretName: my-secret
    defaultMode: 0400  # 只有所有者可读

创建Pod后查看权限:

kubectl exec -it pod-secret-volume -- ls -l /etc/secrets/

输出(类似):

-r-------- 1 root root 5 Mar 20 12:00 username
-r-------- 1 root root 10 Mar 20 12:00 password
4. 挂载指定的Key

如果只需要挂载Secret中的部分Key,可通过items指定:

volumes:
- name: secret-volume
  secret:
    secretName: my-secret
    items:
    - key: username
      path: db/user  # 挂载到/etc/secrets/db/user
      mode: 0600

创建Pod后查看:

kubectl exec -it pod-secret-volume -- ls /etc/secrets/db/
cat /etc/secrets/db/user  # 输出:admin

注意:通过items指定Key挂载的方式不支持热更新,Secret修改后Pod不会自动更新,需重启Pod。

不可更改

和ConfigMap一样,Secret也可设置为不可改变:

apiVersion: v1
kind: Secret
metadata:
  name: my-immutable-secret
type: Opaque
data:
  key: dmFsdWU=
immutable: true

设为不可改变后,无法修改Secret内容,也不能删除重建同名Secret,除非先删除该Secret。

Downward API

概念

Downward API是K8s提供的一种机制,能把Pod自身的元数据(比如Pod名称、IP、标签、资源限制等)注入到容器内部。简单说,就是让容器能“知道”自己所在Pod的信息,无需通过K8s API Server查询(当然也可以通过API Server查,后面会讲)。

  • 支持两种注入方式:环境变量和Volume挂载;
  • 注入的是Pod启动时的元数据,部分数据(如Pod IP)会在Pod生命周期中保持不变,部分数据(如资源使用)不会实时更新。

使用案例

1. 作为环境变量注入

创建pod-downward-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-downward-env
  labels:
    app: my-app
    tier: frontend
  annotations:
    author: "k8s-user"
    version: "1.0"
spec:
  containers:
  - name: busybox-container
    image: busybox:latest
    command: ["/bin/sh", "-c", "env | grep POD_"]
    resources:
      requests:
        cpu: "100m"  # 0.1核
        memory: "128Mi"
      limits:
        cpu: "200m"
        memory: "256Mi"
    env:
    # Pod名称
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    # Pod命名空间
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    # Pod IP
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    # Pod标签(JSON格式)
    - name: POD_LABELS
      valueFrom:
        fieldRef:
          fieldPath: metadata.labels
    # CPU请求
    - name: CPU_REQUEST
      valueFrom:
        resourceFieldRef:
          containerName: busybox-container
          resource: requests.cpu
    # 内存限制
    - name: MEMORY_LIMIT
      valueFrom:
        resourceFieldRef:
          containerName: busybox-container
          resource: limits.memory

创建Pod并查看日志:

kubectl apply -f pod-downward-env.yaml
kubectl logs pod-downward-env

输出(类似):

POD_NAME=pod-downward-env
POD_NAMESPACE=default
POD_IP=10.244.1.10
POD_LABELS={"app":"my-app","tier":"frontend"}
CPU_REQUEST=100m
MEMORY_LIMIT=268435456

2. 通过Volume挂载注入

创建pod-downward-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-downward-volume
  labels:
    app: my-app
    tier: frontend
  annotations:
    author: "k8s-user"
    version: "1.0"
spec:
  containers:
  - name: nginx-container
    image: nginx:alpine
    volumeMounts:
    - name: downward-volume
      mountPath: /etc/pod-info
      readOnly: true
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        cpu: "200m"
        memory: "256Mi"
  volumes:
  - name: downward-volume
    downwardAPI:
      items:
      # Pod名称
      - path: "pod_name"
        fieldRef:
          fieldPath: metadata.name
      # Pod注解
      - path: "pod_annotations"
        fieldRef:
          fieldPath: metadata.annotations
      # CPU限制
      - path: "cpu_limit"
        resourceFieldRef:
          containerName: nginx-container
          resource: limits.cpu
      # 内存请求
      - path: "memory_request"
        resourceFieldRef:
          containerName: nginx-container
          resource: requests.memory

创建Pod并查看挂载的文件:

kubectl apply -f pod-downward-volume.yaml
kubectl exec -it pod-downward-volume -- sh
ls /etc/pod-info/

输出:cpu_limit memory_request pod_annotations pod_name
查看文件内容:

cat /etc/pod-info/pod_name  # 输出:pod-downward-volume
cat /etc/pod-info/cpu_limit  # 输出:200m
cat /etc/pod-info/memory_request  # 输出:134217728
cat /etc/pod-info/pod_annotations  # 输出:{"author":"k8s-user","version":"1.0"}

通过API Server获取集群元数据

除了Downward API,也可以在Pod内通过K8s API Server获取集群的元数据(比如其他Pod、Service信息)。需要给Pod绑定Service Account并授权:

1. 创建Service Account和Role

创建sa-role.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-reader-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader-binding
subjects:
- kind: ServiceAccount
  name: pod-reader-sa
roleRef:
  kind: Role
  name: pod-reader-role
  apiGroup: rbac.authorization.k8s.io

执行创建:

kubectl apply -f sa-role.yaml

2. 创建Pod使用Service Account

创建pod-api-server.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-api-server
spec:
  serviceAccountName: pod-reader-sa  # 使用上面创建的SA
  containers:
  - name: curl-container
    image: curlimages/curl:latest
    command: ["/bin/sh", "-c", "while true; do curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods; sleep 10; done"]

创建Pod并查看日志:

kubectl apply -f pod-api-server.yaml
kubectl logs -f pod-api-server

输出会包含default命名空间下所有Pod的详细信息(JSON格式)。
其中:

  • KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT是Pod内默认的环境变量,指向API Server;
  • /var/run/secrets/kubernetes.io/serviceaccount/ca.crt是集群CA证书,用于验证API Server身份;
  • /var/run/secrets/kubernetes.io/serviceaccount/token是Service Account的Token,用于认证(curl会自动使用?或需要在Header里指定:-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)")。

补充:Pod访问Service的方式

  • 同一命名空间:直接用Service名称访问(比如curl my-service:80);
  • 不同命名空间:用全限定名访问(比如curl my-service.my-namespace.svc.cluster.local:80)。

K8s存储核心笔记:Volume与PV/PVC

第一章 Volume:容器的基础存储机制

1.1 核心概念

Volume是K8s为容器提供的数据存储抽象,本质是将“存储资源”与“容器生命周期”解耦,解决容器重启/迁移后数据丢失的问题。它不是容器内的临时目录,而是独立于容器的存储空间,可被Pod内的多个容器共享。

存在的核心意义

  • 持久化存储:容器终止后,Volume中的数据不会丢失(部分类型除外,如emptyDir);

  • 数据共享:同一Pod内的多个容器可挂载同一个Volume,实现数据互通;

  • 存储解耦:支持多种底层存储(本地目录、NFS、云存储等),容器无需关注存储实现细节。

1.2 常见Volume类型

Volume类型按用途可分为“特殊配置存储”和“通用数据存储”,前序学习的Secret、ConfigMap、DownwardAPI均属于特殊Volume,核心用于配置/元数据注入;以下为重点学习的通用Volume类型:

1.2.1 emptyDir:临时空目录(Pod级存储)

定义:Pod启动时自动创建的空目录,绑定于Pod生命周期——Pod删除时,emptyDir中的数据会被彻底删除;但容器重启时,数据会保留。

核心特性

  • 绑定Pod级别,而非容器级别;

  • 同一Pod内的多个容器可共享同一个emptyDir;

  • 无需配置底层存储,K8s自动在节点上分配临时空间;

  • 适用场景:容器间临时数据交换(如日志缓存、临时计算结果)。

1.2.2 hostPath:主机路径挂载(节点级存储)

定义:将Pod调度到的“节点主机”上的目录/文件,直接挂载到容器内部,实现容器与主机的文件系统互通。

核心特性

  • 绑定Pod级别,依赖节点主机的文件系统;

  • Pod迁移到其他节点时,挂载的是新节点的对应路径(数据不共享);

  • 适用场景:容器需访问主机文件(如监控主机日志、运行依赖主机设备的应用)。

hostPath的8种类型(控制路径存在性校验)

类型 含义 挂载结果
空字符串 默认,无校验 路径不存在则挂载失败
DirectoryOrCreate 目录,不存在则创建 自动创建目录(权限0755,属主root)
Directory 目录,必须存在 路径不存在或非目录则挂载失败
FileOrCreate 文件,不存在则创建 自动创建文件(权限0644,属主root)
File 文件,必须存在 路径不存在或非文件则挂载失败
Socket UNIX域套接字,必须存在 路径非套接字则挂载失败
CharDevice 字符设备,必须存在 路径非字符设备则挂载失败
BlockDevice 块设备,必须存在 路径非块设备则挂载失败

hostPath使用注意事项

  • 节点差异性:相同配置的Pod在不同节点上,挂载的主机路径内容可能不同;

  • 资源调度问题:K8s调度Pod时,不会考虑hostPath占用的主机资源(可能导致节点存储耗尽);

  • 权限问题:容器内用户可能无主机路径的读写权限,需通过`securityContext`配置权限。

1.2.3 其他核心类型

  • nfs:挂载NFS服务器的共享目录,支持多节点数据共享(RWX模式),是分布式场景的常用选择;

  • persistentVolumeClaim(PVC):通过PVC挂载PV,是持久化存储的核心方式(详见第二章);

  • local:挂载节点本地的持久化存储(如本地磁盘分区),性能好但不支持Pod迁移;

  • csi:容器存储接口,统一对接外部存储(如AWS EBS、阿里云OSS),是云原生存储的标准方案。

1.3 hostPath实操实验(含详细步骤与输出)

实验目标

创建一个Pod,将主机的`/test`目录挂载到容器内的`/hostpath`,验证容器与主机的文件共享。

实验步骤

  1. 编写Pod配置文件(hostpath-pod.yaml)apiVersion: v1 kind: Pod metadata: name: hostpath-pod # Pod名称 spec: containers: - name: hostpath-container # 容器名称 image: nginx:alpine # 轻量Nginx镜像 volumeMounts: - name: hostpath-volume # 与volumes.name对应 mountPath: /hostpath # 容器内挂载路径 volumes: - name: hostpath-volume # 卷名称 hostPath: path: /test # 主机上的路径(Pod调度到的节点) type: DirectoryOrCreate # 目录不存在则创建

  2. 创建Pod并查看状态# 提交配置创建Pod kubectl apply -f hostpath-pod.yaml # 查看Pod状态(确保Running) kubectl get pods hostpath-pod输出示例NAME READY STATUS RESTARTS AGE hostpath-pod 1/1 Running 0 30s

  3. 验证主机路径自动创建: 先获取Pod调度到的节点,再登录节点查看`/test`目录: # 查看Pod所在节点(记下列出的NODE_NAME) kubectl describe pod hostpath-pod | grep "Node:" # 登录节点(假设节点名为node-1,根据实际情况替换) ssh node-1 # 查看/test目录是否存在 ls -ld /test输出示例drwxr-xr-x 2 root root 4096 11月 30 18:00 /test说明:`DirectoryOrCreate`类型已自动创建`/test`目录,权限为0755。

  4. 验证容器与主机的文件共享: ① 主机节点创建测试文件: echo "This is a test file from host" > /test/host-file.txt② 进入容器查看文件: # 进入容器终端 kubectl exec -it hostpath-pod -- sh # 查看容器内/hostpath目录下的文件 ls /hostpath # 读取文件内容(验证与主机一致) cat /hostpath/host-file.txt输出示例/ # ls /hostpath host-file.txt / # cat /hostpath/host-file.txt This is a test file from host③ 容器内创建文件,主机验证: # 容器内创建文件 echo "This is from container" > /hostpath/container-file.txt # 退出容器,在主机节点查看 exit # 退出容器 cat /test/container-file.txt输出示例This is from container

  5. 清理资源# 删除Pod kubectl delete pod hostpath-pod # (可选)删除主机/test目录 ssh node-1 "rm -rf /test"

实验结论

hostPath实现了容器与主机的文件互通,`DirectoryOrCreate`类型确保路径不存在时自动创建,适合需要访问主机文件系统的场景。

第二章 PV/PVC:持久化存储的核心方案

Volume(如hostPath、emptyDir)存在“存储供给与使用强耦合”的问题——业务工程师需手动指定底层存储(如NFS路径),与存储团队沟通成本高。PV/PVC通过“抽象层”解决此问题,实现存储供给与使用的分工协作。

2.1 核心概念与分工价值

PV(PersistentVolume,持久卷)

存储工程师/基础设施团队创建,是对底层物理存储(如EMC、Ceph、NFS)的K8s标准化抽象,代表“可用的持久化存储资源”。

核心特点:独立于Pod生命周期,Pod删除后PV仍存在;包含存储容量、访问模式等固定属性。

PVC(PersistentVolumeClaim,持久卷申领)

业务工程师创建,是Pod对存储的“需求声明”,描述所需存储的容量、访问模式、存储类别等,无需关注底层存储细节。

核心特点:通过匹配PV完成绑定,Pod只需挂载PVC即可使用存储,实现“按需申领”。

分工价值(核心优势)

  • 解耦协作:存储团队管PV(供给),业务团队管PVC(使用),无需直接沟通;

  • 标准化:PV定义统一存储属性,PVC按标准声明需求,避免配置混乱;

  • 资源复用:一个PV可被多个PVC复用(解绑后),提高存储利用率。

2.2 PV与PVC的核心属性

2.2.1 PV的核心属性(存储侧定义)

属性 含义 示例/说明
容量(capacity) 存储资源大小 `capacity: {storage: 10Gi}`
访问模式(accessModes) 存储的读写权限范围 RWO/ROX/RWX(三选一或多选,需底层存储支持)
存储类别(storageClassName) PV的“分组标识”,用于PVC精准匹配 `storageClassName: "gold"`(金牌存储)
回收策略(persistentVolumeReclaimPolicy) PVC解绑后PV的处理方式 Retain(保留)/Delete(删除)/Recycle(回收)
存储源(spec) 关联的底层存储(如NFS、本地存储) `nfs: {server: 192.168.1.100, path: /nfs/share}`

2.2.2 PVC的核心属性(业务侧声明)

属性 含义 示例/说明
资源需求(resources) 所需存储容量 `resources: {requests: {storage: 5Gi}}`
访问模式(accessModes) 需与PV完全匹配 `accessModes: ["ReadWriteOnce"]`
存储类别(storageClassName) 需与PV完全匹配(空值匹配空值PV) `storageClassName: "gold"`

2.3 核心知识点:访问模式(Access Modes)

访问模式决定了PV可被多少节点/容器访问,是PV与PVC匹配的关键条件,必须完全一致。共三种模式,需结合底层存储支持选择:

模式 缩写 含义 支持的存储示例 限制说明
单节点读写 RWO 仅允许一个节点上的多个容器读写 hostPath、iSCSI、云盘(AWS EBS) 不支持多节点共享,Pod迁移后需重新挂载
多节点只读 ROX 允许多个节点上的容器只读访问 NFS、CephFS、GlusterFS 所有节点均无写入权限,适合共享配置文件
多节点读写 RWX 允许多个节点上的容器读写访问 NFS、CephFS、GlusterFS 需底层存储支持“网络锁”(如NFS的文件锁),本地文件系统(EXT4/XFS)不支持

关键提醒:iSCSI、hostPath等块存储/本地存储不支持RWX,因EXT4/XFS等本地文件系统只有“本地锁”,无“网络锁”——多节点同时写入会导致文件损坏或数据覆盖。

2.4 PV与PVC的匹配流程

PVC通过“预选+优选”两步匹配PV,最终建立唯一绑定关系,流程如下:

  1. 预选阶段:筛选满足基本条件的PVPVC会过滤掉不符合以下条件的PV:PV容量 ≥ PVC声明的容量;

  2. PV的访问模式包含PVC声明的模式;

  3. PV的存储类别与PVC完全一致;

  4. PV状态为“Available”(可用)。

  5. 优选阶段:选择最优PV在预选通过的PV中,按“资源浪费最少”原则排序,优先选择:容量与PVC完全匹配的PV;

  6. 容量最接近PVC需求的PV(如PVC需5Gi,优先5Gi而非6Gi)。

  7. 绑定阶段:建立唯一关联PVC与最优PV绑定后,PV状态变为“Bound”(绑定),仅该PVC可使用此PV;Pod通过挂载PVC,间接使用PV对应的底层存储。

匹配示例

PVC需求:5Gi容量、RWO模式、存储类别“silver”;

可选PV:

  • PV1:4Gi、RWO、silver → 容量不足,预选淘汰;

  • PV2:5Gi、RWO、silver → 完全匹配,优选第一;

  • PV3:6Gi、RWO、silver → 容量过剩,优选第二;

  • PV4:5Gi、RWX、silver → 访问模式不匹配,预选淘汰;

  • PV5:5Gi、RWO、gold → 存储类别不匹配,预选淘汰。

最终结果:PVC与PV2绑定。

2.5 PV的回收策略与状态

2.5.1 回收策略(PVC解绑后PV的处理方式)

回收策略决定了PVC删除后,PV及其数据的命运,需根据数据重要性选择:

策略 核心行为 适用场景 支持的存储类型
Retain(保留) 1. PV状态变为“Released”(已释放);2. 数据保留,不允许新PVC绑定;3. 需管理员手动清理数据并重置PV状态。 高价值数据(如金融、核心业务数据),防止误删 所有存储类型
Recycle(回收) 1. 自动执行`rm -rf /path/*`清理数据;2. 清理成功后PV状态变为“Available”,可复用。 低价值临时数据,允许数据清除 仅NFS、HostPath
Delete(删除) 1. 自动删除PV;2. 同时删除底层存储资源(如云盘、NFS共享)。 按需付费的云存储,节省成本 云存储(AWS EBS、阿里云云盘)、动态供给PV

生产环境建议:优先选择Retain策略,即使误删PVC,数据仍可通过管理员手动恢复;Recycle策略因清理不彻底(可能残留隐藏文件),不建议生产使用。

2.5.2 PV的四种状态

PV的状态反映其生命周期阶段,通过`kubectl get pv`可查看:

状态 含义 触发场景
Available(可用) PV已创建,等待PVC绑定 PV刚创建完成,无PVC绑定
Bound(绑定) PV已与PVC成功绑定 PVC匹配并绑定PV后
Released(已释放) PVC已删除,但PV数据未清理,不可复用 PVC删除,PV回收策略为Retain
Failed(失败) PV回收策略执行失败,需人工介入 Recycle清理文件失败、Delete删除底层存储失败

2.6 PVC保护机制

为防止数据丢失,K8s内置PVC保护机制,避免存储资源被误删:

  1. PV保护:若PV已绑定PVC,直接执行`kubectl delete pv `会被禁止,需先删除关联的PVC;

  2. Pod保护:若Pod正在使用PVC,执行`kubectl delete pvc `后,PVC状态会变为“Terminating”(终止中),但不会立即删除,需等Pod终止后才彻底删除。

2.7 实操案例:基于NFS的PV/PVC完整流程(含代码与输出)

以NFS为底层存储,实现“存储侧创建PV→业务侧创建PVC→Pod挂载使用”的完整流程。

环境准备

假设NFS服务器地址为`192.168.1.100`,共享目录为`/nfs/k8s`(需提前创建并配置权限)。

步骤1:存储侧创建PV(nfs-pv.yaml)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv  # PV名称
spec:
  capacity:
    storage: 10Gi  # PV容量
  accessModes:
    - ReadWriteMany  # 支持多节点读写(NFS特性)
  storageClassName: "nfs-storage"  # 存储类别
  persistentVolumeReclaimPolicy: Retain  # 回收策略:保留
  nfs:  # 关联NFS存储
    server: 192.168.1.100  # NFS服务器地址
    path: /nfs/k8s  # NFS共享目录

执行创建并查看PV状态: kubectl apply -f nfs-pv.yaml kubectl get pv nfs-pv输出示例NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv 10Gi RWX Retain Available nfs-storage 10s状态为“Available”,表示PV已就绪等待绑定。

步骤2:业务侧创建PVC(nfs-pvc.yaml)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc  # PVC名称
spec:
  accessModes:
    - ReadWriteMany  # 与PV的访问模式匹配
  resources:
    requests:
      storage: 5Gi  # 需求容量(≤PV容量)
  storageClassName: "nfs-storage"  # 与PV的存储类别匹配

执行创建并查看PVC状态: kubectl apply -f nfs-pvc.yaml kubectl get pvc nfs-pvc输出示例NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nfs-pvc Bound nfs-pv 10Gi RWX nfs-storage 5s状态为“Bound”,表示PVC已与nfs-pv成功绑定。此时再查看PV状态,会发现“CLAIM”列显示“default/nfs-pvc”。

步骤3:创建Pod挂载PVC(nfs-pod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: nfs-container
    image: nginx:alpine
    volumeMounts:
    - name: nfs-volume  # 与volumes.name对应
      mountPath: /usr/share/nginx/html  # Nginx网页根目录
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc  # 关联创建的PVC

执行创建并验证: # 创建Pod kubectl apply -f nfs-pod.yaml # 查看Pod状态 kubectl get pods nfs-pod # 在NFS服务器的共享目录创建测试页面 echo "<h1>Hello PV/PVC!</h1>" > /nfs/k8s/index.html # 访问Pod的Nginx服务(验证数据共享) kubectl exec -it nfs-pod -- curl localhost输出示例<h1>Hello PV/PVC!</h1>说明Pod通过PVC成功挂载NFS存储,且能读取底层存储的文件。

步骤4:清理资源

# 删除Pod
kubectl delete pod nfs-pod

# 删除PVC(此时PV状态变为Released)
kubectl delete pvc nfs-pvc

# (可选)删除PV(需先确认PVC已删除)
kubectl delete pv nfs-pv

# (可选)删除NFS共享目录内容
rm -rf /nfs/k8s/*

2.8 核心总结

  • Volume:基础存储单元,解决容器数据临时存储与共享,依赖具体存储实现(如hostPath、NFS);

  • PV/PVC:持久化存储的核心抽象,PV是“存储资源”,PVC是“需求声明”,通过匹配机制实现分工解耦;

  • 关键匹配条件:容量≥需求、访问模式完全匹配、存储类别完全匹配;

  • 核心流程:存储侧建PV→业务侧建PVC→PVC匹配PV→Pod挂载PVC使用存储。

K8s 存储核心知识点笔记

一、Volume 基础

1. 概念通俗理解

K8s 中的 Volume 是 Pod 内容器共享的存储目录,本质是“Pod 级别的存储卷”。它和 Pod 生命周期绑定(Pod 销毁则 Volume 销毁,临时存储除外),可实现容器间数据共享、数据持久化(对接外部存储)。

2. 常见 Volume 类型

  • 临时存储emptyDir(Pod 内临时目录,Pod 销毁即删)、tmpfs(内存级存储,重启丢失);
  • 本地存储hostPath(挂载节点本地目录,跨节点不可用);
  • 网络存储nfscephfsglusterfs(跨节点共享,支持持久化);
  • 云厂商存储awsElasticBlockStore(AWS EBS)、gcePersistentDisk(GCE 云盘)。

3. 简单案例:Pod 挂载 emptyDir 实现容器共享

# pod-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-emptyDir
spec:
  containers:
  - name: container-1
    image: nginx:1.14-alpine
    volumeMounts:
    - name: shared-dir
      mountPath: /usr/share/nginx/html  # 容器1挂载路径
  - name: container-2
    image: busybox:1.35
    command: ["sh", "-c", "echo 'Hello K8s Volume' > /shared/index.html && sleep 3600"]
    volumeMounts:
    - name: shared-dir
      mountPath: /shared  # 容器2挂载路径
  volumes:
  - name: shared-dir
    emptyDir: {}  # 临时共享目录

执行步骤与输出

  1. 创建 Pod:
    kubectl apply -f pod-emptyDir.yaml
    
  2. 查看 Pod 状态:
    kubectl get pod test-emptyDir
    # 输出:
    # NAME            READY   STATUS    RESTARTS   AGE
    # test-emptyDir   2/2     Running   0          10s
    
  3. 验证数据共享:
    kubectl exec test-emptyDir -c container-1 -- cat /usr/share/nginx/html/index.html
    # 输出:Hello K8s Volume
    
  4. 删除 Pod 后,emptyDir 数据随 Pod 销毁。

二、PV/PVC 核心机制

1. 概念通俗理解

  • PV(PersistentVolume):集群级别的“存储资源池”,由运维人员提前创建,定义了具体的存储类型(如 NFS、云盘)、容量、访问模式等,与 Pod 解耦。
  • PVC(PersistentVolumeClaim):Pod 对存储的“申请单”,由开发人员创建,声明需要的存储容量、访问模式,K8s 会自动匹配符合条件的 PV 进行绑定。

核心价值:业务侧无需关注底层存储细节(如 NFS 服务器地址),仅通过 PVC 声明需求;存储侧通过 PV 统一管理资源,实现存储与业务解耦。

2. PV 关键字段详解

(1)spec.capacity 存储容量声明

  • 作用:定义 PV 能提供的最大存储容量,是 PVC 匹配 PV 的核心“容量门槛”。
  • 格式要求
    • 固定键为 storage,值为带单位的存储量;
    • 支持单位:二进制单位(Ki/Mi/Gi/Ti,推荐)、十进制单位(K/M/G/T);
    • 示例:capacity: { storage: 10Gi }
  • 匹配逻辑
    • PVC 声明的 resources.requests.storage 必须 ≤ PV 的 capacity.storage,否则无法绑定;
    • 多 PV 满足条件时,优先匹配容量最接近 PVC 需求的 PV(如 PVC 需 5Gi,优先选 5Gi PV 而非 10Gi PV)。
  • 与底层存储的关系
    PV 声明的容量需 ≤ 底层存储实际可用空间(如 NFS 共享目录实际容量为 20Gi,则 PV 容量不能超过 20Gi),否则会出现“存储空间不足”错误。

(2)spec.accessModes 访问模式

访问模式 含义
ReadWriteOnce(RWO) 仅允许单个节点以读写方式挂载(适合单 Pod 独占存储,如本地磁盘)
ReadWriteMany(RWX) 允许多个节点以读写方式挂载(适合多 Pod 共享存储,如 NFS)
ReadOnlyMany(ROX) 允许多个节点以只读方式挂载(适合多 Pod 共享只读数据,如配置文件)

(3)spec.storageClassName 存储类

用于关联 StorageClass,支持动态创建 PV(后续章节详解)。

(4)spec.persistentVolumeReclaimPolicy 回收策略

策略 含义
Retain PVC 删除后,PV 保留数据并标记为 Released,需手动清理后可复用
Delete PVC 删除后,PV 及底层存储数据自动删除(适合云盘等动态存储)
Recycle PVC 删除后,PV 自动清空数据并标记为 Available(已废弃,不推荐)

3. PV/PVC 实操:基于 NFS 实现存储持久化

步骤 1:搭建 NFS 服务器(CentOS 7/8 环境)

(1)NFS 服务端部署(单节点)
# 1. 安装 NFS 服务
yum install -y nfs-utils rpcbind

# 2. 创建 NFS 共享目录
mkdir -p /exports/nfs
chmod 777 /exports/nfs  # 测试环境放宽权限,生产环境需严格控制

# 3. 配置 NFS 共享规则
echo "/exports/nfs *(rw,sync,no_root_squash,no_all_squash)" > /etc/exports

# 4. 启动服务并设置开机自启
systemctl start rpcbind nfs-server
systemctl enable rpcbind nfs-server

# 5. 验证 NFS 共享
showmount -e localhost
# 输出:
# Export list for localhost:
# /exports/nfs *
(2)K8s 所有节点安装 NFS 客户端
yum install -y nfs-utils
# 验证客户端连通性(任选一个节点)
mount -t nfs <NFS服务器IP>:/exports/nfs /mnt
touch /mnt/test.txt
ls /mnt/test.txt  # 能看到文件则连通成功
umount /mnt

步骤 2:创建 PV(存储资源池)

# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-0  # 第一个 PV
spec:
  capacity:
    storage: 10Gi  # 容量 10Gi
  accessModes:
    - ReadWriteMany  # 支持多节点读写
  nfs:
    path: /exports/nfs
    server: <NFS服务器IP>  # 替换为实际 NFS 服务器地址
  persistentVolumeReclaimPolicy: Retain  # 回收策略为保留数据
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-1  # 第二个 PV
spec:
  capacity:
    storage: 5Gi  # 容量 5Gi(用于测试容量匹配逻辑)
  accessModes:
    - ReadWriteMany
  nfs:
    path: /exports/nfs
    server: <NFS服务器IP>
  persistentVolumeReclaimPolicy: Retain

执行与验证

kubectl apply -f pv-nfs.yaml

# 查看 PV 状态(初始为 Available,未绑定 PVC)
kubectl get pv
# 输出:
# NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
# nfs-pv-0    10Gi       RWX            Retain           Available                                   10s
# nfs-pv-1    5Gi        RWX            Retain           Available                                   10s

步骤 3:创建 PVC(存储申请单)

# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany  # 需与 PV 访问模式匹配
  resources:
    requests:
      storage: 5Gi  # 申请 5Gi 存储,优先匹配 nfs-pv-1

执行与验证

kubectl apply -f pvc-nfs.yaml

# 查看 PVC 与 PV 绑定状态
kubectl get pvc
# 输出:
# NAME      STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
# nfs-pvc   Bound    nfs-pv-1    5Gi        RWX                           10s

kubectl get pv
# 输出:
# NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
# nfs-pv-0    10Gi       RWX            Retain           Available                                   20s
# nfs-pv-1    5Gi        RWX            Retain           Bound    default/nfs-pvc                           20s

结论:PVC 会优先匹配容量最接近的 PV(5Gi PVC 绑定了 5Gi 的 nfs-pv-1,而非 10Gi 的 nfs-pv-0)。

步骤 4:Pod 挂载 PVC 实现数据持久化

# pod-pvc-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pvc-nfs
spec:
  containers:
  - name: nginx
    image: nginx:1.14-alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - name: nfs-storage
      mountPath: /usr/share/nginx/html  # 挂载到 Nginx 网页根目录
  volumes:
  - name: nfs-storage
    persistentVolumeClaim:
      claimName: nfs-pvc  # 关联已创建的 PVC

执行与验证

  1. 创建 Pod 并写入测试数据:
    kubectl apply -f pod-pvc-nfs.yaml
    kubectl exec test-pvc-nfs -- sh -c "echo 'Hello PV/PVC' > /usr/share/nginx/html/index.html"
    
  2. 验证 Pod 内数据:
    kubectl exec test-pvc-nfs -- curl localhost
    # 输出:Hello PV/PVC
    
  3. 验证 NFS 服务端数据(持久化):
    cat /exports/nfs/index.html  # NFS 服务器上执行
    # 输出:Hello PV/PVC
    
  4. 删除 Pod 后重建,验证数据不丢失:
    kubectl delete pod test-pvc-nfs
    kubectl apply -f pod-pvc-nfs.yaml
    kubectl exec test-pvc-nfs -- curl localhost
    # 仍输出:Hello PV/PVC(数据通过 NFS 持久化)
    

三、有状态服务与 StatefulSet

1. 有状态服务 vs 无状态服务

类型 核心特征 典型应用
无状态服务 实例无唯一身份,可随意替换,数据不持久化(或依赖外部存储) Web 应用、缓存服务(Redis 集群除外)
有状态服务 实例有唯一身份(如编号),需持久化数据,实例间有依赖/顺序关系 数据库(MySQL)、消息队列(Kafka)

关键差异:无状态服务 Pod 重建后可直接加入集群;有状态服务 Pod 重建后需保留身份和数据,否则无法正常提供服务。

2. StatefulSet 核心特性

StatefulSet 是 K8s 专门管理有状态服务的控制器,核心特性:

  • 稳定的 Pod 身份:Pod 名称为 <StatefulSet名称>-<序号>(如 nginx-sts-0nginx-sts-1),序号从 0 开始递增且唯一;
  • 有序部署/删除:部署时按序号从 0 到 N 依次创建,删除时按 N 到 0 依次销毁;
  • 稳定的网络标识:通过无头服务(Headless Service)为每个 Pod 分配固定域名(<Pod名称>.<无头服务名>.<命名空间>.svc.cluster.local);
  • 稳定的存储:通过 volumeClaimTemplates 为每个 Pod 自动创建独立 PVC,Pod 重建后仍绑定原 PVC,数据不丢失。

3. StatefulSet 实操:基于 NFS 部署有状态 Nginx 集群

步骤 1:创建无头服务(Headless Service)

无头服务(ClusterIP=None)无负载均衡能力,仅为 Pod 提供稳定 DNS 解析。

# svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  clusterIP: None  # 标记为无头服务
  selector:
    app: nginx-sts  # 匹配 StatefulSet 的 Pod
  ports:
  - port: 80
    targetPort: 80

执行与验证

kubectl apply -f svc-headless.yaml

# 查看无头服务
kubectl get svc nginx-svc
# 输出:
# NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
# nginx-svc   ClusterIP   None         <none>        80/TCP    10s

步骤 2:创建 StatefulSet(关联 PVC 模板)

# sts-nginx.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sts
spec:
  serviceName: nginx-svc  # 关联无头服务
  replicas: 3  # 3 个有状态实例
  selector:
    matchLabels:
      app: nginx-sts
  template:
    metadata:
      labels:
        app: nginx-sts
    spec:
      containers:
      - name: nginx
        image: nginx:1.14-alpine
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www  # 关联 PVC 模板
          mountPath: /usr/local/nginx/html
  volumeClaimTemplates:  # PVC 模板,每个 Pod 自动创建独立 PVC
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteMany" ]  # 与 NFS PV 匹配
      resources:
        requests:
          storage: 1Gi  # 每个 Pod 申请 1Gi 存储

执行与验证

  1. 创建 StatefulSet:
    kubectl apply -f sts-nginx.yaml
    
  2. 查看 Pod 有序创建过程:
    watch kubectl get pod
    # 输出(依次创建 0→1→2):
    # NAME          READY   STATUS    RESTARTS   AGE
    # nginx-sts-0   1/1     Running   0          10s
    # nginx-sts-1   1/1     Running   0          5s
    # nginx-sts-2   1/1     Running   0          2s
    
  3. 查看自动创建的 PVC(每个 Pod 对应一个 PVC):
    kubectl get pvc
    # 输出:
    # NAME            STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    # www-nginx-sts-0 Bound    nfs-pv-0    10Gi       RWX                           20s
    # www-nginx-sts-1 Bound    nfs-pv-1    5Gi        RWX                           15s
    # www-nginx-sts-2 Bound    <新PV>      10Gi       RWX                           10s
    
    (注:若现有 PV 不足,需提前创建更多 NFS PV)

步骤 3:验证 StatefulSet 核心特性

(1)稳定的网络标识(DNS 解析)

在集群内任意 Pod 执行 DNS 解析测试:

# 临时创建测试 Pod
kubectl run -it --rm dns-test --image=busybox:1.35 -- sh

# 解析 Pod 域名
nslookup nginx-sts-0.nginx-svc.default.svc.cluster.local
# 输出(包含 Pod 真实 IP):
# Name:      nginx-sts-0.nginx-svc.default.svc.cluster.local
# Address 1: <Pod-0-IP>

nslookup nginx-sts-1.nginx-svc.default.svc.cluster.local
# 输出:Address 1: <Pod-1-IP>
(2)稳定的存储(Pod 重建后数据不丢失)
# 1. 给 nginx-sts-0 写入专属数据
kubectl exec nginx-sts-0 -- sh -c "echo 'I am Pod 0' > /usr/local/nginx/html/index.html"

# 2. 删除 nginx-sts-0 触发重建
kubectl delete pod nginx-sts-0

# 3. 重建后验证数据
kubectl exec nginx-sts-0 -- curl localhost
# 输出:I am Pod 0(数据通过 PVC 持久化)
(3)有序扩容/缩容
# 扩容到 4 个实例(按 0→1→2→3 顺序创建)
kubectl scale sts nginx-sts --replicas=4

# 缩容到 2 个实例(按 3→2 顺序删除)
kubectl scale sts nginx-sts --replicas=2

步骤 4:StatefulSet 与 PVC 清理

# 1. 删除 StatefulSet(Pod 会被删除,但 PVC 保留)
kubectl delete sts nginx-sts

# 2. 手动删除 PVC(PVC 删除后 PV 变为 Released 状态)
kubectl delete pvc www-nginx-sts-0 www-nginx-sts-1 www-nginx-sts-2

# 3. 复用 Released 状态的 PV(编辑 PV 删除 claimRef 字段)
kubectl edit pv nfs-pv-0
# 删除 spec.claimRef 字段后保存,PV 状态恢复为 Available
kubectl get pv nfs-pv-0
# 输出:nfs-pv-0    10Gi       RWX            Retain           Available                                   10m

四、StorageClass 动态存储供给

1. 概念通俗理解

手动创建 PV 存在“资源浪费、扩缩容麻烦”的问题,StorageClass 实现了 PV 动态创建:当 PVC 声明关联的 StorageClass 时,K8s 会通过 provisioner(存储供应器)自动创建符合需求的 PV,无需运维手动预创建。

2. 核心组件

  • StorageClass:定义存储类型(如 NFS、Ceph)、provisioner、回收策略等;
  • Provisioner:存储供应器,负责实际创建 PV 及底层存储(如 NFS 客户端供应器、云厂商存储供应器);
  • PVC:声明 storageClassName,触发动态 PV 创建。

3. 实操:基于 NFS-Client Provisioner 实现动态存储

步骤 1:部署 NFS-Client Provisioner

NFS-Client Provisioner 是社区提供的 NFS 动态存储供应器,需先部署到 K8s 集群。

(1)创建 ServiceAccount 与权限
# nfs-provisioner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
  resources: ["persistentvolumes"]
  verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
  resources: ["storageclasses"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
  name: nfs-client-provisioner
  namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
(2)部署 NFS-Client Provisioner
# nfs-provisioner-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
      - name: nfs-client-provisioner
        image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: k8s-sigs.io/nfs-subdir-external-provisioner  # 供应器名称,需与 StorageClass 一致
        - name: NFS_SERVER
          value: <NFS服务器IP>  # 替换为实际 NFS 地址
        - name: NFS_PATH
          value: /exports/nfs  # NFS 共享目录
      volumes:
      - name: nfs-client-root
        nfs:
          server: <NFS服务器IP>
          path: /exports/nfs

执行部署

kubectl apply -f nfs-provisioner-rbac.yaml -f nfs-provisioner-deploy.yaml

# 验证 Provisioner 运行状态
kubectl get pod -l app=nfs-client-provisioner
# 输出:
# NAME                                     READY   STATUS    RESTARTS   AGE
# nfs-client-provisioner-7f968c4d9c-9x2zl  1/1     Running   0          20s

步骤 2:创建 StorageClass

# sc-nfs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client-sc  # StorageClass 名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner  # 关联 NFS 供应器
parameters:
  pathPattern: "${.PVC.namespace}/${.PVC.name}"  # 动态 PV 在 NFS 中的目录格式(按命名空间/PVC 名划分)
reclaimPolicy: Retain  # 回收策略为保留数据

执行与验证

kubectl apply -f sc-nfs.yaml

# 查看 StorageClass
kubectl get sc
# 输出:
# NAME            PROVISIONER                                       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
# nfs-client-sc   k8s-sigs.io/nfs-subdir-external-provisioner       Retain          Immediate           false                  10s

步骤 3:创建 PVC 触发动态 PV 创建

# pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-dynamic
spec:
  storageClassName: nfs-client-sc  # 关联动态存储类
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi  # 动态申请 1Gi 存储

执行与验证

  1. 创建 PVC:
    kubectl apply -f pvc-dynamic.yaml
    
  2. 查看动态创建的 PV 和 PVC 绑定状态:
    kubectl get pvc nfs-pvc-dynamic
    # 输出:
    # NAME                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    # nfs-pvc-dynamic     Bound    pvc-7a8b9c0d-1234-5678-90ab-cdef12345678  1Gi        RWX            nfs-client-sc   10s
    
    kubectl get pv pvc-7a8b9c0d-1234-5678-90ab-cdef12345678
    # 输出:
    # NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS    AGE
    # pvc-7a8b9c0d-1234-5678-90ab-cdef12345678  1Gi        RWX            Retain           Bound    default/nfs-pvc-dynamic    nfs-client-sc   10s
    
  3. 验证 NFS 服务端动态创建的目录:
    ls /exports/nfs/default/nfs-pvc-dynamic  # 对应 pathPattern 定义的目录
    # 输出:(PVC 绑定后自动创建空目录)
    # lost+found
    

步骤 4:Pod 挂载动态 PVC 验证

# pod-dynamic-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-dynamic-nfs
spec:
  containers:
  - name: nginx
    image: nginx:1.14-alpine
    volumeMounts:
    - name: dynamic-storage
      mountPath: /usr/share/nginx/html
  volumes:
  - name: dynamic-storage
    persistentVolumeClaim:
      claimName: nfs-pvc-dynamic

执行与验证

kubectl apply -f pod-dynamic-nfs.yaml
kubectl exec test-dynamic-nfs -- sh -c "echo 'Dynamic PV from StorageClass' > /usr/share/nginx/html/index.html"

# 验证 NFS 服务端数据
cat /exports/nfs/default/nfs-pvc-dynamic/index.html
# 输出:Dynamic PV from StorageClass

五、实用技巧:K8s 命令补全

为提升 K8s 命令操作效率,可配置 bash 命令自动补全:

# 1. 安装 bash-completion 工具
yum install -y bash-completion

# 2. 配置全局生效
echo "source /usr/share/bash-completion/bash_completion" >> /etc/bashrc
echo "source <(kubectl completion bash)" >> /etc/bashrc

# 3. 立即生效(无需重启终端)
source /etc/bashrc

验证补全功能

kubectl get po[按Tab键]  # 自动补全为 pod
kubectl delete sts[按Tab键]  # 自动补全为 statefulset

六、核心知识点总结

  1. Volume:Pod 级存储,生命周期与 Pod 绑定,适合临时存储或简单共享;
  2. PV/PVC:集群级存储解耦方案,PV 是资源池,PVC 是申请单,实现存储与业务分离;
  3. StatefulSet:管理有状态服务的核心控制器,提供稳定身份、网络、存储;
  4. StorageClass:实现 PV 动态创建,解决手动创建 PV 的资源浪费问题。
posted @ 2025-11-29 23:29  bestwell  阅读(7)  评论(0)    收藏  举报