­

優化nginx-ingress-controller並發性能

  • 2019 年 11 月 12 日
  • 筆記

這兩天遇到一個很有意思的應用場景:有一個業務應用部署在kubernetes容器中,如果將該應用以Kubernetes Service NodePort暴露出來,這時測試人員測得應用的頁面響應性能較高,可以達到2w多的QPS;而將這個Kubernetes Service再用Ingress暴露出來,測試人員測得的QPS立馬就較得只有1w多的QPS了。這個性能開銷可以說相當巨大了,急需進行性能調優。花了一段時間分析這個問題,終於找到原因了,這裡記錄一下。

問題復現

問題是在生產環境出現了,不便於直接在生產環境調參,這裡搭建一個獨立的測試環境以復現問題。

首先在一台16C32G的伺服器上搭建了一個單節點的kubernetes集群,並部署了跟生產環境一樣的nginx-ingress-controller。然後進行基本的調優,以保證盡量與生產環境一致,涉及的調優步驟如下:

1.ClusterIP使用性能更優異的ipvs實現

$ yum install -y ipset    $ cat << 'EOF' > /etc/sysconfig/modules/ipvs.modules  #!/bin/bash  ipvs_modules=(ip_vs ip_vs_lc ip_vs_wlc ip_vs_rr ip_vs_wrr ip_vs_lblc ip_vs_lblcr ip_vs_dh ip_vs_sh ip_vs_fo ip_vs_nq ip_vs_sed ip_vs_ftp nf_conntrack_ipv4)  for kernel_module in ${ipvs_modules[*]}; do     /sbin/modinfo -F filename ${kernel_module} > /dev/null 2>&1     if [ $? -eq 0 ]; then         /sbin/modprobe ${kernel_module}     fi  done  EOF    $ chmod +x /etc/sysconfig/modules/ipvs.modules    $ /etc/sysconfig/modules/ipvs.modules    $ kubectl -n kube-system edit cm kube-proxy  ......  mode: "ipvs"  ......    $ kubectl -n kube-system get pod -l k8s-app=kube-proxy | grep -v 'NAME' | awk '{print $1}' | xargs kubectl -n kube-system delete pod    $ iptables -t filter -F; iptables -t filter -X; iptables -t nat -F; iptables -t nat -X;  

2.flannel使用host-gw模式

$ kubectl -n kube-system edit cm kube-flannel-cfg  ......       "Backend": {         "Type": "host-gw"       }  ......    $ kubectl -n kube-system get pod -l k8s-app=flannel  | grep -v 'NAME' | awk '{print $1}' | xargs kubectl -n kube-system delete pod

3.集群node節點及客戶端配置內核參數

$ cat << EOF >> /etc/sysctl.conf  net.ipv4.tcp_syncookies = 0  net.nf_conntrack_max = 655350  net.netfilter.nf_conntrack_tcp_timeout_established = 1200  net.core.somaxconn = 655350  EOF    $ sysctl -p --system

4.集群node節點及客戶端配置最大打大文件數

$ ulimit -n 655350  $ cat /etc/sysctl.conf  ...  fs.file-max=655350  ...  $ sysctl -p --system  $ cat /etc/security/limits.conf  ...  *	hard	nofile	655350  *	soft	nofile	655350  *	hard	nproc	6553  *	soft	nproc	655350    ...

然後在集群中部署了一個測試應用,以模擬生產環境上的業務應用:

$ cat web.yaml  apiVersion: extensions/v1beta1  kind: Deployment  metadata:    labels:      app: web    name: web    namespace: default  spec:    selector:      matchLabels:        app: web    template:      metadata:        labels:          app: web      spec:        containers:        - image: nginx:1.17-alpine          imagePullPolicy: IfNotPresent          name: nginx          resources:            limits:              cpu: 60m  ---  apiVersion: v1  kind: Service  metadata:    labels:      app: web    name: web    namespace: default  spec:    externalTrafficPolicy: Cluster    ports:    - nodePort: 32380      port: 80      protocol: TCP      targetPort: 80    selector:      app: web    sessionAffinity: None    type: NodePort  ---  apiVersion: extensions/v1beta1  kind: Ingress  metadata:    annotations:      ingress.kubernetes.io/ssl-redirect: "false"      kubernetes.io/ingress.class: nginx      nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"      nginx.ingress.kubernetes.io/proxy-read-timeout: "300"      nginx.ingress.kubernetes.io/proxy-send-timeout: "300"      nginx.ingress.kubernetes.io/ssl-redirect: "false"      nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"    labels:      app: web    name: web    namespace: default  spec:    rules:    - host: web.test.com      http:        paths:        - backend:            serviceName: web            servicePort: 80          path: /    $ kubectl apply -f web.yaml

注意:這裡故意將pod的cpu限制在60m,這樣一個pod副本可同時處理的頁面請求數有限,以模擬真正的業務應用

接下來簡單測試一下:

# 使用httpd-utils中的ab命令直接壓測Kubernetes Service NodePort,並發請求數為10000,總發出1000000個請求,此時測得QPS為2.4w  $ ab -r -n 1000000 -c 10000 http://${k8s_node_ip}:32380/ 2>&1 | grep 'Requests per second'  Requests per second:    24234.03 [#/sec] (mean)    # 再在客戶端的/etc/hosts中將域名web.test.com指向${k8s_node_ip},通過Ingress域名壓測業務應用,測得QPS為1.1w  $ ab -r -n 1000000 -c 10000 http://web.test.com/ 2>&1 | grep 'Requests per second'  Requests per second:    11736.21 [#/sec] (mean)

可以看到訪問Ingress域名後,確實QPS下降很明顯,跟生產環境的現象一致。

分析原因

我們知道,nginx-ingress-controller的原理實際上是掃描Kubernetes集群中的Ingress資源,根據Ingress資源的定義自動為每個域名生成一段nginx虛擬主機及反向代理的配置,最後由nginx讀取這些配置,完成實際的HTTP請求流量的處理,整個HTTP請求鏈路如下:

          client    ->    nginx    ->    upstream(kubernetes service)    ->    pods  

nginx的實現中必然要對接收的HTTP請求進行7層協議解析,並根據請求資訊將HTTP請求轉發給upstream。

client直接請求kubernetes service有不錯的QPS值,說明nginx這裡存在問題。

解決問題

雖說nginx進行7層協議解析、HTTP請求轉發會生產一些性能開銷,但nginx-ingress-controller作為一個kubernetes推薦且廣泛使用的ingress-controller,參考業界的測試數據,nginx可是可以實現百萬並發HTTP反向代理的存在,照理說才一兩萬的QPS,其不應該有這麼大的性能問題。所以首先懷疑nginx-ingress-controller的配置不夠優化,需要進行一些調優。

我們可以從nginx-ingress-controller pod中取得nginx的配置文件,再參考nginx的常用優化配置,可以發現有些優化配置沒有應用上。

kubectl -n kube-system exec -ti nginx-ingress-controller-xxx-xxxx cat /etc/nginx/nginx.conf > /tmp/nginx.conf

對比後,發現server contextkeepalive_requestskeepalive_timeoutupstream context中的keepalivekeepalive_requestskeepalive_timeout這些配置項還可以優化下,於是參考nginx-ingress-controller的配置方法,這裡配置了下:

$ kubectl -n kube-system edit configmap nginx-configuration  ...  apiVersion: v1  data:    keep-alive: "75"    keep-alive-requests: "100"    upstream-keepalive-connections: "10000"    upstream-keepalive-requests: "100"    upstream-keepalive-timeout: "60"  kind: ConfigMap  ...

再次壓測:

$ ab -r -n 1000000 -c 10000 http://web.test.com/ 2>&1 | grep 'Requests per second'  Requests per second:    22733.73 [#/sec] (mean)

此時發現性能好多了。

分析原理

什麼是Keep-Alive模式?

HTTP協議採用請求-應答模式,有普通的非KeepAlive模式,也有KeepAlive模式。

非KeepAlive模式時,每個請求/應答客戶和伺服器都要新建一個連接,完成 之後立即斷開連接(HTTP協議為無連接的協議);當使用Keep-Alive模式(又稱持久連接、連接重用)時,Keep-Alive功能使客戶端到服 務器端的連接持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連接。

啟用Keep-Alive的優點

啟用Keep-Alive模式肯定更高效,性能更高。因為避免了建立/釋放連接的開銷。下面是RFC 2616 上的總結:

  • TCP連接更少,這樣就會節約TCP連接在建立、釋放過程中,主機和路由器上的CPU和記憶體開銷。
  • 網路擁塞也減少了,拿到響應的延時也減少了
  • 錯誤處理更優雅:不會粗暴地直接關閉連接,而是report,retry

性能大提升的原因

壓測命令ab並沒有添加-k參數,因此client->nginx的HTTP處理並沒有啟用Keep-Alive。

但由於nginx-ingress-controller配置了upstream-keepalive-connectionsupstream-keepalive-requestsupstream-keepalive-timeout參數,這樣nginx->upstream的HTTP處理是啟用了Keep-Alive的,這樣到Kuberentes Service的TCP連接可以高效地復用,避免了重建連接的開銷。

DONE.

參考

  1. https://www.jianshu.com/p/024b33d1a1a1
  2. https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
  3. https://zhuanlan.zhihu.com/p/34052073
  4. http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
  5. http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive