笔者在前边的文章中反复强调过,Kubernetes平台最大的创新就是,没有像同时期的其他各种容器项目那样,把Docker作为整个架构的核心,围绕Docker来构建编排,扩展,调度等运维和管理能力,而仅仅将Docker作为系统的一个组件,这个组件还可以通过CRI来扩展,从这个角度我们就能体会到,Kubernetes的核心就是,从更高的层次,来构建一套可扩展的平台,解决容器化部署运维的各种问题。这样设计有个好处,就是给整个生态的上下游都留有充足的余地。特别是对于遗留以系统,以及需要有特殊处理要求的系统,都可以通过扩展,完美的迁移到Kubernetes平台。这也是生态化思维的完美体现,只有把蛋糕做大了,参与的人多了,大家才能“共同富裕”啊。目前我们国家正在制定二次分配和三次分配的方案,有些朋友很担忧,其实大可不必啊,道理很简单,没有蛋糕,分配个啥,关键是你到底要当做蛋糕的还是等着分配蛋糕来吃的。
扯得有点远,回到正题,对于部署到Kubernetes平台上的应用来说,POD从来就不不应该关心自己运行在哪个node(工作节点上),当然除非有特殊需求,比如我这个POD需要高性能计算支持,那么就需要调度到配置了GPU的机器上,当然这和POD不应该关系node这句话不矛盾。进一步来说,POD也不应该直接访问宿主机文件系统上的文件,因为如果POD因为宿主机故障,可能会被调度到另外一台工作节点,结果就是以前能访问到的数据,就没有了。因此对于云原生应用来说,我们应该尽量的避免直接访问宿主机文件系统的数据。但是有个例外,系统级别的POD必须能够访问宿主机的文件系统,我们在(上)这篇文章中,介绍过Linux操作系统上一切皆文件的概念,而系统级别的POD大多需要访问宿主机上的设备,比如网络设备,GPU设备等,不出意外的是,这些设备从操作形同的角度来看,就是文件系统,对这种特殊的场景,Kubernetes提供了hostPath类型的数据卷,笔者在前边两篇文章中有提到过,今天我们就来了解一下hostPath数据卷类型,以及这种类型适用于那些场景。
【什么是hostPath数据卷?】
简单来说,hostPath类型的数据卷就是将宿主机(工作节点,这两个词在笔者的文章中,可以互换,除非特殊说明。)上的文件或者文件夹挂载到容器中,因此运行在同一台工作节点上的多个POD,如果hostPath数据卷指定的本地目录相同,就可以访问到相同的数据。如下图所示:
基于上边的描述,hostPath类型的数据卷适合系统级别POD读取操作系统固定目录数据的场景,比如说操作系统的日志,因为对于Linux操作系统来说,大部分系统级别的日志都放在固定的位置。
注:为了内容的完整性,虽然我们本篇文章介绍了hostPath类型的数据卷,但是本质上来讲:hostPath是一种安全风险系数极高的数据卷类型,一般情况下只保留给具有优先级的容器实例使用。具体来说,如果我们让用户不加限制的使用hostPath类型的数据卷,恶意用户就可以在宿主机上做任何他们想做的事情。举个例子,用户可以在容器中挂载Docker socket文件(/var/run/docker.sock),然后在容器中启动docker client,就可以在工作节点上以root用户的方式运行任何命令了,包括删除网络接口,文件夹等,安全风险意识奥的同学,估计看到这里,后背发凉啊。
注:其实我们上边的描述中,说的就是Docker in Docker(DinD技术)技术,有这样的运维场景:有时候我们需要在容器内执行docker命令,比如:在jenkins的容器实例中内运行docker命令来打包应用程序镜像,如果我们直接在docker容器内安装全套的docker应用性能太差,为了提高打包的效率,更好的办法是在容器内仅部署docker客户端命令行工具,而具体打包的任务交给宿主机上的dockre deamon,或者集中打包的服务器来完成。通过类似docker run -v /var/run/docker.sock:/var/run/docker.sock的命令将宿主机 docker.sock 文件挂载到容器, 并且直接挂载宿主机的/usr/bin/docker, 这样容器内就不需安装 Docker 程序。当我们在容器内使用docker命令的时候,实际上调用的是将命令发给宿主机的docker daemon来执行。我们可以通过命令docker run -v /var/run/docker.sock:/var/run/docker.sock命令将宿主机docker.sock文件挂载到容器实例(比如jenkins容器),并且挂载宿主机的/usr/bin/docker,这样的话容器中就不需安装Docker程序了,当容器内使用docker命令时,实际上调用的是宿主机的docker daemon和docker命令。
注:关于容器安全,或者说Kubernetes平台的安全体系,笔者后续会有专门的文章来介绍,大家稍安勿躁!
接下来我们来演示一下,如何使用hostPath(顺便通过例子再吓唬一下读者,这种hostPath类型的数据卷安全风险有多高)。我们计划通过配置并挂载了hostPath类型的容器向大家展示如何在容器内部,浏览整个宿主机的文件系统。我们的POD叫yunpan-node-exlorer,YAML文件如下所示:
如上图所示,hostPath数据卷指向工作节点文件系统的根目录,并且这个数据卷被挂载到容器的/host目录,这样就给容器提供了访问工作节点整个文件系统的能力。我们来验证一下,如下图所示在自己的Kubernetes集群中,部署yunpan-node-explorer应用,然后在远程登陆到容器中,cd到host文件夹,你就可以看到宿主机(笔者使用的是本地minikube环境,因此这台虚拟机就叫minikube)的所有文件,如下图所示:
从两个命令输出的结果来看,我们验证了通过容器能够访问到宿主机根目录的场景,由于容器中sh的权限是root,因此登陆到容器的用户可以干任何事情,包括删除宿主机相关的设备和文件,通过上边的这个案例,希望大家能够意识到使用hostPath的风险。如下变的输出,验证了是root账户:
➜ Kubernetes存储 kubectl exec -it yunpan-node-explorer -- sh
/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
笔者这里提的风险绝对不是危言耸听,想象一下恶意攻击者获取了Kubernetes API接口的访问权限,他就可以在集群中部署这样的一个POD,获取敏感数据,并且分分钟钟让你的应用瘫痪。不幸的是,目前版本的Kubernetes并没有约束普通用户使用hostPath类型的数据卷,因此在我们的集群上,必须严格控制这种类型。
我们配置hostPath类型的数据卷除了要指定path参数,还需要指定类型type字段,类型字段表示了path指向的数据类型,比如:文件,文件夹等。Kubernetes的hostPath数据卷类型提供了如下type:
-
- Directory类型,Kubernetes会检查在指定的路径上,是否存在文件夹
- DirectoryorCreate类型,同Directory类型差不多,但是如果文件夹不存在,就自动创建一个空的。从这点可以看到,当类型为Directory的时候,如果在目标目录没有制定的文件夹,那么POD无法启动。
- File类型,必须存在指定的文件
- FileorCreate类型,文件不存在的话,创建一个空的文件
- BlockDevice类型,指定路径必须是block device(注:存储设备分为块设备和字符设备,块设备通过固定长度的数据单元来访问,而字符设备以单个字符来访问,我们机器上的磁盘等都属于块设备,而键盘,打印机等都属于字符设备)。
- CharDevice类型,字符设备,前项有解释,不累述
- Socket类型,指定路径必须是一个Unix网络套接字
这里大家要特别注意的是,如果我们指定的路径和类型不匹配,那么容器就无法运行,我们可以从POD的events列表中看到这个不匹配的错误。
阿里云容器服务ACK,提供的容器存储功能基于Kubernetes存储系统,深度融合阿里云存储服务,并且完全兼容Kubernetes提供的原生存储服务,例如EmptyDir、HostPath、Secret、ConfigMap等存储类型。ACK基于社区容器存储接口(CSI)通过部署CSI插件实现了阿里云存储服务接入能力。具体来说,阿里云容器服务ACK支持Pod自动绑定阿里云云盘、NAS、 OSS、CPFS、本地卷等存储服务。
我们来举个例子,阿里云对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。OSS具有丰富的安全防护能力,支持服务器端加密、客户端加密、防盗链白名单、细粒度权限管控、日志审计、合规保留策略(WORM)等特性。并且我们可以在容器服务Kubernetes集群中直接使用阿里云OSS存储卷,如下所示的YAML文件,直接使用OSS作为存储卷:
Kubernetes提供了PV和PVC这套机制,来降级用户使用存储卷的难度,因此如果我们要在ACK集群中使用阿里云的某种存储Volume,首先需要声明PVC,也就是我们在POD中具体想要使用多大空间的存储,以及读写模式等,然后就可以在POD中使用这个PVC了,如上图所示。但是PVC只是声明,那么具体的存储卷Volume从哪里来呢?其实在成熟运维的集群中,PV来自于运维人员,因为PV的创建需要提供底层存储设备的信息,比如文件系统类型,用户名和密码等,从这个角度你可以看到Kubernetes这套PV和PVC设计的思想,其实还是我们熟悉的接口和实现的模式,PVC属于接口,用户通过Kubernetes提供的这个对象来声明我要什么,而运维人员通过提供(实现)具体的PV来绑定实现。这种解耦本质就是抽象,因为底层的具体实现不应该过多的暴露给开发人员,并且这也是一种Sperated of Concern的设计,PV对象的管理,运维人员更加熟悉,而PVC是开发和部署人员的领域。
如果你对上边的这些解释看的云里雾里,没有关系,这篇文章也不打算把PV和PVC说清楚,笔者在(上)这篇文章中说过,我们会在后续专门的文章中介绍PV和PVC,由于这个概念的重要性,是大规模集群中管理存储的利器。下一篇文章我们就开始介绍PV和PVC来,敬请期待!