编者按 —— 本文是以下系列博文中的一篇(共十篇):
- 生产级 Kubernetes 助您降低复杂性
- 如何通过高级流量管理提高 Kubernetes 的弹性
- 如何提高 Kubernetes 环境的可视性
- 使用流量管理工具保护 Kubernetes 的六种方法
- Ingress Controller 选型指南,第一部分:确定需求
- Ingress Controller 选型指南,第二部分:评估风险和技术前瞻性
- Ingress Controller 选型指南,第三部分:开源、默认和商用版本能力对比
- Ingress Controller 选型指南,第四部分:NGINX Ingress Controller 选项
- 如何选择 Service Mesh
- NGINX Ingress Controller 在动态 Kubernetes 云环境中的性能测试(本文)
您还可以免费下载整套博文集结成的电子书:《Kubernetes:从测试到生产》。
随着越来越多的企业在生产环境中运行容器化应用,Kubernetes 将持续巩固其作为标准容器编排工具的地位。与此同时,在疫情中崛起的居家办公模式加快了互联网流量的增长,导致云计算需求提前几年爆发。目前很多公司都在紧锣密鼓地升级基础架构,帮助客户解决正在面临的重大网络中断和过载问题。
为了在基于云的微服务环境中达到所需的性能水平,您需要使用快速、完全动态的软件来释放下一代超大规模数据中心的可扩展性和性能潜力。许多使用 Kubernetes 管理容器的企业和机构都依赖基于 NGINX 的 Ingress Controller 来实现应用交付。
在这篇博客中,我们针对在互联网中客户端连接的延迟情况,提供了三种 NGINX Ingress Controller 在真实多云环境中的性能测试结果。这三种控制器分别为:
- 基于 NGINX 开源版的 NGINX Ingress Controller,由 Kubernetes 社区维护。我们在之前的博客中称之为“社区版 Ingress Controller”,本篇文章也将沿用这个说法。我们从 Google Container Registry 中提取了其 0.34.1 版本的镜像,用于此次测试。
- NGINX 开源版 Ingress Controller 1.8.0,由 NGINX 维护。
- NGINX Plus Ingress Controller 1.8.0,由 NGINX 维护。
测试方法和收集的指标
我们使用性能测试程序 wrk2 模拟了一个客户端,在规定的时间段内发出连续的 HTTPS 请求。被测试的 Ingress Controller(社区版 Ingress Controller、NGINX 开源版 Ingress Controller 和 NGINX Plus Ingress Controller)将请求转发到部署在 Kubernetes Pods 中的后端应用,并将应用生成的响应返回给客户端。我们生成了稳定的客户端流量,并使用这些流量对 Ingress Controller 进行压力测试,收集了以下性能指标:
- 延迟 — 客户端从生成请求到接收响应所用的时间。我们用百分位分布的形式来报告延迟。例如,如果有 100 个延迟测试样本,则第 99 个百分位数的值是 100 次测试中仅次于最慢值的响应延迟。
- 连接超时 — 由于 Ingress Controller 在特定时间内无法响应请求而被静默断开或丢弃的 TCP 连接。
- 读取错误 — 尝试读取连接失败,因为来自 Ingress Controller 的套接字已关闭。
- 连接错误 — 客户端和 Ingress Controller 之间未建立 TCP 连接。
拓扑结构
对于所有测试,我们都在 AWS 客户端机器上运行 wrk2
程序,生成请求。AWS 客户端会连接到 Ingress Controller 的外部 IP 地址,其中 Ingress Controller 作为 Kubernetes DaemonSet 部署在 Google Kubernetes Engine (GKE) 环境中的 GKE-node-1 上。
Ingress Controller 配置成 SSL 终结(引用 Kubernetes Secret)和 7 层路由模式,并通过 LoadBalancer
类型的 Kubernetes 服务暴露。后端应用则作为 Kubernetes Deployment 在 GKE-node-2 上运行。
有关云主机类型和软件配置的全部信息,请参阅附录。
测试方法
客户端部署
我们在AWS客户端机器上运行以下 wrk2
(版本 4.0.0)脚本。它生成了 2 个 wrk
线程,这些线程共与 GKE 中部署的 Ingress Controller 建立了 1000 个连接。在每轮持续 3 分钟的测试期间内,脚本每秒生成 30,000 个请求 (RPS),我们认为这很好地模拟了 Ingress Controller 在生产环境中的负载。
wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/
其中:
-t
—— 设置线程数(2)-c
—— 设置 TCP 连接数(1000)-d
—— 设置每轮测试的持续时间,单位为秒(180 秒,即 3 分钟)-L
—— 生成详细的延迟百分位信息,以便导出到分析工具-R
—— 设置 RPS 的数量(30,000)
对于 TLS 加密,我们使用了 2048 位 RSA 密钥加密和 PFS 完美正向加密。
每个来自后端应用的响应(访问地址:https://app.example.com:443)都包含大约 1 KB 的基础服务器元数据以及 200
OK
HTTP 状态码。
后端应用部署
我们对后端应用程序进行了静态和动态部署的测试运行。
静态部署中有五个 Pod 副本,并且没有使用 Kubernetes API 做任何更改。
对于动态部署,我们使用以下脚本定期将后端 nginx 部署从五个 Pod 副本扩展到七个,然后再缩减到五个。这模拟了一个动态 Kubernetes 环境,能够测试 Ingress Controller 如何有效适应端点变更。
while [ 1 -eq 1 ]
do
kubectl scale deployment nginx --replicas=5
sleep 12
kubectl scale deployment nginx --replicas=7
sleep 10
done
性能结果
静态部署的延迟结果
如图所示,在静态部署后端应用时,三个 Ingress Controller 具有相似的性能。考虑到它们都基于 NGINX 开源版构建,并且静态部署不需要从 Ingress Controller 重新配置,这一结果也很合理。
动态部署的延迟结果
该图显示了在定期将后端应用从五个 Pod 副本扩展到七个、又减少到五个的动态部署下(详见“后端应用部署”部分),每种 Ingress Controller 产生的延迟。
很明显,只有 NGINX Plus Ingress Controller 在这种环境中表现良好,一直到第 99.99% 个都几乎没有任何延迟。社区版和 NGINX 开源版 Ingress Controller 虽然具有截然不同的延迟模式,但都在很低的百分位上产生了明显的延迟。对于社区版 Ingress Controller,延迟呈缓慢但稳定的上升状态,在第 99 %个达到大约 5000 毫秒(5 秒)并在之后趋于稳定。对于 NGINX 开源版 Ingress Controller,延迟呈急剧攀升状态,在第 99 %个达到大约 32 秒,到第 99.99% 个又变成了 60 秒。
社区版和 NGINX 开源版 Ingress Controller 所经历的延迟是由 NGINX 配置更新和重新加载(以响应后端应用不断变化的端点)后出现的错误和超时引起的,具体内容我们将在“动态部署中的超时和错误结果”部分进行进一步讨论。
这是一个更细粒度的视图,展示了社区版和 NGINX Plus Ingress Controller 在与上图相同的测试条件下得出的结果。NGINX Plus Ingress Controller 第 99.9999% 个的延迟为 254 毫秒,在此之前几乎没有任何延迟。社区版 Ingress Controller 的延迟在第 99% 个稳定增长到 5000 毫秒,此后趋于平稳。
动态部署中的超时和错误结果
此表更详细地显示了引起延迟的原因。
NGINX Open Source | Community | NGINX Plus | |
---|---|---|---|
Connection errors | 33365 | 0 | 0 |
Connection timeouts | 309 | 8809 | 0 |
Read errors | 4650 | 0 | 0 |
使用 NGINX 开源版 Ingress Controller 时,每次更改后端应用端点后都需要更新和重新加载 NGINX 配置,这会导致许多连接错误、连接超时和读取错误的问题。当客户端尝试连接不再分配给 NGINX 进程的套接字时,系统会在 NGINX 重新加载的短时间内发生连接/套接字错误。当客户端与 Ingress Controller 建立连接,但后端端点不再可用时,会发生连接超时。连接错误和连接超时会严重影响延迟,导致延迟在第 99% 个激增到 32 秒,然后在第 99.99% 个变成 60 秒。
使用社区版 Ingress Controller 时,端点随着后端应用的调整而更改,引发了 8809 个连接超时。社区版 Ingress Controller 使用 Lua 代码来避免在端点变更时重新加载配置。结果显示,在 NGINX 内部运行的检测端点变更的 Lua 处理程序解决了 NGINX 开源版 Ingress Controller 的一些性能限制问题——这些限制是由每次更改端点后都重新加载配置引起的。尽管如此,连接超时仍然会发生,导致更高百分位的显著延迟。
而使用 NGINX Plus Ingress Controller 时没有错误或超时——动态环境对性能几乎没有影响。这是因为 NGINX Plus Ingress Controller 使用了 NGINX Plus API,可以在端点变更后动态更新 NGINX 配置。如上文所述,它的最高延迟为 254 毫秒,并且仅发生在第 99.9999% 个。
结语
性能结果表明,要想完全消除动态 Kubernetes 云环境中的超时和错误,Ingress Controller 就必须动态适应后端端点的变更,且无需使用事件处理程序或重新加载配置。根据这些结果,NGINX Plus API 可以说是在动态环境中动态重新配置 NGINX 的最佳解决方案。在我们的测试中,只有 NGINX Plus Ingress Controller 在高度动态的 Kubernetes 环境中实现了符合用户需求的完美性能。
附录
云机器规格
Machine | Cloud Provider | Machine Type |
---|---|---|
Client | AWS | m5a.4xlarge |
GKE-node-1 | GCP | e2-standard-32 |
GKE-node-2 | GCP | e2-standard-32 |
NGINX 开源版和 NGINX Plus Ingress Controller 的配置
Kubernetes 配置
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ingress
namespace: nginx-ingress
spec:
selector:
matchLabels:
app: nginx-ingress
template:
metadata:
labels:
app: nginx-ingress
#annotations:
#prometheus.io/scrape: "true"
#prometheus.io/port: "9113"
spec:
serviceAccountName: nginx-ingress
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
hostNetwork: true
containers:
- image: gcr.io/nginx-demos/nap-ingress:edge
imagePullPolicy: Always
name: nginx-plus-ingress
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: readiness-port
containerPort: 8081
#- name: prometheus
#containerPort: 9113
readinessProbe:
httpGet:
path: /nginx-ready
port: readiness-port
periodSeconds: 1
securityContext:
allowPrivilegeEscalation: true
runAsUser: 101 #nginx
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
args:
- -nginx-plus
- -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
注:
- 该配置适用于 NGINX Plus。NGINX 开源版配置对
nginx‑plus
的引用根据需要进行了调整。 - NGINX App Protect 包含在镜像中(
gcr.io/nginx-demos/nap-ingress:edge
),但是已经禁用(省略了-enable-app-protect
标志)。
ConfigMap
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
worker-connections: "10000"
worker-rlimit-nofile: "10240"
keepalive: "100"
keepalive-requests: "100000000"
社区版 NGINX Ingress Controller 的配置
Kubernetes 配置
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
helm.sh/chart: ingress-nginx-2.11.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.34.1
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
hostNetwork: true
containers:
- name: controller
image: us.gcr.io/k8s-artifacts-prod/ingress-nginx/controller:v0.34.1@sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 1
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
max-worker-connections: "10000"
max-worker-open-files: "10204"
upstream-keepalive-connections: "100"
keep-alive-requests: "100000000"
后端应用配置
Kubernetes 配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-t2dz
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8080
volumeMounts:
- name: main-config-volume
mountPath: /etc/nginx
- name: app-config-volume
mountPath: /etc/nginx/conf.d
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 3
volumes:
- name: main-config-volume
configMap:
name: main-conf
- name: app-config-volume
configMap:
name: app-conf
---
ConfigMaps
apiVersion: v1
kind: ConfigMap
metadata:
name: main-conf
namespace: default
data:
nginx.conf: |+
user nginx;
worker_processes 16;
worker_rlimit_nofile 102400;
worker_cpu_affinity auto 1111111111111111;
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_nodelay on;
access_log off;
include /etc/nginx/conf.d/*.conf;
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-conf
namespace: default
data:
app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 'Server address: $server_addr:$server_port\nServer name:$hostname\nDate: $time_local\nURI: $request_uri\nRequest ID: $request_id\n';}location /healthz {return 200 'I am happy and healthy :)';}}"
---
服务
apiVersion: v1
kind: Service
metadata:
name: app-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: nginx
---