node節點信息:
系統:centos7.6
內核:3.10
IP地址:192.168.1.1
應用環境:
因為需要跑一個nginx的應用叫做http-proxy做流量轉發,公網入口是阿里雲的SLB然轉發到http-proxy的NodePor 端口上,也就是192.168.1.1:30285
spec:
clusterIP: 172.30.253.123
externalTrafficPolicy: Cluster
ports:
- name: http
nodePort: 30285
port: 8080
protocol: TCP
targetPort: 8080
剛配好一切正常,過了幾分鍾SLB開始報健康檢查錯誤,手動檢查了一下發現3、4請求之后必然會有一次timeout
排查過程:
- 先從公網請求一下
$ curl proxy.public.com
幾次請求中必然會有一次timeout
- 首先容器本地確定是否是nginx本身的問題
$ curl localhost:8080
正常
- 在內網環境請求NodePort端口
$ curl 192.168.1.1:30285
正常,這就很詭異了
- 在宿主機本地抓包
# tcpdump port 30285
11:08:36.186722 IP 100.122.64.147.30042 > 192.168.1.1:30285: Flags [S], seq 868295361, win 28480, options [mss 1424,sackOK,TS val 1875975334 ecr 0,nop,wscale 9], length 0
11:08:37.236652 IP 100.122.64.147.30042 > 192.168.1.1:.30285: Flags [S], seq 868295361, win 28480, options [mss 1424,sackOK,TS val 1875976384 ecr 0,nop,wscale 9], length 0
11:08:39.284640 IP 100.122.64.147.30042 > 192.168.1.1:.30285: Flags [S], seq 868295361, win 28480, options [mss 1424,sackOK,TS val 1875978432 ecr 0,nop,wscale 9], length
可以看到有收到來自SLB發來第一握手syn包,但是服務端沒有回應ack - 在容器本地看一下syn drop
$ netstat -s |grep LISTEN
280 SYNs to LISTEN sockets dropped
果然有而且在一直增加,查閱了相關資料后發現有可能是啟用了tcp_tw_recycle參數,前一段時間因time_wait確實優化過這個參數。。。果斷關掉# sysctl -w net.ipv4.tcp_tw_recycle=0
然后一切正常了
為什么會這樣?
復習一下tcp的四次揮手:
第一次揮手:主動關閉方發送一個FIN+ACK報文,此時主動方進入FIN_WAIT1狀態,主動方停止發送數據但仍然能接收數據
第二次揮手:被動方收到FIN+ACK,發送一個ACK給對方,此時被動方進入CLOSE-WAIT狀態,被動方仍然可以給主動方發送數據
第三次揮手:主動方收到ACK后,此時主動方進入FIN_WAIT2狀態,被動方確定沒有數據要發后就會發送FIN+ACK報文
第四次揮手:主動方收到FIN+ACK,此時主動方進入TIME-WAIT狀態,發送一個ACK給被動方,方被動方進入CLOSED狀態
linux系統中的3個參數:
參數 | 默認狀態 | 作用 | 條件 | 影響 | 風險 | 建議 |
net.ipv4.tcp_timestamps |
開啟 | 記錄TCP報文的發送時間 | 雙方都要開啟 | 影響客戶端服務端 | 開啟 | |
net.ipv4.tcp_tw_recycle | 關閉 4.1內核已刪除 |
把TIME-WAIT狀態超時時間設置為成rto,以實現快速回收 | 要啟用net.ipv4.tcp_timestamps | 影響客戶端服務端 | tcp_tw_recycle和tcp_timestamps同時開啟的條件下,60s內同一源ip主機的socket connect請求中的timestamp必須是遞增的,否則數據會被linux的syn處理模塊丟棄 | 內網環境切沒有NAT的時候看情況打開,沒什么必要就不要打開了 |
net.ipv4.tcp_tw_reuse | 關閉 | TIME-WAIT狀態1秒之后可以重用端口 | 要啟用net.ipv4.tcp_timestamps | 影響客戶端 | 比如負載均衡連接后端服務器時,可以在負載均衡服務器上開啟 |
結論:對於服務端來說,客戶端通過SNAT上網時的timestamp遞增性無可保證,所以當服務端tcp_tw_recycle生效時會出現連接異常
k8s的NodePort網絡:
service網絡的實體是kube-proxy維護iptables規則,先看一下流程
- 在nat主鏈攔截SERVICES自定義鏈
# iptables -t nat -nL PREROUTING
其他nat主鏈同樣有KUBE-SERVICES自定義鏈 - 攔截NodePort自定義鏈
# iptables -t nat -nL KUBE-SERVICES
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
- 攔截端口流量
# iptables -t nat -nL KUBE-NODEPORTS
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/http-proxy:http1 */ tcp dpt:30285
KUBE-SVC-NKX6PXTXGL4F5LBG tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/http-proxy:http1 */ tcp dpt:30285
- 攔截端口流量打標記
# iptables -t nat -nL KUBE-MARK-MASQ
MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x1
- 打標記並根據標記做SNAT
# iptables -t nat -nL POSTROUTING
KUBE-POSTROUTING all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
# iptables -t nat -nL KUBE-POSTROUTING
MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x1/0x1
- 第二步攔截端口流量到SCV自定義鏈
# iptables -t nat -nL KUBE-SVC-NKX6PXTXGL4F5LBG
KUBE-SEP-4DYOPOZ4UKLHEHIS all -- 0.0.0.0/0 0.0.0.0/0 /* default/http-proxy:http1 */
# iptables -t nat -nL KUBE-SEP-4DYOPOZ4UKLHEHIS
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/http-proxy:http1 */ tcp to:10.128.0.74:8081
結論:可以看到第五步kube-proxy會做SNAT的操作,這也就不難解釋從SLB過來的流量為什么會不正常了。至於為什么要做SNAT主要是防止路由來回路徑不一致會導致tcp通信失敗,這里就不展開了