1. 存储卷基础
1.1 存储卷基础
从概念上讲,存储卷是可供Pod中的所有容器访问的目录
- Pod规范中声明的存储卷来源决定了目录的创建方式、使用的存储介质以及目录的初始内容
- 存储卷插件(存储驱动)决定了支持的后端存储介质或存储服务,例如hostPath插件使用宿主机文件系统,而nfs插件则对接指定的NFS存储服务等
- kubelet内置支持多种存储卷插件
- Pod在规范中需要指定其包含的卷以及这些卷在容器中的挂载路径
存储卷对象并非Kubernetes系统一等公民,它定义在Pod上,因而卷本身的生命周期同Pod,但其后端的存储及相关数据的生命周期通常要取决于存储介质
1.2 存储卷类型和相应的插件
- In-Tree存储卷插件
这些插件是 Kubernetes 内置的、直接在 Kubernetes 代码库中实现的存储插件。它们在 Kubernetes 集群中本地支持,不需要额外的配置。 - 临时存储卷
- emptyDir
- 节点本地存储卷
- hostPath, local
- 网络存储卷
- 文件系统:NFS、GlusterFS、CephFS和Cinder
- 块设备:iSCSI、FC、RBD和vSphereVolume
- 存储平台:Quobyte、PortworxVolume、StorageOS和ScaleIO
- 云存储:awsElasticBlockStore、gcePersistentDisk、azureDisk和azureFile
- 特殊存储卷
- Secret、ConfigMap、DownwardAPI和Projected
- 扩展接口
- CSI和FlexVolume
- Out-of-Tree存储卷插件
- 这些插件并不内置于 Kubernetes 核心代码库中, 而是经由CSI或FlexVolume接口扩展出来的。它们独立于 Kubernetes 的版本更新,可以更加灵活地支持各种存储系统。
1.3 存储卷资源规范
定义存储卷
- 存储卷对象并非Kubernetes系统一等公民,它需要定义在Pod上
- 卷本身的生命周期同Pod,但其后端的存储及相关数据的生命周期通常要取决于存储介质
存储卷的配置由两部分组成
- 通过.spec.volumes字段定义在Pod之上的存储卷列表,它经由特定的存储卷插件并结合特定的存储供给方的访问接口进行定义
- 嵌套定义在容器的volumeMounts字段上的存储卷挂载列表,它只能挂载当前Pod对象中定义的存储卷
1.4 emptyDir存储卷
emptyDir存储卷
- Pod对象上的一个临时目录
- 在Pod对象启动时即被创建,而在Pod对象被移除时一并被删除
- 通常只能用于某些特殊场景中:
- 同一Pod内的多个容器间文件共享
- 作为容器数据的临时存储目录用于数据缓存系统
配置参数
- medium:此目录所在的存储介质的类型,可用值为“default”或“Memory”
- sizeLimit:当前存储卷的空间限额,默认值为nil,表示不限制
范例:
[root@k8s-master01 volumes]#cat pod-with-emptyDir-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pods-with-emptydir-vol
spec:
containers:
- image: ikubernetes/admin-box:v1.2
name: admin
command: ["/bin/sh", "-c"]
args: ["sleep 99999"]
resources: {}
volumeMounts:
- name: data
mountPath: /data #admin将data卷挂载到/data目录
- image: ikubernetes/demoapp:v1.0
name: demoapp
resources: {}
volumeMounts:
- name: data
mountPath: /var/www/html #demoapp将data卷挂载到/var/www/html
volumes: #定义名为data的卷,emptyDir卷使用内存作为存储介质,限制卷大小为16M
- name: data
emptyDir:
medium: Memory
sizeLimit: 16Mi
dnsPolicy: ClusterFirst
restartPolicy: Always
验证测试
范例2:
#定义config-file-store卷,两个容器均挂载该卷,初始化容器config-file-downloader执行command下载文件到挂载目录中,envoy因为和初始化容器挂载同一卷,所以/etc/envoy目录下存在envoy.yaml文件
[root@k8s-master01 volumes]#cat pod-with-emptyDir-vol-02.yaml
apiVersion: v1
kind: Pod
metadata:
name: volumes-emptydir-demo
namespace: default
spec:
initContainers:
- name: config-file-downloader
image: ikubernetes/admin-box
imagePullPolicy: IfNotPresent
command: ['/bin/sh','-c','wget -O /data/envoy.yaml https://raw.githubusercontent.com/iKubernetes/Kubernetes_Advanced_Practical_2rd/main/chapter4/envoy.yaml']
volumeMounts:
- name: config-file-store
mountPath: /data
containers:
- name: envoy
image: envoyproxy/envoy-alpine:v1.13.1
command: ['/bin/sh','-c']
args: ['envoy -c /etc/envoy/envoy.yaml']
volumeMounts:
- name: config-file-store
mountPath: /etc/envoy
readOnly: true
volumes:
- name: config-file-store
emptyDir:
medium: Memory
sizeLimit: 16Mi
验证
1.5 hostPath存储卷
hostPath卷
- 将Pod所在节点上的文件系统的某目录用作存储卷
- 数据的生命周期与节点相同,删除pod并不会删除数据
配置参数 - path:指定工作节点上的目录路径,必选字段
- type:指定节点之上存储类型
范例:
[root@k8s-master01 volumes]#cat pod-with-hostpath-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: redisdata
mountPath: /data #容器挂载点
volumes:
- name: redisdata
hostPath: #定义hostpath类型的卷,指定目录为/data/redis,如果目录不存在则创建
type: DirectoryOrCreate
path: /data/redis
[root@k8s-master01 volumes]#kubectl apply -f pod-with-hostpath-vol.yaml
pod/redis created
使用kubectl get pods -o wide查看redis pod运行在node02节点,进入pod容器交互式接口,进入redis创建key并保存,登录node02节点查看挂载点目录,发现dump.rdb文件存在
尝试手动修改pod调度节点将其调度到node01,测试发现redis key不存在
1.6 NFS存储卷
nfs存储卷
- 将nfs服务器上导出(export)的文件系统用作存储卷
- nfs是文件系统级共享服务,它支持多路挂载请求,可由多个Pod对象同时用作存储卷后端
配置参数 - server
:NFS服务器的IP地址或主机名,必选字段 - path
:NFS服务器导出(共享)的文件系统路径,必选字段 - readOnly
:是否以只读方式挂载,默认为false
范例:
#nfs服务器配置
[root@Ubuntu2204 ~]#mkdir /data/redis0{1..3} #创建共享目录
[root@Ubuntu2204 ~]apt install nfs-server #安装nfs服务端
[root@Ubuntu2204 ~]#vim /etc/exports
/data/redis01 10.0.0.0/24(rw,no_root_squash,no_subtree_check)
[root@Ubuntu2204 ~]#exportfs -ra #重新导出nfs共享目录,让/etc/exports文件重新加载生效
[root@Ubuntu2204 ~]#showmount -e #查看共享目录列表
Export list for Ubuntu2204:
/data/redis01 10.0.0.0/24
#kubernetes节点安装nfs客户端,
[root@k8s-master01 volumes]#apt install -y nfs-common
注意:Kubernetes 的卷驱动(如 NFS 卷驱动)负责与存储后端的交互和管理挂载操作,但是实际的挂载操作是由宿主机的操作系统执行的。因此需要工作节点安装nfs客户端
[root@k8s-master01 volumes]#cat pod-with-nfs-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis-nfs
spec:
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: redisdata
mountPath: /data
volumes:
- name: redisdata
nfs:
server: 10.0.0.104
path: /data/redis01
[root@k8s-master01 volumes]#kubectl apply -f pod-with-nfs-vol.yaml
验证测试
删除pod,重新调度到node01节点,get key成功获取大数据
2.持久卷
2.1 PV和PVC
在Pod级别定义存储卷有两个弊端
- 卷对象的生命周期无法独立于Pod而存在
- 用户必须要足够熟悉可用的存储及其详情才能在Pod上配置和使用卷
PV和PVC可用于降低这种耦合关系
- PV(Persistent Volume)是集群级别的资源,负责将存储空间引入到集群中,通常由管理员定义
- PVC(Persistent Volume Claim)是名称空间级别的资源,由用户定义,用于在空闲的PV中申请使用符合过滤条件的PV之一,与选定的PV是“一对一”的关系
- 用户在Pod上通过pvc插件请求绑定使用定义好的PVC资源
StorageClass资源支持PV的动态预配(Provision)
工作流程:
PersistentVolume(PV)是指由集群管理员配置提供的某存储系统上的存储空间,它是对底层共享存储的抽象,将共享存储作为一种可由用户申请使用的资源,实现了“存储消费”机制。通过存储插件机制,PV支持使用多种网络存储系统或云端存储等多种后端存储系统,例如,NFS、RBD和Cinder等。PV是集群级别的资源,不属于任何名称空间,用户对PV资源的使需要通过PersistentVolumeClaim(PVC)提出的使申请(或称为声明)来完成绑定,PVC是PV资源的消费者,它向PV申请特定大小的空间及访问模式(如rw或ro),从创建出PVC存储卷,后再由Pod资源通过PersistentVolumeClaim存储卷关联使,如下图:
PV资源
- PV是标准的资源类型,除了负责关联至后端存储系统外,它通常还需要定义支持的存储特性
- Volume Mode:当前PV卷提供的存储空间模型,分为块设备和文件系统两种
- StorageClassName:当前PV隶属的存储类;
- AccessMode:支持的访问模型,分为单路读写、多路读写和多路只读三种
- Size:当前PV允许使用的空间上限
- 在对象元数据上,还能够根据需要定义标签
- 一般需要定义回收策略:Retain、Recycle和Delete
PVC资源
- PVC也是标准的资源类型,它允许用户按需指定期望的存储特性,并以之为条件,按特定的条件顺序进行PV过滤
- VolumeMode → LabelSelector → StorageClassName → AccessMode → Size
- 支持动态预配的存储类,还可以根据PVC的条件按需完成PV创建
2.2 使用静态PV和PVC
使用静态PV和PVC的配置卷的步骤
- 集群管理员在集群中设置某种类型的网络存储,比如 NFS 共享(NFS Export)。
- 管理员通过向 Kubernetes API 提交一个 PV 描述文件来创建 Persistent Volume (PV)。
- 用户通过创建 PersistentVolumeClaim (PVC) 来请求存储资源。
- Kubernetes 自动寻找一个符合用户 PVC 要求的 PV(在容量和访问模式方面)。如果找到了符合条件的 PV,Kubernetes 就会将这个 PV 与用户的 PVC 绑定。绑定后,PVC 就可以通过引用该 PV 来访问存储资源。
- 用户创建一个 Pod,并在 Pod 的配置中引用之前创建的 PVC。通过这种方式,Pod 可以挂载该 PVC 代表的存储卷,从而在容器中使用网络存储资源。
2.3 PV和PVC示例
基于NFS的静态PV和PVC示例
范例:
#pv文件
[root@k8s-master01 volumes]#cat pv-nfs-demo.yaml
apiVersion: v1
kind: PersistentVolume #资源类型为pv持久卷
metadata:
name: pv-nfs-demo
spec:
capacity: #定义pv存储容量为5G
storage: 5Gi
volumeMode: Filesystem #存储卷的模式为文件系统
accessModes: #访问模式为多节点读写
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain #回收策略为Retain,当 PVC 删除后,PV 依然保留,不会被自动删除或回收。管理员可以手动处理这个卷。
mountOptions: #挂载选项
- hard # 使得 NFS 挂载为硬挂载。如果 NFS 服务器失去响应,客户端将不断尝试重新连接,直到服务器恢复。这对于关键应用来说是非常重要的,防止数据丢失
- nfsvers=4.1 #使用 NFS 协议版本 4.1
nfs: #指定nfs服务器共享目录路径和地址
path: "/data/redis02"
server: 10.0.0.104
#pvc文件
[root@k8s-master01 volumes]#cat pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim #资源类型为pvc
metadata:
name: pvc-demo
spec:
accessModes: ["ReadWriteMany"] #访问模式:多节点读写
volumeMode: Filesystem 卷模式:文件系统
resources:
requests: #用户请求的最小存储容量为 3GiB
storage: 3Gi
limits: #PVC 可以使用的最大存储容量为 10GiB
storage: 10Gi
[root@k8s-master01 volumes]#cat pod-with-pvc-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis-dyn-pvc
spec:
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data #容器内挂载路径
name: redis-pvc-vol #挂载卷名
volumes:
- name: redis-pvc-vol #卷名
persistentVolumeClaim:
claimName: nfs-csi #指定使用pvc名称
执行kubectl apply应用pv pvc以及pod文件后,get查看pv和pvc的状态发现pvc和pv已经绑定
2.4 StorageClass
StorageClass 允许 Kubernetes 动态地创建 PersistentVolume (PV)。当用户创建 PersistentVolumeClaim (PVC) 并指定一个 StorageClass 时,Kubernetes 会根据 StorageClass 的定义自动创建一个新的 PV。这种机制使得用户无需手动创建 PV,而是通过 PVC 自动获取所需的存储资源。
StorageClass资源
- Kubernetes支持的标准资源类型之一
- 为管理PV资源之便而按需创建的存储资源类别(逻辑组)
- 是PVC筛选PV时的过滤条件之一
- 为动态创建PV提供“模板”
- 需要存储服务提供管理API
- StorageClass资源上配置接入API的各种参数
- 定义在parameters字段中
- 还需要使用provisioner字段指明存储服务类型
- 一般由集群管理员定义,隶属集群级别
范例:
2.5 PVC和动态PV示例
- 有些存储类型默认并不支持动态PV机制,如下图所示
- 多数CSI存储都支持动态PV,且支持卷扩展和卷快照等功能
3. Kubernetes存储架构及CSI简介
3.1 Kubernetes存储架构
存储卷的具体的管理操作由Kubernetes相关的控制器向卷插件(CSI插件)发起调用请求来完成的
-
AD控制器:是 Kubernetes 中负责将存储设备附加到节点或从节点上分离的组件
- Attach:当一个 Pod 需要使用某个存储卷时,AD控制器会将该存储卷附加到目标节点,使得该节点能够访问存储卷中的数据。
- Detach: 当 Pod 不再需要该存储卷,或该存储卷需要移动到另一个节点时,AD控制器会将存储卷从当前节点上拆除,释放资源。
-
存储卷管理器:负责完成卷的Mount/Umount操作,以及设备的格式化操作等
- Mount/Umount: 存储卷管理器负责将存储卷挂载到容器中的指定路径上,或者从容器中卸载存储卷。
- 格式化操作: 在某些情况下,如果存储卷是新的(尚未格式化),存储卷管理器会在挂载之前执行格式化操作,使得卷可以被文件系统使用。
-
PV控制器:负责PV/PVC的绑定、生命周期管理,以及存储卷的Provision/Delete操作
-
绑定: 当 PVC 请求存储资源时,PV控制器负责将合适的 PV 与 PVC 绑定。
-
生命周期管理: PV控制器管理 PV 和 PVC 的生命周期,包括创建、绑定、使用、释放以及删除等操作。
-
Provision/Delete: 对于动态存储卷创建,PV控制器会调用卷插件或 CSI 插件来创建(Provision)或删除(Delete)存储卷。
-
-
Volume Plugin: 使Kubernetes可以与各种不同类型的存储系统进行交互。它负责处理与存储卷相关的操作,例如创建、挂载、卸载、删除等.由于 AD控制器、PV控制器和卷控制器都涉及存储卷的操作(如附加、分离、创建、挂载、卸载等),它们都需要通过 Volume Plugin 来执行这些操作。
Scheduler:特定调度插件的调度决策会受到目标节点上的存储卷的影响
- 调度决策: 调度器在做决策时会考虑目标节点上的存储卷情况。例如,如果某个 Pod 需要访问特定的存储卷,调度器会优先将 Pod 调度到已经附加了该存储卷的节点上,或者将该存储卷可以快速附加到的节点上。
具体流程:
1.控制器发起请求:
- PV 控制器 负责处理存储卷的创建、删除和绑定操作。当需要创建或删除一个存储卷时,PV 控制器会向 CSI 插件发起请求。
- AD 控制器 负责管理存储卷与节点的附加和分离操作。当需要将存储卷附加到某个节点或从节点分离时,AD 控制器会向 CSI 插件发起请求。
- 卷管理器(Volume Manager) 负责管理存储卷的挂载和卸载操作。当一个 Pod 启动时,卷管理器会通过 CSI 插件将存储卷挂载到节点的文件系统上,并在 Pod 停止时卸载该卷。
2.CSI 插件处理请求:
- CSI 插件 是执行实际存储操作的模块。它与后端存储系统(如 AWS EBS、GCE Persistent Disk、NFS 等)通信,处理控制器发起的各种请求。
例如,当 PV 控制器请求创建一个存储卷时,CSI 插件会通过存储系统的 API 创建这个卷,并将其信息返回给 Kubernetes。
3.操作完成反馈:
当 CSI 插件完成操作后,它会将结果反馈给相应的 Kubernetes 控制器。控制器根据反馈信息更新 Kubernetes 的状态,使得集群能够正确地管理和使用存储卷。
总结:Kubernetes 相关的控制器(如 AD 控制器、PV 控制器、卷管理器)负责管理存储卷的生命周期和使用,而具体的管理操作(如创建、挂载、卸载等)则是由这些控制器向卷插件(CSI 插件)发起调用请求来完成的。CSI 插件提供了一个标准化的接口,使得 Kubernetes 能够与各种后端存储系统进行交互,并处理存储卷的各种操作。
3.2 Out-of-Tree存储
CSI简介
- 容器存储接口规范
- 它使存储供应商能够为容器编排系统(如 Kubernetes)开发存储驱动程序,这种接口的标准化意味着驱动程序可以在不同的编排系统之间通用,而不必为每个平台开发特定的存储插件。
- 与平台无关
- CSI 设计的初衷是要与任何容器编排平台兼容,无论是 Kubernetes、Mesos 还是其他平台。它抽象了存储系统的细节,使得不同的存储类型(块存储、文件存储等)可以通过统一的接口进行管理。
驱动程序组件
- CSI Controller:
- 功能:负责与后端存储服务的 API 进行通信,执行存储管理操作。这些操作包括创建、删除存储卷,快照管理,克隆卷,以及将存储卷附加到节点(Attach)或从节点分离(Detach)等。
- 与存储服务的通信: 当 Kubernetes 需要动态创建存储卷或执行其他存储操作时,CSI Controller 通过调用存储服务提供的 API 完成这些任务。例如,在云环境中,这可能意味着与 AWS、Azure 或 Google Cloud 的存储服务通信。
- Node Plugin
- 功能:Node Plugin 也称为 CSI Node,它负责在每个 Kubernetes 节点上管理存储卷的具体操作。它处理存储卷在节点上的挂载和卸载(Mount/Unmount),并确保存储卷在节点上的正确使用。
- 节点级别的存储管理: 当一个 Pod 在节点上启动时,Node Plugin 会挂载必要的存储卷到节点的文件系统上,使得容器可以通过指定路径访问存储数据。同样,当 Pod 结束运行或存储卷不再需要时,Node Plugin 会卸载这些卷。
注意:
- Node Plugin每个节点都需要部署:无论是控制节点(master node)还是工作节点(worker node),每个节点都需要部署一个 Node Plugin。这是因为 Node Plugin 负责在该节点上执行与存储操作相关的具体工作,如挂载、卸载卷,处理存储设备的格式化等。Node Plugin 通常以 DaemonSet 的形式部署,这样可以确保每个节点都会运行一个相应的 Pod。
- CSI Controller通常只需要一个实例:CSI Controller 负责控制面操作,如创建卷、删除卷、扩展卷等。它通常只需要在 Kubernetes 集群中运行一个 Pod(或多个副本,用于高可用性)。Controller 是通过 Deployment 来部署的,并且它通常运行在控制节点上(但也可以在工作节点上运行),不需要像 Node Plugin 那样在每个节点上运行。
3.2 CSI存储组件和部署架构
-
CSI Controller:由StatefulSet控制器对象编排运行,副本量需要设置为1,以保证只会该存储服务运行单个CSI Controller实例;
-
Node Plugin:由DaemonSet控制器对象编排运行,以确保每个节点上精确运行一个相关的Pod副本
-
CSI Controller Pod中的Sidecar容器
- CSI Controller被假定为不受信任而不允许运行于Master节点之上,因此,kube-controller-manager无法借助UnixSock套接字与之通信,而是要经由API Server进行;
- 该通用需求由Kubernetes提供的external-attacher和external-provisioner程序完成,此两者通常会以一个Sidecar容器运行于CSI Controller Pod中
- external-attacher:负责针对CSI上的卷执行attach和detach操作,类似于CSI专用的AD Controller
- external-provisioner:负责针对CSI上的卷进行Provision和Delete操作等,类似于CSI专用的PV Controller
- CSI支持卷扩展和快照时,可能还会用到external-resizer和external-snapshotter一类的程序
-
CSI Node Pod中的Sidecar容器
- kubelet(实现卷的Mount/Umount操作)将UnixSock套接字与CSI Node Plugin进行通信
- 将Node Plugin注册到kubelet上,并初始化NodeID(获取从Kubernetes节点名称到CSI驱动程序NodeID的映射)的功能由通用的node-driver-registrar程序提供,它通用运行为CSI Node Pod的Sidecar容器
3.3 部署及使用NFS CSI
官方文档:https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-nfs-csi-driver.md
参考文档:https://github.com/iKubernetes/learning-k8s/tree/master/csi-driver-nfs
必要的步骤说明:
- 需要事先有可用的NFS服务,且能够满足CSI-NFS-Driver的使用要求;
- 在Kubernetes集群上部署NFS CSI Driver;
- 在Kubernetes集群上创建StorageClass资源,其provisioner为nfs.csi.k8s.io,而parameters.server要指向准备好的NFS Server的访问入口;
- 测试使用;
部署NFS Server
#创建nfs名称空间,应用配置文件,配置文件中pod和service的名称空间为default,需要手动修改配置文件为nfs
kubectl create namespace nfs
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml --namespace nfs
#nfs-server.yaml
[root@k8s-master01 ~]#cat nfs-server.yaml
---
kind: Service #定义service类型以及暴露端口
apiVersion: v1
metadata:
name: nfs-server
namespace: nfs
labels:
app: nfs-server
spec:
type: ClusterIP # use "LoadBalancer" to get a public ip
selector:
app: nfs-server
ports:
- name: tcp-2049
port: 2049
protocol: TCP
- name: udp-111
port: 111
protocol: UDP
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-server
namespace: nfs
spec:
replicas: 1
selector:
matchLabels:
app: nfs-server
template:
metadata:
name: nfs-server
labels:
app: nfs-server
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: nfs-server
image: itsthenetwork/nfs-server-alpine:latest
env:
- name: SHARED_DIRECTORY
value: "/exports"
volumeMounts: #本地宿主机的/nfs-vol目录,挂载到容器的/exports目录
- mountPath: /exports
name: nfs-vol
securityContext:
privileged: true
ports:
- name: tcp-2049
containerPort: 2049
protocol: TCP
- name: udp-111
containerPort: 111
protocol: UDP
volumes:
- name: nfs-vol
hostPath:
path: /nfs-vol # modify this to specify another path to store nfs share data
type: DirectoryOrCreate
查看nfs名称空间下的pod和service
[root@k8s-master01 csi-driver-nfs]#kubectl get pods -n nfs
NAME READY STATUS RESTARTS AGE
nfs-server-b89d558dc-hbwwk 1/1 Running 0 4m41s
[root@k8s-master01 csi-driver-nfs]#kubectl get svc -n nfs
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nfs-server ClusterIP 10.111.154.244 <none> 2049/TCP,111/UDP 4m58s
安装NFS CSI Driver到kubernetes cluster
git clone https://github.com/iKubernetes/learning-k8s.git
cd learning-k8s
kubectl apply -f csi-driver-nfs/deploy/03-csi-driver-nfs-4.2/
#NFS CSI Driver相关pod默认安装在kube-system名称空间下
[root@k8s-master01 csi-driver-nfs]#kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-5d78c9869d-298n8 1/1 Running 13 (10m ago) 21d
coredns-5d78c9869d-jhvx9 1/1 Running 14 (10m ago) 21d
csi-nfs-controller-7449f86679-hc6tt 3/3 Running 0 4m23s
csi-nfs-node-fxprq 3/3 Running 0 4m23s
csi-nfs-node-hg77h 3/3 Running 0 4m23s
csi-nfs-node-j6z4b 3/3 Running 0 4m23s
etcd-k8s-master01.magedu.com 1/1 Running 14 (10m ago) 21d
kube-apiserver-k8s-master01.magedu.com 1/1 Running 14 (10m ago) 21d
kube-controller-manager-k8s-master01.magedu.com 1/1 Running 14 (10m ago) 21d
kube-proxy-vlhfz 1/1 Running 11 (10m ago) 21d
kube-proxy-wr975 1/1 Running 14 (10m ago) 21d
kube-proxy-wvxsq 1/1 Running 11 (10m ago) 21d
kube-scheduler-k8s-master01.magedu.com 1/1 Running 14 (10m ago) 21d
metrics-server-6f7d6d86f7-nvrh7 1/1 Running 3 (10m ago) 38h
Storage Class 应用 (动态配置)
创建storage class
修改server, 共享现有的NFS server address and share name
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
annotations: #注释将该存储类设置为默认存储类,意味着如果在创建 PVC 时未指定存储类,Kubernetes 将自动使用这个存储类
storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs.csi.k8s.io #指定了由哪个nfs.csi.k8s.io CSI驱动程序来管理存储卷
parameters: #指定NFS服务器的地址,nfs-server.nfs.svc.cluster.local是 Kubernetes 集群内的DNS名称
#server: nfs-server.default.svc.cluster.local
server: nfs-server.nfs.svc.cluster.local
#server: nfs.magedu.com
share: / #nfs导出共享目录的根路径,非nfs的根目录,本例中为/exports目录
#share: /data
reclaimPolicy: Delete #回收策略为删除,当 PVC 被删除时,关联的 PV 也会被自动删除。生产中建议设置为Retain
volumeBindingMode: Immediate #表示卷的绑定操作会在 PVC 创建时立即发生,而不是等到 Pod 实际调度时再进行。这通常用于对延迟要求较低的场景。
#mountOptions: #挂载选项
# - hard #挂载方式硬挂载
# - nfsvers=4.1 #指定NFS协议版本4.1
应用存储类文件,get查看
[root@k8s-master01 csi-driver-nfs]#cat nfs-pvc-dynamic.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-dynamic
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
创建pvs文件并让其生效,执行get pv和pvc查看,发现动态pv创建并与pvc成功绑定
创建pod验证结果
[root@k8s-master01 csi-driver-nfs]#cat volumes-nfs-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: volumes-nfs-csi-demo
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data
name: redisdata
volumes:
- name: redisdata
persistentVolumeClaim:
claimName: pvc-nfs-dynamic
宿主机目录
3.4 阿里云的CSI插件简介
阿里云CSI插件实现了在Kubernetes中对阿里云云存储卷的生命周期管理
- CSI-Plugin组件遵循标准CSI规范,提供了EBS、NAS、OSS等类型阿里云云存储服务的挂载能力
- 自ACK 1.16版本的集群开始,部署集群时默认即会自动安装最新版本的CSI组件
- CSI Plugin提供了数据卷的全生命周期管理,包括数据卷的:创建、挂载、卸载、删除、扩容等服务
3.5 应用配置
3.5.1 ConfigMap和Secret基础
ConfigMap和Secret资源
ConfigMap和Secret是Kubernetes系统上两种特殊类型的存储卷
- ConfigMap用于为容器中的应用提供配置数据以定制程序的行为,而敏感的配置信息,例如密钥、证书等则通常由Secret来配置
- ConfigMap和Secret将相应的配置信息保存于资源对象中,而后在Pod对象上支持以存储卷的形式将其挂载并加载相关的配置,从而降低了配置与镜像文件的耦合关系,提高了镜像复用能力
- Kubernetes借助于ConfigMap对象实现了将配置文件从容器镜像中解耦,从而增强了工作负载的可移植性,使其配置更易于更改和管理,并避免了将配置数据硬编码到Pod配置清单中
此二者都属于名称空间级别,只能被同一名称空间中的Pod引用
ConfigMap和Secret资源都是数据承载类的组件,是Kubernetes API的标准资源类型,是一等公民
-
主要负责提供key-value格式的数据项,其值支持
- 单行字符串:常用于保存环境变量值,或者命令行参数等
- 多行字串:常用于保存配置文件的内容
-
资源规范中不使用spec字段,而是直接使用特定的字段嵌套定义key-value数据
- ConfigMap支持使用data或binaryData字段嵌套一至多个键值数据项
- Secret支持使用data或stringData(非base64编码的明文格式)字段嵌套一至多个键值数据项
从Kubernetes v1.19版本开始,ConfigMap和Secret支持使用immutable字段创建不可变实例
3.5.1 ConfigMap
3.5.1.1 创建ConfigMap对象
创建ConfigMap对象的方法有两种
1.命令式命令
字面量:
kubectl create configmap NAME --from-literal=key1=value1
从文件加载:
kubectl create configmap NAME --from-file=[key=]/PATH/TO/FILE
从目录加载:
kubectl create configmap NAME --from-file=/PATH/TO/DIR/
2.配置文件
命令式:
kubectl create -f
声明式:
kubectl apply -f
提示:基于文件内容生成时,可以使用命令式命令以dry-run模式生成并保存
范例:
命令式命令
kubectl create configmap nginx-confs --from-file=./nginx-conf.d/myserver.conf --from-file=status.cfg=./nginx-conf.d/myserver-status.cfg
配置文件
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-confs
namespace: default
data:
myserver.conf: |
server { #
listen 8080;
server_name www.ik8s.io;
include /etc/nginx/conf.d/myserver-*.cfg;
location / {
root /usr/share/nginx/html;
}
}
status.cfg: | #"|"是键名及多行键值的分割符,多行键值要进行固定缩进
location /nginx-status { #该缩进范围内的文本块即为多行键值
stub_status on;
access_log off;
}
~
3.5.1.2 引用ConfigMap对象
ConfigMap资源对象中以key-value保存的数据,在Pod中引用的方式通常有两种:
1.环境变量
- 引用ConfigMap对象上特定的key,以valueFrom赋值给Pod上指定的环境变量
- 在Pod上使用envFrom一次性导入ConfigMap对象上的所有key-value,key(也可以统一附加特定前缀)即为环境变量名,value自动成为相应的变量值
2.configMap卷
- 在Pod上将ConfigMap对象引用为存储卷,而后整体由容器mount至某个目录下
- key转为文件名,value即为相应的文件内容
- 在Pod上定义configMap卷时,仅引用其中的部分key,而后由容器mount至目录下
- 在容器上仅mount configMap卷上指定的key
在Pod上配置使用ConfigMap示例
通过环境变量引用
apiVersion: v1
kind: ConfigMap
metadata:
name: demoapp-config
namespace: default
data: #创建configmap,定义键值demoapp.port和demoapp.host
demoapp.port: "8080"
demoapp.host: 127.0.0.1
---
apiVersion: v1
kind: Pod
metadata:
name: configmaps-env-demo
namespace: default
spec:
containers:
- image: ikubernetes/demoapp:v1.0
name: demoapp
env:
- name: PORT
valueFrom: #使用valueFrom将configmap的值赋值给pod容器的环境变量
configMapKeyRef:
name: demoapp-config
key: demoapp.port
optional: false
- name: HOST
valueFrom:
configMapKeyRef:
name: demoapp-config
key: demoapp.host
optional: true
#optional: false 表示必须存在对应的配置项,而 optional: true 表示配置项不存在时,Pod 依然可以正常启动。设置为true的情况一般是容器内部有默认的内置环境变量或者该配置项是可选的,Configmap不存在容器依然可以正常运行。
应用配置文件后,查看pod容器监听地址为Configmap的值
私密数据不建议使用环境变量的方式配置,因为只要能进入容器的交互式接口,执行printenv可以打印出环境变量值
通过存储卷引用
#创建Configmap
[root@k8s-master01 configmaps_and_secrets]#kubectl create configmap nginx-config-files --from-file=./nginx-conf.d/
[root@k8s-master01 configmaps_and_secrets]#ls ./nginx-conf.d/
myserver-gzip.cfg myserver-status.cfg myserver.conf
#创建pod引用Configmap
[root@k8s-master01 configmaps_and_secrets]#cat configmaps-volume-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: configmaps-volume-demo
namespace: default
spec:
containers:
- image: nginx:alpine
name: nginx-server
volumeMounts: #将ngxconfs卷挂载到容器的/etc/nginx/conf.d/目录,并且是只读的
- name: ngxconfs
mountPath: /etc/nginx/conf.d/
readOnly: true
volumes:
- name: ngxconfs #ngxconfs卷名,使用名为nginx-config-files的ConfigMap作为卷的来源
configMap:
name: nginx-config-files
optional: false #表示这个 ConfigMap 必须存在,如果不存在,Pod 将无法启动
容器内部执行nginx -T发现configmap定义的配置文件被引用成功
容器内部发现配置文件为链接文件,引用自当前目录的隐藏.data目录,.data又引用自config创建时间戳的目录。是为了如果配置修改,只需更改.data的符号链接引用即可更新到新版本
使用kubectl edit编辑Configmap配置
进入容器内部查看发现配置更新
注意:两种引用方式中,环境变量赋值仅发生在pod启动的那一刻,pod启动起来后,即使Configmap的值改了,pod的值也不会发生变化,因为环境变量引用为间接引用是一次性的。而Configmap卷的引用,在pod启动后,如果配置发生变更,变更是通过随机延迟推送给引用Configmap卷的pod内部去的,以实现灰度发布的目的。
3.5.2 Secret
3.5.2.1 Secret资源
Secret主要用于存储密钥、OAuth令牌和 SSH 密钥等敏感信息,这些敏感信息采用base64编码保存,略好于明文存储
Secret根据其用途等,还有类型上的区分
注意:不同类型的Secret,在定义时支持使用的标准字段也有所不同,例如ssh-auth类型的Secret应该使用ssh-privatekey,而basiauth类型的Secret则需要使用username和password等。
**提示:另外也可能存在一些特殊的用于支撑第三方需求的类型,例如ceph的keyring信息使用的kubernetes.io/rbd等
**
3.5.2.2 创建Secret资源
支持类似于ConfigMap的创建方式,但Secret有类型子命令,而且不同类型在data或stringData字段中支持嵌套使用的key亦会有所有同;
1.命令式命令
generic
kubectl create secret generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1]
除了后面docker-registry和tls命令之外的其它类型,都可以使用该命令中的--type选项进行定义,但有些类型有key的特定要求
2.tls
kubectl create secret tls NAME --cert=path/to/cert/file --key=path/to/key/file
通常,其保存cert文件内容的key为tls.crt,而保存private key的key为tls.key
3.docker-registry
kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source]
通常,从已有的json格式的文件加载生成的就是dockerconfigjson类型,命令行直接量生成的也是该类型
资源示例
generic
kubectl create secret generic mysql-root-authn --from-literal=username=root --from-literal=password=MagEdu.c0m
配置文件
apiVersion: v1
data:
password: TWFnRWR1LmMwbQ==
username: cm9vdA==
kind: Secret
metadata:
name: mysql-root-authn
namespace: default
type: Opaque #通用secret类型
tls
kubectl create secret tls nginx-tls --cert=./certs.d/nginx.crt --key=./certs.d/nginx.key
docker-registry
kubectl create secret docker-registry local-harbor-admin --docker-username=admin --docker-password=magedu.com [email protected] --docker-server=harbor.magedu.com
get发现data数据项只有一个,通过base64转换发现自动将添加的配置项转换为dockerconfigjson格式了
3.5.2.3 引用Secret对象
Secret资源在Pod中引用的方式同样有两种
1.环境变量
- 引用Secret对象上特定的key,以valueFrom赋值给Pod上指定的环境变量
- 在Pod上使用envFrom一次性导入Secret对象上的所有key-value,key(也可以统一附加特定前缀)即为环境变量名,value自动成为相应的变量值
2.secret卷 - 在Pod上将Secret对象引用为存储卷,而后整体由容器mount至某个目录下
- key转为文件名,value即为相应的文件内容
- 在Pod上定义Secret卷时,仅引用其中的部分key,而后由容器mount至目录下
- 在容器上仅mount Secret卷上指定的key
注意:容器很可能会将环境变量打印到日志中,因而不建议以环境变量方式引用Secret中的数据
范例:
secert文件
[root@k8s-master01 configmaps_and_secrets]#cat secret-mysql.yaml
apiVersion: v1
data:
db.name: d3BkYg==
db.pass: bWFnZWR1LmNvbTEyMzQ1Ng==
db.user: d3B1c2Vy
root.pass: TWFnZUVkdS5DMG0=
kind: Secret
metadata:
creationTimestamp: null
name: mysql-secret
#pod文件
[root@k8s-master01 configmaps_and_secrets]#cat secrets-env-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: secrets-env-demo
namespace: default
spec:
containers:
- name: mysql
image: mysql:8.0
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root.pass
范例:
#创建secret
kubectl create secret tls nginx-tls --cert=./certs.d/nginx.crt --key=./certs.d/nginx.key
创建Configmap
[root@k8s-master01 configmaps_and_secrets]#kubectl create configmap nginx-sslvhosts-confs --from-file=./nginx-ssl-conf.d
configmap/nginx-sslvhosts-confs created
[root@k8s-master01 configmaps_and_secrets]#vim secrets-volume-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: secrets-volume-demo
namespace: default
spec:
containers:
- image: nginx:alpine
name: ngxserver
volumeMounts:
- name: nginxcerts
mountPath: /etc/nginx/certs/
readOnly: true
- name: nginxconfs
mountPath: /etc/nginx/conf.d/
readOnly: true
volumes:
- name: nginxcerts
secret: #使用secret引用证书
secretName: nginx-tls
- name: nginxconfs
configMap: #使用configmap引用配置文件
name: nginx-sslvhosts-confs
optional: false
~
查看容器监听端口为80和443
配置加载成功,访问https状态码为200
Image Pull Secret
dockercfg及dockerconfigjson类型的Secret主要用于从私有Image Registry中下载容器镜像,其引用定义在pod.spec.imagePullSecrets字段上
扩展
kube-proxy使用的模式默认为iptables,修改为ipvs
[root@k8s-master01 configmaps_and_secrets]#kubectl get cm -n kube-system kube-proxy -o yaml
使用kubectl edit cm kube-proxy -n kube-system修改为ipvs
kube-proxy由daemonsets控制器来定义的,使用kubectl rollout restart daemonsets/kube-proxy -n kube-system让其自动滚动更新为ipvs
以灰度方式依次重启,避免集群出现故障。
查看ipvs规则生效,iptables规则还在重启节点会自动更新,为避免出现问题不建议手动清理规则。建议集群创建时就修改mode为ipvs