Kubernetes - 配置与存储
# 配置管理
# ConfigMap
创建
使用 kubectl create configmap -h
查看示例,构建 configmap 对象
kubectl create configmap -h
Examples:
# Create a new config map named my-config based on folder bar
kubectl create configmap my-config --from-file=path/to/bar
# Create a new config map named my-config with specified keys instead of file basenames on disk
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
# Create a new config map named my-config with key1=config1 and key2=config2
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
# Create a new config map named my-config from the key=value pairs in the file
kubectl create configmap my-config --from-file=path/to/bar
# Create a new config map named my-config from an env file
kubectl create configmap my-config --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
configmap 可以缩写为 cm。
如在 /opt/k8s/configmap
下创建一个文件 test.yml
spring:
port: 8095
2
然后将文件加载到 ConfigMap
# 加载文件夹的所有文件
kubectl create cm spring-test-config -from-file=/opt/k8s/configmap
# 加载指定文件
kubectl create cm spring-test-config -from-file=/opt/k8s/configmap/test.yml
# 查看文件内容
kubectl describe cm spring-test-config
2
3
4
5
6
7
8
单独加 key=value
形式的键值对
kubectl create configmap my-config --from-literal=jAVA_OPTS_TEST="-Xms512m -Xmx512m" --from-literal=APP_NAME=spring-boot-test
# 查看 my-config
[root@k8s-master ingress-nginx]# kubectl describe cm my-config
Name: my-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
APP_NAME:
----
spring-boot-test
jAVA_OPTS_TEST:
----
-Xms512m -Xmx512m
BinaryData
====
Events: <none>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用 Key-Value 形式
创建一个 Pod,这个 Pod 使用上面定义好的 ConfigMap 里的存储内容
apiVersion: v1
kind: Pod
metadata:
name: test-env-cm
spec:
containers:
- name: env-test
image: alpine
command: ["/bin/sh","-c""env;sleep 3600"]
imagePullPolicy: IfNotPresent
env:
- name: JAVA_VM_OPTS
valueFrom: # value 来源
configMapKeyRef: # 从 Config Map 读取
name: test-env-config # configMap 的名字 key: JAVA_OPTS_TEST # 表示从 name 的 ConfigMap 中获取名字为 key 的 value, 将其赋值给本地环境变量 JAVALVMLOPTS
- name: APP
valueFrom:
configMapKeyRef:
name: test-env-config
key: APP_NAME
restartPolicy: Never
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
使用文件形式
假设 ConfigMap 里有 key 为 db1.properties,value 为
username=kbt
password=admin
2
使用:
apiVersion: v1
kind: Pod
metadata:
name: test-config-file-po
spec:
containers:
- name: config-test
image: alpine
command: ["/bin/sh", "-c", "sleep 3600"]
imagePuliPolicy: IfNotPresent
volumeMounts: # 加载数据卷属性中哪个数据卷
- name: db-config # 表示加载 volumes
mountPath: "/usr/local/mysql/conf" # 想要将数据卷中的文件加载到 容器 的哪个目录下
readOnly: true # 是否只读
volumes: # 数据卷挂载 configmap、secret
- name: db-config # 数据卷的名字,随意设置
configMap: # 数据卷类型为 CofngiMap
name: test-dir-config # configMap 的名字,必须跟想要加载的 configmap 相同
items: # 对 configmap 中的 key 进行映射,如果不指定,默认会讲 configmap 中所有 key 全部转换为一个个同名的文件
- key: "db1.properties" # configMap 中的 key
path: "db.properties" # 将该 key 的值转换为文件
restartPolicy: Never
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
目的是读取 ConfigMap 的 key:db1.properties 对应的所有 value,并创建 db.properties 文件放到里面,这个文件将挂载到宿主机的 /usr/local/mysql/conf
下。
执行该文件生成 Pod 后,可以发现 /usr/local/mysql/conf
有 db.properties 文件,打开文件内容是
username=kbt
password=admin
2
# 加密数据配置 Secret
与 ConfigMap 类似,用于存储配置信息,但是主要用于存储敏感信息、需要加密的信息,Secret 可以提供数据加密、解密功能。
在创建 Secret 时,要注意如果要加密的字符中,包含了有特殊字符,需要使用转义符转移,例如 $ 转移后为 \$
,也可以对特殊字符使用单引号描述,这样就不需要转移例如 1$289*-!
转换为 '1$289*-!'
kubectl create secret generic orig-secret --from-literal=username=admin --from-literal=password=ds@!3-1/
[root@k8s-master ingress-nginx]# kubectl describe secret orig-secret
Name: orig-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 10 bytes
username: 5 bytes
2
3
4
5
6
7
8
9
10
11
12
13
14
Secret 加密其实使用了 Base64 编码,实际不安全,因为拿到值后可以直接 Base64 解码。
Secret 有三种类型创建:
- docker-registry:创建一个给 Docker registry 使用的 secret
- generic:从本地文件、目录或文字值创建加密
- tls:创建一个 TLS secret
常用的就是 docker-registry。场景:搭建了 Docker 私服,如果 docker 或者 k8s 去私服拉取镜像,则需要登录,可以利用这个来登录。
创建 docker 登录信息:
kubectl create secret docker-registry harbor-secret --docker-username=admin --docker-password=admin1234 --docker-email=kbt@qq.com --docker-serve=192.168.199.27:8858
然后创建一个 Pod,从私服拉取镜像
apiVersion: v1
kind: Pod
metadata:
name: private-image-pull-pod
spec:
imagePullSecrets: # 配置登录 Docker Registry 的 Secret
- name: harbor-secret # 获取登录信息
containers:
- name: config-test
image: 192.168.199.27:8858/opensource/nginx:1.9.1 # 私服的镜像
command: ["/bin/sh", "-c", "sleep 3600"]
imagePuliPolicy: IfNotPresent
volumeMounts: # 加载数据卷属性中哪个数据卷
- name: db-config # 表示加载 volumes
mountPath: "/usr/local/mysql/conf" # 想要将数据卷中的文件加载到哪个目录下
readOnly: true # 是否只读
volumes: # 数据卷挂载 configmap、secret
- name: db-config # 数据卷的名字,随意设置
configMap: # 数据卷类型为 CofngiMap
name: test-dir-config # configMap 的名字,必须跟想要加载的 configmap 相同
items: # 对 configmap 中的 key 进行映射,如果不指定,默认会讲 configmap 中所有 key 全部转换为一个个同名的文件
- key: "db1.properties" # configMap 中的 key
path: "db.properties" # 将该 key 的值转换为文件
restartPolicy: Never
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# SubPath 的使用
使用 ConfigMap 或 Secret 挂载到目录的时候,会将容器中源目录给覆盖掉,此时我们可能只想覆盖目录中的某一个文件,但是这样的操作会覆盖整个文件,因此需要使用到 SubPath。
配置方式:
- 定义 volumes 时需要增加 items 属性,配置 key 和 path,且 path 的值不能从
/
开始 - 在容器内的 volumeMounts 中增加 subPath 属性,该值与 volumes 中 items.path 的值相同
containers:
# ......
volumeMounts:
- mountPath: /etc/nginx/nginx.conf # 挂载到容器的哪里
name: config-volume # 使用哪个 configmap 或 secret
subPath: etc/nginx/nginx.conf # 与 volumes.[0].items.path 相同,只挂载指定文件,不会清空其他文件
volumes:
- configMap:
name: nginx-conf # configMap 名字
items: # subPath 配置
key: nginx.conf # configMap 中的文件名
path: etc/nginx/nginx.conf # subPath 路径
2
3
4
5
6
7
8
9
10
11
12
# 配置的热更新
我们通常会将项目的配置文件作为 configmap 然后挂载到 pod,那么如果更新 configmap 中的配置,会不会更新到 pod 中呢?
这得分成几种情况:
- 默认方式:会更新,更新周期是更新时间 + 缓存时间
- subPath:不会更新
- 变量形式:如果 pod 中的一个变量是从 configmap 或 secret 中得到,同样也是不会更新的
对于 subPath 的方式,我们可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置。
但是如果目标位置原本就有文件,可能无法创建软链接,此时可以基于前面讲过的 postStart 操作执行删除命令,将默认的文件删除即可。
修改 configmap:
修改数据:通过 edit 命令直接修改 configmap 的数据
kubectl edit cm xxx
1然后修改对应的数据 Value,保存即可实现热更新
修改文件:通过 replace 替换新的文件
由于 configmap 我们创建通常都是基于文件创建,并不会编写 yaml 配置文件,因此修改时我们也是直接修改配置文件,而 replace 是没有 --from-file 参数的,因此无法实现基于源配置文件的替换,此时我们可以利用下方的命令实现。
该命令的重点在于 --dry-run
参数,该参数的意思打印 yaml 文件,但不会将该文件发送给 apiserver,再结合 -o yaml
输出 yaml 文件就可以得到一个配置好但是没有发给 apiserver 的文件,然后再结合 replace 监听控制台输出得到 yaml 数据即可实现替换
kubectl create cm xxx --from-file=nginx.conf --dry-run -o yaml | kubectl replace -f
kubectl create cm xxx --from-file=nginx.conf --dry-run -o yaml
并不会创建一个 configmap,因为 --dry-run
参数只是运行然后结束,不会执行出结果。
# 不可变的 Secret 和 ConfigMap
对于一些敏感服务的配置文件,在线上有时是不允许修改的,此时在配置 configmap 时可以设置 immutable: true
来禁止修改。
kubectl edit cm xxx
# 在最后一行加上 (第一级)
immutable: true
2
3
4
immutable: true
是通用的,如果加载 Pod 的 yml 文件,则 Pod 的文件禁止修改,在 Pod 的 yml,是和容器 containers 平级。
# 持久化存储
# Volumes
HostPath
将节点上的文件或目录挂载到 Pod 上,此时该目录会变成持久化存储目录,即使 Pod 被删除后重启,也可以重新加载到该目录,该目录下的文件不会丢失。
配置文件:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: nginx-volume
volumeMounts:
- mountPath: /test-pd # 挂载到容器的哪个目录
name: test-volume # 挂载哪个 volume
volumes:
- name: test-volume
hostPath: # 与主机共享目录,加载主机的指定目录到容器中
path: /data # 节点中的目录
type: Directory # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
类型:
空字符串:默认类型,不做任何检查
DirectoryOrCreate:如果给定的 path 不存在,就创建一个 755 的空目录
Directory:这个目录必须存在
FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限为 644
File:这个文件必须存在
Socket:UNIX 套接字,必须存在
CharDevice:字符设备,必须存在
BlockDevice:块设备,必须存在
EmptyDir
EmptyDir 主要用于一个 Pod 中不同的 Container 共享数据使用的,由于只是在 Pod 内部使用,因此与其他 volume 比较大的区别是,当 Pod 如果被删除了,那么 emptyDir 也会被删除。
存储介质可以是任意类型,如 SSD、磁盘或网络存储。可以将 emptyDir.medium
设置为 Memory 让 k8s 使用 tmpfs(内存支持文件系统),速度比较快,但是重启 tmpfs 节点时,数据会被清除,且设置的大小会计入到 Container 的内存限制中。
配置文件:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: nginx-emptydir1
volumeMounts:
- mountPath: /cache
name: cache-volume
- image: nginx
name: nginx-emptydir2
volumeMounts:
- mountPath: /opt
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
此时 ginx-emptydir1 和 nginx-emptydir2 容器共用一个数据卷 cache-volume。ginx-emptydir1 的 /cache 和 cache-volume 关联,inx-emptydir2 的 /opt和 cache-volume 关联。
当 ginx-emptydir1 的 /cache
添加或者删除文件,则 emptyDir 会执行同样的操作,且 ginx-emptydir2 的 /opt
也执行同样的操作。
emptyDir 相当于一个中间商,作为不同容器的交互的入口。
# NFS 挂载
nfs 卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。 不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
安装 nfs:
# 安装 nfs
yum install nfs-utils -y
# 启动 nfs
systemctl start nfs-server
# 自启
systemctl enable nfs-server
# 查看 nfs 版本
cat /proc/fs/nfsd/versions
# 创建共享目录
mkdir -p /nfs
cd /nfs
mkdir rw
mkdir ro
# Master 设置共享目录 export
vim /etc/exports
/nfs/rw 192.168.199.0/24(rw,sync,no_subtree_check,no_root_squash)
/nfs/ro 192.168.199.0/24(ro,sync,no_subtree_check,no_root_squash)
# 重新加载
exportfs -f
systemctl reload nfs-server
# 到其他测试节点安装 nfs-utils 并加载测试
mkdir -p /nfs/rw
mount -t nfs 192.168.199.28:/nfs/rw /nfs/rw
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
配置文件:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /my-nfs-data
name: test-volume
volumes:
- name: test-volume
nfs:
server: my-nfs-server.example.com # 网络存储服务地址
path: /my-nfs-volume # 网络存储路径
readOnly: true # 是否只读
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# PV 与 PVC
PersistentVolume(PV)是集群中已由管理员配置的一段网络村吃。集群中的资源就想一个节点是一个集群资源。
PV 是诸如卷之类卷插件,但是具有独立于使用PV的任何单个 pod 的生命周期。该 API 对象补货存储的实现细节,即 NFS,iSCSI 或云提供商特定的存储系统
PersistentVolumeClaims(PVC)是用户存储的请求。PVC 的使用逻辑:在 pod 中定义一个存储卷(该存储卷类型为 pvc),定义的时候直接指定大小,PVC 必须与对应的 PV 建立关系,PVC 会根据定义去 PV 申请,而 PV 是由存储空间创建出来的。PV 和 PVC 是 k8s 抽象出来的一种存储资源。
虽然 PersistentVolumeClaims 允许用户使用抽象存储资源,但是常见的需求是,用户需要根据不同的需求去创建 PV,用于不同的场景。而此时需要集群管理员提供不同需求的 PV,而不仅仅是 PV 的大小和访问模式,但又不需要用户了解这些卷的实现细节。对于这样的需求,此时可以采用 StorageClass 资源。
PV 是集群中的资源。PVC 是对这些资源的请求,也是对资源的索引检查。PV 和 PVC 之间的相互作用遵循这个生命周期。
看图的右边就知道 PV 和 PVC 的角色定位。
PV 和资源绑定,是代理商,PVC 和 Pod 绑定,是代购,最终 PVC 会根据 Pod 需要的条件去找满足条件的 PV。
# 生命周期
构建
静态构建:集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
动态构建:如果集群中已经有的 PV 无法满足 PVC 的需求,那么集群会根据 PVC 自动构建一个 PV,该操作是通过 StorageClass 实现的。想要实现这个操作,前提是 PVC 必须设置 StorageClass,否则会无法动态构建该 PV,可以通过启用 DefaultStorageClass 来实现 PV 的构建。
绑定
当用户创建一个 PVC 对象后,主节点会监测新的 PVC 对象,并且寻找与之匹配的 PV 卷,找到 PV 卷后将二者绑定在一起。
如果找不到对应的 PV,则需要看 PVC 是否设置 StorageClass 来决定是否动态创建 PV,若没有配置,PVC 就会一致处于未绑定状态,直到有与之匹配的 PV 后才会申领绑定关系。
使用
Pod 将 PVC 当作存储卷来使用,集群会通过 PVC 找到绑定的 PV,并为 Pod 挂载该卷。
Pod 一旦使用 PVC 绑定 PV 后,为了保护数据,避免数据丢失问题,PV 对象会受到保护,在系统中无法被删除。
回收策略
当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除, 从而允许该资源被回收再利用。PersistentVolume 对象的回收策略告诉集群, 当其被从申领中释放时如何处理该数据卷。 目前,数据卷可以被 Retained(保留)、Recycled(回收)或 Deleted(删除)。
策略有:
保留(Retain)
回收策略 Retain 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为「已释放(released)」。 由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:
- 删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在
- 根据情况,手动清除所关联的存储资产上的数据
- 手动删除所关联的存储资产
如果你希望重用该存储资产,可以基于存储资产的定义创建新的 PersistentVolume 卷对象。
删除(Delete)
对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 动态制备的卷会继承 其 StorageClass 中设置的回收策略 (opens new window), 该策略默认为 Delete。管理员需要根据用户的期望来配置 StorageClass; 否则 PV 卷被创建之后必须要被编辑或者修补。
回收(Recycle)
警告:回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。
如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的擦除(rm -rf /thevolume/*
)操作,之后允许该卷用于新的 PVC 申领。
# PV
状态
- Available:空闲,未被绑定
- Bound:已经被 PVC 绑定
- Released:PVC 被删除,资源已回收,但是 PV 未被重新使用
- Failed:自动回收失败
配置文件:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity:
storage: 5Gi # pv 的容量
volumeMode: Filesystem # 存储类型为文件系统
accessModes: # 访问模式:ReadWriteOnce、ReadWriteMany、ReadOnlyMany
- ReadWriteOnce # 可被单节点独写
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: slow # 创建 PV 的存储类名,需要与 pvc 的相同
mountOptions: # 加载配置
- hard
- nfsvers=4.1
nfs: # 连接到 nfs
path: /data/nfs/rw/test-pv # 存储路径
server: 192.168.199.27 # nfs 服务地址
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
accessModes 访问模式:
- ReadWriteOnce:只允许被一个 PVC 绑定读写
- ReadWriteMany:允许多个 PVC 绑定读写
- ReadOnlyMany:允许多个 PVC 绑定读
# PVC
Pod 绑定 PVC
配置文件:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteOnce # 权限需要与对应的 pv 相同
volumeMode: Filesystem
resources:
requests:
storage: 5Gi # 资源可以小于 pv 的,但是不能大于,如果大于就会匹配不到 pv
storageClassName: slow # 名字需要与对应的 pv 相同
# selector: # 使用选择器选择对应的 pv
# matchLabels:
# release: "stable"
# matchExpressions:
# - {key: environment, operator: In, values: [dev]}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在 pod 的挂载容器配置中,增加 pvc 挂载
spec:
containers:
- image: nginx
name: nginx-volume
volumeMounts:
- mountPath: /tmp/pvc # 挂载到容器的目录
name: nfs-pvc-test
volumes:
- name: nfs-pvc-test
persistentVolumeClaim:
claimName: nfs-pvc # PVC 的名称
2
3
4
5
6
7
8
9
10
11
# StorageClass
Kubernetes 提供了一套可以自动创建 PV 的机制,即:Dynamic Provisioning。而这个机制的核心在于 StorageClass 这个 API 对象。
StorageClass 对象会定义下面两部分内容:
- PV 的属性。比如,存储类型,Volume 的大小等
- 创建这种 PV 需要用到的存储插件,即存储制备器
有了这两个信息之后,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass,之后 Kubernetes 就会调用该 StorageClass 声明的存储插件,进而创建出需要的 PV。
但是其实使用起来是一件很简单的事情,你只需要根据自己的需求,编写 YAML 文件即可,然后使用 kubectl create 命令执行即可。
StorageClass 为管理员提供了描述存储「类」的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。 Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为「配置文件」。
# 为什么需要 StorageClass
在一个大规模的 Kubernetes 集群里,可能有成千上万个 PVC,这就意味着运维人员必须实现创建出这个多个 PV,此外,随着项目的需要,会有新的 PVC 不断被提交,那么运维人员就需要不断的添加新的,满足要求的 PV,否则新的 Pod 就会因为 PVC 绑定不到 PV 而导致创建失败。而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。
而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。
# 运行原理
要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner(制备器),这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。
- 自动创建的 PV 以
${namespace}-${pvcName}-${pvName}
这样的命名格式创建在 NFS 服务器上的共享数据目录中 - 而当这个 PV 被回收后会以
archieved-${namespace}-${pvcName}-${pvName}
这样的命名格式存在 NFS 服务器上
# 制备器(Provisioner)
每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。
# 回收策略
由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。
通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收政策。
# NFS 动态制备案例
创建一个 Deployement 来管理制备器,nfs-provisioner.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-system
labels:
app: nfs-client-provisioner
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: quay.io/external_storage/nfs-client-provisioner:latest # 这个往下看,实际不用这个
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.199.28
- name: NFS_PATH
value: /nfs/rw
volumes:
- name: nfs-client-root
nfs:
server: 192.168.199.28
path: /nfs/rw
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。
StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。
管理员可以为没有申请绑定到特定 StorageClass 的 PVC 指定一个默认的存储类。
StorageClass 配置:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
namespace: kube-system
provisioner: fuseim.pri/ifs # 外部制备器提供者,编写为提供者的名称
parameters:
archiveOnDelete: "false" # 是否存档,false 表示不存档,会删除 oldPath 下面的数据,true 表示存档,会重命名路径
reclaimPolicy: Retain # 回收策略,默认为 Delete 可以配置为 Retain
volumeBindingMode: Immediate # 默认为 Immediate,表示创建 PVC 立即进行绑定,只有 azuredisk 和 AWSelasticblockstore 支持其他值
2
3
4
5
6
7
8
9
10
RBAC 配置:
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
namespace: kube-system
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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
namespace: kube-system
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: kube-system
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: kube-system
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: kube-system
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
PVC 处于 Pending 状态:
在 k8s 1.20 之后,出于对性能和统一 apiserver 调用方式的初衷,移除了对 SelfLink 的支持,而默认上面指定的 provisioner 版本需要 SelfLink 功能,因此 PVC 无法进行自动制备。
配置 SelfLink
修改 apiserver 配置文件
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false # 新增该行
2
3
4
5
修改后重新应用该配置
kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
不需要 SelfLink 的 provisioner
将 provisioner 修改为如下镜像之一即可
gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0 (opens new window)
registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0 (opens new window)
即:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-system
labels:
app: nfs-client-provisioner
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: registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.199.28
- name: NFS_PATH
value: /nfs/rw
volumes:
- name: nfs-client-root
nfs:
server: 192.168.199.28
path: /nfs/rw
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
PVC 测试配置:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: managed-nfs-storage
2
3
4
5
6
7
8
9
10
11
创建测试 Pod,查看是否可以正常挂载
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1" # 创建一个SUCCESS文件后退出
volumeMounts:
- name: nfs-pvc # 挂载数据卷
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim: # 数据卷挂载的是 PVC
claimName: test-pvc # 与 PVC 名称保持一致
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
所以总结就是:
- 先创建一个 Pod 制备器(Provisioner),即用 Deployment 创建制备器 Pod,创建制备器 Pod 的 yml 文件绑定存储设备(如 NFS)
- 然后创建 StorageClass,创建 StorageClass 的 yml 绑定制备器
- 然后创建 PVC,PVC 的 yml 文件绑定 StorageClass
- 最后创建一个 Pod 应用,该应用绑定 PVC,并告诉 PVC 需要多少的存储空间,最后 PVC 告诉制备器 Pod 自动在存储设备(如 NFS)创建一个 PV