[TOC]
0x00 前言简述
描述: 我们知道在Docker中可以通过Volume将宿主机文件(配置文件、数据库等
等)映射到Container内部供其容器内的应用程序使用。在Kubernrtes中我们可以采用ConfigMap控制器
创建共享应用配置,亦可采用Kubernetes中的volume
(卷)在一个Pod内多个Container之间进行文件共享;
Q: K8s 与 Docker 两者之间卷的区别?
- 1) K8S 的 Volume (卷) 定义在Pod之上被同一个Pod内的多个容器挂载到具体文件之下便于文件的共享;
- 2) K8S 的 Volume (卷) 与 Pod 的生命周期相同(持久卷除外),即Pod销毁后Volume也会被销毁,但Pod中容器重启或者终止并不会导致Volumes中数据的丢失;
- 3) K8S 的 VOlume (卷) 支持多种分布式文件系统,例如GlusterFS、NFS、Ceph、
Volume 使用流程
- 1) 声明一个Volume(可以是多种类型)
- 2) 在容器内引用该Volume并Mount到Pod容器里的某个指定文件目录之中;
k8s持久化存储方案
描述: 共享存储为分布式系统非常重要的一部分,存储一般要求稳定、可用、性能、可靠。
- 1.从用户角度看
存储就是一块盘或者一个目录,用户不关心盘或者目录如何实现,用户要求非常“简单”,就是稳定,性能好。为了能够提供稳定可靠的存储产品,各个厂家推出了各种各样的存储技术和概念。为了能够让大家有一个整体认识,本文先介绍存储中的这些概念。 - 2.从存储介质角度
存储介质分为机械硬盘和固态硬盘(SSD)。机械硬盘泛指采用磁头寻址的磁盘设备,包括SATA硬盘和SAS硬盘。由于采用磁头寻址,机械硬盘性能一般,随机IOPS一般在200左右,顺序带宽在150MB/s左右。固态硬盘是指采用Flash/DRAM芯片+控制器组成的设备,根据协议的不同,又分为SATA SSD,SAS SSD,PCIe SSD和NVMe SSD。 - 3.从产品定义角度
存储分为本地存储(DAS),网络存储(NAS),存储局域网(SAN)和软件定义存储(SDS)四大类- DAS就是本地盘,直接插到服务器上
- NAS是指提供NFS协议的NAS设备,通常采用磁盘阵列+协议网关的方式
- SAN跟NAS类似,提供SCSI/iSCSI协议,后端是磁盘阵列
- SDS是一种泛指,包括分布式NAS(并行文件系统),ServerSAN等
- 4.从应用场景角度
存储分为文件存储(Posix/MPI),块存储(iSCSI/Qemu)和对象存储(S3/Swift)三大类。
Kubernetes是如何给存储定义和分类呢?
- 1.Kubernetes中跟存储相关的概念有PersistentVolume (PV)和PersistentVolumeClaim(PVC),PV又分为静态PV和动态PV。静态PV方式如下:
PV-PVC
- 2.动态PV需要引入StorageClass的概念,使用方式如下:
StorageClass
Kubernetes 常见类型以及支持的卷
configMap (常用)
secret (常用)
emptyDir (常用)
hostPath (常用)
nfs (常用)
persistentVolumeClaim (常用)
cephfs (常用)
- awsElasticBlockStore azureDisk azureFile csi downwardAPI
- fc flocker gcePersistentDisk gitRepo glusterfs iscsi local
- projected portworxVolume quobyte rbd scaleI0
- storageos vsphereVolume
0x01 Storage – 存储
描述: 以下都是 statefulSet 控制器
涉及有状态服务必须拥有的一些资源对象
- 1) configMap : 在k8s中专门用来存储配置文件。
- 2) Secret : 有一些需要加密的信息,例如密钥、用户名密码信息在Secret中可以被加密,是k8s中加密的
解决方案【base64】
。 - 3) Volume : 用于赋予k8s中pod共享存储卷的能力,例如可以通过nfs共享,本地磁盘目录共享等等。
- 4) Persistent Volume : 简称PV【持久卷】,还包含一个PVC,通过服务进行持久卷的构建。
- 5) StorageClass : 存储类可以动态的绑定PV(持久卷)和创建PVC(持久卷要求)。
- 6) Nfs / Cephfs : 常用的分布式共享存储解决方案。
1.ConfigMap
描述: 在部署应用时常常需要给应用程序提供一些配置信息,比如Database的IP地址和开放端口以及用户密码等;
常用的简单方法有如下几种:
- 1) 通过构建镜像时(Build)将应用配置文件打入Docker Images : 不灵活且以明文不安全,并且修改配置还需重新构建打包Image;
- 2) 通过environment(环境环境)传参 : 不灵活且明文传入,而且更新环境变量时资源对象将需要重新部署;
- 3) 配置管理中心 : 例如 百度 Disconf 、360 的qconf配置管理,在大规模的分布式系统之中将分布式应用所需的配置信息与应用程序进行分离及其重要,它可以极大的简化应用配置管理工作;
- 4) 通过K8s提供的 ConfigMap 资源对象进行实现;
Tips : DisConf 将 X/Y/Z 多个业务平台进行配置统一管理, 其主要实现目标是对于同一个上线包,无需改动配置,即可在多个环境(DEV/TEST/STAGE/PRODUCTION
)上线,简单的说其实现了配置信息与应用程序进行解耦操作
;
- 1) 动态化更改配置部署 : 当更改配置后无需重新打包或重启,即可实时生效;
- 2) 配置统一管理 : 提供Web平台统一管理多个配置的应用环境(`DEV/TEST/STAGE/PRODUCTION`),以及多个产品的所有配置;
- 3) 核心目标 : 一个 Jar 包到处运行;
configMap – 介绍
描述: ConfigMap 功能在 Kubernetes 1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量
中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制(即配置的一种统一管理方法
,使得应用程序与配置信息进行解耦),ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON 二进制大对象。
简单的说: 通过ConfigMap能够把配置以Key-Value
对的形式供其它资源对象消费;
Q: configMap 配置文件注册中心
A: 在集群中有一个配置文件注册中心,集群中的节点向配置中心索要属于自身的配置,配置中心根据各节点的主机名或ip地址进行分发配置。
作用: k8s创建configMap,pod去引用,来达到类似于配置文件注册中心的效果。
Tips : 不同集群获取到的信息是不一样的需要使用者自己配置;
configMap – 创建
描述: configMap的几种创建方式进行实践演示基本创建格式:kubectl create configmap [map-name] [data-source]
0) 通过--from-file
参数进行目录批量&单一文件configMap创建实践流程:
# (1) 查看目录文件清单 & 内容
cat > ~/K8s/Day8/demo1/dir1.properties <<'EOF'
dir.number=1
dir.name=demo1
dir.path=~/K8s/Day8/demo1
EOF
cat > ~/K8s/Day8/demo1/dir2.properties <<'EOF'
dir.number=2
dir.name=demo2
dir.path=~/K8s/Day8/demo2
EOF
cat > ~/K8s/Day8/demo2/file.properties <<'EOF'
file.number=2
file.name=file.properties
file.path=~/K8s/Day8/demo2/file.properties
file.html=configMap Test - WeiyiGeek
EOF
# (2) 目录批量创建 configMap (注意路径为全路径)
~/K8s/Day8/demo1$ ls
# dir1.properties dir2.properties
~/K8s/Day8/demo1$ kubectl create configmap dir-config --from-file=/home/weiyigeek/K8s/Day8/demo1/
# configmap/dir-config created
# - dir-config : 指创建的configMap的名称
# - --from-file : 指定该目录下的所有文件都会被用在ConfigMap里面创建一个键值对, 注意目录可以是在本地或者远程地址中;
# (3) 文件创建 configMap
~/K8s/Day8/demo1$ kubectl create configmap file-config --from-file=/home/weiyigeek/K8s/Day8/demo2/file.properties
# configmap/file-config created
# - --from-file : 在Volume中使用时键的名字就是文件名,值就是对应文件的内容【K/V结构】
# (4) 我们也可以通过Key制定文件创建,通过使用Key制定文件创建的好处是可以使用Key替代文件名称(类似 Alias )可以更改增强文件的可读性;
~/K8s/Day8/demo1$ kubectl create configmap file-config-aliase --from-file file-key=~/K8s/Day8/demo2/file.properties
# configmap/file-config-aliase created
# (5) 信息查看示例关键点
~/K8s/Day8/demo2$ kubectl get cm file-config-aliase
# NAME DATA AGE
# file-config-aliase 1 109s
~/K8s/Day8/demo2$ kubectl get cm file-config-aliase -o yaml
# apiVersion: v1
# data:
# file-key: | # 别名
# file.number=2
# file.name=file.properties
# file.path=~/K8s/Day8/demo2/file.properties
# file.html=configMap Test - WeiyiGeek
# kind: ConfigMap
Tips : 如需要通过多个文件创建configMap可以分别通过多个 –from-file 参数进行制定对应的配置文件;
- 1) 通过
--from-env-file
参数进行创建configMap与上面的方式不同,此种形式对env-file的内容格式有则严格的要求,并且如果您提供了多个env-file文件
仅最后一个文件的内容生效;
# (1) env-file.properties
cat > ~/K8s/Day8/demo2/env-file.properties <<'END'
name=weiyigeek
age=88
allowed="True"
END
# (2) 进行创建configMap
kubectl create configmap env-file --from-env-file env-file.properties
# configmap/env-file created
# (3) 查看 env-file 的configMap
~/K8s/Day8/demo2$ kubectl get cm env-file -o yaml
# apiVersion: v1
# data:
# age: "88"
# allowed: '"True"'
# name: weiyigeek
# kind: ConfigMap
# metadata:
# creationTimestamp: "2021-02-07T01:11:48Z"
# managedFields:
# - apiVersion: v1
# fieldsType: FieldsV1
# fieldsV1:
# f:data:
# .: {}
# f:age: {}
# f:allowed: {}
# f:name: {}
# manager: kubectl-create
# operation: Update
# time: "2021-02-07T01:11:48Z"
# name: env-file
# namespace: default
# resourceVersion: "19745894"
# selfLink: /api/v1/namespaces/default/configmaps/env-file
# uid: 9fb64e82-3ab1-4bcf-b528-c1f8b9aef026
2) 字面值创建简单示例:
# (1) 使用文字值创建利用--from-literal 参数传递配置信息,该参数可以使用多次;
~/K8s/Day8/demo2 $ kubectl create configmap text-config --from-literal=my.name=weiyigeek --from-literal=my.age=18 --from-literal=my.site=http://www.weiyigeek.top
# configmap/text-config created
Tips :通过字面值创建的configMap不便于配置管理的记录
所以不推荐使用;
3) 以资源清单创建
# (1) 资源清单
mkdir -pv ~/K8s/Day8/demo3/
cat > ~/K8s/Day8/demo3/configmap.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap-demo
namespace: default
data:
special.name: weiyigeek
special.site: www.weiyigeek.top
EOF
# (2) 创建
~/K8s/Day8/demo3$ kubectl create -f configmap.yaml
configmap/configmap-demo created
configMap – 操作
# 1.查看创建configMap信息
~/K8s/Day8/demo3$ kubectl api-resources | grep "configmap"
# configmaps cm(缩写) true ConfigMap # 没有 APIGROUP 其资源版本都是 v1
~/K8s/Day8/demo3$ kubectl get cm # 创建的configmap-demo
# NAME DATA AGE
# configmap-demo 2 3m3s
# dir-config 2 23m
# flie-config 1 16m
# text-config 3 10m
# 2.详细信息(通过信息展示的内容与通过cat命令查看一致)
~/K8s/Day8/demo3$ kubectl describe cm file-config
# kubectl get cm file-config -o yaml
# Name: file-config
# Namespace: default
# Labels: <none>
# Annotations: <none>
# Data
# ====
# file.properties: # Key
# ----
# file.number=2 # Value
# file.name=file.properties
# file.path=~/K8s/Day8/demo2/file.properties
# file.html=configMap Test - WeiyiGeek
# Events: <none>
# 3.删除指定名称的configMap的配置
~/K8s/Day8/demo3$ kubectl delete cm flie-config
# configmap "flie-config" deleted
configMap – 使用
(1) 创建Pod资源控制器并以ConfigMap作为环境变量&命令行参数(非常值得学习)
cat > configmap-use-1.yaml<<'EOF'
# Pod Controller
apiVersion: v1
kind: Pod
metadata:
name: configmap-env-demo
spec:
containers:
- name: configmap-env-demo
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME_KEY}, Site: ${SITE_KEY}" ]
env: # 关键点
- name: USERNAME_KEY
valueFrom:
configMapKeyRef: # 关键点key来源设置
name: configmap-demo
key: special.name # Value
- name: SITE_KEY
valueFrom:
configMapKeyRef:
name: configmap-demo
key: special.site # Value
envFrom:
- configMapRef:
name: configmap-demo # Key
restartPolicy: Never
EOF
configMap使用操作流程:
# 1.上传镜像到私有仓库
~/K8s/Day8/demo3$ docker tag busybox:latest harbor.weiyigeek.top/test/busbox:latest
~/K8s/Day8/demo3$ docker push harbor.weiyigeek.top/test/busbox:latest
# The push refers to repository [harbor.weiyigeek.top/test/busbox]
# d2421964bad1: Pushed
# latest: digest: sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc size: 527
# 2.部署Pod
~$ kubectl create -f configmap-use-1.yaml
# 3.查看
~$ kubectl get pod
# NAME READY STATUS RESTARTS AGE
# configmap-env-demo 0/1 Completed 0 157m
$ kubectl logs configmap-env-demo
# KUBERNETES_PORT=tcp://10.96.0.1:443
# KUBERNETES_SERVICE_PORT=443
# HOSTNAME=configmap-env-demo
# SHLVL=1
# HOME=/root
# SITE_KEY=www.weiyigeek.top # 关键点
# special.site=www.weiyigeek.top # 关键点
# KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# KUBERNETES_PORT_443_TCP_PORT=443
# KUBERNETES_PORT_443_TCP_PROTO=tcp
# KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
# KUBERNETES_SERVICE_PORT_HTTPS=443
# KUBERNETES_SERVICE_HOST=10.96.0.1
# PWD=/
# USERNAME_KEY=weiyigeek # 关键点
# special.name=weiyigeek # 关键点
# Name: weiyigeek, Site: www.weiyigeek.top # 输出
(2) 创建Pod资源控制器并通过数据卷(Volume)插件使用ConfigMap对象&热更新
# (1) 在数据卷里面使用这个ConfigMap,有不同的选项。
# 最基本的就是将文件填入数据卷,在这个文件中键就是文件名键值就是文件内容;
cat > configmap-use-3.yaml<<'EOF'
# Deployment Controller
apiVersion: apps/v1
kind: Deployment
metadata:
name: configmap-update-demo
spec:
replicas: 1
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: configmap-update-demo # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: configmap-update-demo
spec:
containers:
- name: configmap-update-demo
image: harbor.weiyigeek.top/test/nginx:v3.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html/
volumes:
- name: config-volume
configMap:
name: file-config
EOF
$ kubectl create -f configmap-use-3.yaml
# deployment.apps/configmap-update-demo created
# (2) 查看
~/K8s/Day8/demo4$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# configmap-env-demo 0/1 Completed 0 25h 10.244.1.129 k8s-node-4 <none>
# configmap-update-demo-597f67f4df-v82j6 1/1 Running 0 56s 10.244.1.131 k8s-node-4 app=configmap-update-demo,pod-template-hash=597f67f4df
~/K8s/Day8/demo4$ kubectl describe cm file-config # file-config 的ConfifMap信息
# Name: file-config
# Namespace: default
# Labels: <none>
# Annotations: <none>
# Data
# ====
# file.properties:
# ----
# file.number=2
# file.name=file.properties
# file.path=~/K8s/Day8/demo2/file.properties
# file.html=configMap Test - WeiyiGeek
# Events: <none>
# (3) 验证
~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "ls /usr/share/nginx/html"
file.properties
~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties
# file.number=2
# file.name=file.properties
# file.path=~/K8s/Day8/demo2/file.properties
# file.html=configMap Test - WeiyiGeek
# (4) 热更新修改ConfigMap的值【使用该命令打开yaml文件进行修改】
~/K8s/Day8/demo4$ kubectl edit cm file-config # file.add=append Text with me, and hot update!
# configmap/file-config edited
~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties
~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "cat /usr/share/nginx/html/file.properties"
# file.number=2
# file.name=file.properties
# file.path=/K8s/Day8/demo2/file.properties
# file.html=configMap Test - WeiyiGeek
# file.add=append Text with me, and hot update!
~/K8s/Day8/demo4$ kubectl logs configmap-update-demo
# (5) 手动更新
# PS: ConfigMap更新后Pod并不会重载这个文件,更新 ConfigMap目前并不会触发相关Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发滚动更新
# 例子中我们在 .spec.template.metadata.annotations 中添加 version/config,每次通过修改 version/config 来触发滚动更新
# 未复现成功: kubectl patch deployment my-nginx --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "20200430"}}}}}'
# 错误的例子: kubectl patch deployment configmap-update-demo--patch ' {"metadata": {"annotations": {"deployment.kubernetes.io/revision": "2"}}}'
Tips : configMap 资源对象消费常用于Pod环境变量消费、Pod command 中消费、Volume 中消费
;
Tips : 非常注意更新 ConfigMap 后数据同步会有一定延迟
- 1.使用该 ConfigMap 挂载的Env不会同步更新
- 2.使用该 ConfigMap 挂载的Volume中的数据需要一段时间(实测大概10秒)才能同步更新
2.Secret
描述: 前面我们说过在k8s中利用ConfigMap
控制器可以去保存配置文件以及一些数据, 这些数据可以被导入到Pod内部
成为环境变量或者文件,从而可以达到热更新的目的, 带来便利的同时却有一定的安全问题(保存的配置以明文的形式保存的),所以密码文件、密钥文件类型的文件通过ConfigMap
去保存就不是很合适,在k8s还有一种保存机制即Secret
。
简单的说: Secret 主要用于保存需要进行加密访问的的配置,解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec
中。
在k8s资源清单中
$ kubectl api-resources | grep "secret"
NAME NAMESPACED KIND
secrets true Secret # kubectl api-versions 由于没有APIGROUP则默认为v1
Q: Secret的应用场景
答: Secret 可以
Volume卷或者环境变量
的方式进行消费使用。
Secret 的三种类型
- (1)
Service Account
: 由 Kubernetes 自动创建用来访问Kubernetes API (所以其访问安全至关重要常规的应用一般不涉及配置它
),并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中 - (2)
Opaque
: Base64编码格式的Secret 用来存储密码、密钥等,注意加密程度并不高。 - (3)
Kubernetes.io/dockerconfigjson
: 将私有仓库的登陆认证信息进行存储;
Service Account
描述: 由Kubernetes 自动创建, 它用于访问我们的 Kubernetes API
并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount
目录中注意其并不需要我们手动去管理和创建
。
实践演示:
# (0) 查看 service-account-token
$ kubectl get secret
# NAME TYPE DATA AGE
# default-token-zglkd kubernetes.io/service-account-token 3 11d
# (1) 查看现有的 pod
$ kubectl get pod configmap-update-demo-597f67f4df-v82j6
# NAME READY STATUS RESTARTS AGE
# configmap-update-demo-597f67f4df-v82j6 1/1 Running 0 18h
# (2) 进入 Pod 容器内部
$ kubectl exec configmap-update-demo-597f67f4df-v82j6 -it -- bash
# (3) 验证 /run/secrets/kubernetes.io/serviceaccount 目录中存在的文件
root@configmap-update-demo-597f67f4df-v82j6:/$ ls /run/secrets/kubernetes.io/serviceaccount/ & cd $_
# ca.crt # 访问API所需的证书文件
# namespace # 当前Pod的名称空间
# token # 认证鉴权相关Token
root@configmap-update-demo-597f67f4df-v82j6:$ /run/secrets/kubernetes.io/serviceaccount# cat namespace
# default
Opaque Secret
描述: Opaque 英 /əʊˈpeɪk/
Secret 类型的数据是一个 map 类型,并且要求 Value 是 Base64 编码格式;
资源清单示例:
cat > Opaque-Secret.yaml<<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: secret-test
type: Opaque
data:
name: dXNlcm5hbWU=
pass: d2VpeWlnZWVr
EOF
# - 指定字符的base64加密
~$ echo -n "username" | base64
dXNlcm5hbWU=
~$ echo -n "weiyigeek" | base64
d2VpeWlnZWVr
# - 输出指定字符的base64解密形式
echo -n "d2VpeWlnZWVr" | base64 -d # weiyigeek
Opaque Secret 创建与查看:
# 1.创建
~/K8s/Day8/demo5$ kubectl create -f Opaque-Secret.yaml
# secret/secret-test created
# 2.查看当前系统中已创建的secret
~/K8s/Day8/demo5$ kubectl get secret
# NAME TYPE DATA AGE
# basic-auth Opaque 1 2d1h
# default-token-zglkd kubernetes.io/service-account-token 3 11d
# secret-test Opaque 2 63s
# 3.查看
~/K8s/Day8/demo5$ kubectl describe secret secret-test
# Name: secret-test
# Namespace: default
# Labels: <none>
# Annotations: <none>
# Type: Opaque
# Data
# ====
# name: 8 bytes
# pass: 9 bytes
下面演示 Opaque Secret 在实际环境中的两种使用方式;
- 1) 将Secret挂载到
Volume
中将会生成文件 - 2) 将Secret导出到
环境变量(env)
中以供脚本使用
资源清单示例:
cat > opaque-Secret-1.yaml<<'EOF'
# 1) Volume 挂载使用
apiVersion: v1 # 注意点: Pod 是 v1
kind: Pod
metadata:
name: seret-test-1
labels:
name: seret-test-1
spec:
volumes:
- name: secrets # 关键点
secret:
secretName: secret-test
containers:
- name: seret-test-1
image: harbor.weiyigeek.top/test/busbox:latest
command: [ "/bin/sh", "-c", "ls /etc/secrets; sleep 700" ]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: secrets
mountPath: "/etc/secrets" # 关键点挂载到此目录之中
readOnly: true
---
# 2) enviroment 环境变量使用
apiVersion: apps/v1
kind: Deployment
metadata:
name: seret-test-2 # 元数据名称
spec:
replicas: 2
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: seret-test-2 # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: seret-test-2
spec:
containers:
- name: seret-test-2
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME}, Pass: ${PASSWORD}; sleep 700" ]
env: # 环境变量设置
- name: USERNAME
valueFrom: # Value 来源
secretKeyRef: # 关键点:上面我们接触过 configMapKeyRef ,此处是 secretKeyRef
name: secret-test
key: name
- name: PASSWORD
valueFrom:
secretKeyRef:
name: secret-test
key: pass
EOF
操作流程:
# (1) 创建
~/K8s/Day8/demo5$ kubectl create -f opaque-Secret-1.yaml
# pod/seret-test-1 created
# deployment.apps/seret-test-2 created
# (2) 查看
~/K8s/Day8/demo5$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# seret-test-1 1/1 Running 0 24s 10.244.2.6 k8s-node-5 name=seret-test-1
# seret-test-2-5bffbffc7d-444db 1/1 Running 0 24s 10.244.1.134 k8s-node-4 app=seret-test-2,pod-template-hash=5bffbffc7d
# seret-test-2-5bffbffc7d-c5mqv 1/1 Running 0 24s 10.244.2.5 k8s-node-5 app=seret-test-2,pod-template-hash=5bffbffc7d
# (3) 验证
# - Volument
~/K8s/Day8/demo5$ kubectl logs seret-test-1
# name
# pass
~/K8s/Day8/demo5$ kubectl exec seret-test-1 -- sh -c "cat /etc/secrets/pass"
# weiyigeek
~/K8s/Day8/demo5$ kubectl exec -it seret-test-1 -- sh
# / # cd /etc/secrets/ & ls
# name pass # 关键点: Key成为了文件名称,而Value成为的文件内容
# /etc/secrets # cat name
# /etc/secrets # cat pass
# weiyigeek
# - environment
~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-444db
# KUBERNETES_SERVICE_PORT=443
# KUBERNETES_PORT=tcp://10.96.0.1:443
# HOSTNAME=seret-test-2-5bffbffc7d-444db
# SHLVL=1
# HOME=/root
# USERNAME=username
# KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# KUBERNETES_PORT_443_TCP_PORT=443
# KUBERNETES_PORT_443_TCP_PROTO=tcp
# KUBERNETES_SERVICE_PORT_HTTPS=443
# KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
# KUBERNETES_SERVICE_HOST=10.96.0.1
# PWD=/
# PASSWORD=weiyigeek
# Name: username, Pass: weiyigeek
~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-c5mqv | egrep "username|weiyigeek"
# USERNAME=username
# PASSWORD=weiyigeek
# Name: username, Pass: weiyigeek
kubernetes.io/dockerconfigjson
描述: 它可以设置我们私有镜像仓库信息帮助我们在没有进行docker login
的Node节点上也可以拉取私有镜像;
演示示例:
# (1) 使用Kuberctl 创建docker registry 认证的 secret
kuberctl create secret docker-registry private-registry-key \
--docker-server=harbor.weiyigeel.top \
--docker-username=weiyigeek \
--docker-password=Harbor12345 \
--docker-email=master@weiyigeek.top
# (2) 资源清单: kubernetes.io/dockerconfigjson 之 Secret 的 引用;
apiVersion: v1
kind: Pod
metadata:
name: docker-config-json
spec:
containers:
- name: busybox
image: harbor.weiyigeel.top/private/busybox:v1.0 # 拉取的私有镜像
imagePullSecrets: # 关键点: 通过 imagePullSecrets 来引用刚创建的`private-registry-key `
- name: private-registry-key # 创建的 docker-registry 类型 secret 的名称
3.Volume
描述: 由于容器磁盘上文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。
首先当容器崩溃时kubelet会重启它,但是容器中的文件将丢失,容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。
Kubernetes 中的 Volume 抽象就很好的解决了这些问题。
- Volume 生命周期 : Kubernetes中的卷有明确的寿命与封装它的Pod相同, 所以卷的生命比 Pod 中的所有容器都长
- Volume 作用 : 当这个容器重启时数据仍然得以保存, 注意
当 Pod不再存在时卷也将不复存在
, 也许更重要的是Kubernetes支持多种类型的卷,Pod 可以同时使用任意数量的卷。
PS : 在 Docker 中如果 restartPolicy
设置为always时容器因docker崩溃重启时将会保留数据,但是在K8s中并不会这样所以我们需要用到持久卷保证容器中指定数据的留存
;
下面实践中讲解一些经常使用以及后续遇到的一些卷配置使用:
emptyDir – 空卷
描述: 正如卷的名字所述它最初是空的,其作用是可以在不同的容器中相同或者不同路径进行文件共享,当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行该卷就会存在,Pod中的容器可以读取和写入 emptyDir 卷中的相同文件,但是当出于任何原因从节点中删除Pod时,emptyDir 中的数据也将被永久删除
;
emptyDir 使用场景:
- 1.Pod 内多容器共享目录,例如一个Pod内的多个Container共享一个目录(
生产者 <-> 消费者
) - 2.暂存空间(临时目录), 例如用于基于磁盘的合并排序,用作长时间计算崩溃恢复时的检查点(
即该目录无需持久化保留
) - 3.Web服务器容器提供数据时,保存内容管理器容器提取的文件
PS : 容器崩溃不会从节点中移除 pod,因此 emptyDir
卷中的数据在容器崩溃时是安全的;
资源清单示例:
cat > emptydir-demo-1.yaml<<'EOF'
# 1) Volume 挂载使用
apiVersion: v1 # 注意点: Pod 是 v1
kind: Pod
metadata:
name: emptydir-test-1
labels:
name: emptydir-test-1
spec:
volumes:
- name: cachedir # 关键点卷名称
emptyDir: {} # 使用空卷
containers:
- name: emptydir-pod-1
image: harbor.weiyigeek.top/test/busbox:latest
command: [ "/bin/sh", "-c","sleep 700" ]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: cachedir # 引用卷配置名称
mountPath: "/cache/pod1" # 挂载空卷到此目录之中
- name: emptydir-pod-2
image: harbor.weiyigeek.top/test/busbox:latest
command: [ "/bin/sh", "-c","sleep 700" ]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: cachedir
mountPath: "/cache/pod2" # 挂载空卷到此目录之中
restartPolicy: Never
EOF
操作流程:
# (1) 创建
~/K8s/Day8/demo6$ kubectl create -f emptydir-demo-1.yaml
# pod/emptydir-test-1 created
# (2) 查看
~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# emptydir-test-1 2/2 Running 0 16s 10.244.2.9 k8s-node-5 name=emptydir-test-1
# (3) 验证(分别在一个Pod的两个容器中查看创建追加的内容)
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh # emptydir-pod-1
/ # echo $HOSTNAME
# emptydir-test-1
/ # echo $HOSTNAME-$(date) > /cache/pod1/time.log
/ # cat /cache/pod1/time.log
emptydir-test-1-Wed Nov 18 03:43:48 UTC 2020
/ # exit
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "echo $HOSTNAME-$(date) >> /cache/pod2/time.log" # emptydir-pod-2
# 在两个容器不同的目录实际指向的是同一个文件(内部通过 `/cache/pod1/` 与 `/cache/pod2/` 进行消费)
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh -c "cat /cache/pod1/time.log"
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "cat /cache/pod2/time.log"
# emptydir-test-1-Wed Nov 18 03:43:48 UTC 2020
# ubuntu-Wed 18 Nov 2020 11:44:41 AM CST
# (4) 删除pod则emptyDir失效(正对于deployment创建的Pod是在deployment删除时候失效)
$ kubectl delete pod emptydir-test-1
pod "emptydir-test-1" deleted
hostPath – 主机路径卷
描述:与emptyDir方式直接将目录或者文件写在Container内部不同,hostPath 卷将主机节点(宿主机)的文件系统(FileSystem
)中的文件或目录挂载到集群(Cluster)中
类似于docker中使用的 -v 宿主机目录:容器挂载目录
, 使用其的好处是可以与任何存储进行对接使用;
hostPath 与 emptyDir 比较
- (1) emptyDir 直接将目录或者文件写在Container内部, 而 HostPath 可以使用
宿主机的高速文件系统
以及更好的持久化; - (2) emptyDir 被限制在一个Pod共享文件或目录中, 而 HostPath 可以实现跨Pod共享;
hostPath 使用场景:
- 1.运行需要访问 Docker 内部的容器(
即容器内部使用容器
), 例如使用/var/lib/docker 以及 /var/run/docker.sock
的 hostPath以及依赖挂载的方式; - 2.在容器中运行 cAdvisor, 使用 /dev/cgroups 的 hostPath;
- 3.同一个宿主机跨Pod共享文件或者;
Tips: 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建以及它应该以什么形式存在(除了所需的 path 属性之外,用户还可以为 hostPath 卷指定 type);
hostPath 卷指定 type 类型说明:
- 空字符串 : (默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
- DirectoryOrCreate : 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为0755,与Kubelet 具有相同的组和所有权。
- Directory : 给定的路径下必须存在目录
- FileOrCreate : 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为0644,与Kubelet具有相同的组和所有权。
- File : 给定的路径下必须存在文件
- Socket : 给定的路径下必须存在UNIX套接字
- CharDevic : 给定的路径下必须存在字符设备
- BlockDevice : 给定的路径下必须存在块设备
注意事项:
- 1.由于每个节点上的文件都不同,具有相同配置(
例如从 podTemplate 创建的
)的pod在不同节点上的行为可能会导致访问的结果不一致
; - 2.当Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源;
- 3.在底层主机上创建的文件或目录只能由 root 写入或者其他可读可写用户。您需要在特权容器中以 root 身份运行进程或修改主机上的文件权限以便写入 hostPath 卷;
- 4.hostPath使用宿主机Qutoa不会纳入或记录到k8s的资源管理配额管理中;
资源清单示例:
cat > hostPath-demo.yaml<<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostpath-test-1 # 元数据名称
spec:
replicas: 2
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: hostpath-test-1 # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: hostpath-test-1
spec:
volumes: # volumes 关键点
- name: test-volume
hostPath: # 采用 hostPath 类型的卷
type: DirectoryOrCreate # 卷类型此处选择如果DirectoryOrCreate如何子节点上没有该目录便会进行创建
path: /data # 各主机节点上已存在的目录
containers:
- name: hostpath-test
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "sleep 700" ]
volumeMounts:
- name: test-volume # 挂载指定卷目录
mountPath: /disk/hostpath/
EOF
Tips : Deployment 不支持 restartPolicy: Never 其参数选项必须是 Always
操作流程:
# (1) 创建Pod资源
$ kubectl create -f hostPath-demo.yaml
# deployment.apps/hostpath-test-1 created
# (2) 查看创建的Pod
~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# hostpath-test-1-785b9544b9-n5vcz 1/1 Running 0 43s 10.244.1.137 k8s-node-4 app=hostpath-test-1,pod-template-hash=785b9544b9
# hostpath-test-1-785b9544b9-sv2wh 1/1 Running 0 44s 10.244.2.11 k8s-node-5 app=hostpath-test-1,pod-template-hash=785b9544b9
~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-785b9544b9-sv2wh
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal Scheduled 102s default-scheduler Successfully assigned default/hostpath-test-1-785b9544b9-sv2wh to k8s-node-5
# Normal Pulled 102s kubelet Container image "harbor.weiyigeek.top/test/busbox:latest" already present on machine
# Normal Created 101s kubelet Created container hostpath-test
# Normal Started 101s kubelet Started container hostpath-test
# (3) 测试Pod与宿主机间共享的目录和文件
$ kubectl exec -it hostpath-test-1-785b9544b9-n5vcz -- sh # Node-4
/ $ echo $(id)-$(date)-$(pwd) > /disk/hostpath/hostpath.log
/ $ cat /disk/hostpath/hostpath.log
# uid=0(root) gid=0(root) groups=0(root)-Wed 18 Nov 2020 01:34:51 PM CST-/root
$ kubectl exec -it hostpath-test-1-785b9544b9-sv2wh -- sh # Node-5
/ $ echo $HOSTNAME-$(date)-$(pwd) >> /disk/hostpath/hostpath.log
/ $ cat /disk/hostpath/hostpath.log
# hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/
# (4) 验证(由于hostPath是各个节点的指定目录他们是独立存在的,除非所有的节点挂载的同一块NFS指向同一个目录除外)
# Pod 容器
kubectl exec pod/hostpath-test-1-785b9544b9-n5vcz -it -- sh -c "cat /disk/hostpath/hostpath.log" # Node 4
# uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
kubectl exec pod/hostpath-test-1-785b9544b9-sv2wh -it -- sh -c "cat /disk/hostpath/hostpath.log" # Node 5
# hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/
# 主机文件(各节点主机如果目录不存在分别创建一个目录存放分配Pod的hostpath卷的数据文件)
weiyigeek@Ubuntu-PC:~$ ansible k8s-node-4 -a "cat /data/hostpath.log"
# k8s-node-4 | CHANGED | rc=0 >>
# uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
weiyigeek@Ubuntu-PC:~$ ansible k8s-node-5 -a "cat /data/hostpath.log"
# k8s-node-5 | CHANGED | rc=0 >>
# hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/
# (5) 测试删除(重启)Pod中Volume卷中的数据是否保留
$ kubectl delete pod --all
# pod "hostpath-test-1-785b9544b9-n5vcz" deleted
# pod "hostpath-test-1-785b9544b9-sv2wh" deleted
$ kubectl get pod -o wide # 由于我们设置的replicas副本期望值为2所以又会进行重建Pod
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# hostpath-test-1-785b9544b9-h5dbw 1/1 Running 0 3m11s 10.244.1.138 k8s-node-4 <none> <none>
# hostpath-test-1-785b9544b9-hnngk 1/1 Running 0 3m11s 10.244.2.12 k8s-node-5 <none> <none>
$ kubectl get pod -o name
kubectl exec pod/hostpath-test-1-785b9544b9-h5dbw -it -- sh -c "cat /disk/hostpath/hostpath.log" 0 # 数据依然存在
# uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
kubectl exec pod/hostpath-test-1-785b9544b9-hnngk -it -- sh -c "cat /disk/hostpath/hostpath.log" # 数据依然存在
# hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/
# (6) 删除deployment资源控制器,此时各节点中/data的数据并不会随着资源控制器的删除而删除;
$ kubectl delete deploy hostpath-test-1
# deployment.apps "hostpath-test-1" deleted
Tips : Deployment 中只有在副本数为1的运行时候此种方式持久化是没有没有问题的因为一把情况下它会运行在初次运行的节点之上,但是如果多个副本数则需要下面的PVC持久化卷才能进行数据的持久化与一致性;
Tips : 通过控制器创建的Pod共享了宿主机上的目录,做到了Node层面的数据持久化,但需要注意删除Pod后宿主机上的Volume仍然存在需要按需清理;
Tips : 通过 docker 的 voulme 命令选择查看与删除未使用的voulme或者通过docker volume rm -force 强制删除一个护或者多个Volume;
$ docker volume ls
# DRIVER VOLUME NAME
# local 1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80
# local 4d655ca0a6fe6eb6df6616b141ba16e9e7e3900d63ae53ea35879d999a061596
# local 05d7960fecfe1ce5810e37f61a179ec0f3e50b4bf7f36b494346715ff0bd60c4
# local 8b2ccd3e2e9707970c61ddaea96e3ed02c83770f560b18591fa4fad1c08f7a8e
# local 73b0aee0ffbe7d467ec5744690b167f2e57496d08fea93a3c0992bb701af4ff0
# local 78f74566d76be4cea00378cb0bfc70f18a3b2592ccf7fda90bf7a584964fd733
# local 3512ea706cfc7f289e3945eaa220e51d91274057cd8463a6b94b52738019bafa
# local 4438e2bfff84a56f1fa321789924701f7c44af3f8e3f199972adfa5151bf9f83
# local 8210de4bab78e36f455772caeb261ebd81e32ea9f6753d717f86e44f579a3baa
# local 831413f617021be764d8b18ca72d6d50381bc7c1816af7da20b7f065eed41c5d
# local b97aa9d44e9bf45e38275eec57f3073ab8e0687580b1f34edce12c8ca1f81fe3
# local b64726c080fd675bde2199a6845702d19ccea2ffda97a1a63e4ffb0ed3c1ec7b
# local bcb02978c32a5f284f675ac2a8088b356a97da5c767c4209f559ac2f11d79f09
# local c3407cff74c46e9672758dc9e17b4e6f01aafa982e18abb3ec7dc201975f506b
# local d1b64baa6fb899ad03117957839deeed722d2214ab2a59f0abc444bb3d292f5b
# local df9e564daff5f45f3c37e38490f8c653b8fc0e0fe7bdd2ee0224e46915cad8f0
# local ecd52129f73d26d3028ae84a3a690a3b0012341c1b25c53010e1b4a491313adb
# local fd4f5c7b9ca9dafec3f2ec8b54e9da9d87f9468b67d40afec94bdc2ebd9a0a69
$ docker volume inspect 1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80
# [
# {
# "CreatedAt": "2021-02-03T10:55:08Z",
# "Driver": "local",
# "Labels": null,
# "Mountpoint": "/var/lib/docker/volumes/1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80/_data",
# "Name": "1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80",
# "Options": null,
# "Scope": "local"
# }
# ]
~$ docker volume prune
# WARNING! This will remove all local volumes not used by at least one container.
# Are you sure you want to continue? [y/N] y
# Deleted Volumes:
# 05d7960fecfe1ce5810e37f61a179ec0f3e50b4bf7f36b494346715ff0bd60c4
# 4438e2bfff84a56f1fa321789924701f7c44af3f8e3f199972adfa5151bf9f83
入坑计:
问题1.配置Volume时各node节点可能没有/data目录此时需要type指定为DirectoryOrCreate,即目录不存在时创建目录(巨坑);
~/K8s/Day8/demo6$ kubectl get pod
# NAME READY STATUS RESTARTS AGE
# hostpath-test-1-565b986876-qgfhk 0/1 ContainerCreating 0 4m59s
~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-565b986876-qgfhk
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal Scheduled 29s default-scheduler Successfully assigned default/hostpath-test-1-565b986876-qgfhk to k8s-node-5
# Warning FailedMount 14s (x6 over 30s) kubelet MountVolume.SetUp failed for volume "test-volume" : hostPath type check failed: /data is not a directory
nfs – 分布式共享存储卷
描述: 我们可以采用nfs直接给我们的Pod定义一个NFS类型的卷Volume示例如下;
cat > nfs-volume-demo.yaml<<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-test-1 # 元数据名称
spec:
replicas: 1 # 副本数量为1,否则大家都写进同一个目录可能会导致日志记录混乱;
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: nfs-test-1 # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: nfs-test-1
spec:
volumes: # volumes 关键点
- name: test-volume
nfs: # 采用 nfs 类型的卷
server: 192.168.1.253 # nfs 主机地址
path: '/data' # nfs 共享目录
containers:
- name: nfs-test
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "sleep 700" ]
volumeMounts:
- name: test-volume
mountPath: /app/tools/ # 指定卷挂载到Pod指定的目录路径之中
EOF
Tips : 此处针对于无状态服务创建,如果是有状态服务请采用statefulset
控制创建;
4.PersistentVolume & PersistentVolumeClaim
描述: 默认情况下对于运行的容器,其对文件系统的写入都发生在其分层文件系统的可写层的(Copy-On-Write), 当迁移应用程序从开发到生产环境时,开发与运维面临着挑战,为了防止容器挂掉、崩溃或者运行结束时,任何与之相关的数据都将会丢失,为了解决这个问题引发的数据丢失,所以我们需要将数据存储持久化也就是本章主题(Persistent Volume [Claim]);
基础概念
Q: 什么是PersistentVolume(PV)?
答: PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期、不支持命名空间划分。它是由管理员设置的存储并且是属于群集的一部分(资源)
此API对象包含存储实现的细节,即NFS、iSCSl或特定于云供应商的存储系统。
Q: 什么是PersistentVolumeClaim (PVC) ?
答: PVC 是用户存储的请求它与Pod比较相似,
支持命名空间的划分
,例如 Pod 消耗节点资源,而PVC消耗PV资源
又例如Pod可以请求特定级别的资源(CPU和内存),而PVC 声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)
PV/PVC生命周期流程
答: 供应准备 -> 绑定 -> 使用 -> 释放 -> 回收(Reclaiming)
持久卷(PV)插件类型
描述: PersistentVolume类型以插件形式实现下面是K8s目前支持的一些插件类型;
- ·GCEPersistentDisk AWSElasticBlockStore AzureFile AzureDisk FC(Fibre Channel)
- ·FlexVolume Flocker NFS iSCSI RBD(Ceph Block Device)CephFS
- ·Cinder(OpenStack block storage)Glusterfs VsphereVolume Quobyte Volumes
- ·HostPath VMware Photon Portworx Volumes Scalelo Volumes StorageOS
# 卷插件 ReadWriteOnce ReadOnlyMany ReadWriteMany
AWSElasticBlockStore ✓ - -
AzureFile ✓ ✓ ✓
AzureDisk ✓ - -
CephFS ✓ ✓ ✓
Cinder ✓ - -
CSI 取决于驱动 取决于驱动 取决于驱动
FC ✓ ✓ -
FlexVolume ✓ ✓ 取决于驱动
Flocker ✓ - -
GCEPersistentDisk ✓ ✓ -
Glusterfs ✓ ✓ ✓
HostPath ✓ - -
iSCSI ✓ ✓ -
Quobyte ✓ ✓ ✓
NFS ✓ ✓ ✓
RBD ✓ ✓ -
VsphereVolume ✓ - - (Pod 运行于同一节点上时可行)
PortworxVolume ✓ - ✓
ScaleIO ✓ ✓ -
StorageOS ✓ - -
参考地址: https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#access-modes
PV 分类
描述: 供应准备通过集群外的存储系统或者云平台来提供存储持久化支持;
- 1) 静态pv : 由集群管理员创建一些PV。它们带有可供群集用户使用的实际存储的细节。它们存在于KubernetesAPl中可用于消费。
- 2) 动态pv : 当管理员创建的静态PV都不匹配用户的
PersistentVolumeClaim
时,集群可能会尝试动态地为PVC创建卷。此配置基于StorageClasse:PVC必须请求[存储类]
,并且管理员必须创建并配置该类才能进行动态创建。声明该类为空可以有效地禁用其动态配置;要启用基于存储级别的动态存储配置,集群管理员需要启用API server上的DefaultStorageClass[准入控制器]
。- 例如,通过确保 DefaultStorageClass位于API server 组件的–admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作。
PV 绑定
Q: 什么是绑定PV以及为啥需要绑定?
答: 由于 PVC 需要对接绑定一个PV才能够正常使用, 由于 master 中的控制环路监视新的PVC,寻找匹配的PV(如果可能),并将它们绑定在一起。
如果为新的PVC动态调配PV,则该环路将始终将该PV绑定到PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦PV和PVC绑定后 PersistentVolumeClaim 绑定是排他性的
不管它们是如何绑定的。简单的说PVC跟PV绑定是一对一的映射一旦PV绑定后便不可与其它PVC绑定。
持久卷(PV)状态
描述: PV卷可以处于以下的某种状态;
- ·Available(可用) – 空闲资源还没有被任何声明绑定
- ·Bound(已绑定) – 卷已经被声明绑定
- ·Released(已释放)- 声明被删除,但是资源还未被集群重新声明
- ·Failed(失败)- 该卷的自动回收失败
PV 使用
描述: 用户可在Pod中像Volume一样使用PVC;
持久卷(PV)声明的保护
描述: PVC保护的目的是确保由pod正在使用的PVC不会从系统中移除,因为如果被移除的话可能会导致数据丢失当启用PVC保护 alpha功能时
,如果用户删除了一个pod 正在使用的PVC,则该PVC不会被立即删除。PVC的删除将被推迟,直到PVC不再被任何 pod 使用;
持久卷(PV)访问模式
描述: PersistentVolume可以资源提供者支持的任何方式挂载到主机上, 如下表所示供应商具有不同的功能,每个PV的访问模式都将被设置为该卷支持的特定模式。
例如,NFS可以支持多个读/写客户端
,但特定的NFSPV可能以只读方式导出到服务器上。每个PV都有一套自己的用来描述特定功能的访问模式;
- ·RWO (
Cli 格式简写
)-ReadWriteOnce 该卷可以被单个节点以读/写模式挂载 - ·ROX-ReadOnlyMany – 该卷可以被多个节点以只读模式挂载
- ·RWX-ReadWriteMany – 该卷可以被多个节点以读/写模式挂载
PS : 每个卷只能同一时刻只能以一种访问模式挂载,即使该卷能够支持 多种访问模式。例如GCEPersistentDisk
可以由单个节点作为ReadWriteOnce
模式挂载,或由多个节点以ReadOnlyMany
模式挂载,但不能同时挂载 ;
PV 释放
描述: 用户删除PVC来回收存储资源此时PV将变成Released状态,由于其还保留着之前的数据,该数据需要根据不同的策略来处理,否则这些存储资源无法被其它PVC使用;
PV 回收
描述: 持久卷(PV) 回收的几种策略
- ·Retain(保留)– 允许人工处理保留的数据;
- ·Recycle (deprecated) – 基本擦除将删除PV与外部关联的存储资源 (rm -rf /thevolume/*),注意:需要插件支持
- ·Delete(删除)– 关联的存储资产(例如AWS EBS、GCE PD、Azure Disk 和OpenStack Cinder卷)将被删除, 注意:需要插件支持
PS :当前只有 NFS 和 HostPath 支持回收策略基本不用(Recycle已被废弃)。AWS EBS、GCE PD、Azure Disk 和Cinder 卷支持删除策略;
PV/PVC 举例
Volume API 资源清单:
~/K8s/Day8/demo6$ kubectl api-resources | grep "volume"
# persistentvolumeclaims pvc true PersistentVolumeClaim
# persistentvolumes pv false PersistentVolume
PV(持久卷)NFS 类型 与 HostPath 类型资源清单:
# (1) 以NFS类型的PV示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-test
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce # 单节点读写
persistentVolumeReclaimPolicy: Delete # 回收策略
storageClassName: slow # 此持久卷所属的StorageClass的名称,空值意味着此卷不属于任何存储类。
mountOptions:
- hard
- nfsvers=4.1
nfs: #PV 类型
path: /tmp
server: 172.17.0.2
---
# (2) 以hostpath类型的PV示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: hostpath-test
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce # 单节点读写
hostPath:
path: /tmp/local
PS : storageClassName 表示所属卷的 StorageClass 的名称,空值意味着此卷不属于任何存储类, 他是PVC绑定到PV卷的重要指标。
PVC(持久卷)NFS 类型 与 HostPath 类型资源清单:
# PVC(持久卷)NFS 类型
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-test-claim
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: slow # 关键点(与上面的NFS创建的PV进行关联)
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
---
# PVC(持久卷)HostPath 类型
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hostpath-test-claim
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
Pod 消费 PV 卷资源清单示例:
apiVersion: v1
kind: Pod
metadata:
name: pv-consumer
spec:
containers:
- name: myfrontend
image: nginx:latest
imagePullPolicy: ifNotPresent
ports:
- containerPort: 80
name: web
volumeMounts: # 卷挂载
- name: pv-claim # 卷名称
mountPath: /usr/share/nginx/html # 卷挂载路径
volumes:
- name: pv-claim
persistentVolumeClaim:
claimName: nfs-test-claim
PV/PVC 实例
NFS 持久化卷配置演示
Step 1.NFS 服务端与客户端配置
# 在主节点上安装nfs-server
> ansible admin -a "sudo apt install -y nfs-kernel-server rpcbind"
sudo mkdir -pv /nfs/data{1..4} /nfs/datamkdir
# mkdir: created directory '/nfs'
# mkdir: created directory '/nfs/data1'
# mkdir: created directory '/nfs/data2'
# mkdir: created directory '/nfs/data3'
sudo chmod -R 777 /nfs
sudo chown -R nobody /nfs
sudo tee /etc/exports <<'EOF'
/nfs/data *(rw,no_root_squash,no_all_squash,sync)
/nfs/data1 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data2 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data3 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data4 *(rw,no_root_squash,no_all_squash,sync)
EOF
sudo systemctl restart rpcbind
sudo systemctl restart nfs-server.service
sudo exportfs -r && sudo exportfs -av
# Node 节点上安装nfs-client以及mount.nfs
> ansible k8s-node-4 -a "sudo apt install -y nfs-common rpcbind"
> ansible k8s-node-5 -a "sudo apt install -y nfs-common rpcbind"
# 节点上挂载测试
showmount -e 10.10.107.202
mount -t nfs 10.10.107.202:/nfsdata /test/
cd /test/ && ls
umount /test/
Step 2.PV 创建
cat > pv-test-demo-1.yaml<<'EOF'
# 1.访问模式为 ReadWriteOnce - pv-test-1
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test-1
spec:
capacity: # 容量
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs # 非常重要 (后面PVC将会根据访问模式以及存储类名称进行绑定PV)
nfs:
path: /nfs/data1
server: 10.10.107.202
---
# 1.访问模式为 ReadWriteOnce - pv-test-2
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test-2
spec:
capacity: #容量
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs # 非常重要 (后面PVC将会根据访问模式以及存储类名称进行绑定PV)
nfs:
path: /nfs/data2
server: 10.10.107.202
---
# 2.访问模式为 ReadOnlyMany
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test-4
spec:
capacity: #容量
storage: 2Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-retain # 非常重要
nfs:
path: /nfs/data4
server: 10.10.107.202
---
# 3.访问模式为 ReadWriteMany 回收策略为 Delete
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test-5
spec:
capacity: #容量
storage: 3 Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
storageClassName: nfs-delete # 非常重要
nfs:
path: /nfs/data
server: 10.10.107.202
EOF
Step 3.操作流程&查看
# 1) 部署PV
~/K8s/Day8/demo7$ kubectl create -f pv-test-demo-1.yaml
# persistentvolume/pv-test-1 created
# persistentvolume/pv-test-2 created
# persistentvolume/pv-test-4 created
# persistentvolume/pv-test-5 created
# 2) 查看PV
~/K8s/Day8/demo7$ kubectl get pv -o wide
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
# pv-test-1 1Gi RWO Retain Available nfs 23s Filesystem
# pv-test-2 1Gi RWO Retain Available nfs 23s Filesystem
# pv-test-4 2Gi ROX Retain Available nfs-retain 22s Filesystem
# pv-test-5 3Gi RWX Delete Available nfs-delete 22s Filesystem
# 3) 查看PV-TEST-1详情
~/K8s/Day8/demo7$ kubectl describe pv pv-test-1
# Name: pv-test-1
# Labels: <none>
# Annotations: <none>
# Finalizers: [kubernetes.io/pv-protection]
# StorageClass: nfs
# Status: Available
# Claim:
# Reclaim Policy: Retain
# Access Modes: RWO
# VolumeMode: Filesystem
# Capacity: 1Gi
# Node Affinity: <none>
# Message:
# Source:
# Type: NFS (an NFS mount that lasts the lifetime of a pod)
# Server: 10.10.107.202
# Path: /nfs/data1
# ReadOnly: false
# Events: <none>
Step 4.Service 和 PV 绑定 与 PVC 创建
cat > pvc-demo1.yaml<<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nginx-pvc-demo # SVC服务的元数据名称
labels:
app: nginx
spec:
clusterIP: None # 关键点: 无头服务可以通过集群CoreDNS解析指向Pod中服务
selector:
app: nginx
ports:
- port: 80
name: web
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web-pvc-demo
spec:
serviceName: nginx-pvc-demo # 绑定Service服务的名称
replicas: 3 # 三个副本数
selector:
matchLabels:
app: nginx # 选择器匹配的标签
template:
metadata:
labels:
app: nginx # 模板标签
spec:
containers:
- name: nginx
image: harbor.weiyigeek.top/test/nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts: # 卷挂载
- name: diskpv # 卷名称
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 持久卷模板引擎
- metadata:
name: diskpv # 卷名称元数据与上进行对应
spec:
accessModes: [ "ReadWriteOnce" ] # 访问模式 与 PV 卷的属性与之对应
storageClassName: "nfs" # 存储类名 与 PV 卷的属性与之对应
resources:
requests:
storage: 1Gi # 大小可以进行设置
EOF
Step 5.部署PVC与查看
# 1) 部署 Server 与 PVC
~/K8s/Day8/demo7$ kubectl create -f pvc-demo1.yaml
# service/nginx-pvc-demo created
# statefulset.apps/web-pvc-demo created
# 2) 查看Service Headless
~/K8s/Day8/demo7$ kubectl get svc -o wide --show-labels | grep "nginx-pvc-demo" # 无头服务
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR LABELS
# nginx-pvc-demo ClusterIP None <none> 80/TCP 84s app=nginx app=nginx
# 3) 查看statefulset 与 Pod
~/K8s/Day8/demo7$ kubectl get statefulset -o wide # statefulset控制器查看
# NAME READY AGE CONTAINERS IMAGES
# web-pvc-demo 2/3 24m nginx harbor.weiyigeek.top/test/nginx:latest
~/K8s/Day8/demo7$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# web-pvc-demo-0 1/1 Running 0 27m 10.244.2.15 k8s-node-5 <none> <none>
# web-pvc-demo-1 1/1 Running 0 8m45s 10.244.1.140 k8s-node-4 <none> <none>
# web-pvc-demo-2 0/1 Pending 0 2m11s <none> <none> <none> <none>
# PS : web-pvc-demo-2 未成功启动得原因是由于没得持久卷给Pod3使用
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Warning FailedScheduling 2m25s default-scheduler 0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
# Warning FailedScheduling 2m25s default-scheduler 0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
# 4) 创建一个给web-pvc-demo-2使用得存储卷
cat > web-pvc-demo-2.yaml<<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test-3
spec:
capacity: #容量
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: nfs # 非常重要
nfs:
path: /nfs/data3
server: 10.10.107.202
EOF
~/K8s/Day8/demo7$ kubectl create -f web-pvc-demo-2.yaml
# persistentvolume/pv-test-3 created
# (5) 创建完成后可以看见所有卷都已经ok
~/K8s/Day8/demo7$ kubectl get pvc -o wide # PVC 绑定的PV 查看
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
# diskpv-web-pvc-demo-0 Bound pv-test-1 1Gi RWO nfs 93m Filesystem
# diskpv-web-pvc-demo-1 Bound pv-test-2 4Gi RWO nfs 93m Filesystem
# diskpv-web-pvc-demo-2 Bound pv-test-3 1Gi RWO nfs 93m Filesystem
~/K8s/Day8/demo7$ kubectl get pv -o wide #命令行会显示PV状态以及绑定到PV的PVC的名称
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
# pv-test-1 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-1 nfs 73m Filesystem
# pv-test-2 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-0 nfs 73m Filesystem
# pv-test-3 1Gi RWO Delete Bound default/diskpv-web-pvc-demo-2 nfs 11s Filesystem
# pv-test-4 2Gi ROX Retain Available nfs-retain 73m Filesystem
# pv-test-5 3Gi RWX Delete Available nfs-delete 73m Filesystem
6.PVC卷使用验证
# (1) 查看Pod容器->PVC->PV绑定的目录
pv-test-1 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-1 nfs 73m Filesystem
pv-test-2 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-0 nfs 73m Filesystem
pv-test-3 1Gi RWO Delete Bound default/diskpv-web-pvc-demo-2 nfs 11s Filesystem
---
# 三个Pod的/usr/share/nginx/html指向不同的卷以及
NAME IP NODE
web-pvc-demo-0 10.244.2.15 k8s-node-5 /nfs/data2
web-pvc-demo-1 10.244.1.140 k8s-node-4 /nfs/data1
web-pvc-demo-2 10.244.2.16 k8s-node-5 /nfs/data3
# (2) 创建html文件(重点) - 此步骤在NFS服务器上执行此处由于我们是在master上建立的
cd /nfs/data1 && echo "web-pvc-demo-1 - $(pwd) -- $(date)" > /nfs/data1/index.html
cd /nfs/data2 && echo "web-pvc-demo-0 - $(pwd) -- $(date)" > /nfs/data2/index.html
cd /nfs/data3 && echo "web-pvc-demo-2 - $(pwd) -- $(date)" > /nfs/data3/index.html
# (3) 访问 Pod 服务查看(由于是无头服务我们可以采用域名方式进行访问)
/nfs/data2$ curl http://10.244.2.15
# web-pvc-demo-0 - /nfs/data2 -- Thu 19 Nov 2020 02:53:47 PM CST
/nfs/data1$ curl http://10.244.1.140
# web-pvc-demo-1 - /nfs/data1 -- Thu 19 Nov 2020 02:53:47 PM CST
# (4) 采用coreDNS域名解析方式的访问
$ kubectl get pod -n kube-system -o wide | grep "coredns"
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# coredns-6c76c8bb89-8cgjz 1/1 Running 2 13d 10.244.0.7 ubuntu <none> <none>
# coredns-6c76c8bb89-wgbs9 1/1 Running 2 13d 10.244.0.8 ubuntu <none> <none>
$ cat /etc/resolv.conf
# nameserver 10.244.0.7
$ dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8 # nginx-pvc-demo.default
# ;; ANSWER SECTION:
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.1.140
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.2.16
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.2.15
/nfs/data3$ curl http://nginx-pvc-demo.default.svc.cluster.local
web-pvc-demo-2 - /nfs/data3 -- Thu 19 Nov 2020 02:53:48 PM CST
7.PV 与 PVC 删除 & 回收策略查看
# (1) statefulSet 资源控制器删除
~/K8s/Day8/demo7$ kubectl delete statefulset --all
# statefulset.apps "web-pvc-demo" deleted
~/K8s/Day8/demo7$ kubectl get pod -w # 关键点:删除时候是有序的
# NAME READY STATUS RESTARTS AGE
# web-pvc-demo-0 0/1 Terminating 0 105m
# web-pvc-demo-1 0/1 Terminating 0 105m
# web-pvc-demo-2 0/1 Terminating 0 105m
# (2) 删除指定的PVC验证PV其Delete的回收策略
$ kubectl delete pvc diskpv-web-pvc-demo-2
# persistentvolumeclaim "diskpv-web-pvc-demo-2" deleted
# 此时pv-test-3无法进行delete回收 (未能解决后续有能力时候补充)
pv-test-3 1Gi RWO Delete Failed default/diskpv-web-pvc-demo-2 nfs 34m
# (3) 删除所有的PVC
$ kubectl delete pvc --all
# persistentvolumeclaim "diskpv-web-pvc-demo-0" deleted
# persistentvolumeclaim "diskpv-web-pvc-demo-1" deleted
# (4) 手动回收PV卷
$ kubectl get pv # 查看到PV的STATUS以及CLAIM并未随着PVC删除而变化此时需要进行手动回收
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pv-test-1 1Gi RWO Retain Released default/diskpv-web-pvc-demo-1 nfs 116m
# pv-test-2 1Gi RWO Retain Released default/diskpv-web-pvc-demo-0 nfs 116m
# pv-test-3 1Gi RWO Delete Failed default/diskpv-web-pvc-demo-2 nfs 42m
# (5) 删除或者nfs共享目录中的数据然后编辑pv删除如下片段
~/K8s/Day8/demo7$ kubectl edit pv pv-test-1
# 删除 yaml 中claimRef对象key-value
# ---
# claimRef:
# apiVersion: v1
# kind: PersistentVolumeClaim
# name: diskpv-web-pvc-demo-0
# namespace: default
# resourceVersion: "2830220"
# uid: 02359666-0fcb-4529-8a28-a2fb1dd18008
# ---
# persistentvolume/pv-test-1 edited
$ kubectl edit pv pv-test-2
# persistentvolume/pv-test-2 edited
$ kubectl edit pv pv-test-3
# persistentvolume/pv-test-3 edited
$ kubectl get pv # 然后您将发现PV一切又恢复到正常的可用状态
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pv-test-1 1Gi RWO Retain Available nfs 119m
# pv-test-2 1Gi RWO Retain Available nfs 119m
# pv-test-3 1Gi RWO Delete Available nfs 46m
# pv-test-4 2Gi ROX Retain Available nfs-retain 119m
# pv-test-5 3Gi RWX Delete Available nfs-delete 119m
# (6) 删除指定的 PV
~/K8s/Day8/demo7$ kubectl delete pv pv-test-3
# persistentvolume "pv-test-3" deleted
Step 8.至此持久化数据卷演示结束;
5.StorageClass – 动态分配持久卷
描述: StorageClass 被用于描述存储分类(存储类),不同的StorageClass
会被关系的服务质量层面或者备份策略;
基础概念
描述: 前面我们说过 Persistent Volume
简称PV是一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过 Persistent Volume Claim
,简称PVC来实现动态绑定。Pod定义里指定的PVC绑定关键字,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用
。
Tips : 比如一个配置了许多 50Gi PV 的集群不会匹配到一个要求 100Gi 的PVC。只有在 100Gi PV被加到集群之后,该PVC才可以被绑定。
Q: 什么是 storageClass?
答: 前面我们说过创建PV有静态(
即手动创建一堆PV组成一个PV池,供PVC来绑定
)和动态两种方式, 而动态是指在现有PV不满足PVC的请求时,即通过一个叫StorageClass(存储类)
的对象由存储系统根据PVC的要求自动创建来完成的。动态卷供给能力让管理员不必进行预先创建存储卷,而是随用户需求进行创建。
所以说 storageclass 是一个存储类,k8s集群管理员通过创建storageclass
可以动态生成一个存储卷
供k8s用户使用。这是一种新的存储供应方式。
Q: 使用StorageClass有什么好处呢?
答: 除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以
封装不同类型的存储供PVC选用
。在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式
。在StorageClass出现后,等于增加了一个绑定维度即StorageClassName
参数。
资源定义
Q: 什么是 storageClass 的资源定义?
答: 每一个StorageClass都包含了
Provisioner、Parameters、ReclaimPolicy
等几个字段,当需要动态配置属于该类的PersistentVolume
时使用这些字段。
描述具体过程为: PV先创建分类,PVC请求绑定某个已创建的类(StorageClass)
的资源,同时由Controller控制器
创建的资源绑定到PVC之中,这样就达到动态配置的效果。
Tips : StorageClass对象的名称很重要,是用户可以请求特定类的方式。管理员在首次创建StorageClass
对象时设置类的名称和其他参数,注意在创建对象后无法更新这些对象
。
Tips : 管理员可以为不请求任何特定类绑定的PVC指定默认的StorageClass
;
yaml文件示例说明:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
mountOptions:
- debug
volumeBindingMode: Immediate
Provisoner – 供应商
Q: 什么是供应商 (Provisoner)?
描述: storageclass需要有一个供应者,用来确定我们使用什么样的存储方式来创建pv;
Tips : provisioner 供应者它既可以是内部供应程序,也可以由外部供应商提供; 如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/ 下提供的方法创建storageclass的provisioner;
例如,NFS不提供内部配置程序,但可以使用外部配置程序。
常见的provisioner供应者列表:
WeiyiGeek.provisioner
Tips : nfs 的 provisioner 部署资源清单文件示例 https://github.com/kubernetes-incubator/external-storage/tree/master/nfs/deploy/kubernetes
【2022年6月15日 20:27:10】补充: 注意 nfs-client-provisioner 已经不在更新维护建议使用 nfs-subdir-external-provisioner (https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)
Parameters – 参数
描述: 其功能与标签类似,但其是为了给外部插件提供某些自定义参数;
Reclaim Policy – 回收策略
描述: 由一个存储类动态创建持久化卷(Persistent Volumes
-PV), 可以通过ReclaimPolicy指定回收策略而该策略可以是删除Delete(默认)
或者保持Retain
;
Mount Options – 挂载选项
描述: 如果Volume Plugin
不支持这个挂载选项,但是指定了就会使provisioner
创建StorageClass失败
Volume Binding Mode – 卷绑定模式
描述: 该字段用来说明什么时候进行卷绑定和动态配置,默认情况下立即模式表示一旦创建了PersistentVolumeClaim
,就会发生卷绑定和动态配置。
对于受拓扑约束且无法从群集中的所有节点全局访问的存储后端
,将在不知道Pod的调度要求的情况下绑定或配置PersistentVolumes, 这可能导致不可调度的Pod。
集群管理员可以通过指定WaitForFirstConsumer
模式来解决此问题,该模式将延迟绑定和配置PersistentVolume
,直到创建使用PersistentVolumeClaim的Pod。将根据Pod的调度约束指定的拓扑选择或配置PersistentVolumes。这些包括但不限于资源需求,节点选择器,pod亲和力和反亲和力,以及污点和容忍度
。
基础实例
环境构建流程
Step 1.设置查看当NFS目录以及定义nfs驱动进行编排以下是nfs-client-provisioner
部署文件;
# (1) 当前 NFS 共享目录
showmount -e
# /nfs/data4 *
# /nfs/data3 *
# /nfs/data2 *
# /nfs/data1 *
# /nfs/data * # 下面案例将使用该目录
# (2) NFS 驱动编排的资源清单
cat > nfs-client-provisioner.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy: # 策略
type: Recreate # 再生(循环使用)
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 服务帐户名称
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
volumeMounts:
- name: timezone
mountPath: /etc/localtime # 时区设置
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME # StorageClass 对象中定义的 provisioner 键需要保持一致
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 10.10.107.202
- name: NFS_PATH
value: /nfs/data
volumes:
- name: timezone # 时区定义
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: nfs-client-root # 存储卷
nfs:
server: 10.10.107.202
path: /nfs/data
EOF
Step 2.Storageclass 部署文件
cat > nfs-client-storageclass.yaml <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage # 重要 StorageClass Name绑定
annotations:
storageclass.kubernetes.io/is-default-class: "true" #设置其为默认存储后端
provisioner: fuseim.pri/ifs # 或选择另一个名称,必须与NFS 驱动编排匹配部署的 env PROVISIONER_NAME
parameters:
archiveOnDelete: "false" # 删除pvc后,后端存储上的pv也自动删除
EOF
Step 3.rbac授权文件
cat > nfs-client-rbac.yaml<<'EOF'
kind: ServiceAccount
apiVersion: v1
metadata:
name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
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"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
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
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
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
EOF
Step 4.准备好以上三个文件后,使用kubectl apply命令应用即可完成nfs-client-provisioner的部署。
# (1) 部署
/nfs/data~/K8s/Day7/demo3$ ls
# nfs-client-provisioner.yaml nfs-client-rbac.yaml nfs-client-storageclass.yaml
/nfs/data~/K8s/Day7/demo3$ kubectl create -f .
# deployment.apps/nfs-client-provisioner created 【前排】
# serviceaccount/nfs-client-provisioner created 【权限绑定】
# clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
# clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
# role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
# rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
# storageclass.storage.k8s.io/managed-nfs-storage created 【存储类创建】
# (2) 查看pod、Deployment运行状态和sc
~/K8s/Day7/demo3$ kubectl get pod,deployment,sc -o wide
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# pod/nfs-client-provisioner-58b5dc958d-5fwl9 1/1 Running 0 9m 10.244.1.172 k8s-node-4 <none> <none>
# NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
# deployment.apps/nfs-client-provisioner 1/1 1 1 9m1s nfs-client-provisioner registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner app=nfs-client-provisioner
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# storageclass.storage.k8s.io/managed-nfs-storage (default) fuseim.pri/ifs Delete Immediate false 9m1s
动态存储类验证
- Step 5.创建的 nfs-client-provisioner(StorageClass动态存储卷)已经正常运行, 我们现在部署几个PVC来测试验证
cat > logstash-pvc.yaml<<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: log-01-pvc
annotations: # 空间标注
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: log-02-pvc
spec:
accessModes: ["ReadWriteMany"] # 注意由于设置默认的存储此次不加注解也是使用的 managed-nfs-storage StorageClass 资源
resources:
requests:
storage: 1Gi
EOF
部署与查看PVC持久卷
~/K8s/Day7/demo3$ kubectl create -f logstash-pvc.yaml
# persistentvolumeclaim/log-01-pvc created
# persistentvolumeclaim/log-02-pvc created
~/K8s/Day7/demo3$ kubectl get pv,pvc -o wide
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
# persistentvolume/pvc-e76d332e-52bf-470a-a362-48742c03ab5f 1Gi RWX Delete Bound default/log-02-pvc managed-nfs-storage 4m5s Filesystem
# persistentvolume/pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b 1Gi RWX Delete Bound default/log-01-pvc managed-nfs-storage 4m6s Filesystem
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
# persistentvolumeclaim/log-01-pvc Bound pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b 1Gi RWX managed-nfs-storage 4m6s Filesystem
# persistentvolumeclaim/log-02-pvc Bound pvc-e76d332e-52bf-470a-a362-48742c03ab5f 1Gi RWX managed-nfs-storage 4m6s Filesystem
Step 6.pvc已经创建成功,并自动创建了一个关联的pv资源对象,我们再查看后端存储目录里面是否生成了对应命名格式的 pv;
# 文件夹的命名方式规则:${namespace}-${pvcName}-${pvName}
/nfs/data$ ls -alh | grep "pvc"
drwxrwxrwx 2 root root 4.0K Dec 9 20:57 default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b
drwxrwxrwx 2 root root 4.0K Dec 9 20:57 default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f
动态存储类使用
- Step 7.在实际工作中,使用 StorageClass 更多的是 StatefulSet 类型的服务,StatefulSet 类型的服务我们也可以通过一个
volumeClaimTemplates 属性
来直接使用 StorageClass;
实战利用StatefulSet控制器部署Nginx以及StorageClass动态PV的使用示例如下:
cat > nginx-use-storageClass.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
type: NodePort
ports:
- name: web
port: 80
targetPort: 80
nodePort: 30001
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
labels:
app: nginx
spec:
serviceName: "nginx"
replicas: 8
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: harbor.weiyigeek.top/test/nginx:v3.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: webroot
mountPath: /usr/share/nginx/html
- name: weblog
mountPath: /var/log/nginx
- name: timezone
mountPath: /etc/localtime
volumes:
- name: timezone # 容器时区设置
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
volumeClaimTemplates: # 持久卷模板
- metadata: # 根据模板自动创建PV与PVC并且进行一一对应绑定;
name: webroot
spec:
accessModes: [ "ReadWriteOnce" ] # 绑定PV关键点(1) - 访问模式
resources:
requests:
storage: 1Gi # 绑定PV关键点(2) - 存储大小
- metadata:
name: weblog
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: managed-nfs-storage # 动态存储类绑定PV/PVC关键点(3)
resources:
requests:
storage: 1Gi
EOF
Step 8.查看创建的SVC以及StatefulSet、pod状态;
~/K8s/Day7/demo3$ kubectl get svc,sts,pod -o wide --show-labels
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR LABELS
# service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 34d <none> component=apiserver,provider=kubernetes
# service/nginx NodePort 10.110.119.27 <none> 80:30001/TCP 7m6s app=nginx app=nginx
# NAME READY AGE CONTAINERS IMAGES LABELS
# statefulset.apps/web 8/8 7m6s nginx harbor.weiyigeek.top/test/nginx:v3.0 <none>
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# pod/nfs-client-provisioner-58b5dc958d-5fwl9 1/1 Running 0 5h43m 10.244.1.172 k8s-node-4 app=nfs-client-provisioner,pod-template-hash=58b5dc958d
# pod/web-0 1/1 Running 0 2m24s 10.244.1.173 k8s-node-4 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-0
# pod/web-1 1/1 Running 0 2m20s 10.244.2.73 k8s-node-5 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-1
# pod/web-2 1/1 Running 0 2m15s 10.244.1.174 k8s-node-4 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-2
# pod/web-3 1/1 Running 0 2m10s 10.244.2.74 k8s-node-5 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-3
# pod/web-4 1/1 Running 0 2m6s 10.244.1.175 k8s-node-4 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-4
# pod/web-5 1/1 Running 0 117s 10.244.2.75 k8s-node-5 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-5
# pod/web-6 1/1 Running 0 107s 10.244.1.176 k8s-node-4 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-6
# pod/web-7 1/1 Running 0 96s 10.244.2.76 k8s-node-5 app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-7
Step 9.访问我们nginx并设置查日志数据存储目录
# (1) 访问 Nginx Pod(轮询机制RR)
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
# Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
# Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
# Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
# Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
# Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
# Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
# Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4
# Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4
# (2) 查看持久卷数据存放目录(可以看出可以自动动态的分配nfs存储卷)
/nfs/data$ ls -alh | grep "web" | cut -f 13 -d " "
# 此处目录定义的格式为 {nameSpace}-{pvcName}-{pvName}
# default-weblog-web-0-pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190
# ....
# default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9
# default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf
# ...
# default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84
# {pvcName} <==> {volumeName}-{statefulSetName}-{replicasSerial}
/nfs/data$ kubectl get pvc,pv | grep "pvc-102f5ada-5599-4152-bae4-968670a0d9a8"
# persistentvolumeclaim/weblog-web-1 Bound pvc-102f5ada-5599-4152-bae4-968670a0d9a8 1Gi RWX managed-nfs-storage 22m
# persistentvolume/pvc-102f5ada-5599-4152-bae4-968670a0d9a8 1Gi RWX Delete Bound default/weblog-web-1 managed-nfs-storage 22m
# (3) 查看web访问的页面以及日志数据
/nfs/data$ find . -name "host.html" -exec grep -H "Hostname" {} \; # 可以看到各个 Pod 的 host.html 是不一致的;
# ./default-webroot-web-5-pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae/host.html:Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-4-pvc-c0c41528-85c0-4881-b05b-8e236bc77a47/host.html:Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf/host.html:Hostname: web-0 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-6-pvc-9b482936-5252-43b2-b3a0-22bfee032dfb/host.html:Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-3-pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352/host.html:Hostname: web-3 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-2-pvc-d8685582-3122-4eb7-997f-b3d9c729918d/host.html:Hostname: web-2 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84/host.html:Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
# ./default-webroot-web-1-pvc-c7baf155-3514-4dce-83ed-6d33362864b4/host.html:Hostname: web-1 ,Image Version: 3.0, Nginx Version: 1.19.4
# (4) 同样访问日志也是独立的每一个Pod都有自己的日志存储的PVC
/nfs/data$ find . -name "*.log" -exec grep -H "200" {} \;
# ./default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a/access.log:10.244.0.0 - - [09/Dec/2020:21:49:21 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a/access.log:10.244.0.0 - - [09/Dec/2020:21:49:21 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09/access.log:10.244.0.0 - - [09/Dec/2020:21:49:24 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09/access.log:10.244.0.0 - - [09/Dec/2020:21:49:24 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83/access.log:10.244.0.0 - - [09/Dec/2020:21:49:26 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83/access.log:10.244.0.0 - - [09/Dec/2020:21:49:26 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9/access.log:10.244.0.0 - - [09/Dec/2020:21:49:17 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
# ./default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9/access.log:10.244.0.0 - - [09/Dec/2020:21:49:17 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
动态存储类扩容缩与删除
Step 10.查看StatefulSet扩容缩对及删除资源时候对StorageClass的影响
# (1) 验证: 扩容缩
/nfs/data$ kubectl scale statefulset --replicas=3 web # Pod 收缩
# statefulset.apps/web scaled
# 结论:Pod数量的减少并会影响PVC,它会一直存储在动态卷里除非手动的进行操作;
/nfs/data$ kubectl get pod,pv,pvc
# NAME READY STATUS RESTARTS AGE
# pod/nfs-client-provisioner-58b5dc958d-5fwl9 1/1 Running 0 6h21m
# pod/web-0 1/1 Running 0 40m
# pod/web-1 1/1 Running 0 40m
# pod/web-2 1/1 Running 0 40m
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# persistentvolume/pvc-102f5ada-5599-4152-bae4-968670a0d9a8 1Gi RWX Delete Bound default/weblog-web-1 managed-nfs-storage 40m
# .....
# persistentvolume/pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09 1Gi RWX Delete Bound default/weblog-web-5 managed-nfs-storage 39m
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# persistentvolumeclaim/log-01-pvc Bound pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b 1Gi RWX managed-nfs-storage 75m
# persistentvolumeclaim/log-02-pvc Bound pvc-e76d332e-52bf-470a-a362-48742c03ab5f 1Gi RWX managed-nfs-storage 75m
# persistentvolumeclaim/weblog-web-0 Bound pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190 1Gi RWX managed-nfs-storage 45m
# ....
# persistentvolumeclaim/weblog-web-7 Bound pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9 1Gi RWX managed-nfs-storage 39m
# persistentvolumeclaim/webroot-web-0 Bound pvc-f709d766-1061-4703-9c18-c2a814d450bf 1Gi RWO managed-nfs-storage 45m
# persistentvolumeclaim/webroot-web-1 Bound pvc-c7baf155-3514-4dce-83ed-6d33362864b4 1Gi RWO managed-nfs-storage 40m
# ....
# persistentvolumeclaim/webroot-web-7 Bound pvc-a7d6783b-2d91-4839-9a5b-d84436018f84 1Gi RWO managed-nfs-storage 39m
# (2) 验证:删除StatefulSet控制器创建的资源
/nfs/data$ kubectl delete sts web
# statefulset.apps "web" deleted
/nfs/data$ kubectl get pod
# NAME READY STATUS RESTARTS AGE
# nfs-client-provisioner-58b5dc958d-5fwl9 1/1 Running 0 6h28m
# web-0 1/1 Terminating 0 47m # 终止中
# web-1 1/1 Terminating 0 47m # 终止中
# web-2 1/1 Terminating 0 47m # 终止中
# 结论: StatefulSet 资源删除后动态持久卷任然存储在我们的nfs服务上并未被清空(你可以手动清空或者)
/nfs/data$ kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# log-01-pvc Bound pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b 1Gi RWX managed-nfs-storage 84m
# log-02-pvc Bound pvc-e76d332e-52bf-470a-a362-48742c03ab5f 1Gi RWX managed-nfs-storage 84m
# weblog-web-0 Bound pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190 1Gi RWX managed-nfs-storage 54m
# ...
# weblog-web-7 Bound pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9 1Gi RWX managed-nfs-storage 49m
# webroot-web-0 Bound pvc-f709d766-1061-4703-9c18-c2a814d450bf 1Gi RWO managed-nfs-storage 54m
# ...
# webroot-web-7 Bound pvc-a7d6783b-2d91-4839-9a5b-d84436018f84 1Gi RWO managed-nfs-storage 49m
~/K8s/Day7/demo3$ kubectl delete pv,pvc --all
persistentvolume "pvc-102f5ada-5599-4152-bae4-968670a0d9a8" deleted
persistentvolume "pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83" deleted
persistentvolume "pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352" deleted
persistentvolume "pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9" deleted
persistentvolume "pvc-9b482936-5252-43b2-b3a0-22bfee032dfb" deleted
persistentvolume "pvc-a7d6783b-2d91-4839-9a5b-d84436018f84" deleted
persistentvolume "pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190" deleted
persistentvolume "pvc-bf4734e3-a010-4336-a190-6a13527d9672" deleted
persistentvolume "pvc-c0c41528-85c0-4881-b05b-8e236bc77a47" deleted
persistentvolume "pvc-c7baf155-3514-4dce-83ed-6d33362864b4" deleted
persistentvolume "pvc-cb930d15-558b-4ede-a087-4f0ee001826a" deleted
persistentvolume "pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54" deleted
persistentvolume "pvc-d8685582-3122-4eb7-997f-b3d9c729918d" deleted
persistentvolume "pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae" deleted
persistentvolume "pvc-f709d766-1061-4703-9c18-c2a814d450bf" deleted
persistentvolume "pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09" deleted
persistentvolumeclaim "weblog-web-0" deleted
persistentvolumeclaim "weblog-web-1" deleted
persistentvolumeclaim "weblog-web-2" deleted
persistentvolumeclaim "weblog-web-3" deleted
persistentvolumeclaim "weblog-web-4" deleted
persistentvolumeclaim "weblog-web-5" deleted
persistentvolumeclaim "weblog-web-6" deleted
persistentvolumeclaim "weblog-web-7" deleted
persistentvolumeclaim "webroot-web-0" deleted
persistentvolumeclaim "webroot-web-1" deleted
persistentvolumeclaim "webroot-web-2" deleted
persistentvolumeclaim "webroot-web-3" deleted
persistentvolumeclaim "webroot-web-4" deleted
persistentvolumeclaim "webroot-web-5" deleted
persistentvolumeclaim "webroot-web-6" deleted
persistentvolumeclaim "webroot-web-7" deleted
~/K8s/Day7/demo3$ ls /nfs/data
data/ data1/ data2/ data3/ data4/
~/K8s/Day7/demo3$ ls /nfs/data/
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-337180d4-e12a-4933-bbe7-bc9eea8d068a default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-6c445381-1a64-40e4-a412-493e691d8a22 default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-ad986a1f-f36e-4223-af17-e4100ca2a587 default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf
archived-default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b default-webroot-web-1-pvc-c7baf155-3514-4dce-83ed-6d33362864b4
archived-default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f default-webroot-web-2-pvc-d8685582-3122-4eb7-997f-b3d9c729918d
archived-default-weblog-web-0-pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190 default-webroot-web-3-pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352
archived-default-weblog-web-1-pvc-102f5ada-5599-4152-bae4-968670a0d9a8 default-webroot-web-4-pvc-c0c41528-85c0-4881-b05b-8e236bc77a47
archived-default-weblog-web-2-pvc-bf4734e3-a010-4336-a190-6a13527d9672 default-webroot-web-5-pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae
archived-default-weblog-web-3-pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54 default-webroot-web-6-pvc-9b482936-5252-43b2-b3a0-22bfee032dfb
archived-default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83 default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84
archived-default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09
WeiyiGeek.Nginx网站与日志数据依然存储
- Step 11.以上即为k8s持久化存储之storageclass实践。
以上就是使用storageclass实现动态pv的具体步骤,内容较为全面而且我也相信有相当的一些工具可能是我们日常工作可能会见到或用到的。
实际案例
MySQL数据库使用StorageClass对数据的持久存储
描述: 接下来我们部署一个mysql应用,测试下 StorageClass 方式声明的 PVC 对象
Step 1.MySQL在K8s集群中部署的资源清单
cat mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
custom.cnf: |
[mysqld]
default_storage_engine=innodb
skip_external_locking
skip_host_cache
skip_name_resolve
default_authentication_plugin=mysql_native_password
cat mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-user-pwd
data:
mysql-root-pwd: cGFzc3dvcmQ=
cat mysql-deploy.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
type: NodePort
ports:
- port: 3306
nodePort: 30006
protocol: TCP
targetPort: 3306
selector:
app: mysql
cat mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" # 空间标注
spec:
accessModes: ["ReadWriteMany"]
storageClassName: managed-nfs-storage
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql
name: mysql
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-user-pwd
key: mysql-root-pwd
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-config
mountPath: /etc/mysql/conf.d/
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: timezone
mountPath: /etc/localtime
volumes:
- name: mysql-config
configMap:
name: mysql-config
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
- Step 2.按照资源清单进行部署构建响应的Pod以及Services
$ kubectl apply -f .
configmap/mysql-config created
service/mysql created
deployment.apps/mysql created
secret/mysql-user-pwd created
Step 3.查看创建的资源
$ kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/mysql-7c5b5df54c-vrnr8 1/1 Running 0 83s
pod/nfs-client-provisioner-c676947d-pfpms 1/1 Running 0 30m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 93d
service/mysql NodePort 10.0.0.19 <none> 3306:30006/TCP 83s
Step 4.可以看到mysql应用已经正常运行,我们通过任意一个node节点的ip和30006端口连接mysql数据库测试
[root@localhost ~]# mysql -uroot -h292.168.248.134 -P30006 -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.19 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.01 sec)
MySQL [(none)]>
Step 5.可以看到mysql数据库连接正常,此时查看nfs存储mysql数据库数据已经持久化到nfs服务器 /data/nfs/default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc
目录中
$ du -sh *
177M default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc
$ cd default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc/
$ ls
auto.cnf binlog.index client-cert.pem ibdata1 ibtmp1 mysql.ibd public_key.pem sys
binlog.000001 ca-key.pem client-key.pem ib_logfile0 innodb_temp performance_schema server-cert.pem undo_001
binlog.000002 ca.pem ib_buffer_pool ib_logfile1 mysql private_key.pem server-key.pem undo_002
关于 StatefulSet 补充使用PV/PVC或者StorageClass的补充
1) StatefulSet 为每个Pod副本创建了一个DNS 域名,这个域名的格式为:(podname).(headless servername)
,也就意味着服务间是通过Pod域名来通信而非PodIP,因为当Pod所在Node发生故障时,Pod会被飘移到其它 Node 上 PodIP 会发生变化但是 Pod域名不会有变化;
# 无论 Pod 删除后重建的容器任然可以通过以下方式访问;
ping web-pvc-demo-0.nginx-pvc-demo
ping web-pvc-demo-1.nginx-pvc-demo
ping web-pvc-demo-2.nginx-pvc-demo
2) StatefulSet 配 Pod name (网络标识) 的模式为:(statefulset名称−序号
),比如上面的示例:web-pvc-demo-0, web-pvc-demo-1,web-pvc-demo-2
3) StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的FQDN为:(servicename).$(namespace).svc.cluster.local
,其中"cluster.local"
指的是集群的域名
dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8
4) 根据volumeClaimTemplates为每个Pod 创建一个PVC,其的命名规则匹配模式:(volumeClaim Templates.name-pod_name)
, 比如上面的volumeMounts.name=diskpv
因此创建出来的PVC是
diskpv-web-pvc-demo-1
diskpv-web-pvc-demo-2
diskpv-web-pvc-demo-3
5) 删除 Pod 不会删除其pvc,需要手动删除 pvc 将自动释放 pv,查看上面的步骤6.PV 与 PVC 删除 & 回收策略查看