对那些曾经使用更传统方式构建应用的开发者来说,转向容器化微服务不是一个容易的转变。当开发者设计分布式应用时,微服务应用也正是分布式的,其中有许多新的概念和细节需要他们去考虑和熟悉。将容器和Kubernetes搅合在一起,为何许多开发者要费力去适应这个新世界也就很明显了。开发者想要关注业务逻辑的开发,并非处理微服务所在的执行环境的必要代码。API一直是连接服务的高效方式,对于Kubernetes(K8s)上的微服务也依然如此。在这篇文章中,我们将阐述为什么API-First(译者注:指API先行,首先考虑API)这种在Kubernetes上构建微服务的方法可以使您从中受益。在我们深入研究之前,让我们快速回顾一下API-First的含义,以及K8s服务常引用的一个概念。
之前DZone的这篇文章描述了API为先意味着:你首先开始设计和实现一个能被其他微服务使用的API,然后再真正开始实现实际的微服务。除了API设计本身之外,你通常还会为API提供模拟和文档。这些随后将用于促进与其他团队的讨论,他们可能成为你们团队正在计划构建的微服务的消费者。换句话说,该方法在你投入太多精力编写实际的微服务之前,允许你检验你的API设计。但是,API-First的方式不仅仅在开发阶段有用。一旦微服务构建完毕,其他希望使用该微服务的团队将受益于文档和模拟的能力。好消息是有很多支持API-First方法的工具。支持API-First方法的最常用规范是OpenAPI和API蓝图。然后,您可以使用Swagger或Apiary等工具来设计您的API,生成模拟,文档甚至客户端库。
所有这些对于需要独立性和松散耦合的应用程序(如微服务应用程序)特别有用,因为它帮助团队在使用其他团队构建的服务时提高工作效率。但是,这种方法如何转化为依赖于诸如Kubernetes之类的协调器来处理每个微服务的部署和执行的现代微服务体系结构?在解释这种方法之前,有必要回顾一下什么是K8s服务。
如前所述,许多开发人员对他们需要学习的所有新概念感到有点不知所措。对于刚接触K8s的开发人员来说,K8s服务的概念非常令人迷惑,因为它在技术上并不涉及微服务的代码本身。以下是K8s服务的一个例子:
apiVersion: v1
kind: Service
metadata:
name: githubstats
labels:
app: githubstats
spec:
ports:
- port: 9000
name: http
selector:
app: githubstats
正如您所看到的,K8s服务与您开发的微服务无关。实际上,它只是一个端口号,提供有关如何访问Pod内的微服务的信息。
在底层,K8s服务会创建一个持久IP地址和DNS条目,以便始终可以访问目标微服务。
K8s使用标签选择器来知道该服务需要指向哪个Pod,在这个示例应用程序中:githubstats。您开发的微服务通常打包在容器映像中并部署到K8。下面的示例显示了容器映像repo / githubstats:0.0.1作为具有标签app:githubstats的部署的一部分。
apiVersion: apps/v1beta2 # for versions before 1.8.0 use
apps/v1beta1
kind: Deployment
metadata:
name: githubstats
spec:
replicas: 3
selector:
matchLabels:
app: githubstats
template:
metadata:
labels:
app: githubstats
spec:
containers:
- name: githubstats
image: repo/githubstats:0.0.1
ports:
- containerPort: 9000
使用K8s服务的真正优势在于它们提供了一个稳定的端点来访问微服务本身,而不关心调度程序将其放入群集中的哪个位置。要理解这点并费劲,因为只看K8s服务,开发者无法获取他们需要的信息去消费该微服务。假设上一个示例中显示的githubstats微服务由团队A开发。现在另一个团队B正在构建一个微服务,将其称为UI服务,该服务需要使用githubstats微服务。团队B获取的唯一信息是githubstats微服务名称和端点信息。微服务本身的信息是完全没有的,比如可以调用什么方法。
正如在开始时提到的,API-First方法的一大优点是您总是从API设计开始,创建模拟服务,文档和客户端库。从K8s的角度来看,API-First方法允许您从更高层次和大多数开发人员进行讨论,以便他们不需要马上了解K8s的内部工作。为了使这种方法在K8s上有用,你需要以某种方式将API与K8s服务绑定。本文的其余部分将重点介绍如何处理此问题。
该流程的第一步涉及创建API的“正式”描述。有各种格式和工具可以使用。例如Oracle的Apiary。Apiary网站提供了一个环境,在那里开发团队可以设计和记录API,如图1所示。
图1 - Apiary 用户界面。
大多数时候,这些工具是在整个工作流程的“设计时间”(design time)被用到,此时开发者正创建附带额外文档的API。考虑到微服务应用程序高度动态的本质,有必要将API-First方法也引入到“运行时”(runtime),该阶段API将会被实际使用到。
为此,您需要创建一个“绑定”关系,使得K8s服务不仅仅像现在这样只是一个主机名和TCP端口。通过将API绑定到K8s服务,开发人员可以立即获得有关服务的重要信息,而无需通过额外的步骤去寻找API文档,并基于定义在API中的规范编写代码来处理请求/响应。这个绑定信息可以保存在一个简单的数据库中,并提供一个用户界面,如图2所示。
图2 - 显示哪些API绑定到K8S服务的简单用户界面。
API-First方法的最后一部分是如何使用该服务。理想情况下,开发人员希望避免必须要做的:实现响应的解析/编组,以及添加可处理HTTP调用的代码。更有效的方法是为服务提供一个客户端库,至少支持最常用的语言。在一些更先进的组织中,客户端库可以作为CI(Continuous Integration)过程的一部分生成。有一些工具,如swagger-codgen可根据规范生成客户端,并使其成为CI过程的一部分,甚至可以将客户端生成包含在你自定义的绑定UI中。在微服务架构中达到真正的API-First方法所缺少的,是包含使生成的代码可以在运行时发现服务在哪里的逻辑。在已有的最佳实践中,当服务被部署时,流程中服务发现阶段的若干部分是被硬编码的。拥有在需要某服务时(当服务正在调用远程服务时)就能判断其在何处运行的能力,使得API-First方法成为比已有的最佳实践更好的解决方案。
本文阐述了如何将API-First方法与K8s结合起来。如果您愿意为“绑定”和代码生成体验付出一点努力,您可以使API-First方法成为现有环境中一部分。其优点不仅在于开发人员可以专注于编写代码,而只有少数人需要了解K8s的内部工作方式,也在于您可以提供对于成功的微服务项目必备的部分管理需求,比如适宜的文档和正确的API版本。