介绍卷
Kubernetes 的卷是 pod 的一个组成部分, 因此像容器一样在 pod 的规范中就定义了,它和 pod 共享相同的生命周期,这意味着在 pod 启动时创建卷, 并在删除 pod时销毁卷。它们不是独立的Kubernetes对象, 也不能单独创建或删除。 pod 中的所有容器都可以使用卷,但必须先将它挂载在每个需要访问它的容器中。在每个容器中,都可以在其文件系统的任意位置挂载卷。
卷的应用示例
假设有一个带有三个容器的 pod(如图6.1所示), 一个容器运行了一个 web 服务器, 该 web 服务器的 HTML 页面目录位于 /var/htdocs, 并将站点访问日志存储到/var/logs 目录中。第二个容器运行了一个代理来创建 HTML 文件, 并将它们存放在/var/html 中, 第三个容器处理在 /var/logs 目录中找到的日志(转换、 压缩、 分析它们或者做其他处理)。 在这个例子中, 只需要在三个容器中挂载两个卷, 这样三个容器将可以一起工作。
pod有一个名为pubilcHtml的卷,这个卷被挂载在WebServer容器的/var/htdocs中,因为这是web服务器的服务目录。在ContentAgent 容器中也挂载了相同的卷, 但在/var/html中, 因为代理将文件写入/var/html中。通过这种方式挂载这个卷, web服务器现在将为contentagent生成的内容提供服务。 pod还有一个名为logVol的卷,用于存放日志,此卷在WebServer和LogRotator容器中的/var/log中挂载, 注意, 它没有挂载在ContentAgent容器中, 这个容器不能访问它的文件,即使容器和卷是同一个pod的一部分,在pod的规范中定义卷是不够的。 如果我们希望容器能够访问它, 还需要在容器的规范中定义一个VolumeMount。 而且本例中2个卷初始都为空,因此可以使用emptyDir的卷。Kubernetes还支持其它类型的卷,这些卷要么是在从外部源初始化卷时填充的, 要么是在卷内挂载现有目录。 这个填充或装入卷的过程是在pod内的容器启动之前执行的。
卷被绑定到pod的lifecycle(生命周期)中,只有在pod存在时才会存在,但取决于卷的类型, 即使在pod和卷消失之后,卷的文件也可能保待原样,并可以挂载到新的卷中。让我们来看看卷有哪些类型
卷的可用类型
- emptyDir——用于存储临时数据的简单空目录
- hostPath——用于将目录从工作节点的文件系统挂载到pod中。
- gitRepo——通过检出Git仓库的内容来初始化的卷。
- nfs——挂载到pod中的NFS共享卷。
- gcePersistentDisk (Google高效能型存储磁盘卷)、awsElasticBlockStore (AmazonWeb务弹性块存储卷)、azureDisk (MicrosoftAzure 磁盘卷)——用于挂载云服务商提供的特定存储类型。
? cinder、cephfs、iscsi、flocker、glusterfs、quobyte、rbd、flexVolume、vsphere-Volume、photoPersistentDisk、scaleIO——用于挂载其他类型的网络存储。 ? configMap、secret、downwardAPI——用于将 Kubernetes 部分资源和集群信息公开给 pod 的特殊类型的卷。 ? persistentVolumeClaim——一种使用预置或者动态配置的持久存储类型(我们将在本 的最后 节对此展开讨论)
通过卷在容器之间共享数据
卷是如何在一个pod的多个容器之间共享数据的
使用emptyDir卷
最简单的卷类型是emptyDir卷,,卷从一个 目录开始,运行在 pod 内的应用程序可以写入它需要的任何文件。因为卷的生存周期与 pod 的生存周期相关联,所以 删除 pod 时,卷的内容就会丢失。 一个 emptyDir 卷对于在同 pod 中运行的容器之间共享文件特别有用。但是它也可以被单个容器用于将数据临时写入磁盘,例如在大型数据集上执行排序操作时,没有那么多内存可供使用。数据也可以写入容器的文件系统本身,但是这两者之间存在着细微的差别(容器的文件系统甚至可能是不可写的),所以写到挂载的卷可能是唯一的选择。
fortune镜像介绍: 系统ubuntu,nginx,fortune 命令每次运行时都会输出一个随机引用 ,可以创建一个脚本每 10 秒调用一次执行,井将其输出存储在index.html中。
创建pod
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
当html-generator容器启动时,它每10秒启动一次 fortune 命令输出到/var/htdocs/index.html 文件。因为卷是在/var/htdocs上挂载的,所以index.html文件被写入卷中,而不是容器的顶层。一旦web-server容器启动,它就开始为/usr/share/nginx/html 目录中的任意 HTML 文件提供服务(这是 Nginx 服务的默认服务文件目录) 。
创建好pod后,选择用端口转发的形式访问看看吧 kubectl port-forward fortune 8080:80
指定emptyDir的介质
作为卷来使用的emptyDir,是在承载 pod的 工作节点的实际磁盘上创建的,因此其性能取决于节点的磁盘类型。但我们可以通知 Kubernetes 在tmfs文件系统(存在内存而非硬盘)上创建emptyDir 。因此,可将emptyDir的medium设置为Memory(见上面yaml的注释)
emptyDir卷是最简单的卷类型,但是其他类型的卷都是在它的基础上构建的,在创建空目录后,它们会用数据填充它。
使用Git仓库作为存储卷
gitRepo卷基本上也是 emptyDir 卷,它通过克隆 Git 仓库并在pod启动时(但在创建容器之前) 检出特定版本来填充数据。
在创建gitRepo卷后,它并不能和对应 repo 保持同步当向仓库推送新增的提交时,卷中的文件将不会被更新。如果所用 pod 是由ReplicationController 管理的,删除这个 pod 将触发新建一个新的 pod,而这个新pod的卷中将包含最新的提交。
我们可以使用 Git 仓库来存放网站的静态 HTML 文件,并创建一个包含web 服务器容器和 gitRepo 卷的 pod 。每当 pod创建时,它会拉取网站的最新版本并开始托管网站。唯一的缺点是,每次将更改推送到 gitRepo 时,都需要删除pod才能托管新版本的网站。
运行一个web服务器pod,使用gitRepo
首先fork一下https://github.com/Arrowarcher/kubia-website-example(在github上创建自己的repo副本,别人的你无权限修改) 然后创建pod,我们需要一个Nginx容器和gitRepo卷:
apiVersion: v1
kind: Pod
metadata:
name: gittrepo-volume-pod
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
repository: https://github.com/Arrowarcher/kubia-website-example.git
revision: master
directory: .
创建好后使用端口转发的方式测试访问: kubectl port-forward gitrepo-volume-pod 8080:80
踩坑记录:kind创建的节点没有安装git 所以要先去节点里安装git(kind的节点都是docker容器) docker exec -it kind-worker3 bash 安装git忽略
要查看新版本的站点,需要删除 pod 并重建,每次进行更改时,没必要每次都删除 pod ,可以运行个附加进程来使卷Git 库保持同步 。在这里不详细解释如何实现。
介绍sidecar
Git的同步进程不应该运行在与nginx站点服务器相同的容器中。在第二容器sidecar container 增加对pod主容器操作,而不是将附加逻辑加入主应用的代码中(更复杂和不可复用) 为了找到一个保持本地目录与 Git 仓库同步的现有容器镜像, 转到 Docker Hub 并搜索 “git syc”,可以看到很多可以实现的镜像。 然后在示例中,从 pod 的一个新容器使用镜像, 挂载 pod 现有的 gitRepo 卷到新容器中,并配置 Git 同步容器来保持文件与 Git repo 同步。 如果正确设置了所有的内容, 应该能看到 web 服务器正在加载的文件与 GitHub repo 同步。
对于使用私有git仓库的gitrepo卷,书中说不可行,要保持gitrepo卷的简单性,不添加任何通过ssh协议克隆私有库的支持,因为这需要向卷添加额外的配置选项。应该使用gitsync sidecar或类似的方法,而不是gitrepo卷。
访问工作节点文件系统上的文件
大多数 pod 应该忽略它们的主机节点, 因此它们不应该访问节点文件系统上的任何文件。 但是某些系统级别的 pod( 切记, 这些通常由 DaemonSet 管理)确实需要读取节点的文件或使用节点文件系统来访问节点设备。 Kubernetes 通过 hostPath卷实现了这一点。
hostPath卷
hostPath卷指向文件系统上的特定文件或目录。在同一节点上运行并在其hostPath卷使用相同的路径的pod可以看到相同的文件。 hostPath 卷是我们介绍的第一种类型的持久性存储,因为gitRepo和emptyDir卷的内容都会在 pod 被删除时被删除, 而 hostPath 卷的内容则不会被删除。如果删除了一个pod, 并且下一个pod使用了指向主机上相同路径的hostPath卷,则新pod将会发现上一个 pod 留下的数据,但前提是必须将其调度到与第一个 pod 相同的节点上。(所以对常规pod使用hostPath卷不是一个好主意,在别的节点会找不到数据)
检查使用hostPath卷的系统pod
kubectl describe pod kindnet-lr9ss -n kube-system 如果检查其他 pod, 则会看到其中没有一个使用 hostPath 卷来存储自己的数据,都是使用这种卷来访问节点的数据。hostPath 卷通常用于尝试单节点集群中的持久化存储,譬如 Minikube、kind 创建的集群。
提示 请记住仅当需要在节点上读取或写入系统文件时才使用 hostPath,切勿使用它们来持久化跨 pod的数据。
使用持久化存储
当运行在一个 pod 中的应用程序需要将数据保存到磁盘上, 并且即使该 pod 重新调度到另 一个节点时也要求具有相同的数据可用。 这就不能使用到目前为止我们提到的任何卷类型, 由于这些数据需要可以从任何集群节点访问, 因此必须将其存储在某种类型的网络存储 (NAS) 中。 要了解允许保存数据的卷, 我们将创建一个运行 MongoDB的 pod。 除了测试目的,仅仅运行没有卷或非持久卷的数据库 pod 没有任何意义,所以需要为该 pod 添加适当类型的卷并将其挂载在MongoDB 容器中。
使用 GCE 持久磁盘作为 pod 存储卷
书中是使用Google Kubemetes Engine,集群节点运行在GCE上,这里不做继续实践。
# 查看集群区域
gcloud container clusters list
# 创建一个 1GiB 容量并命名为 mongodb的GCE持久磁盘
gcloud compute disks create --size=1GiB --zone=europe-west1-b mongodb
由于我是使用kind,不能使用GCE持久磁盘,但是我部署了另一个hostpath卷,指定节点上目录作为持久储存
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
hostPath:
path: /tmp/mongodb
pod 包含一个容器和一个卷,被之前创建的 GCE 持久磁盘支持(如图 6.5 所示)。因为 MongoDB 就是在 /data/db 上存储数据的, 所以容器中的卷也要挂载在这个路径上。
# pod中执行mongdb shell
kubectl exec -it mongodb mongo
>use mystore
>db.foo.insert({name:'foo'})
>db.foo.find()
# { "_id" : Objectid("57a6leb9deOcfd512374cc75"), "name" : "foo" }
kubectl delete pod mongodb
kubectl create -f mongodb-pod-gcepd.yaml
# pod中执行mongdb shell
kubectl exec -it mongodb mongo
>use mystore
>db.foo.find()
# { "_id" : Objectid("57a6leb9deOcfd512374cc75"), "name" : "foo" }
新的 pod 使用与前一个 pod 完全相同的 GCE Persistent Disk, 所以运行在其中的 MongoDB 容器应该会看到完全相同的数据, 即便将 pod 调度到不同的节点也是一样的。(hostpath是利用节点机器目录做持久化,别的节点没有数据哦)
通过底层持久化存储使用其他类型的卷
因为 Kubernetes 集群运行在 Google Kubernetes 引擎上所以需要创建 GCEpersistent disk。当在其他地方运行 Kubernetes 集群时,应该根据不同的基础设施用其他类型的卷。
例如: Amazon的AWS EC2:awsElasticBlockStore卷 Microsoft Azure: azureFile或azureDisk卷
使用NFC卷
如果集群是运行在自有的一组服务器上,那么就有大量其他可移植的选项用于在卷内挂载外部存储。
volumes:
- name: mongodb-data
nfs:
seerver: 1.2.3.4
path: /some/path
其他储存技术
要了解每个卷类型设置需要哪些属性的详细信息,可以转到KubernetesAPI引用中的KubernetesAPI定义,或者通过第三章展示的通过kubectl explain查找信息。
从底层储存技术解耦pod
到目前为止, 我们探索过的所有待久卷类型都要求 pod 的开发人员了解集群可用的真实网络存储的基础结构。 例如, 要创建支持 NFS 协议的卷, 开发人员必知道 NFS 节点所在的实际服务器。 Kubernetes 理念旨在向应用程序及其开发人员隐藏真实的基础设施, 使他们不必担心基础设施的具体状态, 并使应用程序可在大量云服务商和数据企业之间进行功能迁移。 理想的清况是, 在 Kubernetes 上部署应用程序的开发入员不需要知道底层使用的是哪种存储技术, 同理他们也不需要了解应该使用哪些类型的物理服务器来运行pod,与基础设施相关的交互是集群管理员独有的控制领域。
介绍持久卷和持久卷声明
研发人员无须向他们的 pod 中添加特定技术的卷, 而是由集群管理员设置底层存储, 然后通过Kubernetes API 服务器创建持久卷并注册。 在创建持久卷时, 管理员可以指定其大小和所支持的访问模式。 当集群用户需要在其 pod 中使用持久化存储时, 他们首先创建持久卷声明(Persistent VolumeClaim, 简称 PVC) 清单, 指定所需要的最低容量要求和访问模式,然后用户将持久卷声明清单提交给 Kubernetes API 服务器, Kubernetes 将找到可匹配的持久卷并将其绑定到持久卷声明。 持久卷声明可以当作 pod 中的一个卷来使用, 其他用户不能使用相同的持久卷,除非先通过删除持久卷声明绑定来释放。
创建持久卷
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /tmp/mongodb
持久卷不属于任何命名空间,它跟节点一样是集群层面的资源
通过创建持久卷声明来获取持久卷
假设现在需要部署一个需要持久化存储的 pod ,将要用到之前创建的持久卷,但是不能直接在 pod 内使用,需要先声明一个。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
建好声明, Kubernetes 就会找到适当的持久卷并将其绑定到声明 ,持久卷容量必须足够大以满足声明的需求,并且卷的访问模式必须包含声明中指定的访问模式。在该示例中,声明请求1 GiB 的存储空间和 ReadWriteOnce 访问模式。
RWO:ReadWriteOnce,仅允许单个节点挂载读写 ROX:ReadOnlyMany,允许多个节点只读 RWX:ReadWriteMany,允许多个节点挂载读写这个卷 RWO ROX RWX 涉及可以同时使用卷的工作节点的数量而并非 pod 数量。
查看持久卷发现状态已经变成bound。 持久卷显示被绑定在 default/mongodb-pvc 的声明上,default 是声明所在的命名空间。 持久卷是集群范围的,不能在命名空间中创建 持久卷声明只能在特定命名空间创建,因此只能被同命名空间的pod使用
在pod中使用持久卷声明
持久卷现在已经可用了,除非先释放掉卷,否则没有人可以申明相同的卷。要在pod 中使用持久卷,需要在 pod 的卷中引用持久卷声明名称,如下面的代码清单所示。
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
nodeName: kind-worker
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
了解使用持久卷和持久卷声明的好处
上图展示了pod直接使用持久磁盘和通过持久卷和持久卷声明使用持久磁盘。使用后者对开发人员来说更加简单,研发人员不需要关心底层实际使用的储存技术,如mongodb-pod-pvc.yaml 一样。 此外,现在可以在许多不同的Kubernetes集群上使用相同的 pod 和持久卷声明清单,因为它们不涉及任何特定依赖于基础设施的内容。声 明说:“我需要x存储量,并且我需要能够支持一个客户端同时读取和写入 。”然后pod通过其中一个卷的名称来引用声明。
回收持久卷
之前创建声明的时候立即绑定到持久卷,现在变成了pending。持久卷状态为Released ,所以它可能包含前1个声明人的数据,如果集群管理员还没来得及清理,那么不应该将这个卷绑定到全新的声明中。除此之外,通过使用相同的持久卷,新 pod 可以读取由前一个 pod 存放的数据,即使声明和pod 是在不同的命名空间中创建的(因此有可能属于不同的集群租户)
手动回收持久卷 persistentVolumeReclaimPolicy: Retain # 当声明被释放后,PersistentVolume 将会被保留(不清理和删除) 因以上配置,持久卷和其数据会保留,手动回收持久卷并使其恢复可用的唯一方法是删除和重新创建持久卷资源。当这样操作时,你将决定如何处理底层存储中的文件:可以删除这些文件,也可以闲置不用,以便在下个 pod 中复用它们。
自动回收持久卷 存在两种其他可行的回收策略 Recycle和Delete
持久卷的动态卷配置。
从之前得知,使用持久卷和持久卷声 明可以轻松获得持久化存储资源,无须研发人员处理下面实际使用的存储技术,但这仍然需要一个集群管理员来支持实际的存储。幸运的是, Kubernetes 还可以通过动态配置持久卷来自动执行此任务。 管理员可以创建一个持久卷配置,定义一个或多个StorageClass对象,用户在持久卷声明中引用它,可以选择想要的持久卷类型。
通过 StorageClass 资源定义可用存储类型
在用户创建持久卷声明之前,管理员需要创建一个或多个StorageClass 资源然后才能创建新的持久卷。
# storageclass-fast-hostpath.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: k8s.io/minikube-hostpath # 用于配置持久卷的插件
parameters:
type: pd-ssd
这里我本想书中推荐的minikube的provisioner插件来测试,但是我用的是kind,通过查询kind-issue得知,kind含有默认的storageclass change-default-storage-class
因此现在直接创建持久卷声明,指定StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 100Mi
accessModes:
- ReadWriteOnce
storageClassName: standard
创建后发现是pending状态,跟书中不一样,原来是StorageClass的模式是延迟创建。
如果使用书中的yaml创建的sc,应该会创建好持久卷和持久卷声明了。 总结:StorageClass的好处是可以通过名称引用,来创建持久卷声明甚至创建持久卷。一旦部署了存储类,作为集群用户,就可以像以前那样部署完全相同的 PVC 清单和完全相同的 pod 清单。
不指定存储类的动态配置
如果持久卷声明没有明确指出要使用哪个存储类, 默认存储类会 用于动态提供持久卷的内容 创建pvc后会根据standard的配置,生成持久卷
当你想让 PVC 绑定到你手动配置的 PV 时,这里要指定空字符串,否则就会被指定成默认的storageClass,而后新生成一个PV。
总之,将持久化存储附加到一个容器的最佳方式是仅仅创建PVC(有需要就指定明确的storageClass)和容器,其他所有内容都由动态配置持久卷置备程序处理。
|