文章目錄
- 問題復現
- 分析原因
- 解決問題
- 分析原理
- 什么是Keep-Alive模式?
- 啟用Keep-Alive的優點
- 性能大提升的原因
- 參考
這兩天遇到一個很有意思的應用場景:有一個業務應用部署在kubernetes容器中,如果將該應用以Kubernetes Service NodePort暴露出來,這時測試人員測得應用的頁面響應性能較高,可以達到2w多的QPS;而將這個Kubernetes Service再用Ingress暴露出來,測試人員測得的QPS立馬就較得只有1w多的QPS了。這個性能開銷可以說相當巨大了,急需進行性能調優。花了一段時間分析這個問題,終於找到原因了,這里記錄一下。
問題復現
問題是在生產環境出現了,不便於直接在生產環境調參,這里搭建一個獨立的測試環境以復現問題。
首先在一台16C32G的服務器上搭建了一個單節點的kubernetes集群,並部署了跟生產環境一樣的nginx-ingress-controller。然后進行基本的調優,以保證盡量與生產環境一致,涉及的調優步驟如下:
-
ClusterIP使用性能更優異的ipvs實現
12345678910111213141516171819202122232425$ yum install -y ipset$ cat << 'EOF' > /etc/sysconfig/modules/ipvs.modulesipvs_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>&1if [ $? -eq 0 ]; then/sbin/modprobe ${kernel_module}fidoneEOF$ 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; -
flannel使用host-gw模式
12345678$ 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 -
集群node節點及客戶端配置內核參數
12345678910111213141516$ cat << EOF >> /etc/sysctl.confnet.core.somaxconn = 655350net.ipv4.tcp_syncookies = 1net.ipv4.tcp_timestamps = 1net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_fin_timeout = 30net.ipv4.tcp_max_tw_buckets = 5000net.nf_conntrack_max = 2097152net.netfilter.nf_conntrack_max = 2097152net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30net.netfilter.nf_conntrack_tcp_timeout_established = 1200EOF$ sysctl -p --system -
集群node節點及客戶端配置最大打大文件數
12345678910111213141516171819202122$ 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 655350root hard nofile 655350root soft nofile 655350root hard nproc 655350root soft nproc 655350...$ echo 'session required pam_limits.so' >> /etc/pam.d/common-session
然后在集群中部署了一個測試應用,以模擬生產環境上的業務應用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
$ 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副本可同時處理的頁面請求數有限,以模擬真正的業務應用
接下來簡單測試一下:
1
2
3
4
5
6
7
|
# 使用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請求鏈路如下:
1
|
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的常用優化配置,可以發現有些優化配置沒有應用上。
1
|
kubectl -n kube-system
exec -ti nginx-ingress-controller-xxx-xxxx cat /etc/nginx/nginx.conf > /tmp/nginx.conf
|
對比后,發現server context
中keepalive_requests
、keepalive_timeout
,upstream context
中的keepalive
、keepalive_requests
、keepalive_timeout
這些配置項還可以優化下,於是參考nginx-ingress-controller的配置方法,這里配置了下:
1
2
3
4
5
6
7
8
9
10
11
|
$ kubectl -n kube-system edit configmap nginx-configuration
...
apiVersion: v1
data:
keep-alive:
"60"
keep-alive-requests:
"100"
upstream-keepalive-connections:
"10000"
upstream-keepalive-requests:
"100"
upstream-keepalive-timeout:
"60"
kind: ConfigMap
...
|
再次壓測:
1
2
|
$ 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-connections
、upstream-keepalive-requests
、upstream-keepalive-timeout
參數,這樣nginx->upstream
的HTTP處理是啟用了Keep-Alive的,這樣到Kuberentes Service的TCP連接可以高效地復用,避免了重建連接的開銷。
DONE.
參考
- https://www.jianshu.com/p/024b33d1a1a1
- https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
- https://zhuanlan.zhihu.com/p/34052073
- http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
- http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
- https://kiswo.com/article/1018