Harness 教程 02:国内网络环境下创建第一个 CI/CD 流水线

179 阅读16分钟

很多 CI/CD 教程默认使用 GitHub、DockerHub 和海外 npm 源。这个组合在国内企业网络里经常跑不稳:代码拉不下来、镜像推不上去、npm install 卡住、流水线日志看起来一切正常但任务实际超时。

所以这一篇我们换一个更接近国内企业落地的方案:

| 国外默认 | 国内替代方案 | | --- | --- | | 代码仓库 | GitLab / Gitee | | 镜像仓库 | Harbor / 国内云镜像仓库 | | npm 源 | npmmirror | | 构建环境 | 企业内网 Kubernetes | | 部署环境 | Kubernetes dev namespace | | 流水线编排 | Harness | | 执行入口 | Harness Delegate |

核心思路很简单:

  • Harness SaaS

     负责编排和展示

  • Harness Delegate

     放在企业内网

  • Git、Harbor、Kubernetes 都通过 Delegate 访问

  • 基础镜像提前同步到 Harbor

  • 业务镜像统一推送到 Harbor

本篇最终会完成一条可运行的流水线:

code复制

GitLab 拉取代码 → 安装依赖 → 执行单元测试 → 构建 Docker 镜像 → 推送镜像到 Harbor → 手动确认是否部署 → 部署到 Kubernetes dev 环境 → 验证服务健康状态

一、开始之前,你需要准备什么

如果你是跟着本系列做,上一篇应该已经准备好了这些内容:

  • Harness SaaS 账号

  • Harness Org / Project

  • Harness Delegate

  • GitLab / Gitee Connector

  • Harbor / 国内镜像仓库 Connector

  • Kubernetes Cluster Connector

  • 第一个空 Pipeline

本篇默认使用下面这些名称,实际操作时替换成你自己的即可。

1. Harness 侧

  • Org:devops-lab

  • Project:harness-demo

  • Delegate:cn-k8s-dev

  • Delegate 状态:Connected / Available

2. 代码仓库

推荐使用企业内网 GitLab,或者 Gitee 企业版。

code复制

GitLab:
https://gitlab.company.com/devops/harness-demo-app.git

Gitee 企业版:
https://gitee.com/company/harness-demo-app.git

3. 镜像仓库

本文以 Harbor 为例。

code复制

Harbor 地址:
https://harbor.company.com

示例镜像:
harbor.company.com/devops/harness-demo-app

如果你使用的是阿里云 ACR、腾讯云 TCR、华为云 SWR,思路一样,只需要替换 Registry Connector 和镜像地址。

4. Kubernetes Namespace

准备两个 namespace:

  • harness-ci

    :用于 CI 构建任务

  • dev

    :用于部署应用

执行:

bash复制

kubectl create namespace harness-ci
kubectl create namespace dev

5. 国内 npm 源

项目根目录准备 .npmrc

ini复制

registry=https://registry.npmmirror.com
fetch-retries=5
fetch-retry-mintimeout=20000
fetch-retry-maxtimeout=120000

这一步不要省。国内流水线里,npm install 慢、失败、偶发超时,很多时候就是没有提前处理 npm 源。


二、最终流水线长什么样

这条流水线分成三个 Stage:

code复制

Pipeline: nodejs-harbor-k8s-dev

Stage 1: CI Build
  Step 1: 拉取代码
  Step 2: npm install
  Step 3: npm test
  Step 4: docker build
  Step 5: docker push Harbor

Stage 2: Manual Approval
  Step 1: 人工确认是否部署到 dev

Stage 3: Deploy Dev
  Step 1: 部署 Kubernetes Manifest
  Step 2: 等待 Pod 稳定
  Step 3: 访问健康检查接口

第一次做时,建议按这个顺序来:

  1. 先用 Visual Editor 跑通

  2. 再切换到 YAML 看结构

  3. 最后把 YAML 固化到 Git 仓库

不要一上来就手写完整 YAML。Harness 的字段比较多,初学阶段先跑通比"写得漂亮"更重要。


三、示例项目目录结构

本篇使用一个最小 Node.js 示例应用,目录如下:

code复制

harness-demo-app/
├── README.md
├── .npmrc
├── app/
│   ├── package.json
│   ├── server.js
│   └── server.test.js
├── docker/
│   └── Dockerfile
├── k8s/
│   ├── namespace.yaml
│   ├── deployment.yaml
│   └── service.yaml
├── harness/
│   ├── pipeline-nodejs-harbor-k8s-cn.yaml
│   └── inputsets/
│       └── dev.yaml
└── scripts/
    ├── local-build.sh
    ├── local-push.sh
    └── local-deploy.sh

这个结构适合入门,也方便后续扩展成企业模板。


四、编写 Node.js 示例服务

1. .npmrc

项目根目录创建 .npmrc

ini复制

registry=https://registry.npmmirror.com
fetch-retries=5
fetch-retry-mintimeout=20000
fetch-retry-maxtimeout=120000

作用主要有三个:

  • 避免 CI 默认访问海外 npm 源

  • 提高国内构建稳定性

  • 降低流水线随机超时概率


2. app/package.json

json复制

{
"name":"harness-demo-app",
"version":"1.0.0",
"description":"Harness CI/CD demo for China network",
"main":"server.js",
"scripts":{
"start":"node server.js",
"test":"node server.test.js"
},
"dependencies":{
"express":"^4.18.3"
}
}

3. app/server.js

js复制

const express = require("express");

functioncreateApp() {
const app = express();

  app.get("/", (req, res) => {
    res.json({
app: "harness-demo-app",
env: process.env.APP_ENV || "dev",
version: process.env.APP_VERSION || "v1",
message: "Hello Harness CI/CD China Network"
    });
  });

  app.get("/health", (req, res) => {
    res.status(200).json({
status: "UP"
    });
  });

return app;
}

if (require.main === module) {
const app = createApp();
const port = process.env.PORT || 3000;

  app.listen(port, () => {
console.log(`server started on port ${port}`);
  });
}

module.exports = createApp;

4. app/server.test.js

先写一个最小可用的测试,不引入 Jest、Vitest、Mocha 这类测试框架。

js复制

const createApp = require("./server");

functionassert(condition, message) {
if (!condition) {
thrownewError(message);
  }
}

const app = createApp();

assert(app, "app should be created");
assert(typeof app.listen === "function""app should be an express instance");

console.log("unit test passed");

这个测试很简单,但有一个关键作用:只要测试失败,后面的镜像构建和部署就不应该继续执行。


五、编写国内版 Dockerfile

创建 docker/Dockerfile

dockerfile复制

FROM harbor.company.com/library/node:20-alpine

WORKDIR /app

COPY .npmrc ./
COPY app/package*.json ./

RUN npm install --omit=dev

COPY app/server.js ./

ENV PORT=3000
ENV APP_ENV=dev

EXPOSE3000

CMD ["node""server.js"]

这里有一个国内环境里的关键点:

FROM 使用 Harbor 中的基础镜像,不要直接使用 node:20-alpine

也就是说,不建议这样写:

dockerfile复制

FROM node:20-alpine

更推荐先把基础镜像同步到 Harbor:

code复制

harbor.company.com/library/node:20-alpine

这样 CI 构建 Pod 不需要依赖 DockerHub,流水线稳定性会高很多。


六、准备 Kubernetes Manifest

1. k8s/namespace.yaml

yaml复制

apiVersion:v1
kind:Namespace
metadata:
name:dev

2. k8s/deployment.yaml

yaml复制

apiVersion:apps/v1
kind:Deployment
metadata:
name:harness-demo-app
namespace:dev
labels:
app:harness-demo-app
spec:
replicas:1
selector:
matchLabels:
app:harness-demo-app
template:
metadata:
labels:
app:harness-demo-app
spec:
imagePullSecrets:
-name:harbor-pull-secret
containers:
-name:harness-demo-app
image:harbor.company.com/devops/harness-demo-app:<+pipeline.sequenceId>
imagePullPolicy:Always
ports:
-containerPort:3000
env:
-name:APP_ENV
value:"dev"
-name:APP_VERSION
value:"<+pipeline.sequenceId>"
readinessProbe:
httpGet:
path:/health
port:3000
initialDelaySeconds:5
periodSeconds:10
livenessProbe:
httpGet:
path:/health
port:3000
initialDelaySeconds:15
periodSeconds:20

这里用到了 Harness 表达式:

code复制

<+pipeline.sequenceId>

它表示当前 Pipeline 的执行序号。每次流水线运行都会生成一个新的镜像 Tag,Deployment 也使用同一个 Tag,这样可以保证"构建出来的镜像"和"部署出去的镜像"一致。

如果你更希望用 Git Commit ID,也可以改成:

code复制

<+codebase.commitSha>

生产环境更推荐不可变 Tag,后面会专门说。


3. k8s/service.yaml

yaml复制

apiVersion:v1
kind:Service
metadata:
name:harness-demo-app
namespace:dev
spec:
selector:
app:harness-demo-app
ports:
-name:http
port:80
targetPort:3000
type:ClusterIP

七、提前创建 Harbor 拉取密钥

Kubernetes 部署时,需要能从 Harbor 拉取镜像。

bash复制

kubectl create secret docker-registry harbor-pull-secret \
  --docker-server=harbor.company.com \
  --docker-username='robot$harness-demo' \
  --docker-password='替换成 Harbor 机器人账号 Token' \
  --docker-email=devops@company.com \
  -n dev

建议这样做:

  • 每个项目创建独立 Harbor 机器人账号

  • 不同环境使用不同机器人账号

  • 生产环境不要使用个人账号

如果你的 Harbor 使用自签证书,还需要提前处理 Kubernetes 节点或运行时的证书信任问题。否则 Pod 可能会出现 ImagePullBackOff


八、先在本地验证一遍

正式接入 Harness 之前,建议先在本地把应用、镜像和 Manifest 验证一遍。

1. 安装依赖

bash复制

npm --prefix app install

2. 执行测试

bash复制

npm --prefix app test

预期输出:

code复制

unit test passed

3. 本地构建镜像

创建 scripts/local-build.sh

bash复制

#!/usr/bin/env bash
set -e

IMAGE="harbor.company.com/devops/harness-demo-app:local"

docker build \
  -f docker/Dockerfile \
  -t ${IMAGE} \
  .

echo"build success: ${IMAGE}"

执行:

bash复制

chmod +x scripts/local-build.sh
./scripts/local-build.sh

4. 本地推送镜像

创建 scripts/local-push.sh

bash复制

#!/usr/bin/env bash
set -e

IMAGE="harbor.company.com/devops/harness-demo-app:local"

docker login harbor.company.com
docker push ${IMAGE}

echo"push success: ${IMAGE}"

5. 本地部署验证

如果直接使用 k8s/deployment.yaml,里面的 <+pipeline.sequenceId> 只有在 Harness 里才会被解析。本地验证时,可以临时替换成 local

创建 scripts/local-deploy.sh

bash复制

#!/usr/bin/env bash
set -e

sed 's/<+pipeline.sequenceId>/local/g' k8s/deployment.yaml > /tmp/harness-demo-deployment-local.yaml

kubectl apply -f k8s/namespace.yaml
kubectl apply -f /tmp/harness-demo-deployment-local.yaml
kubectl apply -f k8s/service.yaml

kubectl rollout status deployment/harness-demo-app -n dev
kubectl get pods -n dev
kubectl get svc -n dev

这样做的好处是:不用修改仓库里的正式 Manifest,也能完成本地验证。


九、Visual Editor 和 YAML 怎么选

Harness 创建流水线有两种方式:

  • Visual Editor

    :可视化编辑器

  • YAML Editor

    :代码化配置

Visual Editor 适合什么场景

适合:

  • 第一次配置 Connector

  • 第一次配置 CI/CD 流程

  • 团队培训和演示

  • 快速理解 Stage、Step、Service、Environment、Infrastructure

优点是直观,不容易写错字段。缺点是不太适合版本管理和代码审查。

YAML Editor 适合什么场景

适合:

  • 企业级落地

  • 流水线纳入 Git 管理

  • 模板化复用

  • 多环境统一管理

  • 平台工程团队统一治理

缺点也明显:字段较多,初学者容易写错。

所以本篇建议:

  1. 第一遍:Visual Editor 跑通

  2. 第二遍:切换 YAML 查看结构

  3. 第三遍:把 YAML 保存到 Git

  4. 第四遍:后续统一按 YAML 工程化维护


十、创建 Pipeline

进入 Harness 控制台:

Project → Pipelines → New Pipeline

填写:

  • Name

    nodejs-harbor-k8s-dev

  • Identifier

    nodejs_harbor_k8s_dev

  • Description

    : 国内网络环境下 Node.js 应用 CI/CD 流水线

保存后,开始添加 Stage。


十一、创建 CI Build 阶段

1. 添加 CI Stage

点击:

Add Stage → Build

填写:

  • Stage Name

    CI Build

  • Stage Identifier

    ci_build

构建基础设施选择:

code复制

Kubernetes

配置:

  • Kubernetes Connector

    k8s_dev_cluster

  • Namespace

    harness-ci

  • OS

    : Linux

  • Delegate Selector

    cn-k8s-dev

这表示 Harness 会使用 Kubernetes 作为 CI 执行环境。构建过程中会在 harness-ci namespace 中创建临时 Pod,构建完成后释放资源。


2. 配置代码仓库

在 CI Stage 中开启 Clone Codebase

填写:

  • Codebase Connector

    gitlab_company

  • Repository

    devops/harness-demo-app

  • Branch

    main

  • Clone Directory

    /harness

国内环境重点检查三件事:

  • GitLab Connector 必须通过 Delegate 访问
  • GitLab Token 必须有 read_repository 权限
  • 如果 GitLab 使用自签证书,需要处理证书信任

3. 添加 npm install Step

添加 Step:

Add Step → Run

填写:

  • Name

    Install Dependencies

  • Identifier

    install_dependencies

  • Image

    harbor.company.com/library/node:20-alpine

  • Shell

    : Sh

命令:

bash复制

cd app
npm config set registry https://registry.npmmirror.com
npm install

这里依然使用 Harbor 中的 Node.js 基础镜像,不直接拉 DockerHub 的 node:20-alpine


4. 添加单元测试 Step

继续添加 Step:

Add Step → Run

填写:

  • Name

    Unit Test

  • Identifier

    unit_test

  • Image

    harbor.company.com/library/node:20-alpine

  • Shell

    : Sh

命令:

bash复制

cd app
npm test

预期输出:

code复制

unit test passed

如果这个 Step 失败,后面的 Docker 镜像构建和部署都不应该继续执行。


5. 添加 Docker Build & Push Step

添加 Step:

Add Step → Build and Push to Docker Registry

填写:

  • Name

    Build And Push Image

  • Identifier

    build_and_push_image

  • Docker Registry Connector

    harbor_company

  • Dockerfile

    docker/Dockerfile

  • Context

    .

  • Repository

    harbor.company.com/devops/harness-demo-app

  • Tags

    <+pipeline.sequenceId> + latest

说明:

  • <+pipeline.sequenceId>

     用作唯一版本号

  • latest

     只用于开发环境调试

  • 生产环境不建议依赖 latest

这里的 Repository 字段要以你的 Harbor Connector 配置为准。有些团队会填写完整地址,有些团队会在 Connector 中配置 Registry 地址后,只填写 devops/harness-demo-app。如果不确定,优先按 Visual Editor 生成的字段为准。

国内环境重点检查:

  • Harbor Connector 必须能通过 Delegate 访问
  • Harbor 机器人账号必须有 push 权限
  • 基础镜像必须提前同步到 Harbor
  • Dockerfile 中 FROM 必须使用国内镜像地址

十二、创建手动审批阶段

虽然本篇部署的是 dev 环境,但建议加一个手动审批阶段,让大家先理解"发布卡点"的概念。

点击:

Add Stage → Approval

填写:

  • Stage Name

    Manual Approval

  • Stage Identifier

    manual_approval

  • Approval Type

    : Harness Approval

审批说明可以写成这样:

请确认是否将本次构建镜像部署到 dev 环境。

应用:harness-demo-app

镜像地址:harbor.company.com/devops/harness-demo-app:<+pipeline.sequenceId>

触发分支:<+codebase.branch>

提交 ID:<+codebase.commitSha>

执行人:<+pipeline.triggeredBy.name>

审批人配置:

  • Approvers

    devops-admins

  • Minimum approvals

    1

  • Timeout

    1h

建议:

  • dev 环境审批可以选配

  • test 环境按团队流程配置

  • pre / prod 环境必须配置审批

  • 生产审批人不建议是流水线触发人本人


十三、创建 Deploy Dev 阶段

1. 添加 Deploy Stage

点击:

Add Stage → Deploy

填写:

  • Stage Name

    Deploy Dev

  • Stage Identifier

    deploy_dev

  • Deployment Type

    : Kubernetes


2. 配置 Service

Service 表示这次要部署的应用。

填写:

  • Service Name

    harness-demo-app

  • Service Identifier

    harness_demo_app

  • Service Definition

    : Kubernetes

Manifest 来源选择 Git:

  • Manifest Source

    : Git Repository

  • Git Connector

    gitlab_company

  • Repository

    devops/harness-demo-app

  • Branch

    main

  • Manifest Path

    k8s

Harness 会读取 k8s 目录下的 namespace.yamldeployment.yaml 和 service.yaml


3. 配置 Environment

创建环境:

  • Environment Name

    dev

  • Environment Identifier

    dev

  • Environment Type

    : Pre-Production


4. 配置 Infrastructure

填写:

  • Infrastructure Name

    dev-k8s

  • Infrastructure Identifier

    dev_k8s

  • Cluster Connector

    k8s_dev_cluster

  • Namespace

    dev

  • Release Name

    harness-demo-app

这里的含义是:

  • Cluster Connector 指向 Kubernetes 集群

  • Namespace 表示部署到 dev

  • Release Name 用于 Harness 记录发布历史


5. 选择部署策略

初学阶段建议选择:

code复制

Rolling Deployment

原因很简单:配置少、容易理解,符合普通 Kubernetes Deployment 的发布方式。Canary 和 Blue Green 可以放到后面的进阶篇。


6. 添加部署后健康检查 Step

在 Deploy Dev Stage 的 Post-deployment 中添加 Shell Script Step。

填写:

  • Name

    Health Check

  • Identifier

    health_check

  • Shell

    : Bash

脚本:

bash复制

set -e

export KUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}

cleanup() {
if [ -n "${PF_PID:-}" ]; then
kill"${PF_PID}" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT

echo"检查 Deployment 状态"
kubectl rollout status deployment/harness-demo-app -n <+infra.namespace> --timeout=120s

echo"检查 Pod"
kubectl get pods -n <+infra.namespace> -l app=harness-demo-app

echo"启动端口转发并验证 health"
kubectl port-forward svc/harness-demo-app 18080:80 -n <+infra.namespace> > /tmp/port-forward.log 2>&1 &
PF_PID=$!

sleep 5

curl -f http://127.0.0.1:18080/health

echo"健康检查通过"

这里加了 trap cleanup EXIT,避免 curl 失败时端口转发进程残留。


十四、Pipeline YAML 参考

下面这份 YAML 只作为结构参考。不同 Harness 版本、不同连接器类型、不同 Service 配置方式,生成的字段可能略有差异。

企业落地时,不建议直接复制这段 YAML 硬跑。更稳妥的方式是:

  1. 先用 Visual Editor 配置成功

  2. 切换到 YAML 查看 Harness 自动生成结果

  3. 再基于自动生成结果整理成 Git 管理版本

yaml复制

pipeline:
name:nodejs-harbor-k8s-dev
identifier:nodejs_harbor_k8s_dev
projectIdentifier:harness_demo
orgIdentifier:devops_lab
tags:
network:china
app:nodejs
registry:harbor
stages:
-stage:
name:CIBuild
identifier:ci_build
type:CI
spec:
cloneCodebase:true
infrastructure:
type:KubernetesDirect
spec:
connectorRef:k8s_dev_cluster
namespace:harness-ci
automountServiceAccountToken:true
os:Linux
execution:
steps:
-step:
name:InstallDependencies
identifier:install_dependencies
type:Run
spec:
connectorRef:harbor_company
image:harbor.company.com/library/node:20-alpine
shell:Sh
command:|
                      cd app
                      npm config set registry https://registry.npmmirror.com
                      npm install

-step:
name:UnitTest
identifier:unit_test
type:Run
spec:
connectorRef:harbor_company
image:harbor.company.com/library/node:20-alpine
shell:Sh
command:|
                      cd app
                      npm test

-step:
name:BuildAndPushImage
identifier:build_and_push_image
type:BuildAndPushDockerRegistry
spec:
connectorRef:harbor_company
repo:harbor.company.com/devops/harness-demo-app
tags:
-<+pipeline.sequenceId>
-latest
dockerfile:docker/Dockerfile
context:.

-stage:
name:ManualApproval
identifier:manual_approval
type:Approval
spec:
execution:
steps:
-step:
name:ApproveDevDeploy
identifier:approve_dev_deploy
type:HarnessApproval
timeout:1h
spec:
approvalMessage:|
                      请确认是否部署到 dev 环境。

镜像:
harbor.company.com/devops/harness-demo-app:<+pipeline.sequenceId>

分支:
<+codebase.branch>

提交:
<+codebase.commitSha>
includePipelineExecutionHistory:true
approvers:
userGroups:
-devops_admins
minimumCount:1
disallowPipelineExecutor:false

-stage:
name:DeployDev
identifier:deploy_dev
type:Deployment
spec:
deploymentType:Kubernetes
service:
serviceRef:harness_demo_app
environment:
environmentRef:dev
deployToAll:false
infrastructureDefinitions:
-identifier:dev_k8s
execution:
steps:
-step:
name:RolloutDeployment
identifier:rollout_deployment
type:K8sRollingDeploy
timeout:10m
spec:
skipDryRun:false

-step:
name:HealthCheck
identifier:health_check
type:ShellScript
timeout:5m
spec:
shell:Bash
source:
type:Inline
spec:
script:|
                          set -e

exportKUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}

cleanup() {
if [ -n"${PF_PID:-}" ];then
kill"${PF_PID}">/dev/null2>&1||true
fi
                          }
trapcleanupEXIT

kubectlrolloutstatusdeployment/harness-demo-app-n<+infra.namespace>--timeout=120s
kubectlgetpods-n<+infra.namespace>-lapp=harness-demo-app

kubectlport-forwardsvc/harness-demo-app18080:80-n<+infra.namespace>>/tmp/port-forward.log2>&1&
PF_PID=$!

sleep5
curl-fhttp://127.0.0.1:18080/health

echo"健康检查通过"

十五、运行 Pipeline

点击:

Save → Run → Run Pipeline

运行时确认:

  • 分支:main

  • 环境:dev

  • 镜像 Tag:<+pipeline.sequenceId>

流水线执行顺序应该是:

code复制

CI Build
  → Install Dependencies
  → Unit Test
  → Build And Push Image
Manual Approval
  → Approve Dev Deploy
Deploy Dev
  → Rollout Deployment
  → Health Check

十六、验证部署结果

在运维机或本地 kubectl 环境执行:

bash复制

kubectl get pods -n dev
kubectl get deploy -n dev
kubectl get svc -n dev

查看当前部署的镜像版本:

bash复制

kubectl get deploy harness-demo-app -n dev \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

预期输出类似:

code复制

harbor.company.com/devops/harness-demo-app:12

做一次端口转发:

bash复制

kubectl port-forward svc/harness-demo-app 8080:80 -n dev

访问 http://localhost:8080,预期返回:

json复制

{
"app":"harness-demo-app",
"env":"dev",
"version":"12",
"message":"Hello Harness CI/CD China Network"
}

健康检查地址 http://localhost:8080/health,预期返回:

json复制

{
"status":"UP"
}

十七、国内环境常见问题排查

问题 1:CI Stage 拉代码失败

常见原因:

  • GitLab Connector 没有选择通过 Delegate 执行

  • Delegate 无法访问 GitLab

  • GitLab Token 权限不足

  • GitLab 使用自签证书

  • Repository 地址写错

  • Branch 写错

排查方式:

bash复制

kubectl exec -it deploy/firstk8sdel -n harness-delegate-ng -- sh
curl -k -I https://gitlab.company.com

处理建议:

  • 确认 GitLab Connector 使用 Delegate

  • 确认 Delegate Selector 正确

  • 确认 Token 有 read_repository 权限

  • 确认 GitLab 地址可以从 Delegate Pod 访问


问题 2:npm install 很慢或失败

常见原因:

  • 没有 .npmrc

  • Dockerfile 没有 COPY .npmrc

  • Run Step 中没有设置 registry

  • 企业网络限制访问 npm 官方源

处理建议:

bash复制

npm config set registry https://registry.npmmirror.com
npm config get registry

同时确认 Dockerfile 里有这一行:

dockerfile复制

COPY .npmrc ./

问题 3:Docker Build 失败

常见原因:

  • 基础镜像没有同步到 Harbor

  • Dockerfile 中 FROM 仍然是 node:20-alpine

  • Harbor 证书不被信任

  • CI 构建 Pod 无法访问 Harbor

可以先在构建环境里验证:

bash复制

docker pull harbor.company.com/library/node:20-alpine

如果 Harbor 使用自签证书,需要在 Kubernetes 节点或构建环境中配置证书信任。


问题 4:Docker Push 失败

常见原因:

  • Harbor Connector 密码错误

  • 机器人账号没有 push 权限

  • Harbor 项目不存在

  • 镜像仓库地址写错

处理建议:

  • 确认 Harbor 项目 devops 已创建

  • 确认 robot$harness-demo 有 push 权限

  • 确认 Harness Secret 中保存的是机器人账号 Token


问题 5:Kubernetes 部署失败

常见原因:

  • dev namespace 不存在

  • imagePullSecrets 不存在

  • Deployment 中镜像地址错误

  • Kubernetes Connector 权限不足

  • ServiceAccount 没有部署权限

排查命令:

bash复制

kubectl describe pod -n dev
kubectl get events -n dev --sort-by=.metadata.creationTimestamp
kubectl get secret harbor-pull-secret -n dev

问题 6:Health Check 失败

常见原因:

  • Pod 没有 Ready

  • Service selector 和 Pod label 不一致

  • 应用端口不是 3000

  • /health

     接口返回非 200

  • port-forward 没启动成功

排查命令:

bash复制

kubectl get pods -n dev -l app=harness-demo-app
kubectl logs -n dev deploy/harness-demo-app
kubectl describe svc harness-demo-app -n dev

十八、4 个练习:让流水线更接近真实项目

练习 1:补充单元测试

把 app/server.test.js 改成下面这样:

js复制

const createApp = require("./server");

functionassert(condition, message) {
if (!condition) {
thrownewError(message);
  }
}

const app = createApp();

assert(app, "app should be created");
assert(typeof app.listen === "function""app should be an express instance");

const routes = app._router.stack
  .filter(layer => layer.route)
  .map(layer => layer.route.path);

assert(routes.includes("/"), "root route should exist");
assert(routes.includes("/health"), "health route should exist");

console.log("unit test passed");

提交代码后重新运行 Pipeline。

预期结果:

code复制

Install Dependencies 成功
Unit Test 成功
Build And Push Image 成功
Deploy Dev 成功
Health Check 成功

练习 2:配置手动审批

将 Manual Approval Stage 调整为:

  • 审批人组:devops-admins

  • 最少审批人数:1

  • 超时时间:30m

  • 审批说明:展示镜像地址、分支、提交 ID、执行人

审批说明建议:

请确认是否部署到 dev 环境。

应用:harness-demo-app

镜像:harbor.company.com/devops/harness-demo-app:<+pipeline.sequenceId>

分支:<+codebase.branch>

提交:<+codebase.commitSha>

执行人:<+pipeline.triggeredBy.name>

这个练习的目的不是让 dev 环境变复杂,而是让你理解发布治理中的"审批卡点"。


练习 3:测试失败时阻断部署

故意把 server.test.js 改成失败:

js复制

thrownewError("mock unit test failed");

提交后重新运行 Pipeline。

预期结果:

code复制

Unit Test 失败
Build And Push Image 不执行
Manual Approval 不执行
Deploy Dev 不执行

这个练习可以帮助你理解三件事:

  • CI 测试失败必须阻断 CD
  • 不要让有问题的代码进入镜像仓库
  • 不要让有问题的镜像部署到 Kubernetes

练习 4:把 latest 从生产流程中移除

开发环境可以临时使用:

code复制

latest
<+pipeline.sequenceId>

但生产环境建议只使用不可变 Tag,例如:

code复制

<+codebase.commitSha>
v1.0.0
release-20260611-001

修改 Build And Push Image Step:

Tags 改为:<+codebase.commitSha>

同时把 Deployment 中镜像 Tag 也改成:<+codebase.commitSha>

这样做可以提高发布可追溯性,也能避免 latest 带来的回滚困难。


十九、企业落地建议

1. 国内网络环境不要依赖海外源

建议统一替换:

| 海外默认 | 国内替代方案 | | --- | --- | | GitHub | GitLab / Gitee | | DockerHub | Harbor / ACR / TCR / SWR | | npm 官方源 | npmmirror | | 公网构建机 | Kubernetes 内部构建环境 |


2. 不要把密码写进 YAML

错误示例:

yaml复制

password:"123456"

正确做法:

yaml复制

passwordRef:harbor_robot_token

所有敏感信息都应该放到 Harness Secret,或者企业统一密钥系统中。


3. 按环境管理发布策略

推荐:

  • dev

    :自动部署,可选审批

  • test

    :测试人员确认,可选审批

  • pre

    :必须审批

  • prod

    :必须审批 + 变更单 + 灰度策略


4. 镜像 Tag 要有规范

开发环境:

code复制

<+pipeline.sequenceId>
latest

测试环境:

code复制

<+codebase.commitSha>

生产环境:

code复制

release-日期-流水号
Git Tag
语义化版本号

例如:

code复制

release-20260611-001
v1.3.0

5. Pipeline YAML 要纳入 Git 管理

建议目录:

code复制

harness/
├── pipelines/
│   ├── nodejs-dev.yaml
│   ├── nodejs-test.yaml
│   └── nodejs-prod.yaml
├── inputsets/
│   ├── dev.yaml
│   ├── test.yaml
│   └── prod.yaml
└── templates/
    ├── nodejs-ci-template.yaml
    └── k8s-deploy-template.yaml

后续可以把重复流程沉淀成模板:

  • Node.js 构建模板

  • Java Maven 构建模板

  • Docker Build Push 模板

  • Kubernetes Deploy 模板

  • 人工审批模板

  • 健康检查模板


二十、本篇验收标准

完成本篇后,你应该能看到下面这些结果:

  • ✅ 已创建 Node.js 示例应用

  • ✅ 已配置 .npmrc 使用国内 npm 镜像

  • ✅ 已编写 Dockerfile,并使用 Harbor 基础镜像

  • ✅ 已编写 Kubernetes Deployment 和 Service

  • ✅ 已创建 CI Build Stage

  • ✅ 已完成 npm install

  • ✅ 已完成 npm test

  • ✅ 已完成 Docker 镜像构建

  • ✅ 已推送镜像到 Harbor

  • ✅ 已创建 Manual Approval Stage

  • ✅ 已创建 Deploy Dev Stage

  • ✅ 已部署应用到 Kubernetes dev namespace

  • ✅ 已通过 /health 健康检查

  • ✅ 已理解 Visual Editor 和 YAML 的区别


二十一、总结

这篇文章完成了一条适合国内网络环境的 Harness CI/CD 入门流水线。

你需要重点记住这些结论:

  • 国内环境不要默认依赖 GitHub、DockerHub、npm 官方源
  • 代码仓库建议使用 GitLab / Gitee
  • 镜像仓库建议使用 Harbor / 国内云镜像仓库
  • npm 构建建议使用 npmmirror
  • Harness SaaS 不直接访问内网资源,必须通过 Delegate
  • CI 阶段负责构建和测试
  • CD 阶段负责部署和验证
  • 单元测试失败必须阻断部署
  • 手动审批是企业发布治理的基础
  • Visual Editor 适合入门,YAML 适合工程化