少し前にスロットルでサイトを守る(vmod-vsthrottle)で異常なリクエストに対してスロットルをかける方法を紹介しました。
しかしこれはあくまでリクエストレートのみで、そのリクエストがどれだけ帯域を使うかは制限しません。
例えば特定のリソースについては帯域を制限をかけたい場合や(動画のようにDL中でも再生できるのであれば、必要なビットレート+αでもいいかなとか)
ユーザの属性(課金状況など)や、はたまたリクエストが多いユーザに対しては制限をかけたい場合などいろいろあるかと思います。
今回は帯域制限を行うvmod-tcp(ドキュメント)を紹介します。
環境について
vmod-tcpでの帯域制限はtc-fqのSO_MAX_PACING_RATEを指定することによって実現しています。
これはカーネル3.13からの機能のためUbuntuであればTrusty以降が必要です、CentOSの場合はカーネルを新しくする必要があるかと思います。(7だと3.10なので)
インストール
これはvarnish-modulesの中に入ってる一つなので、インストールについては前回の記事を参照ください。
tc-fqに切り替え
先程も述べたとおりtc-fqの機能を利用しているためこれを使うようにします。
tcについてはこちらが詳しいので参照ください(よくわかるLinux帯域制限)
まず、現在のqdiscを確認します
xcir@gw01:~$ tc qdisc show dev eth0 qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
eth0のところは環境によって変更してください(制限したいインタフェース)
今はpfifo_fastを利用していることがわかります。
次にfqに変更します
xcir@gw01:~$ sudo tc qdisc add dev eth0 root handle 1: fq xcir@gw01:~$ tc qdisc show dev eth0 qdisc fq 1: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 quantum 3028 initial_quantum 15140
qdiscがfqになりました。
これでvmod-tcpによる帯域制限が利用できます。
使い方
すごい簡単です。
例えばdatファイルに対して制限をかけたいのであれば
sub vcl_recv { if(req.url ~"\.dat(\?.*)?$"){ // 1000KB/s(8Mbps) tcp.set_socket_pace(1000); } }
とするだけです。
では、実際に動作確認をしてみましょう。
xcir@gw01:~$ curl http://*****:6081/10MB.dat > /dev/null % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 10.0M 100 10.0M 0 0 944k 0 0:00:10 0:00:10 --:--:-- 943k xcir@gw01:~$ curl http://*****:6081/10MB.dat2 > /dev/null % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 10.0M 100 10.0M 0 0 260M 0 --:--:-- --:--:-- --:--:-- 263M
10MBのファイルでやってみましたが、帯域制限に引っかかる方は943KiB/sなので大体8Mbpsでうまく制限されています。(10MB.dat)
対して引っかからない方では263MiB/sと制限されていないことがわかります。
注意事項
割りと簡単に使用できる帯域制限ですが注意する点がいくつかあります。
keep-alive時の制限の持続
この制限はtc-fqを利用してコネクション単位で行われます。
そのためkeep-aliveでコネクションを再利用した場合は当然のことながら制限は持続します。
なので例えば以下のように取得してみると
xcir@gw01:~$ wget http://*****:6081/10MB.dat http://*****:6081/10MB.dat2 --2017-02-09 20:02:10-- http://*****:6081/10MB.dat Connecting to *****:6081... connected. HTTP request sent, awaiting response... 200 OK Length: 10485760 (10M) [application/x-ns-proxy-autoconfig] Saving to: '10MB.dat' 100%[============================================================>] 10,485,760 943KB/s in 11s 2017-02-09 20:02:20 (945 KB/s) - '10MB.dat' saved [10485760/10485760] --2017-02-09 20:02:20-- http://*****:6081/10MB.dat2 Reusing existing connection to *****:6081. HTTP request sent, awaiting response... 200 OK Length: 10485760 (10M) Saving to: '10MB.dat2' 100%[============================================================>] 10,485,760 942KB/s in 11s 2017-02-09 20:02:31 (944 KB/s) - '10MB.dat2' saved [10485760/10485760] FINISHED --2017-02-09 20:02:31-- Total wall clock time: 22s Downloaded: 2 files, 20M in 22s (945 KB/s) xcir@gw01:~$
制限がかからないはずの10MB.dat2についても制限がかかっているのがわかります。
なので、例えば先程の場合であればdatファイルをダウンロードした後(8Mbps)にコネクションを再利用して画像などのリソースをダウンロードする場合は8Mbpsとなります。
なにかリセットする方法がないかなと0を指定しても特に解除されなかったため、vcl_recvの先頭で十分な大きさの指定をしておくと良いと思います。
sub vcl_recv { //8Gbps tcp.set_socket_pace(1000000); if(req.url ~"\.dat(\?.*)?$"){ // 1000KB/s(8Mbps) tcp.set_socket_pace(1000); } }
このようにして試してみると
xcir@gw01:~$ wget http://*****:6081/10MB.dat http://*****:6081/10MB.dat2 --2017-02-09 20:03:40-- http://*****:6081/10MB.dat Connecting to *****:6081... connected. HTTP request sent, awaiting response... 200 OK Length: 10485760 (10M) [application/x-ns-proxy-autoconfig] Saving to: '10MB.dat' 100%[============================================================>] 10,485,760 943KB/s in 11s 2017-02-09 20:03:51 (945 KB/s) - '10MB.dat' saved [10485760/10485760] --2017-02-09 20:03:51-- http://*****:6081/10MB.dat2 Reusing existing connection to *****:6081. HTTP request sent, awaiting response... 200 OK Length: 10485760 (10M) Saving to: '10MB.dat2' 100%[============================================================>] 10,485,760 47.7MB/s in 0.2s 2017-02-09 20:03:51 (47.7 MB/s) - '10MB.dat2' saved [10485760/10485760] FINISHED --2017-02-09 20:03:51-- Total wall clock time: 11s Downloaded: 2 files, 20M in 11s (1.81 MB/s)
と、意図したどおりに動作します
TLS終端を別MWで行いlocal接続しているケースに効かない
Varnishはhttpsを喋れないのでTLS終端をnginxやhaproxyやhitchなどで行いローカルで接続しているケースもあると思いますが
その場合はloインタフェースもtc-fqを適用する必要があります。
http/2利用時
http/2は複数のストリームが1コネクションに入っています。
そのため特定のリソース(=特定のストリーム)のDL時のみを帯域制限をかけたい場合には使用できません。
この場合全体での制限になります。
もちろん、特定の行動をしたクライアントの帯域を絞りたいという用途には利用できます。