常见基础设施服务¶
应用能够启动,并不等于它已经适合被用户访问。生产流量通常还要经过防火墙、负载均衡器、反向代理和缓存;真正返回内容的组件也可能是静态 Web 服务器、应用服务器或二者的组合。本章把这些组件放回同一条请求链路中,重点说明每一层的职责边界、失效方式和选型依据。
学习目标¶
完成本章后,你应当能够:
- 根据流量方向区分正向代理与反向代理,并画出请求经过的信任边界。
- 解释缓存键、有效期、失效和一致性之间的关系。
- 区分网络防火墙、主机防火墙与 Web 应用防火墙的能力边界。
- 为一个无状态 HTTP 服务选择四层或七层负载均衡,并配置健康检查。
- 比较 Nginx、Caddy、Tomcat、Apache HTTP Server 与 IIS 的定位和运维约束。
- 在本机运行一个包含双后端和反向代理的最小环境,并验证负载分发。
- 列出入口服务上线前必须落实的安全、可靠性和可观测性措施。
前置知识¶
核心原理:从一条请求理解各层职责¶
入口架构的首要原则是让每一层只承担清晰、可验证的职责。一个常见但并非唯一的链路如下:
flowchart LR
U[用户代理] --> FW[边界防火墙]
FW --> LB[负载均衡]
LB --> RP[反向代理或 Web Server]
RP --> C[(缓存)]
RP --> A1[应用实例 A]
RP --> A2[应用实例 B]
E[内部客户端] --> FP[正向代理]
FP --> I[互联网服务]
图中组件可以合并。例如,云负载均衡器可能同时终止 TLS、执行七层路由和提供 Web 应用防火墙(WAF);Nginx 或 Caddy 也能同时充当 Web Server、反向代理、缓存和软件负载均衡器。能力可以合并,但职责仍应分别建模,否则出现 502、缓存旧数据或连接超时时很难定位责任层。
数据面与控制面¶
代理、负载均衡器和防火墙实际处理连接的部分称为数据面;下发路由、证书、访问策略和后端列表的部分称为控制面。生产事故经常来自控制面变更,而不是数据面程序崩溃。因此需要同时监控:
- 数据面:吞吐、并发连接、状态码、延迟、重试和丢包。
- 控制面:配置版本、证书期限、变更结果、配置分发延迟和失败回滚。
正向代理¶
正向代理(Forward Proxy)代表客户端访问目标服务。目标站点通常只看到代理出口地址,而不直接看到发起连接的内部主机。企业常用它控制出站访问、审计软件仓库下载、统一内容过滤或让隔离网络通过受控出口访问互联网。
HTTP 客户端可通过 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY 指定代理。对于 HTTPS,传统 HTTP 代理通常通过 CONNECT 建立 TCP 隧道;代理只有在实施 TLS 解密且客户端信任企业证书时才能读取加密内容。SOCKS 代理工作在更通用的连接层,不理解 HTTP 方法和缓存语义。
关键设计点包括:
- 身份与授权:按用户、工作负载或设备身份授权,不要只依赖容易变化的源 IP。
- 目标限制:默认拒绝高风险地址范围,尤其是云元数据地址、环回地址和内部控制面。
- 名称解析:明确 DNS 由客户端还是代理完成,避免策略检查的域名与实际连接地址不一致。
- 绕过列表:
NO_PROXY应精确到必要的主机或网段;过宽会让审计失效,遗漏则可能使内部流量绕远。 - 隐私边界:TLS 解密会扩大敏感数据暴露范围,必须有明确的合规依据、证书治理和访问审计。
代理不是匿名保证
代理仍可能通过认证信息、请求头、日志和流量特征识别客户端。不要把企业代理当作隐私工具,也不要在代理日志中无差别记录令牌、Cookie 或请求正文。
反向代理¶
反向代理(Reverse Proxy)代表服务端接收客户端请求,再选择内部后端。客户端通常只知道代理的公开地址。它常承担以下职责:
- TLS 终止与证书轮换。
- 基于主机名、路径、方法或请求头的七层路由。
- 请求大小、连接数和速率限制。
- 规范化或增删请求头,例如生成
X-Request-ID。 - 压缩、静态文件服务、响应缓存和后端负载均衡。
- 隔离公开网络与应用网络,隐藏后端拓扑。
反向代理必须正确维护“原始客户端信息”。常见协议是 Forwarded 或 X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host。应用只能信任由已知上游代理写入的字段;入口代理应覆盖来自公网的同名头,而不是无条件追加,否则攻击者可能伪造客户端 IP 或 HTTPS 状态。
超时应按调用链逐层收紧。通常客户端超时最大,越靠近后端的代理超时越短,从而给上游留下处理错误和重试的时间。请求是否可重试还取决于幂等性;代理不应自动重放不可安全重复的写请求。
缓存¶
缓存用更快、更近或更便宜的副本换取延迟和源站负载,但同时引入数据陈旧与一致性问题。常见位置包括浏览器缓存、CDN 或边缘缓存、反向代理缓存、应用内缓存和数据库查询缓存。
缓存决策的四个问题¶
- 缓存什么:优先缓存可公开、读取频繁、生成成本高且可接受短暂陈旧的数据。带身份的响应默认不应进入共享缓存。
- 用什么作键:通常至少包含请求方法和规范化后的目标 URI,也就是 scheme、authority(含主机)、路径与查询部分。仅在响应确实随某请求头变化时,才通过
Vary将该头纳入键;无边界地加入 Cookie 会造成缓存碎片。 - 缓存多久:HTTP 使用
Cache-Control表达max-age、s-maxage、private、no-store等约束。ETag和Last-Modified支持条件请求,命中验证时可返回304。 - 如何失效:可使用短 TTL、版本化 URL、主动清除或事件驱动刷新。失效动作必须能够定位具体键并被审计。
缓存未命中时,大量并发请求可能同时回源,形成缓存击穿。可通过请求合并、互斥刷新、过期数据短暂服务(stale-while-revalidate)、随机化 TTL 和后端限流缓解。对于不存在的对象,也可短时缓存明确的 404,但必须避免把临时故障长期负缓存。
优先版本化静态资源
将内容摘要写入文件名,例如 app.a1b2c3.js,然后为资源设置较长有效期。HTML 入口使用短缓存并引用新文件名,通常比频繁执行全站缓存清除更可靠。
防火墙¶
防火墙依据网络身份和连接特征允许或拒绝流量。它是缩小暴露面的基础控制,而不是修复应用漏洞的替代品。
- 无状态包过滤逐包判断源/目标地址、协议和端口,简单快速,但不了解连接上下文。
- 有状态防火墙跟踪 TCP 连接或 UDP 流的状态,可以允许与已有流量匹配的返回流量。
- 主机防火墙运行在服务器上,例如 Linux 的 nftables 或 Windows Defender Firewall,用于限制进出单机的流量。
- 网络防火墙或安全组在子网、虚拟网络或云网卡附近执行策略,便于集中隔离工作负载。
- WAF检查 HTTP 层特征,可执行虚拟补丁、机器人控制和请求速率限制,但不能替代输入验证、参数化查询和正确授权。
策略应遵循默认拒绝、最小开放和双向考虑。入站只允许明确的服务端口,出站也应限制到业务所需目标。规则需要记录负责人、理由和过期时间,并定期通过流日志确认是否仍被使用。
网络地址转换不是防火墙
NAT 会改写地址或端口,但“从外部暂时无法直接发起连接”只是拓扑效果,不是完整安全策略。端口转发、IPv6、同网段攻击和已有连接都可能绕过这种假设。应显式配置并审计允许规则。
负载均衡¶
负载均衡器把请求分配给多个可用后端,以扩展容量并隔离单实例故障。它不能凭空创造可用性:如果所有后端共享同一电源、可用区、数据库或错误配置,入口层仍有共同故障点。
四层与七层¶
- 四层负载均衡(L4)依据 IP、端口和传输层连接转发 TCP 或 UDP。协议开销较小,适合非 HTTP 流量、大吞吐或由后端终止 TLS 的场景。
- 七层负载均衡(L7)理解 HTTP 等应用协议,可按域名、路径、Header 或 Cookie 路由,也能执行 TLS 终止、重定向和 WAF 策略。功能更多,但资源消耗和配置复杂度更高。
常见分配算法包括轮询、加权轮询、最少连接、最短响应时间和一致性哈希。不要只按“平均请求数”选算法:长连接、请求成本差异、实例规格和缓存局部性都会影响实际负载。
健康检查与故障处理¶
存活检查只回答“进程是否还能响应”,就绪检查回答“实例现在是否应接收业务流量”。就绪检查应验证关键依赖但保持轻量,避免每秒对数据库执行昂贵查询。下线阈值过低会因瞬时抖动频繁摘除实例,过高又会持续向故障实例发送请求。
实例发布或缩容时应先标记不就绪,再等待连接排空。会话粘滞可以临时兼容本地会话状态,但会造成热点和故障后的会话丢失;更稳妥的设计是让应用无状态,或把会话放入有复制和过期策略的外部存储。
Web Server 与应用容器¶
以下产品并非完全同类。选型前先判断需要的是静态内容、通用反向代理、特定语言运行时,还是与操作系统深度集成的服务平台。
Nginx¶
重点掌握。Nginx 使用事件驱动模型处理大量并发连接,常用于静态文件、反向代理、TLS 终止、HTTP 缓存和软件负载均衡。配置由 http、server、location 等上下文组成;请求 URI 到 location 的匹配顺序、proxy_pass 是否携带 URI,以及 Header 继承规则都值得专门测试。
Nginx 开源版适合稳定且显式的配置。动态服务发现、主动健康检查和管理 API 等能力要结合具体版本、商业版或外围控制器评估。修改配置后应先运行 nginx -t,再优雅重载,使现有工作进程完成连接而不是被强制中断。
Caddy¶
替代方案。Caddy 的突出能力是自动申请、续期和部署 HTTPS 证书,Caddyfile 对常见站点和反向代理场景较简洁。它也提供结构化 JSON 配置和管理 API,适合自动化控制。
公网自动 HTTPS 依赖域名解析正确、验证端口可达且证书颁发机构可用。生产环境应持久化 Caddy 的数据目录,保护管理接口,设置证书申请速率预案,并在受限网络中明确内部 CA 或外部证书流程。容器每次启动都丢失证书状态会造成重复申请和限额问题。
Apache HTTP Server¶
替代方案。Apache HTTP Server(httpd)是模块化 Web Server,通过 mod_ssl、mod_proxy、mod_rewrite 等模块提供 TLS、代理和重写能力。它支持事件型及其他多处理模块(MPM),也能通过目录级 .htaccess 委派配置。
.htaccess 便于共享托管,但会增加逐目录查找和分散治理的成本;可控服务器上通常优先把规则放入主配置并禁用不需要的覆盖。应只加载所需模块,显式选择合适 MPM,并用 apachectl configtest 验证配置。
Tomcat¶
按需学习。Apache Tomcat 是 Java Servlet、Jakarta Server Pages 等规范的实现,核心定位是 Java Web 应用容器,而非 Nginx 或 Apache HTTP Server 的同义替代。它可以直接提供 HTTP 服务,也常放在反向代理之后,由代理处理公网 TLS、静态资源和请求限制。
生产部署应使用受支持的 JDK 与 Tomcat 组合,移除或严格保护 Manager、Host Manager 和示例应用,限制 AJP 连接器,仅开放需要的连接器。线程池、堆内存、垃圾回收和数据库连接池要联合容量规划;只增大 Tomcat 线程数可能把压力转移到数据库。
名称容易混淆
Apache HTTP Server 与 Apache Tomcat 都属于 Apache 软件基金会项目,但前者是通用 Web Server,后者主要承载 Java Web 应用。mod_proxy_http 可以把 HTTP Server 的请求转发给 Tomcat,它们也可以独立使用。
IIS¶
按需学习。Internet Information Services(IIS)是 Windows Server 的 Web Server 角色,与 Windows 身份验证、证书存储、PowerShell 管理和 ASP.NET 托管紧密集成。站点通过绑定关联 IP、端口、主机名和证书;应用程序池提供进程级隔离、身份和回收策略。
应为应用程序池配置最小权限的专用身份,避免把站点目录授予宽泛写权限。上线前检查请求筛选、日志字段、TLS 绑定、空闲回收和故障快速保护。对 ASP.NET Core,通常由 IIS 接收连接,再通过 ASP.NET Core Module 转交给应用进程。
选型比较¶
可以按以下顺序缩小范围:
- 先确定协议和运行时:Java Servlet 应用优先评估 Tomcat;深度依赖 Windows 身份和管理体系时评估 IIS;纯静态或通用 HTTP 入口再比较 Nginx、Caddy 与 Apache HTTP Server。
- 再确定控制方式:静态文件式配置强调可审查性;管理 API 或云托管入口更适合频繁变化的后端,但必须保护控制面。
- 比较证书运营成本:Caddy 默认自动化程度较高;其他产品也能借助 ACME 客户端或平台证书服务完成自动续期,但集成工作不同。
- 评估必要模块:确认认证、缓存、内容重写、动态服务发现和可观测性是否由入口承担。不要仅因产品“功能多”就开启更多模块。
- 量化迁移成本:专有重写规则、认证模块、应用程序池模型和运维 API 都可能形成绑定。保留标准 HTTP 行为测试和配置生成源,能降低替换成本。
对于负载均衡,低延迟 TCP 服务可优先评估 L4;需要按 URL 路由、统一 TLS 或 WAF 时使用 L7。云托管负载均衡减少节点维护,但可能产生按小时、规则、容量单位和跨区流量费用;自管软件入口控制力更高,却需要自行解决高可用、升级和容量。
最小实践:双后端与 Caddy 反向代理¶
下面的实验只把端口绑定到 127.0.0.1,后端只加入临时容器网络。它会启动两个返回不同文本的后端,并让 Caddy 轮询转发。所有命令应在同一个 Shell 中执行;容器使用运行时返回的 ID,避免与已有容器同名。
1. 编写配置¶
在空目录创建 Caddyfile:
:8080 {
log {
output stdout
format console
}
header {
X-Content-Type-Options nosniff
-Server
}
reverse_proxy app-a:5678 app-b:5678 {
lb_policy round_robin
health_uri /health
health_interval 10s
}
}
因为站点地址是 :8080 而不是域名,此实验使用本地 HTTP,不触发公网证书申请。生产环境不应照搬这一点。
2. 启动隔离环境¶
需要已经安装 Docker:
network_name="handbook-infra-$(id -u)-$$-$RANDOM"
network_id="$(docker network create "$network_name")"
app_a="$(docker run --detach --rm \
--network "$network_id" \
--network-alias app-a \
--cap-drop ALL \
--security-opt no-new-privileges \
--read-only \
hashicorp/http-echo:1.0 \
-listen=:5678 -text="response-from-a")"
app_b="$(docker run --detach --rm \
--network "$network_id" \
--network-alias app-b \
--cap-drop ALL \
--security-opt no-new-privileges \
--read-only \
hashicorp/http-echo:1.0 \
-listen=:5678 -text="response-from-b")"
proxy="$(docker run --detach --rm \
--network "$network_id" \
--publish 127.0.0.1:8080:8080 \
--cap-drop ALL \
--security-opt no-new-privileges \
--read-only \
--tmpfs /data \
--tmpfs /config \
--volume "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" \
caddy:2.11-alpine)"
示例固定了主版本和次版本以减少意外变化;长期项目还应使用经过验证的补丁版本或镜像摘要,并由依赖更新流程维护。
3. 验证路由和响应头¶
for request in 1 2 3 4; do
curl --fail --silent --show-error \
--header "X-Request-ID: lab-$request" \
http://127.0.0.1:8080/
done
curl --include http://127.0.0.1:8080/
docker logs "$proxy"
输出应同时出现 response-from-a 和 response-from-b,响应中应有 X-Content-Type-Options: nosniff。停止一个后端后再次请求,可以观察健康检查将其摘除:
4. 清理¶
这个实验不等于生产配置
实验没有启用 TLS、访问控制、持久证书存储和指标采集。健康端点也只是固定文本,不能证明真实应用已准备好处理业务。
生产实践¶
安全性¶
- 公网只暴露入口需要的端口,管理 API 和状态页面放在独立管理网络并要求强认证。
- 从可信代理列表推导客户端地址;清除公网请求伪造的转发头。
- 对请求头总大小、正文大小、上传时间、并发连接和请求速率设置边界。
- TLS 私钥由专用机密系统提供,文件权限最小化,证书续期失败需要提前告警。
- Web Server 和插件按支持周期升级,删除默认站点、示例应用、目录列表和不需要的模块。
- WAF 先在观察模式评估误报,再逐步阻断;高风险接口仍需应用层认证、授权和输入校验。
可靠性¶
- 至少跨两个独立故障域部署入口和后端,确认 DNS、负载均衡控制面和证书服务也没有单点。
- 对配置执行静态检查、预发布流量验证和小批量发布;失败时回滚到已知配置版本。
- 设置连接建立、首字节、空闲和总请求超时,限制重试次数并加入抖动,避免重试风暴。
- 优雅排空连接。发布时先停止接收新请求,再等待在途请求完成,最后终止进程。
- 估算入口过载行为:宁可快速返回可识别的
429或503,也不要无限排队拖垮全部实例。
可观测性¶
最少采集请求量、错误率和延迟分位数,并按路由、后端和状态码聚合。还应关注活动连接、连接队列、TLS 握手失败、健康后端数、缓存命中率、回源延迟和配置重载结果。
访问日志使用结构化格式并带请求 ID,但不要记录凭据、完整 Cookie 或敏感查询参数。让入口把请求 ID 传给应用,再在日志管理和可观测性体系中关联日志、指标和追踪。
成本与容量¶
入口容量测试要模拟真实响应大小、TLS 新连接比例、长连接和慢客户端。云产品还应计算处理容量单位、规则数、公网 IP、跨区流量和日志存储费用。缓存节省的回源计算和带宽,应与失效复杂度、存储及一致性风险一起评估。
常见误区¶
- 把反向代理当作应用安全边界的全部:它无法判断业务对象是否属于当前用户,授权仍须在应用中执行。
- 信任所有
X-Forwarded-For:攻击者可自行发送该头,导致审计、限流或 IP 授权被绕过。 - 健康检查只访问进程端口:进程存活时数据库连接池可能已耗尽,应区分存活与就绪信号。
- 给写请求配置无条件重试:重复扣款或重复创建资源是典型后果。先设计幂等键和去重机制。
- 把所有响应都缓存:带授权信息、
Set-Cookie或用户数据的响应进入共享缓存会造成数据泄露。 - 依赖会话粘滞掩盖状态设计:扩缩容和故障切换仍会让会话丢失或分布失衡。
- 直接重启入口加载配置:会中断连接。应先语法校验,再使用优雅重载或滚动替换。
- 混淆 Tomcat 与 Apache HTTP Server:二者可组合但运行模型、配置和风险面不同。
- 认为用了 NAT 或云安全组就不需要主机防火墙:多层控制能限制单个配置错误的影响范围。
动手练习¶
- 修改最小实践,为
app-a设置权重 3、app-b设置权重 1,连续请求至少 40 次并统计分布。结果应大致体现权重,而不是要求精确比例。 - 为代理增加一个
/ready路由,并使它返回不经过后端的固定200。说明为什么入口自身健康与后端健康需要分别观测。 - 让一个后端停止 5 秒后恢复,记录从失败到摘除、从恢复到重新加入的时间,评估期间客户端看到的错误数。
- 为一个包含
index.html和内容摘要文件名的 JavaScript 资源设计Cache-Control。结果应让 HTML 可较快更新,而摘要资源可长期缓存。 - 画出你熟悉系统的入口链路,标注每个 TLS 终止点、源客户端地址的传递方式,以及每层超时。若无法确定某项,就把它列为待验证风险。
完成检查¶
- 能从流量代表哪一方解释正向代理和反向代理的区别。
- 能说明缓存键、TTL、条件请求和失效策略。
- 能区分包过滤、主机或网络防火墙与 WAF。
- 能根据协议与路由需求选择 L4 或 L7 负载均衡。
- 能解释健康检查、连接排空、重试和会话粘滞的风险。
- 能说出 Nginx、Caddy、Tomcat、Apache HTTP Server 和 IIS 的主要定位。
- 已运行最小实践,并从响应与日志验证两个后端都收到流量。
- 能为入口服务列出安全、可靠性、可观测性和成本检查项。
官方延伸阅读¶
- RFC 9111:HTTP Caching
- RFC 7239:Forwarded HTTP Extension
- OWASP Web Security Testing Guide
- Nginx 官方文档
- Caddy 官方文档
- Apache HTTP Server 文档
- Apache Tomcat 文档
- Microsoft IIS 文档