🐳

同接1500人耐える配信サーバーをCloudflareを使って構築した

2025/01/09に公開

始めようと思ったきっかけ

Twitchのクローンサイトを作りたいとずっと考えていて、おおえのたかゆき(おえちゃん)さんが一般の配信サイトでは放送できないジャンルのコンテンツを配信できるサイトを探していると知って作成に取り掛かりました。なのでおえちゃんの元々配信していたサイトであるOPENRECにちなんで、サイト名はOpen放送室にしました。

前提

サーバーのプロバイダー: Linode
クライアント: Next.jsでUIはNextUI
バックエンド: Express.js

環境

  • NginxでRTMPとHLS配信
  • NginxでAPIとクライアントへのリバースプロキシ
  • 全ての通信はCloudflare経由

①初期段階の構成

第一回目のテスト放送での出来事です。
結果から言うと600人ぐらいで落ちました。

https://www.youtube.com/watch?v=vYggKz0j4Rg

サーバー

  • Ubuntu 4GB 2 Core (Dedicated Server)

Cache Rules

  • tsファイルを全てCache

落ちた理由

厳密に言うと、繋がらなくなったのはNginx経由のサービスだけでSSHやPingは通りました。
この部分でハマったんですが、原因を調べていくと、プロセスが開けるファイルディスクリプタの上限に達してしまうことで発生する “too many open files” エラーが起きていたようで、Nginxのコンフィグを以下のように変更したら解決しました。

worker_rlimit_nofile 70000;

②初期段階を踏まえた構築

満を持して挑んだ第2回目の放送でしたが、結果から言うと1100人ぐらいで落ちました。

サーバー

  • Ubuntu 4GB 2 Core -> 8GB 4 Coreへ

原因

今回ネックになっていたのはサーバーのCPUでした。
これさえ改善すれば完成だと思っていましたが、実際には…

③ほぼ最終構築

結局サーバースペックを上げてもほんの少し負荷に耐えられるだけで、結局同じぐらいの同接で落ちてしまいました。
なのでずっといじっていなかったCloudflareの設定を見直すことにしました。

Cloudflareの設定見直し

どれが直接的に影響を与えるかわからなかったので、とりあえず以下の設定を有効にしたところ、ページロード&サーバー負荷が軽減されましたが、これは要検証です

  • 0-RTT Connection Resumption
  • Cloudflare Fonts
  • Early Hints
  • Rocket Loader™
  • Tiered Cache TopologyのSmart Tiered Caching Topology

結果

Cloudflareの設定を変えたらとんでもなく軽くなりました。同接約1100人の時はページに繋がらなくなっていましたが、同接1500人でも安定して動作するようになりました。

④最終構築

これだけでも良さそうでしたが、念のためサーバーを増やして負荷分散することにしました.

メインサーバー

rtmp {
    server {
        listen 1935;
        ...
        push rtmp://172.xxx.xxx.xxx:1935/live;
        push rtmp://172.xxx.xxx.xxx:1935/live;
        push rtmp://172.xxx.xxx.xxx:1935/live;
        }
    }
}
...
http {
    ...
    upstream hls_servers {
		#least_conn;
		server 127.0.0.1 weight=4;  # Main server
		server 172.xxx.xxx.xxx max_fails=2 fail_timeout=5s;
		server 172.xxx.xxx.xxx max_fails=2 fail_timeout=5s;
		server 172.xxx.xxx.xxx max_fails=2 fail_timeout=5s;
	}

    server {
         # .m3u8
        location ~* ^/hls/(.+\.m3u8)$ {
            proxy_pass http://hls_servers;
            root /tmp/;
            
            proxy_hide_header Access-Control-Allow-Origin;
            proxy_hide_header Cache-Control;
            add_header Cache-Control "public, max-age=15";
            add_header Access-Control-Allow-Origin *;
            types { application/vnd.apple.mpegurl m3u8; }
        }
    
        # .ts
        location ~* ^/hls/(.+\.ts)$ {
            proxy_pass http://hls_servers;
            root /tmp/;
            
            proxy_hide_header Access-Control-Allow-Origin;
            proxy_hide_header Cache-Control;
            add_header Cache-Control "public, max-age=10";
            add_header Access-Control-Allow-Origin *;
            types { video/mp2t ts; }
        }
    }
    ...
}

配信の遅延について

  • 大体10〜15秒でした。

【番外編】また落ちた(DDoS)

ウンザリしつつもサーバーを確認しようと思ったらサーバーすらも応答しなくなっていました。
対処しようとした頃にはDDoSが終了していたものの、今後の攻撃に備えてCloudflareのFirewall Eventsで特定のIPをブロックすることで応急処置をしました。

UI周り

  • サーバー1はOpen放送室のHLSでサーバー2はメイン配信のHLSが流れていて、どちらかに障害が発生した時にいつでも切り替えられるようにしています。
  • メイン配信では独自のチャットを実装しない代わりに、Kickのチャットを埋め込んでいたので、それをこっちでも確認できるように、Open放送室専用のチャットとKickのチャットの2つを埋め込みました。参照

    管理画面

こだわったこと

  • サイトを使ってもらう際にユーザー登録が障壁になることはわかっていたので、登録を一切する必要なくチャットを送信できるようにしました。
  • 大抵拡張機能が作られそうな機能は予め実装しました。
    • ユーザー名が不要な人用のユーザー名非表示
    • コメ欄をOBSで透過することができるようにクロマキー設定

その他

Livekitを使わなかったワケ

_人人人人人人人人人人人人人_
>  極力安くしたい!!!! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
↑↑↑↑↑これが一番重要事項でした。
通常の同接を考えるとLivekitのFreeプランだと負荷に耐えられなさそうだったのもあります。
(低遅延を実現するために使いたかったけど高い!!!)
(セルフホストをする選択肢は後々になって気づきました)

Linodeを選んだ理由

圧倒的に通信料金が安かった。追加転送は$0.005/GB

Cloudflareの効果

合計のトラフィック量は数十TBでしたが、サーバー側に来たトラフィック量は1〜2GB程度でした。

チャットの実装

  • リアルタイムなことが重要だったので、Socket.ioを使ってチャット実装しました。
  • 荒らしが出るのは目に見えていたので、IPをもとにしたIPBAN機能付きです。

最初からやっておけばよかったこと

  • ファイルディスクリプタの設定
  • Cloudflareの設定

ランニングコスト

  • CDNでほとんどの通信はキャッシュされていたため、Linodeの通信制限(今回は5TB)には到達しなかったため無料
    ・サーバー費は最初のサーバーランニング時点では、約$36(USD)だったが、サーバーの追加云々で大体$70

この裏で起きていたこと

(どちらかといえばこちらが裏なんですけどね)
実は②からOpen放送室はミラー配信サーバーとして機能していました。
私がこのサイトを作ると同じタイミングで、小川楓太(別名ちいかわ社長)さんが超低遅延のサーバー(エンドレスストリーム)を構築していて、そちらがメインで使われることになりました。

初めて配信サイトを作ってみて

RTMPからWebRTCへの移行を検討する上で、改めて配信サイトは遅延が重要だと感じました。正直10秒程度の遅延でも話にならないレベルでした。
無限に通信量を使えるわけじゃなかったので、通信量を抑えるのに苦労しました(見れるギリギリのビットレート、CDNの利用、配信データのエンコーディング、プロトコルの選定など諸々)

感想

明確な目標がある状態でコードを書くのはとてもに刺激的で楽しかったです。同じことに取り組んでいるちいかわ社長もいてとても刺激になりました!!
このサイトを使ってくれたおえちゃんと視聴者の方ありがとう!

Discussion