第二章 测试环境搭建:离线网络环境模拟、容器化部署、DNS与路由配置、依赖服务Mock化

2.1 离线环境模拟原理

离线环境模拟的核心目标是:在本地开发/CI 节点上构造一个与外部网络完全隔离,但内部服务拓扑可自由编排的封闭网络空间。通过这种模拟,集成测试可以脱离对公网、外部 API、第三方服务的实时依赖,实现可重复、可断点、可注入故障的确定性验证。

  • 网络隔离 — 切断被测容器与外部互联网的连接,仅允许预定义的内部路由与端口通信。
  • DNS 劫持 — 将所有外部域名(如 api.example.com)解析到本地 Mock 服务或特定容器 IP,避免真实网络调用。
  • 服务 Mock — 用轻量桩服务替代真实依赖,按测试用例返回预设响应或注入延迟/错误。
原则:离线环境应“像生产一样测试,像测试一样生产”。容器网络命名空间、iptables 规则、DNS 转发链都应与生产环境保持语义一致,仅切断外部可达性。
离线测试环境三层架构图 离线测试环境架构 物理网络层 网络隔离 (iptables / eBPF) 切断外部互联网可达性 防火墙规则 仅允许预定义端口与协议 容器编排层 容器化部署 Docker / Podman DNS 配置 dnsmasq / CoreDNS 路由规则 ip route / 策略路由 服务 Mock 层 Mock Server (WireMock / MockServer) 预设响应 · 延迟注入 · 故障模拟 依赖服务模拟 Redis · MySQL · S3 · 第三方 API 提示:三层之间通过 docker network 与 iptables 规则实现隔离,DNS 劫持所有外部域名到 Mock 服务。

2.2 容器化网络部署方案

采用 Docker 的 bridge 网络模式作为离线环境的基础网络单元,辅以 macvlan 或 none 模式应对特殊场景。所有被测服务容器均连接到自定义 bridge 网络,该网络不设置网关或仅设置指向本地 DNS 容器的网关,从而彻底切断外部网络。

2.2.1 自定义离线网络的创建

# 创建离线 bridge 网络,不分配外部网关
docker network create \
    --driver bridge \
    --subnet=172.20.0.0/16 \
    --gateway=172.20.0.1 \
    --opt com.docker.network.bridge.name=off-bridge \
    --opt com.docker.network.bridge.enable_ip_masquerade=false \
    offline-net

# 验证网络隔离性
docker run --rm --network offline-net alpine ping -c 2 8.8.8.8
# 预期结果:ping 失败,无路由到外部
关键参数:enable_ip_masquerade=false 禁止容器出站时做 SNAT,确保容器无法访问外部网络。配合 iptables 规则丢弃所有非本地回环和预定义子网的出站包。

2.2.2 容器化部署模板

使用 Docker Compose 定义服务拓扑,所有服务挂载到同一个 offline-net 网络。以下是一个典型的离线环境 docker-compose.yml 片段:

version: "3.9"
services:
  dns:
    image: coredns/coredns:1.11
    container_name: offline-dns
    networks:
      offline-net:
        ipv4_address: 172.20.0.2
    volumes:
      - ./coredns/Corefile:/Corefile
      - ./coredns/hosts:/etc/hosts
    ports:
      - "53:53/udp"

  mock-server:
    image: wiremock/wiremock:3.5
    container_name: offline-mock
    networks:
      offline-net:
        ipv4_address: 172.20.0.3
    volumes:
      - ./wiremock/mappings:/home/wiremock/mappings
      - ./wiremock/__files:/home/wiremock/__files
    command: ["--port", "8080", "--global-response-templating"]

  app-under-test:
    build: ./app
    container_name: app-test
    networks:
      offline-net:
        ipv4_address: 172.20.0.10
    depends_on:
      - dns
      - mock-server
    dns:
      - 172.20.0.2
    environment:
      - HTTP_PROXY=
      - HTTPS_PROXY=
      - NO_PROXY=*

networks:
  offline-net:
    external: true
    name: offline-net
dns 字段说明:容器级 DNS 配置优先于宿主机 /etc/resolv.conf,将 dns 设置为本地 CoreDNS 容器 IP(172.20.0.2),确保所有域名解析请求都被劫持到本地 DNS 服务器。

2.3 DNS 与路由配置

DNS 是离线环境的关键枢纽。所有外部域名必须被解析到本地 Mock 服务或特定测试容器,同时内部服务域名(如 svc-a.offline)能够正常解析。这里采用 CoreDNS 作为权威/转发 DNS 服务器。

2.3.1 CoreDNS 配置文件

在 ./coredns/Corefile 中定义解析规则:

.:53 {
    errors
    health
    hosts /etc/hosts {
        ttl 60
        reload 30s
        fallthrough
    }
    forward . 172.20.0.3:8080 {
        force_tcp
        max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}

hosts 文件(./coredns/hosts)用于静态映射内部服务:

172.20.0.3    api.example.com
172.20.0.3    auth.example.com
172.20.0.3    storage.example.com
172.20.0.10   app.internal.offline
172.20.0.11   db.offline
172.20.0.12   cache.offline

2.3.2 路由规则与 iptables 强化

为防止容器通过宿主机的默认路由泄漏流量,在宿主机上添加 iptables 规则:

# 丢弃来自离线网络的非目标子网出站包
iptables -A FORWARD -i off-bridge -s 172.20.0.0/16 \
    ! -d 172.20.0.0/16 -j DROP

# 禁止容器访问宿主机外的 53 端口(防 DNS 泄漏)
iptables -A OUTPUT -o off-bridge -p udp --dport 53 \
    ! -d 172.20.0.2 -j REJECT --reject-with icmp-port-unreachable

# 允许容器间通信
iptables -A FORWARD -i off-bridge -o off-bridge -j ACCEPT
验证路由隔离:在容器内执行 ip route show 应只看到一条默认路由指向 CoreDNS 容器(172.20.0.2),且无法通过宿主机网关出站。
DNS 劫持目标对照表
外部域名劫持目标 IP对应服务
*.example.com172.20.0.3Mock Server (WireMock)
*.googleapis.com172.20.0.3Mock Server (WireMock)
*.s3.amazonaws.com172.20.0.3Mock Server (WireMock)
*.offline按 hosts 映射内部测试服务

2.4 依赖服务 Mock 化

离线环境下,所有外部依赖必须由 Mock 服务替代。WireMock 是首选方案,支持动态响应模板、故障注入和录制回放。

2.4.1 WireMock 映射定义示例

在 ./wiremock/mappings/ 目录下创建 JSON 映射文件。以下模拟 GET /api/users/1 的正常响应:

{
    "request": {
        "method": "GET",
        "urlPattern": "/api/users/[0-9]+"
    },
    "response": {
        "status": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "jsonBody": {
            "id": 1,
            "name": "Alice",
            "email": "alice@offline.test"
        },
        "transformers": ["response-template"]
    }
}

模拟超时或 500 错误的故障注入映射:

{
    "request": {
        "method": "GET",
        "urlPath": "/api/payments/timeout"
    },
    "response": {
        "status": 200,
        "fixedDelayMilliseconds": 30000
    },
    "priority": 1
}

2.4.2 Mock 服务健康检查与预热

# 等待 WireMock 就绪
while ! curl -s http://172.20.0.3:8080/__admin/health; do
    echo "等待 Mock Server 启动..."
    sleep 1
done

# 批量注册映射(可选替代文件挂载)
curl -X POST http://172.20.0.3:8080/__admin/mappings/import \
    -H "Content-Type: application/json" \
    -d @./test-mappings/batch-import.json
推荐:将 WireMock 的 mappings 目录挂载到容器后,修改本地文件即可热加载,无需重启容器。WireMock 默认每 10 秒扫描一次文件变更。

2.5 调试与验证

环境搭建完成后,需要从网络连通性、DNS 解析、Mock 响应三个维度进行验证。

2.5.1 网络连通性验证

# 在 app-under-test 容器内执行
docker exec app-test sh -c "
    ping -c 2 172.20.0.2 && echo 'DNS可达' || echo 'DNS不可达'
    ping -c 2 172.20.0.3 && echo 'Mock可达' || echo 'Mock不可达'
    ping -c 2 8.8.8.8 2>&1 | grep -q '100% packet loss' \
        && echo '外网已隔离' || echo '外网未隔离'
"

2.5.2 DNS 解析验证

# 使用 dig 验证域名劫持
docker exec app-test sh -c "
    dig +short api.example.com @172.20.0.2
    dig +short auth.example.com @172.20.0.2
    dig +short storage.example.com @172.20.0.2
"
# 预期全部返回 172.20.0.3

2.5.3 Mock 服务响应验证

# 验证 Mock 接口
docker exec app-test sh -c "
    curl -s http://172.20.0.3:8080/api/users/1 | jq '.name'
    curl -s -o /dev/null -w '%{http_code}' http://172.20.0.3:8080/api/users/2
"
# 预期:Alice  200
调试利器:在 app-test 容器内使用 tcpdump -i eth0 -n 观察 DNS 查询和目标 IP 流量,确认所有外部请求都被重定向到 Mock Server。

2.6 常见错误与优化

常见错误及排查方法
错误现象根因解决方案
容器仍能访问外网iptables 规则未生效或未加载检查 iptables -L FORWARD -v,确认 DROP 规则在正确链中
DNS 解析返回真实公网 IP容器 dns 字段未设置或 CoreDNS 未监听 53 端口确认 docker-compose.yml 中 dns: 172.20.0.2 已配置
Mock 响应返回 504域名解析到 Mock Server 但端口不匹配检查 WireMock 监听端口(默认 8080)与请求 URL 端口一致
容器启动顺序导致 Mock 未就绪依赖服务启动早于 WireMock使用 depends_on + 健康检查等待脚本

2.6.1 性能与稳定性优化

  • CoreDNS 缓存放大:设置 cache 60 减少 DNS 压力,高并发场景可提升至 120 秒。
  • WireMock 异步响应:启用 --async-response-enabled=true 避免慢映射阻塞线程池。
  • iptables 规则持久化:使用 iptables-save > /etc/iptables/rules.v4 配合 iptables-restore 在宿主机重启后自动加载。
  • 资源限制:为 Mock 容器设置 CPU/Memory limit,防止测试风暴打满宿主机。
# 持久化 iptables 规则(Debian/Ubuntu)
apt install -y iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
注意:在 CI 环境中每次运行测试前,应执行 docker network prune -f 清除残留网络,避免 IP 冲突导致路由混乱。同时对 Corefile 和 iptables 规则做版本管理,确保环境可复现。