一个计算机技术爱好者与学习者

0%

好好学K8S:K8S中的Volume

1. Volume简介

Container 中的文件在磁盘上是临时存放的,这会带来两个问题:1)当容器崩溃时文件会丢失;2)同一Pod中运行多个容器并共享文件时,容器重启时状态丢失。Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。

Docker卷的本质是目录,存在与一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过“联合文件系统”提供一些用于持续存储或共享数据的特性。
K8S卷的本质也是目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。

Kubernetes中的卷分为两类:临时卷和持久卷。临时卷会遵从 Pod 的生命周期,和Pod一起创建和删除;持久卷的生命周期独立于pod的生命周期。

Kubernetes中常用的卷类型包括:

  • configMap:临时卷/投射卷,常用来存储配置数据
  • secret:临时卷/投射卷,常用来存储配置数据,使用base64加密数据
  • downwardAPI:临时卷/投射卷,让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
  • emptyDir:临时卷,存储pod中的数据,pod删除时数据会被删除
  • hostPath:持久卷,将主机节点上的文件或目录挂载到pod中(存在许多安全风险,尽量避免使用)
  • local:持久卷,创建pv时指定pv所在节点,使用pvc的pod会被调度到指定节点,将主机节点上的文件或目录挂载到pod中
  • nfs:持久卷,将 NFS (网络文件系统) 挂载到的pod中。
  • persistentVolumeClaim:持久卷申领,不需要知道持久卷细节。
  • projected:投射卷能将多个卷来源映射到同一目录上。

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。

PVC与PV是一一对应关系,不能一个PVC挂载多个PV,也不能一个PV挂载多个PVC。

参考文档:

2. ConfigMap/Secret为什么是临时卷?

临时卷会遵从 Pod 的生命周期,和Pod一起创建和删除。

ConfigMap本身是一个API对象,用来将非机密性的数据保存到键值对中。使用时,Pods可以将其用作环境变量、命令行参数或者存储卷中的配置文件。
当容器挂载 ConfigMap 时,ConfigMap的信息会被节点本地临时存储,然后挂载到容器的文件系统中。当Pod删除时,容器的文件系统没了,本地临时存储中的ConfigMap信息也没了,因此ConfigMap是临时卷。

Secret同理。

其实,如果说ConfigMap/Secret是持久卷,也没有什么问题,因为ConfigMap/Secret不会随着Pod删除而删除。

不用太纠结于这种概念性的东西,了解即可。

3. 使用configmap

我们有四种方式来使用 ConfigMap 配置 Pod 中的容器:

  • 容器的环境变量
  • 在容器命令和参数内
  • 在只读卷里面添加一个文件,让应用来读取
  • 编写代码在 Pod 中运行,使用 Kubernetes API 来读取 ConfigMap

参考文档:

3.1. 生成configmap

3.1.1. 基于文字生成configmap

1
2
3
4
5
kubectl create cm game-demo \
--from-literal=sleep_seconds=3600 \
--from-literal=player_initial_lives=3 \
--from-literal=ui_properties_file_name=user-interface.properties \
--dry-run=client -oyaml > configmap.yaml

3.1.2. 基于环境变量配置文件生成configmap

1、准备环境变量配置文件 env.sh

1
2
3
sleep_seconds=3600
player_initial_lives=3
ui_properties_file_name=user-interface.properties

2、生成config文件

1
2
3
kubectl create cm game-demo \
--from-env-file=env.sh \
--dry-run=client -oyaml > configmap.yaml

3.1.3. 基于文件生成configmap

1、准备配置文件 game.properties 和 user-interface.properties

1
2
enemy.types=aliens,monsters
player.maximum-lives=5
1
2
3
color.good=purple
color.bad=yellow
allow.textmode=true

2、生成configmap文件

1
2
3
4
kubectl create cm game-demo \
--from-file=game.properties=game.properties \
--from-file=user-interface.properties=user-interface.properties \
--dry-run=client -oyaml > configmap.yaml

3.2. pod中使用configmap简单示例

1、configmap.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 类属性键;每一个键都映射到一个简单的值
sleep_seconds: "3600"
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

2、pod.yaml

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
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: alpine
command: ["sleep", "$(SLEEP_SECONDS)"]
env:
# 定义环境变量
- name: SLEEP_SECONDS # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: sleep_seconds # 需要取值的键
- name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: player_initial_lives # 需要取值的键
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name: config
configMap:
# 提供你想要挂载的 ConfigMap 的名字
name: game-demo
# 来自 ConfigMap 的一组键,将被创建为文件
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"

ConfigMap 不会区分单行属性值和多行类似文件的值,重要的是 Pods 和其他对象如何使用这些值。

上面的例子定义了一个卷并将它作为 /config 文件夹挂载到 demo 容器内, 创建两个文件,/config/game.properties 和 /config/user-interface.properties, 尽管 ConfigMap 中包含了四个键。 这是因为 Pod 定义中在 volumes 节指定了一个 items 数组。 如果你完全忽略 items 数组,则 ConfigMap 中的每个键都会变成一个与该键同名的文件, 因此你会得到四个文件。

PS:使用 envFrom 可以将 ConfigMap 的所有数据定义为容器的环境变量。

4. 使用secret

Pod 可以用三种方式之一来使用 Secret:

  • 作为容器的环境变量。
  • 作为挂载到一个或多个容器上的卷 中的文件。
  • 由 kubelet 在为 Pod 拉取镜像时使用。

创建 Secret 时,我们可以使用 Secret 资源的 type 字段,或者与其等价的 kubectl 命令行参数(如果有的话)为其设置类型。 Secret 类型有助于对 Secret 数据进行编程处理。

Kubernetes 提供若干种内置的类型,用于一些常见的使用场景。 针对这些类型,Kubernetes 所执行的合法性检查操作以及对其所实施的限制各不相同。

如果 type 值为空字符串,则被视为 Opaque 类型(用户定义的任意数据)。

Secret使用base64加密,可以使用echo "YWRtaW4=" | base64 -d命令进行解密。

参考文档:

4.1. 生成secret

4.1.1. 基于文字生成secret

1
2
3
4
kubectl create secret generic mysecret \
--from-literal=username=admin \
--from-literal=password=1f2d1e2e67df \
--dry-run=client -oyaml > secret.yaml

4.1.2. 基于环境变量配置文件生成secret

1、准备环境变量配置文件 env.sh

1
2
username=admin
password=1f2d1e2e67df

2、生成secret

1
2
3
kubectl create secret generic mysecret \
--from-env-file=env.sh \
--dry-run=client -oyaml > secret.yaml

4.1.3. 基于文件生成secret

1
2
3
4
kubectl create secret generic mysecret \
--from-file=ssh-privatekey=path/to/id_rsa \
--from-file=ssh-publickey=path/to/id_rsa.pub \
--dry-run=client -oyaml > secret.yaml

4.2. pod中使用secret简单示例

1、secret.yaml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm

2、pod.yaml

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
apiVersion: v1
kind: Pod
metadata:
name: secret-demo-pod
spec:
containers:
- name: mycontainer
image: alpine
command: ["sleep", "3600"]
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
optional: false # 此值为默认值;意味着 "mysecret"
# 必须存在且包含名为 "username" 的主键
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
optional: false # 此值为默认值;意味着 "mysecret"
# 必须存在且包含名为 "password" 的主键
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
defaultMode: 0400
restartPolicy: Never

容器中看到的环境变量,和文件中的内容,都是明文。

PS:使用 envFrom 可以将 Secret 的所有数据定义为容器的环境变量。

4.3. imagePullSecrets

参考文档 《K8S配置使用imagePullSecrets》

5. 使用emptyDir

参考文档K8S官方文档 - 卷 - emptyDir configuration example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: registry.k8s.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
sizeLimit: 500Mi

6. 使用hostPath

参考文档K8S官方文档 - 卷 - hostPath configuration example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: registry.k8s.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory

7. 使用nfs

7.1. 直接使用nfs

参考文档K8S官方文档 - 卷 - nfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: busybox
name: busybox
command: ["sleep", "3600"]
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

7.2. nfs作为pv

参考文档:

1、pv.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
labels:
release: "stable"
environment: "dev"
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
storageClassName: slow
mountOptions:
- hard
- nfsvers=4
nfs:
server: my-nfs-server.example.com
path: /my-nfs-volume

需要注意的是,persistentVolumeReclaimPolicy使用的Retain,当pvc删除后,真实存储设备上的数据依然存在,pv不能被其他pvc申领;当pv删除后,真实存储设备上的数据依然存在。

如果使用Delete,那么当pvc删除后,pv也会被删除,真实存储设备上的数据也会被删除。pv自动删除可能会删除失败,需要手动删除,手动删除pv后,真实存储设备上的数据依然存在。

如果使用Recycle,那么当pvc删除后,真实存储设备上的数据会被删除(rm -rf /thevolume/*),pv可以被新的pvc申领;如果只删除pv,真实存储设备上的数据不会被删除。
回收策略Recycle 已被废弃。取而代之的建议方案是使用动态制备。

2、pvc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadOnlyMany
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}

3、pod.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: busybox
name: busybox
command: ["sleep", "3600"]
volumeMounts:
- mountPath: /my-nfs-data
name: test-volume
volumes:
- name: test-volume
persistentVolumeClaim:
claimName: nfs-pvc

8. 使用storageclass

参考文档《K8S中安装配置StorageClass》

9. subPath说明

有时,在单个 Pod 中共享卷以供多方使用是很有用的。 volumeMounts.subPath 属性可用于指定所引用的卷内的子路径,而不是其根路径。

例如:

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
apiVersion: v1
kind: Pod
metadata:
name: my-lamp-site
spec:
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpasswd"
volumeMounts:
- mountPath: /var/lib/mysql
name: site-data
subPath: mysql
- name: php
image: php:7.0-apache
volumeMounts:
- mountPath: /var/www/html
name: site-data
subPath: html
volumes:
- name: site-data
persistentVolumeClaim:
claimName: my-lamp-site-data

上面的例子中,Pod 使用同一共享卷。其中 PHP 应用的代码和相关数据映射到卷的 html 文件夹,MySQL 数据库存储在卷的 mysql 文件夹。

使用 subPathExpr 字段可以基于 downward API 环境变量来构造 subPath 目录名。 subPath 和 subPathExpr 属性是互斥的。
例如:

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
apiVersion: v1
kind: Pod
metadata:
name: pod1
spec:
containers:
- name: container1
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
image: busybox:1.28
command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
volumeMounts:
- name: workdir1
mountPath: /logs
# 包裹变量名的是小括号,而不是大括号
subPathExpr: $(POD_NAME)
restartPolicy: Never
volumes:
- name: workdir1
hostPath:
path: /var/log/pods

在这个示例中,Pod 使用 subPathExpr 在 hostPath 卷 /var/log/pods 中创建目录 pod1。 hostPath 卷采用来自 downwardAPI 的 Pod 名称生成目录名。 宿主目录 /var/log/pods/pod1 被挂载到容器的 /logs 中。

如果我们想要挂在一个configmap或者secret配置文件到pod,但是不想覆盖pod中的文件所在目录,这时也需要使用subPath。

1
2
3
4
5
6
7
8
9
10
11
12
13
spec:
containers:
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: nginx-config
items:
- key: "nginx.conf"
path: "nginx.conf"

参数说明:

  • volumes.configMap.items.key 表示configmap中的data key
  • volumes.configMap.items.path 表示要将key映射到的文件的相对路径(相对于volume)
  • containers.volumeMounts.mountPath 表示volume在容器中的挂载路径
  • containers.volumeMounts.subPath 表示volume的子路径

本例中,configmap作为volume,可以认为是根目录/,key nginx.conf 被映射为volume的相对路径nginx.conf,绝对路径就是 /nginx.conf
容器中挂载volume,使用volume的子路径nginx.conf(绝对路径/nginx.conf),挂载到容器中的/etc/nginx/nginx.conf

  • 本文作者: 好好学习的郝
  • 原文链接: https://www.voidking.com/dev-k8s-volume/
  • 版权声明: 本文采用 BY-NC-SA 许可协议,转载请注明出处!源站会即时更新知识点并修正错误,欢迎访问~
  • 微信公众号同步更新,欢迎关注~