k8s阶段03 持久卷, PV和PVC, CSI存储方案示例csi-driver-nfs, OpenEBS, ConfigMap, Secret, DownwardAPI和Projected
2持久卷
PV和PVC
在Pod级别定义存储卷有两个弊端 卷对象的生命周期无法独立于Pod而存在 用户必须要足够熟悉可用的存储及其详情才能在Pod上配置和使用卷 PV和PVC可用于降低这种耦合关系 PV(Persistent Volume)是集群级别的资源,负责将存储空间引入到集群中,通常由管理员定义 PVC(Persistent Volume Claim)是名称空间级别的资源,由用户定义,用于在空闲的PV中申请使用符合过滤 条件的PV之一,与选定的PV是“一对一”的关系 用户在Pod上通过pvc插件请求绑定使用定义好的PVC资源 StorageClass资源支持PV的动态预配(Provision) PV, PVC PV置备(provision)方式: 静态置备:也可以使用StorageClass,分组,亦可作为筛选条件之一,可选项; 动态置备:依赖于StorageClass(模板),必选项#后端存储需要支持动态置备功能
PV资源 PV是标准的资源类型,除了负责关联至后端存储系统外,它通常还需要定义支持的存储特性 Volume Mode:当前PV卷提供的存储空间模型,分为块设备和文件系统两种 StorageClassName:当前PV隶属的存储类; #文件系统没有限制,块设备系统只能单路读写访问 AccessMode:支持的访问模型,分为单路读写、多路读写和多路只读三种 Size:当前PV允许使用的空间上限 在对象元数据上,还能够根据需要定义标签 #Retain:保留数据,PV变为release状态,待人工介入;Recycle:数据清除,PV可再次被其他PVC绑定(危险,被废弃) #Delete:删除PV,但后端存储空间的数据没被删 一般需要定义回收策略:Retain、Recycle和Delete PVC资源 PVC也是标准的资源类型,它允许用户按需指定期望的存储特性,并以之为条件,按特定的条件顺序进行PV过滤 VolumeMode → LabelSelector → StorageClassName → AccessMode → Size 支持动态预配的存储类,还可以根据PVC的条件按需完成PV创建
基于NFS的静态PV和PVC示例
#NFS PV 资源定义示例 apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-demo spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: #这里可以把支持的类型都声明出来 - ReadWriteMany persistentVolumeReclaimPolicy: Retain #回收策略 mountOptions: #挂载选项,非必须项 - hard - nfsvers=4.1 nfs: path: "/data/redis" server: nfs.magedu.com #PVC 资源定义示例 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-demo namespace: default #可省略 spec: accessModes: ["ReadWriteMany"] volumeMode: Filesystem resources: #定义资源 requests: #最小不能小于 storage: 3Gi limits: #最大不能超过 storage: 10Gi #在Pod上使用PVC卷 apiVersion: v1 kind: Pod metadata: name: volumes-pvc-demo namespace: default spec: containers: - name: redis image: redis:alpine imagePullPolicy: IfNotPresent ports: - containerPort: 6379 name: redisport volumeMounts: - mountPath: /data name: redis-rbd-vol volumes: - name: redis-rbd-vol persistentVolumeClaim: claimName: pvc-demo #使用上面定义的pvc名字
实际操作示例
#在nfs服务器上创建文件夹导出 [root@ubuntu ~]#mkdir -pv /data/pv001 [root@ubuntu ~]#vim /etc/exports /data/pv001/ 10.0.0.0/16(rw,async,no_subtree_check,no_root_squash) #重新导出 [root@ubuntu ~]#exportfs -arv #在k8s的控制节点上 [root@master01 volumes]#vim pv-nfs-demo.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-demo spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteMany - ReadOnlyMany - ReadWriteOnce persistentVolumeReclaimPolicy: Retain mountOptions: - hard - nfsvers=4.1 nfs: path: "/data/pv001" server: 10.0.0.155 [root@master01 volumes]#kubectl apply -f pv-nfs-demo.yaml #查看pv (这里没定义STORAGECLASS,等下pvc也不能定义分类,否则找不到) [root@master01 volumes]#kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv-nfs-demo 5Gi RWX Retain Available <unset> 41s [root@master01 volumes]#vim pvc-demo.yaml #会从Available状态的PV中进行筛选 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-demo spec: accessModes: ["ReadWriteMany"] #能兼容对方就可以 volumeMode: Filesystem resources: requests: #尽量接近3G storage: 3Gi limits: storage: 10Gi [root@master01 volumes]#kubectl apply -f pvc-demo.yaml #获取pvc要指名称空间,default可以不指,状态为bound绑定,一创建立即绑定 [root@master01 volumes]#kubectl get pvc -n default NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-demo Bound pv-nfs-demo 5Gi RWO,ROX,RWX <unset> 11m #查看pv也能看出被绑定状态 [root@master01 volumes]#kubectl get pv #定义pod使用pvc [root@master01 volumes]#vim 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: pvc-demo [root@master01 volumes]#kubectl apply -f pod-with-pvc-demo.yaml #进入容器验证 [root@master01 volumes]#kubectl exec -it redis-dyn-pvc -- /bin/sh /data # redis-cli 127.0.0.1:6379> set mykey "Hello" OK 127.0.0.1:6379> BGSAVE Background saving started #nfs服务器上查看 [root@ubuntu ~]#ls /data/pv001/ dump.rdb #删除pod只要不删pvc,那只要引用pvc,所有数据都在,卷也在
StorageClass资源 Kubernetes支持的标准资源类型之一 为管理PV资源之便而按需创建的存储资源类别(逻辑组) 是PVC筛选PV时的过滤条件之一 为动态创建PV提供“模板” 需要存储服务提供管理API StorageClass资源上配置接入API的各种参数 定义在parameters字段中 还需要使用provisioner字段指明存储服务类型 一般由集群管理员定义,隶属集群级别 #StorageClass资源示例 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io #后端存储服务由哪个供应商提供的 parameters: server: nfs-server.default.svc.cluster.local #nfs服务器地址 share: / #nfs服务器暴露的路径 reclaimPolicy: Delete #动态置备的pv拿到的回收策略 volumeBindingMode: Immediate#卷绑定模式,Immediate立即绑定PV和PVC,若用本地存储用延迟绑定(等pod创建出来) mountOptions: #nfs卷使用参数,有些服务不需要指定mountOptions - hard - nfsvers=4.1
有些存储类型默认并不支持动态PV机制,如下图。可以加CSI让它支持 多数CSI存储都支持动态PV,且支持卷扩展和卷快照等功能 #PVC向StorageClass申请绑定PV apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs-dynamic spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: nfs-csi
local持久卷 local卷插件用于将本地存储设备(如磁盘、分区或目录)配置为卷 基于网络存储的PV通常性能损耗较大 直接使用节点本地的SSD磁盘可获取较好的IO性能,更适用于存储类的服务,例如MongoDB、Ceph等 hostPath卷在Pod被重建后可能被调度至其它节点而无法再次使用此前的数据,而基于local卷,调度器能自行完成调度绑定 hostPath卷允许Pod访问节点上的任意路径,也存在一定程度的安全风险 基于local的PV,需要管理员通过nodeAffinity声明其定义在的节点 用户可通过PVC关联至local类型的PV 然后,在Pod上配置使用该PVC即可 调度器将基于nodeAffinity将执行Pod调度 local卷只能关联静态置备的PV,目前尚不支持动态置备 #Local PV资源示例 apiVersion: v1 kind: PersistentVolume metadata: name: local-pv-demo spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: #local卷在某个主机上的路径 path: /disks/vol1 nodeAffinity: #节点选择器 required: nodeSelectorTerms: #节点过滤器 - matchExpressions: #匹配条件 - key: kubernetes.io/hostname #主机上要有该标签 operator: In #主机名得是k8s-node01.magedu.com values: - k8s-node01.magedu.com
PVC迟延绑定 ◼ 配置PVC绑定local PV时,通常要创建一个StorageClass ◆provisioner字段的值“no-provisioner”表示不使用动态置备PV,因为local插件不支持 ◆volumeBindingMode字段的值“WaitForFirstConsumer”表示等待消费者(Pod)申请使用PVC时(即第一次被调度时)再进行PV绑定,即“延迟绑定” ◆延迟绑定机制,提供了基于消费者的需求来判定将PVC绑定至哪个PV的可能性 ◼ 延迟绑定机制下,PVC创建后将处于“Pending”状态,直至被Pod消费 #实际操作示例: #作为分组使用,不作为动态置备作用 [root@master01 local-pv-demo]#vim storageclass-local.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local provisioner: kubernetes.io/no-provisioner #固定格式,没有动态置备就不需要没有供应商 volumeBindingMode: WaitForFirstConsumer #延迟绑定(等待首次被消费) [root@master01 local-pv-demo]#kubectl apply -f storageclass-local.yaml #查看,storageclass可以简写为sc [root@master01 local-pv-demo]#kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE local kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 61 #定义pv,使用local类型卷 [root@master01 local-pv-demo]#vim local-pv-demo.yaml apiVersion: v1 kind: PersistentVolume metadata: name: local-pv-demo spec: capacity: storage: 5Gi #存储空间 volumeMode: Filesystem #卷模式 accessModes: - ReadWriteOnce #访问模式 persistentVolumeReclaimPolicy: Delete #回收策略 storageClassName: local #存储类型,通过local来定义 local: #卷插件类型 path: /disks/vol1 #指定本地磁盘路径 nodeAffinity: #节点选择器 required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node01 #在节点1上,事先创好路径 [root@master01 local-pv-demo]#kubectl apply -f local-pv-demo.yaml #查看 [root@master01 local-pv-demo]#kubectl get pv #定义pvc [root@master01 local-pv-demo]#vim pvc-localpv-demo.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-localpv-demo spec: accessModes:#访问模式 - ReadWriteOnce resources: requests: #需要空间大小 storage: 5Gi storageClassName: local #通过哪个存储类(两种要么统一存储类,要么都不属于任何存储类) [root@master01 local-pv-demo]#kubectl apply -f pvc-localpv-demo.yaml #查看 此时处于pending状态 [root@master01 local-pv-demo]#kubectl get pvc #定义pod 注意:pod调度器会结合pv控制器来判定该被调度到哪个节点上(hostpath卷不会,它不会使用pv control,无法影响pod schedule) [root@master01 local-pv-demo]#vim pod-with-localpv.yaml apiVersion: v1 kind: Pod metadata: name: pod-with-localpv spec: containers: - name: redis image: redis:7-alpine ports: - containerPort: 6379 name: redis volumeMounts: - mountPath: "/data" name: data volumes: - name: data persistentVolumeClaim: claimName: pvc-localpv-demo #使用上面定义的pvc [root@master01 local-pv-demo]#kubectl apply -f pod-with-localpv.yaml #查看,创建在node1节点上 [root@master01 local-pv-demo]#kubectl get -f pod-with-localpv.yaml -o wide #此时查看pvc,处于绑定状态 [root@master01 local-pv-demo]#kubectl get pvc
存储卷的具体的管理操作由相关的控制器向卷插件发起调用请求来完成 ◼ AD控制器:负责存储设备的Attach/Detach操作 ◆Attach:将设备附加到目标节点 ◆Detach:将设备从目标节点上拆除 ◼ 存储卷管理器:负责完成卷的Mount/Umount操作,以及设备的格式化操作等 ◼ PV控制器:负责PV/PVC的绑定、生命周期管理,以及存储卷的Provision/Delete操作 Scheduler:特定存储插件的调度决策会受到目标节点上的存储卷的影响
CSI简介
◼ 容器存储接口规范,与平台无关
◼ 驱动程序组件
◆CSI Controller:负责与存储服务的API通信从而完成后端存储的管理操作
◆Node Plugin:也称为CSI Node,负责在节点级别完成存储卷的管理
CSI Controller:由StatefulSet控制器对象编排运行,副本量需要设置为1,以保证只会该存储服务运行单个CSI Controller实例;
Node Plugin:由DaemonSet控制器对象编排运行,以确保每个节点上精确运行一个相关的Pod副本
动态置备存储
Kuberentes社区 主社区:Kubernetes项目,及各关键子项目 SIG: Special Intresting Group, #csi-driver-nfs在特别兴趣组项目下(github上) kubernetes-sigs/ csi-driver-nfs csi-driver-lvm ... 项目:csi-driver-nfs 负责为现有NFS Server提供PV的动态置备能力,它依赖于一个现有的NFS Server; 测试环境中,部署NFS Server的方法: (1)在Kubernetes上部署一个NFS Server;#只能在测试环境中使用 kubectl create namespace nfs kubectl create -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml \ -n nfs #指定创建在nfs服务空间 访问nfs服务的入口:nfs-server.nfs.svc.cluster.local (2)自行在Kubernetes集群外部准备一个NFS Server; #购买网上的服务 导出目录时要使用的导出选项:(rw,fsid=0,async,no_subtree_check,no_auth_nlm,insecure,no_root_squash) 部署csi-driver-nfs: remote install: curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.6.0/deploy/install-driver.sh | bash -s v4.6.0 -- local install: git clone https://github.com/kubernetes-csi/csi-driver-nfs.git cd csi-driver-nfs ./deploy/install-driver.sh v4.6.0 local 默认部署在kube-system名称空间,各Image的仓库地址是registry.k8s.io;#可能需要上外网 #操作实例 #部署csi-driver-nfs,采用local install方式 [root@master01 ~]#git clone https://github.com/kubernetes-csi/csi-driver-nfs.git [root@master01 ~]#cd csi-driver-nfs/deploy #直接部署4.6.0版本 (每个节点下载镜像并运行) [root@master01 deploy]#kubectl apply -f v4.6.0/ #查看(默认在kube-system空间下) [root@master01 deploy]#kubectl get pods -n kube-system -o wide #nfs服务器上 #创建路径作为动态置备 [root@ubuntu ~]#mkdir /nfspv [root@ubuntu ~]#vim /etc/exports /nfspv 10.0.0.0/16(rw,fsid=0,async,no_subtree_check,no_auth_nlm,insecure,no_root_squash) #执行重新导出 [root@ubuntu ~]#exportfs -arv #k8s控制节点主机上 #准备StroageClass,以完成csi-driver-nfs和nfs-server之间的对接: #要使用nfs上的存储驱动,需要定义storageclass来完成 [root@master01 ~]#vim storageclass-csi-nfs.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 10.0.0.155 #nfs服务器地址 share: /nfspv #nfs对应地址(会在该路径下自动创建子目录作为动态置备存储路径) reclaimPolicy: Delete #生产中用delete回收策略有风险 volumeBindingMode: Immediate [root@master01 ~]#kubectl apply -f storageclass-csi-nfs.yaml #查看存储类 [root@master01 ~]#kubectl get sc #创建PVC进行测试(只需要创建pvc即可,pv会自动创建) #路径在: csi-driver-nfs/deploy/example/pvc-nfs-csi-dynamic.yaml [root@master01 ~]#vim csi-driver-nfs/deploy/example/pvc-nfs-csi-dynamic.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs-dynamic namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: nfs-csi #创建pvc [root@master01 ~]#kubectl create -f csi-driver-nfs/deploy/example/pvc-nfs-csi-dynamic.yaml #查看pvc (显示状态已经Bound绑定了) [root@master01 ~]#kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-nfs-dynamic Bound pvc-f6797db7-bf8e-47fa-8bc1-6c26c6b35876 10Gi RWX nfs-csi <unset> 65s #查看pv(pv已经被自动创建出来) [root@master01 ~]#kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pvc-f6797db7-bf8e-47fa-8bc1-6c26c6b35876 10Gi RWX Delete Bound default/pvc-nfs-dynamic nfs-csi <unset> 104s #在nfs服务上查看,在路径上能看到对应子目录 [root@ubuntu ~]#cd /nfspv/ [root@ubuntu nfspv]#ls pvc-f6797db7-bf8e-47fa-8bc1-6c26c6b35876 #定义一个pod使用该pvc做测试 [root@master01 volumes]#vim pod-with-dyn-pvc.yaml apiVersion: v1 kind: Pod metadata: name: redis-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: pvc-nfs-dynamic #使用上面的pvc [root@master01 volumes]#kubectl apply -f pod-with-dyn-pvc.yaml [root@master01 volumes]#kubectl exec -it redis-pvc -- /bin/sh /data # redis-cli 127.0.0.1:6379> set a "hi hello" OK 127.0.0.1:6379> BGSAVE Background saving started 127.0.0.1:6379> exit /data # ls dump.rdb #nfs服务器上查看到数据 [root@ubuntu nfspv]#ls pvc-f6797db7-bf8e-47fa-8bc1-6c26c6b35876/ dump.rdb
示例: 部署mysql, 使用csi-driver-nfs的动态置备存储
注意: mysql如果用网络卷作为存储,性能会有很大影响, 最好使用local卷
#pvc和pod创建在同一个目录中,文件可以部署多个资源 [root@master01 pods]#vim mydb.yaml --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs-mydb spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: nfs-csi --- #上一个资源内容,用---隔开 apiVersion: v1 kind: Pod metadata: name: mydb spec: containers: - name: mydb image: mysql:8.0 env: - name: MYSQL_RANDOM_ROOT_PASSWORD #超级用户账号 value: M@geEdu - name: MYSQL_DATABASE value: wpdb - name: MYSQL_USER value: wpuser - name: MYSQL_PASSWORD value: wpP@ss volumeMounts: #调用下面的卷挂载 - name: mydb-stor mountPath: /var/lib/mysql/ #挂载点(mysql默认写在该路径下) volumes: - name: mydb-stor persistentVolumeClaim: claimName: pvc-nfs-mydb #调用上面pvc #创建名称空间 [root@master01 pods]#kubectl create namespace dev [root@master01 pods]#kubectl apply -f mydb.yaml -n dev [root@master01 pods]#kubectl get pvc -n dev NAME STATUS VOLUME CAPACITY ACCESS MODES pvc-nfs-mydb Bound pvc-478ac6b1-c1c4-4fa1-a6b5-d77000011f45 10Gi RWX [root@master01 pods]#kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pvc-478ac6b1-c1c4-4fa1-a6b5-d77000011f45 10Gi RWX Delete Bound dev/pvc-nfs-mydb nfs-csi <unset> 10m #nfs服务器上 [root@ubuntu nfspv]#ls pvc-478ac6b1-c1c4-4fa1-a6b5-d77000011f45/ auto.cnf ca.pem ib_buffer_pool mysql public_key.pem undo_002 binlog.000001 client-cert.pem ibdata1 mysql.ibd server-cert.pem wpdb binlog.000002 client-key.pem ibtmp1 mysql.sock server-key.pem binlog.index '#ib_16384_0.dblwr' '#innodb_redo' performance_schema sys ca-key.pem '#ib_16384_1.dblwr' '#innodb_temp' private_key.pem undo_001
CAS(Container Attached Storage)简介
容器附加存储(Container Attached Storage) ◼ Kubernetes的卷通常是基于外部文件系统或块存储实现,这种存储方案称为共享存储(Shared Storage) ◼ CAS则是将存储系统自身部署为Kubernetes集群上的一种较新的存储解决方案 ◆存储系统自身(包括存储控制器)在Kubernetes上以容器化微服务的方式运行 ◆使得工作负载更易于移植,且更容易根据应用程序的需求改动使用的存储 ◆通常基于工作负载或者按集群部署,因此消除了共享存储的跨工作负载甚至是跨集群的爆炸半径 ◼ 存储在 CAS 中的数据可以直接从集群内的容器访问,从而能显著减少读/写时间 ◼ OpenEBS是CAS存储机制的著名实现之一,由CNCF孵化 基于CAS的存储解决方案,通常包含两类组件 ◼ 控制平面 ◆负责配置卷以及其他同存储相关任务 ◆由存储控制器、存储策略以及如何配置数据平面的指令组成 ◼ 数据平面 ◆接收并执行来自控制平面的有关如何保存和访问容器信息的指令 ◆主要组件是实现池化存储的存储引擎,这类引擎本质上负责输入/输出卷路径 ◆OpenEBS支持存储引擎包括Mayastor、cStor、Jiva和OpenEBS LocalPV等 OPenEBS: CAS风格的存储解决方案之一;由控制平面和数据平面,其中的数据平面的主要组成部分是存储引擎 支持的存储引擎,MayaStor, cStor, Jiva, LocalPV
CAS的重要特征
◼ 储控制器分解为可以彼此独立运行的组成部分
◼ 每个工作负载都有自己的一个或多个控制器
OpenEBS简介 ◼ OpenEBS能够将Kubernetes工作节点上可用的任何存储转换为本地卷或分布式复制卷 ◼ 最初由MayaData构建,后捐赠给了CNCF,目前是CNCF的沙箱级项目 #分布式复制卷,一份数据存两份,放到2个不同的节点上 OPenEBS: 卷类型:本地卷和分布式复制卷 本地卷:磁盘、分区、文件系统目录(local Volume)、ZFS、LVM 复制卷: iSCSI: cStor和Jiva NVMEoF: MayaStor Jiva或cStor要建立在本地卷功能的基础之上; Jiva --> 依赖于本地卷来解决每个节点上的存储需求 #OpenEBS架构 OpenEBS存在着众多组件,他们大体可以分以两大类 ◼ 数据引擎 ◼ 控制平面
如何选择数据引擎 ◼ 应用程序处于生产状态且不需要存储级复制,则首选 LocalPV ◼ 应用程序处于生产状态并且需要存储级复制,则首选 cStor ◼ 应用程序较小、需要存储级复制但不需要快照或克隆,则首选 Jiva ◼ 应用程序需要低延迟和接近磁盘的吞吐量,需要存储级复制,并且工作节点具有性能较高的CPU、RAM和NVME,那么 Mayastor 是首选 NDM(Node Disk Manager) ◼ 部署OpenEBS的过程中,NDM由专用DaemonSet编排运行于每个节点上 ◆负责发现裸设备并过滤掉不支持使用的设备,例如已经带有文件系统的磁盘 ◆需要特权模式,访问/dev、/proc和/sys目录来监视连接的设备,并使用各种探测器获取这些设备的详细信息 ◼ 根据过滤器(filters)检测附加到节点上的裸磁盘设备,并将它们识别为“块设备CRD” ◆NDM支持使用include filters或exclude filters ◆filter的配置保存于ConfigMap中 ◼ 基于节点上的裸磁盘设备提供PV的存储引擎,会依赖于NDM实现其功能,这包括Local PV device 和 cStor
部署使用OpenEBS的基本流程 ◼ 在各节点上部署iSCSI client #存储引擎cStor,Jiva需要,其他不需要部署 ◼ 在Kubernetes集群上部署OpenEBS ◼ 选择要使用的数据引擎 ◼ 为选择的数据引擎准备StorageClass
实际操作示例 (本地卷,无法跨节点, 自动置备pv存储)
#安装openebs环境 (3.10.x版本,官网有安装方法) #部署pod [root@master01 ~]#kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml #会创建独立名称空间 openebs [root@master01 ~]#kubectl get ns #查看名称空间中的资源,会创建很多pod [root@master01 ~]#kubectl get all -n openebs #每个节点上都会跑一个ndm(用于发现独立的裸磁盘设备,配置为独立的block device) [root@master01 ~]#kubectl get pods -n openebs -o wide #查看CRD 自定义资源定义(没发现一个块磁盘,就会创建一个blockdevices,目前系统没有,不用管) [root@master01 ~]#kubectl get crd blockdevices.openebs.io 2024-11-21T14:53:41Z #自动创建两个存储类(因为用的都是本地卷,所以采用延迟绑定机制) 可改存储路径,删存储类重建或修改下,并建目录 [root@master01 ~]#kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE openebs-device openebs.io/local Delete WaitForFirstConsumer false 12h openebs-hostpath openebs.io/local Delete WaitForFirstConsumer false 12h #自此openebs环境准备完成,如果对lvm等有需求,openebs官网再部署其他pod #准备pvc文件 [root@master01 local-pv-hostpath]#vim openebs-local-hostpath-pvc.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: openebs-local-hostpath-pvc spec: storageClassName: openebs-hostpath #部署完openebs自动生成的storageClass,会自动置备存储 accessModes: - ReadWriteOnce #此处必须是单路读写 resources: requests: #请求使用空间5个g storage: 5G [root@master01 local-pv-hostpath]#kubectl apply -f openebs-local-hostpath-pvc.yaml root@master01 local-pv-hostpath]#kubectl get pvc NAME STATUS STORAGECLASS VOLUMEATTRIBUTESCLASS AGE openebs-local-hostpath-pvc Pending openebs-hostpath <unset> 17s #准备pod (运行pod会自动置备pv存储) [root@master01 local-pv-hostpath]#vim redis-with-openebs-local-hostpath.yaml apiVersion: v1 kind: Pod metadata: name: redis-with-openebs-local-hostpath spec: containers: - name: redis image: redis:7-alpine ports: - containerPort: 6379 name: redis volumeMounts: - mountPath: /data name: local-storage volumes: - name: local-storage persistentVolumeClaim: claimName: openebs-local-hostpath-pvc [root@master01 local-pv-hostpath]#kubectl apply -f redis-with-openebs-local-hostpath.yaml #查看创建的pods,会是pending状态(首次消费pv,pv有初始化过程,会下载一初始化pod在openebs空间,准备好会自动删除) [root@master01 local-pv-hostpath]#kubectl get pods redis-with-openebs-local-hostpath 0/1 Pending 0 27s #等到running状态后,查看数据存到哪了 [root@master01 local-pv-hostpath]#kubectl get sc [root@master01 local-pv-hostpath]#kubectl get sc openebs-hostpath -o yaml ... Default value is /var/openebs/local ... #/var/openebs/local pod所在节点下,在该目录下创建子目录作为pv存储后端 [root@master01 local-pv-hostpath]#kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE openebs-local-hostpath-pvc Bound pvc-a4125f1e-eaf3-4020-b7e8-1ff770ca7430 5G RWO openebs-hostpath <unset> 157m #节点3上查看 [root@node03 ~]#ls /var/openebs/local pvc-a4125f1e-eaf3-4020-b7e8-1ff770ca7430
实际操作示例 (复制卷,跨节点冗余)
#复制卷后台用的还是本地卷,在本地卷基础上加了复制的引擎(需要单独的pod来运行) #这里采用jiva (cstor和jiva的区别在于cstor可使用快照) #基于上面openebs环境部署完成后 #安装jiva-operator [root@master01 ~]#kubectl apply -f https://openebs.github.io/charts/jiva-operator.yaml #每个节点上安装jiva驱动节点 [root@master01 ~]#kubectl get pods -n openebs #定义jiva卷策略 [root@master01 jiva-csi]#vim openebs-jivavolumepolicy-demo.yaml apiVersion: openebs.io/v1alpha1 kind: JivaVolumePolicy #安装jiva创建的新的jivavolumepolicy metadata: name: jivavolumepolicy-demo namespace: openebs #一般放在这个名称空间下 spec: replicaSC: openebs-hostpath #复制时使用的存储类,这里用基于目录存储(上面用的本地卷方式相同) target: # This sets the number of replicas for high-availability # replication factor <= no. of (CSI) nodes replicationFactor: 2 #复制因子,数据打算冗余几份 [root@master01 jiva-csi]#kubectl apply -f openebs-jivavolumepolicy-demo.yaml #查看创建的jivavolumepolicy资源类型 [root@master01 jiva-csi]#kubectl get jivavolumepolicy -n openebs NAME AGE jivavolumepolicy-demo 32s #创建存储类 [root@master01 jiva-csi]#vim openebs-jiva-csi-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: openebs-jiva-csi provisioner: jiva.csi.openebs.io allowVolumeExpansion: true #是否支持卷扩缩容(使用路径不支持,用LVM支持) parameters: cas-type: "jiva" policy: "jivavolumepolicy-demo" #在哪个policy管控jiva存储卷 [root@master01 jiva-csi]#kubectl apply -f openebs-jiva-csi-storageclass.yaml [root@master01 jiva-csi]#kubectl get sc #创建pvc [root@master01 jiva-csi]#vim openebs-jiva-csi-pvc.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: openebs-jiva-csi-pvc spec: storageClassName: openebs-jiva-csi #使用上面的存储类 accessModes: - ReadWriteOnce resources: requests: storage: 5Gi [root@master01 jiva-csi]#kubectl apply -f openebs-jiva-csi-pvc.yaml #查看pvc,直接绑定了(绑定的是jiva卷) [root@master01 jiva-csi]#kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE openebs-jiva-csi-pvc Bound pvc-1057bf10-245f-4718-a0ca-f91cd899d952 5Gi RWO openebs-jiva-csi <unset> 31s #查看jiva卷(jiva卷要复制2份数据到两个节点上) Unknown是因为初始化pod还没有运行完成(openebs空间下) [root@master01 jiva-csi]#kubectl get jivavolume -n openebs NAME REPLICACOUNT PHASE STATUS pvc-1057bf10-245f-4718-a0ca-f91cd899d952 2 Ready RW #openebs名称空间下创建了3个pod,一个是jiva卷微控制器,另外两个是两份数据副本 [root@master01 jiva-csi]#kubectl get pods -n openebs pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-ctrl-6c75c9d5z4vl 2/2 Running pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-rep-0 1/1 Running pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-rep-1 1/1 Running #在openebs自动创建了2个pvc [root@master01 jiva-csi]#kubectl get pvc -n openebs openebs-pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-rep-0 openebs-pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-rep-1 #要使用jiva的pvc存数据,在所有节点(包括master)上安装iSCSI client [root@node01 ~]#apt-get install open-iscsi [root@node01 ~]#systemctl enable --now iscsid #到此为止,jiva的环境设定完成 #使用redis实例 [root@master01 jiva-csi]#vim redis-with-openebs-jiva-pvc.yaml apiVersion: v1 kind: Pod metadata: name: redis-with-openebs-jiva-pvc spec: containers: - name: redis image: redis:7-alpine ports: - containerPort: 6379 name: redis volumeMounts: - mountPath: /data name: local-storage volumes: - name: local-storage persistentVolumeClaim: claimName: openebs-jiva-csi-pvc #使用上面基于jiva的pvc [root@master01 jiva-csi]#kubectl apply -f redis-with-openebs-jiva-pvc.yaml #测试 [root@master01 jiva-csi]#kubectl exec -it redis-with-openebs-jiva-pvc -- /bin/sh /data # redis-cli 127.0.0.1:6379> SET test "testing" OK 127.0.0.1:6379> BGSAVE Background saving started 127.0.0.1:6379> exit /data # ls dump.rdb lost+found#说明是二进制设备存储空间 #查看存在哪个卷上 [root@master01 jiva-csi]#kubectl get pvc -n openebs [root@master01 jiva-csi]#kubectl get pvc openebs-pvc-1057bf10-245f-4718-a0ca-f91cd899d952-jiva-rep-0 -o yaml -n openebs ... volume.kubernetes.io/selected-node: node01 #node1节点查看(大部分rdb文件被封装了,因这是复制引擎) [root@node01 ~]#ls /var/openebs/local/pvc-023b9bd4-5f23-4e91-a16e-545bc1aeaeef/ log.info volume-head-001.img volume-snap-bc8e1cfc-55f9-41e2-b093-92ec82447d8f.img replica.log volume-head-001.img.meta volume-snap-bc8e1cfc-55f9-41e2-b093-92ec82447d8f.img.meta revision.counter volume.meta
要使用多路读写, 只能建nfs server。或者做一个jiva卷, 在jiva卷的上面再部署nfs服务(支持不太成熟,在官网上有写)
1 ConfigMap和Secret基础
特殊卷:configMap/secret 介绍
应用配置: 临时卷:empytDir 本地卷:hostPath, local 网络卷:nfs, ... 特殊卷:configMap,secret, downwardAPI 卷扩展:CSI 特殊卷:configMap,secret, downwardAPI configMap/secret: 几重概念: (1) 资源类型 (2) 卷插件 资源类型:configMap、secret 保存于api server中的资源对象, 最终存储于etcd中 卷插件: 在Pod上,基于该类型卷插件,以卷的形式引用configmap/secret资源对象 容器配置参数: 环境变量: 1、启动容器时,自定义要运行的命令及选项、参数; Dockerfile, CMD, ENTRYPOINT 2、通过环境进行配置; Dockerfile, entrypoint.sh脚本来代理预处理环境变量 3、将配置焙进Image,重制Image; 4、将配置文件放置在卷上,由容器通过卷加载; 卷:ConfigMap/Secret configMap:保存非敏感的配置,数据以明文存储 secret: 保存敏感配置,数据以base64编码格式存储 数据格式: Key: Value Value:分为两类 单行值:单行字符串 多行值:文件格式的配置信息 每个对象中,可以保存多个kv数据; ConfigMap和Secret是Kubernetes系统上两种特殊类型的存储卷 ◼ ConfigMap用于为容器中的应用提供配置数据以定制程序的行为,而敏感的配置信息,例如密钥、证书等则通常由Secret来配置 ◼ ConfigMap和Secret将相应的配置信息保存于资源对象中,而后在Pod对象上支持以存储卷的形式将其挂载并加载相关的配置,从而降低了配置与镜像文件的耦合关系,提高了镜像复用能力 ◼ Kubernetes借助于ConfigMap对象实现了将配置文件从容器镜像中解耦,从而增强了工作负载的可移植性,使其配置更易于更改和管理,并避免了将配置数据硬编码到Pod配置清单中 此二者都属于名称空间级别,只能被同一名称空间中的Pod引用
◼ ConfigMap和Secret资源都是数据承载类的组件,是Kubernetes API的标准资源类型,是一等公民 ◼ 主要负责提供key-value格式的数据项,其值支持 ◆单行字符串:常用于保存环境变量值,或者命令行参数等 ◆多行字串:常用于保存配置文件的内容 #定义示例 apiVersion: v1 kind: ConfigMap metadata: name: myapp-confs data: PORT: "8080" myserver-status.cfg: | #多行的v用|加换行缩进的方式(将来转为文件会顶格写不会有缩进) location /nginx-status { stub_status on; access_log off; } ◼ 从Kubernetes v1.19版本开始,ConfigMap和Secret支持使用immutable字段创建不可变实例
环境变量
◼ 将configmap对象上的某key的值赋值给(valueFrom)指定的环境变量
卷
◼ 在Pod上基于configMap卷插件引用configmap对象
◼ 在Container上挂载configMap卷
◆每个kv会分别被映射为一个文件,文件名同key,value将成为文件内容
2 ConfigMap
创建ConfigMap对象的方法有两种 ◼ 命令式命令 ◆ 字面量:kubectl create configmap NAME --from-literal=key1=value1 ◆ 从文件加载:kubectl create configmap NAME --from-file=[key=]/PATH/TO/FILE ◆ 从目录加载: kubectl create configmap NAME --from-file=[key=]/PATH/TO/DIR/ ◼ 配置文件 ◆命令式:kubectl create -f ◆声明式:kubectl apply -f 提示:基于文件内容生成时,可以使用命令式命令以dry-run模式生成并保存 创建方法: 指令式命令: kubectl create configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none] 直接值:直接于命令行给出kv 从文件加载:从文件加载生成kv 对象配置: kubectl create -f /path/to/configmap_manifest_file #例: #创建demoapp-cfg名的configmap;--from-literal指定kv;可以指定名称空间,这里默认 ~]#kubectl create configmap demoapp-cfg --from-literal=listen="127.0.0.1" --from-literal=port="8080" #查看configmap,可以简称cm ~]#kubectl get cm #不创建configmap只生成yaml ~]#kubectl create configmap demoapp-cfg --from-literal=listen="127.0.0.1" --from-literal=port="8080" --dry-run=client -o yaml #从文件中加载 ~]#kubectl create configmap nginx-cfg --from-file=./myserver.conf --dry-run=client -o yaml apiVersion: v1 data: myserver.conf: | server { listen 8080; server_name www.ik8s.io; include /etc/nginx/conf.d/myserver-*.cfg; location / { root /usr/share/nginx/html; } } kind: ConfigMap metadata: creationTimestamp: null name: nginx-cfg #可以指定键名,修改将来挂载的文件名 ~]#kubectl create configmap nginx-cfg --from-file=my.conf=./myserver.conf --dry-run=client -o yaml apiVersion: v1 data: my.conf: | server { listen 8080; server_name www.ik8s.io; include /etc/nginx/conf.d/myserver-*.cfg; location / { root /usr/share/nginx/html; } } kind: ConfigMap metadata: creationTimestamp: null name: nginx-cfg #加载多个文件 ~]#kubectl create configmap nginx-cfg --from-file=./myserver.conf --from-file=./myserver-status.cfg --dry-run=client -o yaml #加载当前目录下的所有文件(无法改文件名) ~]#kubectl create configmap nginx-cfg --from-file=./ --dry-run=client -o yaml #使用示例(从环境变量中直接引用) [root@master01 configmaps_and_secrets]#vim configmaps-env-demo.yaml --- apiVersion: v1 kind: ConfigMap metadata: name: demoapp-config namespace: default data: 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: #引用 configMapKeyRef: #configmap中的键引用 name: demoapp-config #configmap名字 key: demoapp.port #键名 optional: false #如在ConfigMap中找不到指定的key,K8s将会报错 - name: HOST valueFrom: configMapKeyRef: name: demoapp-config key: demoapp.host optional: true #创建 [root@master01 configmaps_and_secrets]#kubectl apply -f configmaps-env-demo.yaml configmap/demoapp-config created pod/configmaps-env-demo created #查看ConfigMap [root@master01 configmaps_and_secrets]#kubectl get cm NAME DATA AGE demoapp-config 2 27s [root@master01 configmaps_and_secrets]#kubectl get pods NAME READY STATUS RESTARTS AGE configmaps-env-demo 1/1 Running 0 58s #查看是否监听8080 [root@master01 configmaps_and_secrets]#kubectl exec configmaps-env-demo -- netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN #可以通过env打印出全部的环境变量 [root@master01 configmaps_and_secrets]#kubectl exec configmaps-env-demo -- env #一般不建议修改configmap,但是可以修改,改法有两种 #第一种,直接修改configmap ]#vim configmaps-env-demo.yaml ... demoapp.port: "10080" #只是变了configmap ]#kubectl apply -f configmaps-env-demo.yaml #查看configmap的yaml形式,端口已变10080 ]#kubectl get cm demoapp-config -o yaml #查看环境变量PORT依然是8080 [root@master01 configmaps_and_secrets]#kubectl exec configmaps-env-demo -- env #pod里面监听的端口还是8080,除非重启整个pod #使用示例(基于卷引用方式,卷插件方式) 这种方式修改会重新注入到pod内部(能不能生效看系统是否自动装载新配置) #创建configmap [root@master01 configmaps_and_secrets]#kubectl create configmap nginx-config-files --from-file=./nginx-conf.d/ #调用 [root@master01 configmaps_and_secrets]#vim configmaps-volume-demo.yaml apiVersion: v1 kind: Pod metadata: name: configmaps-volume-demo namespace: default spec: containers: - image: nginx:alpine name: nginx-server volumeMounts: - name: ngxconfs mountPath: /etc/nginx/conf.d/ #挂载点 readOnly: true #只读 volumes: - name: ngxconfs configMap: name: nginx-config-files #引用nginx-config-files的configMap optional: false #必选 [root@master01 configmaps_and_secrets]#kubectl apply -f configmaps-volume-demo.yaml #进入pod容器 [root@master01 configmaps_and_secrets]#kubectl exec -it configmaps-volume-demo -- /bin/sh /etc/nginx/conf.d # ls -l total 0 lrwxrwxrwx 1 root root 24 Nov 23 08:27 myserver-gzip.cfg -> ..data/myserver-gzip.cfg lrwxrwxrwx 1 root root 26 Nov 23 08:27 myserver-status.cfg -> ..data/myserver-status.cfg lrwxrwxrwx 1 root root 20 Nov 23 08:27 myserver.conf -> ..data/myserver.conf /etc/nginx/conf.d # ls -al total 12 drwxrwxrwx 3 root root 4096 Nov 23 08:27 . drwxr-xr-x 3 root root 4096 Nov 12 02:02 .. drwxr-xr-x 2 root root 4096 Nov 23 08:27 ..2024_11_23_08_27_23.4057447185 lrwxrwxrwx 1 root root 32 Nov 23 08:27 ..data -> ..2024_11_23_08_27_23.4057447185 lrwxrwxrwx 1 root root 24 Nov 23 08:27 myserver-gzip.cfg -> ..data/myserver-gzip.cfg lrwxrwxrwx 1 root root 26 Nov 23 08:27 myserver-status.cfg -> ..data/myserver-status.cfg lrwxrwxrwx 1 root root 20 Nov 23 08:27 myserver.conf -> ..data/myserver.conf #实际上配置文件被关联到..data路径下,而它其实是..2024_11_23_08_27_23.4057447185这个隐藏目录,名字是时间戳 #这个时间戳代表configMap版本,每次改了configMap,configMap版本会变 #修改configMap内容测试 #直接在线修改configMap(这样改有风险,不建议) [root@master01 ~]#kubectl edit cm nginx-config-files #随便改写内容保存 #每个pod会等待一段时间(一般几秒~几十秒),pod内文件才会变更,时间k8s自行控制 #新版本可以发现配置文件变更并生效。1.20及之前的版本做不到 #查看内容,时间戳已发生改变 /etc/nginx/conf.d # ls -al total 12 drwxrwxrwx 3 root root 4096 Nov 23 08:43 . drwxr-xr-x 3 root root 4096 Nov 12 02:02 .. drwxr-xr-x 2 root root 4096 Nov 23 08:43 ..2024_11_23_08_43_42.901207710 lrwxrwxrwx 1 root root 31 Nov 23 08:43 ..data -> ..2024_11_23_08_43_42.901207710 lrwxrwxrwx 1 root root 24 Nov 23 08:27 myserver-gzip.cfg -> ..data/myserver-gzip.cfg lrwxrwxrwx 1 root root 26 Nov 23 08:27 myserver-status.cfg -> ..data/myserver-status.cfg lrwxrwxrwx 1 root root 20 Nov 23 08:27 myserver.conf -> ..data/myserver.conf #查看里面nginx的配置已变更 #nginx -T可以查看当前nginx加载的配置,经确认也已变更(说明自动重载新配置了) /etc/nginx/conf.d #nginx -T #注:这种方式修改会重新注入到pod内部,是否生效看系统是否自动装载新配置(新的服务都可以,早期java可能要重启pod)
Secret资源类型: 类型之分: kubectl create secret (docker-registry | generic | tls) [options] docker-registry:专用于认证到docker registry服务上的认证信息类型 kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email[--docker-server=string] 引用方式:pods.spec.imagePullSecrets,列表型数据 第二种引用方式:pods.spec.serviceAccountName serviceaccount.imagePullSecrets tls:专用配置服务基于ssl/tls通信时使用的数字证书和私钥的类型 kubectl create secret tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none] 证书的键名:tls.crt 私钥的键名:tls.key generic: 通用类型,可划分为多种子类型 --type
创建Secret资源
secret和configmap的显著区别是尽量不要用加载环境变量的方式去加载secret(导致敏感信息泄露), 用卷的方式加载
但有些场景没办法, 像mysql必须使用环境变量提供
支持类似于ConfigMap的创建方式,但Secret有类型子命令,而且不同类型在data或stringData字段中支持嵌套使用的key亦会有所有同; 命令式命令 ◼ generic ◆kubectl create secret generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] ◆除了后面docker-registry和tls命令之外的其它类型,都可以使用该命令中的--type选项进行定义,但有些类型有key的特定要求 ◼ 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 ◼ 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示例 (使用环境变量引用,不太靠谱,信息依然会泄露): #创建secret generic类型,名称叫mysql-secret (自动做base64编码进行存储) [root@master01 ~]#kubectl create secret generic mysql-secret --from-literal=root.password='M@geEdu' --from-literal=user.password="wpP@ss" -n wordpress #查看secret (不归类的都定义为Opaque) DATA为2表示有2项 [root@master01 pods]#kubectl get secret -n wordpress NAME TYPE DATA AGE mysql-secret Opaque 2 14s [root@master01 pods]#kubectl get secret -o yaml -n wordpress apiVersion: v1 items: - apiVersion: v1 data: root.password: TUBnZUVkdQ== user.password: d3BQQHNz kind: Secret metadata: creationTimestamp: "2024-11-23T10:04:35Z" name: mysql-secret namespace: default resourceVersion: "634108" uid: 3b51fdcf-ba8c-437a-8b65-1b6273ea3f03 type: Opaque kind: List metadata: resourceVersion: "" #定义mysql的pod,引用上面的secret ]#vim pod-mydb.yaml apiVersion: v1 kind: Pod metadata: name: mydb namespace: wordpress spec: containers: - name: mydb image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD #超级用户账号 valueFrom: secretKeyRef: name: mysql-secret key: root.password optional: false #必选 - name: MYSQL_DATABASE #放在secret或configmap都行 value: wpdb - name: MYSQL_USER #放在secret或configmap都行 value: wpuser - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: user.password [root@master01 ~]#kubectl apply -f pod-mydb.yaml [root@master01 ~]#kubectl get -f pod-mydb.yaml #进入pod [root@master01 ~]#kubectl exec -ti mydb -n wordpress -- /bin/sh #查看环境变量可以看到密码 sh-5.1# env #docker-registry示例 ]#kubectl create secret docker-registry magedu-dockerhub --docker-username=magedu --docker-password=M@geEdu --docker-email=mage@magedu.com --dry-run=client -o yaml apiVersion: v1 data: .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJtYWdlZHUiLCJwYXNzd29yZCI6Ik1AZ2VFZHUiLCJlbWFpbCI6Im1hZ2VAbWFnZWR1LmNvbSIsImF1dGgiOiJiV0ZuWldSMU9rMUFaMlZGWkhVPSJ9fX0= kind: Secret metadata: creationTimestamp: null name: magedu-dockerhub type: kubernetes.io/dockerconfigjson #引用:创建pod时修改pods.spec.imagePullSecrets字段(列表型数据),把Secret值写入 #tls示例 #准备好私钥和证书 [root@master01 certs.d]#ls nginx.crt nginx.key #不创建tls类型的secret,生成yaml [root@master01 certs.d]#kubectl create secret tls nginx-cert --cert=./nginx.crt --key=./nginx.key --dry-run -o yaml W1123 11:13:44.112377 194065 helpers.go:703] --dry-run is deprecated and can be replaced with --dry-run=client. apiVersion: v1 data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsVENDQW4yZ0F3SUJBZ0lVR2Zya05FeGZiS2N5Yy9LYkpLUXJ5MzRTcHI0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dqRUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdNQjBKbGFXcHBibWN4RURBT0JnTlZCQWNNQjBKbAphV3BwYm1jeER6QU5CZ05WQkFvTUJrUmxkazl3Y3pFV01CUUdBMVVFQXd3TmQzZDNMbWxzYVc1MWVDNXBiekFlCkZ3MHlNREEwTVRVd05UQXdORE5hRncweU1EQTFNVFV3TlRBd05ETmFNRm94Q3pBSkJnTlZCQVlUQWtOT01SQXcKRGdZRFZRUUlEQWRDWldscWFXNW5NUkF3RGdZRFZRUUhEQWRDWldscWFXNW5NUTh3RFFZRFZRUUtEQVpFWlhaUApjSE14RmpBVUJnTlZCQU1NRFhkM2R5NXBiR2x1ZFhndWFXOHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRE1pNSsyUStZTlNxeS9vVVhNQzAraklqc2puS2M5SzdjVzYweFhrQzZOa3lSY3BZSmwKdWM4ckFYRUxyZjUxMmJUWGhxb0hqVG5JeFExVFROeDNRbE9oTHBYVjJCbGtObVNzY0w0Uy9IL1VEWTlQayt0cwpiOGlEZSszdlBEQ1ZiQytvOEFYYUhUaktaQ0pXc0oxY0RJY0JGenQ0MFQzUWswL1hQcVYrM1pERWFNcW9LYklJCmRvbENLLzZiN1BlaElXVVFWeFVDK3NoZ0xVbjJReXJmK0UrRC9TQmZFOVd3UGp0YXdCeHdxaDZOczV1dEVsL0cKVmltbEMxM2tsWTNGQ1RMWEhFU3hNKzdGNlU2VUdpYm1CWFRsLzZlV1I2bmlVdW1kMjhyU2NneXVDTnA1NGY2cAp1VGFMK0ZNbG0wN0NiS3lLWHhDZHpVNXVrSFlOYXlqa3p3ck5BZ01CQUFHalV6QlJNQjBHQTFVZERnUVdCQlE1ClhNbkhJanZYZFVCb1BEd3BpbjdiVWN6SHJqQWZCZ05WSFNNRUdEQVdnQlE1WE1uSElqdlhkVUJvUER3cGluN2IKVWN6SHJqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQlVwU1pSNUE5Rgo2bVZhSU16TUl0anZadXFpRDNpeG9PN3NEdEZCcGVsYVZNV0dIaTZ3cFlTL21kNkNmS0hQQ3pySDhnNmpWTGhaClRpRWd5OVREUG5wMmU0VWNBUzdYMVBPM21GWTVscGpVakxJZjR4bUZrUy9FdFQ1ZE5TbUF1NGxGN2ZrRkEraEYKc244b2s5bk9DNk05OVBxbmQ1SlpVR1pwRFJCK1NQblpIVzZ3R2JiSVd3R3hPQmNtejBBMHNJNTkyVm9POThYKwoxK2w4MEVmdkhjRDJQRmdmSzhqS3g5eEl0UjEwcnBtVE4yQmtPQlBiREZ4SHZEcTNjRlRBSVVhMGhWS04xT0xJCk1oSEsvVnJmRUxHanJvY0pTZEFxcGpPTFl0R2JQN09sWURHTzFPRDk2ZEVTYStWWTRXYnFqYzc5S0luREQrWnkKWVlqbkJKR2F5VExyCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBekl1ZnRrUG1EVXFzdjZGRnpBdFBveUk3STV5blBTdTNGdXRNVjVBdWpaTWtYS1dDClpiblBLd0Z4QzYzK2RkbTAxNGFxQjQwNXlNVU5VMHpjZDBKVG9TNlYxZGdaWkRaa3JIQytFdngvMUEyUFQ1UHIKYkcvSWczdnQ3end3bFd3dnFQQUYyaDA0eW1RaVZyQ2RYQXlIQVJjN2VORTkwSk5QMXo2bGZ0MlF4R2pLcUNteQpDSGFKUWl2K20rejNvU0ZsRUZjVkF2cklZQzFKOWtNcTMvaFBnLzBnWHhQVnNENDdXc0FjY0tvZWpiT2JyUkpmCnhsWXBwUXRkNUpXTnhRa3kxeHhFc1RQdXhlbE9sQm9tNWdWMDVmK25sa2VwNGxMcG5kdkswbklNcmdqYWVlSCsKcWJrMmkvaFRKWnRPd215c2lsOFFuYzFPYnBCMkRXc281TThLelFJREFRQUJBb0lCQUNjeWpvMndIMUxtdjRvTgpqc0dXWFZHR3lzeDlSYk04UUY3ZEFvazVNU0tpVXZLS0tSM3phSmIyTk1Lbk9qODlWQ0dGUmVvaWp6TkJSOWR4CndFSCtiT1pUZGhVL3owWGNBcGpsRmhldldaTzZjWDh2ZW9zU05OdTFrUmdxY2FrQXpYVlRZZHUxZzkrTkp1TnoKL3dQWHhydFh4MmJVdWtMUktCaTRnYUI1TnpmY0FSdzBIWG9aUjExbVRydkhLSWRSQUx4Q21KR09aTlg5RjhGMQpsQ1dCN2hjWVNkRVg5MTBkT2VFTlRzUUNMeVJvaDdXWkJSejBEUVBYNnNJd2FJTGtFT0puSVQ0K3JYNldCZGdkCmRkRjN3L0ZYcGxFdW9BSVplSDJmZnkvMzNyTFpSSXlSZWFEVnJUeHFUMnVOQlVkbkJPN1NFa2VYV25kUEh2V2MKaUpRUGxTRUNnWUVBOXdGQmpzOGk2a0YrWDM1N09ueFpuZXB4WnVZMGhqRk5BcVdPSGlUMWsvcWt6T2pRSG1kMgpHSnJZZjc0OUc3VHB2R2Y2TytzalIvZm96ODVsdFViSnh5Wk1IK2JqQzgwM0VUeXp2TmlXZzE2MVp6Nyt5NGpKClI5dlhCS05vd0NlazVFYVRyZ3owZkp5bjJ1WlRDak1aUE1WVVFBRmlYc0RtUTAybUp6dVdwTmtDZ1lFQTAvNkkKTjg2a2szalRzaHhvTEM5VUR0K0x4VTVaUE8vS0t0a21SUEorYU1IbS9uUXJYazZ6a1pJVUF2OHIrOWZXVXdHbQpYUmd2QjVlaHAvT1htdkxLWEhSVmhhVjc3QTZvMEVQVFRrcU5iZU5GSHg1cVFObU85ZHdIVTNSTW5PeWk0TnluCmFJNlljNGIvMWJXdXlBYlVlc0tEdXI3L0FMOFo5UU1XVUVUb2pSVUNnWUF2bzY1aFBOSWZIRUtqYUdHY0JoL0MKdFZUcDQ3eDlwVVNWSGhrcTl6WG1OSkZVZEJLdnlvU2Nla0VIWWttbTdsMm1XT2VLWnUrSEVlbDFLdm15M05STgo5TFQ1OGk0WU9KeEdWczdUdlhKS0pCb1lyNjIwMDh6K2J3Z3BmTnJYTk00NHVPUUN6YnpaeTkwVCt4aEkvMUgrCnhwQlpSK3NSRzJOTjE4d1VCUW9wQVFLQmdFdnNHWDdiRytmUTJ3Z3Iwa2NZd0NML2ZvQXdPaGR2elZpaEltcUkKNmlxOFh1ejhUOWZibWNYbHFoTVVyZnpvNU5JZmdpUlBGL0RCSmwwUENWbXQ0RGxTVkpxamxJa0xDdnhqZmhiSQo3blBQZEI3YjlyTzQ5dEVvZHRzMWlJYWUzUXBwRys5L09pd055aXdRZ0VNVTV4Mzc4Yzk4dmJqWHVBVWVrT3c0CmNZeXRBb0dBTEtNazBwMUUrZHJtVzVuS3VMa2dyNDYzbmtlenBld2Y4eVJ2UHZQcUo4UkdobTBGblphOW1mQ0YKd1JCMDh1MkZ6YTRHSldEY0p2VG9kV3NtSmpCVHRMQ0wzaFlCSmNNQXNVNEdEbXBCZDdQMVBQQ1ZCb0JMeVJjbApyK2VTOFJzZTRwTERjOWdpQk9ZSGp4Q2hsVktSdVhESmh2V2k0azZVNGY5bStBNmp4STg9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== kind: Secret metadata: creationTimestamp: null name: nginx-cert type: kubernetes.io/tls #tls示例,创建nginx服务,配https证书和秘钥 [root@master01 nginx-ssl-conf.d]#vim myserver.conf server { listen 443 ssl; server_name www.ik8s.io; ssl_certificate /etc/nginx/certs/tls.crt; #secret挂载点,键名为文件名(会自动base64解码) ssl_certificate_key /etc/nginx/certs/tls.key; #secret挂载点,键名为key(会自动base64解码) ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; include /etc/nginx/conf.d/myserver-*.cfg; location / { root /usr/share/nginx/html; } } server { listen 80; server_name www.ilinux.io; return 301 https://$host$request_uri; } #pod文件 [root@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: secretName: nginx-ssl-secret #secret名称 - name: nginxconfs configMap: name: nginx-sslvhosts-confs optional: false #不加,默认为false #创建secret tls类型 [root@master01 configmaps_and_secrets]#kubectl create secret tls nginx-ssl-secret --cert=./certs.d/nginx.crt --key=./certs.d/nginx.key [root@master01 configmaps_and_secrets]#kubectl get secret #创建configmap [root@master01 configmaps_and_secrets]#kubectl create configmap nginx-sslvhosts-confs --from-file=./nginx-ssl-conf.d/ [root@master01 configmaps_and_secrets]#kubectl get cm #创建pod [root@master01 configmaps_and_secrets]#kubectl apply -f secrets-volume-demo.yaml #进入pod查看 [root@master01 configmaps_and_secrets]#kubectl exec -it secrets-volume-demo -- /bin/sh / # netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN / # cd /etc/nginx/certs/ /etc/nginx/certs # ls tls.crt tls.key #和configmap的引用逻辑相同 /etc/nginx/certs # ls -al total 4 drwxrwxrwt 3 root root 120 Nov 23 12:42 . drwxr-xr-x 1 root root 4096 Nov 23 12:42 .. drwxr-xr-x 2 root root 80 Nov 23 12:42 ..2024_11_23_12_42_04.1485291318 lrwxrwxrwx 1 root root 32 Nov 23 12:42 ..data -> ..2024_11_23_12_42_04.1485291318 lrwxrwxrwx 1 root root 14 Nov 23 12:42 tls.crt -> ..data/tls.crt lrwxrwxrwx 1 root root 14 Nov 23 12:42 tls.key -> ..data/tls.key #配置信息发生改变,要确保应用程序能自动重载(逻辑同configmap)
4 DownwardAPI 和 Projected
DownwardAPI ◼ 与ConfigMap和Secret不同,DownwardAPI自身并非一种独立的API资源类型 ◼ DownwardAPI只是一种将Pod的metadata、spec或status中的字段值注入到其内部Container里的方式 DownwardAPI提供了两种方式用于将 POD 的信息注入到容器内部 ◼ 环境变量:用于单个变量,可以将 POD 信息和容器信息直接注入容器内部 #一般采用这种 ◼ Volume挂载:将 POD 信息生成为文件,直接挂载到容器内部中去
在容器上基于DownwardAPI引用Pod元数据,可通过两种字段完成
◼ fieldRef:引用常规的元数据
◼ resourceFieldRef:引用同资源限制和资源需求相关的元数据
fieldRef 如图
有关容器资源限制和资源需求的信息则要通过resourceFieldRef 字段注入
◼ 这些信息都能够基于环境变量和卷的方式注入到容器中
resourceFieldRef 如图
#通过环境变量示例 #下面的配置片断截取自由三个RabbitMQ Pod构建的RabbitMQ Cluster配置示例之上 containers: - name: rabbitmq image: registry.magedu.com/rabbitmq/rabbitmq:3.12-management ports: - containerPort: 15672 name: discovery - containerPort: 5672 name: amqp env: - name: RABBIT_POD_NAME valueFrom: fieldRef: apiVersion: v1 #pod api版本号 fieldPath: metadata.name - name: RABBIT_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: RABBITMQ_NODENAME #引用变量$()实现 value: rabbit@$(RABBIT_POD_NAME).rabbitmq.$(RABBIT_POD_NAMESPACE).svc.cluster.local - name: RABBITMQ_USE_LONGNAME value: "true" - name: RABBITMQ_CONFIG_FILE value: "/config/rabbitmq" - name: RABBITMQ_ERLANG_COOKIE valueFrom: secretKeyRef: name: rabbit-secret key: RABBITMQ_ERLANG_COOKIE - name: K8S_HOSTNAME_SUFFIX value: .rabbitmq.$(RABBIT_POD_NAMESPACE).svc.cluster.local #通过volume挂载示例(一般不这么用) 必须是上面支持使用卷引用的字段 ]#vim pod-use-downwardapi.yaml apiVersion: v1 kind: Pod metadata: labels: run: pod-use-downwardapi name: pod-use-downwardapi spec: containers: - image: ikubernetes/demoapp:v1.0 name: pod-use-downwardapi volumeMounts: - name: dapi mountPath: /dapi volumes: - name: dapi downwardAPI: items: - path: "name" #挂载路径下文件名 fieldRef: #引用字段名称 fieldPath: metadata.name #进入容器 ]#ls /dapi/ name ]#cat name pod-use-downwardapi
Projected Volume是一种特殊的卷类型,它能够将已存在的多个卷投射进同一个挂载点目录中 Projected Volume仅支持对如下四种类型的卷(数据源)进行投射操作,这类的卷一般都 是用于为容器提供预先定义好的数据 ◼ Secret:投射Secret 对象 ◼ ConfigMap:投射ConfigMap对象 ◼ DownwardAPI:投射Pod元数据 ◼ ServiceAccountToken:投射ServiceAccount Token #例 volumes: - name: local-storage persistentVolumeClaim: claimName: openebs-jiva-csi-pvc - name: kube-api-access-9s76w projected: defaultMode: 420 sources: #指定哪些卷投射进同一路径下 - serviceAccountToken: expirationSeconds: 3607 path: token - configMap: items: - key: ca.crt path: ca.crt name: kube-root-ca.crt - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.namespace #内容 path: namespace #以namespace文件名挂载 #示例: 修改pod文件 ]#vim pod-use-projected.yaml apiVersion: v1 kind: Pod metadata: labels: run: pod-use-projected name: pod-use-projected spec: containers: - image: ikubernetes/demoapp:v1.0 name: pod-use-downwardapi volumeMounts: - name: proj mountPath: /proj volumes: - name: proj projected: defaultMode: 0644 sources: - configMap: items: - key: myserver.conf path: my.conf #换个名字 name: nginx-config-files - secret: items: - key: tls.crt path: nginx.crt #换个名字 name: nginx-ssl-secret - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.namespace #内容 path: namespace #以namespace文件名挂载 ]#kubectl apply -f pod-use-projected.yaml #进入容器 ]#kubectl exec -it pod-use-projected -- /bin/sh ]#ls /proj ]#my.conf namespace nginx.crt
总结:
为Pod提供配置:
ConfigMap、Secret
API Server支持资源类型:需要先定义出资源对象,而后引用
downwardAPI
非为资源类型,可直接引用,因为它们自身即为Pod属性
fieldRef
resourceFieldRef
Projected
非为资源类型,但其要引用现有的configmap、secret资源对象,或downwardAPI中的元数据信息
投射进同一个挂载点