Cellery:向 Kubernetes 部署应用程序的代码优先方法

阅读数:1145 2019 年 9 月 13 日 08:00

Cellery:向Kubernetes部署应用程序的代码优先方法

本文要点

  • 尽管微服务架构(MSA)有很多好处,但管理数百个松耦合的微服务很快就会变得很麻烦。这就是设计基于单元格的架构(CBA)的原因。
  • CBA 是一种微服务架构模式,它的主要要求是将多个微服务(及其他组件)分组成称为单元格的构建块,以方便管理和重用。
  • 从零开始在容器编排平台上创建 CBA 很费力。在撰写本文时,Kubernetes 是业界广泛采用的容器编排平台;然而,使用 YAML 编写用于此目的的 Kubernetes 工件并不是一项简单的任务。
  • Cellery 遵循代码优先的方法,处理实现 CBA 的底层复杂性。
  • Cellery 包含一个 SDK、一个运行时和一个管理框架。

Cellery 简介

Cellery 到底是什么,它如何帮助我们在 Kubernetes 上部署和管理应用程序?Cellery 是一种在 Kubernetes 上构建、集成、运行和管理复合应用程序的代码优先方法。这种复合应用程序的构建块称为单元格——其名称为 Cellery,而不是 Celery。为了帮你理解单元格和 Cellery,让我们看看如何使用 Cellery 部署、管理和观察一个已有的由谷歌编写的 Kubernetes 应用程序。但是,在此之前,让我们先了解下单元格是什么以及单元格的工作原理。

单元格是什么?

让我们看一下,为什么需要在微服务架构中使用复合组件。

微服务是构建复杂且不断演化的应用程序的热门选项,它可以缩短上市时间,加快创新速度。每个服务都可以由专门负责该服务的团队独立开发,并且他们可以自由选择任何他们认为合理的技术。最重要的是,微服务是可重用的,每个服务都可以独立伸缩,使团队可以使用最能满足服务资源需求的最佳部署基础设施。开发人员可以对其服务进行本地更改,并在测试完成后立即部署这些更改。那么,这有什么挑战吗?

微服务(包括无服务器函数)的使用正在快速增长,因为组织的目标是提高开发速度和可伸缩性,而且它们还必须调整为面向业务能力的团队。在拥有数十个或数百个应用程序的企业中,管理如此多的松耦合微服务,不仅会成为运营的噩梦,也在团队沟通以及服务发现、版本控制和可观察性等方面提出了挑战。更多的服务、更多的沟通路径、更复杂的网络安排以及更多的潜在故障区。这就有了对高级结构的需求,将多个微服务和无服务器函数聚合到易于管理和重用的构建块中。

基于单元格的架构是一种微服务架构模式,它将系统的微服务、数据和其他功能组件(包括前端应用程序、遗留服务、代理、网关和遗留系统适配器)分组为内聚的、可单独部署的架构单元(称为单元格)。

通常,组件分组的依据是作用范围、所有权和组件之间的相互依赖关系。每个单元格都应该单独设计和开发,并且应该可以独立部署、管理和观察。此外,单元格内的组件可以使用支持的传输协议在单元格内部实现相互通信。然而,所有传入的服务请求必须首先通过单元格网关。该网关使用标准网络协议通过受控的网络端点提供安全 API、事件或流。团队可以通过自组织的方式生成可以持续部署和增量更新的单元格。下面是对单元格架构中单元格的一个简单描述:

Cellery:向Kubernetes部署应用程序的代码优先方法

图 1:自包含的架构单元:单元格

实现单元格架构的方法

Cellery 设计用于基于单元格架构原则创建 Kubernetes 应用程序。使用 Cellery,我们可以编写代码来定义单元格及其组件,方法是指向现有的容器镜像(其中包含构成单元格的微服务及其他组件),并定义这些组件之间的关系、对其他单元格的依赖和单元格 API(网关)。然后就可以使用单元格定义代码生成单元格镜像。实际上,一旦我们将单元格定义代码提交到版本控制存储库中,就可以触发 CI/CD 管道。像 Jenkins 这样的 CI/CD 系统可以构建单元格镜像,对其进行测试,并将其推入容器存储库。然后,CI/CD 系统可以拉取单元格镜像并将其部署到相应的生产环境。而且,我们还可以像在 Kubernetes 上部署的其他任何应用程序一样更新、扩展和观察部署好的单元格。

Cellery:向Kubernetes部署应用程序的代码优先方法

图 2:面向 Cellery 的 DevOps 流

使用 Cellery 创建单元格

简言之,Cellery 包含了 SDK、运行时和管理框架。安装 Cellery 时,可以通过 Cellery CLI运行命令,执行各种任务。

首先,你应该在本地机器上创建一个 Kubernetes 集群,或者使用已有的 Kubernetes 集群作为 Cellery 运行时环境。输入一个简单的命令 command cellery,就会看到一个提示,让你通过交互式 CLI 选择部署首选项,Cellery 将根据你的首选项为你配置 Kubernetes 集群。

设置好 Cellery 运行时环境之后,就可以开始用 Cellery 语言编写单元格。Cellery 语言基于 Ballerina 编程语言,因此可以使用 VSCode 和 IntelliJIdea 作为 IDE。要自动生成包含标准导入语句和所需函数的单元格定义文件,可以使用 cellery init 命令。接下来,你可以定义组件,完成构建,并使用 Cellery 语言运行逻辑。然后,Cellery 编译器将编译代码并使用一个简单的命令 cellery build 创建相应的 Kubernetes 构件。

要在 Cellery 运行时环境上部署单元格,运行 cellery run 命令并提供必要的参数。你可以使用 cellery push 命令将构建好的镜像推入单元格镜像存储库,使用 cellery pull 命令从存储库中拉取单元格镜像,并将构建和部署流集成到 CI/CD 管道。此外,你还可以通过 cellery view 命令查看单元格的可视化表示。Cellery 还提供了单元格测试功能和可观察性工具,让你可以监控、记录和跟踪单元格。

为什么是 Cellery?为什么不使用 YAML 为单元格配置 Kubernetes 部署?

对于已经采用容器的组织,开发人员不仅要创建微服务,还要理解容器编制系统如 Kubernetes 的细微差别。此外,除了准备 Kubernetes 集群、创建、部署和管理应用程序,从头开始创建 CBA 涉及到配置服务网格、处理服务身份验证和配置符合 CBA 原则的安全策略等任务。因此,使用标准 Kubernetes 资源配置单元格需要一些 Kubernetes 专业知识。

此外,Kubernetes 资源(如 pod、服务和部署)是通过 YAML 文件声明性地创建的。因此,随着部署规模的增加,面对不断增长的、越来越复杂的 YAML 代码,如果没有成熟的 IDE 帮助他们提高生产效率,DevOps 团队就会陷入挣扎。而且,由于缺乏对函数、抽象和封装等编程概念的支持,YAML 本身就鼓励大量重复代码。因此,在 Kubernetes 上创建复杂的部署意味着 DevOps 团队必须经历一个冗长而令人畏惧的过程,编写并维护可能长达数千行的 YAML 文件。这很容易出错。

Cellery 使用类型安全的、经过验证的代码而不是 YAML 来定义部署,而且,它还负责处理配置部署、单元格连接、服务、自动缩放等底层复杂性。此外,在默认情况下,使用 Cellery 编写的单元格通过单点登录、令牌、基于策略的访问控制和 mTLS 等安全机制来确保安全。Cellery 是围绕 DevOps 实践而设计的,因此,可以使用蓝 / 绿部署和金丝雀部署无缝地进行构建、推送、拉取、测试、部署和更新。用户还可以通过监控和跟踪功能观察部署。

简言之,Cellery 的目标是简化 Kubernetes 上应用程序的配置、构建、测试和部署。如上所述,该项目试图从不同的角度解决这个问题,包括开发、DevOps、安全性和可观察性。

Cellery 实例

让我们看一个真实的微服务示例,你可以亲自尝试一下。(要了解如何使用 Cellery 编写单元格代码,可以查看 Cellery 语法并尝试一些示例。)

为此,我们使用了谷歌的“Hipster Shop”演示应用程序。这里有原始 Hipster Shop 演示程序的详细信息、源代码、Docker 文件等。这个示例适用于 Cellery 0.3.0 版本。

Hipster Shop 应用程序是一个多层次、多语言的微服务应用程序,它是基于 Web 的电子商务应用程序。用户可以浏览商品,将它们添加到购物车中,使用应用程序购买它们。Hipster Shop 由前端和多个微服务组成,通过 gRPC 相互通信。服务架构如图 3 所示,表 1 是 Hipster Shop 微服务的说明。

Cellery:向Kubernetes部署应用程序的代码优先方法

图 3:Hipster Shop 应用的服务架构
服务 语言 说明
frontend Go 暴露一个 HTTP 服务器来为网站提供服务。不需要注册 / 登录,会自动为所有用户生成会话 ID。
cartservice C# 把用户购物车中的物品保存在 Redis 中并检索它。
productcatalogservice Go 从一个 JSON 文件提供产品列表以及搜索产品和获取单个产品的功能。
currencyservice Node.js 将一种货币转换成另一种货币。使用从欧洲央行获取的真实值。这是 QPS 最高的服务。
paymentservice Node.js 从给定的信用卡(mock)扣除给定的金额,并返回一个交易 ID。
shippingservice Go 根据购物车给出运输成本估计。将物品发送到给定的地址(mock)。
emailservice Python 向用户发送一封订单确认邮件(mock)。
checkoutservice Go 检索用户购物车,准备订单,并安排付款、发货和电子邮件通知。
recommendationservice Python 根据购物车中的物品推荐其他产品。
adservice Java 基于给定上下文单词提供文本广告。
loadgenerator Python/Locust 不断向前端发送模拟真实用户购物流的请求。
表 1:Hipster Shop 应用程序的现有服务

为了将 Hipster Shop 的微服务映射到基于单元格的架构,我们将这些微服务分组为五个单元格:ads、products、cart、checkout 和 front-end。我们根据每个微服务单独执行的任务以及它与单元格中其他微服务的关系密切程度设计了这种分类。一个单元格由一个团队拥有,但是一个团队可以拥有一个或多个单元格。定义单元格边界可以基于其他标准,例如组件到组件的连接数量,而不仅限于功能和所有权。要了解更多关于单元格粒度的信息,请点击这里

还有一点非常重要,为了使用 Cellery,我们没有对 Hipster Shop 原来的微服务做任何更改;Cellery 仅引用微服务的现有容器镜像。表 2 列出了单元格及各自的组件,图 4 进行了说明。

单元格 组件
ads adservice
products productcatalogservice、recommendationservice
cart cartservice、cacheservice
checkout checkoutservice、emailservice、paymentservice、shippingservice、currencyservice
front-end frontendservice
表 2:Hipster Shop 服务到单元格的映射

Cellery:向Kubernetes部署应用程序的代码优先方法

图 4:Hipster Shop 应用程序基于单元格的架构

单元格 front-end 包含前端应用程序,这是其唯一组件,HTTP 流量通过其网关访问单元格,而 front-end 通过 gRPC 与其他单元格交互。

单元格 checkout 是该架构中除 front-end 之外惟一与外部单元格(products 和 cart)通信的单元格,剩下的单元格 products、ads 和 cart 是独立的单元格,在这些单元格中,只有其内部组件之间发生通信。可以点击这里查看所有完整的单元格定义文件(扩展名为.bal 的文件)以及运行和部署 Hipster Shop 单元格的说明。请注意,这个示例已经在 Cellery 0.3.0 版本上进行了测试。

创建单元格

让我们看下 ads 的代码,它包含一个组件:adservice。

ads.bal

复制代码
import ballerina/config;
import celleryio/cellery;
public function build(cellery:ImageName iName) returns error? {
int adsContainerPort = 9555;
// Ad 服务组件
// 基于给定上下文单词提供文本广告。
cellery:Component adsServiceComponent = {
name: "ads",
source: {
image: "gcr.io/google-samples/microservices-demo/adservice:v0.1.1"
},
ingresses: {
grpcIngress: <cellery:GRPCIngress>{
backendPort: adsContainerPort,
gatewayPort: 31406
}
},
envVars: {
PORT: {
value: adsContainerPort
}
}
};
// 单元格初始化
cellery:CellImage adsCell = {
components: {
adsServiceComponent: adsServiceComponent
}
};
return cellery:createImage(adsCell, untaint iName);
}
public function run(cellery:ImageName iName, map<cellery:ImageName> instances) returns error? {
cellery:CellImage adsCell = check cellery:constructCellImage(untaint iName);
return cellery:createInstance(adsCell, iName, instances);
}

首先,单元格定义以标准 import 语句开始,并包含两个函数:build 和 run(如果你使用 cellery init 命令,那么这些函数将自动生成)。当用户分别执行 cellery build 和 cellery run 命令时,build 函数和 run 函数将被调用。

在 build 函数中,通过指向公共 Docker 镜像 URL(source)并定义网络访问入口点(ingresses)和环境变量(envVars),定义一个名为 adServiceComponent 的组件来表示 adservice。然后,单元格被初始化并使用名称 adsCell 定义,之前定义的 adServiceComponent 被添加到它的组件列表中。然后,使用 cellery:createImage 方法创建单元格镜像。

最后,run 函数将获取构建的单元格镜像(cellery:ImageName iName),其中包含单元格镜像和相应的实例名,并使用 cellery:createInstance 方法从单元格镜像创建一个正在运行的实例。

单元格内组件间的通信

现在,我们已经看了基本单元格文件的代码结构,让我们看一下有两个或多个组件的单元格的代码,以及如何配置这些组件实现彼此通信。

单元格 products 有两个组件:productcatalogservice 和 recommendationservice。如图 4 所示,recommendationservice 需要与 productcatalogservice 交互,因为它根据购物车中的物品来推荐产品。单元格中组件之间的通信是通过环境变量实现的。

如下面的代码片段所示,recommendationServiceComponent 需要通过环境变量(envVars)PRODUCT_CATALOG_SERVICE_ADDR 获得 productCatalogServiceComponent 的地址。此外,productCatalogServiceComponent 被标记为 dependencies 字段下的一个依赖项,这可以确保 productCatalogServiceComponent 启动并运行,并且可以用于解析依赖项。

products.bal

复制代码
..
// 推荐服务组件
// 根据购物车中的物品推荐其他产品
cellery:Component recommendationServiceComponent = {
name: "recommendations",
source: {
image: "gcr.io/google-samples/microservices-demo/recommendationservice:v0.1.1"
},
ingresses: {
grpcIngress: <cellery:GRPCIngress>{
backendPort: recommendationsContainerPort,
gatewayPort: 31407
}
},
envVars: {
PORT: {
value: recommendationsContainerPort
},
PRODUCT_CATALOG_SERVICE_ADDR: {
value: cellery:getHost(productCatalogServiceComponent) + ":" + productCatalogContainerPort
},
ENABLE_PROFILER: {
value: 0
}
},
dependencies: {
components: [productCatalogServiceComponent]
}
};
..

单元格以名称 productsCell 定义并初始化,productCatalogServiceComponent 和 preferationservicecomponentare 均被添加到其组件列表中,如下面的代码片段所示。

复制代码
..
// 单元格初始化
cellery:CellImage productsCell = {
components: {
productCatalogServiceComponent: productCatalogServiceComponent,
recommendationServiceComponent: recommendationServiceComponent
}
};
..

单元格间通信

讨论完组件间通信,下面介绍一个单元格中的组件如何与另一个单元格中的组件通信。由于 CBA 要求所有外部传入的通信必须通过单元格网关进行,因此单元格 front-end 的代码的唯一组件是 Web 前端应用程序,并且必须与位于不同单元格中的各种组件通信。

front-end.bal

复制代码
..
cellery:Component frontEndComponent = {
name: "front-end",
source: {
image: "gcr.io/google-samples/microservices-demo/frontend:v0.1.1"
},
ingresses: {
portal: <cellery:WebIngress> { // Web ingress is exposed globally.
port: frontEndPort,
gatewayConfig: {
vhost: "my-hipstershop.com",
context: "/"
}
}
},
..

上面的代码显示了 frontEndComponent 如何暴露一个 HTTP 服务器为 Hipster Shop 网站提供服务。为了与相关的内外部微服务通信,同一个组件需要几个环境变量的值。让我们看一下让 frontendcomponent 可以与单元格 products 的组件进行交互的代码。

复制代码
envVars: {
..
PRODUCT_CATALOG_SERVICE_ADDR: {
value: ""
},
RECOMMENDATION_SERVICE_ADDR: {
value: ""
},
..
},

如上面的代码片段所示,frontEndServiceComponent 需要通过环境变量(envVars)PRODUCT_CATALOG_SERVICE_ADDR 和 RECOMMENDATION_SERVICE_ADDR 分别获得 productCatalogServiceComponent 和 recommendationServiceComponent 的地址。

复制代码
dependencies: {
cells: {
productsCellDep: <cellery:ImageName>{ org: "wso2cellery", name: "products-cell", ver: "latest"},
..
}
}

单元格 front-end 依赖于单元格 products,这种依赖关系是通过上面介绍的 frontEndComponent 中的 dependencies 字段定义的。

复制代码
cellery:Reference productReference = cellery:getReference(frontEndComponent, "productsCellDep");
frontEndComponent.envVars.PRODUCT_CATALOG_SERVICE_ADDR.value = <string>productReference.gateway_host + ":" +<string>productReference.products_grpc_port;
frontEndComponent.envVars.RECOMMENDATION_SERVICE_ADDR.value = <string>productReference.gateway_host + ":" +<string>productReference.recommendations_grpc_port;

方法 cellery: getReference (frontEndComponent productsCellDep) 会提供一个指向已部署的 products 单元格实例的引用,借助这个引用,我们可以解析出上述代码中环境变量 PRODUCT_CATALOG_SERVICE_ADDR 和 RECOMMENDATION_SERVICE_ADDR 的值。类似地,front-end 单元格通过上述方法与其他单元格通信。

剩下的两个单元格定义文件遵循相同的原则,可以从 GitHub 库中获取。

cart.bal
单元格 cart 是一个包含两个组件的独立单元格。

checkout.bal
单元格 checkout 包含五个组件,为了从 checkoutservice 调用 cartservice 和 productcatalogservice,它需要分别与单元格 cart 和 products 通信。

在完成了所有单元格定义的编码后,接下来可以构建和部署这些单元格了。你还可以将部署 Hipster Shop 微服务所需的完整 Kubernetes YAML 文件与 Hipstershop Cellery 代码进行比较,后者不仅在 Kubernetes 上部署微服务,而且还围绕这些微服务创建了基于单元格的架构。

构建和部署单元格

请按照这里的说明构建和运行所有的 Hipster Shop 单元格。

运行独立单元格

现在看一下如何构建和运行 ads 单元格,它是一个独立的单元格。

打开终端,定位到 ads.bal 文件所在的位置,运行以下命令构建 ads 单元格:

复制代码
$ cellery build ads.bal wso2cellery/ads-cell:latest

我们在 Docker Hub 中的组织名称是 wso2cellery,使用 ads-cell 作为单元格镜像的名称,使用 latest 作为标签。执行 build 命令后可以看到如下输出:

复制代码
✔ Building image wso2cellery/ads-cell:latest
✔ Removing old Image
✔ Saving new Image to the Local Repository
✔ Successfully built cell image: wso2cellery/ads-cell:latest
What's next?
--------------------------------------------------------
Execute the following command to run the image:
$ cellery run wso2cellery/ads-cell:latest
--------------------------------------------------------

要以实例名 ads-cell 运行单元格镜像 wso2cellery/ads-cell:latest,运行以下命令:

复制代码
$ cellery run wso2cellery/ads-cell:latest -n ads-cell

可以看到以下输出:

复制代码
✔ Extracting Cell Image wso2cellery/ads-cell:latest
Main Instance: ads-cell
✔ Reading Cell Image wso2cellery/ads-cell:latest
✔ Validating dependencies
Instances to be Used:
INSTANCE NAME CELL IMAGE USED INSTANCE SHARED
--------------- ----------------------------- --------------- --------
ads-cell wso2cellery/ads-cell:latest To be Created -
Dependency Tree to be Used:
No Dependencies
? Do you wish to continue with starting above Cell instances (Y/n)? y
✔ Starting main instance ads-cell
✔ Successfully deployed cell image: wso2cellery/ads-cell:latest
What's next?
--------------------------------------------------------
Execute the following command to list running cells:
$ cellery list instances
--------------------------------------------------------
{1}

运行依赖单元格

现在,以单元格 front-end 为例,看看如何构建和运行依赖于其他单元格的单元格。build 命令与执行单元格 ads 的命令类似。

复制代码
$ cellery build front-end.bal wso2cellery/front-end-cell:latest

然而,在运行有依赖项的单元格镜像时,还必须列出单元格所依赖的其他单元格的运行实例的名称。这从单元格 front-end 的 run 命令可以看出来,如下所示。

复制代码
$ cellery run wso2cellery/front-end-cell:latest -n front-end-cell -l cartCellDep:cart-cell -l productsCellDep:products-cell -l adsCellDep:ads-cell -l checkoutCellDep:checkout-cell -d

输出如下:

复制代码
✔ Extracting Cell Image wso2cellery/front-end-cell:latest
Main Instance: front-end-cell
✔ Reading Cell Image wso2cellery/front-end-cell:latest
⚠ Using a shared instance cart-cell for duplicated alias cartCellDep
⚠ Using a shared instance products-cell for duplicated alias productsCellDep
✔ Validating dependency links
✔ Generating dependency tree
✔ Validating dependency tree
Instances to be Used:
INSTANCE NAME CELL IMAGE USED INSTANCE SHARED
---------------- ----------------------------------- ---------------------- --------
checkout-cell wso2cellery/checkout-cell:latest Available in Runtime -
products-cell wso2cellery/products-cell:latest Available in Runtime Shared
ads-cell wso2cellery/ads-cell:latest Available in Runtime -
cart-cell wso2cellery/cart-cell:latest Available in Runtime Shared
front-end-cell wso2cellery/front-end-cell:latest To be Created -
Dependency Tree to be Used:
front-end-cell
├── checkoutCellDep: checkout-cell
├── productsCellDep: products-cell
├── adsCellDep: ads-cell
└── cartCellDep: cart-cell
? Do you wish to continue with starting above Cell instances (Y/n)? y
✔ Starting dependencies
✔ Starting main instance front-end-cell
✔ Successfully deployed cell image: wso2cellery/front-end-cell:latest
What's next?
--------------------------------------------------------
Execute the following command to list running cells:
$ cellery list instances
--------------------------------------------------------
{1}

还可以使用 view 命令查看单个单元格的图形化表示及其依赖关系。例如,要查看单元格 front-end,请键入以下命令:

复制代码
cellery view wso2cellery/front-end-cell:latest

这会打开一个描述单元格 front-end 的 Web 页面,如图 5 所示。

Cellery:向Kubernetes部署应用程序的代码优先方法

图 5:单元格 front-end 的图形化表示

可观察性

为了监控和排除已部署单元格的故障,Cellery 提供了可观察性工具,包括仪表板。Cellery 仪表板显示了许多单元格视图,其中显示了依赖关系图、单元格的运行时指标、对通过网关的请求的端到端分布式跟踪以及单元格组件。所有指标都是从组件和网关收集的,其中包括与 Kubernetes pod 和节点(包括 CPU、内存、网络和文件系统使用情况)相关的系统指标和请求 / 响应指标(应用程序指标)。

Cellery:向Kubernetes部署应用程序的代码优先方法
Cellery:向Kubernetes部署应用程序的代码优先方法

图 6:Cellery 可观察性仪表板

真得需要 Cellery 吗?

如果你正在寻找这个问题的答案,那么你还需要问问自己,微服务项目是否将是云原生的,以及该项目是否会随着时间的推移而增长和进化。如果答案是肯定的,那么你必须记住,管理数百个松耦合的微服务可能很快就会成为一场噩梦。这就是为什么要设计基于单元格的架构,但是在容器编排平台(如 Kubernetes)上使用 YAML 文件从零开始创建 CBA 绝非易事。这就是 Cellery 的作用所在,它使开发人员能够遵循代码优先的方法,并处理实现 CBA 的潜在复杂性,从而真正利用云原生微服务的优势,避免其中的陷阱。

Cellery网站 GitHub 库中提供了更多有趣的内容和学习材料。

关于作者

Dakshitha Ratnayake是 WSO2 的企业架构师,他在软件开发、解决方案架构和中间件技术方面有超过 10 年的经验。这是作者为 InfoQ 撰写的第一篇文章。

原文链接:

Cellery: A Code-First Approach to Deploy Applications on Kubernetes

评论

发布