我们很高兴宣布 ClickHouse Cloud 在 Azure 上正式发布。
在这篇博文中,我们将详细讨论我们是如何构建这项新服务的,包括我们遇到的挑战以及如何克服它们。
ClickHouse Cloud 的简史
我们于 2022 年 12 月在 AWS 上 发布了 ClickHouse Cloud。仅仅几个月后,我们就扩展了对 GCP 上的 ClickHouse Cloud 的支持。我们 Azure 的发布对我们来说是一个重要的里程碑,因为 ClickHouse Cloud 现在已在所有三大云提供商上可用。
ClickHouse Cloud 的架构
在接下来的几节中,我们将介绍 ClickHouse Cloud 的架构和组件,然后再讨论 Azure 特定的差异。
在 ClickHouse Cloud 中,我们采用了“共享一切”的架构,并“分离存储和计算”。这意味着存储和计算是解耦的,可以分别扩展。我们使用对象存储(如 Azure Blob 存储)作为分析数据的首选存储方式,而本地磁盘仅用于缓存、元数据和临时存储。
该架构旨在尽可能地与云服务提供商 (CSP) 中立,以便可以在不同的 CSP(如 AWS、GCP 和 Azure)之间重复使用。
下图显示了该架构的示意图。
ClickHouse Cloud 组件
ClickHouse Cloud 的组件可以最好地描述为两个不同的独立逻辑单元。
- 控制平面 - “面向用户”层:这是允许用户在云上运行其操作、授予其 ClickHouse 服务的访问权限以及允许其与数据交互的 UI 和 API 层。
- 数据平面 - “面向基础设施”部分:管理和编排物理 ClickHouse 集群的功能,包括资源分配、预置、更新、扩展、负载均衡、将不同租户的服务隔离、备份和恢复、可观察性和计量(收集使用数据)。
下图说明了这些 ClickHouse Cloud 组件及其交互。
我们的控制平面构建在 AWS 中,并与 AWS、GCP 和 Azure 中的数据平面交互。我们没有为每个 CSP 设置不同的控制平面。因此,在这篇博文的其余部分中,我们将重点介绍数据平面架构。
多云 - Azure
我们设计 ClickHouse Cloud 是为了实现 CSP 独立性。从广义上讲,这对于所有三大 CSP(AWS/GCP/Azure)都是成立的。但是,每个 CSP 都有其自身的特点和差异。因此,在我们的架构中有一些领域需要我们适应底层 CSP。在本节中,我们将讨论我们在将我们的架构适应 Azure 时遇到的挑战。
ClickHouse Cloud 中的 CSP 组件
如上图所示,我们的数据平面由几个关键组件组成。
- 计算:我们使用托管的 Kubernetes,例如 AWS EKS/GCP GKE/Azure AKS,作为 ClickHouse 集群的计算层。我们使用 Kubernetes 命名空间 + Cilium 来确保为我们的客户提供适当的租户隔离。
- 存储:我们使用对象存储,例如 AWS S3/GCP 云存储/Azure Blob 存储,来存储 ClickHouse 集群中的数据。每个 ClickHouse 集群都有自己的专用存储,以确保租户隔离。
- 网络:每个区域中的 ClickHouse Cloud 基础设施运行在私有的、逻辑隔离的虚拟网络中,该区域中的所有计算都通过此网络连接。在这里,我们也使用 CSP 特定的解决方案,例如 AWS VPC/GCP VPC/Azure VNET
- 身份和访问管理 (IAM):最后,为了使 ClickHouse Cloud 集群计算能够与相应的对象存储进行通信,我们使用 CSP IAM 来提供访问权限并确保正确的授权。每个 ClickHouse Cloud 集群都有自己的身份,并且只有此身份才能访问数据。对于身份和访问管理,我们利用 CSP 产品,例如 AWS IAM/GCP IAM/Microsoft Entra(以前称为 Azure Active Directory)。
虽然每个 CSP 都有其特定的解决方案,但理想情况下,我们应该能够设计通用接口并抽象出大多数复杂性。但是,在现实中,虽然某些组件(如计算 (Kubernetes))在 CSP 之间是无缝的,但并非所有组件都那么容易。
对象存储就是一个例子。Azure Blob 存储在 API 和实现方面与 AWS S3 相当不同。Blob 存储还对存储桶具有不同的基于权限的访问控制。这导致我们在为每个集群组织存储的方式上存在差异。这是更容易解决的差异之一。下面将详细讨论网络设置等更复杂的组件。
我们没有预料到的一点是云资源(例如 VM、身份和存储桶)在 Azure 中的组织方式。我们来讨论一下为什么会出现这种情况。
云资源组织
在 AWS 中,资源组织在帐户下,帐户是所有资源的首要容器。在更高级别,AWS Organizations 允许您集中管理多个帐户,提供合并的计费和集中访问控制。这形成了一个两级层次结构:单个帐户和组织。
类似地,GCP 采用三级层次结构,从最上层开始,是 Organizations,它包含 Projects,以及可选的 Folders 以供进一步组织。Projects 是资源的首要容器,表示计费实体,而 Folders 允许对 Projects 进行分层分组。
另一方面,Azure 具有更复杂的资源层次结构。最顶层是租户,它表示一个专用的 Microsoft Entra ID(以前称为 Azure AD)实例,并在多个订阅之间提供身份管理。在租户下,管理组跨多个订阅管理访问权限、策略和合规性。订阅是用于计费和资源管理的容器,位于管理组下。此外,资源在每个订阅内组织为资源组,资源组是相关资源的逻辑容器。这形成了一个四级层次结构:租户、管理组、订阅和资源组。
细心的读者可能会问,我们是否在不必要地复杂化问题。由于 Azure 中的资源组通常用于一起创建/删除的资源,因此将每个客户的 ClickHouse 集群创建在不同的资源组中,并在 Azure 中每个 ClickHouse Cloud 区域创建一个订阅,所有资源组都存在于该订阅下,这似乎是合乎逻辑的。然而,这并不简单,因为我们需要考虑资源限制。虽然 AWS/GCP 也存在资源限制,但 Azure 资源限制更为广泛,并且适用于更广泛的资源。
例如,在 Azure 中,每个订阅最多可创建 980 个资源组。假设每个区域有一个订阅,我们最多只能创建 980 个集群,或者在一个区域中拥有超过 980 个客户。考虑到我们的规模,我们很可能很快就会遇到这个限制。
同样,在 Azure 中,每个订阅最多可创建 500 个存储帐户(根据请求,默认值为 250)。如果我们在 Azure 中为每个 ClickHouse 实例创建一个单独的存储帐户,我们无法支持超过 500 个客户在一个区域内。同样,这种设计不具有未来可扩展性。因此,每个区域一个订阅行不通。
在我们设计过程中,我们与 Microsoft Azure 和 AKS 团队的架构师和工程师取得了联系,他们在解决这些差异方面提供了很大帮助。我们与他们讨论了设计理念,以确保在扩展时不会遇到这些限制。我们与 Azure 团队讨论的第一个问题是——我们是否应该为每个客户创建一个订阅,以避免遇到上述资源限制?然而,这种设计并不理想,因为订阅被认为是用于存储资源和分组的逻辑容器,以帮助进行计费、使用情况等。在同一租户下创建数千个订阅在 Azure 中不可取。
与 Azure 团队合作,我们设计了一种解决方案,我们为客户 ClickHouse 集群创建了一个Azure 订阅池。每当客户创建新实例时,该实例就会被随机分配到从池中选出的一个订阅,并在该订阅中创建一个新的资源组。随后,客户实例的存储帐户、IAM 等将在该资源组中创建。
这种设计解决了我们之前提到的资源限制问题。由于我们现在有#X 个订阅,上面提到的所有限制都增加了#X 倍,并且由于X 由我们控制,因此这种设计具有很强的可扩展性。资源组和存储帐户仍然对资源有限制,但它们在很大程度上符合我们的要求,因为这些限制是针对每个客户的,因此我们不太可能遇到这些限制。
即使有了这种设计,也有一些对资源的硬性限制,例如 Kubernetes 集群大小(节点数)、IP 限制等,这些限制可能会在扩展时造成问题。
下一节将解释我们如何使用蜂窝结构解决这些问题。
扩展 ClickHouse Cloud
每个公共云提供商都会施加各种限制,在构建服务时必须考虑这些限制。此外,依赖单一大型 Kubernetes 集群通常不可取。
为了确保我们的云能够有效扩展,我们可以使用多种方法
- 在一个 VNET 中部署多个 AKS 集群。
- 为不同的 AKS 集群部署多个 VNET。
除了公共云级别的限制和配额外,Kubernetes 还拥有自己的约束。
截至 Kubernetes v1.30,适用以下限制
- 集群最多可拥有 5,000 个节点。
- 每个节点最多可拥有 110 个 Pod。
- 总 Pod 数不超过 150,000 个。
- 总容器数不超过 300,000 个。
我们必须做好准备,扩展我们的云基础设施以满足需求。
为了应对这一挑战,我们在每个区域中使用一个名为“单元”的概念。这种方法对我们来说并不陌生;我们已经使用它来规避 AWS 帐户限制。
什么是单元?
在 ClickHouse Cloud 中,单元是一个独立的环境,拥有自己的网络和 Kubernetes 集群。单元独立运行,彼此之间没有依赖关系。可以将它们视为一种分片机制。使用 DNS 记录将客户端连接定向到相应的单元,这使得路由变得简单。
如果有必要,我们始终可以在区域中部署一个新的单元,并在该单元中创建新的 ClickHouse Cloud 服务。此过程对最终用户是不可见的。
网络
任何云基础设施的核心都是它的网络堆栈。在 Azure 中,它被称为VNET。与 AWS 不同,AWS 中的子网属于特定可用区,而 Azure 的子网则不属于。这类似于 Google 的云平台 (GCP) 方法。
这种设计提供了一些优势。例如,它简化了 Azure Kubernetes 服务 (AKS) 节点组的配置。我们可以创建一个固定到多个可用区的单个 AKS 节点组(我们使用三个可用区运行 ClickHouse Cloud 服务)。然后,Kubernetes 将管理 Pod 在这些可用区中的分配。
然而,尽管有这些优势,但一些限制阻止我们充分利用此优势,将在下面进行解释。
AKS 网络
自 2022 年在 AWS 上推出我们的服务以来,我们一直在使用 Cilium 作为我们的网络 CNI。我们使用 Cilium 是因为它利用了 eBPF,这可以确保高吞吐量、降低延迟和降低资源消耗,尤其是在管理大量服务时。您可以在我们的博客文章“从头开始构建 ClickHouse Cloud 一年”中找到有关此选择的更多详细信息。从我们 Azure 项目开始,使用 Cilium 就是理所当然的。我们唯一需要决定的就是使用自管理版本(BYOCNI)还是 AKS 支持的管理版本。
Azure Kubernetes 服务 (AKS) 支持管理型 Cilium,也可以使用自带容器网络接口 (BYOCNI) 插件安装自管理型 Cilium。
在我们的 GCP 实施中,我们决定使用管理型 Cilium,这有助于我们减少维护负担。在 GCP 上运行 Cilium 一年后,我们选择在 Azure 中使用相同的方法。您可以在 Azure 中找到有关管理型 Cilium 的详细信息此处。
关于我们在 Azure 中的管理型 Cilium 设置的另一个重要说明是,我们不使用覆盖网络。相反,部署在 AKS 中的 Pod 使用来自 VNET 的 IP 地址,这对于支持跨集群通信是必要的。
NAT 网关故事
如前所述,Azure 中的子网未固定到特定可用区,简化了网络基础设施。我们开始着手实施 NAT 网关,但后来意识到 Azure 提供的简单模型对我们不起作用。由于 Azure 的 NAT 网关实现,我们需要采用类似于 AWS 的子网策略的方法,每个 AZ 一个子网。
Azure NAT 网关处理来自 VNET 的出站连接。对于 ClickHouse Cloud 而言,这是一个关键组件,因为我们的客户从各种来源摄取数据,例如
- Azure Blob 存储
- S3 存储桶
- GCS 存储桶
- HTTP 服务器
- MySQL/Postgres 数据库
- Kafka 集群
- 还有更多!
如果连接不属于“本地”Azure 存储帐户,它将通过 NAT 网关路由。高效且容错的数据摄取对于 ClickHouse Cloud 至关重要。
Azure NAT 网关是一种区域性资源。如果我们只在一个子网中使用 AZ 1 中的 NAT 网关,那么 AZ 1 中的故障将影响 ClickHouse Cloud 中的整个区域。
因此,我们决定部署区域性 NAT 网关,以确保 ClickHouse Cloud 的可靠性
注意:您可以在此处找到有关 Azure NAT 网关部署模型的更多详细信息。
此设置使 AKS 节点组配置变得复杂:我们没有一个节点组,而是必须在 AKS 中创建三个节点组。每个节点组都固定到特定的可用区和子网。
甚至更多子网
在 ClickHouse Cloud 中,我们在一个区域中运行多个 Kubernetes 集群
- 数据平面集群:托管 ClickHouse/ClickHouse Keeper 实例。
- 数据平面管理集群:托管用于管理 ClickHouse Cloud 并为控制平面团队公开 API 端点的软件。
- 代理集群:托管 Istio 代理。所有传入的客户连接都将在此集群中终止,然后转发到数据平面集群。
在 AWS 和 GCP 中,使用安全组很容易控制集群之间的网络隔离。不幸的是,在设计 ClickHouse Cloud 时,Azure 并非如此。
虽然应用程序安全组可能有所帮助,但它们不适用于 AKS。为了实现集群之间的网络隔离,我们为每个 AKS 集群创建了三个子网(由于 NAT 网关限制,每个 AZ 一个子网)。这种方法增加了我们网络基础设施的复杂性,但允许我们使用网络安全组进行网络过滤。
总结一下
- AKS 集群之间的所有流量都由 VNET 防火墙规则过滤。
- 每个集群内的流量都由 Kubernetes 网络策略过滤。
管理的 Kubernetes 入口
除了我们的代理集群之外,我们还公开了其他端点以满足内部需求,例如数据平面 API 服务。
在 AWS 和 GCP 中,我们使用管理的入口来终止 TLS 并过滤流量 (WAF)。Azure 中最接近的等效项是Azure 应用程序网关 v2,但它需要基础设施更改,例如专用子网,这超出了我们的需求。因此,我们最初选择在 Azure 中使用 NGINX 入口控制器。这工作正常,直到我们开始实施高级流量过滤配置。使用 NGINX 管理这些配置很困难,而使用 Istio 完成此任务则容易得多,尤其是在我们已经拥有 Istio 专业知识的情况下。
我们从 NGINX 迁移到 Istio 入口以用于我们的 API 服务器,并且它运行得非常完美。我们计划从 AWS 和 GCP 中的管理的入口解决方案迁移到 Istio。Istio与证书管理器和外部 DNS一起使用效果很好。
ClickHouse 中的 Azure 支持
我们使用 Azure C++ SDK 与 ClickHouse 中的 Azure Blob 存储进行交互,并使用工作负载标识进行身份验证。工作负载标识是 Azure AD Pod 标识的下一代,它使 Kubernetes 应用程序能够安全地访问 Azure 云资源。
为了从 Azure Blob 存储中读取文件,我们使用从流中下载和读取的方法。这使其效率很高,尤其是在处理大型文件时,因为我们不必下载整个文件,并且可以继续按部分或根据需要读取。我们还在可行的情况下并行读取文件。
为了写入 Azure Blob 存储,我们对大型文件使用分块上传,并在可能的情况下利用单部分上传以提高速度。结合我们的异步和并行写入,这意味着我们可以高效地写入文件。对于复制文件,我们可以配置 ClickHouse 使用本机复制,它直接复制文件,而无需读取和写入它们。当使用读取+写入复制大型文件时,我们会按部分读取文件并将它们写入 Azure blob 存储,以便有效地处理内存。
我们在调整重试机制后,看到了显著的性能提升。Azure SDK 存在较长的回退延迟,缩短延迟改善了性能。现在,我们还添加了配置这些延迟的选项。
OpenSSL
工作负载身份凭据仅在 Azure C++ SDK 1.8 版本及更高版本中受支持。由于当时 ClickHouse 使用的是旧版本的 SDK(1.3 版本),因此我们开始升级 SDK。升级到1.7 版本没有问题。
然而,升级到 1.8 版本遇到了问题。
这是因为 Azure SDK v1.8 引入了对OpenSSL 的依赖,将其作为加密库,我们无法轻松地将其修补掉。我们也不愿意更改第三方代码,特别是在安全相关领域。在此之前,ClickHouse(以及 Azure)是针对 boringssl 编译的,它是 Google 对 OpenSSL 的分支,是在多年前Heartbleed 漏洞出现后创建的。
因此,我们着手将 ClickHouse 从 boringssl 迁移到 OpenSSL。作为如此重要的基础库,这次迁移相当复杂。我们将重点介绍在此过渡过程中突出的两个显著问题。
我们最初的迁移尝试涉及 OpenSSL 3.0 版本。在让 OpenSSL 在 ClickHouse 的平台(例如,x86、ARM、PPC、RISC-V 等)上构建后,所有功能测试都通过了,除了 ClickHouse 的加密编解码器的这个测试。结果表明,这些编解码器基于现代抗误用AES-GCM-SIV 密码,OpenSSL 仅从3.2 版本开始支持这些密码。为了保持与使用加密编解码器编码的现有用户数据的兼容性,我们不得不从使用 OpenSSL 3.2 版本开始重新构建。
另一类问题与“代码清理”相关,这是一种使用特殊工具执行代码以查找棘手和罕见错误的技术。ClickHouse 以在其使用的所有第三方库中运行大多数测试并频繁地发现错误而闻名。这一次也不例外 - 测试揭示了 venerable OpenSSL 代码库中的错误!我们向上游报告了这些问题,并迅速得到了解决,例如,这里 和 这里。这就是开源的力量!
性能基准测试
ClickHouse 是世界上最快的数据库之一。性能对我们至关重要。因此,在我们构建了基于 Azure 的 ClickHouse Cloud 后,我们迫不及待地想将其与我们基于 AWS/GCP 的产品进行基准测试。您可能听说过我们便捷的开源工具 ClickBench,它被广泛用于性能基准测试。使用此工具,我们进行了多轮改进,以针对 Azure Blob 存储调整 ClickHouse。上面部分提到了部分优化。查看最终结果,Azure 在 ClickBench 中表现出色。公共 ClickBench 结果可以在此处找到 - https://benchmark.clickhouse.com/.
在热运行中,Azure 在 ClickBench 中是所有 3 种云中速度最快的。
在冷运行中,Azure 比 GCP 快,比 AWS 慢,但总体表现非常出色。
这些结果让我们相信,我们的 Azure 产品在性能方面与其他云相当。
Azure 市场
最后但并非最不重要的是,让我们讨论一下计费。您可以直接注册ClickHouse Cloud,也可以通过Azure 市场注册。通过市场,您可以将所有其他 Azure 资源(ClickHouse Cloud 组织及其资源)与您的 Azure 订阅绑定,从而实现统一计费。我们与 Azure 市场集成,使您可以选择按 PAYG(按使用付费)方式支付 Azure 消耗,或在指定时间段内签署承诺合同。
如果您的组织与 Azure 签订了预先承诺的支出协议,则您可能能够将部分承诺支出用于 Azure 上的 ClickHouse Cloud 消耗。
要点和结论
当我们着手在 Azure 上构建 ClickHouse Cloud 时,我们预计,由于我们已经在 AWS/GCP 上构建了服务,因此我们可以将很多学习成果和架构应用到 Azure 中,从而简化构建过程。虽然其中许多内容确实适用,但我们也遇到了一些意想不到的复杂情况和设置差异。在本博文中,我们讨论了一些重要的差异,例如资源组织、资源限制、网络设置等。我们还得到了 Microsoft Azure 团队的大力帮助,确保我们能够在遵循 Azure 最佳实践的同时设计可扩展性。
我们也期待看到 Azure 平台的一些改进。出于可靠性考虑,每个 ClickHouse Cloud 集群都将在 3 个可用区部署了 pod。但是,并非所有 Azure 区域都支持 3 个可用区。将来,这将是 Azure 的一项功能,有助于提高可靠性。
最后,如果您觉得本博文有趣,请查看我们产品副总裁 Tanya Bragin 在 Microsoft Build 上发表的这个演讲,内容是我们如何在 Microsoft Build 上使用 AKS 为 Azure 上的 ClickHouse Cloud。
本博文由 ClickHouse Cloud 团队的 Vinay Suryadevara、Timur Solodovnikov、Smita Kulkarni 和 Robert Schulze 共同撰写。