Kubernetes 已成为容器化应用的标准管理系统,并受到了许多企业在其生产环境中的采用。在本博文中,我们将描述通过 NGINX Ingress Controller for Kubernetes 可实现的性能,并详细分析三个指标(每秒请求数、每秒 SSL/TLS 事务和吞吐量)性能测试的结果。此外,将介绍我们所用 NGINX 和 Kubernetes 完整的配置。
您可以使用我们的性能数据和配置来决定适合您应用所需性能和可扩展性级别的拓扑结构、规格和 Kubernetes 配置。
拓扑结构
在所有测试中,客户端计算机上运行的 wrk
实用程序生成流量,并通过一个 40 Gb 的以太网链路将其发送到由 10 Gb 链路连接到组成 Kubernetes 集群的两个节点(一个主要节点和一个辅助节点)。
NGINX Ingress Controller 作为 Kubernetes Pod 部署在主要节点上,以执行 SSL 终止和 7 层路由。在辅助节点上运行的上游 Pod 是一台可提供各种大小静态内容的 NGINX Web 服务器。
所用硬件
以下是用于测试的硬件。
计算 | CPU | 网络 | 内存 |
---|---|---|---|
客户端 | 2 颗英特尔 (R) 至强 (R) CPU E5-2699 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+ | 192 GB |
辅助节点 | 2 颗英特尔 (R) 至强 (R) CPU E5-2699 v4 @ 2.20GHz,44 个真正(或 88 HT)内核 | 1 个英特尔 10Gb X540-`AT2 | 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
选项设置 HTTPConnection:
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 数量增加所带来的性能提升。
CPUs | RPS |
---|---|
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 之间,同样因为资源争用,基本没有差异。
CPUs | RPS |
---|---|
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%。
CPUs | 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 集群的辅助节点配有英特尔 10 Gb X540AT2 网络接口卡。
CPUs | 吞吐量 (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),以便将其提供给客户端计算机。该地址必须是分配给主要节点上 40 GbE 网络接口的地址。
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
(一百万)。
然后,如下所示,我们将修改后的模板粘贴到了 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_log
、keepalive_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
定义。binary
ConfigMap 创建 1 KB 文件,在 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-conf
、main-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 结果达到约 340 K 的最大值。而一台仅搭载 16 个 CPU 的计算机可能与一台采用 24 个 CPU 的计算机提供相同的 RPS。在 Kubernetes 中部署生产工作负载时,您可使用以上的信息制定既满足您对性能和扩展要求又经济实惠的解决方案。
立即测试 NGINX Plus 附带的 NGINX Ingress Controller 和 NGINX App Protect 吧,您可以从现在马上开始!
如欲试用 NGINX Open Source 附带的 NGINX Ingress Controller,您可以获取源代码,或者从 DockerHub 下载预编译的容器。