LEMON

记录站

0%

K8S 存储(八)

K8S 存储

  • ConfigMap
  • Secret
  • volume
  • Persistent Volume(PV)

ConfigMap

ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象。

ConfigMap 的创建

  • 目录创建
  • 文件创建
  • 字面值创建

使用目录创建

1
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$ ll /etc/kubernetes/configmap-dir/
game.properties
ui.properties


$ cat /etc/kubernetes/configmap-dir/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30


$ cat /etc/kubernetes/configmap-dir/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice


# --from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容。
$ kubectl create configmap game-config-1 --from-file=/etc/kubernetes/configmap-dir


$ kubectl get configmap -n default -owide
NAME DATA AGE
game-config-1 2 <invalid>


$ kubectl get configmap -n default -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
game.properties: |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
creationTimestamp: "2021-03-08T11:54:57Z"
name: game-config-1
namespace: default
resourceVersion: "15839"
selfLink: /api/v1/namespaces/default/configmaps/game-config-1
uid: cb84c4f5-e509-4ecb-8513-68184b8b57af
kind: List
metadata:
resourceVersion: ""
selfLink: ""


$ kubectl describe configmap game-config-1 -n default
Name: game-config-1
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
ui.properties:
----
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30

Events: <none>

使用文件创建

只要指定为一个文件就可以从单个文件中创建 ConfigMap

1
2
3
$ kubectl create configmap game-config-2 --from-file=/etc/kubernetes/configmap-dir/game.properties
$ kubectl get configmaps game-config-2 -o yaml
# --from-file这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的。

使用字面值创建

使用文字值创建,利用–from-literal参数传递配置信息,该参数可以使用多次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl create configmap game-config-3 --from-literal=k1.how=v1 --from-literal=k2.how=v2
$ kubectl get configmaps game-config-3 -o yaml
apiVersion: v1
data:
k1.how: v1
k2.how: v2
kind: ConfigMap
metadata:
creationTimestamp: "2021-03-08T12:01:19Z"
name: game-config-3
namespace: default
resourceVersion: "16388"
selfLink: /api/v1/namespaces/default/configmaps/game-config-3
uid: b9e048f3-a17d-4517-bfe5-311c51377aaa

Pod 中使用 ConfigMap

使用 ConfigMap 来替代环境变量

  • env: 指定导入k/v
  • envFrom: 全部导入
1
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
# cat cm-test-01.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.sam: charm
special.lep: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
log_level: DEBUG
---
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod-01
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: [ "/bin/sh", "-c", "env" ]
env:
- name: SPECIAL_HOW_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: SPECIAL_SAM_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.sam
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never
1
2
3
4
$ kubectl apply -f cm-test-01.yaml
$ kubectl logs cm-test-pod-01 | grep SPECIAL
SPECIAL_HOW_KEY=very
SPECIAL_SAM_KEY=charm

用 ConfigMap 设置命令行参数

1
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
# cat cm-test-02.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-01
namespace: default
data:
a: b
c: d
---
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod-02
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: ["/bin/sh","-c","echo $(KEY-01) $(KEY-02)"]
env:
- name: KEY-01
valueFrom:
configMapKeyRef:
name: config-01
key: a
- name: KEY-02
valueFrom:
configMapKeyRef:
name: config-01
key: c
restartPolicy: Never
1
2
$ kubectl logs cm-test-pod-02
b d

通过数据卷插件使用ConfigMap

1
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
# cat cm-test-03.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
name: config-02
namespace: default
data:
how: HOW
sam: SAM
---
# 在数据卷里面使用这个 ConfigMap,有不同的选项。最基本的就是将文件填入数据卷,在这个文件中,键就是文件名,键值就是文件内容。
apiVersion: v1
kind: Pod
metadata:
name: test-pod-02
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: ["/bin/sh","-c","sleep 36000"]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: config-02
restartPolicy: Never
1
2
3
4
5
6
7
8
9
10
$ kubectl exec -it test-pod-02 -- ls -lh /etc/config
total 0
lrwxrwxrwx 1 root root 10 Mar 9 08:44 how -> ..data/how
lrwxrwxrwx 1 root root 10 Mar 9 08:44 sam -> ..data/sam

$ kubectl exec -it test-pod-02 -- cat /etc/config/how
HOW

$ kubectl exec -it test-pod-02 -- cat /etc/config/sam
SAM

ConfigMap 的热更新

1
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
apiVersion: v1
kind: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: arminto/my_nginx:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: log-config
1
2
$ kubectl exec -it my-nginx-7b6584d96d-hpbml -- cat /etc/config/log_level
INFO

修改 ConfigMap,将其INFO改为DEBUG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ kubectl edit configmap log-config
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
log_level: DEBUG
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}
creationTimestamp: "2021-03-09T09:04:08Z"
name: log-config
namespace: default
resourceVersion: "36210"
selfLink: /api/v1/namespaces/default/configmaps/log-config
uid: 8e2f15fc-cc05-414b-ad2f-9597cacf7c9a

修改log_level的值为DEBUG等待大概 10 秒钟时间,再次查看环境变量的值

1
2
$ kubectl exec -it my-nginx-7b6584d96d-hpbml -- cat /etc/config/log_level
DEBUG

Secret

Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec中。

并且,Secret 可以以 Volume 或者 环境变量的方式使用。

Secret 有三种类型

  • Service Account
  • Opaque
  • kubernetes.io/dockerconfigjson

Service Account

大致了解一下即可,Service Account 用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

PS : 只有与 apiserver 组件进行交互的 pod 才会有如下的 证书、命名空间、tekon密钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl exec -it kube-proxy-2x7r8 -n kube-system -- ls -lh /run/secrets/kubernetes.io/serviceaccount
total 0
lrwxrwxrwx 1 root root 13 Mar 10 02:50 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Mar 10 02:50 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Mar 10 02:50 token -> ..data/token

# 这几个文件是 default-token 提供的,而 default-token 是 k8s 默认为每一个namespace 创建的,用于Service Account
$ kubectl get secret
NAME TYPE DATA AGE
default-token-c6vqw kubernetes.io/service-account-token 3 46d

$ kubectl describe secret default-token-c6vqw
…………………………………………………………………………
Data
====
ca.crt: 1025 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tYzZ2cXciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjJjNjlmMjkzLTcwNTQtNDY4My05ODNmLTljYmQyNTBlZTQ1ZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.WdQ32P2ndcKUNbyrsKouTgBEVQf8Smq1mSd9zRpBou1KN2DUJ6UPWHvMdDlqwvN2CACOWZcri0eN8NABvNEW6cQMv7O7-GoVsuv5HLDxmO1IC1tTVUr8d-uBxULiSiQZ_Mj2LK1QKYNAHzVYRlEozzI3wZAOWBDqA7ZFVpXRQMES0lzlRc9ETZqQmxIHgF_hyTzpsRaLkR480vppOTORejZGrm_wiSvYVKKvNe3H35TRtHARCZDlGv7DAickld2rnYuKSyLdZz29CmDiE6L0ZyzTCMgPalNkmjcuij28XG19I4GLio7IiS5ZMDrQsmr_4t3G449PPSiH_0C78LICuA

Opaque Secret

base64编码格式的Secret,用来存储密码、密钥等。

Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式。

1、创建 Opaque-Secret

1
2
3
4
5
6
# base64 -d 选项为解密
$ echo -n "admin" | base64
YWRtaW4=

$ echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm
1
2
3
4
5
6
7
8
9
# cat opaque-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: op-secret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
1
2
3
4
5
6
7
8
9
10
11
$ kubectl get secret
NAME TYPE DATA AGE
default-token-c6vqw kubernetes.io/service-account-token 3 46d
op-secret Opaque 2 11s

$ kubectl describe secret op-secret
…………………………………………………………………………
Data
====
password: 12 bytes
username: 5 bytes

2、使用方式

  • 将 Secret 挂载到 Volume 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat volume-secret.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: seret-test
name: seret-test
spec:
volumes:
- name: secrets
secret:
secretName: op-secret
containers:
- image: arminto/my_nginx:v1
name: db
volumeMounts:
- name: secrets
mountPath: '/etc/secrets'
readOnly: true
1
2
3
4
5
6
7
8
9
10
11
$ kubectl exec -it seret-test -- ls -l /etc/secrets
total 0
lrwxrwxrwx 1 root root 15 Mar 10 03:25 password -> ..data/password
lrwxrwxrwx 1 root root 15 Mar 10 03:25 username -> ..data/username

# 并且 secret 是会自己进行解密的
$ kubectl exec -it seret-test -- cat /etc/secrets/password
1f2d1e2e67df

$ kubectl exec -it seret-test -- cat /etc/secrets/username
admin
  • 将 Secret 导出到环境变量中
1
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
# cat env-secret.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: pod-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: pod-deployment
spec:
containers:
- name: pod-1
image: arminto/my_nginx:v1
ports:
- containerPort: 80
env:
- name: TEST_USER
valueFrom:
secretKeyRef:
name: op-secret
key: username
- name: TEST_PASSWORD
valueFrom:
secretKeyRef:
name: op-secret
key: password
1
2
3
4
5
$ kubectl exec -it pod-deployment-7cfd69dcf7-222q2 -- /bin/sh
/ # echo $TEST_USER
admin
/ # echo $TEST_PASSWORD
1f2d1e2e67df

dockerconfigjson

用来存储私有 docker registry 的认证信息。

1、使用 Kuberctl 创建 docker registry 认证的 secret(注:针对私有仓库)

1
2
3
4
5
$ kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD

# --docker-server 仓库地址
# --docker-username 仓库用户
# --docker-password 仓库密码

2、在创建 Pod 的时候,通过 imagePullSecrets 来引用刚创建的 myregistrykey

1
2
3
4
5
6
7
8
9
10
11
# cat key-secret.yaml 
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: hub.lemon.com/library/my_nginx:v1
imagePullSecrets:
- name: myregistrykey

Volume

容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的Volume抽象就很好的解决了这些问题。

Kubernetes 中的卷有明确的寿命 —— 与封装它的 Pod 相同。所f以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes支持多种类型的卷,Pod 可以同时使用任意数量的卷。

Kubernetes 支持以下类型的卷:

  • awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir
  • fc、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs
  • persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret
  • storageos、vsphereVolume

比较常用的两类 emptyDir、hostPath

emptyDir

当 Pod 被分配给节点时,首先创建emptyDir卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir中的数据将被永久删除。

emptyDir的用法有:

  • 暂存空间,例如用于基于磁盘的合并排序
  • 用作长时间计算崩溃恢复时的检查点
  • Web服务器容器提供数据时,保存内容管理器容器提取的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# cat emptyDir.yaml 
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- name: test-container01
image: arminto/my_nginx:v1
volumeMounts:
- mountPath: /cache01
name: cache-volume
- name: test-container02
image: tomcat:latest
volumeMounts:
- mountPath: /cache02
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl exec -it test-pd -c test-container01 -- /bin/sh
/ # cd /cache01
/cache01 # hostname >> test.log
/cache01 # cat test.log
test-pd

$ kubectl exec -it test-pd -c test-container02 -- /bin/sh
# cd /cache02
# ls
test.log
# cat test.log
test-pd

hostPath

hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中。

hostPath 的用途如下:

  • 运行需要访问 Docker 内部的容器;使用 /var/lib/docker 的 hostPath
  • 在容器中运行 cAdvisor;使用 /dev/cgroups 的 hostPath
  • 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建,以及它应该以什么形式存在

除了所需的path属性之外,用户还可以为hostPath卷指定type

行为
空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。
Directory 给定的路径下必须存在目录。
FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
File 给定的路径下必须存在文件。
Socket 给定的路径下必须存在 UNIX 套接字。
CharDevice 给定的路径下必须存在字符设备。
BlockDevice 给定的路径下必须存在块设备。

使用这种卷类型是请注意,因为:

  • 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的 pod 在不同节点上的行为可能会有所不同。
  • 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑hostPath使用的资源。
  • 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入hostPath卷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: h-pod
spec:
containers:
- image: arminto/my_nginx:v1
name: test-container
volumeMounts:
- mountPath: /hv
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
1
2
3
4
5
6
7
8
9
10
$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE
h-pod 0/1 ContainerCreating 0 7s <none> k8s-node-192.168.2.22

$ hostname
k8s-node-192.168.2.22
$ echo 'hv test' > /data/h-test.log

$ kubectl exec -it h-pod -- cat /hv/h-test.log
hv test

Persistent Volume(PV)

PersistentVolume(简称PV) 是 Volume 之类的卷插件,也是集群中的资源,但独立于Pod的生命周期(即不会因Pod删除而被删除),不归属于某个Namespace。

PersistentVolumeClaim(简称PVC)是用户存储的请求,PVC消耗PV的资源,可以请求特定的大小和访问模式,需要指定归属于某个Namespace,在同一个Namespace 的 Pod才可以指定对应的PVC。

PV (持久化卷),是对底层的共享存储的一种抽象,PV 由管理员进行创建和配置, 它和具体的底层的共享存储技术的实现方式有关,比如Ceph、GlusterFS、NFS等,都是通过插件机制完成与共享存储的对接。

PVC (持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类型,Pod 是消耗节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 的内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正存储的用户不需要关心底层的存储实现细节,只需要直接使用PVC即可。

但是通过PVC请求一定的存储空间也很有可能不足以满足对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求也能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes又为我们引入了一个新的资源对象: StorageClass,通过StorageClass的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据StorageClass的描述就可以非常直观的知道各种存储资源特性了,这样就可以根据应用的特性去申请合适的存储资源了。

PV 和 PVC的生命周期

PV 可以看作可用的存储资源,PVC则是对存储资源的需求,PV 和 PVC的互相关系遵循如下图

资源供应 (Provisioning)

Kubernetes支持两种资源的供应模式:静态模式(Staic)和动态模式(Dynamic)。资源供应的结果就是创建好的PV。

  • 静态模式:集群管理员手工创建许多 PV,在定义 PV 时需要将后端存储的特性进行设置。
  • 动态模式:集群管理员无须手工创建 PV,而是通过 StorageClass 的设置对后端存储进行描述,标记为某种 “类型(Class)”。此时要求 PVC 对存储的类型进行声明,系统将自动完成 PV 的创建及 PVC 的绑定。
    • PVC 可以声明 Class 为””,说明该 PVC 禁止使用动态模式。

1、静态资源下,通过PV和PVC完成绑定,并供Pod使用的存储管理机制

2.动态资源下,通过StorageClass和PVC完成资源动态绑定 (系统自动生成PV,并供Pod使用的存储管理机制)

资源绑定 (Binding)

在用户定义好PVC后,系统将根据PVC对存储资源的请求 (存储空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC进行绑定,然后用户的应用就可以使用这个PVC了。如果系统中没有满足PVC要求的PV,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合要求的PV。PV一旦绑定在某个PVC上,就被这个PVC独占,不能再与其他PVC进行绑定了。在这种情况下,当PVC申请的存储空间比PV的少时,整个PV的空间都能够为PVC所用,可能会造成资源的浪费。如果资源供应使用的是动态模式,则系统在PVC找到合适的StorageClass后,将会自动创建PV并完成PVC的绑定。

资源使用 (Using)

Pod 使用volume的定义, 将 PVC 挂载到容器内的某个路径进行使用。volume 的类型为 persistentVoulumeClaim , 在容器应用挂载了一个 PVC 后, 就能被持续独占使用。

不过, 多个Pod可以挂载同一个PVC, 应用程序需要考虑多个实例共同访问一块存储空间的问题。

资源释放 (Releasing)

当用户对存储资源使用哪个完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为已释放,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还留在存储设备上,只有在清除之后该PV才能继续使用。

资源回收 (Reclaiming)

对于PV,管理员可以设定回收策略(Reclaim Policy)用于设置与之绑定的PVC释放资源之后,对于遗留数据如何处理。只有PV的存储空间完成回收,才能供新的PVC绑定和使用。

持久化卷声明的保护

PVC 保护的目的是确保由 pod 正在使用的 PVC 不会从系统中移除,因为如果被移除的话可能会导致数据丢失,当启用 PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。

PV 的类型 及 访问模式

PersistentVolume 类型以插件形式实现。以下仅列部分常用类型:

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • NFS
  • RBD (Ceph Block Device)
  • CephFS、Glusterfs

PV 的访问模式 有三种 ReadWriteOnce 、 ReadOnlyMany 、 ReadWriteMany

PersistentVolume 可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式。

  • ReadWriteOnce——该卷可以被单个节点以读/写模式挂载(命令行缩写:RWO)
  • ReadOnlyMany——该卷可以被多个节点以只读模式挂载(命令行缩写:ROX)
  • ReadWriteMany——该卷可以被多个节点以读/写模式挂载(命令行缩写:RWX)

一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。以下只列举部分常用插件

Volume 插件 ReadWriteOnce ReadOnlyMany ReadWriteMany
AWSElasticBlockStore - -
CephFS
GCEPersistentDisk -
Glusterfs
HostPath - -
NFS
RBD -

PV 的回收策略 及 阶段状态

回收策略包括

  • Retain(保留)——手动回收
  • Recycle(回收)——基本擦除( rm -rf /thevolume/* )【ps 在新版本中该策略已被弃用】
  • Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除。

当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。

PV 可以处于以下的某种状态

  • Available(可用)——一块空闲资源还没有被任何声明绑定
  • Bound(已绑定)——卷已经被声明绑定
  • Released(已释放)——声明被删除,但是资源还未被集群重新声明
  • Failed(失败)——该卷的自动回收失败

命令行会显示绑定到 PV 的 PVC 的名称。

PV 的实验演练 - NFS

部署 NFS 服务器

1
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
# 所有的节点都需要安装 nfs-utils 和 rpcbind
$ yum install -y nfs-common nfs-utils rpcbind
$ mkdir -p /nfs01 /nfs02 /nfs03 /nfs04
$ chmod 777 /nfs01 /nfs02 /nfs03 /nfs04
$ chown nfsnobody /nfs01 /nfs02 /nfs03 /nfs04
$ cat /etc/exports
/nfs01 *(rw,no_root_squash,no_all_squash,sync)
/nfs02 *(rw,no_root_squash,no_all_squash,sync)
/nfs03 *(rw,no_root_squash,no_all_squash,sync)
/nfs04 *(rw,no_root_squash,no_all_squash,sync)
$ systemctl start rpcbind nfs

# 登陆k8s任意一台节点上测试下nfs
$ showmount -e 192.168.245.44
Export list for 192.168.245.44:
/nfs01 *
/nfs02 *
/nfs03 *
/nfs04 *
$ mkdir /test
$ mount -t nfs 192.168.245.44:/nfs01 /test/
$ cd /test/
$ touch tset.txt
$ echo lemon > test.txt
$ cat test.txt
lemon
$ umount /test
$ rm -rf /test

部署 PV 卷

1
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
# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs01
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv2
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs02
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv3
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs03
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv4
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: static
nfs:
path: /nfs04
server: 192.168.245.44

创建服务并使用 PVC

1
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
# cat pvc.yaml 
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: arminto/my_nginx:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi

1
2
3
4
5
6
7
8
9
10
$ echo 'web-0' > /nfs01/index.html
$ echo 'web-1' > /nfs02/index.html
$ echo 'web-2' > /nfs03/index.html

$ curl 10.244.2.24
web-0
$ curl 10.244.1.26
web-1
$ curl 10.244.1.27
web-2

关于 StatefulSet 的说明

  • 匹配 Pod name ( 网络标识 ) 的模式为:$(statefulset名称)-$(序号),比如上面的示例:web-0,web-1,web-2 。
1
2
3
4
5
6
$ kubectl get pod 
NAME READY STATUS RESTARTS AGE
h-pod 1/1 Running 0 23h
web-0 1/1 Running 0 48m
web-1 1/1 Running 0 48m
web-2 1/1 Running 0 48m
  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless servername),也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化。
1
2
3
4
5
6
7
$ kubectl exec -it h-pod -- /bin/sh

/ # ping -c 3 web-0.nginx
PING web-0.nginx (10.244.2.24): 56 data bytes
64 bytes from 10.244.2.24: seq=0 ttl=64 time=0.137 ms
64 bytes from 10.244.2.24: seq=1 ttl=64 time=0.056 ms
64 bytes from 10.244.2.24: seq=2 ttl=64 time=0.076 ms
  • StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:$(servicename).$(namespace).svc.cluster.local,其中,“cluster.local” 指的是集群的域名。
1
2
3
4
5
6
7
8
9
$ kubectl get pod -owide -n kube-system | grep coredns
coredns-5c98db65d4-4v4kd 1/1 Running 13 47d 10.244.1.17
coredns-5c98db65d4-9k5xw 1/1 Running 7 47d 10.244.0.9

$ dig -t A nginx.default.svc.cluster.local. @10.244.0.9
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 11 IN A 10.244.2.24
nginx.default.svc.cluster.local. 11 IN A 10.244.1.26
nginx.default.svc.cluster.local. 11 IN A 10.244.1.27
  • 根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式:(volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Podname=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2 。
1
2
3
4
5
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfspv1 1Gi RWO nfs 173m
www-web-1 Bound nfspv2 10Gi RWO nfs 172m
www-web-2 Bound nfspv3 20Gi RWO nfs 172m
  • 删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv

Statefulset的启停顺序

  • 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl get pv 
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Available nfs
nfspv2 10Gi RWO Retain Available static
nfspv3 20Gi RWO Retain Available slow

$ kubectl apply -f pvc.yaml
service/nginx created
statefulset.apps/web created

# 这里可以看到第二个 pod 一直处于 pending 状态,导致第三个 pod 都没有创建出来
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 19s
web-1 0/1 Pending 0 16s
  • 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0。
1
2
$ kubectl get pod -owide -n default -w
$ kubectl delete statefulset web -n default
  • 有序扩展:当对 Pod 执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。
1
$ kubectl scale statefulset/web --replicas=4

StatefulSet使用场景

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现。
  • 稳定的网络标识符,即 Pod 重新调度后其 PodName 和 HostName 不变。
  • 有序部署,有序扩展,基于 init containers 来实现。
  • 有序收缩。

PV卷 的资源释放流程

生产环境下要再三确认后才可以进行如下操作

1
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
# statefulset > svc > pvc > 使用 edit 删除 pv 卷上的 pvc 记录
$ kubectl delete -f pvc.yaml
$ kubectl delete pvc www-web-0 www-web-1 www-web-2

# 虽然已经删除了 pvc,但实际上还并没有释放 pv,因为在pv的记录信息里面还是有使用者信息的,需要手工删除后才会释放
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Released default/www-web-0 nfs
nfspv2 10Gi RWO Retain Released default/www-web-1 nfs
nfspv3 20Gi RWO Retain Released default/www-web-2 nfs
nfspv4 30Gi RWX Retain Available static

# 手动删除这一段,pv 就会释放资源了, 其他的 pod 也就可以继续使用这些 pv 卷了
$ kubectl get pv nfspv1 -o yaml
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: www-web-0
namespace: default
resourceVersion: "69260"
uid: e175a86b-aa2c-4900-a7c4-faa1865315ab

$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Available nfs
nfspv2 10Gi RWO Retain Available nfs
nfspv3 20Gi RWO Retain Available nfs
nfspv4 30Gi RWX Retain Available static

# 如果这些卷及数据都不想要了的话,直接在对应的nfs机器上删除挂在点上的数据,并在master上删除pv卷 kubectl delete pv xx
-------------本文结束感谢您的阅读-------------