在微服务架构和快速迭代的背景下,开发团队经常希望每个特性分支都能生成一个独立的预览环境。这些环境不需要完整的生产配置,但必须能够快速启动、展示功能并接收反馈。手工为每个分支构建完整镜像不仅耗时,而且容易引入环境差异。Mock2Image 就是针对这一问题设计的轻量方案:它由 Node.js 脚本驱动,在 CircleCI 构建阶段根据分支代码和预置的模拟配置,生成一个可直接被 Kubernetes 拉取的容器镜像。整个过程无需人工干预,只需要一次代码推送。

这套机制的核心思想是"镜像即代码"。Node.js 脚本读取仓库中的 Dockerfile 模板、环境变量清单以及模拟数据文件,然后将它们组合为一个自包含的构建上下文,最后调用 Docker 命令或使用容器构建库(如 dockerode)生成镜像。因为模拟数据已被打包进镜像,所以部署到 Kubernetes 后,应用启动即可读取这些模拟数据,无需再挂载外部 ConfigMap 或依赖外部服务。
Mock2Image 的 Node.js 核心实现
Node.js 在构建工具链中扮演着胶水角色。我们用 fs-extra 操作文件系统,用 path 处理路径,用 child_process 或 dockerode 模块触发镜像构建。首先,脚本需要根据传入的参数(例如分支名、提交哈希)生成唯一的镜像标签。标签中通常会包含分支名和短提交 ID,例如 feature-login-a1b2c3d。这样不仅便于识别,也能避免标签冲突。
接下来,脚本会从模板目录拷贝 Dockerfile。模板中可以使用占位符,比如 ${MOCK_DATA_PATH},由 Node.js 脚本在拷贝时替换为实际路径。模拟数据文件则放在一个约定目录(如 mock-data/)下,构建时直接被 ADD 到镜像内。Node.js 会先检查该目录是否存在,避免构建失败。如果某个分支没有提供模拟数据,脚本可以回退到默认的模拟数据文件,保证构建成功。
下面是一段简化版的 Node.js 脚本片段,展示了如何准备构建上下文并调用 Docker 构建:
const fs = require('fs-extra');
const path = require('path');
const { execSync } = require('child_process');
const branch = process.env.CIRCLE_BRANCH || 'local';
const commit = process.env.CIRCLE_SHA1 || 'dev';
const imageTag = `${branch}-${commit.substring(0,7)}`;
const contextDir = path.join(process.cwd(), 'docker-build');
fs.ensureDirSync(contextDir);
// 拷贝 Dockerfile 模板并替换占位符
const dockerfileTemplate = fs.readFileSync('Dockerfile.template', 'utf8');
const dockerfileContent = dockerfileTemplate
.replace('${MOCK_DATA_PATH}', './mock-data');
fs.writeFileSync(path.join(contextDir, 'Dockerfile'), dockerfileContent);
// 拷贝应用代码和模拟数据
fs.copySync('app/', path.join(contextDir, 'app'));
fs.copySync('mock-data/', path.join(contextDir, 'mock-data'), { overwrite: true });
// 构建镜像
execSync(`docker build -t registry.ipipp.com/myapp:${imageTag} ${contextDir}`);
console.log(`镜像构建完成:registry.ipipp.com/myapp:${imageTag}`);
实际生产环境中,我们还可以利用 dockerode 模块实现更精细的控制,例如实时输出构建日志、中断超时任务等。此外,Mock2Image 脚本通常还会接收参数来决定注入哪种类型的模拟数据(如错误场景、空数据场景),从而生成不同用途的测试镜像。
在 CircleCI 中编排 Mock2Image 流水线
CircleCI 的配置文件 .circleci/config.yml 是这个流程的调度中心。我们通常在工作流中定义一个专门的任务 build-mock-image,它在代码签出后安装 Node.js 依赖,然后执行 Mock2Image 脚本。为了加速构建,可以将 Node.js 依赖缓存起来,并利用 CircleCI 的 Docker 执行器或机器执行器直接运行 Docker 命令。
一个典型的 CircleCI Job 配置如下:
version: 2.1
jobs:
build-mock-image:
machine:
image: ubuntu-2004:2023.07.1
steps:
- checkout
- run:
name: Install Node.js dependencies
command: npm ci
- run:
name: Build Mock Image
command: node scripts/mock2image.js
- run:
name: Push Image to Registry
command: |
echo $DOCKER_PASSWORD | docker login registry.ipipp.com --username $DOCKER_USER --password-stdin
COMMIT_SHORT=$(echo $CIRCLE_SHA1 | cut -c1-7)
TAG="${CIRCLE_BRANCH}-${COMMIT_SHORT}"
docker push registry.ipipp.com/myapp:$TAG
workflows:
mock-build-deploy:
jobs:
- build-mock-image:
filters:
branches:
ignore: main
这里使用了 machine 执行器以便在容器内运行 Docker 命令。如果仅用 Docker 执行器,需要通过挂载 Docker socket 或远程 Docker 服务来实现镜像构建。为了避免主分支产生大量无用镜像,我们通过分支过滤让该任务只在非 main 分支上运行。镜像推送后,CircleCI 还可以通过 store_artifacts 或自定义通知将镜像标签传递给下游部署步骤。
为了进一步提高构建速度,可以在 CircleCI 中使用 Docker 层缓存(DLC),但由于 Mock 镜像本身应该尽量精简,通常我们会选择体积很小的基础镜像(如 node:18-alpine),并在构建过程中清理不必要的文件。
Kubernetes 集成与镜像消费
镜像推送到仓库后,Kubernetes 集群中的控制器或手动命令可以将其拉起。为了让预览环境能够自动获取最新 Mock 镜像,我们可以结合 GitOps 工具(如 ArgoCD)监听镜像仓库标签变化,或者在 CircleCI 流水线中直接使用 kubectl 或 Kubernetes API 更新 Deployment 的镜像字段。
下面是一个典型的 Deployment 清单,其镜像标签由外部流程动态注入(例如通过 sed 替换 __IMAGE_TAG__):
apiVersion: apps/v1
kind: Deployment
metadata:
name: mock-preview
spec:
replicas: 1
selector:
matchLabels:
app: mock-preview
template:
metadata:
labels:
app: mock-preview
spec:
containers:
- name: app
image: registry.ipipp.com/myapp:__IMAGE_TAG__
ports:
- containerPort: 3000
env:
- name: MOCK_MODE
value: "true"
当 CircleCI 构建完成并推送镜像后,可以在后续步骤中执行 kubectl set image deployment/mock-preview app=registry.ipipp.com/myapp:$TAG,从而滚动更新预览环境。为了不让环境因长期未被使用而浪费资源,还可以配合 Kubernetes 的 TTL Controller 或定时清理 CronJob 删除过期的预览 Pod。
整个链路中,Node.js 的 Mock2Image 脚本起到了承上启下的作用:它向下屏蔽了 Docker 构建细节,向上为 CircleCI 提供了统一入口,输出一个标准化的镜像标签。这种方式将构建逻辑与 CI 配置解耦,使得 Mock2Image 脚本可以被其他 CI 系统(如 GitHub Actions)直接复用。同时,通过将模拟数据打包进镜像,Kubernetes 部署时无需额外挂载 ConfigMap,极大降低了环境配置的复杂度,使预览环境真正做到一次构建、随处运行。
Node.jsCircleCIKubernetes修改时间:2026-08-12 20:01:10