一:教程定位
在前 3 篇中,我们已经完成了 Harness 平台环境搭建、第一个 CI/CD 流水线,以及 Git 代码提交自动触发 Pipeline。
第 4 篇开始进入企业 CI/CD 的核心能力:测试质量控制与制品管理。
很多初学者在做流水线时,只会把代码打包成镜像并部署到 Kubernetes,但企业真实落地不能只看"能不能部署"。更重要的是:
代码是否通过单元测试? 构建出的镜像是否可追溯? 镜像是否推送到了企业统一仓库? 失败测试是否能阻断发布? 代码质量和安全扫描是否通过? 制品版本是否能关联到 Git Commit 和流水线执行记录?
本篇目标是基于 Node.js 示例项目,在 Harness Pipeline 中增加:
npm install npm 单元测试 测试报告输出 Docker 镜像构建 镜像推送到 Harbor 镜像版本 Tag 管理 SonarQube 代码质量扫描 质量门检查 失败阻断后续部署
二:适合人群
本文适合:
DevOps 初学者 后端开发人员 前端开发人员 测试工程师 SRE / 运维工程师 正在把 GitLab CI / Jenkins 迁移到 Harness 的团队
建议已经完成:
Harness 教程 01:平台介绍与 Delegate 安装 Harness 教程 02:创建第一个 CI/CD 流水线 Harness 教程 03:代码仓库与触发器基础
三:学习目标
完成本文后,你应该能够:
在 Harness CI Stage 中添加 npm 单元测试步骤 理解测试失败如何阻断镜像构建和部署 把 Docker 镜像推送到企业 Harbor 使用 Harbor Robot Account 做流水线认证 为镜像设计可追溯 Tag 在 Harness 中保存和引用 Secret 通过 Shell Script / Run Step 集成 SonarQube 扫描 使用 SonarQube Quality Gate 阻断不合格代码 理解国内网络环境下 npm、镜像仓库、SonarQube 的常见问题
四:国内网络环境下的整体架构
本篇推荐架构如下:
开发人员
↓ git push
GitLab / Gitee
↓ webhook
Harness Pipeline
↓ 派发任务
Harness Delegate
↓ 拉取代码
GitLab / Gitee
↓ npm install / npm test
npmmirror / 企业 npm 私服
↓ SonarQube 扫描
内网 SonarQube
↓ docker build
CI 构建环境
↓ docker push
Harbor / ACR / TCR / SWR
↓ deploy
Kubernetes dev/test 环境
核心原则:
Git、Harbor、SonarQube、Kubernetes 尽量都在国内网络或企业内网 Harness SaaS 负责流水线编排 Harness Delegate 负责访问内网资源 npm 不直接走海外官方源 基础镜像提前同步到 Harbor 所有密码、Token 都使用 Harness Secret 管理
五:本篇最终流水线结构
本篇 Pipeline 结构如下:
Pipeline: nodejs-test-artifact-harbor
Stage 1: CI Test And Package Step 1: 打印构建上下文 Step 2: 安装依赖 npm install Step 3: 执行 npm 单元测试 Step 4: 生成测试报告 Step 5: SonarQube 代码扫描 Step 6: SonarQube Quality Gate 检查 Step 7: 构建 Docker 镜像 Step 8: 推送镜像到 Harbor
Stage 2: Approval For Deploy Step 1: 人工确认测试与扫描结果
Stage 3: Deploy Dev Step 1: 部署到 Kubernetes dev 环境 Step 2: 健康检查
本篇重点放在 Stage 1:测试与制品管理。部署阶段可以沿用第 2 篇的 Kubernetes 部署配置。
六:环境准备清单
1. Harness 资源
需要提前准备:
Org: devops-lab Project: harness-demo Delegate: cn-k8s-dev Git Connector: gitlab_company Docker Registry Connector: harbor_company Kubernetes Connector: k8s_dev_cluster Pipeline: nodejs-test-artifact-harbor
2. Git 仓库
示例:
推荐分支:
main dev feature/*
3. Harbor
示例地址:
示例项目:
devops library platform security
镜像地址:
harbor.company.com/devops/harness-demo-app
建议 Harbor 中准备:
library/node:20-alpine library/sonar-scanner-cli:latest library/alpine-curl-jq:3.20 devops/harness-demo-app
如果企业可以访问官方镜像,可以先在一台可出网机器上同步;如果完全离线,则由运维统一导入镜像。
4. SonarQube
示例地址:
需要准备:
SonarQube 项目:harness-demo-app SonarQube Token:用于扫描认证 Quality Gate:例如"无 Blocker / Critical 漏洞,新代码覆盖率达标"
如果企业暂时没有 SonarQube,本篇的 SonarQube 部分可以作为练习项,先完成 npm 测试与 Harbor 制品管理。
5. Kubernetes
需要准备命名空间:
kubectl create namespace harness-ci
kubectl create namespace dev
创建 Harbor 拉取密钥:
kubectl create secret docker-registry harbor-pull-secret \
--docker-server=harbor.company.com \
--docker-username='robot$devops+harness-ci' \
--docker-password='替换为 Harbor Robot Token' \
--docker-email=devops@company.com \
-n dev
七:推荐仓库结构
本篇使用如下仓库结构:
harness-demo-app/
├── README.md
├── .npmrc
├── app/
│ ├── package.json
│ ├── server.js
│ ├── server.test.js
│ └── test-runner.js
├── coverage/
│ └── .gitkeep
├── docker/
│ └── Dockerfile
├── k8s/
│ ├── namespace.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── sonar-project.properties
├── scripts/
│ ├── sync-ci-images.sh
│ ├── check-sonarqube-quality-gate.sh
│ ├── build-image-local.sh
│ └── push-image-local.sh
└── harness/
└── pipelines/
└── nodejs-test-artifact-harbor.yaml
八:Node.js 示例应用代码
1. .npmrc
registry=https://registry.npmmirror.com
fetch-retries=5
fetch-retry-mintimeout=20000
fetch-retry-maxtimeout=120000
说明:
国内网络环境建议使用 npmmirror 企业内网有 npm 私服时,可以替换为企业 npm 私服地址 例如:npm.company.com/repository/…
2. app/package.json
{
"name": "harness-demo-app",
"version": "1.0.0",
"description": "Harness CI test and artifact demo for China network",
"main": "server.js",
"scripts": {
"start": "node server.js",
"test": "node server.test.js",
"test:report": "node test-runner.js"
},
"dependencies": {
"express": "^4.18.3"
}
}
3. app/server.js
const express = require("express");
function createApp() {
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 Test And Artifact Management"
});
});
app.get("/health", (req, res) => {
res.status(200).json({ status: "UP" });
});
app.get("/api/users", (req, res) => {
res.json([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]);
});
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
const createApp = require("./server");
function assert(condition, message) {
if (!condition) {
throw new Error(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");
assert(routes.includes("/api/users"), "users route should exist");
console.log("unit test passed");
5. app/test-runner.js
为了让初学者不依赖复杂测试框架,这里用 Node.js 生成一个简单测试报告文件。企业项目可以换成 Jest、Vitest、Mocha、JUnit XML 等。
const fs = require("fs");
const path = require("path");
const reportDir = path.join(__dirname, "..", "coverage");
const reportFile = path.join(reportDir, "unit-test-report.json");
if (!fs.existsSync(reportDir)) {
fs.mkdirSync(reportDir, { recursive: true });
}
const result = {
app: "harness-demo-app",
status: "PASS",
total: 3,
passed: 3,
failed: 0,
timestamp: new Date().toISOString()
};
fs.writeFileSync(reportFile, JSON.stringify(result, null, 2));
console.log("unit test report generated:");
console.log(reportFile);
九:Dockerfile:构建业务镜像
docker/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
EXPOSE 3000
CMD ["node", "server.js"]
变量说明:
harbor.company.com:替换为你的 Harbor 域名 library/node:20-alpine:需要提前同步到 Harbor APP_ENV:运行环境 PORT:服务端口
注意:
Dockerfile 中不要直接 FROM node:20-alpine 国内环境建议统一使用 Harbor 基础镜像 生产镜像不要把测试文件、源码无关文件全部复制进去
十:SonarQube 配置文件
sonar-project.properties
sonar.projectKey=harness-demo-app
sonar.projectName=Harness Demo App
sonar.projectVersion=1.0.0
sonar.sources=app
sonar.exclusions=**/node_modules/**,**/coverage/**
sonar.sourceEncoding=UTF-8
说明:
sonar.projectKey:SonarQube 中唯一项目标识 sonar.projectName:展示名称 sonar.sources:扫描源代码目录 sonar.exclusions:排除 node_modules、coverage 等目录
如果你是多服务仓库,可以按服务目录拆分:
sonar.projectKey=user-service
sonar.sources=services/user-service/src
十一:同步 CI 基础镜像到 Harbor
scripts/sync-ci-images.sh
#!/usr/bin/env bash
set -e
HARBOR_HOST="harbor.company.com"
docker login ${HARBOR_HOST}
echo "同步 Node.js 基础镜像"
docker pull node:20-alpine
docker tag node:20-alpine ${HARBOR_HOST}/library/node:20-alpine
docker push ${HARBOR_HOST}/library/node:20-alpine
echo "同步 SonarScanner CLI 镜像"
docker pull sonarsource/sonar-scanner-cli:latest
docker tag sonarsource/sonar-scanner-cli:latest ${HARBOR_HOST}/library/sonar-scanner-cli:latest
docker push ${HARBOR_HOST}/library/sonar-scanner-cli:latest
echo "同步 curl 工具镜像"
docker pull alpine:3.20
docker tag alpine:3.20 ${HARBOR_HOST}/library/alpine:3.20
docker push ${HARBOR_HOST}/library/alpine:3.20
echo "同步完成"
变量说明:
HARBOR_HOST:替换为企业 Harbor 地址 sonarsource/sonar-scanner-cli:如果不能直接访问 DockerHub,需要在可出网机器上同步
如果企业完全离线,推荐由平台团队统一制作:
harbor.company.com/platform/ci-node-sonar:20
这个镜像可以内置:
node npm curl jq sonar-scanner docker CLI kubectl
这样可以减少流水线中临时安装工具导致的网络问题。
十二:Harbor Robot Account 设计
1. 创建 Harbor 项目
进入 Harbor:
Projects → New Project
创建:
Project Name: devops Access Level: Private
2. 创建 Robot Account
进入:
Projects → devops → Robot Accounts → New Robot Account
推荐名称:
harness-ci
建议权限:
Pull Repository Push Repository List Repository Read Artifact
Harbor 中 Push Repository 权限通常需要同时具备 Pull Repository 权限,因此不要只勾选 Push。
生成后复制 Robot Token,注意:
Harbor Robot Token 创建后需要立即保存 不要提交到 Git 仓库 不要发到微信群或聊天工具 存入 Harness Secret
3. Harness Secret
在 Harness 中创建 Secret:
Project Settings → Secrets → New Secret → Text
填写:
Name: harbor-robot-token Identifier: harbor_robot_token Secret Value: Harbor Robot Token
4. Docker Registry Connector
创建 Docker Registry Connector:
Project Settings → Connectors → New Connector → Artifact Repositories → Docker Registry
填写:
Name: harbor-company Identifier: harbor_company Provider Type: Harbor 或 Other Docker Registry URL: harbor.company.com Authentication: Username and Password Username: robot$devops+harness-ci Password: <+secrets.getValue("harbor_robot_token")> Connectivity: Connect through Delegate Delegate Selector: cn-k8s-dev
YAML 参考:
connector:
name: harbor-company
identifier: harbor_company
description: 企业 Harbor 镜像仓库
orgIdentifier: devops_lab
projectIdentifier: harness_demo
type: DockerRegistry
spec:
dockerRegistryUrl: https://harbor.company.com
providerType: Harbor
auth:
type: UsernamePassword
spec:
username: robot$devops+harness-ci
passwordRef: harbor_robot_token
executeOnDelegate: true
delegateSelectors:
- cn-k8s-dev
注意:
如果你的 Harness 版本中 providerType 没有 Harbor,可选择 Other Harbor 地址不要随意加 /v2,按 UI 测试结果和企业 Registry 地址为准 生产环境建议每个项目一个 Robot Account
十三:SonarQube Token 与 Secret
1. 创建 SonarQube Token
进入 SonarQube:
My Account → Security → Generate Tokens
创建:
Token Name: harness-demo-app-token Type: Project Analysis Token Project: harness-demo-app
复制 Token。
2. 创建 Harness Secret
Project Settings → Secrets → New Secret → Text
填写:
Name: sonar-token Identifier: sonar_token Secret Value: SonarQube Token
再创建 SonarQube 地址变量,也可以直接写在 Pipeline 变量中:
SONAR_HOST_URL=sonarqube.company.com
如果 SonarQube 使用自签证书,建议:
把企业 CA 导入 SonarScanner 镜像 或使用企业统一签发的受信任证书 不建议长期使用跳过证书校验
十四:SonarQube Quality Gate 检查脚本
scripts/check-sonarqube-quality-gate.sh
#!/usr/bin/env bash
set -e
SONAR_HOST_URL="${SONAR_HOST_URL}"
SONAR_TOKEN="${SONAR_TOKEN}"
REPORT_FILE=".scannerwork/report-task.txt"
if [ ! -f "${REPORT_FILE}" ]; then
echo "未找到 ${REPORT_FILE},请确认 sonar-scanner 已成功执行"
exit 1
fi
CE_TASK_ID=$(grep "ceTaskId=" "${REPORT_FILE}" | cut -d'=' -f2)
if [ -z "${CE_TASK_ID}" ]; then
echo "无法从 report-task.txt 获取 ceTaskId"
exit 1
fi
echo "SonarQube CE Task ID: ${CE_TASK_ID}"
for i in $(seq 1 30); do
TASK_JSON=$(curl -s -u "${SONAR_TOKEN}:" \
"${SONAR_HOST_URL}/api/ce/task?id=${CE_TASK_ID}")
STATUS=$(echo "${TASK_JSON}" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
ANALYSIS_ID=$(echo "${TASK_JSON}" | sed -n 's/.*"analysisId":"\([^"]*\)".*/\1/p')
echo "SonarQube task status: ${STATUS}"
if [ "${STATUS}" = "SUCCESS" ]; then
break
fi
if [ "${STATUS}" = "FAILED" ] || [ "${STATUS}" = "CANCELED" ]; then
echo "SonarQube 后台任务失败或取消"
echo "${TASK_JSON}"
exit 1
fi
sleep 5
done
if [ -z "${ANALYSIS_ID}" ]; then
echo "未获取到 analysisId,Quality Gate 无法检查"
exit 1
fi
QG_JSON=$(curl -s -u "${SONAR_TOKEN}:" \
"${SONAR_HOST_URL}/api/qualitygates/project_status?analysisId=${ANALYSIS_ID}")
QG_STATUS=$(echo "${QG_JSON}" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
echo "Quality Gate Status: ${QG_STATUS}"
if [ "${QG_STATUS}" != "OK" ]; then
echo "SonarQube Quality Gate 未通过"
echo "${QG_JSON}"
exit 1
fi
echo "SonarQube Quality Gate 通过"
说明:
先读取 .scannerwork/report-task.txt 通过 ceTaskId 查询后台分析任务是否完成 再通过 analysisId 查询 Quality Gate 状态 状态不是 OK 时退出 1,阻断后续镜像构建和部署
注意:
这个脚本使用 sed 解析 JSON,适合入门和轻量场景 企业生产建议使用 jq 或 Python 解析 JSON 如果 CI 镜像没有 curl,需要使用内置 curl 的工具镜像
十五:在 Harness 中创建 CI 测试与制品流水线
进入 Harness:
Project → Pipelines → New Pipeline
填写:
Name: nodejs-test-artifact-harbor Identifier: nodejs_test_artifact_harbor Description: Node.js 集成测试与 Harbor 制品管理流水线
十六:创建 CI Stage
添加 Stage:
Add Stage → Build
配置:
Stage Name: CI Test And Package Stage Identifier: ci_test_and_package Infrastructure: Kubernetes Kubernetes Connector: k8s_dev_cluster Namespace: harness-ci OS: Linux
启用 Clone Codebase:
Codebase Connector: gitlab_company Repository: devops/harness-demo-app Branch: <+trigger.branch> 或 main Clone Directory: /harness
十七:Step 1 — 打印构建上下文
添加 Run Step:
Name: Print Build Context Identifier: print_build_context Image: harbor.company.com/library/node:20-alpine Shell: Sh
命令:
echo "===== Build Context ====="
echo "Pipeline: <+pipeline.name>"
echo "Execution ID: <+pipeline.executionId>"
echo "Sequence ID: <+pipeline.sequenceId>"
echo "Trigger Type: <+pipeline.triggerType>"
echo "Branch: <+codebase.branch>"
echo "Commit SHA: <+codebase.commitSha>"
echo "Git user: <+trigger.gitUser>"
作用:
方便排查本次构建对应哪个分支、哪个提交、哪个流水线执行
十八:Step 2 — 安装依赖
添加 Run Step:
Name: Install Dependencies Identifier: install_dependencies Image: harbor.company.com/library/node:20-alpine Shell: Sh
命令:
cd app
npm config set registry https://registry.npmmirror.com
npm install
echo "依赖安装完成"
企业内网 npm 私服写法:
npm config set registry https://npm.company.com/repository/npm-group/
npm install
建议:
优先使用 npm ci,而不是 npm install 前提是项目中存在 package-lock.json
可改为:
cd app
npm config set registry https://registry.npmmirror.com
npm ci
十九:Step 3 — 执行 npm 单元测试
添加 Run Step:
Name: Unit Test Identifier: unit_test Image: harbor.company.com/library/node:20-alpine Shell: Sh
命令:
cd app
npm test
预期输出:
unit test passed
如果测试失败,Harness 会标记该 Step 失败,后续 SonarQube、Docker Build、Deploy 都不应该继续执行。
企业建议:
单元测试失败必须阻断镜像构建 不要把测试失败的代码打包成镜像 不要把测试失败的镜像推送到 Harbor
二十:Step 4 — 生成测试报告
添加 Run Step:
Name: Generate Test Report Identifier: generate_test_report Image: harbor.company.com/library/node:20-alpine Shell: Sh
命令:
cd app
npm run test:report
cd ..
cat coverage/unit-test-report.json
说明:
本示例生成 JSON 测试报告 真实项目建议生成 JUnit XML、LCOV、HTML Coverage 等格式
后续可以将测试报告上传到对象存储、制品仓库或 Harness Artifact Registry。
二十一:Step 5 — SonarQube 扫描
1. 使用 Run Step 调用 SonarScanner
添加 Run Step:
Name: SonarQube Scan Identifier: sonarqube_scan Image: harbor.company.com/library/sonar-scanner-cli:latest Shell: Sh
环境变量:
SONAR_HOST_URL=sonarqube.company.com SONAR_TOKEN=<+secrets.getValue("sonar_token")>
命令:
sonar-scanner \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.token=${SONAR_TOKEN}
前提:
仓库根目录存在 sonar-project.properties SonarQube 中已创建 harness-demo-app 项目 SONAR_TOKEN 有该项目分析权限 Delegate / CI Pod 可以访问 SonarQube 地址
2. 如果使用内网自签证书
推荐做法:
制作企业版 sonar-scanner 镜像 将企业 CA 导入镜像 truststore 推送到 harbor.company.com/library/sonar-scanner-cli:company
示例 Dockerfile:
FROM sonarsource/sonar-scanner-cli:latest
USER root
COPY company-ca.crt /usr/local/share/ca-certificates/company-ca.crt
RUN update-ca-certificates || true
USER scanner-cli
构建并推送:
docker build -t harbor.company.com/library/sonar-scanner-cli:company .
docker push harbor.company.com/library/sonar-scanner-cli:company
Pipeline 中改用:
harbor.company.com/library/sonar-scanner-cli:company
二十二:Step 6 — SonarQube Quality Gate 检查
添加 Run Step:
Name: SonarQube Quality Gate Identifier: sonarqube_quality_gate Image: harbor.company.com/library/alpine:3.20 Shell: Sh
环境变量:
SONAR_HOST_URL=sonarqube.company.com SONAR_TOKEN=<+secrets.getValue("sonar_token")>
命令:
apk add --no-cache curl
chmod +x scripts/check-sonarqube-quality-gate.sh
SONAR_HOST_URL=${SONAR_HOST_URL} \
SONAR_TOKEN=${SONAR_TOKEN} \
scripts/check-sonarqube-quality-gate.sh
国内企业注意:
如果 CI Pod 不能访问公网 apk 源,不要在流水线中 apk add 应提前制作包含 curl/jq 的工具镜像 例如 harbor.company.com/platform/ci-tools:node20-curl-jq-sonar
更稳定的企业做法:
harbor.company.com/platform/ci-tools:1.0.0
内置:
node npm curl jq sonar-scanner kubectl docker CLI
二十三:Step 7 — 构建并推送 Docker 镜像到 Harbor
添加 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> <+codebase.commitSha>
说明:
<+pipeline.sequenceId>:流水线执行序号,适合开发环境快速识别 <+codebase.commitSha>:Git 提交 ID,适合追溯代码版本
不建议生产只使用:
latest
开发环境可以额外加:
latest
生产环境建议:
v1.0.0 release-20260615-001 <+codebase.commitSha>
二十四:镜像 Tag 策略
推荐设计:
开发环境: <+pipeline.sequenceId> latest
测试环境: <+codebase.commitSha>
准生产环境: release-<日期>-<流水号>
生产环境: Git Tag 语义化版本号 release-20260615-001
示例:
harbor.company.com/devops/harness-demo-app:105 harbor.company.com/devops/harness-demo-app:9f3a7c1 harbor.company.com/devops/harness-demo-app:release-20260615-001 harbor.company.com/devops/harness-demo-app:v1.3.0
企业要求:
镜像 Tag 必须能追溯到 Git Commit 镜像 Tag 必须能追溯到 Pipeline Execution 生产镜像不能被覆盖 Harbor 开启保留策略和漏洞扫描
二十五:完整 Pipeline YAML 参考
下面 YAML 作为参考,建议先使用 Harness Visual Editor 创建成功,再切换到 YAML 查看实际字段。不同 Harness 版本和模块启用情况可能会导致字段略有差异。
pipeline:
name: nodejs-test-artifact-harbor
identifier: nodejs_test_artifact_harbor
projectIdentifier: harness_demo
orgIdentifier: devops_lab
tags:
tutorial: harness-04
network: china
app: nodejs
stages:
- stage:
name: CI Test And Package
identifier: ci_test_and_package
type: CI
spec:
cloneCodebase: true
infrastructure:
type: KubernetesDirect
spec:
connectorRef: k8s_dev_cluster
namespace: harness-ci
automountServiceAccountToken: true
os: Linux
execution:
steps:
- step:
name: Print Build Context
identifier: print_build_context
type: Run
spec:
connectorRef: harbor_company
image: harbor.company.com/library/node:20-alpine
shell: Sh
command: |
echo "Pipeline: <+pipeline.name>"
echo "Execution ID: <+pipeline.executionId>"
echo "Sequence ID: <+pipeline.sequenceId>"
echo "Trigger Type: <+pipeline.triggerType>"
echo "Branch: <+codebase.branch>"
echo "Commit SHA: <+codebase.commitSha>"
- step:
name: Install Dependencies
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: Unit Test
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: Generate Test Report
identifier: generate_test_report
type: Run
spec:
connectorRef: harbor_company
image: harbor.company.com/library/node:20-alpine
shell: Sh
command: |
cd app
npm run test:report
cd ..
cat coverage/unit-test-report.json
- step:
name: SonarQube Scan
identifier: sonarqube_scan
type: Run
spec:
connectorRef: harbor_company
image: harbor.company.com/library/sonar-scanner-cli:latest
shell: Sh
envVariables:
SONAR_HOST_URL: https://sonarqube.company.com
SONAR_TOKEN: <+secrets.getValue("sonar_token")>
command: |
sonar-scanner \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.token=${SONAR_TOKEN}
- step:
name: SonarQube Quality Gate
identifier: sonarqube_quality_gate
type: Run
spec:
connectorRef: harbor_company
image: harbor.company.com/platform/ci-tools:1.0.0
shell: Sh
envVariables:
SONAR_HOST_URL: https://sonarqube.company.com
SONAR_TOKEN: <+secrets.getValue("sonar_token")>
command: |
chmod +x scripts/check-sonarqube-quality-gate.sh
scripts/check-sonarqube-quality-gate.sh
- step:
name: Build And Push Image
identifier: build_and_push_image
type: BuildAndPushDockerRegistry
spec:
connectorRef: harbor_company
repo: harbor.company.com/devops/harness-demo-app
dockerfile: docker/Dockerfile
context: .
tags:
- <+pipeline.sequenceId>
- <+codebase.commitSha>
说明:
SonarQube Quality Gate 失败时,Build And Push Image 不会执行 Unit Test 失败时,SonarQube 和 Docker Build 都不会执行 Docker Build 成功后,镜像会推送到 Harbor
二十六:部署阶段如何引用镜像
在 k8s/deployment.yaml 中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: harness-demo-app
namespace: dev
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>
ports:
- containerPort: 3000
env:
- name: APP_VERSION
value: "<+pipeline.sequenceId>"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
如果使用 commit sha 作为镜像 Tag:
image: harbor.company.com/devops/harness-demo-app:<+codebase.commitSha>
注意:
Build Step 的 tag 和 Deployment 中引用的 tag 必须一致 否则会出现构建成功但部署拉不到镜像
二十七:运行流水线
点击:
Save Run
选择:
Branch: main 或 dev
期望执行顺序:
Print Build Context Install Dependencies Unit Test Generate Test Report SonarQube Scan SonarQube Quality Gate Build And Push Image
如果前面任一步失败,后面步骤不会继续执行。
二十八:验证测试结果
在 Harness 执行日志中检查:
npm install 是否成功 npm test 是否输出 unit test passed coverage/unit-test-report.json 是否生成 SonarQube 扫描是否完成 Quality Gate 是否 OK Docker 镜像是否推送成功
查看测试报告内容:
{
"app": "harness-demo-app",
"status": "PASS",
"total": 3,
"passed": 3,
"failed": 0,
"timestamp": "2026-06-15T00:00:00.000Z"
}
二十九:验证 Harbor 镜像
在本地或运维机执行:
docker login harbor.company.com
docker pull harbor.company.com/devops/harness-demo-app:替换为流水线序号
查看 Harbor UI:
Projects → devops → Repositories → harness-demo-app → Artifacts
确认:
存在 <+pipeline.sequenceId> Tag 存在 <+codebase.commitSha> Tag 镜像上传时间与流水线执行时间一致 镜像 Digest 可查看
三十:验证 SonarQube
进入 SonarQube:
Projects → harness-demo-app
检查:
Bugs Vulnerabilities Code Smells Duplications Coverage Quality Gate
如果 Quality Gate 为 Failed,Harness 中 SonarQube Quality Gate Step 应该失败,后续 Docker Build 不应执行。
三十一:常见问题排查
1. npm install 很慢或失败
可能原因:
没有使用国内 npm 源 .npmrc 没有提交 企业网络阻断 registry.npmmirror.com CI Pod 无法访问企业 npm 私服
排查:
npm config get registry
curl -I https://registry.npmmirror.com
解决:
配置 .npmrc 使用企业 npm 私服 将 npm 依赖缓存到 Nexus / Verdaccio
2. 单元测试失败
可能原因:
测试代码有问题 依赖没装完整 Node 版本不一致 本地和 CI 环境不一致
排查:
node -v
npm -v
npm test
建议:
本地开发环境和 CI 镜像 Node 版本保持一致 测试失败必须阻断发布
3. SonarQube 扫描失败
可能原因:
SONAR_HOST_URL 错误 SONAR_TOKEN 错误 SonarQube 项目不存在 Delegate / CI Pod 访问不到 SonarQube 自签证书不被信任 sonar-project.properties 配置错误
排查:
curl -k -I https://sonarqube.company.com
解决:
确认 SonarQube Token 有项目分析权限 确认项目 key 与 sonar-project.properties 一致 导入企业 CA 证书到 scanner 镜像
4. Quality Gate 一直等待
可能原因:
SonarQube 后台任务慢 SonarQube Compute Engine 队列堆积 脚本没有读到 .scannerwork/report-task.txt
排查:
cat .scannerwork/report-task.txt
进入 SonarQube:
Administration → Projects → Background Tasks
5. Docker Build 失败
可能原因:
Dockerfile 路径错误 Context 配置错误 基础镜像不存在 基础镜像拉取失败 Harbor 自签证书问题
排查:
docker pull harbor.company.com/library/node:20-alpine
docker build -f docker/Dockerfile -t test .
6. Docker Push 失败
可能原因:
Harbor Connector 认证失败 Robot Account 没有 push 权限 Harbor 项目不存在 镜像名写错 Tag 不合法
排查:
docker login harbor.company.com
docker push harbor.company.com/devops/harness-demo-app:test
解决:
检查 Robot Account 权限 检查 Harness Secret 检查 Docker Registry Connector
7. Kubernetes 拉不到镜像
可能原因:
imagePullSecrets 不存在 Harbor 机器人账号没有 pull 权限 Deployment 中镜像 Tag 和构建 Tag 不一致 Kubernetes 节点不信任 Harbor 证书
排查:
kubectl describe pod -n dev
kubectl get secret harbor-pull-secret -n dev
三十二:企业制品管理最佳实践
1. Harbor 项目分层
建议:
library:基础镜像 platform:平台工具镜像 devops:业务应用镜像 security:安全扫描工具镜像 middleware:中间件镜像
2. Robot Account 分环境
建议:
dev 环境:robotdevops+test-ci prod 环境:robot$devops+prod-release
不要所有环境共用一个机器人账号。
3. 镜像不可变
生产环境建议开启:
Tag 不可覆盖 镜像保留策略 镜像漏洞扫描 镜像签名
4. 测试门禁
推荐最小门禁:
单元测试必须通过 SonarQube Quality Gate 必须 OK Docker 镜像必须构建成功 镜像必须推送到 Harbor 部署前必须人工审批
5. 流水线失败策略
建议:
测试失败:直接终止 质量门失败:直接终止 镜像构建失败:直接终止 推送失败:直接终止 部署失败:触发回滚或人工介入
三十三:练习 1 — 让单元测试失败并观察流水线
修改 app/server.test.js:
throw new Error("mock unit test failed");
提交代码:
git add app/server.test.js
git commit -m "test: mock unit test failure"
git push origin main
预期:
Unit Test Step 失败 Generate Test Report 不执行 SonarQube Scan 不执行 Build And Push Image 不执行 Deploy 不执行
目的:
理解测试失败必须阻断制品发布
三十四:练习 2 — 集成 SonarQube
完成以下任务:
- 在 SonarQube 创建 harness-demo-app 项目
- 创建 Project Analysis Token
- 在 Harness 创建 sonar_token Secret
- 在仓库添加 sonar-project.properties
- 在 Pipeline 中添加 SonarQube Scan Step
- 在 Pipeline 中添加 Quality Gate 检查 Step
验收:
SonarQube 项目中可以看到扫描结果 Quality Gate 为 OK 时继续构建镜像 Quality Gate 为 Failed 时阻断构建镜像
三十五:练习 3 — 优化镜像 Tag
将 Build And Push Image Step 的 Tag 改为:
<+codebase.commitSha>
Deployment 中也改为:
image: harbor.company.com/devops/harness-demo-app:<+codebase.commitSha>
验收:
Harbor 中出现 commit sha 对应镜像 Kubernetes 部署的镜像与 Git commit 一致
三十六:练习 4 — 增加 Harbor 制品保留策略
在 Harbor 中配置:
Project → devops → Policy → Retention
建议策略:
保留最近 30 个 release-* 镜像 保留最近 20 个 main 分支镜像 清理 30 天前无 tag 镜像
目的:
避免 Harbor 存储无限增长 保留可回滚版本
三十七:验收标准
完成本文后,应达到:
已在 Pipeline 中添加 npm install 已在 Pipeline 中添加 npm test 测试失败可以阻断后续步骤 已生成测试报告 已完成 SonarQube 扫描 已完成 Quality Gate 检查 Quality Gate 失败可以阻断镜像构建 已构建 Docker 镜像 已推送镜像到 Harbor 镜像 Tag 可以追溯到 Pipeline 或 Git Commit Harbor 中可以查看对应镜像制品
三十八:本篇总结
本篇完成了 Harness 中"测试 + 制品管理"的基础落地。
你需要重点记住:
CI/CD 不是只做构建和部署,测试门禁同样重要 npm 单元测试应该放在 Docker Build 前面 测试失败不能继续构建镜像 SonarQube 扫描适合放在测试后、镜像构建前 Quality Gate 失败必须阻断发布 Harbor Robot Account 不要使用个人账号 Harbor Token 必须放入 Harness Secret 镜像 Tag 必须能追溯到 Git Commit 和 Pipeline 执行记录 生产环境不要依赖 latest 国内环境要提前同步基础镜像和扫描工具镜像