一、四种存储方案
:
configMap:在k8s中起到专一存储配置文件的目的
Secret:一种加密的方案,比如密钥、用户密码信息,base64
volume:为pod提供一种共享存储卷的能力,比如nfs共享、本地磁盘下某个目录的共享等
Persistent Volume ( PV ):持久卷
Persistent Volume Claim(PVC):
二、ConfigMap
ConfigMap描述信息:
ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象
使用场景:
当我们有很多nginx节点时,如果我们需要修改nginx的配置文件的时候,我们可以通过脚本等方案来批量修改nginx节点的配置文件,但是如果我们的nginx节点分属于不同的组( 即不同组需要不同的配置文件 ),这个时候修改不同组的配置文件就比较繁琐了。
如图,这时候就有了名叫
配置文件注册中心
的这样一种解决方案,不同的nginx应用程序会向配置文件注册中心
索要配置信息,配置文件注册中心
会根据它的主机名、IP等信息向其分配配置文件。当然,不同的应用程序索要到的配置文件是不同的,包括后期需要修改这些配置文件的话,只用修改配置文件注册中心的文件,就会触动更新相应节点中的配置文件
。这样就使维护与更新变得更简单一些
ConfigMap的作用机制就与上面提到的配置文件注册中心几乎一致,当Pod被创建的时候被指定从ConfigMap引入配置文件,当后期ConfigMap中的配置文件被修改之后,Pod中引入的配置文件也会自动更新,但是pod重新载入配置文件的话,可能需要重启、重载等操作
ConfigMap
的创建:
1、使用目录创建
使用目录创建ConfigMap的话,
目录下的文件名会作为键值对的key,文件的内容会作为键值对的value
# kubectl create configmap game-config --from-file=/root/configMap # --from-file指定,在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容
2、使用文件创建
只要指定为一个文件就可以从单个文件中创建 ConfigMap,
--from-file
这个参数可以使用多次,指定多个文件# kubectl create configmap game-config-2 --from-file=/root/configMap/game.properties --from-file=/root/configMap/ui.properties
3、使用字面值创建
使用文字值创建,利用
--from-literal
参数传递配置信息,该参数可以使用多次,格式如下:# kubectl create configmap 名称 --from-literal=键名称=值名称 --from-literal=键名称=值名称 # kubectl get configmaps config名称 -o yaml
在Pod中使用ConfigMap
1、使用ConfigMap来代替环境变量
2、使用Configmap作为命令行参数
3、通过数据卷插件使用ConfigMap
意思就是将configmap挂载到容器中的某一个位置下进行使用,即将文件填入数据卷,在这个文件中,键key就是文件名,键值value就是文件内容(图片描述文字有误)
# 检查哪些api支持当前的kubernetes对象 # kubectl api-resources | grep deployment
ConfigMap的热更新
热更新意为,将ConfigMap挂载到Pod内部后,通过命令
kubectl edit configmap cm名称
修改configmap的话,Pod内部挂载的Configmap也会被更新,但是默认情况下仅会更新Pod内挂载的ConfigMap值,如果该ConfigMap被nginx等应用使用,其实并不会触发nginx的重载等动作1、创建ConfigMap、Deployment,并将ConfigMap挂载到Pod中
2、
exec
进入Pod查看挂载的ConfigMap值
3、
edit
修改ConfigMap的值
5、等待大概10s左右时间,查看Pod中的ConfigMap值是否发生了改变
✦
更新 ConfigMap 目前并不会触发相关 Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发滚动更新# kubectl patch deployment my-nginx --patch'{"spec": {"template": {"metadata": {"annotations":{"version/config": "20201209" }}}}}' # 这个例子里我们在.spec.template.metadata.annotations中添加version/config # 每次通过修改version/config来触发滚动更新
✦
更新 ConfigMap 后:使用该 ConfigMap 挂载的 Env 不会同步更新
使用该 ConfigMap 挂载的 Volume 中的数据需要一段时间(实测大概10秒)才能同步更新
三、Secret
Secret在yaml资源清单中是加密的,但是如果以卷的形式挂载到pod中。在pod中是自动解密的
1、Secret简述
Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec中。Secret 可以以 Volume 或者环境变量的方式使用
Secret有三种类型:
✦
Service Account: 创建出来给Pod用以访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的/run/secrets/kubernetes.io/serviceaccount目录中
✦
Opaque: base64编码格式的Secret,用来存储密码、密钥等(因为base64加密解密都是固定的,其实他人是可以根据加密后的敏感数据得到原数据的)
✦
kubernetes.io/dockerconfigjson: 用来存储私有 docker registry 的认证信息
类型一 Service Account
Service Account 使Pod可以访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod的/run/secrets/kubernetes.io/serviceaccount目录中
类型二 Opaque Secret
Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式( base64对同一字符加密解密得到的结果都是固定的,这就使解密很容易 )
① 创建一个base64编码的账户密码
# echo -n "admin" | base64 YWRtaW4= # echo -n "1f2d1e2e67df" | base64 MWYyZDFlMmU2N2Rm
使用方式:
② 将Secret挂载到Volume中
查看挂载到Pod中的Secret
③ 将 Secret 导出到Pod环境变量中
这个测试中我遇到了一个奇怪的问题,将Opaque Secret作为Env挂载到Pod中时,Pod也无报错,也无异常,但是就是Opaque Secret没有生效成为Pod的Env。然后经我多方测试之后,最终确定了containers.env.name 的
命名上用了横杠"-"就导致
该Secret在Pod中未生效
当我修改了containers.env.name 字段无横杠"-"时,就成功得到了该Env值,就很迷
翻阅了些资料发现出现上述问题的原因应该是源自Kubernetes特定的命名规则
3、kubernetes.io/dockerconfigjson
此种类型的Secret主要是为了应用在镜像仓库认证时使用,用户从镜像仓库拉取镜像需要进行登陆验证,否则无法拉取、推送镜像
如果镜像仓库的镜像是私有的,那么你在拉取镜像时如果不加以认证就会无法拉取该镜像,所以docker-registry的方式就是:先创建一个docker-registry 类型的Secret,然后创建Pod、Deployment的时候通过
imagePullSecrets
字段来引用创建的docker-registry 类型的Secret,便可以与镜像仓库进行认证并下载镜像① 使用kubectl创建docker registry认证的secret
# kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAI docker-registry 是Secret的类型,名称是myregistrykey
② 在创建Pod的时候,通过
imagePullSecrets
来引用刚创建的myregistrykey
apiVersion: v1 kind: Pod metadata: name: foo spec: containers: - name: foo image: nginx imagePullSecrets: - name: myregistrykey
四、Volume
Volume
:
容器内的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。因为,当Pod/容器崩溃时,kubelet 会重启它,这样容器中原有的文件将会丢失——容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的Volume抽象就很好的解决了这些问题
docker与k8s的模式还不太一样,在docker中,如果容器意外崩溃且重启策略为Always的话,容器重启时并不会清除原有的数据,而在k8s中,如果Pod意外崩溃,那么Pod重启时就会以最干净的状态(即镜像最初的状态)重新启动。
背景
:
Kubernetes 中的卷有明确的寿命 —— 与封装它的 Pod 相同。所以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes支持多种类型的卷,Pod 可以同时使用任意数量的卷
那为什么以卷volume的形式挂载到Pod中之后,容器的崩溃不会造成数据的丢失呢?
在上图中,volume卷挂载到了Pod的pause容器上,容器1、2共享pause容器的存储卷,当容器1、2意外崩溃的时候,实际环境的 Deployment 等控制器会重建新的容器1、2,但是挂载卷volume是始终没有发生变化,变化被重启的只是容器本身,这样就避免了数据的丢失。
Kubernetes 支持以下类型的卷:
✦
awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir、
✦
fc、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs、
✦
persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret
✦
storageos、vsphereVolume
1、EmptyDir
当 Pod 被分配给节点时,首先创建emptyDir卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir中的数据将被永久删除
EmptyDir卷的原理与上面的 gif 类似,相当于将node节点的EmptyDir空卷挂载到了Pod的pause容器,当容器意外崩溃时重建的只是容器本身,EmptyDir还是依然挂载在Pause容器上的并没有改变
EmptyDir的用法
:
✦
暂存空间,例如用于基于磁盘的合并排序
✦
用作长时间计算崩溃恢复时的检查点
✦
Web服务器容器提供数据时,保存内容管理器容器提取的文件
EmptyDir测试:
可以将该EmptyDir卷挂载到Pod的多个容器的不同路径下,使多个容器共享该EmptyDir卷
在使用yaml创建Pod多容器的时候,我忘记了一件事情:Pod内一个容器为nginx,一个镜像为centos,我在创建Pod的时候一直在纳闷为什么centos那个容器为什么没有错误日志却一直不是Ready,测试了好大一会才想起来,容器在创建成功之后如果没有后台进程存在的话,那么它就会直接退出!
所以如果我们想在一个Pod内创建多个容器挂载emptyDir卷,那么就要给无后台进程的就要给他加一个sleep避免其自动退出
apiVersion: v1 kind: Pod metadata: name: emptydirpod spec: containers: - image: nginx name: container1 volumeMounts: - mountPath: /emptydir1 name: emptydir-volume - image: centos name: container2 command: ['/bin/sh','-c','sleep 3600s'] volumeMounts: - mountPath: /emptydir2 name: emptydir-volume volumes: - name: emptydir-volume emptyDir: {}
2、hostPath
hostPath
卷能将主机节点的文件系统中的文件或目录挂载到集群的Pod中
hostpath
的一些用法如下:
✦
运行一个需要访问Docker内部的容器时,可使用hostPath
挂载 宿主机的/var/lib/docker
路径。
✦
允许指定所需要的hostPath
在Pod运行前是否应该存在,是否应该创建等
除了必需的 path
属性之外,用户可以选择性地为 hostPath
卷指定 type
值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查 | |
DirectoryOrCreate | 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权 |
Directory | 给定的路径下必须存在目录 |
FileOrCreate | 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权 |
File | 给定的路径下必须存在文件 |
Socket | 给定的路径下必须存在 UNIX 套接字 |
CharDevice | 给定的路径下必须存在字符设备 |
BlockDevice | 给定的路径下必须存在块设备 |
使用hostPath卷类型的时候请注意:
✦
因为hostPath是将宿主机的特定文件或目录挂载到Pod中,比如将宿主机的nfs目录挂载到Pod的test目录下,结果有一天Pod被调度到了另一个node节点,然而这个节点(即宿主机)上并没有nfs目录,这种情况下就会发生问题。所以如果想使用hostpath这种方案的话,就要确定每个node节点上都有一样的挂载目录等设置。
✦
当 Kubernetes 按照计划添加资源感知调度时,将无法考虑hostPath使用的资源。比如hostPath设置将宿主机/data
目录挂载到容器的/test
目录下,不同node节点下的文件数据可能不一样,这就会导致hostPath会使用到不同的资源
✦
在底层主机上创建的文件或目录只能由 root 写入。所以需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入hostPath卷
hostPath测试:
apiVersion: v1 kind: Pod metadata: name: hostpath-test-pod spec: containers: - image: nginx name: container1 volumeMounts: - mountPath: /hostpath1 name: test-hostpath-volume volumes: - name: test-hostpath-volume hostPath: path: /data type: Directory
五、StorageClass
StorageClass
存储类用于描述集群中可以提供的存储的类型。 不同的类型会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。即对应着不同的:
服务等级:(quality-of-service level)
备份策略
集群管理员自定义的策略
其实一句话说就是,Kubernetes本身不限制用户所使用的特定存储类,集群管理员可以根据实际需求和喜好来使用外部的存储,比如AWS EBS、Ali NAS、Ali OSS等等云厂商提供的云存储设备,比如 NFS 等存储服务。而StorageClass
就是用来描述管理员想使用的外部存储类是什么类型。
StorageClass 资源
:
每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume(PV) 时会使用到。
StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。
管理员可以为没有申请绑定到特定 StorageClass 的 PVC 指定一个默认的存储类
StorageClass的种类:
每个 StorageClass 都有一个
种类说明(Provisioner)
,用来决定使用哪种外部存储类型来提供PV所需的存储,该字段必须指定。Kubernetes 提供 19 种存储类 Provisioner(k8s支持的存储类种类清单可见文档kubernetes.io),但是绝大多数都与具体的云环境相关,如 AWSEBS / AzureFile / AzureDisk / GCEPersistentDisk 等。
StorageClass回收策略:
由 StorageClass 动态创建的
PersistentVolume
将会在类的reclaimPolicy
字段中指定回收策略,可以是Delete(回收后删除)
或者Retain(回收后保留)
。如果StorageClass
对象被创建时没有指定reclaimPolicy
,它将默认为Delete
。
StorageClass卷绑定模式 Volume Binding Mode
StorageClass 根据存储卷绑定模式的选项,确定何时执行 存储卷与存储卷声明的绑定、何时执行动态存储卷提供(动态创建存储卷)。可选项有:
即刻绑定 Immediate
存储卷声明创建后,立刻动态创建存储卷并将其绑定到存储卷声明。
首次使用时绑定 WaitForFirstConsumer
直到存储卷声明第一次被容器组使用时,才创建存储卷,并将其绑定到存储卷声明。
六、PV、PVC
PV
其实就是一个持久存储卷,支持很多类型。
PVC
就是对要使用的持久存储卷的声明,表明我要使用的卷的类型、大小、速度等要求。
PV (Persistent Volumes)
:
在k8s中,可以将外部nfs、mfs(一个具有容错性的网络分布式文件系统)等集群外部的存储挂载到K8s集群内部的PV( Persistent Volumes )上,集群内部的Pod可以通过连接PV来使用该集群外部的存储
PV官方概念
:是由管理员设置的存储,它是集群的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是Volume 之类的卷插件,
但PV 独立于 Pod 的生命周期之外
( 即Pod被删除了,PV依然存在 )。此 API 对象包含存储实现的细节,即 NFS、iSCSI 或特定于云供应商的存储系统
PV 供应分类
:
静态PV
:集群管理员自身创建的PV卷,它们带有集群用户使用的实际存储的细节,比如该PV卷的实际IP,连接方式等。它们存在于Kubernetes API中
动态PV
:当管理员创建的静态 PV 都不匹配用户的PVC要求时,集群可能会尝试动态地为 PVC 创建卷。使用动态PV的前提就是,必须已有一个StorageClass已经指定了一个外部存储供动态PV使用。声明该类为
""
可以有效地禁用其动态配置要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的
DefaultStorageClass[准入控制器]
。例如,通过确保DefaultStorageClass位于 API server 组件的--admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作
PV卷类型
:PV 卷类型以插件形式实现。Kubernetes 目前支持以下插件类型:
✦ 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、ScaleIO Volumes、StorageOS
PV卷演示代码:
apiVersion: v1 kind: PersistentVolume # PV卷kind metadata: name: pv0003 spec: capacity: storage: 5Gi volumeMode: Filesystem # 卷类型 accessModes: # PV卷访问模式 - ReadWriteOnce # ReadWriteOnce为该PV仅允许一个单位(即仅允许一个PVC、一个Pod等进行绑定)进行读、写 persistentVolumeReclaimPolicy: Recycle # persistentVolumeReclaimPolicy为PV卷的回收策略 storageClassName: slow # 存储类的名称,比如PV卷挂载的外部存储速度有快有慢,这时候我们就可以根据存储速度进行分类,而每一个类就是通过这个storageClassName进行区分。 # Pod的PVC请求中,就可以通过这个storageClassName来表明所想要使用的PV卷的类型,而该PVC就会去该类下寻找符合其他条件的PV卷 mountOptions: # 挂载的一些额外选项 - hard - nfsvers=4.1 nfs: # 挂载的存储的服务类型,这里是nfs,指定了哪一个服务器下的哪一个目录作为PV卷 path: /tmp server: 172.17.0.2 # 以上资源清单就实现了将nfs外部存储挂载成为集群内部的PV卷pv0003,大小为5GB # 资源清单中涉及到的 accessModes访问模式、persistentVolumeReclaimPolicy回收策略、storageClassName存储类的含义等将会在下面详细介绍
PV 访问模式
:PV卷可以,以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能是以只读的方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式
✦
ReadWriteOnce——该卷可以被单个节点以读/写模式挂载
✦
ReadOnlyMany——该卷可以被多个节点以只读模式挂载
✦
ReadWriteMany——该卷可以被多个节点以读/写模式挂载在命令行中,访问模式的缩写方式为:
✦
RWO - ReadWriteOnce
✦
ROX - ReadOnlyMany
✦
RWX - ReadWriteMany注意:一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。例如,GCEPersistentDisk 可以由单个节点作为ReadWriteOnce模式挂在,或由多个节点以ReadOnlyMany模式挂载,但不能同时挂载。
PV卷回收策略
:
✦
Retain(保留) -- 手动回收。当PVC
被删除时并不会一并删除其所使用的PV,该PV会一直保留到管理员手动删除,如果管理员一直无操作,则该卷会被一直保留。
✦
Recycle(回收)-- 如果下层的卷插件支持,回收策略Recycle
会在卷上执行一些基本的 擦除(rm -rf /thevolume/*
)操作,相当于擦除卷中的数据,之后允许该卷用于新的 PVC 申领。(Recycle
已被废弃。取而代之的建议方案是使用动态供应 )
✦
Delete(删除)-- 对于支持Delete
回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。动态供应的卷会继承其 StorageClass 中设置的回收策略,该策略默认 为Delete
。 管理员需要根据用户的期望来配置 StorageClass;否则 PV 卷被创建之后必须要被 编辑或者修补。
PV 卷状态
:
✦
Available ( 可用 ) -- 此状态下的PV卷没有被任何声明所绑定,处于可用的状态
✦
Bound( 已绑定 )-- 此状态下的PV卷已经被声明所绑定,如果该PV卷的模式为 ReadOnlyMany 、ReadWriteMany
✦
Released( 已释放 )-- 该PV卷绑定的声明已经被删除了,但是资源还没有被集群重新声明,等待一些时间,等待k8s集群重新将其声明化
✦
Failed( 失败 ) -- 回收策略执行失败,产生了这么一个状态命令行会显示绑定到PV的PVC名称
PVC (Persistent Volumes Claim)
:
当集群达到一定规模,PV的数量达到一级级别,比如集群中挂载有上万的PV卷,当我们要使用具体的PV卷时,我们还要去考虑该PV卷的速度如何,容量如何,是否能够达到当前的需求?这就需要我们自己去匹配到某一个具体的PV卷,这个步骤想想就觉得繁琐的不行,要从那么大的一个量级中找出适合自己需求的PV卷。
所以为了解决这个问题,就引出了PVC的概念:
在创建Pod的时候,会给其附加一个PVC的请求,该PVC请求会描述用户所需要使用的PV持久卷的具体要求(比如需要多大的容量、稳定性、速度、级别等),PVC会自动在所有的PV卷中匹配最适合的PV持久卷( PVC会匹配到满足需求的前提下选择资源量最小的一个PV,比如容量要求是20GB,在满足了其他要求后,一个21GB,一个25GB,PVC就会选择这个21GB ),并与之绑定,这样该Pod就可以使用到PVC所绑定的PV持久卷
PVC官方概念
是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。PVC声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)
PV与PVC绑定
:
master 中的控制环路监视者新的 PVC 请求,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量( 满足需求前提下选择最小资源量 )。
一旦 PV 和 PVC 绑定后,PVC的绑定是排他性的,不管它们是如何绑定的。 PVC 跟PV 绑定是一对一的映射。即 PV 与 PVC对应绑定之后,该PV卷将不能被其他PVC所请求到
。
PVC(持久化卷声明)的保护
:
PVC 保护的目的是确保由 pod 正在使用的 PVC 不会从系统中移除,因为如果PVC被移除的话可能会导致数据丢失
( 意即为当Pod被删除的时候,其PVC不会被一并删除,因为如果该PVC被一并删除了,可能将会导致PV数据的丢失 )
需要注意的是: 当Pod的状态为
Pending
并且Pod已经分配给节点,或Pod的状态为Running
时,PVC仍处于活跃状态当启用PVC 保护 ,
alpha 功能
时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用
PV 持久化存储测试: 结合NFS
# nfs server 安装配置 # yum install -y nfs-common nfs-utils rpcbind # mkdir /nfsdata # chmod 666 /nfsdata # chown nfsnobody /nfsdata # cat /etc/exports /nfsdata *(rw,no_root_squash,no_all_squash,sync) # systemctl start rpcbind # systemctl start nfs
# nfs 客户端(k8s每个节点都安装nfs客户端) # yum -y install nfs-utils rpcbind # 测试挂载并查看是否生效 #showmount -e 192.168.52.100 Export list for 192.168.52.100: /nfsdata * # mkdir /mount_nfs # mount -t nfs 192.168.52.100:/nfsdata /mount_nfs/ # cd /mount_nfs/ # cat << EOF >> test.html > hello world > EOF
k8s每个节点都要安装:
测试nfs使用
测试没问题之后进行umount,如果nfs进程仍然打开有文件的话,umount执行可能会失败,用lsof查询一下nfs打开的文件,并将该进程杀掉即可
创建4个pv
apiVersion: v1 kind: PersistentVolume metadata: name: nfspv1 spec: capacity: storage: 1Gi accessModes: # 访问模式 - ReadWriteOnce persistentVolumeReclaimPolicy: Retain # 回收策略指定为保留Retain storageClassName: nfs nfs: path: /1nfsdata # nfs服务器的共享路径 server: 192.168.52.100 # nfs服务器地址 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv2 spec: capacity: storage: 5Gi accessModes: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /2nfsdata server: 192.168.52.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv3 spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: slow nfs: path: /3nfsdata server: 192.168.52.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv4 spec: capacity: storage: 1Gi accessModes: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /4nfsdata server: 192.168.52.100
pv创建出来之后状态为Available的情况下就已经可以被Pod挂载使用了,但是正常情况下我们都是通过PVC请求来绑定PV进行使用的。
创建StatefulSet、无头Service、PVC去使用上面创建PV:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None # Service类型为无头的ClusterIP selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" # 这里StatefulSet所使用的Service要与上面创建的无头Service的Name一致,该StatefulSet就是使用上面的无头Service replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: # 卷声明模板,就是PVC,这一部分来描写所申请PV的一些特定条件,比如访问模式、存储类名、请求的大小 - metadata: name: www spec: accessModes: ["ReadWriteOnce"] storageClassName: "nfs" resources: requests: storage: 1Gi
下面的gif图详细记录了创建的过程,创建无头Service、StatefulSet、PVC的yaml资源清单的介绍在上面的代码中有相关介绍
由上面创建PV的步骤可以得知,集群现有的PV总共有四个,分别是
nfs类的RWO模式
、nfs类的ROX
、slow类的RWX
、nfs类的ROX
,但是我们在创建StatefulSet的3个Pod副本时,PVC都要求PV是nfs类,且访问模式为RWO,这样的话符合条件PV就只有一个了。那么虽然StatefulSet定义了3个副本数目,但是第二个pod因为没有PV可挂载就无法正常启动,因为StatefulSet是按序创建的,,第二个不能创建成功,那第三个pod副本就更无法被创建了。
statefulSet创建的Pod副本通过PVC绑定外部nfs挂载到k8s集群内部的pv卷:
为了测试效果,这里选择删除上面创建的svc、StatefulSet、pvc、然后删除pv,并将PV的访问模式都修改为RWO,且存储类名为nfs,这样就可以匹配pvc所申请PV的要求了。# pv apiVersion: v1 kind: PersistentVolume metadata: name: nfspv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /1nfsdata server: 192.168.52.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv2 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /2nfsdata server: 192.168.52.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv3 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /3nfsdata server: 192.168.52.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: nfspv4 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /4nfsdata server: 192.168.52.100
这样,再去创建新的pv,新的svc、新的statefulSet、新的pvc(该资源清单并未修改),再来查看PVC与PV是否已绑定:
确认一下pod挂载的nfs路径:
因为我们是将nfs路径挂载到了pod下nginx的index目录,所以我们可以做一个测试index文件
注意:这里/usr/share/nginx/html的
html
目录是需要至少755的权限,如果你在访问的时候出现了403权限的问题,请检查html目录权限和index.html的权限
curl 访问测试,结果显示挂载pv的index文件已成功应用:
并且,我们可以通过删除pod来模拟pod意外终止的情况来查看pv的数据是否会丢失:
因为我们设置PV卷的回收策略是
Retain
,当 PVC 被删除时并不会一并删除其所使用的PV,该PV会一直保留到管理员手动删除,如果管理员一直无操作,则该卷会被一直保留。
PV
回收策略测试:
由图我们可以看到,因为
PV
的回收策略是Retain
,删除statefuleSet创建的 pod 并 删除相应的 pvc 之后,pv 由Bound
的状态变为了Released
的状态,如果要使PV
重新可利用,就需要管理员手工删除 pv 中的claimRef
字段
五、StatefulSet知识点回顾
✦
StatefulSet 创建 Pod 时,Pod Name 的模式为:$(statefulset名称)-$(序号)比如上面的测试的示例:web-0,web-1,web-2,其中
web
是 StatefulSet 的名称,后面则是序号
✦
StatefulSet
会为每个Pod
副本创建了一个DNS 域名
,这个域名的格式为:$(podname).(headless ServiceName)
。所以StatefulSet
为Pod
副本创建的DNS域名格式
即为pod名称.无头Service名称
。也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化exec 进入到 pod 内部,即可通过 ping
pod名称.无头Service名称
解析到对应的IP,我这里镜像内部没有 ping 命令等工具,就不再演示了,贴一张网图
✦
StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:$(servicename).$(namespace).svc.cluster.local,其中,“cluster.local” 指的是集群的域名
✦
根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式:(volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Podname=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2
✦
删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv
Statefulset的启停顺序:
✦
有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态
✦
有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0
✦
有序扩展:当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态
StatefulSet使用场景:
✦
稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现
✦
稳定的网络标识符,即 Pod 重新调度后其 PodName 和 HostName 不变
✦
有序部署,有序扩展,基于 init containers 来实现
✦
有序收缩