Kubernetes 存储管理

Kubernetes 存储管理

第一篇 Secrets 敏感信息管理

第一章 Secrets 概述与创建

1.1 知识点:什么是 Secrets

Secrets 是 Kubernetes 中专门用于管理敏感信息的资源对象,用于存储密码、OAuth Token、SSH Key、TLS 证书等敏感数据。与 ConfigMap 不同,Secrets 的设计目标是安全地管理和传递敏感信息,其核心优势包括:

  • 集中管理:将敏感信息从 Pod 定义和容器镜像中分离,避免密码等敏感数据硬编码在代码或配置文件中
  • 细粒度权限控制:通过 RBAC 可以精确控制哪些用户/服务账号可以访问特定的 Secret
  • 多种类型支持:支持 Opaque(通用)、kubernetes.io/tls(TLS 证书)、kubernetes.io/dockerconfigjson(Docker 认证)等多种类型
  • 与 Pod 生命周期解耦:Secret 可以独立于 Pod 存在和更新

安全提示:Secret 数据在 etcd 中以 Base64 编码存储。Base64 是可逆编码而非加密,因此安全性较弱。生产环境建议启用 etcd 静态加密功能,并结合网络策略和 RBAC 进行防护。

Secrets 的类型
类型 说明 适用场景
Opaque 通用类型,数据以 Base64 编码存储 用户名/密码、令牌等
kubernetes.io/tls 存储 TLS 证书和私钥 HTTPS 服务的证书管理
kubernetes.io/dockerconfigjson 存储 Docker Registry 认证信息 私有镜像仓库拉取镜像
kubernetes.io/service-account-token 服务账号令牌 Pod 访问 API Server

1.2 实验:Secrets 的三种创建方式

方式一:通过命令行创建

知识点串联: kubectl create secret generic 创建通用类型(Opaque)的 Secret,每个 --from-literal 指定一个 key-value 对。通过 kubectl describe 查看 Secret 时,只显示数据的字节数而非明文内容,这是 Secret 与 ConfigMap 在显示层面的安全差异。

bash 复制代码
# 创建Secret,包含用户名和密码
[root@master storage]# kubectl create secret generic timinglee \
  --from-literal userlist=timinglee \
  --from-literal password=lee
secret/timinglee created

# 查看Secret列表
[root@master storage]# kubectl get secrets
NAME             TYPE                DATA   AGE
auth-web         Opaque              1      5d19h
timinglee        Opaque              2      2m48s
web-tls-secret   kubernetes.io/tls   2      5d19h

# 查看Secret详情
[root@master storage]# kubectl describe secrets timinglee
Name:         timinglee
Namespace:    default
Labels:       <none>
Annotations:  <none>
Type:  Opaque
Data
====
password:  3 bytes
userlist:  9 bytes
bash 复制代码
# 以YAML格式查看(数据为Base64编码)
[root@master storage]# kubectl get secrets timinglee -o yaml
apiVersion: v1
data:
  password: bGVl
  userlist: dGltaW5nbGVl
type: Opaque
kind: Secret
metadata:
  name: timinglee
  namespace: default

# 验证Base64编码(Base64不等于加密,可逆)
[root@master storage]# echo -n "dGltaW5nbGVl" | base64 -d
timinglee
方式二:通过文件创建

知识点串联: 通过 --from-file 从文件创建 Secret 时,文件名作为 key,文件内容作为 value。这种方式适合存储较大的敏感文件(如证书文件),避免在命令行中直接暴露敏感内容。

bash 复制代码
# 准备密码文件(注意:echo -n 避免写入换行符)
[root@master storage]# echo -n timinglee > username.txt
[root@master storage]# echo -n lee > password.txt

# 通过文件创建Secret
[root@master storage]# kubectl create secret generic userlist \
  --from-file=username.txt \
  --from-file=password.txt
secret/userlist created

# 查看创建的Secret
[root@master storage]# kubectl get secrets userlist -o yaml
apiVersion: v1
data:
  password.txt: bGVl
  username.txt: dGltaW5nbGVl
kind: Secret
metadata:
  name: userlist
type: Opaque
方式三:通过 YAML 创建

知识点串联: 通过 YAML 文件创建 Secret 时,所有值必须预先 Base64 编码。可以使用 echo -n "value" | base64 命令进行编码。YAML 方式适合将 Secret 纳入版本管理(GitOps 流程),但需注意不要将编码后的敏感数据提交到公开仓库。

bash 复制代码
# 预先编码值
[root@master storage]# echo -n "timinglee" | base64
dGltaW5nbGVl
[root@master storage]# echo -n "lee" | base64
bGVl

# 编写YAML文件
[root@master storage]# vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: timinglee
data:
  username: dGltaW5nbGVl
  password: bGVl

# 应用Secret
[root@master storage]# kubectl apply -f secret.yaml
secret/timinglee created

# 验证创建结果
[root@master storage]# kubectl describe secrets timinglee
Name:         timinglee
Namespace:    default
Type:  Opaque
Data
====
password:  3 bytes
username:  9 bytes

第二章 Secrets 的使用方式

2.1 知识点:Secrets 的三种注入方式

Secrets 在 Pod 中有三种主要使用方式,每种方式适用于不同的场景:

注入方式 实现机制 适用场景 是否支持热更新
数据卷挂载 将 Secret 挂载为容器内的文件 TLS 证书、配置文件 是(自动更新)
环境变量注入 通过 secretKeyRef 注入为环境变量 数据库密码等简单敏感值 否(需重启 Pod)
镜像拉取认证 使用 imagePullSecrets 引用 私有镜像仓库认证

核心设计原则:

  • 数据卷方式将 Secret 以文件形式挂载,容器通过读取文件获取敏感信息,适合需要文件形式的证书
  • 环境变量方式将 Secret 值直接设置为环境变量,适合简单的键值对敏感信息
  • 镜像拉取认证方式让 kubelet 在拉取镜像时自动提供认证信息,对应用容器透明

2.2 实验:将 Secret 以数据卷方式注入 Pod

实验一:注入 TLS 证书到 Pod

知识点串联: 使用 kubernetes.io/tls 类型的 Secret 可以存储 TLS 证书和私钥。Kubernetes 会自动将证书和私钥分别存储为 tls.crttls.key 两个文件。将 Secret 挂载到 Pod 后,容器可以直接使用这些证书配置 HTTPS 服务。

bash 复制代码
# 生成自签名TLS证书
[root@k8s-master secrets]# openssl req -newkey rsa:2048 -nodes \
  -sha512 \
  -keyout timinglee.org.key \
  -x509 -days 365 -out timinglee.org.crt

# 填写证书信息
Country Name (2 letter code) [XX]:CN
State or Province Name: Shaanxi
Locality Name: xi'an
Organization Name: timinglee
Organizational Unit Name: example
Common Name: www.timinglee.org
Email Address: admin@timinglee.org

# 创建TLS类型的Secret
[root@k8s-master secrets]# kubectl create secret tls webtlskey \
  --key timinglee.org.key \
  --cert timinglee.org.crt
secret/webtlskey created

# 创建Pod,将Secret挂载到容器
[root@k8s-master secrets]# vim web.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx:latest
    name: nginx
    volumeMounts:
    - name: webtls
      mountPath: /secret
      readOnly: true
  volumes:
  - name: webtls
    secret:
      secretName: webtlskey

# 启动并验证文件已挂载
[root@k8s-master secrets]# kubectl apply -f web.yaml
pod/nginx created
[root@k8s-master secrets]# kubectl exec -it pods/nginx -c nginx -- /bin/bash
root@nginx:/# ls /secret
tls.crt  tls.key
root@nginx:/# cat /secret/tls.crt | head -5
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL...
-----END CERTIFICATE-----
实验二:指定挂载路径和文件名

知识点串联: 通过 items 字段可以精确控制 Secret 中每个 key 挂载到容器中的文件路径和文件名。这在需要将证书文件命名为特定名称(如服务所需的证书文件名)时非常有用。

bash 复制代码
# 指定每个key的挂载路径和文件名
[root@k8s-master secrets]# vim web.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx:latest
    name: nginx
    volumeMounts:
    - name: webtls
      mountPath: /secret
      readOnly: true
  volumes:
  - name: webtls
    secret:
      secretName: webtlskey
      items:
      - key: tls.crt
        path: timinglee.org.crt    # 自定义文件名
      - key: tls.key
        path: timinglee.org.key

# 验证自定义路径
[root@k8s-master secrets]# kubectl apply -f web.yaml
pod/nginx created
[root@k8s-master secrets]# kubectl exec -it pods/nginx -c nginx -- /bin/bash
root@nginx:/# cd /secret/
root@nginx:/secret# ls
timinglee.org.crt  timinglee.org.key

2.3 实验:将 Secret 设置为环境变量

实验一:通过 secretKeyRef 注入数据库密码

知识点串联: 使用 env + valueFrom.secretKeyRef 将 Secret 中的值注入为环境变量,是注入数据库密码等敏感信息的最常用方式。与 ConfigMap 的 configMapKeyRef 类似,但专门用于敏感数据。

bash 复制代码
# 创建Pod,通过secretKeyRef注入用户名和密码
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox
    name: busybox
    command:
    - /bin/sh
    - -c
    - env
    env:
    - name: USERNAME
      valueFrom:
        secretKeyRef:
          name: timinglee
          key: userlist
    - name: PASSWD
      valueFrom:
        secretKeyRef:
          name: timinglee
          key: password
  restartPolicy: Never

# 验证环境变量已注入
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl logs pods/busybox busybox
...
USERNAME=timinglee
PASSWD=lee
实验二:为 MySQL 注入 Root 密码

知识点串联: 在部署数据库应用时,通常通过环境变量传递 Root 密码。使用 Secret 注入密码可以避免在 Pod 定义中明文写入密码,提高安全性。MySQL 镜像通过 MYSQL_ROOT_PASSWORD 环境变量接收密码。

bash 复制代码
# 创建密码Secret
[root@k8s-master secrets]# echo -n "lee" | base64
bGVl
[root@k8s-master secrets]# cat passwd.yaml
apiVersion: v1
kind: Secret
metadata:
  name: passwd
data:
  pass: bGVl

# 创建MySQL Pod,使用Secret中的密码
[root@k8s-master secrets]# vim mysql.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: mysql
  name: mysql
spec:
  containers:
  - image: mysql:8.0
    name: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        secretKeyRef:
          name: passwd
          key: pass

# 验证MySQL可以使用注入的密码登录
[root@k8s-master secrets]# kubectl apply -f mysql.yaml
pod/mysql created
[root@k8s-master secrets]# kubectl exec -it pods/mysql -c mysql -- /bin/sh
sh-5.1# mysql -uroot -plee
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
mysql>

2.4 实验:使用 Secrets 管理 Docker 私有仓库认证

知识点串联: 当 Kubernetes 节点需要从私有镜像仓库(如 Harbor)拉取镜像时,需要配置 imagePullSecrets 提供认证信息。使用 kubectl create secret docker-registry 可以自动生成包含 .dockerconfigjson 的 Secret,格式符合 Docker 认证要求。

bash 复制代码
# 前提:已在Harbor私有仓库上传镜像
# docker login reg.timinglee.org
# docker tag nginx:latest reg.timinglee.org/timinglee/nginx:latest
# docker push reg.timinglee.org/timinglee/nginx:latest

# 问题演示:未配置认证时拉取镜像失败
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: myapp
  name: myapp
spec:
  containers:
  - image: reg.timinglee.org/timinglee/myapp:v1
    name: myapp

[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl get pods
NAME    READY   STATUS             RESTARTS   AGE
myapp   0/1     ImagePullBackOff   0          26s

# 查看错误信息
[root@master storage]# kubectl describe pods myapp
...
Warning  Failed  kubelet  Failed to pull image: unauthorized to access repository

错误分析: ImagePullBackOff 表示镜像拉取失败,原因是私有仓库需要认证。Kubernetes 节点没有该仓库的访问凭证。

bash 复制代码
# 创建Docker Registry认证Secret
[root@master storage]# kubectl create secret docker-registry docker-auth \
  --docker-server reg.timinglee.org \
  --docker-username admin \
  --docker-password lee \
  --docker-email timinglee@timinglee.org
secret/docker-auth created

# 验证Secret内容
[root@master storage]# kubectl get secrets docker-auth -o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWcudGltaW5nbGVlLm9yZyI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJsZWUiLCJlbWFpbCI6InRpbWluZ2xlZUB0aW1pbmdsZWUub3JnIiwiYXV0aCI6IllXUnRhVzQ2YkdWbCJ9fX0=
type: kubernetes.io/dockerconfigjson

# 解码查看内容
[root@master storage]# echo -n "eyJhdXRocyI6eyJyZWcudGltaW5nbGVlLm9yZyI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJsZWUiLCJlbWFpbCI6InRpbWluZ2xlZUB0aW1pbmdsZWUub3JnIiwiYXV0aCI6IllXUnRhVzQ2YkdWbCJ9fX0=" | base64 -d
{"auths":{"reg.timinglee.org":{"username":"admin","password":"lee","email":"timinglee@timinglee.org","auth":"YWRtaW46bGVl"}}}

关键类型说明: kubernetes.io/dockerconfigjson 是专门用于 Docker 镜像仓库认证的 Secret 类型,会自动生成符合 Docker 格式的认证配置文件(.dockerconfigjson)。

bash 复制代码
# 使用imagePullSecrets引用认证Secret
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: myapp
  name: myapp
spec:
  containers:
  - image: reg.timinglee.org/timinglee/myapp:v1
    name: myapp
  imagePullSecrets:
  - name: docker-auth

# 验证镜像成功拉取
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
myapp   1/1     Running   0          3s

第二篇 Volume 卷管理

第三章 emptyDir 临时卷

3.1 知识点:emptyDir 卷

emptyDir 是 Kubernetes 中最基础的卷类型,其核心特性如下

  • 生命周期绑定:emptyDir 卷在 Pod 分配到节点时创建,在 Pod 删除时永久销毁
  • 数据存储位置 :默认使用宿主机磁盘空间;设置 medium: Memory 后使用 tmpfs(内存文件系统)
  • 容器间共享:同一 Pod 中的多个容器可以挂载同一个 emptyDir 卷进行数据交换
  • 数据易失性:Pod 删除或容器崩溃重启后,emptyDir 中的数据会丢失

设计意图:emptyDir 适用于临时存储场景,如缓存、临时文件、容器间数据传递等不需要持久化的场景。

3.2 实验:emptyDir 内存卷的使用与容量限制

知识点串联: 本实验创建一个包含两个容器的 Pod:busybox 用于写入数据,nginx 用于提供 Web 服务,两者共享同一个 emptyDir 卷。通过 medium: Memory 将卷存储在内存中,并通过 sizeLimit 限制内存使用量,验证超出限制时的行为。

bash 复制代码
# 创建包含两个容器的Pod,共享emptyDir内存卷
[root@master volumes]# vim empty.yml
kind: Pod
metadata:
  labels:
    run: empty
  name: empty
spec:
  containers:
  - image: busybox
    name: busybox
    command:
    - /bin/sh
    - -c
    - sleep 100000
    volumeMounts:
    - mountPath: /cache
      name: cache-vol
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: cache-vol
  volumes:
  - name: cache-vol
    emptyDir:
      medium: Memory        # 使用内存存储
      sizeLimit: 100Mi      # 限制最大100MB

# 启动Pod并验证nginx可访问
[root@master volumes]# kubectl apply -f empty.yml
[root@master volumes]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE
empty   2/2     Running   0          66s   10.244.1.16   node1
[root@master volumes]# curl 10.244.1.16
<html><head><title>403 Forbidden</title></head>
bash 复制代码
# 通过busybox容器写入数据
[root@master volumes]# kubectl exec -it pods/empty -c busybox -- /bin/sh
/ # cd cache/
/cache # echo hello timinglee > index.html

# 验证nginx可以读取busybox写入的文件(容器间共享验证通过)
[root@master volumes]# curl 10.244.1.16
hello timinglee

# 测试内存限制:写入99MB成功,写入100MB失败
/cache # dd if=/dev/zero of=bigfile bs=1M count=99
99+0 records in
103809024 bytes (99.0MB) copied, 0.246898 seconds, 401.0MB/s

/cache # dd if=/dev/zero of=bigfile bs=1M count=100
dd: error writing 'bigfile': No space left on device
100+0 records in
104853504 bytes (100.0MB) copied, 0.029183 seconds, 3.3GB/s

关键参数说明:

  • medium: Memory 将卷存储在内存中(tmpfs),读写速度极高但数据易失
  • sizeLimit: 100Mi 限制卷的最大容量,超出时会触发 No space left on device 错误
  • 两个容器通过同一个 cache-vol 名称挂载同一卷,实现容器间数据共享

第四章 hostPath 卷

4.1 知识点:hostPath 卷

hostPath 卷将宿主机文件系统上的文件或目录挂载到 Pod 中。其核心特性与风险:

  • 与节点强绑定:Pod 被调度到指定节点后,才能访问该节点上的 hostPath 路径
  • 数据持久性:Pod 删除后数据保留在宿主机上,但 Pod 重建后可能调度到其他节点导致无法访问
  • 安全隐患:允许 Pod 访问宿主机文件系统,可能泄露敏感数据或破坏宿主机

适用场景:调试、日志收集(如挂载 /var/log)、需要访问宿主机特定文件(如 Docker 引擎的 /var/lib/docker)等。生产环境应谨慎使用,配合 Pod 调度策略确保 Pod 始终运行在指定节点上。

hostPath 的 type 参数
type 值 说明
DirectoryOrCreate 如果路径不存在则自动创建
Directory 路径必须存在且为目录
FileOrCreate 如果路径不存在则自动创建文件
File 路径必须存在且为文件
Socket 路径必须存在且为 Unix 套接字
CharDevice 路径必须存在且为字符设备
BlockDevice 路径必须存在且为块设备

4.2 实验:hostPath 基本使用

知识点串联: 创建 Pod 将宿主机的 /data 目录挂载到容器的 nginx 网页目录,通过在宿主机写入文件来验证数据共享。使用 type: DirectoryOrCreate 确保即使宿主机目录不存在也会自动创建。

bash 复制代码
# 创建Pod,使用hostPath挂载宿主机目录
[root@master volumes]# kubectl run hostpath --image nginx --dry-run=client -o yaml > hostpath.yml
[root@master volumes]# vim hostpath.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: hostpath
  name: hostpath
spec:
  containers:
  - image: nginx
    name: hostpath
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: timinglee
  volumes:
  - name: timinglee
    hostPath:
      path: /data              # 宿主机路径
      type: DirectoryOrCreate  # 如果目录不存在则自动创建

# 启动Pod
[root@master volumes]# kubectl apply -f hostpath.yml
pod/hostpath created
[root@master volumes]# kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP            NODE
hostpath   1/1     Running   0          14s   10.244.1.17   node1
bash 复制代码
# 在宿主机上写入网页文件
ssh -l root node1
[root@node1 ~]# mkdir -p /data
[root@node1 ~]# echo hello timinglee > /data/index.html

# 验证通过Pod的IP可以访问宿主机写入的内容
[root@master volumes]# curl 10.244.1.17
hello timinglee

# 验证Pod删除后数据仍保留在宿主机
[root@master volumes]# kubectl delete pod hostpath
pod/hostpath deleted
[root@node1 ~]# ls /data/
index.html    # 数据依然存在

4.3 实验:企业级 MySQL 初始化(initContainer + hostPath + emptyDir)

知识点串联: 本实验展示了一个企业级场景:使用 initContainer 实现 MySQL 数据库的初始化。checkdata 容器负责检查数据是否已存在,若不存在则从备份文件恢复数据;storedata 容器负责初始化 MySQL 并导入数据。两个容器通过 emptyDir 共享临时备份文件,MySQL 数据目录通过 hostPath 持久化到宿主机。

bash 复制代码
# 企业级MySQL初始化配置
[root@master storage]# vim mysql.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: mysql-1
  name: mysql-1
spec:
  initContainers:
  # 第一个initContainer:检查数据是否已存在
  - name: checkdata
    image: mysql:8.0
    command: ["/bin/sh", "-c"]
    args:
      - |
        DATADIR=/var/lib/mysql
        if [ -f "${DATADIR}/ibdata1" ]
        then
          echo "skip dump data"
          exit 0
        else
          mysqldump -h10.244.1.28 -uroot -plee -B --all-databases > /tmp/dump/allbase.sql && {
            echo data storedata
            exit 0
          }
        fi
    volumeMounts:
    - name: mysqldata
      mountPath: /var/lib/mysql
    - name: mysqldump
      mountPath: /tmp/dump

  # 第二个initContainer:初始化数据库并导入数据
  - name: storedata
    image: mysql:8.0
    command: ["/bin/sh", "-c"]
    args:
      - |
        DATADIR=/var/lib/mysql
        if [ -f "/tmp/dump/allbase.sql" ]
        then
          chown -R 999:999 ${DATADIR}
          mysqld --initialize-insecure --user mysql --datadir=/var/lib/mysql
          mysqld --skip-networking --socket=/var/lib/mysql/mysqld.sock &
          PID=$!
          sleep 8
          mysql -uroot -S /var/lib/mysql/mysqld.sock < /tmp/dump/allbase.sql
          rm -rf /tmp/dump/allbase.sql
          kill ${PID}
          wait ${PID} || true
          echo "dump successful"
        else
          echo "not need dump"
          exit 0
        fi
    volumeMounts:
    - name: mysqldata
      mountPath: /var/lib/mysql
    - name: mysqldump
      mountPath: /tmp/dump

  # 主容器:MySQL服务
  containers:
  - image: mysql:8.0
    name: mysql-1
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        secretKeyRef:
          name: passwd
          key: passwd
    volumeMounts:
    - name: mysqldata
      mountPath: /var/lib/mysql

  volumes:
  - name: mysqldump
    emptyDir:
      medium: Memory       # 临时备份文件存储在内存中
  - name: mysqldata
    hostPath:
      path: /data          # 数据库数据持久化到宿主机
      type: DirectoryOrCreate

关键设计说明:

  • initContainer 按定义顺序依次执行,checkdata 先执行检查,storedata 后执行初始化
  • emptyDir 用于临时备份文件,不占用磁盘空间
  • hostPath 确保 MySQL 数据在 Pod 重建后不丢失
  • 使用 secretKeyRef 注入 Root 密码,避免明文暴露

第五章 NFS 卷

5.1 知识点:NFS 卷

NFS(Network File System)卷允许 Kubernetes Pod 挂载远程 NFS 共享存储。其核心特性:

  • 共享存储:多个 Pod(甚至跨节点)可以挂载同一个 NFS 共享目录
  • 数据持久化:数据存储在 NFS 服务器上,Pod 删除后数据不丢失
  • 访问模式:支持 ReadWriteOnce(RWO)、ReadWriteMany(RWX)、ReadOnlyMany(ROX)等多种访问模式
  • 前置条件 :NFS 服务器需提前配置,且所有需要挂载 NFS 的节点上需安装 nfs-utils
NFS 访问模式对比
访问模式 全称 说明
ReadWriteOnce (RWO) 单节点读写 该卷只能被单个节点以读写方式挂载
ReadOnlyMany (ROX) 多节点只读 多个节点可以以只读方式挂载
ReadWriteMany (RWX) 多节点读写 多个节点可以同时以读写方式挂载

5.2 实验:NFS 共享存储的搭建与使用

步骤一:搭建 NFS 服务器

知识点串联: 在节点上安装 NFS 服务软件包,配置共享目录和访问权限,启动 NFS 服务。/etc/exports 文件定义共享目录和访问规则,sync 参数确保写入操作同步到磁盘,no_root_squash 允许 root 用户以 root 权限访问远程目录。

bash 复制代码
# 在NFS服务器上操作
[root@node3 ~]# mkdir /share
[root@node3 ~]# dnf install nfs-utils -y
[root@node3 ~]# systemctl enable --now nfs-server.service

# 配置共享目录(允许所有客户端以sync,rw,no_root_squash方式访问)
[root@node3 ~]# vim /etc/exports
/share  *(sync,rw,no_root_squash)

# 刷新NFS共享配置
[root@node3 ~]# exportfs -rv
exporting *:/share
[root@node3 ~]# showmount -e
Export list for node3:
/share *
步骤二:在所有工作节点安装 NFS 客户端
bash 复制代码
# 在所有work节点安装nfs-utils
[root@master volumes]# for i in 10 20; do
  ssh -l root 172.25.254.$i dnf install nfs-utils -y
done

# 验证各节点可以访问NFS服务器
[root@master volumes]# for i in 10 20; do
  ssh -l root 172.25.254.$i showmount -e 172.25.254.30
done
Export list for 172.25.254.30:
/share *
步骤三:创建使用 NFS 卷的 Pod

知识点串联: 在 Pod 的 volumes 中指定 nfs 类型,提供 NFS 服务器地址和共享路径,即可将远程 NFS 目录挂载到容器中。NFS 卷的核心优势是跨节点数据共享------无论 Pod 被调度到哪个节点,只要能够访问 NFS 服务器,就可以访问同一份数据。

bash 复制代码
# 创建Pod,挂载NFS共享目录
[root@master volumes]# kubectl run web --image nginx --dry-run=client -o yaml >> nfs.yml
[root@master volumes]# vim nfs.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web1
  name: web1
spec:
  nodeName: node1
  containers:
  - image: nginx
    name: web1
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: cache-vol
  volumes:
  - name: cache-vol
    nfs:
      server: 172.25.254.30    # NFS服务器地址
      path: /share              # 共享目录路径

# 启动Pod
[root@master volumes]# kubectl apply -f nfs.yml
pod/web1 created
[root@master volumes]# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP            NODE
web1   1/1     Running   0          7s    10.244.1.18   node1

# 在NFS服务器上写入网页文件
[root@node3 ~]# echo hello timinglee > /share/index.html

# 验证Pod可以访问NFS上的文件
[root@master volumes]# curl 10.244.1.18
hello timinglee
bash 复制代码
# 验证跨节点共享:将Pod调度到另一个节点,仍然可以访问同一NFS
[root@master volumes]# vim nfs.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web1
  name: web1
spec:
  nodeName: node2          # 更改运行节点
  containers:
  - image: nginx
    name: web1
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: cache-vol
  volumes:
  - name: cache-vol
    nfs:
      server: 172.25.254.30
      path: /share

[root@master volumes]# kubectl apply -f nfs.yml
pod/web1 created
[root@master volumes]# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE
web1   1/1     Running   0          13s   10.244.2.5   node2

[root@master volumes]# curl 10.244.2.5
hello timinglee    # 跨节点访问成功

第六章 PersistentVolume 持久卷

6.1 知识点:PV 与 PVC 架构

PersistentVolume(PV) 是集群中由管理员预配置的一块存储资源,是集群中的全局资源,不隶属于任何命名空间。它抽象了底层存储实现(NFS、ISCSI、云存储等)的细节。

PersistentVolumeClaim(PVC) 是用户对存储的请求,指定需要的存储大小和访问模式。Pod 通过引用 PVC 来使用存储。

PV 与 PVC 的工作流程
  1. 管理员创建 PV(静态供给)或配置 StorageClass(动态供给)
  2. 用户创建 PVC 声明需要的存储
  3. Kubernetes 通过绑定机制将 PV 与 PVC 配对(一对一映射)
  4. Pod 通过引用 PVC 来使用存储
PV 的回收策略(ReclaimPolicy)
策略 说明 适用场景
Retain 保留数据,需管理员手动清理 需要保留数据以备恢复
Delete 自动删除 PV 和底层存储 临时数据,无需保留
Recycle 清除数据后重新可用(已废弃) 不推荐使用
PV 的状态说明
状态 说明
Available 空闲资源,尚未绑定到任何 PVC
Bound 已绑定到某个 PVC
Released 绑定的 PVC 已被删除,但存储资源尚未被集群回收
Failed 自动回收操作失败

6.2 实验:静态持久卷的创建与使用

步骤一:在 NFS 服务器上创建存储目录
bash 复制代码
# 在NFS服务器上创建PV对应的目录
[root@node3 ~]# mkdir /share/pv{1..3} -p
[root@node3 ~]# dnf install nfs-utils -y
[root@node3 ~]# systemctl enable --now nfs-server
[root@node3 ~]# vim /etc/exports
/share  *(sync,rw,no_root_squash)
[root@node3 ~]# exportfs -rv
exporting *:/share
步骤二:创建 PV 和 PVC

知识点串联: 创建三个 PV,分别使用不同的容量和访问模式(RWO/RWX/ROX),然后创建对应的 PVC 进行绑定。PVC 的 storageClassNameaccessModesresources.requests.storage 必须与 PV 匹配才能成功绑定。PVC 请求的容量必须小于或等于 PV 的容量。

bash 复制代码
# 创建三个PV
[root@master volumes]# vim pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce        # 单节点读写
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /share/pv1
    server: 172.25.254.30
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany        # 多节点读写
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /share/pv2
    server: 172.25.254.30
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv3
spec:
  capacity:
    storage: 15Gi
  volumeMode: Filesystem
  accessModes:
  - ReadOnlyMany         # 多节点只读
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /share/pv3
    server: 172.25.254.30

# 应用PV
[root@master volumes]# kubectl apply -f pv.yml
[root@master storage]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      STORAGECLASS   AGE
pv1    5Gi        RWO            Retain           Available           nfs             117s
pv2    10Gi       RWX            Retain           Available           nfs             29s
pv3    15Gi       ROX            Retain           Available           nfs             29s
bash 复制代码
# 创建对应的PVC
[root@master volumes]# vim pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc2
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc3
spec:
  storageClassName: nfs
  accessModes:
    - ReadOnlyMany
  resources:
    requests:
      storage: 15Gi

# 应用PVC(自动绑定到匹配的PV)
[root@master volumes]# kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv1      5Gi        RWO            nfs            66s
pvc2   Bound    pv2      10Gi       RWX            nfs            66s
pvc3   Bound    pv3      15Gi       ROX            nfs            66s

# 验证PV状态从Available变为Bound
[root@master storage]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS   AGE
pv1    5Gi        RWO            Retain           Bound    default/pvc1   nfs             5m
pv2    10Gi       RWX            Retain           Bound    default/pvc2   nfs             5m
pv3    15Gi       ROX            Retain           Bound    default/pvc3   nfs             5m
步骤三:在 Pod 中使用 PVC

知识点串联: Pod 通过 persistentVolumeClaim.claimName 引用 PVC 来使用持久卷。PVC 必须在与 Pod 相同的命名空间中创建。

bash 复制代码
# 创建Pod,通过PVC使用持久卷
[root@master volumes]# vim pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: timinglee
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: vol1
  volumes:
  - name: vol1
    persistentVolumeClaim:
      claimName: pvc1

# 启动Pod并验证
[root@master volumes]# kubectl apply -f pod.yml
pod/timinglee created
[root@master volumes]# kubectl get pods -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP            NODE
timinglee   1/1     Running   0          83s   10.244.2.54   k8s-node2

# 在NFS服务器上写入文件
[root@node3 ~]# echo "PV test data" > /share/pv1/index.html

# 验证Pod可以读取持久卷中的数据
[root@master volumes]# curl 10.244.2.54
PV test data

第七章 StorageClass 动态供给

7.1 知识点:StorageClass 动态供给

StorageClass 提供了一种描述存储类(class)的方法,不同的 class 可能会映射到不同的服务质量等级、备份策略或其他策略。每个 StorageClass 包含以下关键属性:

  • Provisioner(存储分配器) :决定使用哪个卷插件分配 PV。可以指定内部分配器(如 kubernetes.io/aws-ebs)或外部分配器(如 NFS Client Provisioner)
  • Reclaim Policy(回收策略) :通过 reclaimPolicy 字段指定创建的 PV 的回收策略(Delete 或 Retain),未指定时默认为 Delete
  • Volume Binding Mode:控制 PV 的绑定时机(Immediate 或 WaitForFirstConsumer)
NFS Client Provisioner

NFS Client Provisioner 是一个 automatic provisioner,使用 NFS 作为存储后端,自动创建 PV 和对应的 PVC。PV 以 ${namespace}-${pvc name}-${pv name} 的格式在 NFS 服务器上创建子目录,实现多租户隔离。

7.2 实验:StorageClass 动态供给完整流程

步骤一:创建 RBAC 授权

知识点串联: NFS Client Provisioner 需要 ServiceAccount 和相应的 RBAC 权限来操作 Kubernetes 的 PV、PVC 和 StorageClass 资源。

bash 复制代码
# 创建RBAC配置
[root@master storageclass]# vim rbac.yml
apiVersion: v1
kind: Namespace
metadata:
  name: nfs-client-provisioner
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - 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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-client-provisioner
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-client-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-client-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

# 应用RBAC配置
[root@master storageclass]# kubectl apply -f rbac.yml
namespace/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
步骤二:部署 NFS Provisioner 控制器

知识点串联: 部署 NFS Client Provisioner 的 Deployment,通过环境变量指定 NFS 服务器地址和共享路径。Provisioner 会自动监听 PVC 请求并创建对应的 PV。

bash 复制代码
# 部署NFS外部Provisioner
[root@master storageclass]# vim provisioner.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: 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: 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
        - name: NFS_SERVER
          value: 172.25.254.250
        - name: NFS_PATH
          value: /nfsdata
      volumes:
      - name: nfs-client-root
        nfs:
          server: 172.25.254.250
          path: /nfsdata

# 应用并验证
[root@master storageclass]# kubectl apply -f provisioner.yml
deployment.apps/nfs-client-provisioner created
[root@master storageclass]# kubectl -n nfs-client-provisioner get pods
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-6d4b8c7f9-x2k4l   1/1     Running   0          30s
步骤三:创建 StorageClass
bash 复制代码
# 创建StorageClass
[root@master storageclass]# vim storageclass.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate

# 应用StorageClass
[root@master storageclass]# kubectl apply -f storageclass.yml
storageclass.storage.k8s.io/nfs-client created
步骤四:动态创建 PVC 验证

知识点串联: 创建 PVC 时指定 storageClassName: nfs-client,Provisioner 会自动创建对应的 PV,无需手动预配置 PV。PVC 绑定后,Pod 即可使用该 PVC 进行数据存储。

bash 复制代码
# 创建PVC(无需手动创建PV)
[root@master storageclass]# vim pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-claim
spec:
  storageClassName: nfs-client
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

# 应用PVC
[root@master storageclass]# kubectl apply -f pvc.yml
persistentvolumeclaim/test-claim created
[root@master storageclass]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-claim   Bound    pvc-7782a006-381a-440a-addb-e9d659b8fe0b   1Gi        RWX            nfs-client     21m

# 验证PV已自动创建
[root@master storageclass]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   AGE
pvc-7782a006-381a-440a-addb-e9d659b8fe0b   1Gi        RWX            Delete           Bound    default/test-claim   nfs-client     21m
bash 复制代码
# 创建测试Pod验证数据持久化
[root@master storageclass]# vim pod.yml
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox
    command: ["/bin/sh"]
    args:
    - "-c"
    - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: nfs-pvc
      mountPath: /mnt
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-claim

# 验证Pod运行成功
[root@master storageclass]# kubectl apply -f pod.yml
pod/test-pod created
[root@master storageclass]# kubectl logs test-pod
# 无错误输出表示成功
步骤五:设定默认 StorageClass

知识点串联: 将 StorageClass 标记为默认后,创建 PVC 时可以不指定 storageClassName,系统会自动使用默认的 StorageClass 进行动态供给。这在大多数场景都使用同一种存储类型时非常方便。

bash 复制代码
# 将nfs-client设为默认StorageClass
[root@master storageclass]# kubectl edit storageclass nfs-client
# 在annotations中添加: storageclass.kubernetes.io/is-default-class: "true"

# 验证(显示default标记)
[root@master storageclass]# kubectl get storageclass
NAME            PROVISIONER                                     RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-client (default)   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  2d

# 不指定storageClassName的PVC会自动使用默认StorageClass
[root@master storageclass]# vim pvc-default.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-default
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  # 不指定storageClassName,自动使用nfs-client

# 验证自动绑定
[root@master storageclass]# kubectl apply -f pvc-default.yml
persistentvolumeclaim/pvc-default created
[root@master storageclass]# kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-default   Bound    pvc-xxx-xxx-xxx-xxx                        5Gi        RWX            nfs-client     5s

第八章 StatefulSet 与动态卷整合

8.1 知识点:StatefulSet 有状态应用管理

StatefulSet 是 Kubernetes 中用于管理有状态应用的控制器,解决了 Deployment 无法有效管理有状态应用的问题。其核心特性:

拓扑状态

应用实例必须按照某种顺序启动。新创建的 Pod 必须和原来 Pod 的网络标识一致。StatefulSet 为每个 Pod 分配编号(从 0 开始),Pod 名称格式为 $(statefulset名称)-$(序号)

存储状态

应用的多个实例分别绑定不同的存储数据。通过 volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC,实现每个实例拥有独立的持久化存储。

固定网络标识

Pod 被删除后重建,重建 Pod 的网络标识不会改变。StatefulSet 为每个 Pod 提供固定且唯一的 DNS 记录,格式为 $(pod-name).$(service-name).$(namespace).svc.cluster.local

8.2 实验:StatefulSet + Headless Service + 动态 PV 完整实战

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

知识点串联: Headless Service(clusterIP: None)不会分配 Cluster IP,而是直接返回 Pod 的 IP 地址。StatefulSet 依赖 Headless Service 来为每个 Pod 生成 DNS 记录,实现稳定的网络标识。

bash 复制代码
# 创建Headless Service
[root@master statefulset]# vim headless.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None    # 无头服务,不分配Cluster IP
  selector:
    app: nginx

# 应用Headless Service
[root@master statefulset]# kubectl apply -f headless.yml
service/nginx-svc created
步骤二:创建 StatefulSet

知识点串联: StatefulSet 通过 volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC。PVC 名称格式为 $(volumeClaimTemplateName)-$(pod-name),自动使用指定的 StorageClass 进行动态供给。

bash 复制代码
# 创建StatefulSet,使用volumeClaimTemplates自动创建PVC
[root@master statefulset]# vim statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx-svc
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      storageClassName: nfs-client
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

# 应用StatefulSet
[root@master statefulset]# kubectl apply -f statefulset.yml
statefulset.apps/web created

# 验证Pod和PVC的创建(按顺序创建:web-0, web-1, web-2)
[root@master statefulset]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          3m26s
web-1   1/1     Running   0          3m22s
web-2   1/1     Running   0          3m18s

# 验证PVC自动创建
[root@master statefulset]# kubectl get pvc
NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0         Bound    pvc-xxx-xxx-xxx-xxx                        1Gi        RWO            nfs-client     3m
www-web-1         Bound    pvc-yyy-yyy-yyy-yyy                        1Gi        RWO            nfs-client     3m
www-web-2         Bound    pvc-zzz-zzz-zzz-zzz                        1Gi        RWO            nfs-client     3m

# 在NFS服务器上查看自动创建的目录
[root@reg nfsdata]# ls /nfsdata/
default-www-web-0-xxx   default-www-web-1-yyy   default-www-web-2-zzz
步骤三:有序扩缩容验证

知识点串联: StatefulSet 支持有序扩缩容。缩容时从最大编号开始删除,扩容时从最小编号开始创建。每个 Pod 都有独立的 PVC,扩缩容不影响其他 Pod 的数据。

bash 复制代码
# 扩缩到2个副本
[root@master statefulset]# kubectl scale statefulset web --replicas 2
statefulset.apps/web scaled

# 验证web-2被删除
[root@master statefulset]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          10m
web-1   1/1     Running   0          10m

# 扩缩到3个副本(web-2重新创建,保留之前的PVC)
[root@master statefulset]# kubectl scale statefulset web --replicas 3
statefulset.apps/web scaled

# 验证web-2重新创建并绑定到之前的PVC
[root@master statefulset]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          12m
web-1   1/1     Running   0          12m
web-2   1/1     Running   0          15s

# 也可以通过编辑配置改变副本数
[root@master statefulset]# kubectl edit statefulsets.apps web
# 将 replicas: 3 改为 replicas: 5
步骤四:验证数据持久化和网络稳定性

知识点串联: 为每个 Pod 写入不同的数据,然后通过 Headless Service 的 DNS 名称访问每个 Pod,验证数据持久化和网络标识的稳定性。即使删除 StatefulSet 后重建,Pod 名称和 DNS 记录也不会改变。

bash 复制代码
# 为每个Pod写入不同的数据
[root@master statefulset]# kubectl exec -it web-0 -c nginx -- /bin/sh -c "echo 'web-0' > /usr/share/nginx/html/index.html"
[root@master statefulset]# kubectl exec -it web-1 -c nginx -- /bin/sh -c "echo 'web-1' > /usr/share/nginx/html/index.html"
[root@master statefulset]# kubectl exec -it web-2 -c nginx -- /bin/sh -c "echo 'web-2' > /usr/share/nginx/html/index.html"

# 通过Headless Service的DNS名称访问每个Pod
[root@master statefulset]# kubectl run testpod --image busybox --restart=Never -it --rm -- /bin/sh
/ # curl web-0.nginx-svc
web-0
/ # curl web-1.nginx-svc
web-1
/ # curl web-2.nginx-svc
web-2

# 删除StatefulSet后重建,数据依然保留
[root@master statefulset]# kubectl delete -f statefulset.yml
statefulset.apps "web" deleted

# 重新创建StatefulSet
[root@master statefulset]# kubectl apply -f statefulset.yml
statefulset.apps/web created

# 重建后通过DNS仍然可以访问,且数据不变
[root@master statefulset]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          10s
web-1   1/1     Running   0          8s
web-2   1/1     Running   0          6s

# 验证数据保留
kubectl run testpod --image busybox --restart=Never -it --rm -- /bin/sh
/ # curl web-0.nginx-svc
web-0    # 数据依然保留
/ # curl web-1.nginx-svc
web-1    # 数据依然保留
/ # curl web-2.nginx-svc
web-2    # 数据依然保留

关键设计说明:

  • StatefulSet 的 Pod 名称格式为 $(name)-$(ordinal),从 0 开始编号
  • volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC,格式为 $(templateName)-$(podName)
  • Headless Service 提供稳定的 DNS 记录:$(podName).$(serviceName).$(namespace).svc.cluster.local
  • 删除 StatefulSet 后重建,Pod 名称、PVC 和 DNS 记录均保持不变,实现有状态应用的可靠管理

附录:K8s 存储方案速查表

存储方案对比

存储方案 适用场景 数据持久性 跨节点共享 性能 推荐度
emptyDir 临时缓存、容器间共享 否(Pod删除即失) 是(同Pod内) 高(内存) 临时数据
hostPath 调试、日志收集 是(宿主机) 否(绑定节点) 不推荐生产
NFS 多Pod共享文件 中小型共享存储
PV/PVC(静态) 需要固定存储资源 取决于后端 固定资源场景
StorageClass(动态) 需要自动供给存储 取决于后端 云原生推荐
StatefulSet 有状态应用(DB/中间件) 独立存储 有状态应用首选

典型场景推荐

场景 推荐方案 理由
Web 应用静态文件 NFS + PV/PVC 多副本共享、数据持久化
数据库(MySQL/PostgreSQL) StatefulSet + StorageClass 独立存储、稳定网络标识
日志收集 hostPath 直接访问宿主机日志目录
缓存/临时数据 emptyDir(内存) 高性能、无需持久化
多应用共享配置 ConfigMap 与镜像解耦、热更新
数据库密码管理 Secret(环境变量) 安全注入、避免明文
私有镜像拉取 Secret(dockerconfigjson) 自动认证、无需手动配置

生产环境最佳实践

安全

  • 启用 etcd 静态加密存储 Secret 数据
  • 使用 RBAC 限制 Secret 的访问权限
  • 避免在日志和事件中包含敏感信息

性能

  • 对性能敏感的存储使用本地磁盘(local PV)
  • NFS 存储避免小文件密集场景
  • 合理设置 sizeLimit 防止内存卷耗尽节点内存

高可用

  • 使用多副本 StatefulSet 保证服务可用性
  • 选择支持多节点访问的存储后端(RWX)
  • 定期备份持久化数据

监控

  • 监控 PV/PVC 的使用率和容量
  • 设置存储告警阈值
  • 监控 NFS 服务器的连接数和吞吐量
相关推荐
harmony&1 小时前
Kubernetes 实战:认证授权、集群 Dashboard 与动态卷供应全解析
云原生·容器·kubernetes
Henry-SAP3 小时前
SAP PP 从停工待料到精准排产
人工智能·云原生·sap·erp
报错小能手3 小时前
Kubernetes入门实战课 4
云原生·容器·kubernetes
天天喝旺仔4 小时前
Docker 镜像瘦身实战:多阶段构建把体积缩小 90%
运维·后端·ci/cd·docker·云原生·容器·性能优化
Henry-SAP4 小时前
AI标准落地加速 安全与应用双突破
人工智能·云原生·sap·erp
NJCloud4 小时前
Docker 私有仓库部署:Registry、加密传输与认证鉴权
运维·网络·docker·云原生·容器
w200512244 小时前
K8S控制器管理
linux·云原生·容器·kubernetes
harmony&5 小时前
Docker 镜像完全指南:从最小镜像到企业级私有仓库 Harbor
云原生·eureka
DolitD5 小时前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算