4、测试环境搭建:Docker、Kubernetes、证书模拟服务

做自动化回归测试,环境搭不好,后面全白忙。证书接口的测试尤其烦人——你得有一个能签发、验证、吊销证书的完整环境,又不能动生产CA。这套环境怎么搭?我踩过不少坑,今天把最稳的路线讲给你。

说白了,我们要三样东西:Docker 把服务包起来、Kubernetes 编排起来、证书模拟服务 把证书链跑起来。缺一个,测试就不完整。

为什么需要这个铁三角?

你想想看,证书接口回归测试,每次都要真实地发证、验证、撤销。如果依赖第三方测试CA,网络慢、限流、证书过期……我遇到过一回,测试因为CA服务器响应超时集体失败,排查半天才发现是外部服务挂了。

所以我的原则是:所有依赖都在本地集群里跑。Docker 保证环境一致,K8s 保证服务编排和自愈,自定义 CA 模拟服务保证证书生命周期完全可控。

核心目标:每次 CI 触发,自动拉起一个隔离的测试环境,跑完回归用例后自动销毁。整个过程不用碰任何外部网络资源。

整体架构

先看一张图,帮你建立全局认知。这张图解释了三个组件怎么协同:

证书接口回归测试环境架构 代码仓库 CI/CD 管道 容器镜像仓库 Kubernetes 集群(本地 / CI 环境) 证书模拟服务 CA / RA / OCSP Responder 被测证书接口服务 签发 / 验证 / 吊销 回归测试容器 自动化测试用例 说明: CI 管道启动 → 拉取镜像 → 部署到 K8s → 证书模拟服务自动初始化 → 测试容器按顺序执行回归用例 → 生成报告 → 清理 Namespace

第一步:Docker 容器化服务

所有组件都要 Docker 化。证书模拟服务、被测接口、测试脚本,各自一个镜像。

证书模拟服务的 Dockerfile

我习惯用 CFSSL 作为轻量级 CA。官方镜像太大?自己编一个 Alpine 版:

FROM alpine:3.19
RUN apk add --no-cache cfssl cfssljson
WORKDIR /ca
COPY ca-config.json ca-csr.json .
RUN cfssl gencert -initca ca-csr.json | cfssljson -bare ca
EXPOSE 8888
CMD ["cfssl", "serve", "-address=0.0.0.0", "-port=8888", "-ca=ca.pem", "-ca-key=ca-key.pem", "-config=ca-config.json"]

不要在生产开发共用镜像。回归测试用的 CA 证书有效期设成 10 年,避免频繁更新。我吃过亏,证书过期后管道整整红了一周。

多阶段构建被测服务

# 构建阶段
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o cert-api .

# 运行阶段
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/cert-api /usr/local/bin/
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/
EXPOSE 8443
CMD ["cert-api"]

注意这里拷贝了系统根证书,因为被测服务可能需要验证客户端证书。我在项目里还加了一层 HEALTHCHECK,让 K8s 能感知服务状态。

第二步:Kubernetes 编排

Docker 镜像造好了,接下来怎么在 CI 里跑?用 Minikube 或者 Kind 最省事。我推荐 Kind,因为它能把 集群跑在 Docker 容器里,CI 节点不用额外安装 K8s。

完整的部署清单

# 1. 命名空间隔离
apiVersion: v1
kind: Namespace
metadata:
  name: cert-test-$CI_PIPELINE_ID
---
# 2. 证书模拟服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mock-ca
  namespace: cert-test-$CI_PIPELINE_ID
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mock-ca
  template:
    metadata:
      labels:
        app: mock-ca
    spec:
      containers:
      - name: ca
        image: ${CI_REGISTRY}/mock-ca:${CI_COMMIT_SHA}
        ports:
        - containerPort: 8888
---
apiVersion: v1
kind: Service
metadata:
  name: mock-ca-svc
  namespace: cert-test-$CI_PIPELINE_ID
spec:
  selector:
    app: mock-ca
  ports:
  - port: 8888
---
# 3. 被测服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cert-api
  namespace: cert-test-$CI_PIPELINE_ID
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cert-api
  template:
    metadata:
      labels:
        app: cert-api
    spec:
      containers:
      - name: api
        image: ${CI_REGISTRY}/cert-api:${CI_COMMIT_SHA}
        env:
        - name: CA_ENDPOINT
          value: http://mock-ca-svc:8888
        ports:
        - containerPort: 8443

关键点:命名空间带上流水线 ID,保证并发跑不同分支时互不干扰。我在项目中曾因为没隔离,两个分支同时测同一个 CA 服务,结果证书状态全乱套了。

Init Container 等待依赖

证书模拟服务启动需要几秒。如果被测服务先启动连不上 CA,测试全失败。加一个 Init Container 做健康检查。

initContainers:
- name: wait-for-ca
  image: busybox:1.36
  command:
  - sh
  - -c
  - "until nc -z mock-ca-svc 8888; do echo waiting for CA; sleep 1; done"

第三步:证书模拟服务细节

这是整个环境的核心。我们需要一个能模拟 CA 签发的服务,同时支持 OCSP 和 CRL 查询。

使用 cfssl 搭建

cfssl 自带 REST API,正好符合接口测试场景。下面是 ca-config.json 模板:

{
  "signing": {
    "default": {
      "expiry": "87600h",
      "usages": ["signing", "key encipherment", "server auth", "client auth"]
    },
    "profiles": {
      "test-server": {
        "expiry": "72h",
        "usages": ["signing", "key encipherment", "server auth"]
      },
      "test-client": {
        "expiry": "24h",
        "usages": ["signing", "key encipherment", "client auth"]
      },
      "revocable": {
        "expiry": "1h",
        "usages": ["signing", "key encipherment", "client auth"]
      }
    }
  }
}

为什么加不同 Profile?回归测试需要覆盖:服务器证书、客户端证书、即将过期的证书、已被吊销的证书。每个 Profile 对应一种测试场景。

注入测试数据

在容器启动脚本里,用 cfssljson 预生成一批测试证书:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -profile=test-server ca-csr.json | cfssljson -bare server1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -profile=revocable revoke-csr.json | cfssljson -bare to-revoke

# 然后通过 API 吊销 to-revoke
cfssl revoke --ca=ca.pem --ca-key=ca-key.pem --reason="keyCompromise" --serial=$(openssl x509 -in to-revoke.pem -noout -serial | cut -d= -f2)

这套数据会随镜像一起打包。CI 每次拉起环境,证书池都是干净的已知状态,回归用例可以精确断言。

自动化清理

测试跑完后必须清理。用 kubectl delete namespace cert-test-$CI_PIPELINE_ID 在 CI 的 after_script 里执行。如果忘了清理,集群会被占满。我自己一次没配清理钩子,CI 跑了 200 多次后 Minikube 直接挂掉。

组件镜像大小启动时间关键配置
证书模拟服务~30 MB<3秒ca-config.json 预定义 Profile
被测证书接口~15 MB<1秒CA_ENDPOINT 环境变量
回归测试容器~50 MB<1秒挂载测试证书文件

嗯,到这里环境搭建的核心内容就讲完了。这些实践我反复打磨过,直接拿去做你的 CI 管道底座,证书接口回归测试就不会再因为环境问题翻车了。