1 - 全局配置

介绍 FGW 的全局配置,控制 FGW 的核心行为和处理流程

介绍

全局配置项是用来管理和调整 FGW 的核心行为和处理流程,这些配置能在 FGW 的各个功能和模块中被应用,支持如日志管理、网络超时设置、请求处理和健康检查等各种全局行为的定制化设置。

配置说明

下面是对全局配置的详细说明:

  • EnableDebug:激活或禁用调试日志输出。当此选项设置为 true 时,系统将记录详细的调试信息,这对于开发和故障排查非常有价值。需要注意的是,持续的日志记录可能会对系统性能产生影响。

  • StripAnyHostPort:控制是否剥离 HTTP 请求头中 Host 字段的端口信息。当此项为 true 时,例如,Host: www.aaa.com:8080 会被视作 Host: www.aaa.com,这有助于简化主机名的处理和路由。

  • DefaultPassthroughUpstreamPort:在 TLS 透传 模式中,默认使用此端口号来转发流量,尤其是在上游服务器端口未被明确指定的情况下。它有助于确保在缺乏详细配置信息的情况下,流量能够被正确转发到目标。

  • SocketTimeout:定义网络 socket 操作的超时时长,单位为 ,默认值是 60。这是系统等待网络响应的最大时间阈值,通过这一设置可以防止网络问题导致的资源挂起。

  • PidFile:指定一个文件路径,系统将在此文件中写入 Pipy 进程的 ID。这在进行进程管理和监视时可能会非常有用。

  • ResourceUsage:控制是否收集及如何收集 Pipy 进程的资源使用数据,如 CPU 使用率和内存消耗。这些数据可用于监视系统的健康状态和进行性能优化。

    • ScrapeInterval: 定义了 CPU 和内存使用率的数据采集时间间隔,单位是秒。设置这个值可以帮助系统以一定的频率检查资源使用情况,从而更有效地监视资源利用率。
    • StorageAddress: 指定存储 CPU 和内存使用率数据的 REST URL。在此 URL 定义的位置,采集到的数据将被存储和进一步处理或分析。如果这个字段没有被配置,数据将不被存储。
    • Authorization: 包含访问上述 REST URL 所需的 Basic 认证信息。这在目标存储位置要求身份验证时是必要的,以确保数据的安全传输和存储。如果没有启用认证,此字段可以省略。
  • HealthCheckLog:确定如何记录和存储系统的健康检查日志。该设置指定了存储这些日志的服务器或存储解决方案的详细信息。

    • StorageAddress: 用于指定一个 REST URL,用于存储健康检查日志信息。通过将日志信息发送到此 URL,可以方便的对健康检查的结果进行保存和进一步分析。若此配置项未设置,健康检查日志将不被外部存储。
    • Authorization: 提供访问上述 REST URL所需的 Basic 认证信息。此配置确保健康检查日志数据的安全传输和存储。在没有启用目标URL的认证机制的情况下,此配置项可省略。
  • Gzip:包含控制如何压缩静态文本文件的参数。这些设置影响如何对静态内容应用 Gzip 压缩,有助于减少传输的数据量和提高响应速度。详细说明,请参考 gzip 压缩

  • ProxyRedirect:定义了如何重写上游服务器在 HTTP 响应(例如 Location 或 Refresh 头)中的 URL。这允许在必要时修改重定向 URL,以确保它们满足下游客户端的需求。详细说明,请参考重定向响应控制

  • ErrorPage:允许为特定的 HTTP 错误代码定义定制的错误页面或链接。当 FGW 检测到定义的错误代码时,它将返回与之关联的自定义页面或链接,以提供更丰富或友好的错误信息。详细说明,请参考自定义错误页面

示例

{
  "Configs": {
    "DefaultPassthroughUpstreamPort": 8443,
    "EnableDebug": true,
    "StripAnyHostPort": true,
    "SocketTimeout": 30,
    "PidFile": "/var/log/pipy.pid",
    "ResourceUsage": {
      "ScrapeInterval": 60
    }
  }
}

功能

gzip 压缩

在 Web 开发中,利用 gzip 压缩可以减小传输的数据量,从而提高页面加载速度、减轻服务器的负担、并在一定程度上减少网络带宽的消耗。通过下面的字段,可以对 gzip 压缩进行精细化控制。

  • DisableGzip: 用于启用或禁用静态文本文件的 Gzip 压缩功能。若设置为 true,则不进行 Gzip 压缩;默认值为 false,表示启用 Gzip 压缩。
  • GzipMinLength: 定义文本文件压缩的最小字节数。仅当文本文件的大小达到或超过此值时,文件会被压缩;默认值为 1024
  • GzipHttpVersion: 设定启用文本文件压缩的最低 HTTP 版本。只有满足或超过此 HTTP 版本的请求才会收到 Gzip 压缩的响应。
  • GzipTypes: 指定了一组 content-type,仅对这些类型的文本文件进行 Gzip 压缩,以确保仅对可安全压缩的文件类型执行压缩操作。
  • GzipDisable: 提供一个针对 user-agent 的禁用压缩设置。通过识别请求头中的 user-agent 信息,当其符合此处设置的模式时,Gzip 压缩将不会被执行。
{
  "Configs": {
    "Gzip": {
      "GzipMinLength": 1024,
      "GzipTypes": [
        "text/css",
        "text/xml",
        "text/html",
        "text/plain",
        "application/xhtml+xml",
        "application/javascript"
      ]
    }
  }
}

重定向响应控制

该功能用于修改代理服务器传回的 HTTP 响应中的 LocationRefresh 头字段中的文本。这主要是用在 FGW 作为反向代理时,以确保重定向 URL 正确指向通过 Nginx 代理访问的地址,而不是直接指向后端服务器的地址。

比如下面的配置中,会对响应中的 LocationRefresh 头字段中内容的路径进行替换。

{
  "Configs": {
    "ProxyRedirect": {
      "http://www.flomesh.com/aa": "http://$http_host/ab",
      "http://www.flomesh.com/a0": "/a1"
    }
  }
}

自定义错误页面

这个功能用于定义当服务器返回指定的错误代码时应显示的页面。这是一种定制错误页面的方法,以便在出现问题时向用户显示更友好或更有用的信息。

  • Error: 定义了一组要被自定义错误页面覆盖的 HTTP 错误码。例如,[404, 500] 表示对这两种 HTTP 错误提供自定义页面响应。
  • Page: 指定用于展示的自定义错误页面的名称或者 URL 地址。如果提供了完整的 URL 地址,将直接重定向至该 URL;若提供文件名,则需要结合Directory参数来找到文件的实际路径。
  • Directory: 指定自定义错误页面文件的存储目录。仅当Page参数是一个文件名而非完整 URL 时适用。此路径将与Page参数一起用来检索自定义错误页面的完整文件路径。如果Page是一个完整的 URL,此参数将被忽略。
{
  "Configs": {
    "ErrorPage": [
      {
        "Error": [
          404
        ],
        "Page": "404.html",
        "Directory": "pages/"
      },
      {
        "Error": [
          502
        ],
        "Page": "502.html",
        "Directory": "pages/"
      }
    ]
  }
}

2 - TCP 负载均衡

本文档将介绍如何配置 FGW 代理和负载均衡 TCP 流量。

介绍

在 L4 负载均衡过程中,FGW 主要基于网络层和传输层信息,如 IP 地址和端口号,来决定将流量分配到哪个后端服务器。这种处理方式使得 FGW 可以快速地进行决策并将流量转发给适当的服务器,从而提高了整体的网络性能。

如果要负载均衡 HTTP 流量,请参考文档 L7 负载均衡

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • 两个后端服务

配置

  1. 要使用 L4 负载均衡,首先我们需要将监听端口的协议设置为 TCP,参考文档监听端口配置
{
  "Listeners": [
    {
      "Protocol": "TCP",
      "Port": 8080
    }
  ]
}
  1. 接下来是设置端口 8080 的路由规则,参考文档 TCP 协议端口号路由规则配置
{
  "RouteRules": {
    "8080": {"backendService1":100}
  }
}
  1. 配置服务端点,参考文档服务配置
{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  }
}
  1. 不要忘记插件链的配置,本文档主要介绍 TCP 流量的负载均衡,这里只需要引入 TCPRoute 插件链和 tcp/forward.js 插件即可。完整的插件配置可以参考文档完整的插件配置
"Chains": {
    "TCPRoute": [
      "tcp/forward.js"
    ]
  }
  1. 最后完整的配置如下,使用其替换 FGW 工程中的 pjs/config.json
{
  "Listeners": [
    {
      "Protocol": "TCP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "backendService1": {
        "Weight": 100
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "TCPRoute": [
      "tcp/forward.js"
    ]
  }
}

多次访问负载均衡器的 8080 端口,可以看到轮流返回两个后端服务的响应。

3 - HTTP/HTTPS 负载均衡

L7 负载均衡功能可以对更高级别的网络协议(如 HTTP、HTTPS)进行有效的负载均衡。本文档将介绍如何使用 FGW 的 L7 负载均衡功能。

介绍

负载均衡是确保应用程序的可用性和弹性的关键组件。它能够分发进入的网络和应用程序流量到后端服务器,这确保了用户请求能够得到快速处理,同时避免了单一服务器的过载。HTTP/HTTPS 负载均衡特指对基于 HTTP 和 HTTPS 协议的流量进行分发。有如下常见的使用场景:

  • 保障高流量应用程序的稳定性和响应速度。
  • 分发流量到多个服务器以提高性能和容错能力。
  • 提供 SSL/TLS 终结,从而集中管理证书和解密流量。

FGW 的 L7 负载均衡功能可以基于网络请求的内容,如路径、头信息、方法等进行流量分配,提供精细化的负载均衡机制。

如果要负载均衡 TCP 流量,请参考文档 L4 负载均衡

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • 两个后端服务

HTTP 配置

配置监听端口

要使用 L7 负载均衡,首先我们需要将监听端口的协议设置为 HTTP,参考文档 监听端口配置

  • Port:应用使用的端口号。这是设置服务将接收请求的端口。有效范围是 1~65535,并且是必须配置的。
  • Listen:监听端口号。这不是必须的配置项。如果未配置,FGW 将使用 Port 字段值作为监听端口号。有效范围同样是 1~65535。
  • Protocol:端口所使用的协议。这定义了服务将如何处理入站请求。可选值包括 HTTPHTTPSTLS、和 TCP,这是一个必须配置的字段。
  • AccessControlLists:访问控制列表。此字段用于设置访问者 IP 地址的黑白名单。若设置了白名单,则只允许白名单中的 IP 访问;若未设置白名单,但设置了黑名单,则除黑名单外的所有 IP 都可访问。具体的设置方法和格式可以参考 访问控制规则文档
  • bpsLimit:网络限速。该字段用于限制服务的网络带宽使用,单位为字节/秒。例如,如果设置了 "bpsLimit": 10000000,那么网络的最大吞吐量将被限制为 10MB/s。
  • TLS:配置 TLS 相关的证书信息。此字段用于设置和管理端到端的加密连接,确保数据在传输过程中的安全性。具体的设置方法和格式可以参考 TLS 配置文档
{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ]
}

配置端口的路由规则

接下来是设置端口 8080 的路由规则,参考文档 HTTP 协议端口号路由规则配置。端口的路由规则是一个键值对,键为域名,值为路由规则。这里我们使用 * 来匹配所有的域名,也就是说忽略域名。路由规则的字段:

  • RouteType:路由规则,可选 HTTP 或者 GRPC
  • Matches:匹配规则,可根据请求路径、头部、参数或者方法等信息将请求匹配到响应的后端。
    • Path:HTTP uri path 匹配。当请求的 URI 路径与此字段匹配时,将根据其他匹配条件确定是否路由到指定的后端服务或静态页面目录。这不是一个必须字段,但常用于基于 URL 的路由策略。具体匹配规则请参考 Path 规则配置
    • Headers:HTTP header 匹配。您可以基于请求头中的特定字段进行路由。例如,可以根据 User-Agent 头信息来路由移动设备的请求到特定的后端服务。具体匹配规则请参考 头部规则配置
    • Methods:允许的 HTTP 方法。这允许你基于请求的 HTTP 方法(例如 GETPOSTDELETEPUT)来进行路由选择。当请求方法与此字段中的任何方法匹配时,将考虑其他匹配条件。
    • QueryParams:HTTP 请求参数匹配。可以根据 URL 查询参数中的特定键值对进行路由,这提供了更细粒度的匹配能力。具体匹配规则请参考 请求参数规则配置
    • BackendService:后端服务。这定义了将请求路由到哪个后端服务。值为一个键值对,其中键是服务名,值是权重。与 ServerRoot 相比,BackendServiceServerRoot 必须至少有一个存在。
    • ServerRoot:静态页面所对应的目录。如果请求匹配并且需要路由到静态内容,则使用此字段指定的目录作为静态内容的根目录。与 BackendService 相比,BackendServiceServerRoot 必须至少有一个存在。
    • Method:当 RouteTypeGRPC 时,用于匹配服务的字段。这允许根据特定的 gRPC 方法进行路由选择。具体匹配规则请参考 GRPC 服务规则配置
    • RateLimit:路由限流配置。可以对匹配的路由进行限流,以防止某些请求因频繁访问而影响系统性能。具体限流规则请参考 限流使用文档
{
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  }
}

配置服务端点

参考文档 服务配置

  • StickyCookieName:使用 cookie sticky 负载均衡时的 cookie 名称。该字段用于在 sticky 负载均衡策略中标识客户端,以确保客户端请求始终被路由到同一上游服务。使用方法可参考文档 会话保持
  • StickyCookieExpires:使用 cookie sticky 时的 cookie 有效期。该字段定义了 cookie 的生命周期,以秒为单位。例如,如果设置为 3600,那么 cookie 的有效期为 1 小时。
  • HealthCheck:对上游服务的健康检查配置。健康检查用于定期检查上游服务的健康状态,确保请求只被路由到健康的服务实例。具体的使用方法,可参考文档 健康检查
  • Endpoints:上游服务信息。这是必须配置的字段,它定义了 FGW 将请求路由到哪些上游服务。具体的配置方法和格式可以参考 端点配置文档
  • Filters:过滤器配置。过滤器用于在请求被路由到上游服务之前或之后执行特定的操作,如修改请求或响应、记录日志等。具体的配置方法和格式可以参考 过滤器配置文档
  • CircuitBreaking:熔断配置。熔断器用于在上游服务出现问题时防止请求流向该服务,以此来保护服务和提高系统的可用性。此配置仅适用于 HTTPHTTPS 协议的场景。具体的使用方法可参考文档 熔断
  • RetryPolicy:重试配置。此字段定义了当请求失败时的重试策略,如重试次数、重试间隔等。此配置同样只适用于 HTTPHTTPS 协议的场景。具体的使用方法可参考文档 重试
  • UpstreamCert:访问上游时默认使用的 TLS 证书。当 FGW 与上游服务之间的通信需要加密时,此字段用于提供 TLS 证书的配置。具体的使用方法,可以参考文档 TLS 上游
{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  }
}

配置插件链

不要忘记插件链的配置,本文档主要介绍 HTTP 流量的负载均衡,这里只需要引入 HTTPRoute 插件链和其中的部分插件即可。完整的插件配置可以参考文档 完整的插件配置,这里我们使用的是 HTTP 负载均衡的最小插件集合。

{
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

完整配置

最后完整的配置如下,使用其替换 FGW 工程中的 pjs/config.json

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

使用地址 http://localhost:8080 多次访问负载均衡器的 8080 端口,可以看到轮流返回两个后端服务的响应。

HTTPS 配置

  1. 要让负载均衡器支持 HTTPS 配置,我们需要先签发 CA 证书和服务器证书。
openssl genrsa 2048 > ca-key.pem

openssl req -new -x509 -nodes -days 365000 \
   -key ca-key.pem \
   -out ca-cert.pem \
   -subj '/CN=flomesh.io'

openssl genrsa -out server-key.pem 2048
openssl req -new -key server-key.pem -out server.csr -subj '/CN=example.com'
openssl x509 -req -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365

执行上面的命令,我们会得到如下几个文件。

ca-cert.pem
ca-cert.srl
ca-key.pem
server-cert.pem
server-key.pem
server.csr

可以使用命令 awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' ca-cert.pem 来输出单行。

  1. 接下来在前面 HTTP 配置 的基础上,先添加一个新的端口 8443,协议类型为 HTTPS
{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    },
    {
      "Protocol": "HTTPS",
      "Port": 8443,
      "TLS": {}
    }
  ]
}
  1. TLS 证书的配置,可以参考文档 TLS 配置,将上面生成的 server-cert.pemserver-key.pemca-cert.pem 分别配置在 CertChainPrivateKeyIssuingCA 字段。
{
  "TLS": {
    "TLSModeType": "Terminate",
    "mTLS": false,
    "Certificates": {
      "CertChain": "-----BEGIN CERTIFICATE-----\nMIICpzCCAY8CCQDlrcc1+3rp8zANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMB4XDTIzMDgwNzA3NDU0MVoXDTI0MDgwNjA3NDU0MVowFjEUMBIG\nA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQC5OKtPgkgiUlzRWgFFyuNrm+oD5pWHaEyHQyfM/GkW3wJvqnQGPOC7SZr2du8a\nQGoC8qFJnkNkqcXwpCe9RrLAyndsny/U75TQQMTf9MojE46/lhf6YtMzip+hWnEy\ny/azYfaLsnbFu33ZHWaEIeOz8kNpK/qP5CNeMrjH9Ph3RLj0a3gXQcyvmVf9HX1A\nP2HuZsbf5q9X0cVqxcn6Irtw4F2CRU/NUcRMIofk+OgxDYc6Y+XBZfGt54C1x1lM\nxkHnigtIyItw7Ps+MgfczSK175b6xY/eM47Nnl7l7VH6PqMGBEi/NQVDO0toGSgk\nxtFZm2RSLJOO+iMzbjCurmkjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD9PVwvV\nJBDqfv1MSs13t309su/so+MNdnYqpIx8zsCUEu9uLVEZYwmK+KLxZJ33MDR5VuZk\ncXiNpIJqnhndMf/Ep/E/LiDvRvygNcXUhrTvTJOwo5rJtt9mxmCtGV/S9VIOXZ9d\nBnvVfb2RgnSNmw6odxH+C56Dxu2HX+byhDqeR03JONfJ8414uw0jOWNNb590haQn\n8AE+BrFeWOscv7/8hRf5oiWICfbPt+0Y+d9Ed8qyWD6lWamdWGraY4c+VfVYUve+\nXapZWJNWou2wsFwn3e0yZG7PyRBo3k2J7IocEzUN/zI96okdtJ+2smhgw68/bYLj\nKWFfharluQhLAtI=\n-----END CERTIFICATE-----\n",
      "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAwVHWlokixQtr2cM3OFeqJ1Y2YXMwTEP8tKxPD47IzUJqB17tMOsy6fssDEOaTAz05eEdlFRkQHveVazouxG6goLFzooRWmyiGL+sMq6z2ZsfjOP3gnCQbLW37uRJJGSK1IIyvaUsbiI29Ltx/WJxJm3mYFhRMoM/XZ5RYGwUjkYjjIrLs8/cUHuTbVK4W8vPYOJ7cxoLhozfqv8wGPa9fEPtsn0KGLm2gpPgwpKH2wh35VZ0IHkcIVlj4siKwpNeH68VVFLO2wfaMHD0UGgPJcZEigLSf6SI2PY3j8JEmpungb+e6XbUS7mrNjj7jne0+Kjh1jCa8acBSeWcQDujFwIDAQABAoIBAHyhkTGtqZ/VNCvJAjGtusHvf9GlnG6eqi2kpLfH+sbx2T91QH94MnPMfWJOtwvukngdgJ9fJN65vOYJOmVYEaEQRAxa0MM2I+7Gq3JlVQemTVnconYSsdmT8cfunwT6WNKWObYv5Yv/POTcb6nGrGH1Gj/k0Dw7hz+I0LfUFhB+Io0DV4T9UBzXDtKkbp1s9jM94GxxsEjePJAsjBDNbpTcEvVJusgUVgwyA0kmGhc0+DwpnUJH3r1sIxLWeVhB+lmPAACCngphdHkWpGsdjPe6aE68Lb24jNv3U195nwkGgTLv4TX+fhgq2IpAz1jrz8qzumFZAm7UQ7LFUoBMCWECgYEA4isHTF56qGMemU8WUPvIyP00U2tIv9/7eRKUiiVS3izfBdasOWiGY+k1fQzrJXI77CjPaWhlQrwgdr0sDqk2a+5zzMIuNikB05gG2MoMs3pBpggHu306gC3axokwwWlJn4VwFO9GwsBhbRP9YM1Oq1P/Vi6v+oxvvbfaT1/GLkcCgYEA2tGf3+mIF41Fo8K2eTa0NJBiIsHacLK/4IAjLKwORcdVr7/211q4ECWB8m9g/sdl86LHDY4pD82/VjqSeu5qK1NgzbusgNEY6BW6AssYw0cWXm7CNf1m0CDjNKLzs7WdtQRCeY1SKYKEXceIelJ6pHRO3yWykP6Kmkl+Qz07PLECgYEAsUR2gPYgf3DJH/KsFCd09Yv4glW5fKKq8PeOM0UT0Y4r8+CRtqFljFPSl8QTXpNNwkkuYHjxvT/E1ixppsgcHraUTu332H2Fr/odi7e6AsaVQ/RRUzPRMXw/WJNZAo9qpDyrX803khfFhQBA/amNup2oqT0Is4F1Z6b91m7D36sCgYAp0AKXu70omvMirrNFiEF5BdnqwFYoUM+/a1zNTXdQuB1Ufv8A+bHQTAp/s+654IpHuuQEYBTSk0Mri/evi903uC/4QBNfbhUvS++GVx69Odk5ZDqyLGC4BoDD7xtYTKz9CPpW1b1Md0cp0FXw4c/TmvHzS/XKJQmBH+gDmzC1kQKBgD/9KpDsXs19pkuj3MmIAfWpCEiuQHd9TwrAf0cvH6cfeI1nmRBv49nhSxrBmVH5/cKWqE1xlN7dC+E1kH7qZUDrwbVe1p1m+YYbGShKv8olYlmbT5Z5zpTDnKkotjpkx9TuXC3bqrmvniREAm7W3FyXZn9x1Vc2gxu4tqni8U4J-----END RSA PRIVATE KEY-----\n",
      "IssuingCA": "-----BEGIN CERTIFICATE-----\nMIICqDCCAZACCQClfWE0dDDQ/DANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMCAXDTIzMDgwNzA3NDU0MVoYDzMwMjIxMjA4MDc0NTQxWjAVMRMw\nEQYDVQQDDApmbG9tZXNoLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA1tmBTZgxEb8NdlavWK+//KqdTdCrpwD841MXgjm2/tlhkuS1wNdqFfNwJtMv\nSdHmHI8V5LcqY56WZKgrlTTMlmROgWqV4+LjsrQNfnW2D/exbvtGPb9yjGIXsWVx\nGR0jddeAUlwC1o48A2u9st2pciFnzLYMd5MYQk3USmMoO/+pJgHEIn1wOC27de6Q\nU0x2SVZoja+ov6xrKOZBIChwhXzY1KvS73XRfZ1gTqMHde1By9EoByPnaMz3wN/9\nM3tSosW1q1KdN/TXkXa0MbyhUhKIjs+1q0YZJVknUBmNV2219yyteNLmlGaGqCL4\nf8R9/QE5DmXpaplXR03oPQL1/wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA1+wx1\npg+nZZAnbkdV3Lqe+FnWf3TegnkrTVhytxPLajRgtQ6iY+S2KYwPwXlPp6bpwbMd\nxmHy9wrpvozPdacgC+ZwqjoqiWmwk5RuimAbVL/GkNNC5HaWuWlyeA6V2sUr5poj\nDrlxfU2C0Yk80krGOK4+MUcwDlAqQq62/8xv7Ofdo/4uN3oB6WPG76xkevAgfNu9\n6saKaXZRLBAIXmvEidY6vKsnwjAhcidDPpfeJhgFOGGtnD+uTld5X5cOqzPR06Wa\n+mLu5uaEhpSbCngxRRiEnKOMScz7oyX8dXc3ZIkHLrsKN4hNdqe1h/vr1qDwFf9f\n/+ECh0PElzLMepoK\n-----END CERTIFICATE-----\n"
    }
  }
}
  1. 接下来为 HTTPS 端口 8443 配置路由规则,可以在原来的键 8080 上添加 8443(使用逗号 , 间隔),复用 HTTP 端口的路由配置。
{
  "RouteRules": {
    "8080,8443": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  }
}
  1. HTTPS 场景下,多了 TLS 卸载的逻辑。需要为 HTTPS 增加一个新的 HTTPSChain,跟 前面 HTTPChain 的唯一区别就是引入了 common/tls-termination.js 插件。
{
  "Chains": {
    "HTTPSRoute": [
      "common/tls-termination.js",
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}
  1. 最终完整的配置如下:
{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    },
    {
      "Protocol": "HTTPS",
      "Port": 8443,
      "TLS": {
        "TLSModeType": "Terminate",
        "mTLS": false,
        "Certificates": [
          {
            "CertChain": "-----BEGIN CERTIFICATE-----\nMIICpzCCAY8CCQDlrcc1+3rp9DANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMB4XDTIzMDgwNzA4NTA0NVoXDTI0MDgwNjA4NTA0NVowFjEUMBIG\nA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQDWjG2AJ99p4dmG2yczeG76r4veV+6/nSYIA/UcPIA/VPE24/74/qCfz0+bGbQI\no2MEGZon71NluU2L85kkuJmfZLhfZRxOoCbjzP+8BdVth/hm/OHXf4Lf8EPQJbdy\n5Gf9XWuFYpEZllN6LBQzrdAjMutlAGIXf3d1QcJbFM9cofOcA745Bm5+7M7c9cPT\nyMROwjdymBhm3IYMnF4JkIoTvsqsSYf0DyIpsRtMumuikNRAx0JYu3OIiDNt8tJT\nT79zDcFcXxCiyndNHNRN8LmwxhWBMdYB+asfaiuJJ9C5gvRCIIcH54cyUE8WZmcm\nsA4oVqnq0QO6CGAAD8F+p+QfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADq4pUQE\nVbgjmBh/bQqQ8jshpTQDK+3lbFKoLYsBxCHokoAQ7JiyqTQfyGA8AYVm2ero9X5i\n44ezy14kTXmYD5k8vN2aTTySnIgbHtCUafhCFckn6jvszVPxqUzFsly1bbOf7PZE\nKQk9flqJA7+9qhVlw8c1/TPbyuXWS2SVTXp01mCA+3LjQHaQNMjm0RpZQZJVgPwM\nt8vGpLbXUbd5Ptn3i7aXgIIHp5Lub68hyrwEvDLTEeHnWz9KPRLS+xI/uGDzdR67\nIqneMn9Skx8sNE8hKEI0Im19+BBdWnOJZNQGe+s9EaAwbBrqaYNU+YyljeSF0tNZ\nUlevnb0vrsHIfho=\n-----END CERTIFICATE-----\n",
            "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA1oxtgCffaeHZhtsnM3hu+q+L3lfuv50mCAP1HDyAP1TxNuP+\n+P6gn89Pmxm0CKNjBBmaJ+9TZblNi/OZJLiZn2S4X2UcTqAm48z/vAXVbYf4Zvzh\n13+C3/BD0CW3cuRn/V1rhWKRGZZTeiwUM63QIzLrZQBiF393dUHCWxTPXKHznAO+\nOQZufuzO3PXD08jETsI3cpgYZtyGDJxeCZCKE77KrEmH9A8iKbEbTLpropDUQMdC\nWLtziIgzbfLSU0+/cw3BXF8Qosp3TRzUTfC5sMYVgTHWAfmrH2oriSfQuYL0QiCH\nB+eHMlBPFmZnJrAOKFap6tEDughgAA/BfqfkHwIDAQABAoIBAQCtCpH+vSoKgigq\nBnP1pXsNIa0T5aQgU6Uq7dYxsfJWIjJy7SzmsqfmfRRdqjt0hCMGWYfmEbcX4n7T\nE+Q+o8zzrA6wkiJkn/L95IeWpLXhI7uLhQa6ApQR/f0T0nfFaMceqMxhxn/1PTOS\n5B5fGB85ZIZK7iYvgZVds24IfB5LPLDfruh7aezM9Gbu/i1tryZyasKZUXiiCEvy\n+18D2Ae7cF37VZCoVzy4K+WpjulxBb1MZGKwBMloxhzBInW5UPopR9sQZclEnSgY\nCh6jWhuyzikR3kkC9rxc09CmzWrmbU/N+NULfoNd/KZXO3E/uqg7DEs9CjWHIghN\nPB/qjVEhAoGBAPyFsjbgRh3Zv7jeDQ67CPnPWh/q7rEITZCKRagJ3ACOF+ijTkWo\n+dAeqs0PoW+4CnNqx7HAxHgQ4SeZPbv9oyaXZUd3jzOpxCuV+De6igohP5qrjR0i\nmj59nDdT4T9inzAs1ElRXQssG8qevzzHB5MmOZM2mItOHH++0Ud7Wt+XAoGBANmA\n2YsX/wN6yufkSR0IGvLLfx7/adodtDZ1n7UGOo8udH9aKjBzl47Hepc/RjRlvS6V\nJlzvOMZNH0Z0yZbLvMuQkdFyglhYeLx/SmUL7ybBlb7/QlCBiGaZfiaaZBLgHWr4\n8V5iKlzj1Ao+8Mn6tSj8YHSqI8mOtQZpsS0ycDC5AoGAA5lMLugHV8mQp+vSN9GG\nkTjZSfcpK7C4mkS+NWTek8tyn8gkB24fEU4+lOmSHWt8CqUM74WVxzhGXTAb5x/4\nQUaLFPepPM1AlHZwsSqhaP+MToH/YtjpZdaYcVlqrmKTbjZVWC4mq1AXnU2h4BXe\nD8TNsUFn7yRP16o6hVBGvUUCgYAqMe8CJvOYDzhR6F2uviXMOGI+9znn0J9neUY0\nbjLqGA8NrcZFhAdA8b38nY/XFm2vHcxFdztCbS/GEV4SXRARRcikI1zaGr/BgchC\n9h+9Gw0b8pVA3QBDNz/b6VPEvam3WPgqYUzqnGBEZJV9+Z8vhlaIC4HJ1l+UEOkI\nZaKSMQKBgEZKzgoNlFMlfUR0nzCBpEdn4BOKLYfYYurVJw6lcx9hE9lEnRHBv/zO\nb4iNjA8pqLFfoi/O2luUG4lutDhGWYkEYnc4hPjityJFFtimw920CIFg+HCMOjoz\n+pLVg/dMnNUJpA8Hr7Kqh+Qsewo6s2HDwc0ZmanJp+99Lr4yn1OL\n-----END RSA PRIVATE KEY-----\n",
            "IssuingCA": "-----BEGIN CERTIFICATE-----\nMIICqDCCAZACCQCd1SFT0vZFKDANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMCAXDTIzMDgwNzA4NTA0NVoYDzMwMjIxMjA4MDg1MDQ1WjAVMRMw\nEQYDVQQDDApmbG9tZXNoLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAwMQdfgcRZxsoTTqmSU2PTGrLP7aqwteoMw0IULtvb17Fa5k3bVQj6Ob7ztUD\n77/SMqq+VXG4vE+TBx26pheiKRrClfvlPl+eS7jnTJQkQkuhsuJHmTfM/+mwcw7N\nuAIatKeq6CRaTnMAbfPqa7Baaotr/md1ThGImPj3E294MVbi8MIME3XlbFuB6YfK\nebTIcZOF4WzDskL8TwMqYZS3UNeZA4YALd68UQQHymIA6G97tuRh62kI3dZx1+RR\nJ1JP9gSFp1sbDD1mVY732w9olkv5K1KvKgDb+Ct8aqrG2Xmtrst7k37bQsszZcCb\ndDRS+qwYQSOT0iQAli4ITGH9tQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBKx4Jd\nVcJvnls2tDozUvAjdmN/RfWv9kurUJX6G+lR+2YoZF4BPEDraRiDeWjmV9NATofg\nk7AbHYSBDN1QQXsCmOtQzU4pTblNxglpAlOWbhw1BD1CkFAb6kqV9NnC3AJPPril\nwXR/Mznk180SXkXo0uQbvlMgrSTZkp0gzIvUSCMEFuzeONqPlT1GCNFeEb8W1JKM\n7eCs2j1tY/1LDTDJg2YB2nZHLMhNwHjQ6mef5QGu8eqK5Ijpzou3FrVZ9no1isQB\nwARkMU3HXVeSipUxr8/uS4ZmJ1jPCrLYBrxUiDYeGrfZAN0ZGMu1s9Z9VX+QBowq\nimKzAYRDka7IzVMh\n-----END CERTIFICATE-----\n"
          }
        ]
      }
    }
  ],
  "RouteRules": {
    "8080,8443": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ],
    "HTTPSRoute": [
      "common/tls-termination.js",
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

执行下面的命令发送请求进行测试。

curl --cacert ca-cert.pem https://example.com --connect-to example.com:443:127.0.0.1:8443

3.1 - 路由

在本文档中我们将介绍 FGW 的路由功能

介绍

路由在计算机网络中是一个至关重要的概念。FGW 的路由功能是专为现代 Web 应用和微服务设计的。它提供了细粒度的控制,让开发者能够精确地控制网络流量的流向和行为。这在现代的微服务架构中尤为关键,因为它可以优化网络性能、增强安全性,并提供更好的用户体验。常见的使用场景有:

  1. 蓝绿部署和灰度发布: 通过 FGW 的路由功能,开发者可以轻松地将流量定向到新版本的服务,进行 A/B 测试,或逐步部署新功能,确保平稳过渡。
  2. API 版本管理: 通过不同的路由规则,您可以管理多版本的 API,并在不中断服务的情况下进行迁移。
  3. 安全性增强: 通过具体的 HTTP 头部或请求参数匹配,FGW 可以有效地拦截恶意请求或重定向到安全审核流程。

在 L7(应用层)负载均衡中,FGW 能够根据 HTTP/HTTPS 的请求内容(例如 URL,头部信息等)来决定如何将流量路由到后端服务。

通过高度灵活的路由规则配置,FGW 能够满足各种复杂的路由需求。更多的高级功能,请参考 FGW 策略 部分。

在介绍 HTTP/HTTPS 负载均衡 时,我们将两个后端服务作为负载均衡的两个端点,对外提供服务。在本篇文档中,我们将分别为两个后端服务配置独立的路由。

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • 两个后端服务

配置说明

路由规则 (RouteRules) 的配置:

  • 端口号: 定义哪些端口将应用特定的路由规则。这使得微服务或应用能够在不同的端口上根据需求使用不同的路由策略。例如:8080, 443
    • 域名: 用于识别虚拟主机并根据请求的目标域名决定如何路由流量。例如:*www.test.com, api.test.com
      • RouteType: 描述了此规则的目标流量类型,决定了数据应如何被解析和路由。例如:HTTPGRPC
      • Matches: 为请求提供具体的匹配条件,只有满足这些条件的请求才会被路由。
        • Path: 对请求的 URI 路径进行匹配,从而决定相应的路由策略。
          • Type: 描述了如何匹配路径,是否是精确匹配、前缀匹配还是正则表达式匹配。例如:PrefixExactRegex
          • Path: 真实要匹配的路径值。例如:/, /prefix
        • Headers: 根据 HTTP 请求头部的特定值决定如何路由流量。
          • Exact: 针对头部值进行精确匹配。
          • Regex: 允许复杂的正则表达式模式匹配,提供更大的灵活性。
        • Methods: 限制只有特定的 HTTP 方法的请求才能被路由,提高安全性和效率。例如:GETPOSTDELETEPUTHEADOPTION 等。
        • QueryParams: 对请求参数进行匹配,为 API 版本控制或特性开关提供能力。
          • Exact: 对查询参数进行精确匹配。
          • Regex: 允许对查询参数使用正则匹配。
        • BackendService: 指定请求应该路由到哪个后端服务,使得多个微服务可以平滑地共享一个入口点。
        • ServerRoot: 为那些需要提供静态内容的服务提供一个物理路径,如网站的首页或资源。
        • Method: 当路由的流量类型为 GRPC 时,这是决定如何处理流量的关键组件。
          • Type: 描述如何匹配 GRPC 请求。例如:Exact
          • Service: 确定请求是针对哪个 GRPC 服务。例如:com.example.GreetingService
          • Method: 确定请求是针对服务中的哪个具体方法。例如:Hello
        • RateLimit: 根据预设的标准对流量进行限制,确保服务不会被过多的请求淹没。属于路由级别的限流,参考 限流策略 的使用。
      • RateLimit: 在域名级别对流量进行限制,为每个域名提供特定的限制标准。参考 限流策略 的使用。

示例

{
  "RouteRules": {
    "80": {
      "test.com": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/path"
            },
            "Headers": {
              "Exact": {
                "a": "1",
                "b": "2",
                "c": "3"
              }
            },
            "Methods": [
              "GET",
              "POST"
            ],
            "QueryParams": {
              "Exact": {
                "abc": "1"
              }
            },
            "BackendService": {
              "www8088": 100
            }
          }
        ]
      },
      "*.test.com": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/repo"
            },
            "Headers": {
              "Exact": {
                "b": "2"
              }
            },
            "Methods": [
              "GET",
              "POST"
            ],
            "QueryParams": {
              "Exact": {
                "abc": "1"
              }
            },
            "BackendService": {
              "www8088": 100
            }
          }
        ]
      }
    }
  }
}

配置

  1. 第一步仍然是从配置端口监听开始,这里我们使用 HTTP 协议的端口。参考文档 监听端口配置
{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ]
}
  1. 假设两个服务的路由分别是 /hello/hi,参考路由的配置文档 HTTP 协议端口号路由规则配置。后端服务这里,我们使用 backendService1backendService2 分别代表两个后端服务。
{
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/hello"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/hi"
            },
            "BackendService": {
              "backendService2": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  }
}
  1. 后端服务的配置,可以参考文档 服务配置
{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    },
    "backendService2": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  }
}
  1. 最后是插件链的配置,仍然使用最小功能集的插件链。完成的 HTTP 插件链配置可以参考 文档
{
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}
  1. 最终的配置如下。
{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/hello"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/hi"
            },
            "BackendService": {
              "backendService2": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    },
    "backendService2": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

4 - UDP 负载均衡

本文档将介绍如何配置 FGW 代理和负载均衡 TCP 流量。

介绍

FGW 的 UDP 负载均衡功能针对基于 UDP 协议的网络流量提供了负载均衡解决方案。UDP 作为一个轻量级的、无连接的协议,通常用于视频流、在线游戏、VoIP 等应用,其中快速传输比完整性或顺序更重要。FGW 通过其 UDP 负载均衡功能,能够确保这些应用的流量在多个服务器之间得到有效分配,从而提高性能和可靠性。

UDP 负载均衡的配置与 TCP 的类似。

FGW 的 UDP 负载均衡配置涉及指定要分配流量的 UDP 服务端口,以及确定如何将流量分配到后端服务器。

通过精确配置这些参数,可以确保 UDP 负载均衡既高效又可靠,满足高性能应用的需求。

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • 两个后端服务

在这篇文档里我们将使用 Pipy 来运行简单的 UDP 应用。

pipy -e "pipy().listen(8078, {protocol: 'udp'}).replaceData(data => (console.log(data.toString()), new Data('You are requesting port ' + __inbound.localPort + ' \n')))" &
pipy -e "pipy().listen(8079, {protocol: 'udp'}).replaceData(data => (console.log(data.toString()), new Data('You are requesting port ' + __inbound.localPort + ' \n')))" &

配置

  1. 要负载均衡 UDP 的请求,需要配置一个处理 UDP 协议的监听器,参考文档监听端口配置
{
  "Listeners": [
    {
      "Protocol": "UDP",
      "Port": 8000
    }
  ]
}
  1. 接下来要设置端口 8000 的路由规则,参考文档 UDP 协议端口号路由规则配置
{
  "RouteRules": {
    "8000": {"backendService1":100}
  }
}
  1. 配置后端服务,参考文档服务配置。后端服务的端点,配置为上面启动的两个 UDP 服务。
{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8078": {
          "Weight": 100
        },
        "127.0.0.1:8079": {
          "Weight": 100
        }
      }
    }
  }
}
  1. 针对 UDP 请求的负载均衡,使用的插件同样简单。仅需引入 UDPRoute 插件链和 udp/forward.js 插件。完整的插件配置可以参考文档完整的插件配置
{
  "Chains": {
    "UDPRoute": [
      "udp/forward.js"
    ]
  }
}
  1. 最后,可以得到完整的配置,替换 FGW 工程中的 pjs/config.json
{
  "Listeners": [
    {
      "Protocol": "UDP",
      "Port": 8000
    }
  ],
  "RouteRules": {
    "8000": {
      "backendService1": 100
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8078": {
          "Weight": 100
        },
        "127.0.0.1:8079": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "UDPRoute": [
      "udp/forward.js"
    ]
  }
}
  1. 测试
echo '' | nc -4u -w1 localhost 8000
You are requesting port 8079

echo '' | nc -4u -w1 localhost 8000
You are requesting port 8078

echo '' | nc -4u -w1 localhost 8000
You are requesting port 8079

5 - TLS

5.1 - TLS 卸载

TLS卸载是在负载均衡器或网关上终止TLS连接,解密流量后将其传送到后端服务器,从而减轻后端服务器的加密和解密负担。

介绍

TLS 卸载是一种网络架构模式,它允许网络设备或服务器在接收到加密的传入网络连接后,解密并处理传输的数据,然后再将数据转发到后端服务器。这种模式可以在负载均衡、安全性和性能方面提供优势。与之相对的是 TLS 透传

在 TLS 卸载的架构中,通常有一台前端设备(如负载均衡器、反向代理服务器)负责处理客户端的加密连接、解密数据,并根据特定规则将请求分发到后端服务器。这样可以减轻后端服务器的计算负担,因为它们不需要处理加密和解密操作。这里,FGW 可以承担该前端设备的角色。

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • 后端服务

配置说明

配置以下参数以启用和配置 TLS 卸载:

  • TLSModeType:定义 TLS 工作模式,设置为 “Terminate” 启动 TLS 卸载)。
  • mTLS:定义是否启用双向 TLS,增加额外的安全层来验证客户端的身份。
  • Certificates:包括以下三个配置项来配置 TLS 证书信息:
    • CertChain:服务器的证书链,通常开始于服务器的证书,后跟任何中间证书。
    • PrivateKey:用于与证书链中的服务器证书配对的私钥。
    • IssuingCA:用于签名证书链中的服务器证书的证书机构(CA)证书。

示例

{
  "TLS": {
    "TLSModeType": "Terminate",
    "Certificates": {
      "CertChain": "—–BEGIN CERTIFICATE—–\n...",
      "PrivateKey": "—–BEGIN RSA PRIVATE KEY—–\n...",
      "IssuingCA": "—–BEGIN CERTIFICATE—–\n..."
    }
  }
}

配置

  1. 要让负载均衡器支持 HTTPS 配置,我们需要先签发 CA 证书和服务器证书。
openssl genrsa 2048 > ca-key.pem

openssl req -new -x509 -nodes -days 365000 \
   -key ca-key.pem \
   -out ca-cert.pem \
   -subj '/CN=flomesh.io'

openssl genrsa -out server-key.pem 2048
openssl req -new -key server-key.pem -out server.csr -subj '/CN=example.com'
openssl x509 -req -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365

执行上面的命令,我们会得到如下几个文件。

ca-cert.pem
ca-cert.srl
ca-key.pem
server-cert.pem
server-key.pem
server.csr

可以使用命令 awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' ca-cert.pem 来输出单行。

  1. 设置监听端口的协议,参考 监听端口配置文档,我们选择 TLS 作为端口的协议。
{
  "Listeners": [
    {
      "Protocol": "TLS",
      "Port": 8443,
      "TLS": {}
    }
  ]
}
  1. TLS 证书的配置,可以参考文档 TLS 配置,将上面生成的 server-cert.pemserver-key.pemca-cert.pem 分别配置在 CertChainPrivateKeyIssuingCA 字段。功能模式 TLSModeType 支持 TerminatePassthrough 两种类型。PassthroughHTTPS 负载均衡 中有使用,这里我们使用 Terminate 类型。
{
  "TLS": {
    "TLSModeType": "Terminate",
    "mTLS": false,
    "Certificates": [
      {
        "CertChain": "-----BEGIN CERTIFICATE-----\nMIICpTCCAY0CCQD9vpm9qHfkjTANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMB4XDTIzMDgwOTA5MDUwNVoXDTI0MDgwODA5MDUwNVowFDESMBAG\nA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nv76yA1Zm5wKgfZjVKqNdEKd6Ejf8aQNHyk1gY8IKvWt2AeyqVAOqOjas/Pj8uVaG\nOrxfIAqqT1U5QF/WEPgUdxBgKTt2rXFr8f27bTmNQ6Z9irKaJx9BSyK7u6bvv9x6\nMgKxjtq43712R0W8gelfI3412KpYtpKTecMX2f3scCXNnksQUx7F35gg8kGcCLGN\nWsrT7+Jmdp6QP+S2fTLKr3qDEmZZMvRx/tKIR6WoZEhs7pnj6T3SLkK4pBjhwnxK\nclgJDBCG9UM0KNqmAnGMkSFKPTGs1x02eYhCf9+Hk3oXMi6DcWt7GH+Efmat/k4d\nNidOjJRBJuhMOZD+IhnxCwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAiWpzIROIN\nVySnxOm8Ua4E29c3GxB6KDOIpwxBizOW66wGA7zCfa8j/QgfTLV7q+hBj964PZJJ\nkK/Wuh8H99rzpQToOxWuFfBgNmPezFN/dSE5k+PQ6mItesWTtfDqFJIod4yIWoDf\nq9dbnOs0c3WGexP4YMo5WW9v/TKWfVgcc83aPsVnO5YpJC8y7VUDiyGKJW6R/k4y\n16fsrX4V1Gpj6ivFenG9OkftViff1/JuattMr3wq994SGlAggWL+H93CkyofbW+U\n3BVI4750Y0MYuDpt4DyI2zsKe2N5Ga7lNTfbj6tTdrfHkz0jdABDXc5lQr6ahTNO\nBvvV2Dpr0ozn\n-----END CERTIFICATE-----\n",
        "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAv76yA1Zm5wKgfZjVKqNdEKd6Ejf8aQNHyk1gY8IKvWt2Aeyq\nVAOqOjas/Pj8uVaGOrxfIAqqT1U5QF/WEPgUdxBgKTt2rXFr8f27bTmNQ6Z9irKa\nJx9BSyK7u6bvv9x6MgKxjtq43712R0W8gelfI3412KpYtpKTecMX2f3scCXNnksQ\nUx7F35gg8kGcCLGNWsrT7+Jmdp6QP+S2fTLKr3qDEmZZMvRx/tKIR6WoZEhs7pnj\n6T3SLkK4pBjhwnxKclgJDBCG9UM0KNqmAnGMkSFKPTGs1x02eYhCf9+Hk3oXMi6D\ncWt7GH+Efmat/k4dNidOjJRBJuhMOZD+IhnxCwIDAQABAoIBACjXLkVltt9HgPWf\ngu/lAeKVOXv97sZTS4w8dOZqoyz7YZRBW3ovmadyk+ACDJpRYp/KFZzWiLYDGgGr\nKAZPQNSnaUP/BWUl/m75s10tX/hj0uOi7RCeKKMfT8tFYFWGWYSjbDxYO/5z9Why\n4xbspTYDIOb4SZMBn2XU9xSYcC7mj/MaFr5bkMjLg+SMnG/QUgWoMA3F/hbmzrm2\n+5cqDY6Ew6EQk8IGRKRI3BrrgWrtljTcfDahLfA9Lu1+dE0ABkXUFuJ8ilgKunHi\nmo+61TPbVpo5A1hX3iexA1b38E8e2vAGzL8Lf4lAAxaJ823JmVqTJ28OZR+MFFXD\nXG05gyECgYEA5qS4aYHZM4/pvc1VIoLafWdH/nlbYQK9D6bBh3ICm2MGWWdta/nd\nB1bI7JT2J3lpb/mPK/pr8CXn60whO24XY18gU8A5HOlPO1l3QAHzIRLQFz11nb1M\nnwNen+u4zYEwnQPJSoWhh4S3686fY0OEUsxhc92x0QvkapcA429kTLECgYEA1NM0\nwbHjKNpbwW0P0KVHP2Cbwy3nB9topwJ8A6sT7cedjViDuP/ontvM7yosjc1YMN4t\noqg0tCPFGCfyoOSSnikEPBSYZzGCxaZ0K7tBWdHl+oDVNdPBGqIZzr2JypNpcRr6\nkH6gzjTFey/OJsVVOQPZ9D6TuMkk7mJAi11gmHsCgYEAyCUy3mPOvv7ooEtZ0Ivq\n3B3PDNX05RdCRx23HTljZ8Ij1Vt6SdPW6TJ3Q0302cZzJ7dRdaFnH0tVmQtEX1Um\nuJXo8KSDK0KO/fqiEAphGFdB+pjbwtltbyO2bmJYyQSN0gNiHugdhwM1s0xnZfVG\nE6/F9YzxbG28dn65R6P3TtECgYArjDQFVkLm/xc7Uvejd85GV5xHqcLWRrz5P3bk\nwULIqsnAPFZnqmWM6+jZH0YSlevvw+aOm+B847zWnoX1ChA+MKJfMM+mfekGTHME\n58INgPeP9ICsDPI8YuLo/LuPKe6vaBfRLTf2ObIW7MdAA6zWh8U3Rv6vFulppc0T\nNz4mtQKBgDApvH3mB5mCwh/Go0rKXzpOPfMKPip0/c65bkqzFqTjgFRG/A8gNMgD\ns9eGq8Gu+EO4ZvJvJHM/wf1d6vZ4kD0A1Cg/7VC7auy49dyI+A2J+1p/ebBjr3Dt\nPAgRO1uDYM6t7gstO4RddL3+fNW6whJ/QQRBwKJkwl8vVutROn7m\n-----END RSA PRIVATE KEY-----\n",
        "IssuingCA": "-----BEGIN CERTIFICATE-----\nMIICqDCCAZACCQCnQtjr6YkElTANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMCAXDTIzMDgwOTA5MDUwNVoYDzMwMjIxMjEwMDkwNTA1WjAVMRMw\nEQYDVQQDDApmbG9tZXNoLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAyjCOniAGJ/Afd99lrzVHVHnba3JM3mTuWAhfgXe/0A+RQejTpi/6XjBzWWsv\nmSX+ujtsEZWNZh8UeNtMwtS2ozM1gEte/jnIDBDl8tI7ZU78C3DGi9YvyXxUi6TO\nOJXtU3Z9Q2pKlusnLxUfqaXtq6cEGnU8x/62MOvoBxQ68B6ekuQZdA4i54K2S7KI\ngt+ROm2CD6iIr5vK6HouyS7LM4TECli1O5aFPhR3+PVVduiauhrcIhgELRpC84+e\n5VwtC4p7hZwQbtiOaXfaKCPFbzhdL2lzHJEDao58VpNMyE+nUI8uAtWjWvHXkD2t\nqSAqb8BPFAVAdWMmOeloWTZn8wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBqKsi2\nWnVFTRJ3+tJU0FX+rTiogYYxzFfhiJtwS6D/RRXRYOWIXRkhJWRAu+Gm4D2+yF9C\nSngD3dU9koG6N59qwnQ3nnRLfxvGltjk4VjDIiyJU1RU/3T4jDVXIwRB090j1/Se\nrra1WTKwqwm3EWoVbUNCdRsetRirSylqqYApfvWqeRewP7znX9MhfU0uncLOuTUe\nHpBOynUA48e5pxqBOeLS1iICuvixyqJHxLC/aIurpJm05CVNnjQGW2IPFBKJ9kb9\n+w1EC3kfDg9UQEK9QHh56bMUfBl2njWuBIw2AZ/lZe2yHSkauv2FCXfi6T3pO/OW\n+TdumkRp5puMKcn7\n-----END CERTIFICATE-----\n"
      }
    ]
  }
}
  1. 接下来是设置端口 8443 的路由规则,参考文档 TCP 协议端口号 Terminate 路由规则配置。其中键的内容为 TLS 证书的 SNI 名字,值为后端服务名。此处,我们使用自签发证书时使用的 example.com;值为服务 backendService1
{
  "RouteRules": {
    "8443": {
      "example.com": {"backendService1":100}
    }
  }
}
  1. 配置服务端点,参考文档 服务配置
{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  }
}
  1. 不要忘记插件链的配置,本文档主要介绍 TLS 卸载,这里只需要引入 TLSTerminate 插件链。完整的插件配置可以参考文档 完整的插件配置
{
  "Chains": {
    "TLSTerminate": [
      "common/tls-termination.js",
      "common/consumer.js",
      "tls/forward.js"
    ]
  }
}
  1. 最后完整的配置如下,使用其替换 FGW 工程中的 pjs/config.json
{
  "Listeners": [
    {
      "Protocol": "TLS",
      "Port": 8443,
      "TLS": {
        "TLSModeType": "Terminate",
        "mTLS": false,
        "Certificates": [
          {
            "CertChain": "-----BEGIN CERTIFICATE-----\nMIICpzCCAY8CCQD9vpm9qHfkjjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMB4XDTIzMDgwOTEzMzAwMloXDTI0MDgwODEzMzAwMlowFjEUMBIG\nA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQC8PVeWrdUW/daf7AWv1nGaxd+tVLe95RDk+A6rOmr5uoFBdO2OKfdYg3cNr3ti\nE2XbcBa00lrdhxMHANvNtvB+YApmyjXzLQW0XoJe6H6B28Q2RiSvlt4ek1w4h5j8\nUNGmKDZvL9PgPcVecKMqikHjsoPxKO/IdcB0SZ1sEnY6mQrFStVJI6ReUFAVIxK6\nFnbgJhhslU22+vF6hWvjdljl6YDyIpeuh+hjyGY6opFUv6hs7oHKvxnSPXb+c/Qm\nKfWfQiOswKJhgZcEeFvOfYrMNtCPXgF3TDHaAHbJ0+WPCllDlvNtCyFIcN8yD5JB\nyuPmXVJYP+Wg3ac/9mxLk+D/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBABQpLMo+\nD4E/amfDxJN2oDo0Q8SA4H4uTqDgJdIL9iy91CmjMbiK1vrw7TSNoxjn33ds6bBt\n0xqsc//ckrgFSrUzqbkr7FYhLEd9Mwl4IaXwl6tk4IRzVEUMt+cRC4qadXd8uYVZ\ntbVMoMQ0vi3OEJhemb8eGB6yVhufCw7535oU0Us1lDegQ3nTp+jf7LYMLgbQKk1v\nqrvoSx5kgwXKHIDJ7jYHMtm2KH2H18274XM+WH13RydHe0IIwa2TAJnHjv/a6m/N\niiq/eHJZAIWKAm2zP9pZCDV1FEJx+HDy+L9L/i8q6bYs3M5l5xi7+HeFW6Hi1jff\nu7LQ4p5Ms4tsElI=\n-----END CERTIFICATE-----\n",
            "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAvD1Xlq3VFv3Wn+wFr9ZxmsXfrVS3veUQ5PgOqzpq+bqBQXTt\njin3WIN3Da97YhNl23AWtNJa3YcTBwDbzbbwfmAKZso18y0FtF6CXuh+gdvENkYk\nr5beHpNcOIeY/FDRpig2by/T4D3FXnCjKopB47KD8SjvyHXAdEmdbBJ2OpkKxUrV\nSSOkXlBQFSMSuhZ24CYYbJVNtvrxeoVr43ZY5emA8iKXrofoY8hmOqKRVL+obO6B\nyr8Z0j12/nP0Jin1n0IjrMCiYYGXBHhbzn2KzDbQj14Bd0wx2gB2ydPljwpZQ5bz\nbQshSHDfMg+SQcrj5l1SWD/loN2nP/ZsS5Pg/wIDAQABAoIBACOMzjLlx32dGOCA\n+Z34uOHLBvA8NKtHTIaBlnud/8AECg8rnwWfRVhRE7Xg80NVeIIVzCQAKir2LJDB\nB8H1D7w+NRiujbvMP+yNgL+d1u59a7P4UUtcCbzqhZsjeLAGL2Ha7FTZSoFqCRFJ\n4nbRP5paB3MPESHhoyQTFwjm/68XDzUUNman/7235ms85X6VOoSuF936JgkxMNoi\nCfcMQ9kfII6QKqDbB5LxY0P0PWc4CMgKCf492yRARFJC4HUoT/eXH+lYh+DOYlWw\nQt1Y54fxWVeG+Rz46uKsL/5+8jqCd5Ah2gfPc59Ji7DZlls2JXwRCdEvjlGFj0mO\n7qTa9tkCgYEA3zoN+ec2B6xC71vtLFGpls2yPyLnjprXkEeJQNF9yIewuYpGVGLP\ndcAKLSiB+/YvuIKZul/uBdQkHRezXJIlClWzsATZWlIcr84ZUtf4hkwgW0EmpJXJ\n3069+uTLcXr0s7mOuep1TlLEWceaiwOWZW+JYodriDsJWrRGz4G0RYsCgYEA1+BM\n5b9o0M2cMdoMqaE6XTBJRVEAFl3dokiUtQxXHEi7bDfr9Sx0Ps6pg7CdXBufDovP\nqLzfEqqnOnOVFGxaXDNhvoqLW/itn6ANPaw1XiqlhTFrFD3Rqtm194wdo5mKfILo\njgese/hb+t6RloWdaDmYBN3vaJC1cXFPWN5GiN0CgYBN89IJoOpHR6qgN7PdNC9K\n0E4cqi2+qOf6JGET15RbQLdAM79XnKHh9swW9PxfZptHjaPtZ66RLoHl/u7NtuNk\ndoUnRKo6Vk5aPliti2noTBFIjLnX4875QmApi1hYKp3lXTkwR2Xrkg+rYn7faMNO\nbOLHG487pZIgsK/BqwOu/QKBgQCnazL38uxNE0iRePPdEkb7QplwgpM4xW8/jl6V\n0o40R0vjb7M1H1a/5vKcSPqhFmLSmydfS6sNBQBQWpdBkY66drbVWQkfOMseQrhC\nHi39a8GWfG748cCLafCvnSDXYhp+2d+VVuoz8rcS5k2umM0sqY32KFClnaS56BCL\ncUbumQKBgED/8Ovu4uowO+BWbiYMX5Eov0iEk92e02SIBvCsqoe63iO7RJtdsfEr\n+QPNz35K/xyXSEjZi6gj1ui9Td91cuUC2OwRbor+yAwXgCFuxNncjQY28Ul3F4TH\n/zFuuJlb5ca/6TqMc5hxC0qdOPg3weK1dRlDfQb/vikbBD40QA/6\n-----END RSA PRIVATE KEY-----\n",
            "IssuingCA": "-----BEGIN CERTIFICATE-----\nMIICqDCCAZACCQDFLOUQOxJGoTANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApm\nbG9tZXNoLmlvMCAXDTIzMDgwOTEzMzAwMloYDzMwMjIxMjEwMTMzMDAyWjAVMRMw\nEQYDVQQDDApmbG9tZXNoLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAx3s/KDkaRTd1yL+Ku0qfHsj2M6JyjGycZvcB7m+rxdKqCRvZXZa7dk5qfqGE\nN7xyIQQQTWvZB+yYQPtvDnB184Idyh3h/5khfkX/V2bfaifb4v85SKpdcKIq3MsG\nQe3oBaYUeOoRHJiw9Cb2bHthtDjlsfXxm8LXf1YGHShWDZqOnGd/P7Hvw4ZQ5P+v\nBwpwpuAPgcIA3gq6qb22LZbHeENCulIfqd7giUVd6NtBP5G3Lu4ATGpwhIBnJliF\nw3VSG9G7zrH6qNx4E9qlI+PvGLidQg+qJM6J+y3dvtVICoAMD5yDOgKttmP65BYR\nQgyQFxY7zj1UwSGxGUT4waIDjQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQADpzp7\n6nBQufE9Yhr8D9QRDT4oXo/eEO7jY274LjB/YhdZ2SN5kQXHneF91x4NsA3qTwyn\nMIRPJT9QwJuYKtX+S7lBxUh39WdtLI+Q1L2bB3DB5PEafeAJHyFszz6Gk8GcD1qu\nwinL7Fy111MiMXlU2R3rvm/z0VkDhW0vx4VaOuIgWDt/ou0jSL2xOP7aH14MZ5FG\nvIVhyVsY+O8RJj5yg9Bzso0wj4sMvNJgFEmA0ENY9KeoULcHcyfMN5fSoA5Qw/6l\nZ1Ac5sYW9UwcDzKyXtzWvabJ3lwPLnviPoorowmmT3rhhNvrVhH4jdyrLnTaFzGo\nAfYeWwziVKdGC5Yy\n-----END CERTIFICATE-----\n"
          }
        ]
      }
    }
  ],
  "RouteRules": {
    "8443": {
      "example.com": {
        "backendService1": {
          "Weight": 100
        }
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "TLSTerminate": [
      "common/tls-termination.js",
      "common/consumer.js",
      "tls/forward.js"
    ]
  }
}

多次执行下面的命令,访问负载均衡器的 8443 端口,可以看到轮流返回两个后端服务的响应。

curl --cacert ca-cert.pem https://example.com --connect-to example.com:443:127.0.0.1:8443

5.2 - TLS 透传

TLS透传是指负载均衡器或网关不解密TLS流量,而是直接将加密数据传送到后端服务器,由后端服务器进行解密和处理。

介绍

TLS 透传(TLS Passthrough)是代理服务器处理 TLS 请求的两种方式之一(另一种是 TLS 卸载)。在 TLS 透传模式下,代理不会解密来自客户端的 TLS 请求,而是将其传递到上游服务器进行解密,这就意味着数据在通过代理的时候保持加密的状态,以此来保证重要和敏感数据的安全性。

TLS 透传的优点

  • 由于数据不在代理上解密,而是以加密的方式转发到上游服务器,数据可以免受网络攻击。
  • 加密数据未经解密到达上游服务,确保了数据的机密性。
  • 这也是代理配置 TLS 的最简单方法。

TLS 透传的缺点

  • 流量中可能会恶意代码,这些代码将直接到达后端服务器。
  • 在 TLS 透传过程中,无法切换服务器。
  • 无法做 7 层流量处理。

前置条件

  • Pipy(版本 >= 0.90.3-2)
  • FGW Repo(版本 >= v1.0.0)
  • TLS 后端服务

TLS 的后端服务,本文档使用 https://httpbin.org 作为后端服务。

配置说明

在处理 TLS 透传时,配置非常简单。

  • TLSModeType:设置为 Passthrough。这种模式下FGW 不会终止 TLS 连接,而是将加密的流量直接传递给后端服务,这意味着后端服务必须能够处理 TLS 解密。

示例

{
  "TLS": {
    "TLSModeType": "Passthrough"
  }
}

配置

  1. 设置监听端口的协议,参考 监听端口配置文档,我们选择 TLS 作为端口的协议。
{
  "Listeners": [
    {
      "Protocol": "TLS",
      "Port": 8443,
      "TLS": {}
    }
  ]
}
  1. 作为使用 TLS 协议的端口,需要对其 TLS 进行配置。根据 TLS 配置文档,功能模式 TLSModeType 支持 TerminatePassthrough 两种类型。TerminateHTTPS 负载均衡 有使用,这里我们使用 Passthrough 类型。
{
  "TLS": {
    "TLSModeType": "Passthrough"
  }
}
  1. TLS 透传是工作在 L4 上,配置路由规则时使用 TLS 协议端口号 Passthrough 路由规则配置,键值分别为上游的域名地址(或域名地址 + 端口)。这里,我们使用配置 "httpbin.org": "httpbin.org"
{
  "RouteRules": {
    "8443": {
      "httpbin.org": "httpbin.org"
    }
  }
}
  1. 还有插件链的配置,我们选择用于 TLS 透传的插件链 TLSPassthrough
{
  "Chains": {
    "TLSPassthrough": [
      "tls/passthrough.js",
      "common/consumer.js"
    ]
  }
}
  1. 最终得到完整的配置。
{
  "Listeners": [
    {
      "Protocol": "TLS",
      "Port": 8443,
      "TLS": {
        "TLSModeType": "Passthrough"
      }
    }
  ],
  "RouteRules": {
    "8443": {
      "httpbin.org": "httpbin.org"
    }
  },
  "Chains": {
    "TLSPassthrough": [
      "tls/passthrough.js",
      "common/consumer.js"
    ]
  }
}

测试时,可以执行下面的命令向代理服务器发送请求。

curl https://httpbin.org/get --connect-to httpbin.org:443:127.0.0.1:8443

6 - 策略

本系列文档将介绍 FGW 高级策略的使用。

Flomesh Gateway (FGW) 提供了一系列先进的负载均衡策略,以确保流量在后端服务之间有效、安全地分发。这些策略包括熔断、限流、超时、重试、重定向、路径重写、HTTP 头修改、会话保持和主动健康检查等。

本文档将深入解释这些策略的功能以及如何配置和使用。

这里介绍的部分策略与端点 Endpoints 一样,都是服务维度的配置。

有些策略则是路由或者路由+服务的粒度:

当然还有粒度更加灵活的策略,如 限流故障注入,可以作用于域名和路由的粒度。

示例

{
  "RouteRules": {
    "PORT": {
      "HOST": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {},
            "BackendService": {
              "serviceName": {
                "Weight": 100,
                "Filters": [{
                  "Type": ""
                }]
              }
            },
            "RateLimit": {},
            "Filters": [
              {
                "Type": "RequestHeaderModifier"
              },
              {
                "Type": "ResponseHeaderModifier"
              },
              {
                "Type": "RequestMirror"
              },
              {
                "Type": "RequestRedirect"
              },
              {
                "Type": "HTTPURLRewriteFilter"
              }
            ]
          }
        ],
        "RateLimit": {}
      }
    }
  },
  "Services": {
    "backendService": {
      "Endpoints": {},
      "Filters": {},
      "CircuitBreaking": {},
      "MaxRequestsPerConnection": 1,
      "MaxPendingRequests": 1,
      "RetryPolicy": {},
      "HealthCheck": {},
      "StickyCookieName": "COOKIE_NAME",
      "StickyCookieExpires": 3600
    }
  }
}

6.1 - 熔断

熔断策略的引入,使得系统具备更强的容错能力和更优化的资源利用。本文档将介绍如何使用 FGW 的熔断功能。

介绍

熔断是一种防止系统过载并保证服务可用性的机制。当一个服务连续失败达到一定次数时,Flomesh Gateway(FGW)将“熔断”该服务,暂时停止向其发送请求。这可以防止故障的蔓延,并给出故障服务恢复所需的时间。

熔断策略的引入,使得系统具备更强的容错能力和更优化的资源利用。

前置条件

这里后端服务建议使用工具 Fortio。Fortio 作为服务器运行时,可以在请求时指定延迟、响应状态,及其分布,非常方便测试熔断场景。同时 Forito 还可以作为负载生成器来使用。

启动服务器可以使用命令 fortio server -http-port 8082

配置说明

在 FGW 中熔断是 服务 Service 维度的配置,可以针对服务的慢访问、错误进行熔断,并支持配置熔断后的响应。这两种熔断的触发条件很容易理解:

  • 慢访问触发熔断:当系统中某个服务的响应时间超过预定阈值时,熔断器会开启,以阻止对该服务的进一步访问。
  • 错误触发熔断:当系统中某个服务的错误率超过预定阈值时,熔断器会开启,以阻止对该服务的进一步访问,比如服务返回大量的 5xx 错误。

熔断配置项

  • MinRequestAmount: 触发熔断必须达到的访问次数。为了避免基于小样本的误判,这一字段确保在短时间内的极少数错误或慢访问不会导致过早的熔断。只有在统计时间窗口内的请求达到此设定值,系统才会考虑熔断策略。必须字段。
  • StatTimeWindow: 熔断判定统计窗口(单位:秒)。这定义了评估慢访问和错误请求的时间范围。在这个窗口内,系统会收集相关数据,以判断是否需要熔断。必须字段。
  • SlowTimeThreshold: 慢访问时间阈值(单位:秒)。它定义了什么样的响应时间应该被认为是“慢”的。如果一个请求的响应时间超过了这个阈值,它就被标记为慢访问。非必须字段。
  • SlowAmountThreshold: 在 StatTimeWindow 时间统计窗口内,触发熔断的慢访问次数。达到或超过这个阈值的慢访问次数会触发熔断。非必须字段。
  • SlowRatioThreshold: 触发熔断的慢访问占比(0.00~1.00)。这与上面的次数阈值不同。如果慢访问的占比超过这个设置的阈值,则会触发熔断。非必须字段。
  • ErrorAmountThreshold: 在 StatTimeWindow 时间统计窗口内,触发熔断的失败访问次数。错误的访问次数达到或超过此阈值时,会触发熔断。非必须字段。
  • ErrorRatioThreshold: 触发熔断的失败访问占比(0.00~1.00)。与次数阈值不同,如果失败的请求占比超过这个阈值,则会触发熔断。非必须字段。
  • DegradedTimeWindow: 熔断时间窗口(单位:秒)。当熔断被触发后,服务将进入一个“降级”状态,只返回错误或预设响应,不再处理实际的请求,持续这个时间窗口长度。之后,服务会尝试自我恢复,再次接受并处理请求。必须字段。
  • DegradedStatusCode: 当服务被熔断时,返回给客户端的 HTTP 状态码。这是通知客户端因为某种原因服务当前不可用的标准方式。必须字段。
  • DegradedResponseContent: 当发生熔断时,返回给客户端的提示信息。这可以为客户端提供更具体的错误信息或建议的后续操作。非必须字段。

示例

{
  "CircuitBreaking": {
    "MinRequestAmount": 10,
    "StatTimeWindow": 60,
    "SlowTimeThreshold": 1.5,
    "SlowAmountThreshold": 5,
    "SlowRatioThreshold": 0.1,
    "ErrorAmountThreshold": 3,
    "ErrorRatioThreshold": 0.05,
    "DegradedTimeWindow": 300,
    "DegradedStatusCode": 503,
    "DegradedResponseContent": "Service is temporarily unavailable."
  }
}

配置

负载均衡器配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8082;然后根据 HTTP 插件链配置,将 http/circuit-breaker.js 插件配置在相应的配置。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/circuit-breaker.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

前面提到熔断的配置是服务维度的,接下来我们开始熔断的配置。

慢访问数量熔断

在配置熔断之前,检查下负载均衡器对慢访问的反应。将服务端的延迟设置为 600ms,概率为 30。在统计延迟分布的时候,特地设置了 71 这个百分比。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?delay\=600ms:30

# target 50% 0.0058
# target 71% 0.601213
# target 90% 0.608895
# target 95% 0.610917
# target 99% 0.612534

IP addresses distribution:
127.0.0.1:8080: 10
Code 200 : 100 (100.0 %)

设置服务 backendService1 在 10 秒的时间统计窗口内、慢访问(延迟大于 500ms)的数量达到 20 时进行熔断。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "CircuitBreaking": {
        "MinRequestAmount": 10,
        "StatTimeWindow": "60",
        "ErrorAmountThreshold": "20",
        "DegradedTimeWindow": 10,
        "DegradedStatusCode": 503,
        "DegradedResponseContent": "Service is temporarily unavailable."
      }
    }
  }
}

在配置慢访问熔断后,再进行测试就会发现有 30% 的响应状态码为 503,熔断生效。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?delay\=600ms:30

# target 50% 0.0117143
# target 71% 0.0175385
# target 90% 0.610336
# target 95% 0.614643
# target 99% 0.618088

IP addresses distribution:
127.0.0.1:8080: 30
Code 200 : 70 (70.0 %)
Code 503 : 30 (30.0 %)

慢访问占比熔断

同样,假如我们将慢访问熔断从数量触发熔断改成百分比触发, 将 SlowAmountThreshold=20 改为 SlowRatioThreshold=0.2,即慢访问的占比达到 20% 触发熔断。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "CircuitBreaking": {
        "MinRequestAmount": 10,
        "StatTimeWindow": "60",
        "SlowTimeThreshold": "0.5",
        "SlowRatioThreshold": 0.2,
        "DegradedTimeWindow": 10,
        "DegradedStatusCode": 503,
        "DegradedResponseContent": "Service is temporarily unavailable."
      }
    }
  }
}

还是用同样的方式进行测试,可以发现有 80% 的请求被熔断。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?delay\=600ms:30

# target 50% 0.00353846
# target 71% 0.00433333
# target 90% 0.00733333
# target 95% 0.601387
# target 99% 0.606937

IP addresses distribution:
127.0.0.1:8080: 80
Code 200 : 20 (20.0 %)
Code 503 : 80 (80.0 %)

假如设置 15% 的响应延迟 600ms 的话,并不会触发熔断。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?delay\=600ms:15

# target 50% 0.00286111
# target 71% 0.00469231
# target 90% 0.603466
# target 95% 0.606932
# target 99% 0.609705

IP addresses distribution:
127.0.0.1:8080: 10
Code 200 : 100 (100.0 %)

错误响应数触发熔断

我们先看下没有配置熔断的情况下,服务端 30% 响应返回 503 的情况(实际数据会有一定的浮动 +- 5%)。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?status\=500:30

IP addresses distribution:
127.0.0.1:8080: 37
Code 200 : 68 (68.0 %)
Code 500 : 32 (32.0 %)

我们设置触发熔断的错误数:ErrorAmountThreshold=20

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "CircuitBreaking": {
        "MinRequestAmount": 10,
        "StatTimeWindow": "60",
        "ErrorAmountThreshold": "20",
        "DegradedTimeWindow": 10,
        "DegradedStatusCode": 503,
        "DegradedResponseContent": "Service is temporarily unavailable."
      }
    }
  }
}

还是保持服务端的 30% 错误率,再次测试可以看到 20 个 500 错误影响之后触发了熔断,返回了预设的 503 错误。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?status\=500:30

IP addresses distribution:
127.0.0.1:8080: 60
Code 200 : 40 (40.0 %)
Code 500 : 20 (20.0 %)
Code 503 : 40 (40.0 %)

错误响应占比触发熔断

接下来我们测试下错误响应占比的触发策略,将 ErrorAmountThreshold=20,修改为 ErrorRatioThreshold=0.25

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "CircuitBreaking": {
        "MinRequestAmount": 10,
        "StatTimeWindow": "60",
        "ErrorRatioThreshold": "0.25",
        "DegradedTimeWindow": 10,
        "DegradedStatusCode": 503,
        "DegradedResponseContent": "Service is temporarily unavailable."
      }
    }
  }
}

再次测试,可以看到 30 个请求中,有 6 个请求返回了 500 的响应,达到了 25% 的熔断阈值,触发了熔断。

fortio load -quiet -c 10 -n 100 -p 50,71,90,95,99 http://localhost:8080/echo\?status\=500:30

IP addresses distribution:
127.0.0.1:8080: 85
Code 200 : 24 (24.0 %)
Code 500 : 6 (6.0 %)
Code 503 : 70 (70.0 %)

6.2 - 限流

限流可以确保服务在高流量下仍然稳定,预防系统雪崩。本文档将介绍如何使用 FGW 的限流功能。

介绍

限流(Rate Limiting)是一个非常重要的功能,尤其在现代的分布式应用程序中。

限流是控制数据请求速率的方法,以确保在任何给定的时间内,传入的请求数量不会超过系统预定义的限额。通过这种方式,限流可以保护系统避免因过多的请求而过载,从而维护服务的质量和响应速度。

FGW 中限流的粒度与 熔断 等策略的服务维度少有不同,可以作用在域名和路由两个维度上,提供更加精细化的限流配置。

前置条件

这里后端服务建议使用工具 Fortio。Fortio 作为服务器运行时,可以在请求时指定延迟、响应状态,及其分布,非常方便测试熔断场景。同时 Forito 还可以作为负载生成器来使用。

启动服务器可以使用命令 fortio server -http-port 8082

配置说明

限流配置

下面列出了限流配置的字段,也可以参考文档 Ratelimit 配置

  • Backlog: 积压值是指在达到限流阈值时,系统允许排队的请求数量。这是一个重要的字段,尤其是在系统突然遭受大量请求时,这些请求可能会超过设置的限流阈值。积压值为系统提供了一定的缓冲,可以处理超出限流阈值的请求,但在积压值上限内。一旦达到积压上限,任何新的请求都会立即被拒绝,无需等待。可选字段。
  • Requests: 请求值是指在限流时间窗口内允许的访问次数。这是限流策略的核心参数,它确定了在特定的时间窗口内可以接受多少请求。设置此值的目的是为了确保在给定的时间窗口内,后端系统不会受到超过它能处理的请求。必须字段。
  • Burst: 爆发值表示在短时间内允许的最大请求次数。这是一个可选字段,它主要用于处理短时间的请求高峰。爆发值通常大于请求值,允许在短时间内接受的请求数量超过平均速率。非必须字段。
  • StatTimeWindow: 限流时间窗口(单位:秒)定义了统计请求数量的时间段。限流策略通常基于滑动窗口或固定窗口来实现。StatTimeWindow 定义了这个窗口的大小。比如,如果 StatTimeWindow 设置为 60 秒,并且 Requests 为 100,则意味着每 60 秒内最多只能有 100 个请求。必须字段。
  • ResponseStatusCode: 发生限流时,返回给客户端的 HTTP 状态码。这个状态码告诉客户端请求被拒绝的原因是因为达到了限流阈值。常见的状态码是 429(Too Many Requests),但可以根据需要自定义。必须字段。
  • ResponseHeadersToAdd: 发生限流时,要添加到响应中的 HTTP 头部信息。这可以用来通知客户端有关限流策略的更多信息。例如,可以添加一个 Retry-After 头来告诉客户端多久后可以重试。还可以提供关于当前限流策略或如何联系系统管理员的其他有用信息。可选字段。

示例

{
  "RateLimit": {
    "Local": {
      "Backlog": 1,
      "Requests": 100,
      "Burst": 200,
      "StatTimeWindow": 60,
      "ResponseStatusCode": 429,
      "ResponseHeadersToAdd": [
        {
          "Name": "RateLimit-Limit",
          "Value": "100"
        }
      ]
    }
  }
}

限流的维度

基于 Host 的限流(参考 域名配置文档),可以所有对某个 Host 的请求进行统计限流。

{
  "example.com": {
    "RouteType": "HTTP",
    "Matches": [],
    "RateLimit": {}
  }
}

基于路由的限流(参考路由 配置文档),可以对匹配某个路由的请求进行统计限流。

{
  "example.com": {
    "RouteType": "HTTP",
    "Matches": [
      {
        "Path": {},
        "BackendService": {},
        "RateLimit": {}
      }
    ]
  }
}

限流的作用范围

限流是为了控制到达系统或组件的请求速率。根据其作用范围和实施地点,限流策略可以分为本地限流全局限流

注:当前版本 v1.0.0 仅支持本地限流。

1. 本地限流 (Local Rate Limiting)

  • 定义:本地限流通常在单个实例或节点上实施,独立于其他节点。每个节点都根据自己的限流策略和规则来处理流入的请求。
  • 作用范围:只对单一实例或节点有效。
  • 优势
    • 延迟较低:由于限流判断在本地完成,无需与其他系统或节点通信,因此延迟很小。
    • 简单:通常很容易实施和管理。
  • 劣势
    • 非均匀限流:由于每个节点都是独立的,因此在高流量下,某些节点可能会被快速饱和,而其他节点可能还有未使用的容量。
    • 缺乏集中的控制和可见性:各个节点可能无法获知整体系统的状态。

2. 全局限流 (Global Rate Limiting)

  • 定义:全局限流通常在系统或服务的所有实例或节点之间共享。所有的请求,不论它们到达哪个节点,都会计入全局的限流计数器。
  • 作用范围:涵盖了整个系统或服务的所有实例。
  • 优势
    • 均匀限流:确保整个系统都遵循相同的限流规则,避免了单个节点的过载。
    • 集中的控制和可见性:提供一个集中的位置来配置、管理和监视限流,使得运维更为简便。
  • 劣势
    • 潜在的延迟:为了同步全局计数器,节点间可能需要进行通信,这可能会增加延迟。
    • 实施复杂性:全局限流策略需要更多的协调和同步机制,可能需要额外的组件(如共享的数据存储或分布式计数器)。

应用场景

  • 对于小型或低流量的系统,本地限流可能就足够了,因为它简单且开销小。
  • 对于大型、分布式或高流量的系统,全局限流可能更为适合,因为它可以更有效地控制和分配请求,尤其是在多个节点或实例间。

最终,选择本地限流还是全局限流取决于系统的规模、需求以及预期的流量模式。

配置

负载均衡器配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8082;然后根据 HTTP 插件链配置,将 http/throttle-domain.jshttp/throttle-route.js 插件配置在相应的配置。

顾名思义,这两个插件将分别处理基于 Host 的限流和基于路由的限流。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/throttle-domain.js",
      "http/throttle-route.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

基于域名的限流配置

在上面的负载均衡器配置中,使用了通配符 * 来匹配所有的域名,我们也可以将基于域名的限流赋给它。

下面增加限流的配置,时间窗口为 60s,最大请求数为 100,爆发值设置为 200。同时在被限流的响应头部加上限流值的提醒。

{
  "*": {
    "RouteType": "HTTP",
    "Matches": [
      {
        "Path": {
          "Type": "Prefix",
          "Path": "/"
        },
        "BackendService": {
          "backendService1": {
            "Weight": 100
          }
        }
      }
    ],
    "RateLimit": {
      "Local": {
        "Backlog": 1,
        "Requests": 100,
        "Burst": 200,
        "StatTimeWindow": 60,
        "ResponseStatusCode": 429,
        "ResponseHeadersToAdd": [
          {
            "Name": "RateLimit-Limit",
            "Value": "100"
          }
        ]
      }
    }
  }
}

使用 Fortio 作为负载生成器:以 10 个并发、200 的 QPS 发送 1000 个请求。

fortio load -quiet -c 10 -n 1000 -qps 200 http://localhost:8080/echo
# target 50% 0.00204893
# target 75% 0.00281346
# target 90% 0.00371774
# target 99% 0.00742857
# target 99.9% 3.00293

IP addresses distribution:
127.0.0.1:8080: 802
Code 200 : 200 (20.0 %)
Code 429 : 800 (80.0 %)

上面将限流的统计时间窗口设置为了 1 分钟,因此在执行完 fortio load 命令后,仍然会处在限流状态。此时,我们使用 curl 来请求,可以查看响

curl -i http://localhost:8080/echo
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
content-length: 0
connection: keep-alive

上面是基于 Host 的限流配置,如果将这段配置移到路由上也可以得到同样的结果。

基于路由的限流

我们在原有的路由基础上,添加一条到 /echo 的路由,并将原来域名限流的配置移到这条路由上。

{
  "*": {
    "RouteType": "HTTP",
    "Matches": [
      {
        "Path": {
          "Type": "Prefix",
          "Path": "/echo"
        },
        "BackendService": {
          "backendService1": {
            "Weight": 100
          }
        },
        "RateLimit": {
          "Local": {
            "Backlog": 1,
            "Requests": 100,
            "Burst": 200,
            "StatTimeWindow": 60,
            "ResponseStatusCode": 429,
            "ResponseHeadersToAdd": [
              {
                "Name": "RateLimit-Limit",
                "Value": "100"
              }
            ]
          }
        }
      },
      {
        "Path": {
          "Type": "Prefix",
          "Path": "/"
        },
        "BackendService": {
          "backendService1": {
                "Weight": 100
          }
        }
      }
    ]
  }
}

还是以同样条件进行负载测试,可以得到同样的测试结果。但是我们马上分别请求 /echo/,可以看到两种不同的结果。

curl -i http://localhost:8080/echo
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
content-length: 0
connection: keep-alive

curl -i http://localhost:8080/
HTTP/1.1 200 OK
date: Sun, 13 Aug 2023 11:34:36 GMT
content-length: 0
connection: keep-alive

只有访问设置了限流的路由的请求才会被限流,其他路由并不会收到影响。

6.3 - 重试

重试功能为架构提供了额外的稳定性和韧性。本文档将介绍 FGW 的重试功能

介绍

在复杂的网络环境中,服务间的通信可能会因为各种原因失败。为了提高系统的可用性和韧性,FGW 提供了重试功能,允许在满足特定条件时自动重新发送失败的请求。

FGW 的重试功能为架构提供了额外的稳定性和韧性。通过合理地配置重试策略,可以有效地提高服务的可用性,确保在面对网络波动和服务暂时性问题时,请求仍有机会得到正确的处理。

重试策略的考虑因素

在配置重试策略时,应考虑以下几点:

  1. 服务的敏感性:对于某些服务,如支付、订单创建等,应谨慎配置重试策略,避免重复请求导致的数据不一致。
  2. 服务的容量:过度的重试可能会导致目标服务的资源耗尽。确保目标服务有足够的容量来处理重试请求。
  3. 重试间隔:为了防止因频繁的重试导致的资源耗尽或服务雪崩,建议设置一个合理的重试间隔。

前置条件

这里后端服务建议使用工具 Fortio。Fortio 作为服务器运行时,可以在请求时指定延迟、响应状态,及其分布,非常方便测试熔断场景。同时 Forito 还可以作为负载生成器来使用。

启动服务器可以使用命令 fortio server -http-port 8082

配置说明

在 FGW 中重试是 服务 Service 维度的配置

  • RetryOn:重试条件字段,定义了触发重试机制的条件。此字段用于确定在哪些特定条件下应该执行重试。例如,可以设置为特定的 HTTP 状态码,如“5xx”,这意味着当后端服务返回任何 5xx 错误时,都将触发重试。必须字段。
  • PerTryTimeout:每次尝试的超时时间,定义了单次重试请求等待响应的最长时间。如果在这个时间内没有得到后端服务的响应,该次重试会被认为是失败的,进而触发下一次的重试(如果还有剩余的重试次数)。此字段在网络不稳定或后端服务响应延迟的场景中特别有用,确保不会因为一个长时间未响应的请求而浪费整个重试机会。可选字段。如果未设置,每次重试可能会采用全局或默认的超时时间。可选字段。
  • NumRetries:重试次数,表示在放弃前尝试重新发送请求的次数。此字段对于控制可能的重试风暴非常有用。设置适当的值可以确保不会对后端服务产生过多的负担,同时也能给请求一个重新成功的机会。可选字段,如果未设置,可能需要默认值或固定重试次数。
  • RetryBackoffBaseInterval:重试间隔,定义了两次重试之间的等待时间。此字段帮助确保在连续的重试之间有适当的冷却时间,从而避免在短时间内对后端服务产生大量请求。它可以基于固定时间间隔,或者根据特定算法(如指数退避)动态调整。可选字段。如果未设置,重试可能会立即进行,或使用默认的间隔。

示例

{
  "RetryPolicy": {
    "RetryOn": "5xx",
    "PerTryTimeout": 5,
    "NumRetries": 5,
    "RetryBackoffBaseInterval": 1
  }
}

配置

负载均衡器配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8082;然后根据 HTTP 插件链配置,重试的功能在 http/forward.js" 插件中实现。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

使用 Fortio 负载生成工具:1 个并发使用 qps 10 发送 100 个请求;同时设置服务端 30% 的情况下返回 503 错误,实际测试在 30% 上下 5% 范围内浮动。

fortio load -quiet -c 1 -n 100 -qps 10 http://localhost:8080/echo\?status\=503:30

IP addresses distribution:
127.0.0.1:8080: 33
Code 200 : 68 (68.0 %)
Code 503 : 32 (32.0 %)
All done 100 calls (plus 0 warmup) 3.524 ms avg, 10.0 qps

配置重试策略

设置服务 backendService1 的重试策略:针对 503 错误进行重试、重试 5 次。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "RetryPolicy": {
        "RetryOn": "503",
        "PerTryTimeout": 5,
        "NumRetries": 5,
        "RetryBackoffBaseInterval": 1
      }
    }
  }
}

设置完重试策略后,使用同样的负载进行测试。此时可以看到所有的请求均成功,但总耗时增长非常明显,实际的 qps 只有 2.1。因此重试多次,对客户端来说单个请求的耗时增加了。所以,需要在设置策略的时候充分考虑服务的容量,避免错误的设置导致重试风暴压垮后端服务。

fortio load -quiet -c 1 -n 100 -qps 10 http://localhost:8080/echo\?status\=503:30

IP addresses distribution:
127.0.0.1:8080: 10
Code 200 : 100 (100.0 %)
All done 100 calls (plus 0 warmup) 482.113 ms avg, 2.1 qps

假如我们修改一下生成负载的设置,使服务端同样概率下返回 500 的错误,而重试策略只针对 503 错误。

fortio load -quiet -c 1 -n 100 -qps 10 http://localhost:8080/echo\?status\=500:30

IP addresses distribution:
127.0.0.1:8080: 28
Code 200 : 72 (72.0 %)
Code 500 : 28 (28.0 %)
All done 100 calls (plus 0 warmup) 3.397 ms avg, 10.0 qps

6.4 - 健康检查

FGW 的健康检查功能可用于提升系统的高可用性。本文档将介绍如何使用 FGW 的健康检查功能。

介绍

FGW 的健康检查功能允许自动监控和验证后端服务的健康状况。这样,FGW 可以确保流量只被转发到健康的、能够正常处理请求的服务。此功能尤其在微服务或分布式系统中至关重要,它确保了高可用性和弹性。

前置条件

用于健康检查的两个服务,可以使用 Pipy 快速模拟。

#healthy
pipy -e "pipy().listen(8081).serveHTTP(new Message({status: 200},'Hello, world'))"
#unhealthy
pipy -e "pipy().listen(8082).serveHTTP(new Message({status: 500},''))"

配置说明

参考 健康检查配置文档

  • Interval:健康检查间隔,表示系统对后端服务进行健康检查的时间间隔。此字段决定了多久检查服务一次,以确保它们的健康状况。例如,如果设置为 10 秒,系统将每 10 秒检查一次上游服务的健康状态。这有助于在服务变得不可用时快速检测并作出反应。不设置此字段意味着不进行主动健康检查。可选字段。
  • MaxFails:最大失败次数,定义了在将上游服务标记为不可用之前,可以连续失败的健康检查次数。这是一个关键参数,因为它决定了系统在决定服务不健康之前的容忍度。例如,如果设置为 3,那么在第三次连续的健康检查失败后,该服务将被标记为不可用。必须字段。
  • FailTimeout:失败超时,定义了一个上游服务在被标记为不健康后,将被暂时禁用的时间长度。这意味着,即使服务再次变得可用,它也会在这段时间内被代理视为不可用。此字段在未设置主动健康检查的情况下尤为重要,因为它决定了一个服务在多长时间内被认为是不健康的。如果已设置主动健康检查,此参数会被忽略。可选字段。
  • Uri:健康检查 URI,用于 HTTP 健康检查的路径。当使用 HTTP 方法进行健康检查时,系统会向此 URI 发送请求以确定服务的健康状态。如果此字段未设置,则健康检查将使用 TCP 方法检查端口的健康状况,而不是使用 HTTP 方法。可选字段。
  • Matches:匹配条件,用于确定 HTTP 健康检查的成功或失败。此字段可以包含多个条件,例如期望的 HTTP 状态码、响应体内容等。当健康检查的响应满足这些条件时,服务将被认为是健康的。否则,它将被认为是不健康的。这允许更细粒度的控制,确保服务不仅是可达的,而且是正常运行的。可选字段,但如果使用 HTTP 健康检查,建议设置。
    • Type:匹配类型。这个字段定义了你想在健康检查响应中匹配的具体部分。它有三个有效的值:
      • status:匹配 HTTP 响应的状态码列表,如 [200,201,204]
      • body:匹配 HTTP 响应的主体内容。
      • headers:匹配 HTTP 响应的头部信息。此字段是必须的。
    • Value:期望的数据。此字段与 Type 字段一起使用,定义了预期的匹配值。例如,如果 Type 设置为 statusValue 可能会设置为 200,表示期望 HTTP 响应的状态码为 200。此字段是必须的。
    • Name:当 Typeheaders 时使用。它定义了你想在 HTTP 响应头部中匹配的具体字段名。例如,如果你想检查 Content-Type 头部的值,你会设置 NameContent-Type。此字段只在 Type 设置为 headers 时有效,其他情况下不应该设置。此字段是可选的。

示例

{
  "HealthCheck": {
    "Interval": 10,
    "MaxFails": 3,
    "FailTimeout": 30,
    "Uri": "/",
    "Matches": [
      {
        "Type": "status",
        "Value": [
          200,
          201
        ]
      }
    ]
  }
}

配置

负载均衡器配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081127.0.0.1:8082;重试功能实现在 common/health-check.js 中,该插件是自动引入,无需再插件链中显式定义。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

在配置健康检查,我们可以看一下负载均衡的结果,一半的请求下返回了 500。

curl --head http://localhost:8080/
HTTP/1.1 200 OK
content-length: 12
connection: keep-alive

curl --head http://localhost:8080/
HTTP/1.1 500 Internal Server Error
content-length: 0
connection: keep-alive

健康检查配置

接下来为服务 backendService1 添加健康检查的配置。为了便于快速查看效果,我们讲检查的间隔时间设置为 2 秒,失败次数 2 次之后就被认定为不健康的端点。同时,设置检查的依据为 200 响应码。

实际场景中,过于频繁的健康检查虽然可以快速隔离不健康的端点,但也势必会给后端服务造成压力。同样较低的最大失败数判定,也会快速隔离不健康的端点提升服务的可用性,但也会因为偶尔的网络抖动、服务突发负载过高等原因导致大量端点“被”下线,导致服务的不可用。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "HealthCheck": {
        "Interval": 2,
        "MaxFails": 2,
        "FailTimeout": 30,
        "Uri": "/",
        "Matches": [
          {
            "Type": "status",
            "Value": [
              200,
              201
            ]
          }
        ]
      }
    }
  }
}

配置生效后,再次进行测试可以看到不健康的端点已经被隔离。

curl --head http://localhost:8080/
HTTP/1.1 200 OK
content-length: 12
connection: keep-alive

curl --head http://localhost:8080/
HTTP/1.1 200 OK
content-length: 12
connection: keep-alive

6.5 - HTTP 头部控制

在 FGW 的配置中,HTTP 头部控制功能允许你微调传入和传出的请求和响应头部。本文档将介绍 FGW 的 HTTP 头部控制功能。

介绍

在日常的网络交互中,HTTP 头部扮演了非常重要的角色。它们可以传递关于请求或响应的各种信息,如身份验证、缓存控制、内容类型等。FGW 提供了 HTTP 头部控制功能,允许用户对传入和传出的请求和响应头部进行精确的控制,以满足各种安全、性能和业务需求。

这里的后端服务,可以使用 Pipy 来模拟,执行下面的命令即可。

pipy -e "pipy().listen(8081).serveHTTP(msg => new Message({ 'status': 200, headers: { 'content-type': 'application/json', 'server': 'Pipy', 'token': 'CONFIDENTIAL'} }, JSON.encode({ headers: msg.head })))"

模拟的服务会返回如下信息,在消息体中返回原始请求的所有头部信息。

curl -i http://localhost:8081/
HTTP/1.1 200 OK
content-type: application/json
server: Pipy
content-length: 200
connection: keep-alive

{"headers":{"protocol":"HTTP/1.1","headers":{"host":"localhost:8080","user-agent":"Apache-HttpClient/x.y.z","accept":"*/*","client-id":"client-1","connection":"keep-alive"},"method":"GET","path":"/"}}

配置说明

FGW 的 HTTP 头部控制功能分为 请求头部控制 RequestHeaderModifier 和响应头部控制 ResponseHeaderModifier,使用二者都可以对 HTTP 头部进行增加、删除、修改三种操作。

  • Set: 设置 HTTP header。此字段用于设定或修改 HTTP 头部的值。如果指定的头部已存在,则其值将被替换为新值;如果不存在,则会添加此头部。它是一个可选字段,其值是一个对象列表,其中每个对象都包含NameValue两个键。
  • Add: 增加 HTTP header。此字段用于添加新的 HTTP 头部,而不会影响到原有的头部。如果指定的头部已存在,新的头部将被添加,而不会替换已有的头部。它是一个可选字段,其值格式与 Set 字段相同。
  • Remove: 删除 HTTP header。此字段用于从 HTTP 请求或响应中删除指定的头部。这对于确保某些敏感或不必要的头部不被暴露给客户端或后端服务是很有用的。它是一个可选字段,其值是一个字符串列表,代表要删除的头部名称。

示例

{
  "Filters": [
    {
      "Type": "RequestHeaderModifier",
      "RequestHeaderModifier": {
        "Set": [
          {
            "Name": "host",
            "Value": "set-bar"
          }
        ],
        "Add": [
          {
            "Name": "accept",
            "Value": "xxx"
          }
        ],
        "Remove": [
          "user-agent",
          "my-header4"
        ]
      }
    },
    {
      "Type": "ResponseHeaderModifier",
      "ResponseHeaderModifier": {
        "Set": [
          {
            "Name": "dummy1",
            "Value": "set-bar"
          }
        ],
        "Add": [
          {
            "Name": "dummy2",
            "Value": "add,baz"
          }
        ],
        "Remove": [
          "dummy3",
          "my-header8"
        ]
      }
    }
  ]
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081;然后根据 HTTP 插件链配置,将 filter/header-modifier.js 插件配置在相应的配置。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "filter/header-modifier.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

通过负载均衡访问后端服务。

curl -i http://localhost:8080
HTTP/1.1 200 OK
content-type: application/json
server: Pipy
token: CONFIDENTIAL
content-length: 164
connection: keep-alive

{"headers":{"protocol":"HTTP/1.1","headers":{"host":"localhost:8080","user-agent":"curl/8.1.2","accept":"*/*","connection":"keep-alive"},"method":"GET","path":"/"}}

HTTP 头部控制配置

看到上面的结果后,我们希望对后端服务隐藏客户端的信息,比如将请求中的 user-agent 替换为 Apache-HttpClient/x.y.z;增加 client-id 信息 client-1;同时为了安全期间,避免服务端的 token 泄露,并模拟服务端的 server 信息为 Tomcat Server

{
  "Matches": [
    {
      "Path": {},
      "BackendService": {},
      "Filters": [
        {
          "Type": "RequestHeaderModifier",
          "RequestHeaderModifier": {
            "Set": [
              {
                "Name": "user-agent",
                "Value": "Apache-HttpClient/x.y.z"
              }
            ],
            "Add": [
              {
                "Name": "client-id",
                "Value": "client-1"
              }
            ]
          }
        },
        {
          "Type": "ResponseHeaderModifier",
          "ResponseHeaderModifier": {
            "Set": [
              {
                "Name": "server",
                "Value": "Tomcat Server"
              }
            ],
            "Remove": [
              "token"
            ]
          }
        }
      ]
    }
  ]
}

再次通过负载均衡访问后端服务,对应的头部信息已经做了修改。

curl -i http://localhost:8080/
HTTP/1.1 200 OK
content-type: application/json
server: Tomcat Server
content-length: 200
connection: keep-alive

{"headers":{"protocol":"HTTP/1.1","headers":{"host":"localhost:8080","user-agent":"Apache-HttpClient/x.y.z","accept":"*/*","client-id":"client-1","connection":"keep-alive"},"method":"GET","path":"/"}}

6.6 - 负载均衡算法

本文档将介绍如何为服务指定负载均衡算法

介绍

在微服务和 API 网关架构中,负载均衡是至关重要的,它确保每个服务实例都能平均地处理请求,同时也为高可用性和故障恢复提供了机制。FGW 提供了多种负载均衡算法,让可以根据业务需求和流量模式选择最适合的方法。

FGW 支持多种负载均衡算法,方便高效地分配流量,最大化资源利用率,提高服务的响应时间:

  • RoundRobinLoadBalancer:这是最常见的负载均衡算法,请求将按顺序分配给每个服务实例。如果不特别指定,FGW 默认使用此算法。
  • HashingLoadBalancer:根据请求的某些属性(如来源 IP 或请求头)计算哈希值,然后根据该哈希值将请求路由到特定的服务实例。这确保了相同的请求者或相同类型的请求总是被路由到同一服务实例。
  • LeastConnectionLoadBalancer:这种算法会考虑每个服务实例的当前工作负载(连接数),并将新的请求分配给当前负载最小的实例,从而确保更均匀的资源利用。

前置条件

用于会话保持的两个服务,可以使用 Pipy 快速模拟。

#svr-1
pipy -e "pipy().listen(8081).serveHTTP(new Message({status: 200},'Hello, world'))"
#svr-2
pipy -e "pipy().listen(8082).serveHTTP(new Message({status: 200},'Hi, world'))"

配置说明

负载均衡算法(Algorithm)是服务级别(Service)的配置,可以参考 服务配置文档

配置方法很简单,只需在服务的 Algorithm 字段指定所需的算法即可。

示例

{
  "Services": {
    "backendService1": {
      "Algorithm": "LeastWorkLoadBalancer",
      "Endpoints": {}
    }
  }
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081127.0.0.1:8082;然后根据 HTTP 插件链配置,重试的功能在 http/forward.js" 插件中实现。

为了便于测试,我们将两个端点的权重分别设为 10010

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 10
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

当不指定任何负载均衡算法时,默认使用 RoundRobinLoadBalancer。使用 fortio load -quiet -c 55 -t 60s -qps 20 http://localhost:8080 生成负载,并查看到两个后端的连接数。

可以看到在加权轮训算法下,代理会按照权重选择后端的示例建立连接。50:5 正好是两个后端实例的权重比 100:10

netstat -lant | grep EST | awk '{print $5}' | grep 127.0.0.1 | grep -e "8081\|8082" | sort | uniq -c
  50 127.0.0.1.8081
   5 127.0.0.1.8082

配置哈希一致性均衡算法

在服务配置上通过 Algorithm 指定使用 HashingLoadBalancer 哈希一致性负载均衡算法。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 10
        }
      },
      "Algorithm": "HashingLoadBalancer"
    }
  }
}

待配置生效后,多次请求都可以获得相同的结果。

curl http://localhost:8080
Hello, world
curl http://localhost:8080
Hello, world
curl http://localhost:8080
Hello, world

同样在使用 fortio load -quiet -c 55 -t 60s -qps 20 http://localhost:8080 使用 55 个连接发送请求时,所有连接都连到了同一个后端实例。

netstat -lant | grep EST | awk '{print $5}' | grep 127.0.0.1 | grep -e "8081\|8082" | sort | uniq -c
  55 127.0.0.1.8082

配置最小连接数均衡算法

将均衡算法改为 LeastWorkLoadBalancer

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 10
        }
      },
      "Algorithm": "LeastConnectionLoadBalancer"
    }
  }
}

同样在使用 fortio load -quiet -c 55 -t 60s -qps 20 http://localhost:8080 使用 55 个连接发送请求时,到后端实例的连接数接近均衡。

netstat -lant | grep EST | awk '{print $5}' | grep 127.0.0.1 | grep -e "8081\|8082" | sort | uniq -c
  28 127.0.0.1.8081
  27 127.0.0.1.8082

6.7 - 重定向

请求重定向是一种使客户端将其请求发送到另一个位置的方法。本文档将介绍 FGW 的请求重定向功能。

介绍

请求重定向是一种常见的网络应用功能,它使得服务器能够告诉客户端:“你请求的资源已经移动到了另一个位置,请到新的位置去获取”。

随着互联网应用的复杂性增加,请求重定向变得越来越常见。FGW 的请求重定向功能提供了一个灵活而高效的方式来控制和管理这些重定向,确保流量能够正确并高效地流向正确的目的地。

前置条件

用于重定向的两个服务,可以使用 Pipy 快速模拟。

#origin
pipy -e "pipy().listen(8081).serveHTTP(new Message({status: 200},'Hello, world'))"
#target
pipy -e "pipy().listen(8082).serveHTTP(new Message({status: 200},'Hello, there'))"

两个后端服务返回不同的结果。

curl http://localhost:8081
Hello, world
curl http://localhost:8082
Hello, there

配置说明

下面根据 请求重定向的参考文档,对各个字段就行说明。

  • scheme:协议描述,此字段定义了重定向目标的协议类型。参考值:httphttps。例如,若选择了 https,那么请求会被重定向到一个 HTTPS 协议的地址。
  • hostname:重定向到的域名,此字段指定了重定向目标的域名或主机名。例如,设置为 example.com,则请求会被重定向到 example.com
  • path:重定向到的路径,此字段是必须的,它定义了重定向目标的特定路径。例如,若设定路径为 /new-path,请求将被重定向到指定的 /new-path
  • statusCode:重定向返回的状态码,此字段是必须的,它定义了服务器返回的 HTTP 状态码,通常用来告知客户端重定向的类型。

示例

{
  "Filters": [
    {
      "Type": "RequestRedirect",
      "RequestRedirect": {
        "Scheme": "https",
        "Hostname": "",
        "Path": "/abc",
        "Port": 8443,
        "StatusCode": 301
      }
    }
  ]
}

配置

负载均衡器配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081 ,重定向功能实现在 filter/request-redirect.js 中,需要将其添加到插件链中。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "filter/request-redirect.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

在服务的端点列表中,我们配置了 127.0.0.1:8081,通过负载均衡访问会返回 Hello, world

curl http://localhost:8080
Hello, world

配置请求重定向

设置将请求重定向到 8082 的后端服务,将 statusCode 设置为 301

{
  "Matches": [
    {
      "Path": {},
      "BackendService": {},
      "Filters": [
        {
          "Type": "RequestRedirect",
          "RequestRedirect": {
            "Scheme": "http",
            "Hostname": "127.0.0.1",
            "Path": "/",
            "Port": 8082,
            "StatusCode": 301
          }
        }
      ]
    }
  ]
}

配置生效后,再次请求。

curl -i http://localhost:8080
HTTP/1.1 301 Moved Permanently
Location: http://localhost:8082/
content-length: 0
connection: keep-alive

借助 -L 参数,实现自动重定向。

curl -L http://localhost:8080
Hello, there

6.8 - URL 重写

URL 重写功能为系统提供了更大的灵活性来适应后端服务的不断变化。本文档将介绍如何使用 FGW 的 URL 重写功能。

介绍

URL 重写功能为 FGW 用户提供了一种在流量进入目标服务之前修改请求 URL 的方法。这不仅提供了更大的灵活性来适应后端服务的变化,而且确保了应用的流畅迁移和 URL 的规范化。

URL 重写常被用在以下场景:

  • 版本迁移:当应用或网站进行版本迁移时,可以使用前缀匹配来将旧版本的路径前缀替换为新版本的路径前缀。
  • 目录结构更改:若应用或网站的目录结构发生变化,但仍需保持旧的 URL 可访问,可利用全路径匹配重写功能。
  • URL 规范化:保证 URL 符合特定规范或风格。

前置条件

后端服务可以使用 Pipy 来模拟一个返回正在请求的路径的服务。

pipy -e "pipy().listen(8081).serveHTTP(msg => new Message('You are requesting ' + msg.head.headers.host + msg.head.path))"

它会返回我们正在请求的路径。

curl http://localhost:8081/api/hi
You are requesting localhost:8081/api/hi
curl http://localhost:8081/user
You are requesting localhost:8081/user

配置说明

下面参考 URL重写文档 对配置字段进行说明。

  • Hostname:重写后请求的目标域名或主机名。此字段可用于改变请求的目标地址,将流量引导至一个完全不同的域名或子域名。值为任何有效的域名,如 example.comsub.example.com。可选字段,不配置时会继续使用原请求中 host

  • Path:对 path 路径的重写规则。

    • Type:URL 重写匹配规则,此字段是必须的,它定义了路径重写时所采用的匹配方式。参考值有:ReplacePrefixMatch 表示前缀匹配,和 ReplaceFullPath 表示全路径匹配。选择合适的匹配规则是重写功能的基础。
    • ReplacePrefixMatch:前缀匹配时的 path,当 Type 字段设置为 ReplacePrefixMatch 时,此字段必须配置。它定义了需要被替换的 URL 前缀。例如,如果设置为 /old-prefix,那么所有以 /old-prefix 开始的路径都会被替换。
    • ReplacePrefix:前缀匹配时替换成这个 path,当 Type 字段设置为 ReplacePrefixMatch 时,此字段可以配置,但不是必须的。它定义了用来替换的新前缀。默认值为 /。例如,如果 ReplacePrefixMatch 设置为 /old-prefix 并且 ReplacePrefix 设置为 /new-prefix,那么 /old-prefix/example 会被重写为 /new-prefix/example
    • ReplaceFullPath:全路径匹配时的 path,当 Type 字段设置为 ReplaceFullPath 时,此字段必须配置。它定义了需要完全匹配并替换的路径。例如,如果设置为 /old-path/example, 那么只有完全匹配到这个路径的请求会被重写。

示例

{
  "Filters": [
    {
      "Type": "HTTPURLRewriteFilter",
      "UrlRewrite": {
        "Hostname": "",
        "Path": {
          "Type": "ReplacePrefixMatch",
          "ReplacePrefixMatch": "/path-prefix"
        }
      }
    },
    {
      "Type": "HTTPURLRewriteFilter",
      "UrlRewrite": {
        "Hostname": "",
        "Path": {
          "Type": "ReplacePrefixMatch",
          "ReplacePrefixMatch": "/path-prefix",
          "ReplacePrefix": "/new-path-prefix"
        }
      }
    },
    {
      "Type": "HTTPURLRewriteFilter",
      "UrlRewrite": {
        "Hostname": "",
        "Path": {
          "Type": "ReplaceFullPath",
          "ReplaceFullPath": "/path-prefix"
        }
      }
    }
  ]
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081;同时将该后端服务的路由路径设置为 /sample;最后根据 HTTP 插件链配置,将 filter/url-rewrite.js 插件配置在相应的配置。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/sample"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "filter/url-rewrite.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

测试服务的访问,/sample 的前缀会被带到后端服务。

curl http://localhost:8080/sample/api/hi
You are requesting localhost:8080/sample/api/hi

前缀重写配置

假如前缀 /sample 并不是后端服务的合法路径,负载均衡器在转发请求的时候需要将该前缀去掉。我们使用 ReplacePrefixMatch 类型的 URL 重写规则,将 /sample 作为前缀匹配请求,然后将该前缀替换为 /release

{
  "Matches": [
    {
      "Path": {},
      "BackendService": {},
      "Filters": [
        {
          "Type": "URLRewrite",
          "UrlRewrite": {
            "Path": {
              "Type": "ReplacePrefixMatch",
              "ReplacePrefixMatch": "/release"
            }
          }
        }
      ]
    }
  ]
}

还是使用之前的地址发送请求,这次后端服务接收到的请求路径为 /release/api/hi,前缀被替换。

curl http://localhost:8080/sample/api/hi
You are requesting localhost:8080/release/api/hi

如果想将其重写为 /api/hi,也就是去掉前缀场景,可以将 ReplacePrefixMatch 设置为 ""(空字符串)。

全路径重写配置

有些情况下如果服务路径已经不再使用,但出于安全和合规的原因,不希望外部用户访问它,可以使用全路径重写将其重定向到一个通用的 404 页面或其他安全页面。

添加 HTTPURLRewriteFilter 全路径重写规则,将访问服务的所有请求路径都修改为目标路径 /404

{
  "Matches": [
    {
      "Path": {},
      "BackendService": {},
      "Filters": [
        {
          "Type": "URLRewrite",
          "UrlRewrite": {
            "Path": {
              "Type": "ReplaceFullPath",
              "ReplaceFullPath": "/404"
            }
          }
        }
      ]
    }
  ]
}

此时再请求,就会去到 /404 地址。

curl http://localhost:8080/sample/api/hi
You are requesting localhost:8080/404

重写主机名

在上面的例子中,后端服务打印的主机名都是 localhost:8080 也就是代理的地址。有时候,我们希望对上游服务隐藏代理的地址,这时候我们就需要重写主机名。

这里就不需要重写路径了,而是需要 Hostname 字段。在下面的代码片段中,我们将主机名改写为 example.com

{
  "Matches": [
    {
      "Path": {},
      "BackendService": {},
      "Filters": [
        {
          "Type": "URLRewrite",
          "UrlRewrite": {
            "Hostname": "example.com"
          }
        }
      ]
    }
  ]
}

应用配置后,我们还是发送原来的请求,可以看到后端服务拿到的主机名为 example.com

curl http://localhost:8080/sample/api/hi
You are requesting example.com/sample/api/hi

6.9 - 流量镜像

本文档将介绍如何在不影响生产流量的前提下,将网络流量的副本发送到另一个服务。

介绍

FGW 的流量镜像功能主要用于在不影响生产流量的前提下,将网络流量的副本发送到另一个服务。这项功能常用于故障排查、性能监控、数据分析和安全审计等场景。通过流量镜像,可以实现实时数据捕获和分析,而不会对现有的业务流程造成任何干扰。

前置条件

使用 Pipy 模拟两个后端服务,这两个服务分别监听 80818082 端口。

pipy -e "pipy().listen(8081).serveHTTP(msg=>(console.log(msg.head),msg))"
pipy -e "pipy().listen(8082).serveHTTP(msg=>(console.log(msg.head),msg))"

当请求后端服务时,其会将请求的头部信息打印出来。

#request
curl localhost:8081/query
#log
2023-11-29 15:42:58.803 [INF] { protocol: "HTTP/1.1", headers: { "host": "localhost:8081", "user-agent": "curl/8.1.2", "accept": "*/*" }, method: "GET", scheme: undefined, authority: undefined, path: "/query" }

配置说明

如同在 策略概览 中提到的,流量镜像策略可以应用在服务/路由粒度。它的配置只有一个字段:

  • BackendService:指定请求应该镜像到哪个后端服务。

示例

{
  "Filters": [
    {
      "Type": "RequestMirror",
      "BackendService": "backendService2"
    }
  ]
}

配置

基础配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081。然后根据 HTTP 插件链配置,将 filter/request-mirror.js 插件配置在相应的配置。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "filter/request-mirror.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

通过 FGW 访问后端服务 curl localhost:8080/query,可以看到 8081 的后端服务会打印请求的头部信息。

2023-11-29 16:15:15.306 [INF] { protocol: "HTTP/1.1", headers: { "host": "127.0.0.1:8081", "user-agent": "curl/8.1.2", "accept": "*/*", "x-forwarded-for": "127.0.0.1" }, method: "GET", scheme: undefined, authority: undefined, path: "/query" }

路由粒度的流量镜像

修改路由的配置,在路由上添加 RequestMirror 配置,将流量镜像到后端服务 backendService2

{
  "Matches": [
    {
      "Path": {
        "Type": "Prefix",
        "Path": "/"
      },
      "BackendService": {
        "backendService1": {
          "Weight": 100
        }
      },
      "Filters": [
        {
          "Type": "RequestMirror",
          "BackendService": "backendService2"
        }
      ]
    }
  ]
}

同时,配置后端服务 backendService2 的端点。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    },
    "backendService2": {
      "Endpoints": {
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  }
}

此时再次发送请求,会发现后端服务 8082 也会同样打印出请求的信息。

2023-11-29 16:48:28.153 [INF] { protocol: "HTTP/1.1", headers: { "host": "127.0.0.1:8082", "user-agent": "curl/8.1.2", "accept": "*/*", "x-forwarded-for": "127.0.0.1" }, method: "GET", scheme: undefined, authority: undefined, path: "/query" }

服务粒度的流量镜像

在前面的配置基础上调整:将路由负载均衡到后端服务 80818082,但是在选择 8081 作为后端服务时,将请求镜像到 8082

{
  "Matches": [
    {
      "Path": {
        "Type": "Prefix",
        "Path": "/"
      },
      "BackendService": {
        "backendService1": {
          "Weight": 100,
          "Filters": [
            {
              "Type": "RequestMirror",
              "BackendService": "backendService2"
            }
          ]
        },
        "backendService2": {
          "Weight": 100
        }
      }
    }
  ]
}

在测试的时候,还是同样的请求会被均衡到两个后端服务,但 `8081` 的请求会被镜像到 `8082`,也就是说 `8082` 将会收到所有的请求。

6.10 - 故障注入

本篇文档将介绍如何在网关层面注入特定的故障来测试系统的行为和稳定性。

介绍

网关的故障注入功能允许在网络层面引入特定故障,如延迟和中断,以测试和评估应用在不同故障情况下的行为和稳定性。这个功能特别适合进行弹性和容错能力的测试。

这个功能特别适合进行弹性和容错能力的测试。

功能特点

  • 延迟注入:模拟网络延迟,观察应用对延迟的响应。
  • 错误注入:模拟服务错误响应,测试系统的恢复和容错机制。
  • 可配置的故障比例:可以设定故障注入的覆盖范围,例如只对一定比例的请求注入故障。

前置条件

后端服务可以使用 Pipy 来模拟一个返回正在请求的路径的服务。

pipy -e "pipy().listen(8081).serveHTTP(msg => new Message('You are requesting ' + msg.head.headers.host + msg.head.path))"

它会返回我们正在请求的路径。

curl http://localhost:8081/user
You are requesting localhost:8081/user

配置说明

FGW 提供了三种粒度的故障注入:网关全局域名路由

Fault: 用于配置故障注入策略。

  • Delay: 配置响应延时的参数。
    • Percent: 对请求注入延时的百分比,决定了有多少比例的请求会被注入延时。
    • Fixed: 指定固定的延时时间(如果设置),单位由 Unit 参数确定。
    • Range: 提供延时时间的随机范围,如 “0-100”,表示延时时间在0到100毫秒之间随机变动。
    • Unit: 延时时间的单位,支持 “ms”、“s”、“m” ,默认为毫秒(ms)。
  • Abort: 配置故障响应码的参数。
    • Percent: 注入特定错误响应的请求比例。
    • Status: 指定要注入的HTTP或GRPC错误状态码,如503表示服务不可用。
    • Message: 可选,提供错误响应的提示信息。

示例

在这个配置示例中,30% 的请求将遇到 50 至 300 毫秒之间的随机延迟,5% 的请求将收到带有 “Error injected” 消息的 503 错误响应。

{
  "Fault": {
    "Delay": {
      "Percent": 30,
      "Fixed": 2000,
      "Range": "50-300",
      "Unit": "ms"
    },
    "Abort": {
      "Percent": 5,
      "Status": 503,
      "Message": "Error injected"
    }
  }
}

配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081。然后根据 HTTP 插件链配置,将 http/fault-injection.js 插件配置在相应的配置。

{
  "Configs": {
    "DefaultPassthroughUpstreamPort": 443,
    "EnableDebug": true
  },
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/fault-injection.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

路由粒度的故障注入

/user 路径增加一条路由,并在该路由上设置故障注入:50% 的概率下返回 503 的状态码,以及 Error injected 响应。

{
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/user"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            },
            "Fault": {
              "Abort": {
                "Percent": 50,
                "Status": 503,
                "Message": "Error injected"
              }
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  }
}

多次请求 /user 50% 的情况下会遇到 503 的错误,而请求其他路径正常返回。

curl -i localhost:8080/user

HTTP/1.1 503 Service Unavailable
content-length: 14
connection: keep-alive

Error injected

curl -i localhost:8080/user

HTTP/1.1 200 OK
content-length: 38
connection: keep-alive

You are requesting 127.0.0.1:8081/user

域名粒度的故障注入

删除路由上的故障注入配置。

接下来,我们在域名 * 上配置故障注入:100% 的情况下加入 2000ms 的延迟。

{
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ],
        "Fault": {
          "Delay": {
            "Percent": 100,
            "Fixed": 2000,
            "Unit": "ms"
          }
        }
      }
    }
  }
}

此时测试可以发现响应的耗时都在 2s 以上。

time curl localhost:8080/user
You are requesting 127.0.0.1:8081/usercurl localhost:8080/user  0.00s user 0.01s system 0% cpu 2.032 total

网关粒度的全局配置

删除域名上的故障注入配置。

添加全局配置,设置 100% 的情况下加入 1000ms 的延迟以及返回 401 Unauthorized! 响应。

{
  "Configs": {
    "DefaultPassthroughUpstreamPort": 443,
    "EnableDebug": true,
    "Fault": {
      "Delay": {
        "Percent": 100,
        "Fixed": 1000,
        "Unit": "ms"
      },
      "Abort": {
        "Percent": 100,
        "Status": 401,
        "Message": "Unauthorized!\n"
      }
    }
  }
}

测试。

time curl -i localhost:8080/user
HTTP/1.1 401 Unauthorized
content-length: 14
connection: keep-alive

Unauthorized!
curl -i localhost:8080/user  0.01s user 0.01s system 1% cpu 1.028 total

6.11 - 会话保持

会话保持功能允许用户的连续请求在一段时间内被定向到同一台后端服务器。本文档将介绍如何使用 FGW 的会话保持功能。

介绍

会话保持功能允许用户的连续请求在一段时间内被定向到同一台后端服务器。它通常在需要连续交互或状态跟踪的场景中被使用,例如在线购物车、用户登录状态或多步骤的事务处理。

前置条件

用于会话保持的两个服务,可以使用 Pipy 快速模拟。

#svr-1
pipy -e "pipy().listen(8081).serveHTTP(new Message({status: 200},'Hello, world'))"
#svr-2
pipy -e "pipy().listen(8082).serveHTTP(new Message({status: 503},'Service unavailable'))"

两个后端服务返回不同的状态码,用于区分会话保持是否生效。

curl -i http://localhost:8081
HTTP/1.1 200 OK
content-length: 12
connection: keep-alive

Hello, world

curl -i http://localhost:8082
HTTP/1.1 503 Service Unavailable
content-length: 19
connection: keep-alive

Service unavailable

配置说明

会话保持属于服务粒度的策略配置,可以参考 服务配置文档

  • StickyCookieName:使用 cookie 保持会话的负载均衡时的 cookie 名称。这个字段是非必须的,但当启用基于 cookie 的会话保持时,它定义了存储后端服务器信息的 cookie 的名称,例如 _srv_id。这意味着当用户首次访问应用时,一个名为 _srv_id 的 cookie 会被设置,其值通常对应于某个后端服务器。当该用户再次访问时,此 cookie 会确保他们的请求被路由到与之前相同的服务器。
  • StickyCookieExpires:使用 cookie 保持会话时,cookie 的有效期。这定义了 cookie 的存活时间,即多长时间内用户的连续请求会被定向到同一台后端服务器。参考值为 3600,意味着该 cookie 在一小时后过期。在此期间,所有来自同一用户的请求都会被路由到他们首次访问时的那台服务器。一旦 cookie 过期,下一次的请求可能会被路由到任何后端服务器,同时会收到一个新的 cookie。

示例

{
  "Services": {
    "backendService1": {
      "StickyCookieName": "_srv_id",
      "StickyCookieExpires": 3600,
      "Endpoints": {}
    }
  }
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081127.0.0.1:8082;然后根据 HTTP 插件链配置,重试的功能在 http/forward.js" 插件中实现。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

使用同一个 cookie 多次请求会被负载均衡到不同的后端服务。

curl -b "_srv_id=client-1" localhost:8080
Service unavailable
curl -b "_srv_id=client-1" localhost:8080
Hello, world
curl -b "_srv_id=client-1" localhost:8080
Service unavailable

会话保持配置

添加会话保持的配置,使用 _srv_id 作为 cookie 名。

{
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        },
        "127.0.0.1:8082": {
          "Weight": 100
        }
      },
      "StickyCookieName": "_srv_id",
      "StickyCookieExpires":  3600
    }
  }
}

配置生效后,通过负载均衡器访问后端服务,负载均衡器会在响应的头部添加 cookie。

curl -i localhost:8080
HTTP/1.1 200 OK
set-cookie: _srv_id=7376698948304402; path=/; expires=Mon, 14 Aug 2023 16:14:33 GMT; max-age=3600
content-length: 12
connection: keep-alive

Hello, world

再次访问,但这次带上前面返回的 cookie。可以看到,每次都能获取到同样的结果,说明同一个会话(cookie 值相同)下,所有的请求都由相同的后端端点处理。

curl -b "_srv_id=7376698948304402" localhost:8080
Hello, world
curl -b "_srv_id=7376698948304402" localhost:8080
Hello, world
curl -b "_srv_id=7376698948304402" localhost:8080
Hello, world

6.12 - 黑白名单

本文档介绍如何使用 FGW 的黑白名单功能对访问来源进行控制

介绍

在微服务和 API 网关中,黑白名单功能是一种常用的安全机制,它可以根据指定的 IP 地址或 IP 地址范围来控制对服务或路由的访问。通过 FGW 的黑白名单功能,可以轻松地对 IP 进行访问控制,提高服务的安全性。

使用黑白名单,可以精确地控制哪些 IP 可以访问的服务,以及哪些 IP 应被禁止。这种功能在对外公开的 API 或面向特定客户或合作伙伴的服务中特别有用。

  • 黑名单:一旦 IP 地址被列入黑名单,来自这些 IP 地址的请求将被 FGW 拒绝。这通常用于封锁可疑的流量或已知的恶意 IP 地址。
  • 白名单:仅允许列在白名单中的 IP 地址访问。不在白名单上的所有 IP 都将被 FGW 拒绝。这通常用于确保只有特定的合作伙伴或内部 IP 可以访问特定的服务或路由。

黑白名单功能支持多种粒度的控制:端口、域名(服务)以及路由,通过在不同级别配置黑白名单来可以完成细粒度地灵活控制,可根据使用场景灵活选择。

  • 端口级别的控制在建立连接时就会进行检查,不被允许的 IP 会直接拒绝连接,效率会更好。
  • 服务和路由级别的控制粒度更小更灵活,但是需要在 HTTP 协议解码之后才可以进行检查,相比端口级别的黑白名单效率相对较低。

前置条件

这里的后端服务,可以使用 Docker 运行一个 httpbin 服务。

docker run --rm -d --name httpbin -p 8081:80 kennethreitz/httpbin

这个服务提供多个端点,比如 /ip/headers

curl -i http://localhost:8081/status/200
HTTP/1.1 200 OK
Server: gunicorn/19.9.0
Date: Thu, 31 Aug 2023 03:22:37 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Length: 0

curl http://localhost:8081/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "localhost:8081",
    "User-Agent": "curl/8.1.2"
  }
}

配置说明

访问控制列表(AccessControlLists)的配置字段:

  • blacklist:列出要阻止访问的 IP 地址或子网。例如, ["1.1.1.1", "2.2.2.0/24"]。如果指定了该配置,FGW 将拒绝列出的 IP 地址或范围内的所有请求。
  • whitelist:列出允许访问的 IP 地址或子网。例如, ["1.1.1.1", "2.2.2.0/24"]。如果指定了该配置,FGW 仅允许列出的 IP 地址或范围内的请求访问。
  • enableXFF:是否检查请求头部 x-forwarded-for 中的 IP 地址(逗号分隔),默认不检查。
  • status:拒绝访问后返回的响应状态码,不指定时默认为 403,可以用于定制响应。
  • message:拒绝访问后返回的响应内容,不指定时默认为空字符,可以用于定制响应。

示例配置

{
  "AccessControlLists": {
    "enableXFF": true,
    "status": "403",
    "message": "IP Forbidden",
    "blacklist": [
      "1.1.1.1",
      "2.2.2.0/24"
    ],
    "whitelist": [
      "3.3.3.3",
      "4.4.4.0/24"
    ]
  }
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,修改服务的端点列表为我们的后端服务地址 127.0.0.1:8081;然后根据 HTTP 插件链配置,将 common/access-control.jshttp/access-control-domain.jshttp/access-control-route.js 插件配置在相应的配置。

这里我们会多加入一个监听端口 8079,与端口 8080 使用同样的负载均衡配置。当我们做端口级别的黑白名单时,方便进行进行对比验证。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8079
    },
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8079,8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/ip"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  },
  "Services": {
    "backendService1": {
      "Endpoints": {
        "127.0.0.1:8081": {
          "Weight": 100
        }
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/access-control.js",
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/access-control-domain.js",
      "http/access-control-route.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

通过负载均衡访问后端服务。

curl http://localhost:8080/ip
{
  "origin": "192.168.215.1"
}

curl http://localhost:8080/headers
{
  "headers": {
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Host": "localhost:8080",
    "User-Agent": "curl/8.1.2"
  }
}

curl http://localhost:8079/headers
{
  "headers": {
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Host": "localhost:8079",
    "User-Agent": "curl/8.1.2"
  }
}

端口级别黑白名单配置

我们在监听端口 8080 上配置黑名单,因为请求发起方和代理都位于同一机器上,IP 地址使用 127.0.0.1

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080,
      "AccessControlLists": {
        "blacklist": [
          "127.0.0.1"
        ]
      }
    }
  ]
}

我们发起请求验证是否生效。

curl http://localhost:8080/headers
curl: (56) Recv failure: Connection reset by peer

curl http://localhost:8079/headers
{
  "headers": {
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Host": "localhost:8079",
    "User-Agent": "curl/8.1.2"
  }
}

域名级别黑白名单设置

记得移除上面配置的端口级别黑白名单配置。

接下来,我们将同样的配置挂在域名 * 下。

{
  "RouteRules": {
    "8079,8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/ip"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ],
        "AccessControlLists": {
          "blacklist": [
            "127.0.0.1"
          ]
        }
      }
    }
  }
}

此时,访问 80808079 都会收到 403 的响应。

curl -i http://localhost:8080/headers
HTTP/1.1 403 Forbidden
content-length: 0
connection: keep-alive

curl -i http://localhost:8079/headers
HTTP/1.1 403 Forbidden
content-length: 0
connection: keep-alive

路由级别黑白名单设置

记得移除上面配置的端口级别黑白名单配置。

有些时候,我们希望更细粒度的控制,比如限制某些 IP 对某个路由的访问。我们将同样的配置挂在 /ip 路由下。

{
  "RouteRules": {
    "8079,8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/ip"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            },
            "AccessControlLists": {
              "blacklist": [
                "127.0.0.1"
              ]
            }
          },
          {
            "Path": {
              "Type": "Prefix",
              "Path": "/"
            },
            "BackendService": {
              "backendService1": {
                "Weight": 100
              }
            }
          }
        ]
      }
    }
  }
}

此时,我们访问 /ip 路由被限制,而其他的路由如 /headers 则无限制。

curl http://localhost:8080/headers
{
  "headers": {
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Host": "localhost:8080",
    "User-Agent": "curl/8.1.2"
  }
}

curl -i http://localhost:8080/ip
HTTP/1.1 403 Forbidden
content-length: 0
connection: keep-alive

curl -i http://localhost:8079/ip
HTTP/1.1 403 Forbidden
content-length: 0
connection: keep-alive

7 - 静态 Web 服务

介绍

FGW 的静态 Web 服务功能主要指的是 FGW 能够直接为客户端提供静态资源,如 HTML、CSS、JavaScript 文件、图像等,而无需转发请求到后端应用服务器。这可以减少不必要的网络延迟,提高静态资源的加载速度,同时也减轻了后端服务器的负担。

可以在 FGW 的配置中指定一个本地目录作为静态资源的存储位置。当 FGW 接收到请求时,可以根据请求的 URL 在资源目录中搜索匹配的文件,可以作为一个静态服务器运行。

FGW 能够识别多种静态文件类型,并根据文件类型发送适当的 Content-Type 响应头。这确保了客户端(如浏览器)能够正确地解析和显示资源。

前置条件

配置说明

静态 Web 服务功能的配置非常简单,只需在 配置端口的路由规则 时,不指定后端服务,而是通过 ServerRoot 字段指定本地存放静态资源的目录即可。即从路由到后端服务,变为路由到本地资源。

  • ServerRoot 字段指定了本地资源目录,可以使用绝对路径或相对路径。使用相对路径时,本地资源应该存放到当前 代码库 的目录中。
  • Index 指定了默认文件的列表,当 URI 指向的是个目录时(以 / 结尾),会在对应的目录中查找默认文件。
  • TryFiles 在许多 Web 服务器配置中都有类似的功能,它的主要目的是为了定义一个资源查找的优先级和顺序。在给定的静态 Web 服务配置中,它表示在请求某个 URI 时,服务器应该尝试按照 TryFiles 指定的顺序来查找和提供文件。如下面示例中会优先按照 URI 来查找文件,然后查找 URI 对应的子目录 defaut 的默认文件(index.htmlindex.htm),最后仍然找不到会返回 404。

示例

{
  "RouteRules": {
    "8080": {
      "*": {
        "Matches": [
          {
            "ServerRoot": "/var/www/html",
            "Index": [
              "index.html",
              "index.htm"
            ],
            "TryFiles": [
              "$uri",
              "$uri/default/",
              "=404"
            ]
          }
        ]
      }
    }
  }
}

配置

负载均衡配置

我们借助在文档 HTTP 负载均衡 中的负载均衡器配置,去掉路径的路由匹配规则以及服务配置,改为静态服务配置。

在静态服务配置中,将本地资源目录设置为 /tmp,并设置默认文件列表。

{
  "Listeners": [
    {
      "Protocol": "HTTP",
      "Port": 8080
    }
  ],
  "RouteRules": {
    "8080": {
      "*": {
        "RouteType": "HTTP",
        "Matches": [
          {
            "ServerRoot": "/tmp",
            "Index": [
              "index.html",
              "index.htm"
            ]
          }
        ]
      }
    }
  },
  "Chains": {
    "HTTPRoute": [
      "common/consumer.js",
      "http/codec.js",
      "http/route.js",
      "http/service.js",
      "http/forward.js",
      "http/default.js"
    ]
  }
}

然后在 /tmp 目录中添加 index.html 文件。

cat > /tmp/index.html <<EOF
<!DOCTYPE html>  
<html>  
<body>  
<h1>hello</h1>  
<p>www1</p>  
</body>  
</html>
EOF

尝试访问 web 服务,可以返回配置的默认页面。

curl http://localhost:8080/
<!DOCTYPE html>
<html>
<body>
<h1>hello</h1>
<p>www1</p>
</body>
</html>

8 - 可观测性

8.1 - Logging

8.2 - Metrics

8.3 - Tracing