如何優(yōu)雅的使用 IPtables 在多租戶環(huán)境中實(shí)現(xiàn) TCP 限速
我們有個(gè)服務(wù)以類似 SideCar 的方式和應(yīng)用一起運(yùn)行,SideCar 和應(yīng)用通過(guò) Unix Domain Socket 進(jìn)行通訊。為了方便用戶,在開(kāi)發(fā)的時(shí)候不必在自己的開(kāi)發(fā)環(huán)境中跑一個(gè) SideCar,我用 socat 在一臺(tái)開(kāi)發(fā)環(huán)境的機(jī)器上 map UDS 到一個(gè)端口。這樣用戶在開(kāi)發(fā)的時(shí)候就可以直接通過(guò)這個(gè) TCP 端口測(cè)試服務(wù),而不用自己開(kāi)一個(gè) SideCar 使用 UDS 了。
因?yàn)樗腥硕家眠@一個(gè)地址做開(kāi)發(fā),所以就有互相影響的問(wèn)題。雖然性能還可以,幾十萬(wàn) QPS 不成問(wèn)題,但是總有憨憨拿來(lái)搞壓測(cè),把資源跑滿,影響別人。我在使用說(shuō)明文檔里用紅色大字寫(xiě)了這是開(kāi)發(fā)測(cè)試用的,不能壓測(cè),還是有一些視力不好的同事會(huì)強(qiáng)行壓測(cè)。隔三差五我就得去解釋一番,禮貌地請(qǐng)同事不要再這樣做了。
最近實(shí)在累了。研究了一下直接給這個(gè)端口加上 per IP 的 rate limit,效果還不錯(cuò)。方法是在 Per-IP rate limiting with iptables[1] 學(xué)習(xí)到的,這個(gè)公司是提供一個(gè)多租戶的 SaaS 服務(wù),也有類似的問(wèn)題:有一些非正常用戶 abuse 他們的服務(wù),由于 abuse 發(fā)生在連接建立階段,還沒(méi)有進(jìn)入到業(yè)務(wù)代碼,所以無(wú)法從應(yīng)用的層面進(jìn)行限速,解決發(fā)現(xiàn)就是通過(guò) iptables 實(shí)現(xiàn)的。詳細(xì)的實(shí)現(xiàn)方法可以參考這篇文章。
iptables 本身是無(wú)狀態(tài)的,每一個(gè)進(jìn)入的 packet 都單獨(dú)判斷規(guī)則。rate limit 顯然是一個(gè)有狀態(tài)的規(guī)則,所以要用到 module: hashlimit。(原文中還用到了 conntrack,他是想只針對(duì)新建連接做限制,已經(jīng)建立的連接不限制速度了。因?yàn)檫@個(gè)應(yīng)用內(nèi)部就可以控制了,但是我這里是想對(duì)所有的 packet 進(jìn)行限速,所以就不需要用到這個(gè) module)
完整的命令如下:
- $ iptables --new-chain SOCAT-RATE-LIMIT
- $ iptables --append SOCAT-RATE-LIMIT \
- --match hashlimit \
- --hashlimit-mode srcip \
- --hashlimit-upto 50/sec \
- --hashlimit-burst 100 \
- --hashlimit-name conn_rate_limit \
- --jump ACCEPT
- $ iptables --append SOCAT-RATE-LIMIT --jump DROP
- $ iptables -I INPUT -p tcp --dport 1234 --jump SOCAT-RATE-LIMIT
第一行是新建一個(gè) iptables Chain,做 rate limit;
第二行處理如果在 rate limit 限額內(nèi),就接受包;否則跳到第三行,直接將包 DROP;
最后將新的 Chain 加入到 INPUT 中,對(duì)此端口的流量進(jìn)行限制。
有關(guān) rate limit 的算法,主要是兩個(gè)參數(shù):
- --hashlimit-upto 其實(shí)本質(zhì)上是 1s 內(nèi)可以進(jìn)入多少 packet,50/sec 就是 20ms 一個(gè) packet;
- 那如何在 10ms 發(fā)來(lái) 10 個(gè) packet,后面一直沒(méi)發(fā)送,怎么辦?這個(gè)在測(cè)試情景下也比較常見(jiàn),不能要求用戶一直勻速地發(fā)送。所以就要用到 --hashlimit-burst。字面意思是瞬間可以發(fā)送多少 packet,但實(shí)際上,可以理解這個(gè)參數(shù)就是可用的 credit。
兩個(gè)指標(biāo)配合起來(lái)理解,就是每個(gè) ip 剛開(kāi)始都會(huì)有 burst 個(gè) credit,每個(gè) ip 發(fā)送來(lái)的 packet 都會(huì)占用 burst 里面的 credit,用完了之后再發(fā)來(lái)的包就會(huì)被直接 DROP。這個(gè) credit 會(huì)以 upto 的速度一直增加,但是最多增加到 burst(初始值),之后就 use it or lost it.
舉個(gè)例子,假如 --hashlimit-upto 50/sec --hashlimit-burst 20 的話,某個(gè) IP 以勻速每 ms 一個(gè) packet 的速度發(fā)送,最終會(huì)有多少 packets 被接受?答案是 70. 最初的 20ms,所有的 packet 都會(huì)被接受,因?yàn)?--hashlimit-burst 是 20,所以最初的 credit 是 20. 這個(gè)用完之后就要依賴 --hashlimit--upto 50/sec 來(lái)每 20ms 獲得一個(gè) packet credit 了。所以每 20ms 可以接受一個(gè)。
這是限速之后的效果,非常明顯: