网关和代理初学者指南

2025-05-26

网关和代理初学者指南

你听说过网关、代理(正向和反向)、负载均衡器、API 网关吗?你可能听说过,即使没听说过,也可能听说过nginxHAProxyEnvoyTraefikKongAmbassadorTyk等产品。你可能每次都在不知不觉中使用网关。

获取本文的PDF 和 epub 格式

网关通常位于客户端与后端应用程序或服务之间。网关的作用是代理或“协商”客户端与服务器之间的通信。想象一下,当你从正门走进一家商场时,你可能会遇到以下情况之一:

商场目录 - 照片由 Steven Damron 拍摄(https://www.flickr.com/photos/sadsnaps/)

商场指南。无论你去哪个商场,你都会遇到一份显示所有商店名称、位置以及可能还包含地图的指南。假设你想去乐高商店。你知道这家店在商场里,但不知道确切的地址(你为什么要知道?!)。幸运的是,只要知道商店名称,并利用指南找到它在商场内的具体位置就足够了。

现在,如果我们将商场视为一台服务器(或一组服务器),那么商场中的商店就是在该服务器上运行的服务或应用程序。在这种情况下,客户端就是您或您的计算机。如果商场中只有一家商店,那么问题就很简单了——只有一个地址,您知道该去哪里。

客户端与服务器对话

然而,我们都知道这并非现实。就像商场里有数百家商店一样,服务器上也可能运行着数百甚至更多的应用程序。我们可以说,客户端记住所有地址至少是不切实际的,更不用说服务器上只运行一个应用程序了。

客户端应该调用哪个服务?

回到之前的类比——你可以把商场目录想象成软件世界中的网关。这个网关知道所有应用程序的所在位置。它知道服务器上运行的每个应用程序的实际地址(例如 IP 地址或完全限定域名)。

就像您不需要知道商店的确切地址一样,通过网关发出请求的客户端也不需要知道。

API网关

如果您想进入乐高商店或调用应用程序,您发出请求,stores.example.com/lego网关就会知道将请求转发或代理到实际地址stores.example.com/stores/level3/suite1610

网关可以放置在客户端和应用程序之间,用于处理传入流量,因此得名入口网关。客户端无需向单独的应用程序发出请求,只需了解网关并向网关发出请求即可。

路由传入请求并通过公共端点公开 API 只是网关的部分职责。网关的其他典型任务包括速率限制、SSL 终止、负载均衡等等。

什么是速率限制?

速率限制器

可以把速率限制想象成一个漏斗,它只允许单位时间内一定数量的请求通过应用程序。如果我把商场的比喻延伸到黑色星期五——商店客满,所以你需要限制可以进入商店的人数。

速率限制器的作用非常类似——它限制一定时间段内可以发出的请求数量。例如,如果速率限制为每秒 10 个请求,则客户端每秒只能发出 10 个请求。如果客户端尝试每秒发出超过 10 个请求,我们就说该客户端受到了服务器的速率限制。在这种情况下,服务器的 HTTP 响应为429:请求过多

什么是 SSL 终止?

SSL 代表安全套接字层协议。SSL 终止(也称为 SSL 卸载)是解密加密流量的过程。SSL 终止与网关模式完美兼容。当加密流量到达网关时,它会在网关处被解密,然后传递到后端应用程序。在网关级别执行 SSL 终止还可以减轻服务器的负担,因为您只需在网关级别执行一次,而不是在每个应用程序中都执行一次。

您可以在每个应用程序或服务上实现这些,如下图所示。

每个应用程序的 SSL 和速率限制

然而,如果在每个应用程序级别执行 SSL 终止和速率限制,都会“耗费”时间和资源。网关可以帮助分担此功能,并在网关级别执行一次。

SSL 和网关速率限制

以下是一些可以在网关级别卸载和执行的功能的列表:

  • 验证
  • SSL 终止
  • 负载均衡
  • 速率限制
  • 熔断
  • 基于客户端的响应转换

什么是熔断?

熔断是一种有助于提高服务弹性的模式。它用于防止应用程序在发生故障后发出不必要的请求。您可以查看“什么是熔断?”一文了解更多详情。

网关方法也有其缺点。它需要您额外开发、维护或至少配置一个软件(如果使用现有的网关解决方案)。您还需要确保网关不会成为瓶颈——如果没有必要,不要试图将太多任务卸载到网关上。

出口网关

另一方面(或者说,从另一个角度来说),出口网关运行在你的私有网络内部,可以作为流量离开网络的出口点。例如,如果你的应用程序与外部 API(例如 Github)交互,任何对https://api.github.com的请求都会首先经过出口网关,然后出口网关可以代理对外部服务的调用。

出口网关

为什么要使用 Egress 网关?Egress 网关用于控制所有流出网络的流量。例如,如果您知道外部依赖项(例如 Github API),则可以阻止任何其他出站连接。如果您的服务受到威胁,阻止所有出站连接将阻止潜在攻击者进行进一步攻击。如果我们更进一步,您可以在专用计算机上运行 Egress 网关,在该计算机上您可以应用更严格的安全策略并单独监控该计算机。另一种常见情况是您的服务器无法访问外部 IP 或公共互联网。在这种情况下,您的 Egress 网关(可供您网络中的服务访问)将充当任何外部请求的出口点。

网关实践

让我们用一个简单的示例来结束本文,该示例展示了网关的一些基本功能。我将使用HAProxy,但其他代理也可以实现相同的功能。您可以从 GitHub 仓库获取源代码

我创建了一个名为 Square 的服务,它只对外暴露一个 API。该 API 从 URL 中获取一个参数(一个数字),并返回该数字的平方。该服务打包在一个 Docker 镜像中。要在你的机器上运行该服务,你需要下载并安装 Docker。你可以按照 Docker Desktop 的说明进行操作

安装 Docker Desktop 后,打开终端窗口,然后运行learnloudnative/square:0.1.0​​Docker 镜像。

docker run -p 8080:8080 learncloudnative/square:0.1.0
Enter fullscreen mode Exit fullscreen mode

首次运行上述命令时,可能需要一些时间,因为 Docker 需要下载(或者用 Docker 术语来说,是拉取)镜像。由于 Square 服务公开了一个 API,我们需要提供一个访问该 API 的端口号。因此,-p 8080:8080第一个参数8080表示我们希望在本地机器的 8080 端口上公开该服务,第二个参数8080表示该服务正在监听的端口号。

一旦图像下载完毕并且容器运行,您将看到如下消息:

$ docker run -p 8080:8080 learncloudnative/square:0.1.0
{"level":"info","msg":"Running on 8080","time":"2020-04-25T21:20:01Z"}
Enter fullscreen mode Exit fullscreen mode

让我们尝试向该服务发送一个请求。打开第二个终端窗口,以便保持服务运行,然后运行以下命令:

$ curl localhost:8080/square/25
625
Enter fullscreen mode Exit fullscreen mode

服务响应结果(625),您会在前一个终端窗口中注意到该请求也已被记录:

$ docker run -p 8080:8080 learncloudnative/square:0.1.0
{"level":"info","msg":"Running on 8080","time":"2020-04-25T21:20:01Z"}
{"level":"info","msg":"GET | /square/25 | curl/7.64.1 | 6.781µs","time":"2020-04-25T21:22:50Z"}
Enter fullscreen mode Exit fullscreen mode

您可以按 CTRL+C 来停止运行容器。

添加代理

为了简化运行,我将使用 Docker Compose 来同时运行 Square 服务和 HAProxy 实例。如果您不熟悉 Docker Compose,也不用担心,它只是一种同时运行多个 Docker 容器的方法。

docker-compose.yaml文件定义了两个服务 -haproxysquare-service。该docker-compose.yaml文件如下所示:

version: '3'
services:
  haproxy:
    image: haproxy:1.7
    volumes:
      - ./:/usr/local/etc/haproxy:ro
    ports:
      - '5000:80'
    links:
      - square-service

  square-service:
    image: learncloudnative/square:0.1.0
Enter fullscreen mode Exit fullscreen mode

除了 Compose 文件之外,我们还需要一个配置文件来配置 HAProxy 的功能。记住,我们不会square-service直接调用 ,而是将请求发送给代理,然后代理会将我们的请求传递给square-service

HAProxy 使用haproxy.cfg具有以下内容的文件进行配置:

global
  maxconn 4096
  daemon

defaults
    log global
    mode http

    timeout connect 10s
    timeout client 30s
    timeout server 30s

frontend api_gateway
    bind 0.0.0.0:80
    default_backend be_square

# Backend is called `be_square`
backend be_square
    # There's only one instance of the server and it
    # points to the `square-service:8080` (name is from the docker-compose)
    server s1 square-service:8080
Enter fullscreen mode Exit fullscreen mode

我们感兴趣的是两个部分 -frontendbackend。我们称之为前端部分api_gateway,在这里我们将代理绑定到地址和端口,以及路由传入流量的位置。我们只是将 设置default_backendbe_square在前端部分之后定义的后端。

在后端部分,我们创建一个名为s1端点的单个服务器square-service:8080- 这是我们为文件中的方形服务定义的名称docker-compose.yaml

docker-compose.yaml让我们使用 Docker Compose 来运行这两个服务。请确保从你的和文件所在的文件夹运行以下命令haproxy.cfg

$ docker-compose up
Starting gateway_square-service_1 ... done
Starting gateway_haproxy_1 ... done
Attaching to gateway_square-service_1, gateway_haproxy_1
square-service_1 | {"level":"info","msg":"Running on 8080","time":"2020-04-25T21:41:12Z"}
haproxy_1 | <7>haproxy-systemd-wrapper: executing /usr/local/sbin/haproxy -p /run/haproxy.pid -db -f /usr/local/etc/haproxy/haproxy.cfg -Ds
Enter fullscreen mode Exit fullscreen mode

Docker Compose 完成了它的工作,创建了一个新网络和两个服务。在第二个终端中,我们再次运行 curl 命令:

$ curl localhost:5000/square/25
625
Enter fullscreen mode Exit fullscreen mode

请注意,这次我们使用的端口号5000是 HAProxy 暴露的端口号(请查看docker-compose.yaml文件中的 ports 部分)。和之前一样,您会收到来自 Square 服务的响应。不同之处在于,这次请求首先经过了代理。

您可以再次按 CTRL+C 来停止运行 Docker Compose。

在 HAProxy 上启用统计信息

由于每个请求都经过代理,它可以收集有关请求、前端和后端服务器的统计信息。

让我们通过在文件末尾添加以下行来启用 HAProxy 中的统计信息haproxy.cfg

listen stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 5s
Enter fullscreen mode Exit fullscreen mode

以上代码启用了 port8404和 URI的统计信息/stats。由于我们想从代理访问统计信息,因此还需要在 中公开它。在文件中的 keydocker-compose.yaml添加以下行"8404:8404"portsdocker-compose.yaml

ports:
  - "5000:80"
  - "8404:8404"
Enter fullscreen mode Exit fullscreen mode

如果 docker-compose 正在运行,请停止它(按 CTRL+C),然后运行docker-compose down以删除服务,然后docker-compose up重新启动它们。

容器启动后,您可以http://localhost:8404/stats在浏览器中打开。运行以下命令发出几个请求curl localhost:5000/square/25以生成一些数据。您将在 HAProxy 的统计报告中看到会话数。

HAProxy 统计数据

启用健康检查

HAProxy 还支持健康检查。您可以配置 HAProxy 定期向后端服务发送 TCP 请求,以确保它们处于“活动”状态。要启用健康检查,您可以在文件check中定义服务器后端的同一行添加haproxy.cfg。如下所示:

server s1 square-service:8080 check
Enter fullscreen mode Exit fullscreen mode

更新配置文件后,停止 Docker 组合(CTRL+C),然后docker-compose up再次运行以重新启动容器。

如果您打开或刷新统计信息页面http://localhost:8404/stats,您会注意到表格中的相应行be_square现在显示为绿色,这表示代理正在执行健康检查,并且服务健康。在报告图例中,您将看到active UP已使用量。此外,该LastChk列将显示健康检查的结果。

拒绝请求

假设我们想要保护我们超酷的 Square 服务,并要求用户在发出请求时提供 API 密钥。如果他们没有 API 密钥,我们就不允许他们调用该服务。

使用 HAProxy 实现此目的的一种方法是将其配置为拒绝所有未设置 API 标头的请求。为此,您可以bind在文件 frontend 部分中的命令后立即添加以下行haproxy.cfg

http-request deny unless { req.hdr(api-key) -m found }
Enter fullscreen mode Exit fullscreen mode

这行代码告诉代理拒绝所有请求,除非存在名为api-keyset 的标头。让我们重启容器(CTRL+C 和docker-compose up)看看它是如何工作的。

如果您发出的请求没有api-key设置标头,您将收到 403 响应,如下所示:

$ curl localhost:5000/square/25
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>
Enter fullscreen mode Exit fullscreen mode

但是,如果您包含一个api-key标头,代理就会让请求通过,然后您就会从服务中获得响应,就像以前一样:

$ curl -H "api-key: hello" localhost:5000/square/25
625
Enter fullscreen mode Exit fullscreen mode

限速请求

最后,我们还要实现一个速率限制器,这样单个用户就不会发出太多请求并给服务造成不必要的压力。

我们需要在haproxy.cfg文件中定义一些内容。我会先分别解释它们,然后再把它们放在一起。

存储/统计请求

为了使速率限制器正常工作,我们需要一种方法来统计并存储已发出的请求数量。我们将使用 HAProxy 的内存存储系统stick table。使用 stick table,您可以存储请求数量,并在一定时间(在本例中为 5 分钟)后自动使其过期(删除):

stick-table type string size 1m expire 5m store http_req_cnt
Enter fullscreen mode Exit fullscreen mode

设置请求限制

我们还需要设置一个限制。这个限制是一个数字,达到这个数字后,我们将开始拒绝(或限制请求速率)。我们将使用访问控制列表 (ACL) 来测试某个条件(例如,请求数量是否大于 X),并根据该条件执行操作(例如,拒绝请求):

acl exceeds_limit req.hdr(api-key),table_http_req_cnt(api_gateway) gt 10
Enter fullscreen mode Exit fullscreen mode

上面这行代码检查具有特定值的请求数量是否api-key大于 10。如果没有超过限制,我们将跟踪该请求并允许其继续:

http-request track-sc0 req.hdr(api-key) unless exceeds_limit
Enter fullscreen mode Exit fullscreen mode

否则,如果超出限制,我们将拒绝请求:

http-request deny deny_status 429 if exceeds_limit
Enter fullscreen mode Exit fullscreen mode

所有这些更改都需要在文件frontend api_gateway的 部分进行haproxy.cfg。如下所示:

frontend api_gateway
    bind 0.0.0.0:80

    # Deny the request unless the api-key header is present
    http-request deny unless { req.hdr(api-key) -m found }

    # Create a stick table to track request counts
    # The values in the table expire in 5m
    stick-table type string size 1m expire 5m store http_req_cnt

    # Create an ACL that checks if we exceeded the value of 10 requests
    acl exceeds_limit req.hdr(api-key),table_http_req_cnt(api_gateway) gt 10

    # Track the value of the `api-key` header unless the limit was exceeded
    http-request track-sc0 req.hdr(api-key) unless exceeds_limit

    # Deny the request with 429 if limit was exceeded
    http-request deny deny_status 429 if exceeds_limit

    default_backend be_square
    ....
Enter fullscreen mode Exit fullscreen mode

是时候尝试一下了!重启容器,并向服务发出 10 个请求。在第 11 个请求时,你将收到429 Too Many Requests 的响应,如下所示:

$ curl -H "api-key: hello" localhost:5000/square/25
<html><body><h1>429 Too Many Requests</h1>
You have sent too many requests in a given amount of time.
</body></html>
Enter fullscreen mode Exit fullscreen mode

您可以等待 5 分钟让速率限制器信息过期,或者尝试使用不同的限制器api-key,您会注意到请求将通过:

$ curl -H "api-key: hello-1" localhost:5000/square/25
625
Enter fullscreen mode Exit fullscreen mode

最后,您可以再次检查统计信息页面,特别是表格中的“Deniedapi_gateway ”列。“Denied ”列将显示被拒绝的请求数量。

结论

在本文中,我解释了什么是网关或代理,并展示了几个实际示例,说明如何使用网关来实现速率限制或拒绝请求。

文章来源:https://dev.to/peterj/beginners-guide-to-gateways-and-proxies-1npn
PREV
理解 CSS 定位的轻松指南
NEXT
六个月的远程工作教会了我十件事