ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Aptos Forge K8s Deployer 后端:以 Kubernetes Job 驱动测试基础设施部署的完整指南

Aptos Forge K8s Deployer 后端:以 Kubernetes Job 驱动测试基础设施部署的完整指南 Aptos Forge K8s Deployer 后端以 Kubernetes Job 驱动测试基础设施部署的完整指南【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core导读Aptos Forge 是 aptos-core 仓库中的端到端测试框架而 K8s Deployer 后端testsuite/forge/src/backend/k8s_deployer/是它在 Kubernetes 上自动拉起测试环境的部署器管理中枢。本文以 testsuite/forge/src/backend/k8s_deployer/README.md 为核心结合 deployer.rs、constants.rs 等源码系统讲解 Forge Deployer 的设计模型、FORGE_DEPLOY_VALUES_JSON定制机制、Job/ConfigMap 的编排细节、ForgeDeployerManager生命周期管理以及 testnet、indexer、PFN 三类组件如何并行部署。读完本文你将掌握该后端以 Job 驱动组件部署、以 ConfigMap 传递配置、以命名空间隔离测试环境的完整实现原理与使用方式。一、背景Forge 测试框架中的部署器角色Forge 是 Aptos 的混沌测试与性能测试框架测试执行前需要一套完整的链上基础设施验证节点、全节点、索引器栈、公网全节点等。K8s Deployer 后端负责管理这些deployer——它们是运行在 Kubernetes 集群中的 Job负责拉起 Forge 测试所需的全部基础设施。从源码结构看Forge 拥有多个后端testsuite/forge/src/backend/ 下包含k8s、k8s_deployer、local三个子模块其中k8s_deployer在 backend/mod.rs 中被声明并公开导出mod k8s_deployer; pub use k8s_deployer::*;k8s_deployer模块只有三个文件职责非常聚焦README.md模块设计说明constants.rs镜像仓库、服务账号、环境变量名等常量定义deployer.rs核心管理器ForgeDeployerManager及其单元测试。二、Forge Deployer 的三个核心设计约束2.1 每个 Deployer 只部署一个组件每个 Forge deployer 只负责部署 Forge 基础设施中的单一组件且该组件可能依赖其他组件或资源。例如 indexer 栈依赖已存在的 testnet 栈才能运行。这种一 Job 一组件、组件间依赖的模型让部署可以按需组合、并行推进也便于单独重试。2.2 通过环境变量FORGE_DEPLOY_VALUES_JSON注入定制值Deployer 可以接收定制化配置注入通道是名为FORGE_DEPLOY_VALUES_JSON的环境变量。该变量名在 constants.rs 中定义/// This is the environment variable that is required to be set in the pod to provide the deployer pub const FORGE_DEPLOYER_VALUES_ENV_VAR_NAME: str FORGE_DEPLOY_VALUES_JSON;注意从实现看这个环境变量并不是直接以值形式注入而是通过valueFrom.configMapKeyRef从 ConfigMap 中读取详见下文 4.3 节这样避免将大型 JSON 直接塞进 Pod 环境变量也更利于审计与复用。2.3 已知的 values schema 默认 profileDeployer 有已知的 values 结构但大多数测试并不需要逐个字段定制而是依赖一个profile来提供合适大多数场景的默认值。默认 profile 在 constants.rs 中定义pub const DEFAULT_FORGE_DEPLOYER_PROFILE: str forge;在 CLI 层deployer_profile是forge operator create的一个可配置参数默认值即上述常量见 testsuite/forge-cli/src/main.rs#[clap( long, help The deployer profile used to spin up and configure forge infrastructure, default_value DEFAULT_FORGE_DEPLOYER_PROFILE, )] deployer_profile: String,三、安全模型cluster-admin 与隔离集群要求README 的 Implementation Notes 明确指出一个强制性的安全前提Forge Deployers 需要创建命名空间、ServiceAccount、RoleBinding 等权限并会为其创建的命名空间授予cluster-adminClusterRole。因此 Forge必须始终运行在隔离的 Kubernetes 集群中。这一点在源码中得到完全印证。ForgeDeployerManager::build_role_bindingdeployer.rs构造的 RoleBinding 将forgeServiceAccount 绑定到cluster-adminfn build_role_binding(self) - RoleBinding { RoleBinding { metadata: ObjectMeta { name: Some(forge-admin.to_string()), namespace: Some(self.namespace.clone()), ..Default::default() }, role_ref: k8s_openapi::api::rbac::v1::RoleRef { api_group: rbac.authorization.k8s.io.to_string(), kind: ClusterRole.to_string(), name: cluster-admin.to_string(), }, subjects: Some(vec![k8s_openapi::api::rbac::v1::Subject { kind: ServiceAccount.to_string(), name: FORGE_DEPLOYER_SERVICE_ACCOUNT_NAME.to_string(), namespace: Some(self.namespace.clone()), ..Default::default() }]), } }同时K8sFactory::new 会校验命名空间命名必须为default或以forge开头否则直接报错退出从入口处约束 Forge 只能运行在专用的 Forge 命名空间中match kube_namespace.as_str() { default { info!(Using the default kubernetes namespace); }, s if s.starts_with(forge) { info!(Using forge namespace: {}, s); }, _ { bail!( Invalid kubernetes namespace provided: {}. Use forge-*, kube_namespace ); }, }部署建议请将 Forge 部署在专用的、与生产集群隔离的测试集群中避免cluster-admin权限对生产环境造成影响。四、核心实现ForgeDeployerManager4.1 结构体与依赖注入deployer.rs 中的ForgeDeployerManager是模块的核心pub struct ForgeDeployerManager { // 逐一声明所需的 k8s API便于测试时注入 mock pub jobs_api: Arcdyn ReadWriteJob, pub config_maps_api: Arcdyn ReadWriteConfigMap, pub namespace_api: Arcdyn ReadWriteNamespace, pub serviceaccount_api: Arcdyn ReadWriteServiceAccount, pub rolebinding_api: Arcdyn ReadWriteRoleBinding, pub namespace: String, pub image_repo: String, pub image_tag: OptionString, }设计要点五种 Kubernetes 资源Job、ConfigMap、Namespace、ServiceAccount、RoleBinding分别使用独立的Arcdyn ReadWriteT抽象注释明确说明这是为了方便测试——单元测试中可以注入MockK8sResourceApi而无需真实集群。new构造函数deployer.rs基于kube::Client创建各资源的 API 句柄。注意namespace_api使用from_client(kube_client.clone(), None)即 Namespace 是集群级资源不限定命名空间。4.2 命名规范从镜像仓库推导 deployer 类型get_name()deployer.rs从image_repo的最后一段推导出 deployer 的类型名并作为 Job、ConfigMap 的规范名称pub fn get_name(self) - String { // derive the deployer_type from the image_repo. The type is the last part of the image repo let deployer_type self .image_repo .split(/) .next_back() .expect(Failed to get deployer type from image repo); deployer_type.to_string() }例如镜像us-docker.pkg.dev/aptos-registry/docker/forge-indexer-deployer会得到名字forge-indexer-deployer。4.3 资源配置构建ConfigMap 承载 valuesbuild_forge_deployer_k8s_config_mapdeployer.rs将调用方传入的serde_json::Value序列化为 JSON 字符串存入名为deploy-values.json的 ConfigMap data 键中fn build_forge_deployer_k8s_config_map(self, config: serde_json::Value) - ResultConfigMap { let configmap_name self.get_name(); let deploy_values_json serde_json::to_string(config)?; // create the configmap with values let config_map ConfigMap { metadata: ObjectMeta { name: Some(configmap_name.clone()), namespace: Some(self.namespace.clone()), ..Default::default() }, data: Some(BTreeMap::from([( deploy-values.json.to_string(), deploy_values_json, )])), ..Default::default() }; Ok(config_map) }4.4 Job 构建以 ConfigMap 为 env 来源build_forge_deployer_k8s_jobdeployer.rs构建 deployer Job其关键约定如下字段值说明容器名forge-deployer固定容器名镜像{image_repo}:{image_tag}无 tag 时回退到DEFAULT_FORGE_DEPLOYER_IMAGE_TAG拉取策略Always保证每次拉取最新镜像ServiceAccountforge即FORGE_DEPLOYER_SERVICE_ACCOUNT_NAMEFORGE_DEPLOY_VALUES_JSON从 ConfigMap 的deploy-values.json键读取configMapKeyRef方式重启策略NeverJob 不自动重启容器backoffLimit0Job 失败后不重试整个 Job核心片段env: Some(vec![k8s_openapi::api::core::v1::EnvVar { name: FORGE_DEPLOYER_VALUES_ENV_VAR_NAME.to_string(), value_from: Some(k8s_openapi::api::core::v1::EnvVarSource { config_map_key_ref: Some( k8s_openapi::api::core::v1::ConfigMapKeySelector { name: Some(configmap_name), key: deploy-values.json.to_string(), ..Default::default() }, ), ..Default::default() }), ..Default::default() }]),image_tag缺省时的默认值定义在 constants.rs/// The version of the forge deployer image to use. pub const DEFAULT_FORGE_DEPLOYER_IMAGE_TAG: str release_3e3c697775f3c4a939855076ed1c365526a081ca; // latest stable build (2026-07-14)4.5 生命周期start 与 wait_completedstartdeployer.rs是部署入口执行三个步骤确保命名空间就绪以指数退避重试初始 1s、最大 30s、最多 20 次调用ensure_namespace_prepared只对ApiError::RetryableError重试清理历史残留调用cleanup_deployer_resources删除同名 Job 与 ConfigMap创建 ConfigMap 与 Job先建 ConfigMap再建引用它的 Job。pub async fn start(self, config: serde_json::Value) - Result() { RetryPolicy::exponential(Duration::from_millis(1000)) .with_max_delay(Duration::from_secs(30)) .with_max_retries(20) .retry_if( || self.ensure_namespace_prepared(), |e: ApiError| matches!(e, ApiError::RetryableError(_)), ) .await?; self.cleanup_deployer_resources().await; let config_map self.build_forge_deployer_k8s_config_map(config)?; let job self.build_forge_deployer_k8s_job(config_map.name())?; info!(Creating forge deployer configmap: {}, config_map.name()); self.config_maps_api .create(PostParams::default(), config_map) .await?; info!(Creating forge deployer job: {}, job.name()); self.jobs_api.create(PostParams::default(), job).await?; Ok(()) }幂等性保障cleanup_deployer_resourcesdeployer.rs使用grace_period_seconds: Some(0)强制删除按先 Job 后 ConfigMap创建的反序清理并妥善处理 404不存在与其他错误记日志后继续。其目的正是规避 CI 中断遗留资源导致的409 AlreadyExists错误。ensure_namespace_prepareddeployer.rs依次尽力创建三个资源Namespace以self.namespace命名ServiceAccount名为forgeFORGE_DEPLOYER_SERVICE_ACCOUNT_NAMERoleBinding名为forge-admin把该 ServiceAccount 绑定到cluster-admin。三者均通过maybe_create_k8s_resource创建见 cluster_helper.rs 附近实现存在时跳过保证重复调用幂等。wait_completeddeployer.rs等待 Job 完成采用固定 10 秒间隔、最多6 * 10 60次约 10 分钟的重试策略并调用 cluster_helper.rs 中的wait_log_job实时 tail Job 日志pub async fn wait_completed(self) - Result() { // retry for ~10 min at a fixed interval... let retry_policy RetryPolicy::fixed(Duration::from_secs(10)).with_max_retries(6 * 10); wait_log_job( self.jobs_api.clone(), self.namespace, self.get_name(), retry_policy, ) .await }注释还说明了一个设计取向实际 Job 执行可能超过 10 分钟但最后一次 tail 日志会成功理想情况下 deployer 自身应根据工作负载健康状态快速失败fail fast。4.6 单元测试Mock 驱动的行为验证deployer.rs 内嵌 5 个单元测试全部基于MockK8sResourceApi#[cfg(test)]导出自 k8s/mod.rs 的kube_api::mocks测试验证点test_start_deployer_fresh_environment全新命名空间下 start 后 Job 与 ConfigMap 均存在test_start_deployer_existing_job存在残留 Job 时 start 仍成功先清理test_start_deployer_existing_job_and_configmapJob 与 ConfigMap 同时残留时 start 仍成功test_ensure_namespace_prepared_fresh_namespace新建 Namespace、ServiceAccount、RoleBindingtest_ensure_namespace_prepared_existing_resources资源已存在时幂等不报错测试中使用的样例 config也展示了 values 的基本形态let config serde_json::from_value(json!( { profile: large-banana, era: 1, namespace: manager.namespace.clone(), } )) .expect(Issue creating Forge deployer config);五、Deployer 镜像与常量总览constants.rs 集中定义了 deployer 相关常量常量值说明FORGE_INDEXER_DEPLOYER_DOCKER_IMAGE_REPOus-docker.pkg.dev/aptos-registry/docker/forge-indexer-deployerindexer 部署器镜像仓库FORGE_TESTNET_DEPLOYER_DOCKER_IMAGE_REPOus-docker.pkg.dev/aptos-registry/docker/forge-testnet-deployertestnet 部署器镜像仓库FORGE_PFN_DEPLOYER_DOCKER_IMAGE_REPOus-docker.pkg.dev/aptos-registry/docker/forge-pfn-deployerPFN 部署器镜像仓库VALIDATOR_DOCKER_IMAGE_REPOus-docker.pkg.dev/aptos-registry/docker/validator验证节点镜像仓库供 indexer 的 fullnode 引用INDEXER_GRPC_DOCKER_IMAGE_REPOus-docker.pkg.dev/aptos-registry/docker/indexer-grpcindexer-grpc 镜像仓库DEFAULT_FORGE_DEPLOYER_IMAGE_TAGrelease_3e3c697775f3c4a939855076ed1c365526a081cadeployer 镜像默认 tag2026-07-14 稳定构建FORGE_DEPLOYER_SERVICE_ACCOUNT_NAMEforgedeployer 使用的 ServiceAccount 名FORGE_DEPLOYER_VALUES_ENV_VAR_NAMEFORGE_DEPLOY_VALUES_JSON承载 values 的环境变量名DEFAULT_FORGE_DEPLOYER_PROFILEforge默认 deployer profileFORGE_GENESIS_SHARED_BUCKETgs://aptos-forge-shared-genesis-bucket/genesis共享 genesis 桶PFN 部署时读取 genesis 数据六、三种组件部署的 values 结构实战参考Forge 的 K8s 后端在真实运行中会构造三种 deployer 的 values JSON。以下内容均来自仓库实际调用代码可作为自定义部署时的结构参考。6.1 Testnet 部署器在 cluster_helper.rs 的install_testnet_resources中构造并启动 testnet deployerlet config: serde_json::Value serde_json::from_value(serde_json::json!({ profile: deployer_profile, era: new_era, namespace: kube_namespace.clone(), testnet-values: aptos_node_helm_values, genesis-values: genesis_helm_values, }))?; let testnet_deployer ForgeDeployerManager::new( kube_client.clone(), kube_namespace.clone(), FORGE_TESTNET_DEPLOYER_DOCKER_IMAGE_REPO.to_string(), None, ); testnet_deployer.start(config).await?; testnet_deployer.wait_completed().await?;其中testnet-values来自 helm-values/aptos-node-default-values.yaml 的默认 Helm values再经construct_node_helm_values_from_input注入numValidators、numFullnodeGroups、imageTag、chain.era、haproxy.enabled以及labelsforge-namespace / forge-image-tag / forge-test-suite / forge-username见 cluster_helper.rs。启用 indexer 时还会置空genesis_blob_upload_url因为 indexer 需要在集群内读取 genesis。6.2 Indexer 部署器在 k8s/mod.rs 中indexer deployer 的 values 结构如下let config serde_json::from_value(json!({ profile: indexer_profile, era: indexer_era, namespace: indexer_kube_namespace, indexer-grpc-values: { indexerGrpcImage: format!({}:{}, INDEXER_GRPC_DOCKER_IMAGE_REPO, indexer_init_version), fullnodeConfig: { image: format!({}:{}, VALIDATOR_DOCKER_IMAGE_REPO, indexer_init_version), } }, }))?; let indexer_deployer ForgeDeployerManager::new( indexer_kube_client, indexer_kube_namespace.clone(), FORGE_INDEXER_DEPLOYER_DOCKER_IMAGE_REPO.to_string(), None, ); indexer_deployer.start(config).await?; let result indexer_deployer.wait_completed().await;6.3 PFN 部署器在 k8s/mod.rs 中PFN deployer 的 values 结构包含pfn-deployments数组与共享的pfn-valueslet genesis_bucket_path format!( {}/{}/{}, FORGE_GENESIS_SHARED_BUCKET, pfn_kube_namespace, pfn_era ); let mut pfn_values json!({ imageTag: pfn_init_version, genesis_bucket_path: genesis_bucket_path, chain: { era: pfn_era, name: ephemeral, }, }); if let Some(node_config) pfn_base_node_config { pfn_values[fullnode][config] node_config; } let config serde_json::from_value(json!({ profile: pfn_profile, era: pfn_era, namespace: pfn_kube_namespace, pfn-deployments: pfn_deployment_configs, pfn-values: pfn_values, }))?; let pfn_deployer ForgeDeployerManager::new( pfn_kube_client, pfn_kube_namespace.clone(), FORGE_PFN_DEPLOYER_DOCKER_IMAGE_REPO.to_string(), None, ); pfn_deployer.start(config).await?; let result pfn_deployer.wait_completed().await;pfn-deployments数组的每个元素形如{ helmReleaseName: pfn-0 }并可通过values.fullnode.config携带每个 PFN 的节点配置覆盖详见 config.rs 的build_pfn_deployment_configs。注释说明deployer 会将 per-PFN values 深度合并deep-merge到pfn-values之上。提示相同结构的 PFN 部署逻辑也出现在 CLI 的forge operator create命令中testsuite/forge-cli/src/main.rs可通过--num-pfns、--deployer-profile、--enable-indexer、--indexer-image-tag等参数直接驱动。6.4 并行编排与 fail-fast三个组件testnet / indexer / PFN的部署通过future::try_join3并行执行k8s/mod.rs任一 deployer 失败则立即取消其余两个并调用uninstall_testnet_resources清理整个命名空间后报错。每个阶段还通过record_cluster_spinup_phase记录ClusterPhase指标Cleanup / TestnetInstall / IndexerDeploy / PfnDeploy / HealthCheck / Total便于观测集群拉起耗时。七、从 CLI 驱动 K8s DeployerForge CLItestsuite/forge-cli/src/main.rs是使用该后端最直接的入口。相关命令与参数运行模式判定ForgeRunnerMode::try_from_env()runner.rs优先读取FORGE_RUNNER_MODE环境变量取值local/k8s若未设置则在KUBERNETES_SERVICE_HOST存在时默认K8s模式否则Local模式。forge operator create在指定命名空间创建整套基础设施testnet 可选 indexer 可选 PFN常用参数见 main.rs--namespace NAMESPACE 必填目标 k8s 命名空间 --num-validators N 默认 30 --num-fullnodes N 默认 1 --validator-image-tag TAG 默认 main --testnet-image-tag TAG 默认 main --enable-indexer 同时拉起 indexer 栈 --indexer-image-tag TAG 需配合 --enable-indexer --num-pfns N 默认 00 时拉起公网全节点 --deployer-profile PROFILE 默认 forge --enable-haproxy 为每个验证节点启用 HAProxy --connect-directly 不使用 kubectl port-forwardforge operator cleanup按命名空间或通过 management ConfigMap 批量清理支持--dry-run预览对应 main.rs 与cleanup_cluster_with_management。启动launch_swarm的完整流程k8s/mod.rs为delete_k8s_resources清理 → 创建 management ConfigMap记录keep与cleanup时间戳见 cluster_helper.rs→generate_new_era()生成随机 era形如forge{8位hex}见 cluster_helper.rs→ 并行启动三个 deployer → 健康检查K8sSwarm::new→ 记录总耗时。八、总结Aptos Forge 的 K8s Deployer 后端用一套简洁而完备的抽象解决了测试基础设施动态拉起问题组件化部署testnet / indexer / PFN 各由独立 deployer Job 负责支持依赖关系与并行编排配置即数据所有定制值经FORGE_DEPLOY_VALUES_JSON实际由 ConfigMapdeploy-values.json键提供注入结构为profile era namespace 组件专属 values生命周期管理ForgeDeployerManager统一处理命名空间准备Namespace ServiceAccount cluster-admin RoleBinding、残留清理、Job 创建与日志 tail 等待安全边界因授予cluster-adminForge 必须运行在隔离集群且命名空间被强制要求为default或forge-*前缀。深入阅读建议先看 k8s_deployer/README.md再对照 deployer.rs 的 5 个单元测试理解行为契约最后结合 k8s/mod.rs 与 cluster_helper.rs 掌握其在整个 Forge 集群拉起流程中的位置。【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表