容器化近年来备受关注,围绕着容器技术很多不同的项目也诞生了。这些项目中的一类就涉及到容器编排。当前已出现了许多不同的方案,针对云专有的解决方案,例如Amazon ECS,开源的解决方案有如Kubernetes。这些方案都有一个相同的目标,那就是使容器编排更简单。但是,这些工具所提供的真的如它们所声称那样,管理更简单,部署更简单吗?
在本文中,我们先简要谈论下容器化概念,后面我将使用两种不同的编排方法来部署一个AI-API架构,一个包含简单API的AI聊天机器人。一种是使用Kubernetes,另外一种是只使用基于容器的手动控制管理平面。
容器与虚拟机
在AWS开始在云上提供虚拟机(VM)后,全球大多数服务器部署现在都在使用某类虚拟化系统。如果你始终可以100%利用资源的话,那么在价格与性能方面,虚拟机往往更加昂贵。假设你是购买基于它们构建的免费服务层的话,例如RDS,它们现在能更容易,更快地部署,更易于管理并且需要的维护更少。就像物理服务器和虚拟机之间的差异一样,容器化使得管理和部署服务器或服务变得更加容易。
虽然虚拟机可以共享物理资源,但它们必须在其之上引入自己的操作系统,当然包括内核。虽然这能创建一个理想的隔离环境,但它也会产生自己的问题,例如运行多个内核和操作系统时浪费的资源以及出于安全原因的更新和维护。容器化就是通过利用内核命名空间创建具有自己的文件系统的隔离工作区。因此仅使用一个操作系统并共享相同的内核,来运行多个(服务器)应用程序,这就是容器的意义所在。
容器同时也具备分层镜像的功能,虽然虚拟机解决方案中也存在这样的东西,但它没有像容器那样充分利用。大多数应用程序有时需要数小时才能构建和安装,而一个应用程序镜像甚至可以在几秒钟内下载并运行。如果你需要运行容器化的WordPress安装,那么你需要运行Docker来运行WordPress。容器镜像可以缓存无需多次重复下载。
接下来我们开始讨论容器编排。
容器编排
创建和管理容器的便捷性使许多自动化工作流程得以实现。在最初,所有基于容器的部署都使用一些专有技术栈来编排和运行它们。但是在Docker开源并开始统治该领域之后,它逐渐成为运行容器的标准,因此Docker镜像也逐渐成为分发容器镜像的标准方式。所以很多关于自定义编排的项目出现时都会以Docker为基础。
下图是我想要创建的第一种编排类型。但我需要解决的一个很重要的问题就是启动时间。我们的软件启动时间很长,所以我们希望始终有一个服务处于就绪状态可以服务于每个请求。在我的架构中,我希望有一个控制器容器可以在我准备新的容器时作为负载均衡器以及HTTP服务器将请求转发到正确的AI容器去。
我使用docker-py库来完成这项工作,并使用了flask来提供HTTP请求。Docker.py库有着很好的文档而且很容易使用,只需为控制器和AI应用分别创建了一个Dockerfile。这个过程很简单,在开发过程中我学到了更多关于Docker的知识。虽然这是我创建的一个非常原始的专有容器编排解决方案,但它总算完成自己的使命。
好了,接下来是时候介绍下Kubernetes了,因为基本上它为编排提供了类似的目的,我已经创建了基于Kubernetes的解决方案来减少需要编写的代码量。
为了在Kubernetes中应用相同的思路,我不得不从一开始就重新思考我的架构。因为Kubernetes仅仅只需要你提供一个部署的模式(像Amazon ECS那样)并尝试将该模式保持在稳定状态。当我为下次请求创建自己的容器时,编排系统应该能适时在类似这样的过程中准备或是处理一些事情,经过一番搜索,我发现可以使用Kubernetes的标签功能来完成我的程序。
我的想法是将所有新创建的AI容器打上assigned:not_assigned的标签,使之应用到每个容器。我需要声明,我想要其中3个包含标签assigned:not_assigned。当新请求到来时,我的控制器容器应该将此标签更改为assigned:assigned。更改标签会引起状态改变,3个已部署容器中的2个将会带有assigned:not_assigned的标签。当Kubernetes观察到状态被变更时,它将用assigned:not_assigned标签启动另一个新的容器。
因此,只是为了管理Kubernetes集群,我又编写了另一个类。它实际上并不需要实现如创建或管理容器的某些功能,但它需要能转发请求并删除标签。完成这个工作删除了大量代码,可想而知维护的代码行数量也减少了,这意味着攻击面更小了。在Pod中创建与Kubernetes主机的连接非常简单。此后我又花了一些时间来创建服务并将请求路由到正确的容器。
结论
在这个试验中,我尝试使用现成的容器编排解决方案和我自己编写的编排工具。编写我自己的编排解决方案很快,其中的概念并不陌生,并且会有很多文章指导如何去做。但是当切换到Kubernetes时,一切都变了。为了能够使用Kubernetes,关于容器的知识是不够的,我必须学习新的概念和一种新的思维方式以便能够按我的需求来使用它,例如在Kubernetes中部署和服务作为第一公民,而不是容器。但最后,我们可以放心地假设,使用Kubernetes进行容器编排能使我的架构更安全,更稳定,因为我的软件中的大多数复杂的部分,例如维持稳定数量的容器,这些都是在Google使用并推广的一个开源项目的帮助下完成的。