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 valueFrom: configMapKeyRef: name: game-demo key: sleep_seconds - name: PLAYER_INITIAL_LIVES valueFrom: configMapKeyRef: name: game-demo 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: - name: config configMap: name: game-demo 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 - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: mysecret key: password optional: false 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: path: /data 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
。