测试用于Kubernetes的NGINX Ingress Controller的性能
113 次浏览
发表于 2021-08-16 14:28

Kubernetes 已成为容器化应用的标准管理系统受到许多企业在其生产环境中采用。在本博文中,我们描述通过 NGINX Ingress Controller for Kubernetes 实现的性能,详细分析三个指标每秒请求数、每秒 SSL/TLS 事务和吞吐量)性能测试的结果。此外,介绍我们所用 NGINX 和 Kubernetes 完整的配置。

您可以使用我们的性能数据和配置来决定适合应用所需性能和可扩展性级别的拓扑结构、规格和 Kubernetes 配置


拓扑结构

所有测试客户端计算机上运行的 wrk 实用程序生成流量,并通过一个 40Gb以太网链路将其发送到由 10Gb 链路连接组成 Kubernetes 集群的两个节点(一个主要节点和一个辅助节点)

NGINX Ingress Controller作为 Kubernetes Pod 部署在主要节点上,以执行 SSL 终止和 7 层路由。在辅助节点上运行的上游 Pod 是一台可提供各种大小静态内容 NGINX Web 服务器


所用硬件

以下用于测试的硬件

计算机

CPU

网络

内存

客户端

2 颗英特尔 (R) 至强 (R) CPU E52699 v4 @ 2.20GHz,44 个真正(或 88 HT)内核

1 个英特尔 XL710 40GbE QSFP+

128 GB

主要节点

2 颗英特尔 (R) 至强 (R) 奔腾 8168 CPU @ 2.70GHz,48 个真正(或 96 HT)内核

1 个英特尔 XL710 40GbE QSFP+

128 GB

辅助节点

2 颗英特尔 (R) 至强 (R) CPU E52699 v4 @ 2.20GHz,44 个真正(或 88 HT)内核

1 个英特尔 10Gb X540AT2

128 GB


所用软件

测试软件如下

●  wrk 版本 4.1.0,按照相关说明进行安装

●  取自 Docker Hub 的NGINX Ingress Controller版本 1.4.3(包括测试时可用的最新 GitHub commit

●  NGINX 开源版本 1.15.8

●  Kubernetes 版本 1.13.1

●  Ubuntu 18.04.1 LTS(三台计算机)

●  OpenSSL 版本 1.1.0g

●  Flannel作为网络覆盖堆栈


收集的指标

我们运行了测试收集三项性能指标:

●  每秒请求数 (RPS) — NGINX Ingress Controller每秒可处理的请求数(取固定时间段内平均值)。客户端发出每个请求定向到 NGINX Ingress Controller将请求代理到上游 Pod,以获取客户端请求的静态内容。静态内容是一个大小约为一个小型 CSS 或 JavaScript 文件,或者一个非常小的图像的 1 KB 文件

●  每秒 SSL/TLS 事务 (TPS) — NGINX Ingress Controller每秒可建立支持的新 HTTPS 连接数(取固定时间段内平均值)。客户端所发出的每个HTTPS 请求来源一个新链接。客户端和 NGINX Ingress Controller通过 TLS 握手以建立安全连接,然后 NGINX Ingress Controller请求并发回 0 KB 响应。请求得到满足后连接关闭。

●  吞吐量 — 固定时间段内 NGINX 在处理静态内容的 HTTP 请求的同时可保持的数据传输速率。静态内容是 1 MB 文件,更大文件的请求会增加系统的整体吞吐量。


测试方法

我们使用以下 wrk 选项生成了客户端流量:

●  -c 选项要创建的 TCP 连接数量。在我们的测试中,我们将其设置为 1000 个连接。

●  -d 选项指生成流量的用时。我们的每项测试均持续了 180 秒(3 分钟)。

●  -t 选项指要创建的线程数量。我们指定了 44 个线程。

为了充分利用每个 CPU,我们使用了可以将单个 wrk 进程绑定到 CPU单位taskset。与增加 wrk 数相比,该方法能够产生更加一致结果在测试 HTTPS 性能时,我们使用了一个2048 位密钥长度和完美正向加密的 RSA;SSL 密码是 ECDHE-RSA-AES256-GCM-SHA384

测试 RPS

我们在客户端计算机上运行了以下脚本来测试RPS

taskset -c 0-21,44-65 wrk -t 44 -c 1000 -d 180s https://host.example.com:443/1kb.bin

该脚本在 44 个 CPU 上均生成1 个 wrk 线程。每个线程创建 1000 个 TCP 连接,并通过每个连接在 3 分钟内持续请求一个 1 KB 文件。

测试 TPS

我们运行了以下脚本来测试TPS

taskset -c 0-21,44-65 wrk -t 44 -c 1000 -d 180s -H ‘Connection: Close’ https://host.example.com:443

该测试使用了与 RPS 测试相同的 wrk 选项,但其重点是处理 SSL/TLS 连接,所以与 RPS 测试在两方面存在显著差异:

●  客户端每个请求打开并关闭连接(-H 选项设置 HTTP Connection: close 标头)。

●  所请求的文件大小为 0 KB,而非 1 KB。

测试 HTTP 吞吐量

我们运行了以下脚本来测试吞吐量

taskset -c 0-21,44-65 wrk -t 44 -c 1000 -d 180s https://host.example.com:443/1mb.bin

该测试 wrk 选项作为 RPS 测试,但所请求的文件大小为 1 MB,而非 1 KB。


性能分析

HTTP 请求的 RPS

该表格和图表展现了 NGINX Ingress 控制器在不同 CPU数量中每秒处理1 KB 文件的 HTTP 请求数。

性能与 CPU 数量大致成正比增长,直到 16  CPU。我们发现,较之 16 CPU,24  CPU 性能几乎没有优势。这是因为资源的竞最终会抵消 CPU 数量增加所带来的性能提升。

 CPURPS 
 1  36,647
 2  74,192
 4  148,936
 8  300,625
16  342,651
24  342,785


HTTPS 请求的 RPS

未加密 HTTP 请求相比,HTTPS 请求所需的加解密增加了计算开销。

采用 8  CPU 执行的测试结果符合预期:HTTPS 的 RPS 比 HTTP 低约 20%。

采用 16 或 24  CPU 执行的测试,HTTPS 和 HTTP 之间同样因为资源争用基本没有差异

 CPURPS 
 1  28,640
 2  58,041
 4  117,255
 8  236,703
16  341,232
24  342,785

TPS

以下表格和图表澄明了在采用和不采用英特尔® 超线程技术 (HT) 的情况下,NGINX Ingress Controller基于不同数量 CPU 的 TPS 性能。

因为 TLS 握手受到 CPU 限制且加密任务具有可并行性, HT 显著提高该指标的性能。我们发现,在搭载多至 16  CPU 情况下,性能提高了 50-65%。

CPU

SSL TPS(禁用 HT)

SSL TPS(启用 HT)

1

4,433

7,325

2

8,707

14,254

4

17,433

26,950

8

31,485

47,638

16

54,877

56,715

24

58,126

58,811

吞吐量

该表格和图表显示了NGINX搭载不同数量的 CPU 时,3 分钟可保持的 HTTP 请求吞吐量(单位:Gbps)。

吞吐量与客户端发出的请求大小成正比。与预期一样,性能的峰值达到了略低于 10 Gbps :Kubernetes 集群的辅助节点配有英特尔 10Gb X540AT2 网络接口卡。

CPU 吞吐量(Gbps) 
 1  1.91
 2  4.78
 4  8.72
 8  8.64
 16  8.80
 24  8.80


测试技术细节

用于部署 NGINX Ingress Controller的 Kubernetes 配置

我们使用以下 Kubernetes 配置文件部署了 NGINX Ingress Controller。它定义 Kubernetes DaemonSet 使用一个专用于我们 DockerHub 存储库中的 nginx/nginx-ingress:edge 容器镜像Pod 副本为了将Pod分配至辅助节点,我们在 nodeSelector 字段中指定主要节点的标签 (npq3)

运行以下命令列出集群节点附加的标签

$ kubectl get nodes --show-labels

容器镜像对端口80和443开放,分别对应出入向HTTP 和 HTTPS 连接。我们在args字段中包括了两个参数

●  -nginx-configmaps 参数我们使用 ConfigMaps 将配置变更部署到NGINX Ingress Controller Pod

●  -default-server-tls-secret 设置用于到达NGINX Ingress Controller入口点TLS请求的默认Secret,该入口点Ingress资源中指定TLS 路径规则不匹配

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: nginx-ingress
namespace: nginx-ingress
spec:
selector:
matchLabels:
app: nginx-ingress
template:
metadata:
labels:
app: nginx-ingress
spec:
serviceAccountName: nginx-ingress
nodeSelector:
kubernetes.io/hostname: npq3
hostNetwork: true
containers:
- image: nginx/nginx-ingress:edge
imagePullPolicy: IfNotPresent
name: nginx-ingress
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
args:
- -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret

使用Kubernetes 服务配置以暴露 NGINX Ingress Controller

以下配置创建了一个在静态端口(80 和 443)向主机IP暴露NGINX Ingress Controller 的Kubernetes 服务。在测试环境中,我们在 externalIPs 字段中为 NGINX Ingress Controller Pod 分配一个外部 IP 地址 (10.10.16.10),以便将其提供给客户端计算机。该地址必须是分配给主要节点上 40GbE 网络接口的地址。

apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
namespace: nginx-ingress
spec:
externalTrafficPolicy: Local
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 443
targetPort: 443
protocol: TCP
name: https
externalIPs:
- 10.10.16.10
selector:
app: nginx-ingress

用于 NGINX Ingress Controller的 Kubernetes ConfigMap

ConfigMap 为您提供更精细NGINX 配置控制 ,以使用高级 NGINX 特性并自定义 NGINX 行为。您可通过 data 条目下的可用ConfigMap 键列表并为其赋值,以设置 NGINX 指令。

些 NGINX 模块和参数无法通过通用 ConfigMap 和注释直接设置。但通过 main-template ConfigMap 键,您可以加载自定义 NGINX 配置模板,从而在 Kubernetes 环境中完全控制 NGINX 配置。

例如,在该配置中,我们修改了主 nginx 模板,并设置了以下指令,以进一步提高运行性能:

●  tcp_nodelay 和 tcp_nopush 设置为 on

●  access_log 设置为 off

●  测试 RPS 时,keepalive_timeout 设置为 315s(315 秒),keepalive_requests 设置为 1000000(100 万)

然后,如下所示,我们将修改后的模板粘贴到 main-template ConfigMap 键的值中。我们缩短了 http{} 上下文中配置,以突重要的指令修改后的内容显示为橙色。

kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
worker-processes: "24"
worker-connections: "100000"
worker-rlimit-nofile: "102400"
worker-cpu-affinity: "auto 111111111111111111111111"
keepalive: "200"
main-template: |
user nginx;
worker_processes {{.WorkerProcesses}};
{{- if .WorkerRlimitNofile}}
worker_rlimit_nofile {{.WorkerRlimitNofile}};{{end}}
{{- if .WorkerCPUAffinity}}
worker_cpu_affinity {{.WorkerCPUAffinity}};{{end}}
{{- if .WorkerShutdownTimeout}}
worker_shutdown_timeout {{.WorkerShutdownTimeout}};{{end}}
daemon off;
 
error_log /var/log/nginx/error.log {{.ErrorLogLevel}};
pid /var/run/nginx.pid;
 
{{- if .MainSnippets}}
{{range $value := .MainSnippets}}
{{$value}}{{end}}
{{- end}}
 
events {
worker_connections {{.WorkerConnections}};
}
 
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

...

 
sendfile on;
access_log off;
tcp_nopush on;
tcp_nodelay on;
 
keepalive_timeout 315;
keepalive_requests 10000000;
 
#gzip on;
...
}


注:在测试时,我们使用模板设置 access_logkeepalive_timeout 及 keepalive_requests 参数。在 NGINX Ingress Controller后续 commit 中,添加了额外的 ConfigMap 键使您无需使用模版配置上述参数您还可以使用 http-snippets 键配置其他参数(例如 tcp_nodelay 和 tcp_nopush,这是模版的替代方案

用于后端的 Kubernetes DaemonSet

本节描述了我们在测试中对 NGINX Ingress Controller和上游 Pod 之间的网络进行的性能优化。优化项目包括 Flannel 网络堆栈和用于 Web 服务器上游的 DaemonSet。

网络优化

Flannel 是Kubernetes 环境中 3 层网络结构的配置工具。当集群中的节点不在同一子网中且无法通过 2 层连接访问时,我们建议使用 Flannel 的 VXLAN 后端选项。当跨主机容器通信需在不同子网进行时,每个节点上的 flannel 设备就是一个TCP数据包封装入UDP数据包VXLAN 设备,并将其发送至远程 VXLAN 设备。但因为每个请求均需通过 flannel 设备才可到达正确的 Pod 目标位置 ,VXLAN 后端选项并非提高性能最佳方案

我们测试环境中的集群节点同一子网,并通过 2 层网络连接。因此,进行跨主机容器通信无需使用 VXLAN 设备我们能够在同一局域网内创建到远程机器IP地址的直接内核IP路由。 

因此,每当 NGINX Ingress Controller将连接代理到 Web 服务器 Pod 时,我们都将数据包绕过 flannel 设备转发至目标主机,而非使用 flannel进程 (flanneld UDP 数据包封装到远程主机操作不仅提高了性能减少了依赖项数量。设置该选项最简单方法是在部署 Flannel 网络前,将 Flannel ConfigMap 中的后端选项更改为 "Type": "host-gw"。或者,您可以手动更改内核路由表

后端 DaemonSet 部署

用于后端 Pod 的 NGINX 配置由  app-conf 和 main-conf ConfigMap 定义。binary ConfigMap 创建1KB文件,在 RPS 测试中将该文件返回至客户端

apiVersion: v1
data:
app.conf: "server {\n listen 80;\nlocation / {\n root /usr/share/nginx/bin;
\n }\n}\n"
kind: ConfigMap
metadata:
name: app-conf
namespace: default
 
---
 
apiVersion: v1
data:
nginx.conf: |+
user nginx;
worker_processes 44;
worker_rlimit_nofile 102400;
worker_cpu_affinity auto 0000000000000000000000111111111111111111111100000000000000000000001111111111111111111111;
 
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
 
events {
worker_connections 100000;
}
 
http {
 
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
 
sendfile on;
tcp_nopush on;
tcp_nodelay on;
 
access_log off;
 
include /etc/nginx/conf.d/*.conf;
}
 
---
 
apiVersion: v1
data:
1kb.bin: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
kind: ConfigMap
metadata:
name: binary
namespace: default

现在,我们创建了一个Kubernetes DaemonSet,其中包含了一个NGINX后端Pod用于返回静态文件辅助节点(web-server-payload)。ConfigMap(app-confmain-conf 和 binary)作为卷挂载到nginx 镜像上。我们在 nodeSelector 字段中指定辅助节点的标签 (nbdw34),以将 Pod 分配至辅助节点。确保您指定的标签附加到辅助节点

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: web-server-payload
spec:
selector:
matchLabels:
app: web-server-payload
template:
metadata:
labels:
app: web-server-payload
spec:
hostNetwork: true
containers:
- name: web-server-payload
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: app-config-volume
mountPath: /etc/nginx/conf.d
- name: main-config-volume
mountPath: /etc/nginx
- name: binary-payload
mountPath: /usr/share/nginx/bin
volumes:
- name: app-config-volume
configMap:
name: app-conf
- name: main-config-volume
configMap:
name: main-conf
- name: binary-payload
configMap:
name: binary
nodeSelector:
kubernetes.io/hostname: nbdw34

为了进一步网络性能最大化,我们将 hostNetwork 字段设置为 true,从而将 web-server-payload Pod 设置为主机网络命名空间。这一设置可直接在端口 80 将 web-server-payload Pod 暴露给主机(辅助节点)网络驱动程序,而非执行将其在容器网络命名空间 (cni0) 中暴露的默认行为。在将 hostNetwork 设置为 true 后,RPS 将平均增加 10–15%。

最后,我们 NGINX Ingress Controller 暴露 web-server-payload Pod 的 web-server-svc 服务

apiVersion: v1
kind: Service
metadata:
name: web-server-svc
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: web-server-payload


Kubernetes Ingress 资源

Ingress 资源允许您设置基本的NGINX特性,例如SSL 终止和 7 层基于路径路由我们使用 Kubernetes Secret 执行双向 TLS 身份验证,并规定所有带有 host.example.com 主机标头的请求 URI 均通过端口 80 代理至 web-server-svc 服务。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-ingress
spec:
tls:
- hosts:
- host.example.com
secretName: example-secret
rules:
- host: host.example.com
http:
paths:
- path: /
backend:
serviceName: web-server-svc
servicePort: 80


结语

我们提供了 Kubernetes 环境中使用 NGINX Ingress Controller生成最佳性能测试结果的配置和部署信息。当 CPU 数量扩展到超过 16 时,我们的 RPS 结果达到约 340K 的最大值。一台仅搭载 16  CPU 的计算机可能与一台采用 24  CPU 的计算机提供相同的 RPS。在 Kubernetes 中部署生产工作负载时,可使用以上的信息制定满足性能和扩展要求又经济实惠的解决方案

立即测试NGINX Plus附带的NGINX Ingress ControllerNGINX App Protect吧,您可以从现在马上开始

如欲试用NGINX Open Source附带的NGINX Ingress Controller,您可以获取源代码,或者从 DockerHub 下载预编译的容器。


发表评论
发表者

NGINX官方账号

NGINX官方账号

  • 55

    文章

  • 2

    关注

  • 125

    粉丝

活动推荐
版权所有©F5 Networks,Inc.保留所有权利。京ICP备16013763号-5