本文介绍了容器和kubernetes的底层概念, 以及它们如何给应用开发提供了新的模式.
让我们从计算机开聊. 当计算机启动时, 它会运行一个叫init的程序. 然后init会启动其他所需的程序: 服务器, 终端, 窗口管理器等. Init能做几件有趣的事情, 例如让一个程序开机启动, 隔一段时间运行一个程序, 还有确保一个程序没有失败或者crash, 如果有就重启它. 正在运行的程序可以看到这台机器上的所有东西: 其它在运行的程序, 所有的文件, 以及网络.
多个进程同时跑在一台计算机上. 所有的进程可以自由的互相之间交互, 或者与常规的资源交互.
通过将进程进行划分,程序员可以有一个更加简单的模型来方便理解, 所以创建命名空间(namespace)的工具也被开发出来了. 程序或者进程只能看到运行在同一个命名空间下的其他进程. 如果它们寻找文件, 那么只能看见硬盘上分配到这个命名空间的那一部分. 从安全的角度而言, 一个命名空间里面的某个进程被黑掉了影响的仅仅也只是这个命名空间而已.
类似于Docker和Rkt这样的工具被开发出来以后使得我们能系统化地使用这些特性. 这些工具提供了打包的功能, 将一个命名空间打包成一个容器, 使得我们可以很方便的将它搬到另一台机器上运行, 不出意外的它会跟之前完全一致的方式继续运行, 因为它本身的隔离特性. 事实上, 通常可以很容易的将容器想象为可以完全独立的运行的小计算机. 因为这些新的工具非常易用, 它们就成为了一种非常流行的构建软件的方式.
容器就是新的进程.
容器中的进程.在这里, 一个进程仅仅能够与所在同一个容器里面的其他进程和资源交互
一台计算机的资源是有限的, 而且同时仅能处理有限的数据和运行有限的进程. 当面临增长的负载时(比如更多用户, 更大的数据集)一个简单的应对方式是垂直扩展, 也即是增加更多的处理能力和内存给到这台计算机, 但是很快这个代价就会非常昂贵,而且本身扩展的空间也相当有限. 另一种方式就是通过增加更多的计算机来水平扩展. 这些计算机一起就组成了集群.
为了能跑在集群上,应用也需要以不同的方式架构. 例如, 如果我们确认同一个程序的两份拷贝可以需要访问对方的数据就能运行, 那么我们就能放心的将它的多份拷贝放到不同的计算机上运行.
水平扩展:在这里集群里, 三台计算机每台运行两个容器. 一共有两个app server的实例来处理大的负载.
虽然容器本身并没有给我们任何其他的工具来构建分布式应用, 但是考虑一下这个级别上的抽象能让构建集群的应用方便一些. 容器模型所鼓励的模型是:
由于在集群里面有这么多的计算机要管理, 我们面临一些额外挑战:
在只有一台计算机的时候, 只有一个ip地址就可以了. 在有多个计算机之后,我们需要维护一个进程到ip的映射, 例如像etcd这样的分布式数据库. 当一个进程在一台机器上启动时, 这个信息就被加入到数据库中. 如果进程挂掉或者机器宕机, 也需要将这个条目从数据库中删除.
程序员对于开发跑在一台计算机上的应用很得心应手了. 理想状态下,我们想要的是有一个工具能将集群里面所有的计算机管理起来,而展现给程序员的就像一台”巨型”的计算机.
这个方向上的一个进展是CoreOS的Fleet项目, 它的基本思想就是像一台计算机上的init进程那样延伸做整个集群的init.
Google 贡献的Kubernetes项目则让我们更加接近我们想要一台”巨型”计算机的模型.
Kubernetes做的第一件事情就是拿走你的所有计算机, 然后还回给你一个”巨型”计算机--一个kubernetes的集群.
一个kubernetes的pod指定一组需要运行docker或者rkt容器.
之前我们描述的是一个集群里面不同计算机上跑着不同进程, 现在我们看到的是kubernetes集群里面的不同pod里跑着不同进程.
一个kubernetes集群围绕着pod也就是容器组构建了一个模型. 这些pod基于资源和”亲和度”的约束被动态分配到底层节点上.
之前,我们考虑的是什么进程需要在一台机器上一起运行. 现在, 我们考虑将哪些进程组构造成什么pod; pod已经成为一种优美的方式来对一个应用的一个功能单元构造模型.我们甚至可以直接使用社区构造的pod,直接将他们跑起来, 例如日志和监控.
一个pod里面的所有进程跑在同一台机器上, 这样解决了类似挂载磁盘这样的资源共享的问题. 背后是kubernetes将pod分配到不同的计算节点也就是kubernetes node上.我们可以给pod或者node设置发生的条件例如资源约束, 亲和性等.
计算机就是资源的集合: 计算能力, 内存, 磁盘和网络接口. 与之类似, 一个pod可以从底层的资源池中分配一定量的资源. 它也会有自己的网卡和pod所在的虚拟网络的ip.
所以, pod就是新的计算机.
如果我们需要某个特定功能进行扩展, 我们只需要在集群中多跑几个这个pod的拷贝. 当硬件不足, 我们就往集群里面增加更多的计算和存储. 通过将资源与它所承载的功能解耦, 调度器可以保证所有的可用资源会被尽可能高效利用.
Kubernetes复制控制器用来保证任意时间某个pod的一定数量的拷贝在运行. 就像一个分布式的init, 如果一个pod挂了: 起因可能是里面的一个进程失败了, 或者pod 的依赖挂了, 或者它所在的节点down了; kubernetes会探测到并在另一个可用的节点上启动一个新的拷贝.
一个kubernetes的service会跟踪集群里某种特定type的pod的所有实例. 例如, 我们有一个ap server service, 它会跟踪cluster里面所有的app server的pod. service是一个非常简便的抽象; 我们的应用可以非常快的找到某种类型服务的所有功能单元然后将工作分发给他们.
一个完整的Kubernetes集群图
Pod被动态分配到节点上. 每一种pod对应的服务都有服务发现和负载均衡. 同时也描绘了pod和服务的虚拟网络.
Kubernetes既是一个在集群里面管理和调度进程的框架, 也是一种构建应用的新的思维模型, 基于的是pod里面的进程分组和service所提供的服务发现.
管理一台计算机已经是一个难题了. 管理一大群互相通讯的机器更是复杂得多. 感谢发明了像docker, kubernetes这样非凡工具的好心人, 我们现在有了容器这样的简单模型, 也有工具将集群管理起来就像一台计算机. 构建可扩展的应用也从没像现在这样如此简单.
容器和集群管理软件业也影响了人们构建应用的方式. 他们创造了新的模式和抽象, 很多的可能性仍在探索中. 例如, 使用容器来构建可重用的应用组件或者库可能也会很有意思. 在Hasura, 我们正给数据库, 搜索, 用户管理, 文件管理等等创建组件, 构建应用就只需将它们快速组装起来.
总的来说, 在追求创造更简模型的道路上我们已经前进了一大步. 当今的所有软件本质就是运行代码,执行功能. 从这个角度, 我们做的所有的事情仅仅是管理这些功能: 将它们分组, 运行它们的多份拷贝, 找到并与它们交互, 然后处理失败的情况. 由此推出一个逻辑的结论, 或许某一天我们会有这样一个系统, 我们只需要描述我们需要的功能, 余下的交给系统按照它完成即可. 那确实是求之不得啊!
Akshaya领导着Hasuar的平台工程团队. 他曾经在Intellectual Ventures的一个咨询团队与敏捷开发团队一起工作过, 也曾经作为Tech mentor在MEST, Ghana工作过.