第一章:概述
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 进行绑定了。
- ③ 资源使用:用户可以在 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 来统一管理了。
[...]Kubernetes 概念 Kubernetes(v1.21)工作负载 Kubernetes(v1.21)配置和存储Kubernetes(v1.21)网络Kubernetes(v1.21)调度原理Kubernetes(v1.21)安全K8s - Ingress 限流K8s-Pod重启策略Kubernetes滚动更新解决Mac/Windows版Desktop Docker中自带的K8s无法访问pod[...]