"容器",实际上是一个由Linux Namespace、Linux Cgroups和rootfs三种技术构建出来的进程的隔离环境。
一个正在运行的容器可以被“一分为二”看待:
/var/lib/docker/aufs/mnt
上的rootfs,这部分称为“容器镜像”,是容器的静态视图。作为一个开发者,我们并不关心容器运行时的差异,因为,在整个“开发->测试->发布”的流程中,真正承载着容器信息进行传递的,是容器镜像,而不是运行时。
这也正是在Docker项目成功后,迅速走向“容器编排”这个“上层建筑”的主要原因。
这个逻辑正是所有云计算提供商如此热衷容器技术的重要原因:通过容器镜像,它们可以和潜在用户(开发者)直接关联起来。
容器编排工具:
谷歌公开发表的基础设施体系(The Google Stack):
编排?调度?容器云?集群管理?
以上功能,Docker的(Compose+Swarm)或者传统的PaaS就能做到,因此Kubernetes的核心定位不止于此,全局架构如下:
Kubernetes由Master和Node两种节点组成,分别对应这控制节点和计算节点。
出发点:如何编排、管理、调度用户提交的作业。
由三个密切协作的独立组件组合而成:
整个集群的持久化数据,由kube-apiserver处理后保存到Etcd中。
因此,kubernetes项目并不关心部署的是什么容器运行时,使用的什么技术实现,只要容器运行时能够运行标准的容器,就可以通过实现CRI接入到Kubernetes中。
Kubernetes项目着重要解决的问题是:运行在大规模集群中的各种任务之间,实际上存在着各种各样的关系。这些关系的处理,才是作业编排和管理系统最困难的地方。
如何处理这些关系?利用Docker Swarm 和Compose来处理一些简单依赖关系。
比如,在Compose项目中,可以为两个容器定义一个“link”,Docker项目负责维护这个“link”关系,具体的做法,将两个容器相互访问所需要的IP地址,端口号等信息以环境变量的形式注入,供应用进程使用:
DB_NAME=/web/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
DB_PORT_5432_TCP_PROTO=tcp
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_ADDR=172.17.0.5
当容器发生变化时(如镜像更新或者被迁移到其他宿主机),这些环境变量的值会由Docker项目自动更新。
简单的依赖关系,使用以上方法没有问题,但是如果要将所有的依赖关系都处理好,link这种简单的方式就不行了。
所以,Kubernetes项目最主要的设计思想是:从更宏观的角度,以统一的方式来定义任务之间的各种关系,并且为将来支持更多种类的关系保留余地。
常见的“紧密交互”关系:应用之间需要非常频繁的交互和访问或者通过本地文件进行信息交换。常规环境下,这些应用会被部署在同一台服务器,通过localhost通信,通过本地磁盘交换文件。在Kubernetes中,这些容器会被划分为一个Pod,Pod中的容器共享同一个Network Namespace、同一组数据卷,从而达到高效交换信息的目的。
常规需求,如web服务和数据库之间的访问关系;kubernetes提供了一种叫“Service”的服务。像这样的两个应用,往往故意部署在不同的机器上,从而提高容灾能力。但是对于一个容器来说IP地址是不固定的,那么Web怎么找到数据库容器对应的Pod呢?kubernetes通过给Pod绑定Service,而Service声明的IP地址始终不变。 这个Service主要作用是作为Pod的代理入口,从而代替Pod对外暴露一个固定的网络地址。
这样,Web应用只需要关心数据库Pod的Service的信息,Servie后端真正代理的Pod的IP地址、端口等信息的自动更新、维护,则是kubernetes项目的职责。
围绕Pod为核心,构建出Kubernetes项目的核心功能“全景图”:
Kubernetes推崇的做法:
这就是所谓的声明式API,这些API对应的“编排对象”和“服务对象”,都是kubernetes项目中的API对象。
Kubernetes不仅提供了一个编排工具,更重要的是提供了一套基于容器构建分布式系统的基础依赖。