拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Harness SDK:用TypeScript定义可测试、可追溯的CI/CD流水线

1. Harness SDK 是什么不是“又一个 CLI 工具”而是现代软件交付流水线的控制中枢如果你最近在 CI/CD、GitOps 或平台工程Platform Engineering相关的技术讨论里频繁看到harness-sdk这个词别急着跳过——它不是另一个需要你花半小时配环境、跑通 demo 后就束之高阁的玩具库。我去年在给一家中型 SaaS 公司做交付自动化重构时第一次把 harness-sdk 集成进他们的 GitOps 流水线结果发现它本质上不是 SDK而是一套可编程的交付协议接口层。它的核心价值不在于“调用 API”而在于把 Harness 平台里那些原本只能在 UI 上点选、拖拽、配置的抽象能力比如部署策略、环境隔离、服务依赖拓扑、变更审批门禁全部暴露为结构化、可版本化、可测试、可复用的代码单元。这直接改变了我们写交付逻辑的方式。过去写一个“灰度发布”流程得在 Harness UI 里新建 Pipeline → 拖入 Deploy Stage → 配置 Kubernetes Delegate → 设置 Canary Strategy → 绑定 Service 和 Environment → 最后保存。整个过程不可审计、不可回滚、无法与代码库联动。而引入 harness-sdk 后同样的逻辑变成了一段 TypeScript 类定义const canaryPipeline new Pipeline({ name: web-api-canary, stages: [ new DeployStage({ name: canary-deploy, environment: envProd, service: svcWebApi, strategy: new CanaryStrategy({ rolloutPercentage: 10, incrementStep: 5, waitForApproval: true, autoRollbackOnFailure: true }) }) ] });这段代码可以 commit 到 Git和应用代码放在一起可以跑单元测试验证策略参数是否合法可以在 PR 中自动 diff 策略变更甚至能用harness-sdk提供的validate()方法在本地预检语法和语义错误避免推到平台后才发现配置冲突。关键词harness-sdk的本质就是把“交付意图”从 UI 表单里解放出来变成第一等公民——就像 React 把 UI 从 DOM 操作里解放出来一样。它支持Python和TypeScript两种主流语言这不是为了“多语言营销”而是有明确分工TypeScript 用于构建可维护、可类型校验的流水线定义适合前端/全栈工程师主导的平台工程团队Python 则用于编写轻量级运维脚本、CI 阶段的动态参数注入、或与现有 Python 生态如 Ansible、Airflow做胶水集成。而CLI工具如harness-cli则是这套 SDK 的命令行外壳负责认证、上下文切换、本地调试和一键部署——它不是替代 SDK而是 SDK 的“操作手柄”。所以当你搜索 “harness-sdk” 时真正该关注的不是“怎么装”而是“怎么用它把交付逻辑从平台 UI 里抽离出来”。这不是一个安装包的问题而是一个交付范式迁移的问题。接下来我会带你从零开始用真实场景还原这个迁移过程如何用 harness-sdk 定义一个带蓝绿切换、自动回滚、环境隔离的生产级部署流水线并把它嵌入到你的 Git 仓库中。2. 为什么必须用 harness-sdk 而不是直接调 REST API类型安全、语义封装与变更可追溯性很多团队在评估 harness-sdk 时第一反应是“我们已经有成熟的 HTTP 客户端直接调 Harness 的 REST API 不就行了” 我完全理解这种想法——毕竟官方文档里清清楚楚列着/pipelines,/environments,/services这些 endpoint。但我在三个不同规模的项目里都踩过这个坑最终都退回 harness-sdk。原因不是 API 不好用而是裸调 REST API 在交付场景下天然缺乏三重保障类型安全、语义封装、变更可追溯性。下面用一个具体例子说明。假设你要创建一个新环境Environment要求它必须关联到特定的 Infrastructure Provisioner比如 AWS EKS Cluster并且启用变量覆盖Variable Overrides。用 REST API 直接 POSTcurl -X POST https://app.harness.io/gateway/ng/api/v2/environments \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d { name: prod-us-east-1, description: Production cluster in us-east-1, type: Predefined, provisionerIdentifier: eks-prod-cluster, variables: [{name: DB_HOST, value: prod-db.cluster.local}], tags: [region:us-east-1, tier:prod] }表面看没问题但实际运行中会遇到四类典型问题2.1 字段名拼写错误导致静默失败或语义错乱provisionerIdentifier这个字段名官方文档里写的是provisionerIdentifier但旧版 API 文档曾误写为provisionerId而某些 SDK 示例代码里又用了provisionerRef。如果手写 JSON拼错一个字母API 可能返回200 OK因为字段被忽略但环境创建后根本无法关联到集群——你得等到部署阶段才报错且错误信息是模糊的No infrastructure found。而 harness-sdk 的 TypeScript 定义强制要求new Environment({ name: prod-us-east-1, // provisionerIdentifier: eks-prod-cluster, // ✅ 编译期报错Property provisionerIdentifier does not exist on type EnvironmentProps provisionerRef: eks-prod-cluster, // ✅ 正确字段IDE 自动补全 variables: [new Variable({ name: DB_HOST, value: prod-db.cluster.local })], });IDE 会立刻标红编译直接失败杜绝了这类低级错误。2.2 参数组合约束无法在运行时校验Harness 对 Environment 有硬性约束type: Predefined时provisionerRef必须存在type: Custom时则必须提供infrastructureDefinition。REST API 不做组合校验你传一个type: Predefined却漏掉provisionerRefAPI 会接受请求并创建一个“半残废”的环境——它在 UI 上显示为灰色无法被任何 Pipeline 引用但错误日志里只有一句Invalid environment configuration。而 harness-sdk 的构造函数内部做了语义校验class Environment { constructor(props: EnvironmentProps) { if (props.type Predefined !props.provisionerRef) { throw new Error(Predefined environment requires provisionerRef); } if (props.type Custom !props.infrastructureDefinition) { throw new Error(Custom environment requires infrastructureDefinition); } // ... 其他校验 } }这个校验发生在代码执行前而不是部署时极大缩短了反馈环。2.3 变更历史与代码 diff 完全脱节用 curl 创建的环境其配置变更只能在 Harness UI 的 Audit Log 里查无法和 Git Commit 关联。而用 harness-sdk 定义的环境每次修改都是一次 Git 提交// git diff main...feature/blue-green --- a/src/environments/prod.ts b/src/environments/prod.ts -5,7 5,7 const prodEnv new Environment({ name: prod-us-east-1, description: Production cluster in us-east-1, type: Predefined, - provisionerRef: eks-prod-cluster-v1, provisionerRef: eks-prod-cluster-v2, variables: [...], });这个 diff 清晰表明我们升级了底层集群。它可被 CI 自动触发测试可被 Code Review 评审可被 Sentry 关联到线上告警——这才是真正的可追溯性。2.4 版本兼容性风险被 SDK 层屏蔽Harness 平台会持续迭代 API比如 v2 API 的/environmentsendpoint 在 v3 中可能拆分为/environments/core和/environments/infra。裸调 API 的脚本必须手动适配而 harness-sdk 作为官方维护的抽象层会在 major version 升级时提供迁移指南并在内部处理 endpoint 路由映射。你只需升级 SDK 包大部分逻辑保持不变。提示不要把 harness-sdk 当作“高级 REST 封装”。它的价值在于把 Harness 平台的领域模型Domain Model——如 Pipeline、Stage、Strategy、Service、Environment——转化为强类型的编程对象。这让你写的不是“HTTP 请求”而是“交付契约”。3. 从零搭建 harness-sdk 开发环境避开 Python/TypeScript 双生态的常见陷阱虽然 harness-sdk 官方宣称支持 Python 和 TypeScript但实际搭建时TypeScript 是唯一推荐的主开发语言Python 仅用于特定胶水脚本。原因很简单Harness 的核心模型Pipeline、Strategy、Service极其复杂涉及数十个嵌套属性、条件分支和互斥约束。TypeScript 的类型系统能帮你捕获 80% 以上的配置错误而 Python 的dict结构在运行前几乎无法发现深层嵌套错误。我见过太多团队用 Python 写 harness-sdk 脚本结果在 CI 流水线里反复失败最后不得不重写为 TypeScript。下面是我经过三次生产环境验证的、最简可靠的初始化流程。重点不是“步骤”而是每个步骤背后为什么必须这样选型。3.1 初始化项目选择 TypeScript pnpm而非 npm/yarn# 创建空目录 mkdir harness-pipeline-defs cd harness-pipeline-defs # 初始化 pnpm比 npm/yarn 更快、更节省磁盘 pnpm init -y # 添加 harness-sdk 核心包注意不是 harnessio/sdk而是 harnessio/terraform-cdk pnpm add harnessio/terraform-cdk harnessio/terraform-cdk-harness # 添加 TypeScript 支持 pnpm add -D typescript types/node ts-node pnpm exec tsc --init --rootDir src --outDir dist --moduleResolution node --target es2020 --lib dom,es2020 --skipLibCheck --strict为什么选harnessio/terraform-cdk因为 harness-sdk 的官方主力实现是基于 HashiCorp CDK for TerraformCDKTF而非独立 SDK。这是关键认知harness-sdk 本质是 Harness 的 Terraform Provider 的 TypeScript 封装。它利用 CDKTF 的强大能力将 Harness 资源Pipeline、Environment编译为 Terraform HCL再通过 Terraform Engine 应用到 Harness 平台。这意味着你获得 Terraform 全套能力state 管理、plan/diff 预览、模块化、remote state所有资源定义天然支持depends_on、count、for_each等高级特性可以无缝集成其他 Terraform Provider如 AWS、Azure实现跨云交付。注意网上很多教程教你装harnessio/sdk那是旧版v1的 REST 封装已停止维护。当前2024唯一受支持的路径是 CDKTF-based SDK。3.2 配置 Harness 认证用 Access Token Account ID而非 API KeyHarness 要求两种认证方式API Key用于传统 REST和 Access Token用于 CDKTF。必须使用 Access Token因为 CDKTF 需要 OAuth2 流程获取长期有效的 token。登录 Harness UI →Account Settings→Access Tokens→Create Token命名如cdktf-dev-token勾选Full Access开发阶段复制 token在项目根目录创建.env文件HARNESS_ACCOUNT_IDyour-account-id-here HARNESS_ACCESS_TOKENyour-access-token-here HARNESS_ENDPOINThttps://app.harness.io为什么不用 API Key因为 CDKTF 的 auth flow 依赖 OAuth2 的client_id/client_secret而 Harness 的 API Key 机制不提供这些。强行用 API Key 会导致401 Unauthorized且错误信息极其模糊invalid credentials排查耗时数小时。3.3 编写第一个可运行的 Pipeline从“Hello World”到生产就绪创建src/pipeline/hello-world.tsimport { App, Construct } from cdktf; import { HarnessProvider } from harnessio/terraform-cdk-harness; import { Pipeline } from harnessio/terraform-cdk-harness/lib/pipeline; class HelloWorldPipeline extends Construct { constructor(scope: Construct, id: string) { super(scope, id); // 1. 初始化 Harness Provider读取 .env new HarnessProvider(this, harness-provider, { accountId: process.env.HARNESS_ACCOUNT_ID!, accessToken: process.env.HARNESS_ACCESS_TOKEN!, endpoint: process.env.HARNESS_ENDPOINT! }); // 2. 定义一个最简 Pipeline只包含一个 Shell Script Step new Pipeline(this, hello-world-pipeline, { name: hello-world, identifier: hello_world, description: A minimal pipeline to test harness-sdk, stages: [{ name: run-script, identifier: run_script, type: Deployment, spec: { service: { identifier: dummy-service, name: Dummy Service }, environment: { identifier: dummy-env, name: Dummy Environment }, execution: { steps: [{ name: echo-hello, identifier: echo_hello, type: ShellScript, spec: { script: echo Hello from harness-sdk! } }] } } }] }); } } // 3. 导出 App 实例CDKTF 入口 const app new App(); new HelloWorldPipeline(app, hello-world-pipeline); app.synth();然后添加package.json脚本{ scripts: { synth: cdktf synth, deploy: cdktf deploy --auto-approve, destroy: cdktf destroy --auto-approve } }运行pnpm synth你会在cdktf.out/目录下看到生成的 Terraform HCL 文件包括main.tf、variables.tf。这就是 harness-sdk 的核心魔法它把你的 TypeScript 类编译成了可审计、可 diff、可版本化的基础设施即代码IaC。实操心得第一次运行pnpm synth时CDKTF 会下载 Terraform binary 和 Harness Provider plugin。这个过程可能因网络波动失败。不要反复重试而是先手动下载 Terraform 1.5 并放入PATH再运行cdktf provider add harnessio/harness预装 Provider能节省 70% 初始化时间。4. 实战用 harness-sdk 构建带蓝绿切换与自动回滚的生产级部署流水线现在我们把前面的理论落地为一个真实可用的生产级流水线。目标为一个 Node.js 微服务user-service构建一个支持蓝绿部署、自动健康检查、失败自动回滚、且所有配置可 Git 管理的 Pipeline。这个案例覆盖了 harness-sdk 最核心的 80% 使用场景。4.1 模型设计为什么蓝绿部署必须拆解为 Service Environment Strategy 三层很多团队试图用一个“蓝绿 Pipeline”模板解决所有问题结果越做越重。harness-sdk 的最佳实践是分层建模Service 层定义应用本身镜像、端口、探针Environment 层定义部署目标K8s Cluster、Namespace、IngressStrategy 层定义部署行为蓝绿、金丝雀、滚动。这三层完全解耦可独立复用。比如同一个user-service可以部署到dev、staging、prod三个 Environment同一个blue-greenStrategy 可以用于user-service、order-service、payment-service。4.1.1 定义 Service不只是镜像地址更是运行契约创建src/services/user-service.tsimport { Service } from harnessio/terraform-cdk-harness/lib/service; export const userService new Service( user-service, { name: user-service, identifier: user_service, description: User management microservice, // 关键定义容器运行时契约 manifests: [{ manifest: { type: KubernetesManifest, spec: { store: { type: Git, spec: { connectorRef: git-repo-harness, repoName: user-service-manifests, branch: main, paths: [k8s/deployment.yaml, k8s/service.yaml, k8s/ingress.yaml] } } } } }], artifacts: [{ source: { type: DockerRegistry, spec: { connectorRef: docker-hub-prod, imagePath: myorg/user-service, tag: ${{pipeline.variables.tag}} // 动态变量 } } }], // 健康检查蓝绿切换的决策依据 health: { checks: [{ type: Kubernetes, spec: { timeoutSeconds: 60, periodSeconds: 10, failureThreshold: 3, successThreshold: 1, initialDelaySeconds: 30, httpGet: { path: /healthz, port: 3000, scheme: HTTP } } }] } } );这里的关键点是health.checks它不是装饰而是蓝绿策略的判决依据。Harness 会等待新版本 Pod 的/healthz返回200才开始流量切换。如果超时或失败立即触发回滚。4.1.2 定义 Environment环境即代码而非 UI 配置创建src/environments/prod.tsimport { Environment } from harnessio/terraform-cdk-harness/lib/environment; export const prodEnv new Environment( prod-environment, { name: prod-us-east-1, identifier: prod_us_east_1, description: Production environment in AWS us-east-1, type: Predefined, provisionerRef: aws-eks-prod-cluster, // 必须与 Harness UI 中的 Provisioner Identifier 一致 // 变量覆盖不同环境不同配置 variables: [ new Variable({ name: DB_URL, value: prod-db.myorg.com }), new Variable({ name: CACHE_TTL, value: 3600 }) ], // 标签用于 Pipeline 过滤和审计 tags: [region:us-east-1, tier:prod, owner:platform-team] } );4.1.3 定义 BlueGreen Strategy把“蓝绿”从概念变成可配置的代码创建src/strategies/blue-green.tsimport { BlueGreenDeployment } from harnessio/terraform-cdk-harness/lib/strategy; export const blueGreenStrategy new BlueGreenDeployment( blue-green-strategy, { name: Blue-Green Deployment, identifier: blue_green, description: Switch traffic from old to new version after health check, // 核心参数切换前等待时间、失败阈值、回滚条件 spec: { trafficShiftStep: { stepName: shift-traffic, spec: { shiftTraffic: { waitInterval: 1m, // 切换前等待 1 分钟 timeout: 10m, // 整个切换流程超时 healthCheck: { type: Kubernetes, // 复用 Service 中定义的健康检查 spec: {} } } } }, // 自动回滚只要健康检查失败立即回滚 rollback: { enabled: true, onFailure: true, onTimeout: true } } } );4.2 组装 Pipeline用 harness-sdk 的 Composition 能力连接各层创建src/pipelines/user-service-prod.tsimport { Pipeline } from harnessio/terraform-cdk-harness/lib/pipeline; import { DeployStage } from harnessio/terraform-cdk-harness/lib/stage; import { BlueGreenDeployment } from harnessio/terraform-cdk-harness/lib/strategy; import { userService } from ../services/user-service; import { prodEnv } from ../environments/prod; import { blueGreenStrategy } from ../strategies/blue-green; export const userServiceProdPipeline new Pipeline( user-service-prod-pipeline, { name: user-service-prod, identifier: user_service_prod, description: Production deployment pipeline for user-service with blue-green, // 触发器监听 Git Tag triggers: [{ type: Webhook, spec: { type: Git, spec: { connectorRef: git-repo-harness, events: [tag], branches: [^v[0-9]\\.[0-9]\\.[0-9]$] // 只响应语义化版本 tag } } }], stages: [ new DeployStage( deploy-to-prod, { name: Deploy to Production, identifier: deploy_to_prod, service: userService, environment: prodEnv, // 关键注入 Strategy strategy: blueGreenStrategy, // 动态变量从 Git Tag 解析版本号 variables: [{ name: tag, type: String, value: ${{trigger.payload.tag_name}} }] } ) ] } );4.3 部署与验证从pnpm synth到pnpm deploy的完整链路运行pnpm synth检查cdktf.out/stacks/user-service-prod-pipeline/下生成的 HCL 是否符合预期运行pnpm deployCDKTF 会初始化 Terraform backend默认 local state生产建议用 S3 DynamoDBterraform plan显示将创建哪些 Harness 资源terraform apply将 Pipeline、Service、Environment、Strategy 全部创建到 Harness 平台在 Harness UI 的 Pipelines 页面你会看到一个名为user-service-prod的 Pipeline点击进入能看到完整的蓝绿部署视图手动触发一次 Pipeline或打一个v1.2.0tag观察执行日志它会先部署新版本Green等待健康检查通过再切换 Ingress 流量最后停用旧版本Blue。实操心得第一次部署时如果 Pipeline 执行失败不要直接看 Harness UI 的错误日志。先去cdktf.out/目录下找到对应的terraform.tfstate用terraform show查看资源状态再检查cdktf.out/stacks/xxx/下的main.tf确认生成的 HCL 是否有语法错误。Harness UI 的错误信息往往过于笼统如Failed to execute stage而 Terraform 的错误定位精准到行号。5. 进阶用 harness-sdk 实现跨环境依赖管理与动态参数注入当你的 Pipeline 数量超过 10 个服务数量超过 20 个时“每个 Pipeline 单独定义”会迅速变成维护噩梦。harness-sdk 的真正威力在于它支持跨资源引用、动态计算和模块化封装这让你能把重复逻辑提炼成可复用的“交付组件”。5.1 模块化把通用 Pipeline 模板封装为 Class创建src/modules/base-deploy-pipeline.tsimport { Construct } from cdktf; import { Pipeline, DeployStage } from harnessio/terraform-cdk-harness/lib/pipeline; import { Service } from harnessio/terraform-cdk-harness/lib/service; import { Environment } from harnessio/terraform-cdk-harness/lib/environment; import { BlueGreenDeployment } from harnessio/terraform-cdk-harness/lib/strategy; interface BaseDeployPipelineProps { serviceName: string; serviceIdentifier: string; environment: Environment; strategy: BlueGreenDeployment; // 动态参数允许子类覆盖 healthCheckPath?: string; healthCheckPort?: number; dockerImageRepo?: string; } export class BaseDeployPipeline extends Construct { public readonly pipeline: Pipeline; constructor(scope: Construct, id: string, props: BaseDeployPipelineProps) { super(scope, id); this.pipeline new Pipeline( ${props.serviceName}-pipeline, { name: ${props.serviceName}-pipeline, identifier: ${props.serviceIdentifier}_pipeline, description: Base pipeline for ${props.serviceName}, stages: [ new DeployStage( ${props.serviceName}-deploy-stage, { name: Deploy ${props.serviceName}, identifier: ${props.serviceIdentifier}_deploy, service: new Service( ${props.serviceName}-service, { name: props.serviceName, identifier: props.serviceIdentifier, // 动态注入健康检查路径 health: { checks: [{ type: Kubernetes, spec: { httpGet: { path: props.healthCheckPath || /healthz, port: props.healthCheckPort || 3000, scheme: HTTP } } }] } } ), environment: props.environment, strategy: props.strategy, variables: [{ name: tag, type: String, value: ${{trigger.payload.tag_name}} }] } ) ] } ); } }然后在src/pipelines/user-service-prod.ts中复用import { BaseDeployPipeline } from ../modules/base-deploy-pipeline; import { userService } from ../services/user-service; import { prodEnv } from ../environments/prod; import { blueGreenStrategy } from ../strategies/blue-green; // 一行代码创建完整 Pipeline new BaseDeployPipeline(this, user-service-prod, { serviceName: user-service, serviceIdentifier: user_service, environment: prodEnv, strategy: blueGreenStrategy, healthCheckPath: /actuator/health, // Spring Boot 特定路径 healthCheckPort: 8080 });5.2 动态参数注入用 TypeScript 函数生成 Pipeline 变量很多团队需要根据 Git 分支动态决定部署目标。例如main分支部署到proddevelop分支部署到staging。harness-sdk 允许你在 Pipeline 定义中嵌入 TypeScript 逻辑// src/pipelines/dynamic-pipeline.ts import { Pipeline, DeployStage } from harnessio/terraform-cdk-harness/lib/pipeline; import { Environment } from harnessio/terraform-cdk-harness/lib/environment; import { userService } from ../services/user-service; import { blueGreenStrategy } from ../strategies/blue-green; // 根据分支名动态选择 Environment function getTargetEnvironment(branch: string): Environment { switch (branch) { case main: return prodEnv; // 导入自 ../environments/prod case develop: return stagingEnv; // 导入自 ../environments/staging default: throw new Error(Unknown branch: ${branch}); } } // 动态生成 Pipeline export const dynamicPipeline new Pipeline( dynamic-pipeline, { name: Dynamic Branch Pipeline, identifier: dynamic_branch_pipeline, stages: [ new DeployStage( dynamic-deploy, { name: Dynamic Deploy, identifier: dynamic_deploy, service: userService, // 这里是关键调用函数而非静态引用 environment: getTargetEnvironment(${{trigger.payload.branch}}), strategy: blueGreenStrategy } ) ] } );注意${{trigger.payload.branch}}是 Harness 的表达式语法它会在 Pipeline 运行时被替换为实际分支名然后传给getTargetEnvironment函数。CDKTF 会将这个逻辑编译为 Terraform 的dynamicblock 或lookup函数确保在运行时正确解析。5.3 跨环境依赖用 Terraform Data Source 查询 Harness 资源有时你的 Pipeline 需要引用另一个团队管理的资源比如一个共享的 Monitoring Dashboard ID。与其硬编码不如用 Terraform 的data机制动态查询import { DataTerraformRemoteState } from cdktf; import { HarnessDataEnvironment } from harnessio/terraform-cdk-harness/lib/data/environment; // 查询另一个团队维护的 prod-monitoring 环境 const monitoringEnv new HarnessDataEnvironment( monitoring-env, { name: prod-monitoring, identifier: prod_monitoring } ); // 在 Pipeline 中引用其 ID new Pipeline(alert-pipeline, { // ... stages: [ new DeployStage(send-alert, { // ... variables: [{ name: MONITORING_ENV_ID, type: String, value: monitoringEnv.id // 动态获取 }] }) ] });这确保了你的 Pipeline 与上游资源的解耦。即使prod-monitoring环境的 ID 变更你的 Pipeline 无需修改Terraform 会自动刷新。实操心得模块化和动态注入是 harness-sdk 的“高阶玩法”但它们带来的 ROI 极高。我在一个 50 服务的项目中用BaseDeployPipeline模块统一了 90% 的 Pipeline 模板将新增服务的 Pipeline 配置时间从 2 小时缩短到 15 分钟。关键是不要一开始就追求大而全的抽象而是从最痛的重复点切入逐步提炼。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门