第二章 测试环境搭建:离线网络环境模拟、容器化部署、DNS与路由配置、依赖服务Mock化
2.1 离线环境模拟原理
离线环境模拟的核心目标是:在本地开发/CI 节点上构造一个与外部网络完全隔离,但内部服务拓扑可自由编排的封闭网络空间。通过这种模拟,集成测试可以脱离对公网、外部 API、第三方服务的实时依赖,实现可重复、可断点、可注入故障的确定性验证。
- 网络隔离 — 切断被测容器与外部互联网的连接,仅允许预定义的内部路由与端口通信。
- DNS 劫持 — 将所有外部域名(如
api.example.com)解析到本地 Mock 服务或特定容器 IP,避免真实网络调用。 - 服务 Mock — 用轻量桩服务替代真实依赖,按测试用例返回预设响应或注入延迟/错误。
原则:离线环境应“像生产一样测试,像测试一样生产”。容器网络命名空间、iptables 规则、DNS 转发链都应与生产环境保持语义一致,仅切断外部可达性。
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),且无法通过宿主机网关出站。
| 外部域名 | 劫持目标 IP | 对应服务 |
|---|---|---|
*.example.com | 172.20.0.3 | Mock Server (WireMock) |
*.googleapis.com | 172.20.0.3 | Mock Server (WireMock) |
*.s3.amazonaws.com | 172.20.0.3 | Mock 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 规则做版本管理,确保环境可复现。