Kubernetes Cookbook 编程指南 中文版教程
创建时间:2018-12-07  访问量:3673  7  0

Kubernetes Cookbook 编程指南 中文版教程

卷(volumes)的使用

容器中的文件是短暂的。容器终止时,文件就消失了。Docker引入了卷和卷容器,通过从主机磁盘目录或其他容器装载数据来帮助我们管理数据。但是,对于容器集群,使用Docker很难跨主机管理卷及其生存期。

Kubernetes引入了卷(volume),它与跨容器重启的pod共存。它支持以下不同类型的网络磁盘:

  • emptyDir

  • hostPath

  • nfs

  • iscsi

  • flocker

  • glusterfs

  • rbd

  • gitRepo

  • awsElasticBlockStore

  • gcePersistentDisk

  • secret

  • downwardAPI

在本节,我们将详细介绍emtyDir,hostPath,nfs和glusterfs。Secret是用来存储安全凭证的,将在下一节介绍。其中大部分都与Kubernetes语法类似,只是后端不同。

开始

当您开始在Kubernetes中使用卷时,存储提供者是必需的,但emptyDir除外,当删除容器时将删除它。 对于其他存储提供者,必须先新建文件夹,服务器或群集,然后才能在pod定义中使用它们。

不同的卷类型具不同的存储提供者:

Volume类型 存储提供者
emptyDir Local host
hostPath Local host
nfs NFS server
iscsi iSCSI target provider
flocker Flocker cluster
glusterfs GlusterFS cluster
rbd Ceph cluster
gitRepo Git repository
awsElasticBlockStore AWS EBS
gcePersistentDisk GCE persistent disk
secret Kubernetes configuration file
downwardAPI Kubernetes pod information

如何去做...

卷在pod定义的volumes区域使用一个唯一的名称定义的。每个卷类型都有不同的配置。一旦你定义了一个卷,你就可以将它挂载到容器spec.volumeMounts.name的volumeMounts区域并且必须要配置volumeMounts.mountPath项,它指明了你定义的volume名称和容器中挂载的目录。

在下面的例子中,我们将使用Kubernetes YAML格式的配置文件来创建带有卷的pod。

emptyDir

emptyDir是最简单的卷类型,它将为容器创建一个空的卷并在同一个Pod中共享。当Pod删除时,emptyDir中的文件同时也会清除。emptyDir在Pod创建的时候创建。在下面的配置文件中,我们将创建一个Pod运行Ubuntu并执行命令使其睡眠3600秒。正如你所看到的,在volumes区域创建了一个名为data的卷,并且将其挂载到Ubuntu容器中的/data-mount目录下:

// configuration file of emptyDir volume
# cat emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
 name: ubuntu
labels: 
 name: ubuntu
spec:
 container:
  -
   image: ubuntu
   command: 
    - sleep
    - "3600"
   imagePullPolicy: IfNotPresent
    name: ubuntu
    volumeMounts:
     -
      moutPath: /data-mount
      name: data
    volumes:
     -
      name: data
      emptyDir: {}


//通过emptyDir.yaml配置文件创建Pod
# kubectl create -f emptyDir.yaml

检验Pod运行哪个Node节点上

通过使用kubectl describe pod <Pod name> | grep Node命令,你可以验证Pod运行在哪个Node节点上。

Pod运行起来后,你可以在目标Node上使用docker inspect <container ID>命令,你就可以看到容器内部挂载点的详细信息:

"Mounts": [
    {
        "Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~empty-dir/data",
        "Destination": "/data-mount",
        "Mode": "",
        "RW": true
    },
    ...
]

这里,你可看到Kubernetes简单的创建了一个空目录/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~empty-dir/<volumeMount name>供Pod使用。如果你创建了一个具有多个容器的Pod,所有这些容器的目标目录/data-mount都会使用相同的源目录。

在前面的配置文件emptyDir.yaml中,如果我们将emptyDir.medium设置为Memory,那么emptyDir可以被挂载为tmpfs格式:

volumes:
 -
  name: data
  emptyDir:
   medium: Memory

我们也可以通过kubectl describe pods ubuntu来验证Volumes的信息并查看是否设置成功:

# kubectl describe pods ubuntu
Name: ubuntu
Namespace: default
Image(s): ubuntu
Node: ip-10-96-219-192/
Status: Running
...
Volumes:
    data:
        Type: EmptyDir (a temporary directory that shares a pod's lifetime)
        Medium: Memory

hostPath

hostPath充当Docker卷。在hostPath中列出的Node节点的本地文件夹将会被挂载到Pod上。因为Pod可以运行在任何Node节点中,卷中发生的读写功能可以显示的存在于运行Pod的Node节点中。然而,在Kubernetes中,Pod应该是无节点敏感的。请注意,当使用hostPath时,在不同的Node节点上的配置和文件有可能是不同的。因此,由相同的命令和配置文件创建的相同Pod,在不同的Node节点上作用可能也不一样。

通过使用hostPath,你能够在容器与Node节点的本地磁盘之间读写文件。对于卷的定义,我们需要做的就是在hostPath.path配置项中指定Node节点上的目标挂载文件夹:

// configuration file of hostPath volume
# cat hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
 name: ubuntu
spec:
 containers:
  -
   image: ubuntu
   command: 
    - sleep
    - "3600"
   imagePullPolicy: IfNotPresent
   name: ubuntu
   volumeMounts:
    -
     mountPath: /data-mount
     name: data
 volumes:
  -
   name: data
   hostPath:
    path: /target/path/on/host

使用docker inspect来验证卷详细信息,你将看到主机上的卷挂载到/data-mount的目录下:

"Mounts": [
    {
        "Source": "/target/path/on/host",
        "Destination": "/data-mount",
        "Mode": "",
        "RW": true
    },
    ...
]

创建一个文件来验证卷是否创建成功 使用kubectl exec <pod name> <command>这个命令,你可以在Pod中执行命令。在这种情况下,如果我们运行kubectl exec ubuntu touch /data-mount/sample,我们应该能够在主机的/target/path/on/host目录中看到一个名为sample的空文件。

nfs

你可以在你的Pod中挂载Network File System(NFS)作为nfs卷。多个Pod可以挂载并在同一个nfs卷中共享文件。在nfs卷中存储的数据将中跨Pod生存期持久化的。你需要在使用nfs卷这前创建你自己的NFS服务器,并保证在Kubernetes Node节点上安装了nfs-utils软件包。

在你继续之前验证nfs服务器是否正常工作 你应该检查一个/etc/exports文件是否具有合适的共享参数和目录,并使用mount -t nfs <nfs server>:<share name> <local mounted point>命令来检查它是否可在本地挂载。

nfs卷类型的配置文件与其它的类似,但是nfs.server和nfs.path是定义卷所必需的参数,它指定了NFS服务器的信息和挂载源目录。nfs.readOnly是一个可选的字段,它用来指定卷是否是只读的(黙认是false):

// configuration file of nfs volume
# cat nfs.yaml
apiVersion: v1
kind: Pod
metadata:
 name: nfs
spec:
 containers:
  -
   name: nfs
   image: ubuntu
   volumeMounts: 
    - name: nfs
      mountPath: "/data-mount"
 volumes:
  - name: nfs
    nfs:
     server: <your nfs server>
     path: "/"

在你运行kubectl create -f nfs.yaml命令之后,你就可以通过使用kubectl describe <pod name>描述你的Pod以检查挂载状态。如果挂载成功,它应该显示conditions。如果Ready显示的是True,那么目录nfs就挂载好的:

Conditions:
 Type Staus
 Ready True
Volumes:
 nfs:
  Type: NFS (an NFS mount that lasts the lifetime of a pod)
  Server: \<your nfs server>
  Path: /
  ReadOnly: false

如果我们通过Docker命令查看容器,你将发现在Mounts区域的卷信息:

"Mounts": [
	{
        "Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/nfs",
        "Destination": "/data-mount",
        "Mode": "",
        "RW": true
    }
    ...
]

实际上,Kubernetes仅仅将你的<nfs server>:<share name>挂载到/var/lib/kubect/pods/<id>/volumes/kubernetes.io~nfs/nfs上,然后将它再挂载到容器中的目标目录/data-mount下。正如前面技巧提到的,你也可以使用kubectl exec来新建一个文件,测试是否完美挂载。

glusterfs

GlusterFS(https://www.gluster.org) 是一个可扩展的网络附加存储文件系统。glusterfs卷类型允许你在Pod中挂载GlusterFS卷。就像NFS卷一样,在GlusterFS卷中的数据是跨Pod生存期持久化的。如果Pod终止了,GlusterFS卷中的数据依然可以访问,在使用GlusterFS卷之前,你应该先构建一个GlusterFS系统。

在继续之前先验证GlusterFS是否正常工作 在GlusterFS服务器上使用gluster volume info命令,你可以查看当前可用卷。通过在本地使用mount -t glusterfs <flusterfs server>:/<volume name> <local mounted point>,你可以检查GlusterFS系统是否成功挂载。

因为GlusterFS中的卷副本数必须大于1,我们假设我们在服务器中有两个副本gfs1和gfs2并且卷名称为gvol。

首先, 我们需要创建一个端点来作为gfs1和fgs2之间的桥:

# cat gfs-endpoint.yaml
kind: Endpoints
apiVersion: v1
metadata:
 name: glusterfs-cluster
subsets:
 -
  addresses:
   -
    ip: \<gfs1 server ip>
  ports:
   -
    port: 1
 -
  addresses:
   -
    ip: \<gfs2 server ip>
  ports:
   -
    port: 1
    
//创建端点
# kubectl create -f gfs-endpoint.yaml

然后,我们可以使用kubectl get endpoints来检查端点是否正确创建:

# kubectl get endpoints
NAME 				ENDPOINTS 			AGE
glusterfs-cluster 	<gfs1>:1,<gfs2>:1 	12m

之后,我们应该能够通过glusterfs.yaml创建具有GlusterFS卷的Pod。glusterfs卷定义参数是glusterfs.endpoints,它指定了我们刚刚创建的端点名称,glusterfs.path参数是卷的名称gvol。glusterfs.readOnly用来设置挂载的卷是否是只读模式:

# cat glusterfs.yaml
apiVersion: v1
kind: Pod
metadata:
 name: ubuntu
spec:
 containers:
  -
   image: ubuntu
   command: 
    - sleep
    - "3600"
   imagePullPolicy: IfNotPresent
   name: ubuntu
   volumeMounts:
    - 
     mountPath: /data-mount
     name: data
   volumes:
    -
     name: data
     glusterfs:
      endpoints: glusterfs-cluster
      path: gvol

我们通过kubectl describe来检查一个卷的设置:

Volumes:
 data:
  Type: Glusterfs (a Glusterfs mount on the host that shares a pod's lifetime)
  EndpointsName: glusterfs-cluster
  Path: gvol
  ReadOnly: false

使用docker inspect你可看到挂载源是/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~gusterfs/data,挂载到目标/data-mount目录下。

iscsi

iscsi卷是用于在你的Pod中挂载已存在的iSCSI。不像nfs卷,iscsi卷仅允许在一个单独的容器中以只读模式挂载。数据将跨Pod生存期持久化:

字段名称 字段定义
targetPortal IP Address of iSCSI target portal
Iqn IQN of the target portal
Lun Target LUN for mounting
fsType File system type on LUN
readOnly Specify read-only or not, default is false

flocker

Flocker是一个开源容器数据卷管理器。当容器移动时,flocker数据卷将移动到目标节点。在Kubernetes使用Flocker之前,必须要先有个Focker集群(Flocker控制服务,Flocker数据集代理,Flocker容器代理)。Flocker的官方网站(https://docs.clusterhq.com/en/1.8.0/install/index.html )有详细的安装说明。

在你准备好了Flocker集群之后,创建一个数据集并在Kubernetes配置文件的Flocker数据卷定义中指定数据集的名称:

字段名称 字段定义
datasetName Target dataset name in Flocker

rbd

可以使用rbd数据卷将Ceph RADOS 块设备(http://docs.ceph.com/docs/master/rbd/rbd/) 安装到pod中。在使用rbd数据卷之前,你需要先安装一个Ceph。为了保持身份认证的秘密,rbd数据卷支持的定义是秘密的。

字段名称 字段定义 黙认值
monitors Cepth monitors  
pool The name of RADOS pool rbd
image The image name rbd created  
user RADOS user name admin
keyring The path of keyring, will be overwritten if secret name is provided /etc/ceph/ keyring
secretName Secret name  
fsType File system type  
readOnly Specify read-only or not False

gitRepo

gitRepo数据卷将作为空字典挂载并在Pod中通过Git克隆一个特定版本的仓库供你使用:

字段名称 字段定义
repository Your Git repository with SSH or HTTPS
Revision The revision of repository
readOnly Specify read-only or not

awsElasticBlockStore

awsElasticBlockStore 数据卷在Pod中挂载一个AWS EBS的数据卷。为了使用它,你必须要让你的Pod运行在AWS EC2上,并且有与EBS相同的可用性区域。目前,EBS仅支持附加到EC2,因此这意味着您无法将单个EBS卷附加到多个EC2实例:

字段名称 字段定义
volumeID EBS volume info - aws://<availability zone>/<volume-id>
fsType File system type
readOnly Specify read-only or not

gcePersistentDisk

与awsElasticBlockStore 类似,Pod使用gcePersistentDisk数据卷必须运行在GCE上,且具有相同的工程和区域。当readOnly=false时,gcePersistentDisk 仅支持单个写入者。

字段名称 字段定义
pdName GCE persistent disk name
fsType File system type
readOnly Specify read-only or not

downwardAPI

downwardAPI卷是一个Kubernetes卷插件,它能够将一些pod信息以纯文本的形式保存到容器中。downwardAPI卷当前支持的元数据为:

  • metadata.annotations

  • metadata.namespace

  • metadata.name

  • metadata.labels

downwardAPI的定义是一个项目列表。一个项包含了一个path和fieldRef。然后Kubernetes将fieldRef中列出的指定元数据转储到mountPath下的一个名为path的文件,并将<volume name>挂载到指定的目的地:

{
    "Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~downward-api/<volume name>",
    "Destination": "/tmp",
    "Mode": "",
    "RW": true
}

对于pod的IP,使用环境变量在pod spec 中传送会容易得多:

spec:
 containers:
  - name: envsample-pod-info
   env:
    - name: MY_POD_IP
     valueFrom:
      fieldRef:
        fieldPath: status.podIP

更多示例,请参见Kubernetes GitHub中的示例目录,它包含了环境变量和downwardAPI卷的更多示例。

不止这些...

在前面的案例中,用户需要知道存储提供者的详细信息。Kubernetes提供了PersistentVolume(PV)来抽象存储提供者与存储消费者的详细信息。Kubernetes当前支持的PV类型如下所示:

  • GCEPersistentDisk

  • AWSElasticBlockStore

  • NFS

  • iSCSI

  • RBD(Ceph Block Device)

  • GlusterFS

  • HostPath(not workable in multi-node cluster)

PersistentVolume

下图展示了持久卷的说明信息。首先,管理员提供一个PersistentVolume的规范。其次,他们通过PersistentVolumeClaim为消费者提供存储请求。

管理员首先需要规定并分配持久卷。

这里是使用NFS的一个示例:

// example of PV with NFS
# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
 name: pvnfs01
spec:
 capacity:
  storage: 3Gi
 accessModes:
  - ReadWriteOnce
 nfs:
  path: /
  server: <your nfs server>
persistentVolumeReclaimPolicy: Recycle

// create the pv
# kubectl create -f pv.yaml
persistentvolume "pvnfs01" created

我们在这里可以看到有三个参数分别是:capacity、accessModes和表示PV长度的persistentVolumeReclaimPolicy.capacity。accessModes是基于存储提供者的容量,并可以在提供期间设置特定的模式。例如,NFS同时支持多个读取与写入,因此,我们可以将accessModes参数设为ReadWriteOnce,ReadOnlyMany或者ReadWriteMany。一个卷的accessModes一次只能设置一种模式。persistentVolumeReclaimPolicy用来定义PV释放时的行为。目前,它对于nfs和hostPath支持的策略是Retain和Recycle。如果设置了Retain模式,那么你必须自己清除这个卷;另一方面,如果设置的是Recycle模式,Kubernetes将会擦除这个卷。

PV是一个资源,与Node类似。我们可以使用kubectl get pv来查看当前提供的PV:

// list current PVs
# kubectl get pv
NAME    LABELS  CAPACITY ACCESSMODES STATUS CLAIMREASON       AGE
pvnfs01 <none>  3Gi      RWO         Bound  default/pvclaim01 37m

接下来,我们要将PersistentVolume与PersistentVolumeClaim进行绑定,以致于可以将其作为一个卷挂载到Pod容器中:

// example of PersistentVolumeClaim
# cat claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: pvclaim01
spec:
 accessModes:
  - ReadWriteOnce
 resources:
  requests:
   storage: 1Gi

// create the claim
# kubectl create -f claim.yaml
persistentvolumeclaim "pvclaim01" created

// list the PersistentVolumeClaim (pvc)
# kubectl get pvc
NAME 	  LABELS STATUS VOLUME  CAPACITY ACCESSMODES AGE
pvclaim01 <none> Bound  pvnfs01 3Gi      RWO         59m

accessModes与storage的约束可以在PersistentVolumeClaim中设置。如果声明绑定成功,它的状态就会变成Bound;相反,它的状态就Ubound,它意味着当前没有PV匹配请求。

然后我们就可以通过使用PersistentVolumeClaim将PV作为卷进行挂载:

// example of mounting into Pod
# cat nginx.yaml
apiVersion: v1
kind: Pod
metadata:
 name: nginx
 labels:
  project: pilot
  environment: staging
  tier: frontend
spec:
 containers:
  -
   image: nginx
   imagePullPolicy: IfNotPresent
   name: nginx
   volumeMounts:
   - name: pv
    mountPath: "/usr/share/nginx/html"
   ports:
   - containerPort: 80
volumes:
 - name: pv
  persistentVolumeClaim:
   claimName: "pvclaim01"


// create the pod
# kubectl create -f nginx.yaml
pod "nginx" created

这个语法与其它的卷类型语法类似。仅仅是在卷的定义中添加了persistentVolumeClaim的claimName。我们都准备好了!让我们验证详细信息并查看是否挂载成功了:

// check the details of a pod
# kubectl describe pod nginx
...
Volumes:
 pv:
  Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
  ClaimName: pvclaim01
  ReadOnly: false
...

我们可以看到,在nginx pod中已经挂载了一个卷,并且它的类型是pv pvclaim01。使用docker inspect查看一看它是如何挂载的:

"Mounts": [
    {
        "Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/pvnfs01",
        "Destination": "/usr/share/nginx/html",
        "Mode": "",
        "RW": true
    },
    ...
]

Kubernetes将/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/<persistentvolume name>目录挂载到目标Pod中。

还可以参考

volumes是放在Pod或副本控制器的容器spec下。看看下面的章节以唤起你的记忆:

  • Pod的使用

  • 副本控制器的使用