第一章:概述
1.1 概述
- 容器的生命周期可能很短,会被频繁的创建和销毁。那么容器在销毁的时候,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器中的数据,Kubernetes 引入了 Volume 的概念。和 Docker 中的卷管理(匿名卷、具名卷、自定义挂载目录,都是挂载在本机,功能非常有限)不同的是,Kubernetes 天生就是集群,所以为了方便管理,Kubernetes 将
卷
抽取为一个对象资源,这样可以更方便的管理和存储数据。 - Volume 是 Pod 中能够被多个容器访问的共享目录,它被定义在 Pod 上,然后被一个 Pod 里面的多个容器挂载到具体的文件目录下,Kubernetes 通过 Volume 实现同一个 Pod 中不同容器之间的数据共享以及数据的持久化存储。Volume 的生命周期不和 Pod 中的单个容器的生命周期有关,当容器终止或者重启的时候,Volume 中的数据也不会丢失。
1.2 Kubernetes 支持的 Volume 类型
1.2.1 简版
- Kubernetes 的 Volume 支持多种类型,如下图所示:
1.2.2 细分类型
- Kubernetes 目前支持多达 28 种数据卷类型(其中大部分特定于具体的云环境如 GCE/AWS/Azure 等)
- 非持久性存储:
- emptyDir
- HostPath
- 网络连接性存储:
- SAN:iSCSI、ScaleIO Volumes、FC (Fibre Channel)
- NFS:nfs,cfs
- 分布式存储
- Glusterfs
- RBD (Ceph Block Device)
- CephFS
- Portworx Volumes
- Quobyte Volumes
- 云端存储
- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- Cinder (OpenStack block storage)
- VsphereVolume
- StorageOS
- 自定义存储
- FlexVolume
第二章:配置
2.1 配置最佳实战
- 云原生应用的 12 要素中,提出了配置分离。
- 在推送到集群之前,配置文件应该存储在版本控制系统中。这将允许我们在必要的时候快速回滚配置更改,它有助于集群重新创建和恢复。
- 使用 YAML 而不是 JSON 编写配置文件,虽然这些格式几乎可以在所有场景中互换使用,但是 YAML 往往更加用户友好。
- 建议相关对象分组到一个文件,一个文件通常比几个文件更容易管理。请参阅 guestbook-all-in-one.yaml 文件作为此语法的示例。
- 除非必要,否则不指定默认值(简单的最小配置会降低错误的可能性)。
- 将对象描述放在注释中,以便更好的内省。
2.2 Secret
2.2.1 概述
- Secret 对象类型用来保存敏感信息,如:密码、OAuth2 令牌以及 SSH 密钥等。将这些信息放到 Secret 中比放在 Pod 的定义或者容器镜像中更加安全和灵活。
- 由于创建 Secret 可以独立于使用它们的 Pod, 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施,如:避免将机密数据写入非易失性存储。
- Secret 类似于 ConfigMap 但专门用于保存机密数据。
注意:Secret 和 ConfigMap 是保存在 etcd 中。
2.2.2 Secret 的种类
- 命令行:
create secret --help
- 细分类型:
| 内置类型 | 用法 |
| —- | —- |
|
Opaque
| 用户定义的任意数据 | |kubernetes.io/service-account-token
| 服务账号令牌 | |kubernetes.io/dockercfg
|~/.dockercfg
文件的序列化形式 | |kubernetes.io/dockerconfigjson
|~/.docker/config.json
文件的序列化形式 | |kubernetes.io/basic-auth
| 用于基本身份认证的凭据 | |kubernetes.io/ssh-auth
| 用于 SSH 身份认证的凭据 | |kubernetes.io/tls
| 用于 TLS 客户端或者服务器端的数据 | |bootstrap.kubernetes.io/token
| 启动引导令牌数据 |
2.2.3 Pod 如何引用
- 要使用 Secret,Pod 需要引用 Secret。 Pod 可以用三种方式之一来使用 Secret :
- 作为挂载到一个或多个容器上的 卷 中的文件。(volume进行挂载)
- 作为容器的环境变量(envFrom字段引用)
- 由 kubelet 在为 Pod 拉取镜像时使用(此时Secret是docker-registry类型的)
- Secret 对象的名称必须是合法的 DNS 子域名。 在为创建 Secret 编写配置文件时,你可以设置
data
与/或stringData
字段。data
和stringData
字段都是可选的。data
字段中所有键值都必须是 base64 编码的字符串。如果不希望执行这种 base64 字符串的转换操作,你可以选择设置stringData
字段,其中可以使用任何字符串作为其取值。
2.2.4 创建 Secret
- 命令行创建 Secret :
kubectl create secret generic secret-1 \
--from-literal=username=admin \
--from-literal=password=123456
- 使用 base64 对数据进行编码:
# 准备username YWRtaW4=
echo -n "admin" | base64
# MTIzNDU2
echo -n "123456" | base64
- 使用 yaml 格式创建 Secret :
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: k8s-secret
namespace: default
type: Opaque
data:
username: YWRtaW4=
password: MTIzNDU2
kubectl apply -f k8s-secret.yaml
- 有的时候,觉得手动将数据编码,再配置到 yaml 文件的方式太繁琐,那么也可以将数据编码交给 Kubernetes :
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: k8s-secret-string-data
namespace: default
type: Opaque
stringData:
username: admin
password: "123456"
kubectl apply -f k8s-secret.yaml
注意:如果同时使用 data 和 stringData ,那么 data 会被忽略。
- 根据文件创建 Secret :
echo -n 'admin' > username.txt
echo -n '123456' > password.txt
kubectl create secret generic k8s-secret-file \
--from-file=username.txt \
--from-file=password.txt
注意:密钥 的 Key 默认是文件名的名称。
- 根据文件创建 Secret(自定义密钥 的 Key )
echo -n 'admin' > username.txt
echo -n '123456' > password.txt
kubectl create secret generic k8s-secret-file \
--from-file=username=username.txt \
--from-file=password=password.txt
2.2.5 查看 Secret
- 示例:以 JOSN 的形式提取 data
kubectl get secret k8s-secret-file -o jsonpath='{.data}'
- 示例:以 yaml 的格式
kubectl get secret k8s-secret-file -o yaml
2.2.6 使用 Secret 之环境变量引用
- 示例:
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: default
type: Opaque
stringData:
username: admin
password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
namespace: default
labels:
app: pod-secret
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh","-c","sleep 3600"]
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
env:
- name: SECRET_USERNAME # 容器中的环境变量名称
valueFrom:
secretKeyRef:
name: my-secret # 指定 secret 的名称
key: username # secret 中 key 的名称,会自动 base64 解码
- name: SECRET_PASSWORD # 容器中的环境变量名称
valueFrom:
secretKeyRef:
name: my-secret # 指定 secret 的名称
key: password # secret 中 key 的名称
- name: POD_NAME
valueFrom:
fieldRef: # 属性引用
fieldPath: metadata.name
- name: POD_LIMITS_MEMORY
valueFrom:
resourceFieldRef: # 资源限制引用
containerName: alpine
resource: limits.memory
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
kubectl apply -f k8s-secret.yaml
注意:环境变量引用的方式不会被自动更新。
2.2.7 使用 Secret 之 卷挂载
- 示例:
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: default
type: Opaque
stringData:
username: admin
password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
namespace: default
labels:
app: pod-secret
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh","-c","sleep 3600"]
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: app
mountPath: /app
volumes:
- name: app
secret:
secretName: my-secret # secret 的名称,Secret 中的所有 key 全部挂载出来
restartPolicy: Always
kubectl apply -f k8s-secret.yaml
注意:
- 如果 Secret 以卷挂载的方式,Secret 里面的所有 key 都是文件名,内容就是 key 对应的值。
- 如果 Secret 以卷挂载的方式,Secret 的内容更新,那么容器对应的值也会被更新(subPath 引用除外)。
- 如果 Secret 以卷挂载的方式,默认情况下,挂载出来的文件是只读的。
- 示例:指定挂载的文件名
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: default
type: Opaque
stringData:
username: admin
password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
namespace: default
labels:
app: pod-secret
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh","-c","sleep 3600"]
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: app
mountPath: /app
volumes:
- name: app
secret:
secretName: my-secret # secret 的名称,Secret 中的所有 key 全部挂载出来
items:
- key: username # secret 中 key 的名称,Secret 中的 username 的内容挂载出来
path: username.md # 在容器内挂载出来的文件的路径
restartPolicy: Always
kubectl apply -f k8s-secret.yaml
2.3 ConfigMap
2.3.1 概述
- ConfigMap 和 Secret 非常类似,只不过 Secret 会将信息进行 base64 编码和解码,而 ConfigMap 却不会。
2.3.2 创建 ConfigMap、环境变量引用、卷挂载
- 示例:
vi k8s-cm.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: cm
namespace: default
data:
# 类属性键;每一个键都映射到一个简单的值
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# 类文件键
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
---
apiVersion: v1
kind: Pod
metadata:
name: "pod-cm"
namespace: default
labels:
app: "pod-cm"
spec:
containers:
- name: alpine
image: "alpine"
command: ["/bin/sh","-c","sleep 3600"]
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
env:
- name: PLAYER_INITIAL_LIVES
valueFrom:
configMapKeyRef:
name: cm
key: player_initial_lives
ports:
- containerPort: 80
name: http
volumeMounts:
- name: app
mountPath: /app
volumes:
- name: app
configMap:
name: cm # secret 的名称,Secret 中的所有 key 全部挂载出来
items:
- key: ui_properties_file_name # secret 中 key 的名称,Secret 中的 ui_properties_file_name 的内容挂载出来
path: ui_properties_file_name.md # 在容器内挂载出来的文件的路径
restartPolicy: Always
kubectl apply -f k8s-cm.yaml
注意:
- ConfigMap 和 Secret 一样,环境变量引用不会热更新,而卷挂载是可以热更新的。
- 最新版本的 ConfigMap 和 Secret 提供了不可更改的功能,即禁止热更新,只需要在 Secret 或 ConfigMap 中设置
immutable = true
。
2.3.3 ConfigMap 结合 SpringBoot 做到生产配置无感知
第三章:临时存储
3.1 概述
Kubernetes 为了不同的目的,支持几种不同类型的临时卷:
emptyDir
、configMap
、downwardAPI
、secret
是作为 本地临时存储 提供的。它们由各个节点上的 kubelet 管理。- CSI 临时卷
必须
由第三方 CSI 存储驱动程序提供。 - 通用临时卷
可以
由第三方 CSI 存储驱动程序提供,也可以由支持动态配置的任何其他存储驱动程序提供。 一些专门为 CSI 临时卷编写的 CSI 驱动程序,不支持动态供应:因此这些驱动程序不能用于通用临时卷。 - 使用第三方驱动程序的优势在于,它们可以提供 Kubernetes 本身不支持的功能, 例如,与 kubelet 管理的磁盘具有不同运行特征的存储,或者用来注入不同的数据。
3.2 emptyDir
- Pod 分派到某个 Node 上时,
emptyDir
卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。 - 就像其名称表示的那样,卷最初是空的。
- 尽管 Pod 中的容器挂载
emptyDir
卷的路径可能相同也可能不同,这些容器都可以读写emptyDir
卷中相同的文件。 - 当 Pod 因为某些原因被从节点上删除时,
emptyDir
卷中的数据也会被永久删除。
注意: 容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间
emptyDir
卷中的数据是安全的。
emptyDir
的一些用途:- 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
emptyDir
卷存储在该节点的磁盘或内存中,如果设置emptyDir.medium = Memory
,那么就告诉 Kubernetes 将数据保存在内存中,并且在 Pod 被重启或删除前,所写入的所有文件都会计入容器的内存消耗,受到容器内存限制约束。
- 示例:
vi k8s-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-empty-dir
namespace: default
labels:
app: nginx-empty-dir
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: app
mountPath: /usr/share/nginx/html
- name: alpine
image: alpine
command: ["/bin/sh","-c","while true;do sleep 1; date > /app/index.html;done"]
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: app
mountPath: /app
volumes:
- name: app
emptyDir: {} # emptyDir 临时存储
restartPolicy: Always
kubectl apply -f k8s-emptyDir.yaml
3.3 hostPath
- emptyDir 中的数据不会被持久化,它会随着 Pod 的结束而销毁,如果想要
简单
的将数据持久化到主机中,可以选择 hostPath 。 - hostPath 就是将 Node 主机中的一个实际目录挂载到 Pod 中,以供容器使用,这样的设计就可以保证 Pod 销毁了,但是数据依旧可以保存在 Node 主机上。
注意:hostPath 之所以被归为临时存储,是因为实际开发中,我们一般都是通过 Deployment 部署 Pod 的,一旦 Pod 被 Kubernetes 杀死或重启拉起等,并不一定会部署到原来的 Node 节点中。
- 除了必需的
path
属性之外,用户可以选择性地为hostPath
卷指定type
。 | 取值 | 行为 | | —- | —- | | | 空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |DirectoryOrCreate
| 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 | |Directory
| 在给定路径上必须存在的目录。 | |FileOrCreate
| 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 | |File
| 在给定路径上必须存在的文件。 | |Socket
| 在给定路径上必须存在的 UNIX 套接字。 | |CharDevice
| 在给定路径上必须存在的字符设备。 | |BlockDevice
| 在给定路径上必须存在的块设备。 |
hostPath 的典型应用就是时间同步:通常而言,Node 节点的时间是同步的(云厂商提供的云服务器的时间都是同步的),但是,Pod 中的容器的时间就不一定了,有 UTC 、CST 等;同一个 Pod ,如果部署到中国,就必须设置为 CST 了。
示例:
vi k8s-hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-host-path
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath: # hostPath
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
kubectl apply -f k8s-hostPath.yaml
第四章:持久化存储
4.1 VOLUME
4.1.1 基础
- Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。
- 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。
- 当 Pod 不再存在时,Kubernetes 也会销毁临时卷。
- Kubernetes 不会销毁
持久卷
。 - 对于给定 Pod 中
任何类型的卷
,在容器重启期间数据都不会丢失。 - 使用卷时, 在
.spec.volumes
字段中设置为 Pod 提供的卷,并在.spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。
4.1.2 subPath
- 有时,在单个 Pod 中共享卷以供多方使用是很有用的。
volumeMounts.subPath
属性可用于指定所引用的卷内的子路径,而不是其根路径。
注意:ConfigMap 和 Secret 使用子路径挂载是无法热更新的。
4.1.3 安装 NFS
- NFS 的简介:网络文件系统,英文Network File System(NFS),是由 SUN 公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。
注意:实际开发中,不建议使用 NFS 作为 Kubernetes 集群持久化的驱动。
- 本次以 Master (192.168.65.100)节点作为 NFS 服务端:
yum install -y nfs-utils
- 在 Master(192.168.65.100)节点创建 /etc/exports 文件:
# * 表示暴露权限给所有主机;* 也可以使用 192.168.0.0/16 代替,表示暴露给所有主机
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
- 在 Master(192.168.65.100)节点创建
/nfs/data/
(共享目录)目录,并设置权限:
mkdir -pv /nfs/data/
chmod 777 -R /nfs/data/
- 在 Master(192.168.65.100)节点启动 NFS :
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
- 在 Master(192.168.65.100)节点加载配置:
exportfs -r
- 在 Master(192.168.65.100)节点检查配置是否生效:
exportfs
- 在 Node(192.168.65.101、192.168.65.102)节点安装 nfs-utils :
# 服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许,否则远端客户无法连接。
yum install -y nfs-utils
- 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令检查 nfs 服务器端是否有设置共享目录:
# showmount -e $(nfs服务器的IP)
showmount -e 192.168.65.100
- 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令挂载 nfs 服务器上的共享目录到本机路径
/root/nd
:
mkdir /nd
# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount
mount -t nfs 192.168.65.100:/nfs/data /nd
- 在 Node (192.168.65.101)节点写入一个测试文件:
echo "hello nfs server" > /nd/test.txt
- 在 Master(192.168.65.100)节点验证文件是否写入成功:
cat /nfs/data/test.txt
- 示例:
vi k8s-nginx-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/ # / 一定是文件夹
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
nfs: # 使用 nfs 存储驱动
path: /nfs/data # nfs 共享的目录
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
restartPolicy: Always
kubectl apply -f k8s-nginx-nfs.yaml
4.2 PV 和 PVC
4.2.1 概述
- 前面我们已经学习了使用 NFS 提供存储,此时就要求用户会搭建 NFS 系统,并且会在 yaml 配置 NFS,这就带来的一些问题:
- ① 开发人员对 Pod 很熟悉,非常清楚 Pod 中的容器那些位置适合挂载出去。但是,由于 Kubernetes 支持的存储系统非常之多,开发人员并不清楚底层的存储系统,而且要求开发人员全部熟悉,不太可能(术业有专攻,运维人员比较熟悉存储系统)。
- ② 在 yaml 中配置存储系统,就意味着将存储系统的配置信息暴露,非常不安全(容易造成泄露)。
- 为了能够屏蔽底层存储实现的细节,方便用户使用,Kubernetes 引入了 PV 和 PVC 两种资源对象。
- PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下 PV 由 Kubernetes 管理员进行创建和配置,它和底层具体的共享存储技术有关,并通过插件完成和共享存储的对接。
- PVC(Persistent Volume Claim)是持久化卷声明的意思,是用户对于存储需求的一种声明。换言之,PVC 其实就是用户向Kubernetes 系统发出的一种资源需求申请。
PV 的缺点:
- ① 需要运维事先准备好 PV 池。
- ② 资源浪费:没有办法预估合适的 PV,假设运维向 k8s 申请了 20m 、50m、10G 的 PV,而开发人员申请 2G 的 PVC ,那么就会匹配到 10G 的PV ,这样会造成 8G 的空间浪费。
也有人称 PV 为静态供应。
4.2.2 PVC 和 PV 的基本演示
- 创建 PV (一般是运维人员操作):
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-10m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 10m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-20m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-500m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 500m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1g
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
kubectl apply -f k8s-pv.yaml
- 创建 PVC (一般是开发人员):
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc-500m
namespace: default
labels:
app: nginx-pvc-500m
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500m
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
persistentVolumeClaim:
claimName: nginx-pvc-500m
readOnly: false
restartPolicy: Always
kubectl apply -f k8s-pvc.yaml
注意:
- pv 和 pvc 的 accessModes 和 storageClassName 必须一致。
- pvc 申请的空间大小不小于 pv 的空间大小。
- storageClassName 就相当于分组的组名,通过 storageClassName 可以区分不同类型的存储驱动,主要是为了方便管理。
注意:
- Pod 的删除,并不会影响 PVC;换言之,PVC 可以独立于 Pod 存在,PVC 也是 K8s 的系统资源。不过,推荐将 PVC 和 Pod 也在一个 yaml 文件中。
- PVC 删除会不会影响到 PV,要根据 PV 的回收策略决定。
4.2.3 PV 的回收策略
- 目前的回收策略有:
- Retain:手动回收(默认)。
- Recycle:基本擦除 (
rm -rf /thevolume/*
)。 - Delete:诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除。
目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。
示例:演示 Retain
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-10m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 10m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-20m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-500m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 500m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1g
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
kubectl apply -f k8s-pv.yaml
echo 111 > /nfs/data/500m/index.html
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc-500m
namespace: default
labels:
app: nginx-pvc-500m
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500m
kubectl apply -f k8s-pvc.yaml
kubectl delete -f k8s-pvc.yaml
- 示例:演示 Recycle
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-10m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 10m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
persistentVolumeReclaimPolicy: Recycle # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-20m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
persistentVolumeReclaimPolicy: Recycle # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-500m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 500m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
persistentVolumeReclaimPolicy: Recycle # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1g
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
persistentVolumeReclaimPolicy: Recycle # 回收策略
kubectl apply -f k8s-pv.yaml
echo 111 > /nfs/data/500m/index.html
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc-500m
namespace: default
labels:
app: nginx-pvc-500m
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500m
kubectl apply -f k8s-pvc.yaml
kubectl delete -f k8s-pvc.yaml
4.2.4 PV 的生命周期
- 一个 PV 的生命周期,可能会处于 4 种不同的阶段:
- Available(可用):表示可用状态,还未被任何 PVC 绑定。
- Bound(已绑定):表示 PV 已经被 PVC 绑定。
- Released(已释放):表示 PVC 被删除,但是资源还没有被集群重新释放。
- Failed(失败):表示该 PV 的自动回收失败。
4.2.5 PV 的访问模式
- 访问模式(accessModes):用来描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载。
4.2.6 生命周期
- PVC 和 PV 是一一对应的,PV 和 PVC 之间的相互作用遵循如下的生命周期:
- ① 资源供应:管理员手动创建底层存储和 PV。
- ② 资源绑定:
- 用户创建 PVC ,Kubernetes 负责根据 PVC 声明去寻找 PV ,并绑定在用户定义好 PVC 之后,系统将根据 PVC 对存储资源的请求在以存在的 PV 中选择一个满足条件的。
- 一旦找到,就将该 PV 和用户定义的 PVC 进行绑定,用户的应用就可以使用这个 PVC 了。
- 如果找不到,PVC 就会无限期的处于 Pending 状态,直到系统管理员创建一个符合其要求的 PV 。
- PV 一旦绑定到某个 PVC 上,就会被这个 PVC 独占,不能再和其他的 PVC 进行绑定了。
- 用户创建 PVC ,Kubernetes 负责根据 PVC 声明去寻找 PV ,并绑定在用户定义好 PVC 之后,系统将根据 PVC 对存储资源的请求在以存在的 PV 中选择一个满足条件的。
- ③ 资源使用:用户可以在 Pod 中像 Volume 一样使用 PVC ,Pod 使用 Volume 的定义,将 PVC 挂载到容器内的某个路径进行使用。
- ④ 资源释放:
- 用户删除 PVC 来释放 PV 。
- 当存储资源使用完毕后,用户可以删除 PVC,和该 PVC 绑定的 PV 将会标记为
已释放
,但是还不能立刻和其他的 PVC 进行绑定。通过之前 PVC 写入的数据可能还留在存储设备上,只有在清除之后该 PV 才能再次使用。
- ⑤ 资源回收:
- Kubernetes 根据 PV 设置的回收策略进行资源的回收。
- 对于 PV,管理员可以设定回收策略,用于设置与之绑定的 PVC 释放资源之后如何处理遗留数据的问题。只有 PV 的存储空间完成回收,才能供新的 PVC 绑定和使用。
4.3 动态供应
4.3.1 概述
- 静态供应:集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
- 动态供应:集群自动根据 PVC 创建出对应 PV 进行使用。
4.3.2 动态供应的完整流程
- ① 集群管理员预先创建存储类(StorageClass)。
- ② 用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim)。
- ③ 存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume)。
- ④ 系统读取存储类的信息。
- ⑤ 系统基于存储类的信息,在后台自动创建 PVC 需要的 PV 。
- ⑥ 用户创建一个使用 PVC 的 Pod 。
- ⑦ Pod 中的应用通过 PVC 进行数据的持久化。
- ⑧ PVC 使用 PV 进行数据的最终持久化处理。
4.3.3 设置 NFS 动态供应
注意:不一定需要设置 NFS 动态供应,可以直接使用云厂商提供的 StorageClass 。
- 官网地址。
- 部署 NFS 动态供应:
vi k8s-nfs-provisioner.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定一个供应商的名字
# or choose another name, 必须匹配 deployment 的 env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false" # 删除 PV 的时候,PV 中的内容是否备份
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: ccr.ccs.tencentyun.com/gcr-containers/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: 192.168.65.100 # NFS 服务器的地址
- name: NFS_PATH
value: /nfs/data # NFS 服务器的共享目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.65.100
path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
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: default
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: default
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: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
kubectl apply -f k8s-nfs-provisioner.yaml
- 示例:测试动态供应
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
namespace: default
labels:
app: nginx-pvc
spec:
storageClassName: nfs-client # 注意此处
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
persistentVolumeClaim:
claimName: nginx-pvc
readOnly: false
restartPolicy: Always
kubectl apply -f k8s-pvc.yaml
4.3.4 设置 SC 为默认驱动
- 命令:
kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
- 设置 SC 为默认驱动:
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
- 示例:测试默认驱动
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
namespace: default
labels:
app: nginx-pvc
spec:
# storageClassName: nfs-client 不写,就使用默认的
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
persistentVolumeClaim:
claimName: nginx-pvc
readOnly: false
restartPolicy: Always
4.3.5 展望
- 目前,只需要运维人员部署好各种 storageclass,开发人员在使用的时候,创建 PVC 即可;但是,存储系统太多太多,运维人员也未必会一一掌握,此时就需要 Rook 来统一管理了。