本文字数:11047;估计阅读时间:28 分钟
审校:庄晓东(魏庄)
本文在公众号【ClickHouseInc】首发
简介
在 ClickHouse,我们致力于以 API 为先的开发方式来构建 ClickHouse Cloud。用户通过用户界面执行的每个操作都应该可以通过脚本语言执行,从而可以供其他系统利用。这意味着我们最近发布的 Cloud API 也是一个产品,具有通过 swagger 定义其行为的合同,用户可以依赖该合同。尽管我们现有的用户非常期待该 API 的发布,以满足自动化配置和取消配置、定期扩展以及灵活的配置管理等需求,但它也使我们能够开始与工具集成:首先是 Terraform。
在这篇博客文章中,我们将探讨我们的新 Terraform 提供程序以及如何使用它来解决一个常见需求:对需要针对 ClickHouse 实例进行测试的系统进行 CI/CD。作为示例,我们将介绍如何将我们的 Go 客户端测试从单体 ClickHouse Cloud 服务迁移到使用 Terraform,并仅在测试期间提供临时服务。这不仅可以帮助我们降低成本,还可以在客户端和调用之间隔离我们的测试。我们希望其他人也能从这种模式中受益,并为他们的测试基础设施带来成本节约和简化!
Terraform
Terraform 是一个由 HashiCorp 创建的开源基础设施即代码软件工具,它允许用户使用一种称为 HashiCorp 配置语言(HCL)或可选的 JSON 的声明性配置语言来定义基础设施。
基础设施即代码是通过可机器读取的定义文件而不是物理硬件配置或交互式配置工具来管理和配置计算资源的过程。这种方法已经几乎被普遍接受为管理云计算资源的手段。Terraform 作为实现这一过程的工具,已经获得了广泛的用户群和广泛的采用。
为了与 Terraform 集成并允许用户配置 ClickHouse Cloud 服务,必须实现一个提供程序插件,并且最好通过 Hashicorp 注册表提供。
身份验证
由于 ClickHouse 提供程序依赖于 ClickHouse API,因此需要认证密钥才能配置和管理服务。用户可以通过 ClickHouse Cloud 界面创建一个令牌,以及一个密钥。下面展示了这个简单的过程:
用户还应该记录他们的组织 ID,如下所示。
使用提供程序
一旦令牌和密钥被创建,用户可以创建一个 .tf 文件,并声明使用提供程序。为了避免将凭据放在主文件中,token_key、token_secret 和 organization_id 被替换为 Terraform 变量。这些变量又可以在 secret.tfvars 文件中指定,该文件不应提交到源代码控制中。
main.tf
terraform {required_providers {clickhouse = {source = "ClickHouse/clickhouse"version = "0.0.2"}}
}variable "organization_id" {type = string
}variable "token_key" {type = string
}variable "token_secret" {type = string
}provider clickhouse {environment = "production"organization_id = var.organization_idtoken_key = var.token_keytoken_secret = var.token_secret
}
secret.tfvars
token_key = "<token_key>"
token_secret = "<token_secret>"
organization_id = "<organization_id>"
假设用户已安装了 Terraform,可以使用 terraform init 命令安装提供程序。
terraform initInitializing the backend...Initializing provider plugins...
- Finding clickhouse/clickhouse versions matching "0.0.2"...
- Installing clickhouse/clickhouse v0.0.2...
- Installed clickhouse/clickhouse v0.0.2 (self-signed, key ID D7089EE5C6A92ED1)Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.htmlTerraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.Terraform has been successfully initialized!
配置好我们的提供程序后,可以通过在上面的文件中添加几行 HCL 代码来部署 ClickHouse Cloud 服务。
variable "service_password" {type = string
}resource "clickhouse_service" "service" {name = "example-service"cloud_provider = "aws"region = "us-east-2"tier = "development"idle_scaling = truepassword = var.service_passwordip_access = [{source = "0.0.0.0/0"description = "Anywhere"}]
}output "CLICKHOUSE_HOST" {value = clickhouse_service.service.endpoints.0.host
}
在这里,我们指定了我们期望的云提供商、区域和层级。层级可以是开发版或生产版。开发版代表 ClickHouse Cloud 中的入门产品,适用于较小的工作负载和入门项目。对于上述示例,我们启用了 idling,这样我们的服务在未使用时不会产生费用。
启用 idling_scaling 是开发版实例的唯一有效值,即不能禁用它。提供程序的未来版本将验证此设置。
我们还必须指定一个服务名称和一个可访问此服务的 IP 地址列表(在我们的示例中是任何地方),以及服务的密码。我们再次将此抽象为我们的秘密文件中的一个变量。
我们的输出声明将我们服务的端点捕获为 CLICKHOUSE_HOST 输出变量,确保一旦服务准备就绪,获取连接详细信息就变得简单了。完整的示例 main.tf 文件可以在此处找到【https://pastila.nl/?025ef1fd/d369943908f299267b8b8d488c230380】。
通过单个命令 terraform apply 加上 -var-file 选项来传递我们的秘密文件,即可部署此服务。
terraform apply -var-file=secrets.tfvarsTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:+ createTerraform will perform the following actions:# clickhouse_service.service will be created+ resource "clickhouse_service" "service" {+ cloud_provider = "aws"+ endpoints = (known after apply)+ id = (known after apply)+ idle_scaling = true+ ip_access = [+ {+ description = "Anywhere"+ source = "0.0.0.0/0"},]+ last_updated = (known after apply)+ name = "example-service"+ password = (sensitive value)+ region = "us-east-2"+ tier = "development"}Plan: 1 to add, 0 to change, 0 to destroy.Do you want to perform these actions?Terraform will perform the actions described above.Only 'yes' will be accepted to approve.Enter a value: yesclickhouse_service.service: Creating...
clickhouse_service.service: Still creating... [10s elapsed]
clickhouse_service.service: Still creating... [20s elapsed]
clickhouse_service.service: Still creating... [30s elapsed]
clickhouse_service.service: Still creating... [40s elapsed]
clickhouse_service.service: Still creating... [50s elapsed]
clickhouse_service.service: Still creating... [1m0s elapsed]
clickhouse_service.service: Still creating... [1m10s elapsed]
clickhouse_service.service: Creation complete after 1m12s [id=fd72178b-931e-4571-a0d8-6fb1302cfd4f]Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
如上所示,Terraform根据定义构建了一个计划,然后才提供服务。由于之前配置了输出,因此也会打印分配给我们服务的主机名。可以使用 terraform destroy 命令删除上述服务。
为了使Terraform对一组资源应用更改,它需要一种获取当前状态的手段,包括已提供的资源及其配置。这在一个“状态”中描述,其中包含资源的完整描述。这允许随着时间的推移对资源进行更改,每个命令都能确定采取的适当操作。在我们的简单案例中,我们将这个状态保存在上述命令运行的文件夹中。然而,状态管理是一个更加复杂的话题,有许多维护它的方法适用于真实环境,包括使用HashiCorp的云服务。当预计会有多个个体或系统在任何给定时间操作状态并且需要并发控制时,这一点尤为重要。
一个实际的CI/CD例子
将Terraform添加到Github操作中
针对ClickHouse Cloud进行测试对于向用户提供高质量的客户端至关重要。在Terraform提供程序可用之前,我们的ClickHouse客户端是针对ClickHouse Cloud中的单个服务进行测试的,测试由Github操作进行编排。这个实例被共享给我们的客户端,每个客户端在针对存储库进行PR或提交时都会创建自己的数据库和表。虽然这是足够的,但它也存在一些局限性:
-
单点故障。任何与此服务相关的问题,例如由于区域可用性引起的问题,都会导致所有测试失败。
-
资源冲突。通过确保所有资源(例如表)都遵循使用客户端名称和时间戳的命名约定来避免,但这会产生后果(见下文)。
-
资源增长和测试复杂性。确保测试可以并行运行意味着确保特定测试使用的表、数据库和用户是唯一的,以避免冲突 - 这需要在所有客户端中保持一致的样板代码。当与需要大量测试以确保在ClickHouse中覆盖功能的客户端结合使用时,这意味着可能会创建数百个表。需要进一步的测试编排以确保每个客户端在完成时删除这些表,以避免表爆炸 - 或许并不令人意外,ClickHouse并不适用于10k个表!
-
成本效率低。尽管上述测试的查询负载并不重大,但我们的服务实际上一直处于活动状态,并且由于大量并发的DDL操作可能导致潜在的高zookeeper负载。这意味着我们使用了生产服务。此外,我们的测试需要对服务处于空闲状态进行健壮处理,以防止服务关闭。
-
可观察性的复杂性。随着许多客户端和多个测试的运行,使用服务器日志调试测试失败变得更加复杂。
Terraform提供程序承诺提供这些问题的简单解决方案,每个客户端只需在测试开始时创建一个服务,运行其测试套件,并在完成后销毁服务。因此,我们的测试服务变得短暂。
这种方法具有许多优势:
-
测试隔离性 - 尽管测试仍然容易受到某个区域ClickHouse Cloud不可用的影响,但已经能够抵御服务问题,例如,客户端触发ClickHouse错误导致整个服务出现问题,或者客户端测试导致整个服务的配置更改。我们的客户端测试立即得到了隔离。
-
没有资源增长和简化的测试 - 我们的服务仅存在于测试运行的生命周期内。客户端开发人员现在只需考虑由于自己的测试并发引起的资源冲突的可能性。他们还可以对整个服务进行配置更改,从而可能简化测试。
-
成本效率低 - 较小的(dev)服务可以创建并仅存在几分钟(在大多数情况下<10分钟),从而最大程度地减少成本。
-
简单的可观察性 - 虽然我们在测试完成时销毁服务,但服务ID会被记录下来。如果需要,可以在我们的可观察性系统中使用它来检索服务器日志。
现有工作流程
对于我们的第一个客户端,我们选择了Clickhouse Go,使用简单的Github操作,并且在代码的测试套件中封装了大量的测试复杂性。
Github操作提供了一个基于工作流的简单CI/CD平台。与Github紧密集成,用户只需在.github/workflow目录下以yml文件的形式声明性地创建工作流,每个工作流包含要运行的作业。这些作业由步骤组成,可以配置为按计划运行或针对特定事件运行,例如PR。
现有的Cloud测试包括一个配置为针对上述单体服务运行的作业。测试套件已经支持通过环境变量CLICKHOUSE_HOST和CLICKHOUSE_PASSWORD指定要执行测试的ClickHouse实例。这些变量通过Github Secrets进行填充。这还需要将环境变量CLICKHOUSE_USE_DOCKER设置为false,以禁用现有的基于docker的测试。
除了这些特定的更改之外,云测试与基于docker的单节点测试类似 - 使用矩阵来测试客户端针对不同的go版本,并使用步骤来检出代码并在运行测试之前安装go。
integration-tests-cloud:runs-on: ubuntu-lateststrategy:max-parallel: 1fail-fast: truematrix:go:- "1.19"- "1.20"steps:- uses: actions/checkout@main- name: Install Go ${{ matrix.go }}uses: actions/setup-go@v2.1.5with:stable: falsego-version: ${{ matrix.go }}- name: Run testsenv:CLICKHOUSE_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST }}CLICKHOUSE_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD }}CLICKHOUSE_USE_DOCKER: falseCLICKHOUSE_USE_SSL: truerun: |CLICKHOUSE_DIAL_TIMEOUT=20 CLICKHOUSE_TEST_TIMEOUT=600s CLICKHOUSE_QUORUM_INSERT=3 make test
新工作流程
在迁移工作流程之前,我们需要一个简单的Terraform资源定义,用于Clickhouse服务。以下是在之前相同示例的基础上构建的,创建了一个处于开发层的服务,但引入了organization_id、token_key、token_secret、service_name和service_password的变量。我们还输出了服务ID,以帮助后续调试,并允许我们的服务从任何地方都可以访问 - 由于其临时性质,安全风险很低。以下main.tf文件存储在clickhouse-go客户端的根目录中。
terraform {required_providers {clickhouse = {source = "ClickHouse/clickhouse"version = "0.0.2"}}
}variable "organization_id" {type = string
}variable "token_key" {type = string
}variable "token_secret" {type = string
}variable "service_name" {type = string
}variable "service_password" {type = string
}provider clickhouse {environment = "production"organization_id = var.organization_idtoken_key = var.token_keytoken_secret = var.token_secret
}resource "clickhouse_service" "service" {name = var.service_namecloud_provider = "aws"region = "us-east-2"tier = "development"idle_scaling = truepassword = var.service_passwordip_access = [{source = "0.0.0.0/0"description = "Anywhere"}]
}output "CLICKHOUSE_HOST" {value = clickhouse_service.service.endpoints.0.host
}output "SERVICE_ID" {value = clickhouse_service.service.id
}
Terraform支持通过以TF_VAR_为前缀的环境变量指定变量值。例如,要填充组织ID,我们只需设置TF_VAR_organization_id。
与以前的工作流程类似,这些环境变量的值可以通过Github加密密钥填充。在我们的情况下,我们在组织级别创建这些密钥,以便它们可以跨客户端和服务共享,这样可以简化管理。
注意:我们在这里没有为服务名称设置值。除了不敏感之外,我们还希望确保这些名称在测试运行中是唯一的,这样我们就可以识别服务的来源和创建时间。
为了使Terraform在runner上可用,我们使用了hashicorp/setup-terraform操作。这会在Github操作CLI runner上安装Terraform,并公开其CLI,以便我们可以像从终端中一样进行调用。
我们的最终工作流如下所示:
integration-tests-cloud:runs-on: ubuntu-latestdefaults:run:shell: bashstrategy:max-parallel: 1fail-fast: truematrix:go:- "1.19"- "1.20"steps:- name: Check Out Codeuses: actions/checkout@v3- name: Setup Terraformuses: hashicorp/setup-terraform@v2.0.3with:terraform_version: 1.3.4terraform_wrapper: false- name: Terraform Initid: initrun: terraform init- name: Terraform Validateid: validaterun: terraform validate -no-color- name: Set Service Namerun: echo "TF_VAR_service_name=go_client_tests_$(date +'%Y_%m_%d_%H_%M_%S')" >> $GITHUB_ENV- name: Terraform Applyid: applyrun: terraform apply -no-color -auto-approveenv:TF_VAR_organization_id: ${{ secrets.INTEGRATIONS_TEAM_TESTS_ORGANIZATION_ID }}TF_VAR_token_key: ${{ secrets.INTEGRATIONS_TEAM_TESTS_TOKEN_KEY }}TF_VAR_token_secret: ${{ secrets.INTEGRATIONS_TEAM_TESTS_TOKEN_SECRET }}TF_VAR_service_password: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD }}- name: Set Hostrun: echo "CLICKHOUSE_HOST=$(terraform output -raw CLICKHOUSE_HOST)" >> $GITHUB_ENV- name: Service Idrun: terraform output -raw SERVICE_ID- name: Install Go ${{ matrix.go }}uses: actions/setup-go@v2.1.5with:stable: falsego-version: ${{ matrix.go }}- name: Run testsenv:CLICKHOUSE_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD }}CLICKHOUSE_USE_DOCKER: falseCLICKHOUSE_USE_SSL: truerun: |CLICKHOUSE_DIAL_TIMEOUT=20 CLICKHOUSE_TEST_TIMEOUT=600s CLICKHOUSE_QUORUM_INSERT=2 make test- name: Cleanupif: always()run: terraform destroy -no-color -auto-approveenv:TF_VAR_organization_id: ${{ secrets.INTEGRATIONS_TEAM_TESTS_ORGANIZATION_ID }}TF_VAR_token_key: ${{ secrets.INTEGRATIONS_TEAM_TESTS_TOKEN_KEY }}TF_VAR_token_secret: ${{ secrets.INTEGRATIONS_TEAM_TESTS_TOKEN_SECRET }}TF_VAR_service_password: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD }}
总结一下,这个工作流程包括以下步骤:
-
通过 uses: actions/checkout@v3 将现有代码检出到runner上。
-
通过 uses: hashicorp/setup-terraform@v2.0.3 在runner上安装terraform。
-
调用 terraform init 来安装ClickHouse provider.
-
通过 terraform validate 命令验证已检出代码根目录中的terraform资源定义文件。
-
将环境变量 TF_VAR_service_name 设置为日期字符串,前缀为 go_client_tests_。这样确保我们的服务在客户端和测试运行中具有唯一的名称,并有助于调试。
-
运行 terraform apply 创建一个具有指定密码的Cloud服务,组织ID、令牌和密钥通过环境变量传递。
-
将 CLICKHOUSE_HOST 环境变量设置为前面应用步骤的输出值。
-
捕获服务ID以供调试目的。
-
基于当前的矩阵版本安装go。
-
运行测试 - 注意上面已设置 CLICKHOUSE_HOST。敏锐的读者会注意到我们传递环境变量使测试类似于我们之前的工作流程,以增加超时时间。但是,我们将 CLICKHOUSE_QUORUM_INSERT 降低到2。这是必需的,因为一些测试需要在查询之前在所有节点上存在数据。虽然我们之前的单体服务有三个节点,但我们较小的开发服务只有两个。
-
通过 terraform destroy 命令销毁服务,不管工作流是否成功(if: always())。
这些更改现在已生效!每当对存储库发出PR或提交时,更改将针对一个临时的ClickHouse Cloud集群进行测试!
目前,这些测试不适用于从分支提出的PR(这需要ClickHouse组织的成员)。这是pull_request事件的标准Github策略,因为它可能导致秘密泄漏。我们计划在未来的改进中解决这个问题
结论
在本博文中,我们使用了新的Terraform提供程序为ClickHouse Cloud构建了一个CI/CD工作流,在Github actions中为测试提供临时集群。我们使用这种方法来降低在ClickHouse Cloud中进行客户端测试的成本和复杂性。
征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com
联系我们
手机号:13910395701
邮箱:Tracy.Wang@clickhouse.com
满足您所有的在线分析列式数据库管理需求