本教程是将以下内容中的概念付诸实践的四篇教程之一:2022年3月微服务专题:Kubernetes网络:
- 通过自动扩缩容降低Kubernetes延迟
- 使用速率限制保护Kubernetes API(本文)
- 保护Kubernetes应用免受SQL注入攻击
- 通过金丝雀部署提高正常运行时间和韧性
想了解如何使用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 已安装
- 可启动浏览器窗口的配置。如果无法启动,则需要确定如何通过浏览器访问相关服务。
为充分利用实验和教程,建议在开始前:
- 观看概念概述直播录像
- 查看背景博客、网络研讨会和视频
- 观看这段18分钟的实验室内容摘要视频: 
本教程使用以下技术:
每项挑战的说明均包含用于配置应用的 YAML 文件全文。也可以从我们的GitHub 仓库复制这些文本。每个 YAML 文件的文本旁均提供 GitHub 链接。
本教程包含三项挑战:
挑战1:部署集群、应用、API和Ingress Controller
在此挑战中,您将部署minikube集群并安装Podinfo,将其用作示例应用和API。然后,您将部署NGINX Ingress Controller、配置流量路由并测试Ingress配置。
创建Minikube集群
创建一个minikube集群。几秒钟后,系统会显示一条消息,确认部署成功。
$ minikube start 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default 安装Podinfo应用和Podinfo API
Podinfo是一个“使用Go构建的Web应用,用于展示在Kubernetes中运行微服务的最佳实践”。由于占用资源少,我们将其用作示例应用和API。
- 使用您选择的文本编辑器创建一个名为1-apps.yaml的YAML文件,并添加以下内容(也可以从GitHub复制)。该文件定义了一个Deployment,其中包括:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: stefanprodan/podinfo
ports:
- containerPort: 9898
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
ports:
- port: 80
targetPort: 9898
nodePort: 30001
selector:
app: api
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: stefanprodan/podinfo
ports:
- containerPort: 9898
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
ports:
- port: 80
targetPort: 9898
nodePort: 30002
selector:
app: frontend
type: LoadBalancer- 一个用于呈现HTML页面的Web应用(我们称之为“Podinfo Frontend”)
- 一个返回JSON有效负载的API(Podinfo API)
- 部署应用和API:
$ kubectl apply -f 1-apps.yamldeployment.apps/api created
service/api created
deployment.apps/frontend created
service/frontend created - 确认Podinfo API和Podinfo Frontend的 Pod 已成功部署,具体可通过
Running列中的STATUS值确认。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
api-7574cf7568-c6tr6 1/1 Running 0 87s
frontend-6688d86fc6-78qn7 1/1 Running 0 87s
部署NGINX Ingress Controller
安装NGINX Ingress Controller最快捷的方式是使用Helm。
使用Helm将NGINX Ingress Controller安装到单独的命名空间(nginx)中。
- 创建命名空间:
$ kubectl create namespace nginx - 将NGINX仓库添加到Helm:
$ helm repo add nginx-stable https://helm.nginx.com/stable - 在集群中下载并安装NGINX Ingress Controller:
$ helm install main nginx-stable/nginx-ingress \ --set controller.watchIngressWithoutClass=true \ --set controller.ingressClass=nginx \ --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30010 \ --set controller.enablePreviewPolicies=true \ --namespace nginx - 确认NGINX Ingress Controller Pod已部署,判断依据是
Running值显示在STATUS列中(为便于阅读,输出分为两行显示)。
$ kubectl get pods -namespace nginx NAME READY STATUS ...
main-nginx-ingress-779b74bb8b-d4qtc 1/1 Running ...
... RESTARTS AGE
... 0 92s 将流量路由到您的应用
- 使用任意文本编辑器创建一个名为“2-ingress.yaml”的YAML文件,并填入以下内容(也可以从GitHub复制)。该文件定义了将流量路由到应用和API所需的Ingress清单。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: first
spec:
ingressClassName: nginx
rules:
- host: "example.com"
http:
paths:
- backend:
service:
name: frontend
port:
number: 80
path: /
pathType: Prefix
- host: "api.example.com"
http:
paths:
- backend:
service:
name: api
port:
number: 80
path: /
pathType: Prefix- 部署Ingress资源:
$ kubectl apply -f 2-ingress.yaml ingress.networking.k8s.io/first created 测试Ingress配置
- 为确保Ingress配置按预期运行,请使用临时Pod进行测试。在集群中启动一个用后即弃的BusyBox Pod:
$ kubectl run -ti --rm=true busybox --image=busybox If you don't see a command prompt, try pressing enter.
/ #- 测试Podinfo API,方法是向主机名为api.example.com的NGINX Ingress Controller Pod发送请求。显示的输出表明该API正在接收流量。
/ # wget --header="Host: api.example.com" -qO- main-nginx-ingress.nginx {
"hostname": "api-687fd448f8-t7hqk",
"version": "6.0.3",
"revision": "",
"color": "#34577c",
"logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
"message": "greetings from podinfo v6.0.3",
"goos": "linux",
"goarch": "arm64",
"runtime": "go1.16.9",
"num_goroutine": "6",
"num_cpu": "4"
} - 测试Podinfo Frontend:在同一BusyBox Pod中运行以下命令,模拟Web浏览器并获取网页。显示的输出是网页开头部分的HTML代码。
/ # wget --header="Host: example.com" --header="User-Agent: Mozilla" -qO- main-nginx-ingress.nginx <!DOCTYPE html>
<html>
<head>
<title>frontend-596d5c9ff4-xkbdc</title>
# ...- 在另一个终端中,通过浏览器打开Podinfo。显示“greetings from podinfo”页面即表示Podinfo正在运行。
$ minikube service podinfo- 恭喜!NGINX Ingress Controller正在接收请求,并将其转发到应用和API。

- 在原终端中,结束BusyBox会话:
/ # exit
$挑战 2:让应用和 API 不堪重负
在此挑战中,您将安装Locust。Locust是一款开源负载生成工具,您将使用它模拟流量激增,使API不堪重负并导致应用崩溃。
安装Locust
- 使用任意文本编辑器创建一个名为“3-locust.yaml”的YAML文件,并添加以下内容(也可以从GitHub复制)。
ConfigMap对象定义了一个名为“locustfile.py”的脚本。该脚本会生成发送到Pod的请求,并包含正确的标头。应用与API之间的流量分配并不均匀,请求主要发送到Podinfo API,每5个请求中只有1个发送到Podinfo Frontend。Deployment和Service对象用于定义Locust Pod。
apiVersion: v1
kind: ConfigMap
metadata:
name: locust-script
data:
locustfile.py: |-
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(0.7, 1.3)
@task(1)
def visit_website(self):
with self.client.get("/", headers={"Host": "example.com", "User-Agent": "Mozilla"}, timeout=0.2, catch_response=True) as response:
if response.request_meta["response_time"] > 200:
response.failure("Frontend failed")
else:
response.success()
@task(5)
def visit_api(self):
with self.client.get("/", headers={"Host": "api.example.com"}, timeout=0.2) as response:
if response.request_meta["response_time"] > 200:
response.failure("API failed")
else:
response.success()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: locust
spec:
selector:
matchLabels:
app: locust
template:
metadata:
labels:
app: locust
spec:
containers:
- name: locust
image: locustio/locust
ports:
- containerPort: 8089
volumeMounts:
- mountPath: /home/locust
name: locust-script
volumes:
- name: locust-script
configMap:
name: locust-script
---
apiVersion: v1
kind: Service
metadata:
name: locust
spec:
ports:
- port: 8089
targetPort: 8089
nodePort: 30015
selector:
app: locust
type: LoadBalancer- 部署Locust:
$ kubectl apply -f 3-locust.yaml configmap/locust-script created
deployment.apps/locust created
service/locust created - 验证Locust部署。在以下示例输出中,验证命令是在
kubectlapply命令执行后仅几秒运行的,因此安装仍在进行中。Locust Pod的STATUS字段值为ContainerCreating,表明安装尚未完成。请等待该值变为Running,再继续下一部分。(为便于阅读,输出分为两行显示。)
$ kubectl get pods
NAME READY STATUS ... api-7574cf7568-c6tr6 1/1 Running ...
frontend-6688d86fc6-78qn7 1/1 Running ... locust-77c699c94d-hc76t 0/1 ContainerCreating ...
... RESTARTS AGE
... 0 33m
... 0 33m
... 0 4s模拟流量激增
- 在浏览器中打开Locust。
$ minikube service locust-

- 在字段中输入以下值:
- 用户数:1000
- 生成速率:30
- 主机:http://main-nginx-ingress
- 点击“Start swarming”按钮,向Podinfo API和Podinfo Frontend发送流量。在Locust的“Charts”和“Failures”选项卡中观察流量模式:
- 图表:随着API请求数量增加,Podinfo API的响应时间会延长。
- 故障:由于Podinfo API和Podinfo Frontend共享一个Ingress控制器,API请求数量不断增加,很快导致Web应用开始返回错误。
这会带来严重问题,因为只需一个恶意行为者滥用API,就可能导致API以及NGINX Ingress Controller提供服务的所有应用全部中断!
挑战 3:使用双 Ingress Controller 和速率限制保护应用及 API
在最后一项挑战中,您将部署两个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的优势。

该解决方案的第二部分旨在防止Podinfo API因请求过多而不堪重负,具体做法是将NGINX Ingress Controller用作API网关来实施速率限制。
什么是速率限制?
速率限制可限制用户在给定时间段内发出的请求数量。例如,为缓解DDoS攻击,可以使用速率限制,将传入请求速率限制在真实用户的常见水平。使用NGINX实施速率限制后,发送过多请求的客户端会被重定向到错误页面,从而避免对API造成负面影响。有关其工作原理,请参阅NGINX Ingress Controller文档。
什么是API网关?
API网关将客户端的API请求路由到相应的服务。对于这个简单定义,一个常见的严重误解是将API网关视为一种独立的技术。事实并非如此。“API网关”描述的是一组用例,可通过不同类型的代理来实现,最常见的是ADC或负载均衡器和反向代理,而Ingress控制器或服务网格也日益得到采用。速率限制是部署API网关的常见用例。要详细了解Kubernetes中的API网关用例,请阅读我们的博客文章如何选择?API网关、Ingress控制器还是服务网格。
准备集群
在实施新架构和速率限制之前,必须删除原有的NGINX Ingress Controller配置。
- 删除NGINX Ingress Controller配置:
$ kubectl delete -f 2-ingress.yaml
ingress.networking.k8s.io "first" deleted
- 创建一个名为“nginx‑web”的命名空间,用于Podinfo Frontend:
$ kubectl create namespace nginx-web
namespace/nginx-web created
- 创建一个名为“nginx‑api”的命名空间,用于Podinfo API:
$ kubectl create namespace nginx-api
namespace/nginx-api created
为Podinfo前端安装NGINX Ingress Controller
- 安装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
- 创建一个名为“4-ingress-web.yaml”的Ingress清单,用于Podinfo Frontend(也可以从GitHub复制)。
apiVersion: k8s.nginx.org/v1 kind: Policy metadata: name: rate-limit-policy spec: rateLimit: rate: 10r/s key: ${binary_remote_addr} zoneSize: 10M --- apiVersion: k8s.nginx.org/v1 kind: VirtualServer metadata: name: api-vs spec: ingressClassName: nginx-api host: api.example.com policies: - name: rate-limit-policy upstreams: - name: api service: api port: 80 routes: - path: / action: pass: api - 部署新的清单:
$ kubectl apply -f 4-ingress-web.yaml
ingress.networking.k8s.io/frontend created
重新配置Locust
现在,重新配置Locust并验证以下事项:
- Podinfo API不会过载。
- 无论向Podinfo API发送多少请求,都不会影响Podinfo Frontend。
执行以下步骤:
- 按如下方式修改Locust脚本:由于Locust控制面板仅支持一个URL,请使用YAML文件6-locust.yaml,将该值硬编码到Python脚本中。文件内容如下(也可以从GitHub复制)。请注意每个
task中的URL。
apiVersion: v1
kind: ConfigMap
metadata:
name: locust-script
data:
locustfile.py: |-
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(0.7, 1.3)
@task(1)
def visit_website(self):
with self.client.get("http://web-nginx-ingress.nginx-web/", headers={"Host": "example.com", "User-Agent": "Mozilla"}, timeout=0.2, catch_response=True) as response:
if response.request_meta["response_time"] > 200:
response.failure("Frontend failed")
else:
response.success()
@task(5)
def visit_api(self):
with self.client.get("http://api-nginx-ingress.nginx-api/", headers={"Host": "api.example.com"}, timeout=0.2) as response:
if response.request_meta["response_time"] > 200:
response.failure("API failed")
else:
response.success()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: locust
spec:
selector:
matchLabels:
app: locust
template:
metadata:
labels:
app: locust
spec:
containers:
- name: locust
image: locustio/locust
ports:
- containerPort: 8089
volumeMounts:
- mountPath: /home/locust
name: locust-script
volumes:
- name: locust-script
configMap:
name: locust-script
---
apiVersion: v1
kind: Service
metadata:
name: locust
spec:
ports:
- port: 8089
targetPort: 8089
nodePort: 30015
selector:
app: locust
type: LoadBalancer
- 所有发往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配置。输出确认脚本已更改,但其他元素保持不变。
$ kubectl apply -f 6-locust.yaml
configmap/locust-script configured
deployment.apps/locust unchanged
service/locust unchanged
- 删除Locust Pod,以强制重新加载新的ConfigMap。为确定要删除的Pod,
kubectldeletepod命令的参数采用管道命令形式,从所有Pod的列表中选择Locust Pod。
$ kubectl delete pod `kubectl get pods | grep locust | awk {'print $1'}`
- 验证Locust是否已重新加载(Locust Pod在“
AGE”列中的值仅为几秒)。
$ kubectl get pods
NAME READY STATUS ... api-7574cf7568-jrlvd 1/1 Running ...
frontend-6688d86fc6-vd856 1/1 Running ... locust-77c699c94d-6chsg 0/1 Running ...
... RESTARTS AGE
... 0 9m57s
... 0 9m57s
... 0 6s
验证速率限制
- 返回Locust,并更改以下字段中的参数:
- 用户数:400
- 生成速率:10
- 主机:http://main-nginx-ingress
- 点击开始压测按钮,将流量发送到Podinfo API和Podinfo Frontend。在左上角的Locust标题栏中,可以看到,随着状态列中的用户数增加,失败数列中的值也随之增加。不过,错误不再来自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 Open Source的NGINX Ingress Controller,可以获取发布版源代码,也可以从DockerHub下载预构建容器。
相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
