八、Kubernetes资源对象之数据卷存储

以Docker为代表的容器运行时通常都支持配置容器使用“存储卷”将数据持久存储于容器自身文件系统之外的存储空间中,这些存储空间可来自宿主机文件系统或网络存储系统。相应地,Kubernetes也支持类似的存储卷功能以实现短生命周期的容器应用数据的持久化,不过,其存储卷绑定于Pod对象而非容器级别,并可共享给内部的所有容器使用。

一、存储卷基础

Pod本身有生命周期,其应用容器及生成的数据自身均无法独立于该生命周期之外持久存在,并且同一Pod中的容器可共享PID、Network、IPC和UTS名称空间,但Mount和USER名称空间却各自独立,因而跨容器的进程彼此间默认无法基于共享的存储空间交换文件或数据。因而,借助特定的存储机制甚至是独立于Pod生命周期的存储设备完成数据持久化也是必然之需。

1、储存卷的概述

存储卷是定义在Pod资源之上可被其内部的所有容器挂载的共享目录,该目录关联至宿主机或某外部的存储设备之上的存储空间,可由Pod内的多个容器同时挂载使用。Pod存储卷独立于容器自身的文件系统,因而也独立于容器的生命周期,它存储的数据可于容器重启或重建后继续使用。如图展示了Pod容器与存储卷之间的关系。

 

每个工作节点基于本地内存或目录向Pod提供存储空间,也能够使用借助驱动程序挂载的网络文件系统或附加的块设备,例如使用挂载至本地某路径上的NFS文件系统等。Kubernetes系统具体支持的存储卷类型要取决于存储卷插件的内置定义,如图所示,不过Kubernetes也支持管理员基于扩展接口配置使用第三方存储。另外,Kubernetes甚至还支持一些有着特殊功用的存储卷,例如将外部信息投射至Pod之中的ConfigMap、Secret和Downward API等。

 

存储卷并非Kubernetes上一种独立的API资源类型,它隶属于Pod资源,且与所属的特定Pod对象有着相同的生命周期,因而通过API Server管理声明了存储卷资源的Pod对象时也会相应触发存储卷的管理操作。在具体的执行过程中,首选由调度器将该Pod对象绑到一个工作节点之上,若该Pod定义存储卷尚未被挂载,Controller Manager中的AD控制器(Attach/Detach Controller)会先借助相应的存储卷插件把远程的存储设备附加到该目标节点,而由内置在kubelet中的Pod管理器(Pod Manager)触发本地的存储卷操作实现,它借助存储卷管理器(Volume Manager)调用存储卷插件进行关联并驱动相应存储服务,并完成设备的挂载、格式化和卸载等操作。存储卷独立于Pod对象中容器的生命周期,从而使得容器重启或更新之后数据依然可用,但删除Pod对象时也必将删除其存储卷。 Kubernetes系统内置了多种类型的存储卷插件,因而能够直接支持多种类型存储系统(即存储服务方),例如CephFS、NFS、RBD、iscsi和vSphereVolume等。定义Pod资源时,用户可在其spec.volumes字段中嵌套配置选定的存储卷插件,并结合相应的存储服务来使用特定类型的存储卷,甚至使用CS或flexVolume存储卷插件来扩展支持更多的存储服务系统。 对Pod对象来说,卷类型主要是为关联适配的存储系统时提供相关的配置参数。例如,关联节点本地的存储目录与关联GlusterFS存储系统所需要的配置参数差异巨大,因此指定了存储卷类型也就限定了其关联到的后端存储设备。目前,Kubernetes支持的存储卷可简单归为以下类别,它们也各自有着不少的实现插件。

1)临时存储卷:emptyDir。

2)本地存储卷:hostPath和local。

3)网络存储卷:

▪云存储——awsElasticBlockStore、gcePersistentDisk、azureDisk和azureFile。
▪网络文件系统——NFS、GlusterFS、CephFS和Cinder。
▪网络块设备——iscsi、FC、RBD和vSphereVolume。
▪网络存储平台——Quobyte、PortworxVolume、StorageOS和ScaleIO。

4)特殊存储卷:Secret、ConfigMap、DownwardAPI和Projected。 5)扩展支持第三方存储的存储接口(Out-of-Tree卷插件):CSI和FlexVolume。

通常,这些Kubernetes内置提供的存储卷插件可归类为In-Tree类型,它们同Kubernetes源代码一同发布和迭代,而由存储服务商借助于CSI或FlexVolume接口扩展的独立于Kubernetes代码的存储卷插件则统称为Out-Of-Tree类型,集群管理员也可根据需要创建自定义的扩展插件,目前CSI是较为推荐的扩展接口,如图所示。

 

网络存储基本都具有持久存储能力,但它们都要求Pod资源清单的编写人员了解可用的真实网络存储的基础结构,并且能够准确配置用到的每一种存储服务。例如,要创建基于Ceph RBD的存储卷,用户必须要了解Ceph集群服务器(尤其是Monitor服务器)的地址,并且能够理解接入Ceph集群的必要配置及其意义。

2、配置Pod存储卷

在Pod中定义使用存储卷的配置由两部分组成:一部分通过.spec.volumes字段定义在Pod之上的存储卷列表,它经由特定的存储卷插件并结合特定的存储系统的访问接口进行定义;另一部分是嵌套定义在容器的volumeMounts字段上的存储卷挂载列表,它只能挂载当前Pod对象中定义的存储卷。不过,定义了存储卷的Pod内的容器也可以选择不挂载任何存储卷。

spec:
  volumes:
  - name: <string> #存数卷名称,仅可使用DNS标签格式的字符,在当前Pod中必须唯一
    VOL_TYPE <Object> #存储卷插件及具体的目标存储系统的相关配置
  containers:
  - name: ...
    image: ...
    volumeMount:
    - name: <string> #要挂载的存储卷的名称,必须匹配存储卷列表中某项的定义
      mountPath: <string> #容器文件系统跟上的挂载点路劲
      readOnly: <boolean> #是否挂载为只读模式,默认为""
      subPath: <string> #挂载存储卷上的一个子目录至指定的挂载点
      subPathExpr: <string> #挂载由指定模式匹配到的存储卷的文件或目录至挂载点
      mountPropagation: <string> #挂载卷的传播模式

Pod配置清单中的.spec.volumes字段的值是一个对象列表,每个列表项定义一个存储卷,它由存储卷名称(.spec.volumes.name <String>)和存储卷对象(.spec.volumes.VOL_TYPE <Object>)组成,其中VOL_TYPE是使用的存储卷类型名称,它的内嵌字段随类型的不同而不同,具体参数需要参阅Pod上各存储卷插件的相关文档说明。 定义好的存储卷可由当前Pod资源内的各容器进行挂载。Pod中仅有一个容器时,使用存储卷的目的通常在于数据持久化,以免重启时导致数据丢失,而只有多个容器挂载同一个存储卷时,“共享”才有了具体的意义。挂载卷的传播模式(mountPropagation)就是用于配置容器将其挂载卷上的数据变动传播给同一Pod中的其他容器,甚至是传播给同一个节点上的其他Pod的一个特性,该字段的可用值包括如下几项。

▪None:该挂载卷不支持传播机制,当前容器不向其他容器或Pod传播自己的挂载操作,也不会感知主机后续在该挂载卷或其任何子目录上执行的挂载变动;此为默认值。
▪HostToContainer:主机向容器的单向传播,即当前容器能感知主机后续对该挂载卷或其任何子目录上执行的挂载变动。
▪Bidirectional:主机和容器间的双向传播,当前容器创建的存储卷挂载操作会传播给主机及使用了同一存储卷的所有Pod的所有容器,也能感知主机上后续对该挂载卷或其任何子目录上执行的挂载变动;该行为存在破坏主机操作系统的危险,因而仅可用于特权模式下的容器中。

除了配置参数的不同,各类型存储卷的大体使用格式基本相似。

二、临时存储卷

emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此通常仅用于数据缓存或临时存储。不过,基于emptyDir构建的gitRepo存储卷可以在Pod对象的生命周期起始时,从相应的Git仓库中克隆相应的数据文件到底层的emptyDir中,也就使得它具有了一定意义上的持久性。

1、emptyDir存储卷

emptyDir存储卷可以理解为Pod对象上的一个临时目录,类似于Docker上的“Docker挂载卷”,在Pod对象启动时即被创建,而在Pod对象被移除时一并被删除。因此,emptyDir存储卷只能用于某些特殊场景中,例如同一Pod内的多个容器间的文件共享,或作为容器数据的临时存储目录用于数据缓存系统等。 emptyDir存储卷嵌套定义在.spec.volumes.emptyDir字段中,可用字段主要有两个。

▪medium:此目录所在的存储介质的类型,可用值为default或Memory,默认为default,表示使用节点的默认存储介质;Memory表示使用基于RAM的临时文件系统tmpfs,总体可用空间受限于内存,但性能非常好,通常用于为容器中的应用提供缓存存储。
▪sizeLimit:当前存储卷的空间限额,默认值为nil,表示不限制;不过,在medium字段值为Memory时,建议务必定义此限额。

下面是一个使用了emptyDir存储卷的简单示例,它保存在volumes-emptydir-demo.yaml配置文件中。

apiVersion: v1
kind: Pod
metadata:
    name: volumes-emptydir-demo
    namespace: default
spec:
  initContainers:
  - name: config-file-downloader
    #image: ikubernetes/admin-box
    image: harbor.ywx.net/k8s-baseimages/admin-box
    imagePullPolicy: IfNotPresent
    command: ['/bin/sh','-c','wget -O /data/envoy.yaml http://ilinux.io/envoy.yaml']
    volumeMounts:
    - name: config-file-store1
      mountPath: /data
  containers:
  - name: envoy
    #image: envoyproxy/envoy-alpine:v1.14.1
    image:  harbor.ywx.net/k8s-baseimages/envoy-alpine:v1.14.1
    imagePullPolicy: IfNotPresent
    command: ['/bin/sh','-c']
    args: ['envoy -c /etc/envoy/envoy.yaml']
    volumeMounts:
    - name: config-file-store
      mountPath: /etc/envoy
      readOnly: true
    - name: config-file-store2
      mountPath: /app/data
  volumes:
  #定义config-file-store1使用内存作为存储
  - name: config-file-store
    emptyDir:
      medium: Memory
      sizeLimit: 16Mi
  #定义config-file-store2使用本地节点作为存储
  - name: config-file-store2
    emptyDir: {}

在该示例清单中,为Pod对象定义了一个名为config-file-store的、基于emptyDir存储插件的存储卷。初始化容器将该存储卷挂载至/data目录后,下载envoy.yaml配置文件并保存于该挂载点目录下。主容器将该存储卷挂载至/etc/envoy目录,再通过自定义命令让容器应用在启动时加载的配置文件/etc/envoy/envoy.yaml上,如图所示。

 

Pod资源的详细信息中会显示存储卷的相关状态,包括其是否创建成功(Events字段中输出)、相关的类型及参数(Volumes字段中输出),以及容器中的挂载状态等信息(Containers字段中输出)。如下面的命令结果所示。

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-emptydir-demo.yaml 
pod/volumes-emptydir-demo created


root@k8s-master01:~# kubectl describe pod volumes-emptydir-demo 
Name:         volumes-emptydir-demo
Namespace:    default
Priority:     0
Node:         172.168.33.211/172.168.33.211
Start Time:   Sun, 03 Oct 2021 15:20:24 +0800
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           172.20.58.214
IPs:
  IP:  172.20.58.214
#Init Containers执行的下载文件任务
Init Containers:
  config-file-downloader:
    Container ID:  docker://faab615972f8b62758ea37e2d8c52f58362297eb695843664401c591348a05f8
    Image:         harbor.ywx.net/k8s-baseimages/admin-box
    Image ID:      docker-pullable://harbor.ywx.net/k8s-baseimages/admin-box@sha256:56e2413d2bcc279c6667d36fa7dfc7202062a933d0f69cd8a1769b68e2155bbf
    Port:          <none>
    Host Port:     <none>
    #init containers下载envoy.yaml文件并存放在/data中
    Command:
      /bin/sh
      -c
      wget -O /data/envoy.yaml http://ilinux.io/envoy.yaml
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Sun, 03 Oct 2021 15:20:24 +0800
      Finished:     Sun, 03 Oct 2021 15:20:25 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      #把config-file-store目录挂载init containers的/data目录中用于保存下载的envoy.yaml文件
      #envoy.yaml文件实际是存放在config-file-store目录中,便于主容器到config-file-store中调用envoy.yaml文件
      /data from config-file-store (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-d2zdv (ro)
#主容器
Containers:
  envoy:
    Container ID:  docker://572830ca035b817229d690ff3fe778da0110846a559a140cd47dc37a27f8c98a
    Image:         harbor.ywx.net/k8s-baseimages/envoy-alpine:v1.14.1
    Image ID:      docker-pullable://harbor.ywx.net/k8s-baseimages/envoy-alpine@sha256:3a7001058482f86ad4069017d96435a80540f4a0534b8dbb045f1870b7e2eab6
    Port:          <none>
    Host Port:     <none>
    Command:
      /bin/sh
      -c
    Args:
      envoy -c /etc/envoy/envoy.yaml
    State:          Running
      Started:      Sun, 03 Oct 2021 15:20:26 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      #把config-file-store2目录挂载到主容器的/app/data目录
      /app/data from config-file-store2 (rw)
      #读取/etc/envoy文件,该文件是init containers时下载到config-file-store目录
      /etc/envoy from config-file-store (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-d2zdv (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  config-file-store:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  16Mi
  config-file-store2:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     
    SizeLimit:  <unset>
  kube-api-access-d2zdv:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  21s   default-scheduler  Successfully assigned default/volumes-emptydir-demo to 172.168.33.211
  Normal  Pulled     21s   kubelet            Container image "harbor.ywx.net/k8s-baseimages/admin-box" already present on machine
  Normal  Created    21s   kubelet            Created container config-file-downloader
  Normal  Started    21s   kubelet            Started container config-file-downloader
  Normal  Pulled     20s   kubelet            Container image "harbor.ywx.net/k8s-baseimages/envoy-alpine:v1.14.1" already present on machine
  Normal  Created    20s   kubelet            Created container envoy
  Normal  Started    19s   kubelet            Started container envoy

为Envoy下载的配置文件中定义了一个监听所有可用IP地址上TCP 80端口的Ingress侦听器,以及一个监听所有可用IP地址上TCP的9901端口的Admin接口,这与Envoy镜像中默认配置文件中的定义均有不同。下面命令的结果显示它吻合自定义配置文件的内容。

root@k8s-master01:~# kubectl exec volumes-emptydir-demo -- netstat -tnl
Defaulted container "envoy" out of: envoy, config-file-downloader (init)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:9901            0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN    

root@k8s-master01:~# podIP=$(kubectl get pods volumes-emptydir-demo -o jsonpath={.status.podIP})
root@k8s-master01:~# curl $podIP:9901/listeners
listener_0::0.0.0.0:80

另外还直接在挂载了一个config-file-store2在主容器的/app/data中

root@k8s-master01:~# kubectl exec -it volumes-emptydir-demo -- /bin/sh
Defaulted container "envoy" out of: envoy, config-file-downloader (init)
/ # echo 123 > /app/data/ywx.log
/ # ls /app/data/ywx.log 
/app/data/ywx.log
/ # 

root@k8s-master01:~# kubectl get pod -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP              NODE             NOMINATED NODE   READINESS GATES
volumes-emptydir-demo   1/1     Running   0          15m   172.20.58.214   172.168.33.211   <none>           <none>

#在k8s-node02的宿主机上查找
root@k8s-node02:~# find /var/lib/kubelet/pods -name ywx.log
/var/lib/kubelet/pods/2d7ac625-f69d-464b-9eb0-e1a23eba5ec1/volumes/kubernetes.io~empty-dir/config-file-store2/ywx.log

root@k8s-node02:~# cat /var/lib/kubelet/pods/2d7ac625-f69d-464b-9eb0-e1a23eba5ec1/volumes/kubernetes.io~empty-dir/config-file-store2/ywx.log
123

#为了更好的验证,我们宿主机中创建一个kaka.log,在去容器中查看
root@k8s-node02:~# echo 456 >> /var/lib/kubelet/pods/2d7ac625-f69d-464b-9eb0-e1a23eba5ec1/volumes/kubernetes.io~empty-dir/config-file-store2/kaka.log

#在容器中查看
root@k8s-master01:~# kubectl exec -it volumes-emptydir-demo -- ls /apps/data
Defaulted container "envoy" out of: envoy, config-file-downloader (init)
ls: /apps/data: No such file or directory
command terminated with exit code 1
root@k8s-master01:~# kubectl exec -it volumes-emptydir-demo -- /bin/sh
Defaulted container "envoy" out of: envoy, config-file-downloader (init)
/ # ls /app/data/
kaka.log  ywx.log
/ # cat /app/data/kaka.log 
456
/ # 

emptyDir卷简单易用,但仅能用于临时存储。如果Pod被删除,emptyDir卷会被一起删除,数据会丢失。

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl delete -f volumes-emptydir-demo.yaml 
pod "volumes-emptydir-demo" deleted

#在k8s-node02上确认
root@k8s-node02:~# find /var/lib/kubelet/pods -name ywx.log
root@k8s-node02:~# 

2、gitRepo存储卷

gitRepo存储卷可以看作是emptyDir存储卷的一种实际应用,使用该存储卷的Pod资源可以通过挂载目录访问指定的代码仓库中的数据。使用gitRepo存储卷的Pod资源在创建时,会首先创建一个空目录(emptyDir)并克隆(clone)一份指定的Git仓库中的数据至该目录,而后再创建容器并挂载该存储卷。 定义gitRepo类型的存储卷时,其可嵌套使用字段有如下3个。

repository <string>:Git仓库的URL,必选字段。
▪directory <string>:目标目录名称,但名称中不能包含“..”字符;“.”表示将仓库中的数据直接克隆至存储卷映射的目录中,其他字符则表示将数据克隆至存储卷上以用户指定的字符串为名称的子目录中。
▪revision <string>:特定revision的提交哈希码。

使用gitRepo存储卷的Pod资源运行的工作节点上必须安装有Git程序,否则克隆仓库的操作将无法完成。

下面的配置清单示例(volumes-gitrepo-demo.yaml)中的Pod资源在创建时,会先创建一个空目录,将指定的Git仓库https://github.com/iKubernetes/Kubernetes_Advanced_Practical_2rd.git中的数据克隆一份直接保存在此目录中,而后将此目录创建为存储卷html,再由容器nginx将此存储卷挂载到/usr/share/nginx/html目录上。

apiVersion: v1
kind: Pod
metadata:
  name: volumes-gitrepo-demo
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.16.0
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: nginx-html
      mountPath: /usr/share/nginx/html
  volumes:
  - name: nginx-html
    gitRepo:
      repository: https://github.com/iKubernetes/Kubernetes_Advanced_Practical_2rd.git
      directory: .
      revision: "master"
      
 #注意:nginx:1.16.0镜像中需要安装git命令

访问此Pod资源中的nginx服务,即可看到它来自Git仓库中的页面资源。不过,gitRepo存储卷在其创建完成后不会再与指定的仓库执行同步操作,这意味着在Pod资源运行期间,如果仓库中的数据发生了变化,gitRepo存储卷不会同步到这些内容。当然,此时可以为Pod资源创建一个Sidecar容器来执行此类的同步操作,尤其是数据来源于私有仓库时,通过Sidecar容器完成认证等必要步骤后再进行克隆操作就更为必要。 gitrRepo存储卷构建于emptyDir之上,其生命周期与Pod资源一样,故使用中不应在此类存储卷中保存由容器生成的重要数据。另外,gitRepo存储插件即将废弃,建议在初始化容器或Sidecar容器中运行git命令来完成相应的功能。

三、hostPath存储卷

hostPath存储卷插件是将工作节点上某文件系统的目录或文件关联到Pod上的一种存储卷类型,其数据具有同工作节点生命周期一样的持久性。hostPath存储卷使用的是工作节点本地的存储空间,所以仅适用于特定情况下的存储卷使用需求,例如将工作节点上的文件系统关联为Pod的存储卷,从而让容器访问节点文件系统上的数据,或者排布分布式存储系统的存储设备等。hostPath存储卷在运行有管理任务的系统级Pod资源,以及Pod资源需要访问节点上的文件时尤为有用。 配置hostPath存储卷的嵌套字段有两个:一个用于指定工作节点上的目录路径的必选字段path;另一个用于指定节点之上存储类型的type。hostPath支持使用的节点存储类型有如下几种。

▪DirectoryOrCreate:指定的路径不存在时,自动将其创建为0755权限的空目录,属主和属组均为kubelet。
▪Directory:事先必须存在的目录路径。
▪FileOrCreate:指定的路径不存在时,自动将其创建为0644权限的空文件,属主和属组均为kubelet。
▪File:事先必须存在的文件路径。
▪Socket:事先必须存在的Socket文件路径。
▪CharDevice:事先必须存在的字符设备文件路径。
▪BlockDevice:事先必须存在的块设备文件路径。
▪"":空字符串,默认配置,在关联hostPath存储卷之前不进行任何检查。

这类Pod对象通常受控于DaemonSet类型的Pod控制器,它运行在集群中的每个工作节点上,负责收集工作节点上系统级的相关数据,因此使用hostPath存储卷也理所应当。然而,基于同一个模板创建Pod对象仍可能会因节点上文件的不同而存在着不同的行为,而且在节点上创建的文件或目录默认仅root用户可写,若期望容器内的进程拥有写权限,则需要将该容器运行于特权模式,不过这存在潜在的安全风险。

下面是定义在配置清单volumes-hostpath-demo.yaml中的Pod对象,容器中的filebeat进程负责收集工作节点及容器相关的日志信息并发往Redis服务器,它使用了3个hostPath类型的存储卷,第一个指向了宿主机的日志文件目录/var/logs,后面两个则与宿主机上的Docker运行时环境有关。

apiVersion: v1
kind: Pod
metadata:
  name: vol-hostpath-pod
  namespace: default
spec:
  containers:
  - name: filebeat
    image: ikubernetes/filebeat:5.6.7-alpine
    imagePullPolicy: IfNotPresent
    env:
    - name: REDIS_HOST
      value: redis.ilinux.io:6379
    - name: LOG_LEVEL
      value: info
    volumeMounts:
      - name: varlog
        mountPath: /var/log
      - name: socket
        mountPath: /var/run/docker.sock
      - name: varlibdockercontainers
        mountPath: /var/lib/docker/containers
        readOnly: true
  terminationGracePeriodSeconds: 30
  volumes:
  - name: varlog
    hostPath:
      path: /var/log
  - name: varlibdockercontainers
    hostPath:
      path: /var/lib/docker/containers
  - name: socket
    hostPath:
      path: /var/run/docker.sock

上面配置清单中Pod对象的正确运行要依赖于REDIS_HOST和LOG_LEVEL环境变量,它们分别用于定义日志缓冲队列服务和日志级别。如果有可用的Redis服务器,我们就可通过环境变量REDIS_HOST将其对应的主机名或IP地址传递给Pod对象,待Pod对象准备好之后即可通过Redis服务器查看到由该Pod发送的日志信息。测试时,我们仅需要给REDIS_HOST环境变量传递一个任意值(例如清单中的redis.ilinux.io)便可直接创建Pod对象,只不过该Pod中容器的日志会报出无法解析指定主机名的错误,但这并不影响存储卷的配置和使用。

提示
在Filebeat的应用架构中,这些日志信息可能会由Logstash收集后发往集中式日志存储系统Elasticsearch,并通过Kibana进行展示。

对于由Deployment或StatefulSet等一类控制器管控的、使用了hostPath存储卷的Pod对象来说,需要注意在基于资源可用状态的调度器调度Pod对象时,并不支持参考目标节点之上hostPath类型的存储卷,在Pod对象被重新调度至其他节点时,容器进程此前创建的文件或目录则大多不会存在。一个常用的解决办法是通过在Pod对象上使用nodeSelector或者nodeAffinity赋予该Pod对象指定要绑定到的具体节点来影响调度器的决策,但即便如此,管理员仍然不得不手动管理涉及的多个节点之上的目录,低效且易错。因此,hostPath存储卷虽然能持久保存数据,但对于由调度器按需调度的应用来说并不适用。

一个简单的hostpath案例vol-hostpath-html.yaml

apiVersion: v1
kind: Pod
metadata:
  name: vol-hostpath-html
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.16.0
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - name: vol-html
        mountPath: /usr/share/nginx/html     
  volumes:
  - name: vol-html
    hostPath:
      path: /data/html

执行清单

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f vol-hostpath-html.yaml 
pod/vol-hostpath-html created

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP              NODE             NOMINATED NODE   READINESS GATES
vol-hostpath-html   1/1     Running   0          38s   172.20.58.215   172.168.33.211   <none>           <none>

#在pod在k8s-node02上创建,在k8s-node02的/data/html目录创建一个index.html文件
root@k8s-node02:~# echo vol-hostpaht-html > /data/html/index.html
#访问pod
root@k8s-master01:/apps/k8s-yaml/volume-case# curl 172.20.58.215
vol-hostpaht-html

四、网络存储卷

Kubernetes内置了多种类型的网络存储卷插件,它们支持的存储服务包括传统的NAS或SAN设备(例如NFS、iscsi和FC等)、分布式存储(例如GlusterFS、CephFS和RBD等)、云存储(例如gcePersistentDisk、azureDisk、Cinder和awsElasticBlockStore等)以及构建在各类存储系统之上的抽象管理层(例如flocker、portworxVolume和vSphereVolume等)。这类服务通常都是独立运行的存储系统,因相应的存储卷可以支持超越节点生命周期的数据持久性。

1、NFS存储卷

NFS即网络文件系统(Network File System),它是一种分布式文件系统协议,最初是由Sun Microsystems公司开发的类UNIX操作系统之上的经典网络存储方案,其功能旨在允许客户端主机可以像访问本地存储一样通过网络访问服务器端文件。作为一种由内核原生支持的网络文件系统,具有Linux系统使用经验的读者多数都应该有NFS使用经验。 Kubernetes的NFS存储卷用于关联某事先存在的NFS服务器上导出的存储空间到Pod对象中以供容器使用,该类型的存储卷在Pod对象终止后仅是被卸载而非被删除。而且,NFS是文件系统级共享服务,它支持同时存在的多路挂载请求,可由多个Pod对象同时关联使用。定义NFS存储卷时支持嵌套使用以下几个字段。

server <string>:NFS服务器的IP地址或主机名,必选字段。
▪path <string>:NFS服务器导出(共享)的文件系统路径,必选字段。
▪readOnly <boolean>:是否以只读方式挂载,默认为false。

下面的配置清单示例中以Redis为例来说明NFS存储卷的功能与用法。Redis是一个著名的高性能键值存储系统,应用非常广泛。它基于内存存储运行,数据持久化存储的需求通过周期性地将数据同步到主机磁盘之上完成,因此将Redis抽象为Pod对象部署运行于Kubernetes系统之上时,需要考虑节点级或网络级的持久化存储卷的支持,本示例就是以NFS存储卷为例,为Redis进程提供跨Pod对象生命周期的数据持久化功能。

apiVersion: v1
kind: Pod
metadata:
  name: volume-nfs-demo
  labels:
    app: redis
spec:
  containers:
  - name: redis
    image: redis:alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 6379
      name: redisport
    securityContext:
      runAsUser: 1000
    volumeMounts:
    - mountPath: /data
      name: redisdata
  volumes:
  - name: redisdata
    nfs: #NFS存储卷插件
      server: 172.168.33.201  #nfs服务器的地址
      path: /data/redis_data  #nfs共享目录
      readOnly: false  #是否以只读方式挂载,默认为false。

上面的示例定义在名为volumes-nfs-demo.yaml资源清单文件中,容器镜像文件redis:alpine默认会以redis用户(UID是1000)运行redis-server进程,并将数据持久保存在容器文件系统上的/data目录中,因而需要确保UID为1000的用户有权限读写该目录。与此对应,NFS服务器上用于该Pod对象的存储卷的导出目录(本示例中为/data/redis目录)也需要确保让UID为1000的用户拥有读写权限,因而需要在172.168.33.201服务器上创建该用户,将该用户设置为/data/redis目录的属主,或通过facl设置该用户拥有读写权限。

以ubuntu20.04为例,在一个专用的主机(172.168.33.201)上以root用户设定所需的NFS服务器的步骤如下。

1)安装NFS Server程序包,Ubuntu 20.04上的程序包名为nfs-kernel-server
~#apt install nfs-kernel-server
2)设定基础环境,包括用户、数据目录及相应授权。
~# mkdir /data/redis_data
~# groupadd -r -g 1000 redis && useradd -r -m -s /bin/bash -u 1000 -g 1000 redis
~# chown redis.redis /data/redis_data
3)编辑/etc/exports配置文件,填入类似如下内容:
/data/redis_data     *(rw,no_root_squash) 
4)启动NFS服务器:
~# systemctl start nfs-server
5)在各工作节点安装NFS服务客户端程序包,ubuntu20.04上的程序包名为nfs-common。
~# apt install -y nfs-common

待上述步骤执行完成后,切换回Kubernetes集群可运行kubectl命令的主机之上,运行命令创建配置清单中的Pod对象:

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volume-nfs-demo.yaml 
pod/volume-nfs-demo created

资源创建完成后,可通过其命令客户端redis-cli创建测试数据,并手动触发其与存储系统同步,下面为要执行的Redis命令。

root@k8s-master01:~# kubectl get pod -o wide
NAME              READY   STATUS    RESTARTS   AGE     IP               NODE             NOMINATED NODE   READINESS GATES
volume-nfs-demo   1/1     Running   0          4m13s   172.20.135.175   172.168.33.212   <none>           <none>

[root@k8s-master01:~#  kubectl exec -it volume-nfs-demo -- redis-cli
127.0.0.1:6379> set key1 "hello world"
OK
127.0.0.1:6379> get key1
"hello world"
127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> exit
root@k8s-master01:~# 

为了测试其数据持久化效果,下面先删除此前创建的Pod对象volume-nfs-demo.yaml ,而后待重建该Pod对象后检测数据是否依然能够访问。

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl delete -f volume-nfs-demo.yaml 
pod "volume-nfs-demo" deleted

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod -n default
No resources found in default namespace.

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volume-nfs-demo.yaml 
pod/volume-nfs-demo created

root@k8s-master01:~# kubectl get pod -n default -o wide
NAME              READY   STATUS    RESTARTS   AGE   IP              NODE             NOMINATED NODE   READINESS GATES
volume-nfs-demo   1/1     Running   0          30s   172.20.58.216   172.168.33.211   <none>           <none>

待其重建完成后,通过再次创建的Pod资源的详细描述信息可以观察到它挂载使用NFS存储卷的相关状态,也可通过下面的命令来检查redis-server中是否还保存有此前存储的数据。

root@k8s-master01:~#  kubectl exec -it volume-nfs-demo -- redis-cli
127.0.0.1:6379> get key1
"hello world"
127.0.0.1:6379>

上面的命令结果显示出此前创建的键mykey及其数据在Pod对象删除并重建后依然存在,这表明删除Pod对象后,其关联的外部存储设备及数据并不会被一同删除,因而才具有了跨Pod生命周期的数据持久性。若需要在删除Pod后清除具有持久存储功能的存储设备上的数据,则需要用户或管理员通过存储系统的管理接口手动进行。

2、RBD存储卷

ceph集群的安装部署

https://www.cnblogs.com/yaokaka/p/15156785.html

Ceph是一个专注于分布式的、弹性可扩展的、高可靠的、性能优异的存储系统平台,同时支持提供块设备、文件系统和对象存储3种存储接口。它是个高度可配置的系统,并提供了一个命令行界面用于监视和控制其存储集群。Kubernetes支持通过RBD卷插件和CephFS卷插件,基于Ceph存储系统为Pod提供存储卷。要配置Pod对象使用RBD存储卷,需要事先满足以下前提条件。

▪存在某可用的Ceph RBD存储集群,否则需要创建一个。
▪在Ceph RBD集群中创建一个能满足Pod资源数据存储需要的存储映像。
▪在Kubernetes集群内的各节点上安装Ceph客户端程序包(ceph-common)。

定义RBD类型的存储卷时需要指定要连接的目标服务器和认证信息等配置,它们依赖如下几个可用的嵌套字段。

▪monitors <[]string>:Ceph存储监视器,逗号分隔的字符串列表;必选字段。
▪image <string>:rados image(映像)的名称,必选字段。
▪pool <string>:Ceph存储池名称,默认为rbd。
▪user <string>:Ceph用户名,默认为admin。
▪keyring <string>:用户认证到Ceph集群时使用的keyring文件路径,默认为/etc/ceph/keyring。
▪secretRef <Object>:用户认证到Ceph集群时使用的保存有相应认证信息的Secret资源对象,该字段会覆盖由keyring字段提供的密钥信息。
▪readOnly <boolean>:是否以只读方式访问。
▪fsType:要挂载的存储卷的文件系统类型,至少应该是节点操作系统支持的文件系统,例如Ext4、xfs、NTFS等,默认为Ext4。

2.1 使用keyring文件来挂载rbd

下面提供的RBD存储卷插件使用示例定义在volumes-rbd-demo.yaml配置清单文件中,它使用admin(ceph默认账户)用户认证到Ceph集群中,并关联RDB存储池k8spool中的存储映像k8srbd为Pod对象volumes-rbd-demo的存储卷,由容器进程挂载至/data目录进行数据存取。

apiVersion: v1
kind: Pod
metadata:
  name: volumes-rbd-demo
spec:
  containers:
  - name: redis
    image: redis:alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redis-rbd-vol
  volumes:
    - name: redis-rbd-vol
      rbd:
        monitors:
        - '172.168.32.201:6789,172.168.32.202:6789,172.168.32.201:6789'  #ceph-mon地址:6789,多mon之间用逗号隔开
        pool: k8spool #ceph的存储池
        image: k8srbd #rados image(映射)名称
        fsType: xfs #挂载卷的类型
        readOnly: false #是否为只读
        user: admin #ceph的用户名
        #user: k8s #使用k8s用户来挂载,需要在ceph中创建k8s用户并授权
        keyring: /etc/ceph/ceph.client.admin.keyring #用户认证到Ceph集群时使用的keyring文件路径
        #keyring: /etc/ceph/ceph.client.k8s.keyring #k8s的keyring

RBD存储卷插件依赖Ceph存储集群作为存储系统,这里假设其监视器(MON)的地址为172.168.32.201,172.168.32.202,172.168.32.203,集群上的存储池k8spool中需要有事先创建好的存储映像k8srbd。客户端访问集群时要事先认证到Ceph集群并获得相应授权才能进行后续的访问操作,该示例使用了用户的keyring文件。该示例实现的逻辑架构如图所示。

 

为了完成示例中定义的资源的测试,需要事先完成如下几个步骤。

1)在Ceph集群上的k8spool存储池
root@ceph01:~# ceph osd pool create k8spool 128 128
pool 'k8spool' created

root@ceph01:~# ceph osd pool ls
device_health_metrics
k8spool

2)给创建的pool开启rbd功能
root@ceph01:~# ceph osd pool application enable k8spool rbd
enabled application 'rbd' on pool 'k8spool'

3)在k8spool中创建大小为1G的RBD映像文件
#因为挂载rbd的宿主机因内核版本原因不支持rbd的所有特性,只开启部分特性
root@ceph01:~# rbd create k8srbd --size 1G --pool k8spool --image-format 2 --image-feature layering
#开启全部特性。注意挂载宿主机的内核版本是否支持rbd的特性功能
#root@ceph01:~# rbd create k8srbd --size 1G --pool k8spool
#查看pool中所存在的img
root@ceph01:~# rbd ls --pool k8spool
k8srbd

4)在Kubernetes集群的所有节点安装ceph客户端(ceph-common)
①把ceph-01上的ceph.repo拷贝到Kubernetes的所有node节点
#master节点
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.207:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.208:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.209:/etc/apt/sources.list
#node节点
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.210:/etc/apt/sources.list 
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.211:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.212:/etc/apt/sources.list
②kubernetes各个node节点安装ceph-common客户端
root@k8s-master01:~# apt update && apt install -y ceph-common
root@k8s-master02:~# apt update && apt install -y ceph-common
root@k8s-master03:~# apt update && apt install -y ceph-common
root@k8s-node01:~# apt update && apt install -y ceph-common
root@k8s-node02:~# apt update && apt install -y ceph-common
root@k8s-node03:~# apt update && apt install -y ceph-common

5)把ceph中admin的账号密码文件和ceph.conf拷贝到kubernetes的所有node节点上
kubernetes所有的node节点
root@k8s-master01:~# mkdir /etc/ceph
root@k8s-master02:~# mkdir /etc/ceph
root@k8s-master03:~# mkdir /etc/ceph
root@k8s-node01:~# mkdir /etc/ceph
root@k8s-node02:~# mkdir /etc/ceph
root@k8s-node03:~# mkdir /etc/ceph

root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.207:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.208:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.209:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.210:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.211:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.212:/etc/ceph

6)在k8s节点上可以查询ceph集群的状态
root@k8s-master01:~# ceph -s
  cluster:
    id:     b6a53e40-2661-4754-8684-7fccb328c274
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum ceph01,ceph02,ceph03 (age 8d)
    mgr: ceph01(active, since 8d), standbys: ceph02, ceph03
    osd: 6 osds: 6 up (since 8d), 6 in (since 8d)
 
  data:
    pools:   2 pools, 640 pgs
    objects: 5 objects, 34 B
    usage:   70 MiB used, 120 GiB / 120 GiB avail
    pgs:     640 active+clean

使用普通的k8s用户来挂载

#如果使用普通用户挂载rbd
#1)给pool=k8spool创建一个client的k8s用户,对mon有r权限,对k8spool地址池有rwx权限
root@ceph01:~# ceph auth add client.k8s mon 'allow r' osd 'allow rwx pool=k8spool'
added key for client.k8s

root@ceph01:~# ceph auth get client.k8s
[client.k8s]
    key = AQAHWGRh/d+JNxAAHrzplwi8WWjwXc/NGdJeAw==
    caps mon = "allow r"
    caps osd = "allow rwx pool=k8spool"
exported keyring for client.k8s

#2)创建一个名为ceph.client.k8s.keyring的空的密钥环文件
root@ceph01:~# ceph-authtool --create-keyring ceph.client.k8s.keyring
creating ceph.client.k8s.keyring

#3)导出client.k8s信息之指定的keyring文件
root@ceph01:~# ceph auth get client.k8s -o ceph.client.k8s.keyring 
exported keyring for client.k8s

root@ceph01:~# cat ceph.client.k8s.keyring 
[client.k8s]
    key = AQAHWGRh/d+JNxAAHrzplwi8WWjwXc/NGdJeAw==
    caps mon = "allow r"
    caps osd = "allow rwx pool=k8spool"

#4)把ceph.client.k8s.keyring 拷贝到所有的k8s节点
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.207:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.208:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.209:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.210:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.211:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/ceph.client.k8s.keyring 172.168.33.212:/etc/ceph

#5)在k8s上使用k8s用户可以查询ceph集群的状态
root@k8s-master01:~# ceph -s --user k8s
  cluster:
    id:     b6a53e40-2661-4754-8684-7fccb328c274
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum ceph01,ceph02,ceph03 (age 8d)
    mgr: ceph01(active, since 8d), standbys: ceph02, ceph03
    osd: 6 osds: 6 up (since 8d), 6 in (since 8d)
 
  data:
    pools:   2 pools, 640 pgs
    objects: 5 objects, 34 B
    usage:   70 MiB used, 120 GiB / 120 GiB avail
    pgs:     640 active+clean

待完成如上必要的准备步骤后,便可执行如下命令将前面定义在volumes-rbd-demo.yaml中的Pod资源创建在Kubernetes集群上进行测试。

使用ceph的admin账户来挂载

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-rbd.yaml 
pod/volumes-rbd-demo created

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod  -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP               NODE             NOMINATED NODE   READINESS GATES
volumes-rbd-demo   1/1     Running   0          2m32s   172.20.135.176   172.168.33.212   <none>           <none>

随后从集群上的Pod对象volumes-rbd-demo的详细描述中获取存储的相关状态信息,确保其创建操作得以成功执行。下面是相关的存储卷信息示例。

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl describe pod volumes-rbd-demo -n default
......
VVolumes:
  redis-rbd-vol:
    Type:          RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
    CephMonitors:  [172.168.32.201:6789,172.168.32.202:6789,172.168.32.201:6789]
    RBDImage:      k8srbd
    FSType:        xfs
    RBDPool:       k8spool
    RadosUser:     admin
    Keyring:       /etc/ceph/ceph.client.admin.keyring
    SecretRef:     nil
    ReadOnly:      false
......

使用ceph的k8s普通账户来挂载

1)删除pod
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl delete -f volumes-rbd.yaml 
pod "volumes-rbd-demo" deleted

2)修改volumes-rbd.yaml 文件,替换admin用户及keyring
cat > volumes-rbd.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: volumes-rbd-demo
spec:
  containers:
  - name: redis
    image: redis:alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redis-rbd-vol
  volumes:
    - name: redis-rbd-vol
      rbd:
        monitors:
        - '172.168.32.201:6789,172.168.32.202:6789,172.168.32.201:6789'  #ceph-mon地址:6789,多mon之间用逗号隔开
        pool: k8spool #ceph的存储池
        image: k8srbd #rados image(映射)名称
        fsType: xfs #挂载卷的类型
        readOnly: false #是否为只读
        #user: admin #ceph的用户名
        user: k8s #使用k8s用户来挂载,需要在ceph中创建k8s用户并授权
        #keyring: /etc/ceph/ceph.client.admin.keyring #用户认证到Ceph集群时使用的keyring文件路径
        keyring: /etc/ceph/ceph.client.k8s.keyring #k8s的keyring
EOF

3)再次运行volumes-rbd.yaml 清单
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-rbd.yaml 
pod/volumes-rbd-demo created

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod -n default 
NAME               READY   STATUS    RESTARTS   AGE
volumes-rbd-demo   1/1     Running   0          29s

4)查看pod中的volume信息
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl describe pod volumes-rbd-demo
......
Volumes:
  redis-rbd-vol:
    Type:          RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
    CephMonitors:  [172.168.32.201:6789,172.168.32.202:6789,172.168.32.201:6789]
    RBDImage:      k8srbd
    FSType:        xfs
    RBDPool:       k8spool
    RadosUser:     k8s
    Keyring:       /etc/ceph/ceph.client.k8s.keyring
    SecretRef:     nil
    ReadOnly:      false
......

删除Pod对象仅会解除它对RBD映像的引用而非级联删除它,因而RBD映像及数据将依然存在,除非管理员手动进行删除。我们可使用类似前一节测试Redis数据持久性的方式来测试本示例中的容器数据的持久能力。

2.2 使用secretRef字段来挂载rbd

应该把认证到Ceph集群上的用户的认证信息存储为Kubernetes集群上的Secret资源,并通过secretRef字段进行指定,而非直接使用keyring字段引用相应用户的keyring文件。

使用secretRef的方式来挂载,则不需要keyring文件

使用clinet.k8s用户来实验该方法

1)删除pod
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl delete -f volumes-rbd.yaml 
pod "volumes-rbd-demo" deleted

2)在ceph上获取client.ks8的key并进行64位编码
#获取client.k8s的key值
root@ceph01:~# ceph auth get client.k8s
[client.k8s]
    key = AQAHWGRh/d+JNxAAHrzplwi8WWjwXc/NGdJeAw==
    caps mon = "allow r"
    caps osd = "allow rwx pool=k8spool"
exported keyring for client.k8s
#对key值做base64编码
root@ceph01:~# echo AQAHWGRh/d+JNxAAHrzplwi8WWjwXc/NGdJeAw== | base64
QVFBSFdHUmgvZCtKTnhBQUhyenBsd2k4V1dqd1hjL05HZEplQXc9PQo=

3)创建client-k8s-rbd-secret.yml

apiVersion: v1
kind: Secret
metadata:
  name: client-k8s-rbd-secret
data:
#Please note this value is client.k8s base64 encoded.
# ceph auth get client.k8s | base64
  key: QVFBSFdHUmgvZCtKTnhBQUhyenBsd2k4V1dqd1hjL05HZEplQXc9PQo= #client.k8s的key值的base64编码


4)修改volumes-rbd.yaml清单
cat > volumes-rbd.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: volumes-rbd-demo
spec:
  containers:
  - name: redis
    image: redis:alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redis-rbd-vol
  volumes:
    - name: redis-rbd-vol
      rbd:
        monitors:
        - 172.168.32.201:6789
        - 172.168.32.202:6789
        - 172.168.32.203:6789 
        pool: k8spool 
        image: k8srbd 
        user: k8s 
        secretRef:
          name: client-k8s-rbd-secret #调用上面secret(client-k8s-rbd-secret)
        fsType: xfs 
        readOnly: false  
EOF

4)运行client-k8s-rbd-secret.yaml和修改后的volumes-rbd.yaml 清单
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f client-k8s-rbd-secret.yml 
secret/client-k8s-rbd-secret created

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-rbd.yaml 
pod/volumes-rbd-demo created

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod
NAME               READY   STATUS    RESTARTS   AGE
volumes-rbd-demo   1/1     Running   0          7s

5)查看volume信息
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl describe pod volumes-rbd-demo
......
olumes:
  redis-rbd-vol:
    Type:          RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
    CephMonitors:  [172.168.32.201:6789 172.168.32.202:6789 172.168.32.201:6789]
    RBDImage:      k8srbd
    FSType:        xfs
    RBDPool:       k8spool
    RadosUser:     k8s
    Keyring:       /etc/ceph/keyring
    SecretRef:     &LocalObjectReference{Name:client-k8s-rbd-secret,}
    ReadOnly:      false
......

3、CephFS存储卷

CephFS(Ceph文件系统)是在分布式对象存储RADOS之上构建的POSIX兼容的文件系统,它致力于为各种应用程序提供多用途、高可用和高性能的文件存储。CephFS将文件元数据和文件数据分别存储在各自专用的RADOS存储池中,其中MDS通过元数据子树分区等支持高吞吐量的工作负载,而数据则由客户端直接相关的存储池直接进行读写操作,其扩展能跟随底层RADOS存储的大小进行线性扩展。Kubernetes的CephFS存储卷插件以CephFS为存储方案为Pod提供存储卷,因而可受益于CephFS的存储扩展和性能优势。

CephFS存储卷插件嵌套定义于Pod资源的spec.volumes.cephfs字段中,它支持通过如下字段的定义接入到存储预配服务中。

▪monitors <[]string>:Ceph存储监视器,为逗号分隔的字符串列表;必选字段。
▪user <string>:Ceph集群用户名,默认为admin。
▪secretFile <string>:用户认证到Ceph集群时使用的Base64格式的密钥文件(非keyring文件),默认为/etc/ceph/user.secret。
▪secretRef <Object>:用户认证到Ceph集群过程中加载其密钥时使用的Kubernetes Secret资源对象。
▪path <string>:挂载的文件系统路径,默认为CephFS文件系统的根(/),可以使用CephFS文件系统上的子路径,例如/kube/namespaces/default/redis1等。
▪readOnly <boolean>:是否挂载为只读模式,默认为false。

3.1 使用keyring文佳来挂载cephfs

下面提供的CephFS存储卷插件使用示例定义在volumes-cephfs-demo.yaml配置清单文件中,它使用admin用户认证到Ceph集群中,并关联CephFS上的子路径/kube/namespaces/default/redis1,作为Pod对象volumes-cephfs-demo的存储卷,并由容器进程挂载至/data目录进行数据存取。

apiVersion: v1
kind: Pod
metadata:
  name: volumes-cephfs-demo
  namespace: default
spec:
  containers:
  - name: redis
    image: redis:alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: redis-cephfs-vol
      mountPath: "/data"
  volumes:
  - name: redis-cephfs-vol
    cephfs:
      monitors:
      - 172.168.32.201:6789
      - 172.168.32.202:6789
      - 172.168.32.203:6789
      path: /kube/namespaces/default/redis1
      user: admin
      secretFile: /etc/ceph/admin.secret
      readOnly: false

Kubernetes集群上需要启用了CephFS,并提供了满足条件的用户账号及授权才能使用CephFS存储卷插件。客户端访问集群时需要事先认证到Ceph集群并获得相应授权才能进行后续的访问操作,该示例使用了保存在/etc/ceph/fsclient.key文件中的CephFS专用用户认证信息。要完成示例清单中定义的资源的测试,需要事先完成如下几个步骤。

1)创建cephfs-metadata
root@ceph01:~/cephcluster# ceph osd pool create cephfs-metadata 32 32
pool 'cephfs-metadata' created

2)创建cephfs-data
root@ceph01:~/cephcluster# ceph osd pool create cephfs-data 64 64
pool 'cephfs-data' created

3)创建一个叫mycephfs的cephFS
root@ceph01:~/cephcluster# ceph fs new mycephfs cephfs-metadata cephfs-data
new fs with metadata pool 3 and data pool 4

4)验证cephfs
root@ceph01:~/cephcluster# ceph fs status mycephfs
mycephfs - 0 clients
========
RANK  STATE    MDS       ACTIVITY     DNS    INOS   DIRS   CAPS  
 0    active  ceph03  Reqs:    0 /s    10     13     12      0   
      POOL         TYPE     USED  AVAIL  
cephfs-metadata  metadata  32.0k   113G  
  cephfs-data      data       0    113G  
STANDBY MDS  
   ceph02    
   ceph01    
MDS version: ceph version 16.2.6 (ee28fb57e47e9f88813e24bbf4c14496ca299d31) pacific (stable)

5)在Kubernetes集群的所有节点安装ceph客户端(ceph-common)
①把ceph-01上的ceph.repo拷贝到Kubernetes的所有node节点
#master节点
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.207:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.208:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.209:/etc/apt/sources.list
#node节点
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.210:/etc/apt/sources.list 
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.211:/etc/apt/sources.list
root@ceph01:~# scp /etc/apt/sources.list  172.168.33.212:/etc/apt/sources.list
②kubernetes各个node节点安装ceph-common客户端
root@k8s-master01:~# apt update && apt install -y ceph-common
root@k8s-master02:~# apt update && apt install -y ceph-common
root@k8s-master03:~# apt update && apt install -y ceph-common
root@k8s-node01:~# apt update && apt install -y ceph-common
root@k8s-node02:~# apt update && apt install -y ceph-common
root@k8s-node03:~# apt update && apt install -y ceph-common

5)把ceph中admin的账号密码文件和ceph.conf拷贝到kubernetes的所有node节点上
kubernetes所有的node节点
root@k8s-master01:~# mkdir /etc/ceph
root@k8s-master02:~# mkdir /etc/ceph
root@k8s-master03:~# mkdir /etc/ceph
root@k8s-node01:~# mkdir /etc/ceph
root@k8s-node02:~# mkdir /etc/ceph
root@k8s-node03:~# mkdir /etc/ceph

root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.207:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.208:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.209:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.210:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.211:/etc/ceph
root@ceph01:~# scp -r /etc/ceph/{ceph.client.admin.keyring,ceph.conf} 172.168.33.212:/etc/ceph

6)在kubernetes的所有节点中将/etc/ceph/ceph.client.admin.keyring中的密钥导入到/etc/ceph/admin.secret
cat /etc/ceph/ceph.client.admin.keyring
root@k8s-master01:~# cat /etc/ceph/ceph.client.admin.keyring
[client.admin]
    key = AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==
    caps mds = "allow *"
    caps mgr = "allow *"
    caps mon = "allow *"
    caps osd = "allow *"


root@k8s-master01:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

root@k8s-master02:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

root@k8s-master03:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

root@k8s-node01:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

root@k8s-node02:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

root@k8s-node03:~# cat /etc/ceph/admin.secret
AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==

在Kubernetes的某工作节点上手动挂载CephFS,以创建由Pod对象使用的数据目录。

在kubernetes的任意一台k8s节点k8s-master01上操作,挂载cephfs,并再挂载的cephfs中创建子目录,用于在pod中挂载

root@k8s-master01:~# mount -t ceph -o name=admin,secretfile=/etc/ceph/admin.secret 172.168.32.201:6789:/ /mnt

root@k8s-master01:~# df -Th | grep mnt
172.168.32.201:6789:/ ceph      114G     0  114G   0% /mnt

root@k8s-master01:~# mkdir -p /mnt/kube/namespaces/default/redis1

上述准备步骤执行完成后即可运行如下命令创建清单volumes-cephfs-demo.yaml中定义的Pod资源,并进行测试:

root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-cephfs-demo.yaml 
pod/volumes-cephfs-demo created


root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl get pod
NAME                  READY   STATUS    RESTARTS   AGE
volumes-cephfs-demo   1/1     Running   0          3s

随后通过Pod对象volumes-cephfs-demo的详细描述了解其创建及运行状态,若一切无误,则相应的存储卷会显示出类似如下的描述信息:

root@k8s-master01:~# kubectl describe pod volumes-cephfs-demo
......
Volumes:
  redis-cephfs-vol:
    Type:        CephFS (a CephFS mount on the host that shares a pod's lifetime)
    Monitors:    [172.168.32.201:6789 172.168.32.202:6789 172.168.32.203:6789]
    Path:        /kube/namespaces/default/redis1
    User:        admin
    SecretFile:  /etc/ceph/admin.secret
    SecretRef:   nil
    ReadOnly:    false
......

除Pod对象仅会卸载其挂载的CephFS文件系统(或子目录),因而文件系统(或目录)及相关数据将依然存在,除非管理员手动进行删除。测试Redis数据持久性的方式来测试本示例中的容器数据的持久性,这里不再测试。

3.2 使用secretRef字段来挂载cephfs

在实践中,应该把认证到CephFS文件系统上的用户的认证信息存储为Kubernetes集群上的Secret资源,并通过secretRef字段进行指定,而非像该示例中那样,直接使用secretFile字段引用相应用户密钥信息文件。

1)删除pod
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl delete -f volumes-cephfs-demo.yaml 
pod "volumes-cephfs-demo" deleted

2)在ceph上获取client.admin的key并进行64位编码
#获取client.k8s的key值
root@ceph01:~# cat /etc/ceph/ceph.client.admin.keyring 
[client.admin]
    key = AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA==
    caps mds = "allow *"
    caps mgr = "allow *"
    caps mon = "allow *"
    caps osd = "allow *"
#对key值做base64编码
root@ceph01:~# echo AQCNxVlhDfpPBBAALJ1Bn6KOGXKug1GIaBXYVA== | base64
QVFDTnhWbGhEZnBQQkJBQUxKMUJuNktPR1hLdWcxR0lhQlhZVkE9PQo=

3)创建client-admin-cephfs-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: client-admin-cephfs-secret
data:
#Please note this value is client.k8s base64 encoded.
# ceph auth get client.k8s | base64
  key: QVFDTnhWbGhEZnBQQkJBQUxKMUJuNktPR1hLdWcxR0lhQlhZVkE9PQo= #client.admin的key值的base64编码


4)修改volumes-cephfs-demo.yaml清单
cat > volumes-cephfs-demo.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: volumes-cephfs-demo
  namespace: default
spec:
  containers:
  - name: redis
    image: redis:alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: redis-cephfs-vol
      mountPath: "/data"
  volumes:
  - name: redis-cephfs-vol
    cephfs:
      monitors:
      - 172.168.32.201:6789
      - 172.168.32.202:6789
      - 172.168.32.203:6789
      path: /kube/namespaces/default/redis1
      user: admin
      secretRef:
        name: client-admin-cephfs-secret
      readOnly: false 
EOF

4)运行client-k8s-cephfs-secret.yaml和修改后的volumes-cephfs-demo.yaml 清单
root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f client-admin-cephfs-secret.yaml
secret/client-admin-cephfs-secret created


root@k8s-master01:/apps/k8s-yaml/volume-case# kubectl apply -f volumes-cephfs-demo.yaml 
pod/volumes-cephfs-demo created

root@k8s-master01:~# kubectl get pod
NAME                  READY   STATUS    RESTARTS   AGE
volumes-cephfs-demo   1/1     Running   0          116s

5)查看volume信息
root@k8s-master01:~# kubectl describe pod volumes-cephfs-demo
......
Volumes:
  redis-cephfs-vol:
    Type:        CephFS (a CephFS mount on the host that shares a pod's lifetime)
    Monitors:    [172.168.32.201:6789 172.168.32.202:6789 172.168.32.203:6789]
    Path:        /kube/namespaces/default/redis1
    User:        admin
    SecretFile:  
    SecretRef:   &LocalObjectReference{Name:client-admin-cephfs-secret,}
    ReadOnly:    false
......

 

posted @ 2021-10-03 16:31  yaowx  阅读(1181)  评论(0编辑  收藏  举报