博客 | NGINX

NGINX 教程: 使用速率限制保护 Kubernetes API

NGINX-F5-horiz-black-type-RGB 的一部分
Daniele Polencic 缩略图
丹尼尔·波伦契奇
2022 年 3 月 21 日发布

本教程是将2022 年 3 月微服务中的概念付诸实践的四个教程之一: Kubernetes 网络

是否想要有关使用 NGINX 实现更多 Kubernetes 网络用例的详细指导? 下载我们的免费电子书《使用 NGINX 管理 Kubernetes 流量》: 实用指南

您的组织刚刚在 Kubernetes 中启动了其第一个应用程序和 API。 您已被告知要预计高流量(并且已实施自动扩展以确保 NGINX Ingress Controller 可以快速路由流量),但有人担心该 API 可能成为恶意攻击的目标。 如果 API 收到大量 HTTP 请求(可能存在暴力密码猜测或 DDoS 攻击),那么 API 和应用程序都可能不堪重负甚至崩溃。

但你很幸运! 称为速率限制的流量控制技术是 API 网关用例,它将传入请求速率限制为真实用户的典型值。 您可以配置 NGINX Ingress Controller 来实施速率限制策略,从而防止应用程序和 API 因过多请求而无法承受。 做得好!

实验室和教程概述

本博客是 2022 年 3 月微服务第 2 单元实验室的配套文章——在 Kubernetes 中公开 API ,演示了如何将多个 NGINX Ingress Controller 与速率限制相结合,以防止应用程序和 API 不堪重负。

要运行本教程,您需要一台具有以下配置的机器:

  • 2 个或更多 CPU
  • 2 GB 可用内存
  • 20 GB 可用磁盘空间
  • 互联网连接
  • 容器或虚拟机管理器,例如 Docker、Hyperkit、Hyper-V、KVM、Parallels、Podman、VirtualBox 或 VMware Fusion/Workstation
  • minikube安装
  • 安装Helm
  • 允许您启动浏览器窗口的配置。 如果不可能的话,您需要弄清楚如何通过浏览器获取相关服务。

为了充分利用实验室和教程,我们建议您在开始之前:

本教程使用了以下技术:

每个挑战的说明都包括用于配置应用程序的 YAML 文件的完整文本。 您也可以从我们的GitHub repo复制文本。 每个 YAML 文件的文本都附带有 GitHub 链接。

本教程包括三个挑战:

  1. 部署集群、应用程序、API 和入口控制器
  2. 压垮你的应用程序和API
  3. 使用双入口控制器和速率限制来保存您的应用程序和 API

挑战1: 部署集群、应用程序、API 和入口控制器

在此挑战中,您将部署一个 minikube 集群安装 Podinfo作为示例应用程序和 API。然后,您将部署 NGINX Ingress Controller配置流量路由测试 Ingress 配置

创建 Minikube 集群

创建一个minikube集群。 几秒钟后,会出现一条消息确认部署成功。

$ minikube start 🏄 完成!kubectl 现在配置为默认使用“minikube”集群和“default”命名空间 

安装 Podinfo 应用程序和 Podinfo API

Podinfo是一个“用 Go 制作的 Web应用,展示了在 Kubernetes 中运行微服务的最佳实践”。 由于它的占用空间很小,我们将其用作示例应用程序和 API。

  1. 使用您选择的文本编辑器,创建一个名为1-apps.yaml的 YAML 文件,其中包含以下内容(或从 GitHub 复制)。 它定义了一个部署,其中包括:

    • 一个 Web 应用(我们称之为Podinfo Frontend ),用于呈现 HTML 页面
    • 返回 JSON 有效负载的 API( Podinfo API
    apiVersion:apps/v1
    种类: 部署 
    元数据: 
    名称:api 
    规范: 
    选择器: 
    匹配标签: 
    应用程序:api 
    模板: 
    元数据: 
    标签: 
    应用程序:api 
    规范: 
    容器: 
    -名称:api 
    图像:stefanprodan / podinfo 
    端口: 
    -容器端口: 9898 
    --- 
    api版本:v1 
    种类: 服务 
    元数据: 
    名称:api 
    规范: 
    端口: 
    - 端口: 80 
    目标端口: 9898 
    节点端口: 30001 
    选择器:
    应用程序:api 
    类型: 负载均衡器 
    --- 
    apiVersion:apps/v1 
    种类: 部署 
    元数据: 
    名称:前端 
    规格: 
    选择器: 
    匹配标签: 
    应用程序:前端 
    模板: 
    元数据: 
    标签: 
    应用程序:前端 
    规格: 
    容器: 
    -名称:前端 
    图像:stefanprodan / podinfo 
    端口: 
    -容器端口: 9898 
    --- 
    api版本:v1 
    种类: 服务 
    元数据: 
    名称:前端 
    规格: 
    端口: 
    - 端口: 80 
    目标端口: 9898 
    节点端口: 30002 
    选择器:
    应用程序:前端 
    类型: 负载均衡器
    
  2. 部署应用程序和 API:

    $ kubectl apply -f 1-apps.yaml deploy.apps/api 创建 service/api 创建 deploy.apps/frontend 创建 service/frontend 创建 
    
  3. 确认Podinfo APIPodinfo Frontend的 pod 已成功部署,如STATUS列中的值Running所示。

    $ kubectl get pods名称 就绪状态 重新启动时间 api-7574cf7568-c6tr6 1/1 运行 0 87s frontend-6688d86fc6-78qn7 1/1 运行 0 87s 
    
    
    

部署 NGINX Ingress 控制器

安装 NGINX Ingress Controller 最快的方法是使用Helm

使用 Helm 在单独的命名空间 ( nginx ) 中安装NGINX Ingress Controller

  1. 创建命名空间:

    $ kubectl 创建命名空间 nginx 
    
  2. 将 NGINX 存储库添加到 Helm:

    $ helm repo 添加 nginx-stable https://helm.nginx.com/stable 
    
  3. 在你的集群中下载并安装 NGINX Ingress Controller:

    $ helm install main nginx-stable/nginx-ingress \ --设置 controller.watchIngressWithoutClass=true \ --设置 controller.ingressClass=nginx \ --设置 controller.service.type=NodePort \ --设置 controller.service.httpPort.nodePort=30010 \ --设置 controller.enablePreviewPolicies=true \ --命名空间 nginx 
    
  4. 确认 NGINX Ingress Controller pod 已部署,如STATUS列中的Running值所示(为了便于阅读,输出分布在两行)。

    $ kubectl get pods-namespace nginx NAME READY STATUS...main-nginx-ingress-779b74bb8b-d4qtc 1/1 正在运行...... 重新开始年龄... 0 92秒 
    

将流量路由到您的应用

  1. 使用您选择的文本编辑器,创建一个名为2-ingress.yaml的 YAML 文件,其中包含以下内容(或从 GitHub 复制)。 它定义了将流量路由到应用程序和 API 所需的 Ingress 清单。
  2. api版本:networking.k8s.io/v1 
    种类: 入口 
    元数据: 
    名称:first 
    规范: 
    入口类名称:nginx 
    规则: 
    -主机:“example.com” 
    http: 
    路径: 
    -后端: 
    服务: 
    名称:前端 
    端口: 
    编号: 80 
    路径: / 
    路径类型: 前缀 
    - 主机:“api.example.com” 
    http: 
    路径: 
    - 后端: 
    服务: 
    名称:api 
    端口: 
    号码: 80 
    路径: / 
    路径类型: 前缀
    
  3. 部署 Ingress 资源:
  4. $ kubectl apply -f 2-ingress.yaml ingress.networking.k8s.io/first created 
    

测试入口配置

  1. 为了确保您的 Ingress 配置按预期执行,请使用临时 pod 对其进行测试。 在集群中启动一次性BusyBox pod:

    $ kubectl run -ti --rm=true busybox --image=busybox如果您没有看到命令提示符,请尝试按 Enter。/#
    
  2. 通过向 NGINX Ingress Controller pod 发出主机名为api.example.com 的请求来测试Podinfo API 。 显示的输出表明 API 正在接收流量。

    /# wget--header=“主机:api.example.com”-qO-main-nginx-ingress.nginx {“主机名”:“api-687fd448f8-t7hqk”,“版本”: “6.0.3”,“修订”:“”,“颜色”:“#34577c”,“徽标”:“https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif”,“消息”:“来自 podinfo v6.0.3 的问候”,“goos”:“linux”,“goarch”:“arm64”,“运行时”:“go1.16.9”,“num_goroutine”: “6”,“CPU 数量”: "4" } 
    
  3. 通过在同一个 BusyBox pod 中发出以下命令来测试Podinfo Frontend ,以模拟 Web 浏览器并检索网页。 显示的输出是网页开始的 HTML 代码。
    / # wget --header="主机:example.com" --header="用户代理: Mozilla" -qO- main-nginx-ingress.nginx <!DOCTYPE html> 
    <html> 
    <head> 
    <title>frontend-596d5c9ff4-xkbdc</title> 
    # ...
    
  4. 在另一个终端中,在浏览器中打开 Podinfo。 podinfo 页面的问候语表明 Podinfo 正在运行。

    $ minikube 服务 podinfo
    

    恭喜! NGINX Ingress Controller 正在接收请求并将其转发到应用程序和 API。

  5. 在原始终端中,结束BusyBox会话:

    /#退出$
    

挑战2: 压垮你的应用程序和API

在这个挑战中,您将安装一个开源负载生成工具 Locust ,并使用它来模拟流量激增,从而压倒 API 并导致应用程序崩溃。

安装 Locust

  1. 使用您选择的文本编辑器,创建一个名为3-locust.yaml的 YAML 文件,其中包含以下内容(或从 GitHub 复制)。

    ConfigMap对象定义了一个名为locustfile.py的脚本,该脚本生成要发送到 pod 的请求,并带有正确的标头。 流量在应用程序和 API 之间分布并不均匀——请求偏向Podinfo API ,5 个请求中只有 1 个发往Podinfo Frontend

    DeploymentService对象定义了 Locust pod。

    api版本:v1 
    种类: ConfigMap 
    元数据: 
    名称:locust-script 
    数据: 
    locustfile.py:|- 
    来自 locust 导入 HttpUser、task、between 
    
    类 QuickstartUser(HttpUser): 
    wait_time = between(0.7,1.3) 
    
    @task(1) 
    def visit_website(self): 
    使用 self.client.get(“/”,headers={“Host”:“example.com”,“User-Agent”: "Mozilla"}, timeout=0.2, catch_response=True) 作为响应: 
    如果 response.request_meta["response_time"] > 200: 
    response.failure("前端失败") 
    else: 
    response.success() 
    
    @task(5) 
    def visit_api(self): 
    使用 self.client.get("/", headers={"Host": "api.example.com"}, timeout=0.2) 作为响应: 
    如果 response.request_meta["response_time"] > 200: 
    response.failure("API 失败") 
    else: 
    response.success() 
    --- 
    apiVersion: apps/v1 
    kind: 部署 
    元数据: 
    名称:locust 
    规格: 
    选择器: 
    匹配标签: 
    应用程序:locust 
    模板: 
    元数据: 
    标签: 
    应用程序:locust 
    规格: 
    容器: 
    -名称:locust 
    图像:locustio/locust 
    端口: 
    -容器端口: 8089 
    volumeMounts: 
    -mountPath:/home/locust 
    名称:locust-script 
    volumes: 
    -名称:locust-script 
    configMap: 
    名称:locust-script 
    --- 
    apiVersion:v1 
    种类: 服务 
    元数据: 
    名称: locust 
    规格: 
    端口: 
    - 端口: 8089 
    目标端口: 8089 
    节点端口: 30015 
    选择器:
    应用程序: locust 
    类型: 负载均衡器
    
  2. 部署 Locust:

    $ kubectl apply -f 3-locust.yaml configmap/locust-script 创建deployment.apps/locust 创建service/locust 
    
  3. 验证 Locust 部署。 在以下示例输出中,验证命令在kubectl apply命令之后仅运行了几秒钟,因此安装仍在进行中,如STATUS字段中 Locust pod 的值ContainerCreating所示。 等到值为Running后再继续下一部分。 (为了易读,输出分为两行。)
    $ kubectl get pods NAME READY STATUS...api-7574cf7568-c6tr6 1/1 正在运行...frontend-6688d86fc6-78qn7 1/1 正在运行...locust-77c699c94d-hc76t 0/1 正在创建容器...... 重新开始年龄... 0 33米... 0 33米... 0 4秒
    

模拟流量激增

  1. 在浏览器中打开 Locust。

    $ minikube 服务蝗虫
    
  2. 在字段中输入以下值:

    • 用户数量1000
    • 生成率30
    • 主机http://main-nginx-ingress
  3. 单击开始群集按钮将流量发送到Podinfo APIPodinfo Frontend 。 观察 Locust图表故障选项卡上的流量模式:

    • 图表– 随着 API 请求数量的增加, Podinfo API响应时间变得越来越糟。
    • 故障——由于Podinfo APIPodinfo Frontend共享一个 Ingress 控制器,API 请求数量的增加很快会导致 Web 应用程序开始返回错误。

这是有问题的,因为使用该 API 的单个恶意行为者不仅可以关闭 API,还可以关闭 NGINX Ingress Controller 提供服务的所有应用程序!

挑战3: 使用双入口控制器和速率限制来保存您的应用程序和 API

在最后的挑战中,您将部署两个 NGINX Ingress Controller 以消除以前部署的限制,为每个控制器创建一个单独的命名空间,为Podinfo FrontendPodinfo API安装单独的 NGINX Ingress Controller 实例,重新配置 Locust 以将应用程序和 API 的流量引导到各自的 NGINX Ingress Controller,并验证速率限制是否有效。 首先,让我们看看如何解决架构问题。 在上一个挑战中,您的 API 请求使 NGINX Ingress Controller 不堪重负,这也影响了应用程序。发生这种情况的原因是单个 Ingress Controller 负责将流量路由到 Web 应用程序 ( Podinfo Frontend ) 和 API ( Podinfo API )。

为每个服务运行单独的 NGINX Ingress Controller pod 可防止您的应用受到过多 API 请求的影响。 这并不是每个用例都需要的,但在我们的模拟中很容易看到运行多个 NGINX Ingress Controller 的好处。

解决方案的第二部分是使用 NGINX Ingress Controller 作为 API 网关来实现速率限制,以防止Podinfo API不堪重负。

 

什么是速率限制?

速率限制限制了用户在给定时间段内可以发出的请求数量。 例如,为了减轻 DDoS 攻击,您可以使用速率限制将传入请求速率限制为真实用户的典型值。 使用 NGINX 实现速率限制时,提交过多请求的客户端将被重定向到错误页面,以免对 API 产生负面影响。请参阅NGINX Ingress Controller 文档了解其工作原理。

什么是 API 网关?

API 网关将来自客户端的 API 请求路由到适当的服务。 对这个简单定义的一个很大的误解是,API 网关是一项独特的技术。 它不是。 相反,“API 网关”描述了一组可以通过不同类型的代理实现的用例——最常见的是 ADC 或负载均衡器和反向代理,以及越来越多的 Ingress 控制器或服务网格。 速率限制是部署 API 网关的常见用例。 详细了解 Kubernetes 中的 API 网关用例 我该如何选择? API 网关与…… 入口控制器与 服务网格 在我们的博客上。

准备集群

在实现新的架构和速率限制之前,您必须删除以前的 NGINX Ingress Controller 配置。

  1. 删除 NGINX Ingress Controller 配置:

     

    $ kubectl delete -f 2-ingress.yaml ingress.networking.k8s.io “first” 已删除 
    
    
    
  2. Podinfo Frontend创建一个名为nginx‑web 的命名空间:

    $ kubectl 创建命名空间 nginx-web namespace/nginx-web created 
    
    
    
  3. Podinfo API创建一个名为nginx‑api的命名空间:

    $ kubectl 创建命名空间 nginx-api命名空间/nginx-api 创建 
    
    
    

为 Podinfo Frontend 安装 NGINX Ingress Controller

  1. 安装 NGINX Ingress Controller:

    $ helm install web nginx-stable/nginx-ingress --set controller.ingressClass=nginx-web \ --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30020 \ --namespace nginx-web
    
    
    
  2. Podinfo Frontend创建一个名为4-ingress-web.yaml的 Ingress 清单(或从 GitHub 复制)。

    api版本:k8s.nginx.org/v1 种类: 策略元数据:名称:速率限制策略规范:速率限制:速率: 10r/s 键:${binary_remote_addr}区域大小: 10M --- apiVersion: k8s.nginx.org/v1 种类: VirtualServer 元数据:名称:api-vs 规范:ingressClassName:nginx-api 主机:api.example.com 策略:-名称:rate-limit-policy 上游:-名称:api 服务:api 端口: 80 条路线: - 路径:/ 操作:传递:api
  3. 部署新的清单:

    $ kubectl apply -f 4-ingress-web.yaml ingress.networking.k8s.io/frontend 创建  
    
    
    

重新配置 Locust

现在,重新配置 Locust 并验证:

  • Podinfo API不会过载。
  • 无论向Podinfo API发送多少请求,都不会对Podinfo Frontend产生影响。

执行以下步骤:

  1. 更改 Locust 脚本以便:

    • Podinfo Frontend 的所有请求均定向到nginx‑web NGINX Ingress Controller,网址为http://web-nginx-ingress.nginx-web
    • Podinfo API 的所有请求都将定向到nginx‑api NGINX Ingress Controller,网址为http://api-nginx-ingress.nginx-api

    由于 Locust 在仪表板中仅支持单个 URL,因此使用 YAML 文件6-locust.yaml对 Python 脚本中的值进行硬编码,内容如下(或从 GitHub 复制)。 记下每个任务中的 URL。

    
    api版本:v1
    种类: ConfigMap 
    元数据: 
    名称:locust-script 
    数据: 
    locustfile.py:|- 
    来自 locust 导入 HttpUser、task、between 
    
    类 QuickstartUser(HttpUser): 
    wait_time = between(0.7,1.3) 
    
    @task(1) 
    def visit_website(self): 
    使用 self.client.get(“http://web-nginx-ingress.nginx-web/”,headers={“Host”:“example.com”,“User-Agent”: "Mozilla"}, timeout=0.2, catch_response=True) 作为响应: 
    如果 response.request_meta["response_time"] > 200: 
    response.failure("前端失败") 
    else: 
    response.success() 
    
    @task(5) 
    def visit_api(self): 
    使用 self.client.get("http://api-nginx-ingress.nginx-api/", headers={"Host": "api.example.com"}, timeout=0.2) 作为响应: 
    如果 response.request_meta["response_time"] > 200: 
    response.failure("API 失败") 
    else: 
    response.success() 
    --- 
    apiVersion: apps/v1 
    kind: 部署 
    元数据: 
    名称:locust 
    规格: 
    选择器: 
    匹配标签: 
    应用程序:locust 
    模板: 
    元数据: 
    标签: 
    应用程序:locust 
    规格: 
    容器: 
    -名称:locust 
    图像:locustio/locust 
    端口: 
    -容器端口: 8089 
    volumeMounts: 
    -mountPath:/home/locust 
    名称:locust-script 
    volumes: 
    -名称:locust-script 
    configMap: 
    名称:locust-script 
    --- 
    apiVersion:v1 
    种类: 服务 
    元数据: 
    名称: locust 
    规格: 
    端口: 
    - 端口: 8089 
    目标端口: 8089 
    节点端口: 30015 
    选择器:
    应用程序: locust 
    类型: 负载均衡器 
    
    
    
  2. 部署新的 Locust 配置。 输出确认脚本已更改,但其他元素保持不变。

    $ kubectl apply -f 6-locust.yaml configmap/locust-script 配置deployment.apps/locust 不变 service/locust 不变
    
    
    
  3. 删除 Locust pod 以强制重新加载新的 ConfigMap。 为了识别要删除的 pod, kubectl delete pod命令的参数表示为管道命令,从所有 pod 的列表中选择 Locust pod。

    $ kubectl 删除 pod`kubectl 获取 pod | grep locust | awk {'print $1'}` 
    
    
    
  4. 验证 Locust 是否已重新加载( AGE列中 Locust pod 的值只有几秒钟)。

    $ kubectl get pods NAME READY STATUS...api-7574cf7568-jrlvd 1/1 正在运行...frontend-6688d86fc6-vd856 1/1 正在运行...locust-77c699c94d-6chsg 0/1 正在运行...... 重新开始年龄... 0 9分57秒... 0 9分57秒... 0 6秒
    
    
    

验证速率限制

  1. 返回 Locust 并更改这些字段中的参数:

    • 用户数量400
    • 生成率10
    • 主机http://main-nginx-ingress
  2. 单击开始群集按钮将流量发送到Podinfo APIPodinfo Frontend

    在左上角的 Locust 标题栏中,观察STATUS列中用户数量的增加, FAILURES列中的值也随之增加。 但是,错误不再来自Podinfo Frontend ,而是来自Podinfo API,因为为 API 设置的速率限制意味着过多的请求被拒绝。 在右下角的跟踪中,你可以看到 NGINX 正在返回消息503服务暂时不可用,这是速率限制功能的一部分,可以自定义。 API 的速率受到限制,但 Web应用始终可用。 做得好!

下一步

在现实世界中,单靠速率限制不足以保护您的应用程序和 API 免受恶意行为者的攻击。 您需要实施以下一种或多种方法来保护 Kubernetes 应用程序、API 和基础设施:

  • 身份验证和授权
  • Web应用防火墙和 DDoS 保护
  • 端到端加密和零信任
  • 遵守行业法规

我们将在 2022 年 3 月微服务第 3 单元 – Kubernetes 中的微服务安全模式中介绍这些主题及更多内容。 要尝试使用 NGINX Plus 和 NGINX App Protect 的 NGINX Ingress Controller for Kubernetes,请立即开始30 天免费试用联系我们讨论您的用例。 要使用 NGINX 开源尝试 NGINX Ingress Controller,您可以获取发布源代码,或从DockerHub下载预构建的容器。


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”