本教程是将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 不堪重负。
要运行本教程,您需要一台具有以下配置的机器:
为了充分利用实验室和教程,我们建议您在开始之前:
观看该实验室的 18 分钟视频摘要:
本教程使用了以下技术:
每个挑战的说明都包括用于配置应用程序的 YAML 文件的完整文本。 您也可以从我们的GitHub repo复制文本。 每个 YAML 文件的文本都附带有 GitHub 链接。
本教程包括三个挑战:
在此挑战中,您将部署一个 minikube 集群并安装 Podinfo作为示例应用程序和 API。然后,您将部署 NGINX Ingress Controller 、配置流量路由并测试 Ingress 配置。
创建一个minikube集群。 几秒钟后,会出现一条消息确认部署成功。
$ minikube start 🏄 完成!kubectl 现在配置为默认使用“minikube”集群和“default”命名空间
Podinfo是一个“用 Go 制作的 Web应用,展示了在 Kubernetes 中运行微服务的最佳实践”。 由于它的占用空间很小,我们将其用作示例应用程序和 API。
使用您选择的文本编辑器,创建一个名为1-apps.yaml的 YAML 文件,其中包含以下内容(或从 GitHub 复制)。 它定义了一个部署,其中包括:
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
选择器:
应用程序:前端
类型: 负载均衡器
部署应用程序和 API:
$ kubectl apply -f 1-apps.yaml deploy.apps/api 创建 service/api 创建 deploy.apps/frontend 创建 service/frontend 创建
确认Podinfo API和Podinfo Frontend的 pod 已成功部署,如STATUS
列中的值Running
所示。
$ kubectl get pods名称 就绪状态 重新启动时间 api-7574cf7568-c6tr6 1/1 运行 0 87s frontend-6688d86fc6-78qn7 1/1 运行 0 87s
安装 NGINX Ingress Controller 最快的方法是使用Helm 。
使用 Helm 在单独的命名空间 ( nginx ) 中安装NGINX Ingress Controller 。
创建命名空间:
$ kubectl 创建命名空间 nginx
将 NGINX 存储库添加到 Helm:
$ helm repo 添加 nginx-stable https://helm.nginx.com/stable
在你的集群中下载并安装 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
确认 NGINX Ingress Controller pod 已部署,如STATUS
列中的Running
值所示(为了便于阅读,输出分布在两行)。
$ kubectl get pods-namespace nginx NAME READY STATUS...main-nginx-ingress-779b74bb8b-d4qtc 1/1 正在运行...... 重新开始年龄... 0 92秒
api版本:networking.k8s.io/v1
种类: 入口
元数据:
名称:first
规范:
入口类名称:nginx
规则:
-主机:“example.com”
http:
路径:
-后端:
服务:
名称:前端
端口:
编号: 80
路径: /
路径类型: 前缀
- 主机:“api.example.com”
http:
路径:
- 后端:
服务:
名称:api
端口:
号码: 80
路径: /
路径类型: 前缀
$ kubectl apply -f 2-ingress.yaml ingress.networking.k8s.io/first created
为了确保您的 Ingress 配置按预期执行,请使用临时 pod 对其进行测试。 在集群中启动一次性BusyBox pod:
$ kubectl run -ti --rm=true busybox --image=busybox如果您没有看到命令提示符,请尝试按 Enter。/#
通过向 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" }
/ # wget --header="主机:example.com" --header="用户代理: Mozilla" -qO- main-nginx-ingress.nginx <!DOCTYPE html>
<html>
<head>
<title>frontend-596d5c9ff4-xkbdc</title>
# ...
在另一个终端中,在浏览器中打开 Podinfo。 podinfo 页面的问候语表明 Podinfo 正在运行。
$ minikube 服务 podinfo
恭喜! NGINX Ingress Controller 正在接收请求并将其转发到应用程序和 API。
在原始终端中,结束BusyBox会话:
/#退出$
在这个挑战中,您将安装一个开源负载生成工具 Locust ,并使用它来模拟流量激增,从而压倒 API 并导致应用程序崩溃。
使用您选择的文本编辑器,创建一个名为3-locust.yaml的 YAML 文件,其中包含以下内容(或从 GitHub 复制)。
ConfigMap
对象定义了一个名为locustfile.py的脚本,该脚本生成要发送到 pod 的请求,并带有正确的标头。 流量在应用程序和 API 之间分布并不均匀——请求偏向Podinfo API ,5 个请求中只有 1 个发往Podinfo Frontend 。
Deployment
和Service
对象定义了 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
类型: 负载均衡器
部署 Locust:
$ kubectl apply -f 3-locust.yaml configmap/locust-script 创建deployment.apps/locust 创建service/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秒
在浏览器中打开 Locust。
$ minikube 服务蝗虫
在字段中输入以下值:
单击开始群集按钮将流量发送到Podinfo API和Podinfo Frontend 。 观察 Locust图表和故障选项卡上的流量模式:
这是有问题的,因为使用该 API 的单个恶意行为者不仅可以关闭 API,还可以关闭 NGINX Ingress Controller 提供服务的所有应用程序!
在最后的挑战中,您将部署两个 NGINX Ingress Controller 以消除以前部署的限制,为每个控制器创建一个单独的命名空间,为Podinfo Frontend和Podinfo 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 网关”描述了一组可以通过不同类型的代理实现的用例——最常见的是 ADC 或负载均衡器和反向代理,以及越来越多的 Ingress 控制器或服务网格。 速率限制是部署 API 网关的常见用例。 详细了解 Kubernetes 中的 API 网关用例 我该如何选择? API 网关与…… 入口控制器与 服务网格 在我们的博客上。
在实现新的架构和速率限制之前,您必须删除以前的 NGINX Ingress Controller 配置。
$ kubectl delete -f 2-ingress.yaml ingress.networking.k8s.io “first” 已删除
为Podinfo Frontend创建一个名为nginx‑web 的命名空间:
$ kubectl 创建命名空间 nginx-web namespace/nginx-web created
为Podinfo API创建一个名为nginx‑api的命名空间:
$ kubectl 创建命名空间 nginx-api命名空间/nginx-api 创建
安装 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
为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
部署新的清单:
$ kubectl apply -f 4-ingress-web.yaml ingress.networking.k8s.io/frontend 创建
现在,重新配置 Locust 并验证:
执行以下步骤:
更改 Locust 脚本以便:
由于 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
类型: 负载均衡器
部署新的 Locust 配置。 输出确认脚本已更改,但其他元素保持不变。
$ kubectl apply -f 6-locust.yaml configmap/locust-script 配置deployment.apps/locust 不变 service/locust 不变
删除 Locust pod 以强制重新加载新的 ConfigMap。 为了识别要删除的 pod, kubectl
delete
pod
命令的参数表示为管道命令,从所有 pod 的列表中选择 Locust pod。
$ kubectl 删除 pod`kubectl 获取 pod | grep locust | awk {'print $1'}`
验证 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秒
返回 Locust 并更改这些字段中的参数:
单击开始群集按钮将流量发送到Podinfo API和Podinfo Frontend 。
在左上角的 Locust 标题栏中,观察STATUS列中用户数量的增加, FAILURES列中的值也随之增加。 但是,错误不再来自Podinfo Frontend ,而是来自Podinfo API,因为为 API 设置的速率限制意味着过多的请求被拒绝。 在右下角的跟踪中,你可以看到 NGINX 正在返回消息503
服务
暂时
不可用
,这是速率限制功能的一部分,可以自定义。 API 的速率受到限制,但 Web应用始终可用。 做得好!
在现实世界中,单靠速率限制不足以保护您的应用程序和 API 免受恶意行为者的攻击。 您需要实施以下一种或多种方法来保护 Kubernetes 应用程序、API 和基础设施:
我们将在 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 内容。”