[24日目] NAT Traversalって知ってますか

Cerevoアドベントカレンダー2016、最終日です。といっても、どうやら大トリは弊社代表が年末までに昨年のネタの更新版を出すようなので、私はトリらしい何かとかでもなく、テックブログらしく技術ネタを書きたいと思います。
まつけんです。CTOをしています。今日はハードはほぼ関係ない、ソフトというかUDP/IP、TCP/IPな世界の話です。IPレイヤーより上でのお話です。

まず、NATと言われて動作を想像できる方どれくらいいるでしょうか。今や、ルータという名でNATが動作する機器は各家庭にほぼ設置されているのではないかと思いますし、携帯向けネットワークも昨今はLarge Scale NATもしくはCarrier Grade NATの導入という形でちょっと話題になったようにNATが導入されています。そんな世界では、グローバルIPが直接振られるのではなく、ルータやキャリア側でローカルIPからグローバルIPへのアドレス変換が行われるのが一般的です。NATと言いますが、今挙げた例は、正確に言えば、IP masquarade(マスカレード)です。NATはNetwork address translationなのでアドレス変換をするというもう少し広い意味で、グローバルIPとローカルIPが1:1で変換されるようなケースも包含されますし、サブネットを分けたローカルネットワークでの1:1や1:Nでの変換などにも利用されることもあります。

今回は、最も一般的な多:1でローカルIPからグローバルIPへとソースアドレスを書き換えて通信を行う(グローバルIPアドレスを共有する典型例)、ほぼどんな環境でも導入されていると言っても過言でなくなった、いわゆるIPマスカレードされている環境でのP2Pでの通信を実現する、NAT Traversalのお話です。

NAT Traversalとは

まず、NAT Traversal、日本語で言うと、NAT越え(NAT超えかもしれません。ここでは越えで統一します。)はなぜ必要なのか。IPv4アドレスが世に溢れ、すべてのデバイスにグローバルIPが割り当てられている状態であればそもそもNAT越えは必要ないわけですが、現実にはIPv4アドレスは足りないし、ルータ下にいない直接グローバルIPをデバイスがもつというのはセキュリティ的にも推奨されない(デバイス自身がbindしているポートへの通信をローカルネットワーク内のパケットに限定でき、脅威となるデバイスやサーバが容易に直接のアクセスがしづらい)のが現実です。そんなNATがあふれる世界でもP2P通信をしたい、という需要を満たすためにNAT Traversalという技術(というか、手法でしょうか)が考えだされるわけです。これはもうかなり昔からあるもので、様々な資料がネット上にもありますが、一度、自分の整理のためにこの記事を書いてみている次第です。

ルータの下にいるデバイス同士が直接するのはなぜそんなに難しいのでしょうか。まず簡単な通信の流れを下記図を見ながら説明します。もっとも一般的な構成として、ルータ下のネットワークをローカルネットワーク、ルータの外に存在するネットワークをグローバルネットワークとして考えます。そのときに、ルータ下のデバイスがグローバルネットワークと通信とパケットをルータの外のネットワークにあるアドレスに送ります。そのローカルネットワークの内からNATを通して外へという形で出て行く分にはルータがアドレス変換をよしなに行います。これがIPマスカレードです。やることはそんなに難しくなく、TCPの場合、ソースになったIPアドレスとポート(つまり、デバイスのIPとポート)と、宛先になるIPアドレスとポート、ソースアドレス書換後のルータ自身のグローバルネットワーク側のアドレスとポートを、最初にデバイスからパケットが来た時に組み合わせで覚えます。その上で、ソースアドレスをNATが動作しているルータ自身のIPとポートに書き換え、宛先にパケットを投げる、宛先からパケットが返ってくれば覚えてるマップにしたがって、その返信パケットの宛先をローカルネットワークにいる通信元のデバイスに書き換えてパケットを送信します。こうすることで、デバイスはソースアドレス変換が行われたことを知ることなく、外のインターネットな世界と通信ができるわけです。

oneNAT
ここでNAT越えの話に戻ります。でも、宛先はあくまでグローバルIPでなければ(ルータを通してNATを通じて書き換えが行われなければ)、ルータは宛先を書き換える機能を発揮できません(通信双方のアドレスやポートを覚えて変換と転送ができません)。では、相手もNAT下にいる場合、そのローカルアドレスを指定するわけにもいきませんし(ローカルネットワークのアドレスなわけで別ネットワークにいるので届くわけはありません)、グローバルIPはルータがもっているわけなので、ルータ下に居るデバイスと通信するというのは極めて困難なわけです。

NATを越えてデバイス同士が直接通信するには

これの安直な解決方法のひとつがサーバーリレーです。要はデバイス同士が同じサーバに接続をして(この場合、サーバがグローバルIPを持っているのでどちらもルータ下のローカルネットワークから容易に接続ができる)その後の通信はサーバが介在して両者で通信を行います。ただ、この方式、TCPレベルで解決するには宛先を別途アプリレイヤーで指定する必要がありますし、なによりも、トラフィックがすべてサーバを経由するので、サーバの通信量が2倍、そして、サーバを経由するため経路としてはあきらかに冗長に長くなりレイテンシが悪化します。これは、P2Pでの典型的な例である、ビデオチャットやゲーム対戦の特性に対してあまりマッチしません。

ここまで前提を並べてきて、やっと本題にたどり着きました。それぞれのデバイスがルータの下にいたとしても直接パケットをどうにかして届かせたいという必要性が出てきます、それがNAT越えです。ここからはNAT越えを単純な手法から順に説明していきましょう。まずは極めて単純なUDP hole punchingです。これはあとから紹介しますが、特定の動作をするNATでしか通用しません。ここから、NATの種別ごとにどういう形で対応を増やしていくか、というのがこのNAT越えの議論のおもしろいところです。

fullconeNAT
UDP hole punchingは上記の図の通り、cone NATが対象である限り、NATのグローバル側のIPとポートさえわかれば、そして、それをサーバで観測すれば、その後はサーバからIPとポートをそれぞれが教えてもらい、そこに対して、UDPパケットを投げ込めば到達できるわけです。
ここで、唐突にでてきたcone NATを含め、NATの動作によって分類をまず行いましょう。

  • full cone NAT
  • address restricted cone NAT
  • port restricted cone NAT
  • sequential port smmetric NAT
  • random port symmetric NAT

というふうに考えると、NAT越えの方法と特性が分類されます。まずはconeとsymmetricの差を考えてみましょう。

cone NATはシンプルな動作です。ルータ下のデバイスがグローバルアドレスに対して通信を開始し、パケットを送信したとき、ルータはソースになるIPとポートを覚えるのみです。それにしたがって、ルータのグローバル側のIPとポートをマップします。つまり、グローバル側のIPとポートに対して、グローバル側にいるデバイスであれば誰でもパケットを送信し、それはルータ下のデバイスにパケットはアドレスを書き換え転送されます。したがって、下記のような流れを経れば、UDPによるP2P通信が可能になるわけです。

  • ローカルネットワークAに存在するデバイスをαとし、ローカルネットワークBにいるデバイスをβとします。αとβが互いにUDPパケットを直接送受信できるのが最終目標です。
  • αとβを仲介するサーバをSとします。まず、P2P通信を開始したいという意思をαとβで共有します。そして、通信を開始するぞとなるタイミングをSから受け取ります。
  • すると、αとβはまず、サーバSに対して、UDPパケットを送信します。すると、パケットはαとβそれぞれのNATされたグローバルのIPとポートを観測することができます(ソースアドレスはNATされたルータのものが見えます)
  • それをαの情報はβへ、βの情報はαへと、サーバから通知を行います。(たとえば、P2P通信開始処理の間は、サーバとの間にTCPがはられていてここでやりとりするとかです。)
  • αはβのルータのグローバルのIPとポートに対して、UDPパケットを投げます。βも同様です。
  • cone NATであるため、ソースアドレスやポートに制限はありませんから、それぞれのUDPパケットは無事、それぞれのデバイスに届くことになります。

さて、cone NATの場合、こういったサーバが介在してアドレスとポートを伝えるという処理はあるものの、まだまだ単純な処理で終わります。次はaddress restricted cone NATとport restricted cone NATを攻略していきましょう。

address restricted cone NAT, port restricted cone NATはそれぞれfull cone NATに比べて制限が増えます。マッピングするときに、ソースのIP、ポート以外に、宛先のアドレスを覚えるのがaddress restricted cone NAT、ポート番号までも覚えるのがport restricted cone NATです。それぞれ攻略をしていきましょう。

address restricted cone NATは割りと簡単です。要は送った宛先のIPからのパケットでないと受け取りません。でも、サーバに送ってしまえば宛先はサーバになるわけでサーバからのパケットしか受けないことになります。では、αからβに送ったパケットはどうしてもNATに阻まれるはずです。でも、ここでcone NATのもう一つの特徴が際立ってきます。
cone NATの定義のひとつに、NATの外側のポートはソースアドレスになるデバイスのポートに従います。すなわち、αがport 30000をbindしてパケットを送ったとしたら、基本的には、NATもport 30000を利用するのがcone NATです。ここからが実際のNAT越えの方法をまた流れに沿って説明しましょう。

α、βそれぞれがサーバに向かってUDPパケットを投げ、サーバはそのIPアドレスとポートを観測します。そして、それをαとβに伝える。
すると、βはαのIPアドレスとポートに対して、UDPパケットを投げます。ここで重要なのは、βはbindするポートをサーバにUDPパケットを送ったときと同じポートを使います。このとき、NATはfull cone NATであれば通りますが、address restricted portやport restricted portでは、サーバとIPアドレスもポートも違う訳ですから通りません。が、β側のNATにはαのIPアドレスとポートへのパケットが記録され、βのIPアドレスとポートは維持されるわけです。ここで、αがβのIPドレスとポートに対してパケットを投げたとします、そうすると、βはさきほどのパケットはNATに阻まれたとはいえ、NATのマッピングができあがっているため、αからのパケットはβに到達します。また、このパケットでも、αはサーバへのUDPパケットを送ったポートをbindしていればNATには同様のマッピングがβのIPアドレスとポートに対してできあがるわけです。これで、αとβはそれぞれのNATを越えて通信ができるようになりました。

ここまで、cone NATの場合のNAT越えを説明してきました。実はこれはすでによくある方式として、STUNという形で標準化されています。実際はユーザ認証なども含みますが、P2Pの経路開通を行う方式はまさにいままでの説明通りです。いくつかのアプリケーションではSTUNが実装されているので参考にしてみてください。

ここからが、難しく解決できない問題に対して考えて行く形になります。
Symmetric NATです。これはcone NATと違い、ポート番号が再利用されません。つまり、ソースとなるデバイスでポートを固定したとしても宛先によって、別ポートが割り当てられることになります。そのため、サーバで観測したとしても、そのポートを再利用する方法がないのです。

さて、この場合、どういった方法でSymmetric NATを攻略するかを考えます。Symmetricの何がツライかというと、NATの外側のポートを観測してもそれを利用できないことです。いや、それは本当でしょうか。観測できることではなく、NATでどのポートが利用されるかと言い換えられるわけで、つまるところ、NATのポートは当たるなら予測でも良いわけです。では、予測可能か、が重要になります。Symmetric NATと言われるのもを更に分類しましょう。といっても、NATの内(ソースとなるデバイスのポートは固定)から外への通信が起こった際に、ポートがシーケンシャルに変化するか、ランダムに変化するか、です。

つまるところ、方法としては、例えばこうです。ソースとなるデバイスからサーバに対して、複数のパケットを打ちます。その際、bindするポートを固定します。そして、サーバの受けるポートはパケット数分変化させます。ここでは3発のパケットをサーバに対して投げてみます。そして、サーバからポートを観測します。このとき、ポートがどう変化するかです。例えば、+1されていく、+2されていく、-3されていく、ランダムに変化する、様々なパターンが見えるはずです。ランダムに変化する場合を除いて、NATの特性を予測するわけです。+1される場合はどうするか、もう分かりますね。NAT下のデバイスが1つしかなく、ほかの通信がないと仮定すれば、αからβへの通信を同じポートをbindした上でパケットを投げます。サーバで観測されたポート番号+1されたβはパケットを投げ込みます。そうすることで、NATのグローバル側のポートは予測され、そこにパケットを投げ込めば、おそらくは疎通するはずです。

ここからが考えどころです。実際にはNAT下のデバイスは複数あって他のUDPパケットが送られてポート番号はずれるかもしれません。もしくは、同じbindしたポートから通信しても、宛先のアドレスが違えば外側のポートが変わるかもしれません。前者はリトライでカバーするか、複数のポートをつかってα、βから送ってどれかが疎通したらその後はそれを使うかなどいくつかの方法が考えられますね。宛先のアドレスが違えば届かないことを考えると、サーバも複数のIPアドレスを持ち、それぞれに対しておくったときのポート変化も観測する必要があります。このあたりから観測の方法がどんどんと複雑化していくため、これに対して、どう対処していくかがNAT越えの最大の難所です。いままで、cone NAT, SymmetricNATという類型で説明してきましたが、昨今、この類型に意味がないと言われる所以がこのあたりから始まります。(とはいえ、NAT Traversalという技術を理解するにはこの類型も私は有用だと思っています。)

とはいえ、越えられないひとつに、ランダムにポートが割り当てられるSymmetric NATは対処のしようがなさそうです。この場合のみサーバーリレーを行うというのが疎通する確率を100%にする一つの方法です。そうではなく、ランダムにポートを開けるNATに対するポート予測方法をなんらか確立できる手法もあるかもしれません。もし、すでに公知だよという場合はぜひ情報へのポインタを教えてください。

ここまでで、UDP hole punchingのなんとなくの基本を学んできました。cone NATへの対処はSTUNの仕様、規格を読んでみてください。たぶん、この文章よりはよっぽど分かりやすいです、ここまで読んでいただく方にそう言うのもアレですが。また、実際、Symmetric NATの場合の方法としては、 https://tools.ietf.org/html/draft-takeda-symmetric-nat-traversal-00 を読むのがおそらく一番詳しく丁寧です。そういった意味でも、ここでの解説は初歩の初歩です、じゃあどう実装するんだというのから、サーバで何を観測するのか、そもそもNATが複数段あったらこの手法は果たして通用するのか。観測を複雑にしていけばいくほど、開通までの時間がかかります、それは本当にターゲットのアプリケーションにとって許容できるのか。Large Scale NATではこの穴開けが通用するのか、キープアライブはどうするか、セキュリティ的に必要な相手のパケットだけを見分けられるのか、認証はどうするか等、現実世界では、この抜け穴のような手法に対しての課題は山盛りです。そんなところの実地調査として、UDP hole punching自体はあまり表に見えない手法ではありますが意外なところで結構導入されていたりしますので、とあるゲーム機なんかで、P2Pで対戦通信しているな?とおもったときに、パケットキャプチャしてみるのもオススメです。

さて、最後に、いままでは、UDP hole punchingをベースに説明をしてきましたが、ではTCPの場合はどうなるでしょうか。まず、TCPにはcone NATという概念はまずあり得ません。それは、宛先との通信はステートをもって(つまりシーケンス番号を使って)通信されるため、宛先を限定しないポートマッピングというのはほぼ意味がありませんし、ほかのパケットを転送するというのはただのセキュリティホールです。なので、通常は、上記類型で言えば、Symmetric NATしか存在しません。また、さらに面倒くさいところが、TCPの通信シーケンスの始め方です。接続元はSYNパケットを送り、接続先はSYN、ACKをセットにして送ります、そして、接続元からACKが送られて、はじめてTCPでの通信は始まります。つまり、UDP hole punchingのように最初の一発のパケットはなかったことにするわけにはいきません。あくまで、SYNパケットは両者に届かないわけにはいかないのです。この通信開始の流れを3-handshakeと呼びます。

でも、それでもTCPでNATを越たい、という人にはヒントはあります。まず、TCPでの仕様では、3-handshakeだけでなく、4-handshakeでも問題なく通信は開始できます。つまり、αからSYN、βからもSYN、その後、双方からACKが送信されるようなケースです。ここから見えてくるのは、要はSYNをUDP hole punchingの最初のパケットとしてつかって複数送ったとしてもひとつだけ届けば通信は確立されるということです。これは、どこかで少し話題になりましたが、TCPの仕様に実際、TCP Simultaneous Openという名前で記載があります。ここまでくればあとの考えは分かりますね。これで、Symmetric NATと同じ手法でNAT越えはおそらく可能になります(これは私は実際に実装したことはないので、たぶん)

さて、というわけで……。ざっと、通常言われるNAT Traversalの手法の入り口を紹介してきました。STUN以上のもので簡単な実装を見られるものはいくつかあります。また、Symmetric NATへの対処の実装も、いくつかGitHubを漁ればでてきます。 https://github.com/zerotier/ZeroTierOne など。

とはいえ、NATの越方は様々、この上でどういう通信をするかも様々です。UDPでしか通信できない経路ではアプリケーションでは使い勝手が悪い場合もあります。そういう場合には、TCP over UDPを実装してあげてVPN的に使うのもよいかもしれません(OpenVPNが使っているようなTUN/TAPデバイスという仮想トンネルデバイスはLinuxではよく使われますね)また、NATに関わらず通信するためのサーバリレーの方法としてはメジャーなのがTURNを使った実装でしょうか。越えられなかったらTURNをつかってサーバリレーをするというところまでフローを実装するとより使いやすくなります。また、今回は出てきませんでしたが、ルータに実装されているUPnP実装では静的なポートマッピングが可能なものが多く存在します。UDP hole punchingとは別の方法で、NATの外→内の通信を可能にできる方法なので、こちらも組み合わせるとより多くのNATに対応できるはずです。でも、この場合も複数段NATがある場合はじゃあどうする、みたいなことがなかなかに難しいポイントです。まずは、ローカルネットワークとグローバルネットワークを見分けるような方法を実装しなければなりません。方法はいくつかありそうですが、ICMPでTTLを変化させて使うとできそうな気もします。要はtracerouteですね。

昨今、IPv6も徐々に普及し始め、NAT Traversalは不要になるかと思っていましたが、NATはIPマスカレードではなく、1:1のアドレス変換として生き残る雰囲気も少しだけ感じます。そんな中で、意外にNAT Traversalは必要とされる技術なのかもしれません。WebRTCは特性上、P2P通信ができれば好ましく、NAT TraversalもSTUNなどを使って行われるケースもあるようです、同時にTURNも使われる例の最たるものとしても。

今後どうなっていくか

さて、最後に、将来はというところを少し。これまでは、UDP/TCPの話をしてきました、それ以外もあります。SCTPが一番最有力なのではないかと思います。現状、メジャーではないものの、すでに実装はLinuxではしっかり入っていますし、途中経路のルータさえしっかり対応すれば(これが一番大変なんですが)、様々な課題を解決しています。ここから、SCTPを解説しはじめると、この記事はいつになっても終わらない形になってしまいますので、NAT Traversalにまつわるアレコレはここまでで一度締めさせていただきます。年末年始の空いた時間でSCTPを調べていただくときっと楽しいのではと思ったりしています。

最後に

この記事が果たしてアドベントカレンダーの最後に相応しかったかどうかはよくわかりませんが、NAT Traversalという古くからあるけれどマイナーな、でも、結構有用だと思っている技術の導入となる方がいたりすると幸いです。
途中から図がなくなったのは気力が間に合わなかったので、後で追加できたらしたいなと思っています。

[1日目] 高価な市販NASを買わずに、HDDがクラッシュしても大丈夫な運用しやすい社内のファイル共有サーバを構築する方法

唐突ですが、アドベントカレンダーというのを皆さんご存じでしょうか。
アドベントカレンダーとは……本来の意味のほうではなく、ネット上で数年前から行われている企画で、12月1日から24日まで連続して行われるリレーブログのことです。特に技術系アドベントカレンダーということで、プログラミングに関する内容などで行われていることが多いですが、今回、Cerevoでもテックブログでやってみることにしました。テックブログではありますが、基本、書く内容は完全自由に、それぞれのメンバーが書きたいことを書くことにしました。きっと多彩な内容が展開されるとおもいますので、乞うご期待!

というわけで、トップバッターを務めます、アドベントカレンダーできるくらいに人数が増えていることにまだ慣れない、まつけんです。最初の話題から全くハードウェア開発は特に関係なく、社内で使うNASをどうしていくかという通常進行の内容です。

弊社も気がつけば70名を超え、以前とは比べものにならない人数になってきました。そうなってくると、様々なポイントでいままでふんわり適当にやってた部分に問題が出てきます。その一つが社内のファイル共有用NASです。

とりあえず、昨年の10名程度の時点までは、NASといえば、Centuryのエンクロージャに過去に使って余っていたHDDを詰めてただ動かしていたくらいです。とはいえ、巨大なデータの受け渡し用&大容量データの一時的な置き場といった感じで、クラッシュしてもデータは諦めてね、バックアップは各自でよろしくね、くらいノリでものすごいテキトーな運用です。

実際、これとは別にチャットワークに適宜ファイルをアップロードしたり、GoogleDriveにファイルを置いたりで大抵賄えてしまうので、そんなに活用されていないというのが実情でした。大事なデータ(ソースコードなど)はGitかMercurialのリポジトリがAWS上で運用されていて、そちらは日々のバックアップも取られているので、そちらに置きましょうという運用です。(こちらは最近Bitbucketに移行しつつあります。)

これが70名程度になると、そもそも扱われるファイル数も一気に増え、チャットワーク、GoogleDrive、リポジトリにファイルを分散させると容易に目的のファイルを見つけられないし、アップロード先によっては欲しい人がアクセスできるわけでもないという状況に陥ります。そうすると気軽にアクセスできる本格的に社内向けにNASが欲しいということになってきますが、NASを入れるにしても、この状況では容量、アクセス速度、同時アクセスする人数もかなり増えますし、HDDクラッシュしたのでデータ消えました、てへ、なんてノリもそろそろ許されないよなぁという壁にぶち当たります。とはいえ、Cerevoには未だ社内システムをちゃんと担当するような人なんていません(私が片手間でできる範囲で、という域をいまだに超えていません)

そこで、いくつかNASについてどうするか、案をざっと出します。

  • Drobo, QNAPくらいの小規模オフィス向けNASの導入
  • AWSのStorage Gatewayを導入
  • 余っている社内の自作PC的サーバでファイルサーバを作る

これらの案は、基本、コストが初期費用が十数万円くらいで、月額も数万円単位くらいは考えた方が良いレベルか、コストは低いけど、自分でファイルサーバ運用は単に手間の問題でそんなことやってる暇があったら……と、たぶん私が怒られるというところでどれもちょっとさくっと導入というわけにはいかなそうです。

ということで、どうしたもんかなーということでふと思いついたのが、NASとか買わずにPCにHDD1つだけ接続して、履歴付きでDropBoxと同期するだけでいいんじゃね?って案です。HDDはクラッシュしたら諦めて換装して再同期、PCでファイル共有ならパフォーマンスに問題は出にくい(Core-i5とかメモリも多めのハードウェアを選べるし、アプライアンスに比べれば価格対性能比は良くなる)っていうところで。

結果としては以下のような構成にしました。

  • ちょうどMac miniを余らせていた同僚さんがいたのでそちらを中古で買い取り、内蔵のSSD + 2.5″ HDD 250GBのものをHDDだけ2TBのものに換装(PCじゃなく、Macになりました。)
  • DropBoxで履歴付きProを契約して、HDD上のフォルダを指定して同期
  • OS X Serverを入れてファイル共有を有効にする

これだけやってしまえば、とりあえず1TB分はDropBoxで履歴付きで同期して保存してくれるので、特にデータが消えるリスクにおびえなくてよくなります。別にMac miniでなくても良いのですが、Core i5でメモリも4GBとか載っているのでNASとしては充分に高速です。HDDがクラッシュしてしまった場合は換装して同期し直せば復旧できるお手軽さです(そうしたときに、同期が終わるまで使えないのが玉に瑕ではありますが、まあ滅多に起こることではないので今回は諦めます。)

あと、デメリット的なところでは、回線によっては初期の同期にものすごい時間かかります。途中までLimitがデフォルトでかかっていることに気づかず、このままいくと同期に20日くらいかかるのではって危惧したのですが、Limitを外したところ、10MB/s前後出るようになり、2日ほどで同期できました。

同時に、気をつけたほうが良いのが、制限の少ない法人向け回線などを使っていればよいのですが、スタートアップにありがちなフレッツ+個人向けISPみたいな組み合わせだと、アップロードの総量規制にひっかかって同期できないという悲しいオチが起こるかもしれないという部分です。実際、初期同期は今回は500GBくらいを2日でアップロードしたので、1日あたり250GBアップロードしていて、大体の個人向けISPでは確実に規制対象です。今回はそういった制限のない回線(So-net NURO BizをCerevoでは契約しています)を使っているので特に問題は出ていませんが、制限のある回線環境ではヤバそうです。ゆっくりのんびりアップロードするとか、制限のないところで同期だけ先にやっちゃうなどの対策が必要かもしれません。

ついでにこれをやると、副作用として、外からアクセスしたいときにVPNでつないだりしなくてもDropBoxで共有してしまえば割と簡単にファイルにアクセスできます(セキュリティ上、これは問題があると言われるとなかなかツライところなので、あくまで緊急用という運用に留めていますが)

これに加えて、1TBを超えたらどうするのよって話もあるので、最近、Amazon Cloud Driveへのバックアップも追加しました。ここは最終的にどうしていくかをさらに良い手をこれから考えたいところですが、今のところ、rdiff-backupを走らせた結果の30日分のバックアップを単純にAmazon Cloud Driveにアップロードしています。こういったバックアップとかもOSX上でスクリプト(acd_cliは便利でした)を書いたり、バックアップソフトをOSX用があれば簡単に動かせてしまうというのも、この構成の考えてはいなかった意外なメリットかなと思います。rdiff-backupじゃなく、1日ごとの世代の完全バックアップのほうが後から楽かなーというところを今はちょっと迷っています。

この運用であれば、クラッシュ時のHDD換装以外は特に手間をかけることなく、そこそこ高速なNASと最低限のデータ保護が実現できます。AmazonCloudDriveのバックアップだけにするか、DropBoxのビジネス向けプランにしてしまえば容量制限はなくなるので、ローカルのHDDを換装して8TBか、もうすこし経てば10TBくらいまでは割と現実的な価格で運用ができそうです。

NASって言われると最低でもTeraStationとかQNAPとか買ってしまいそうになりますが、コスト優先、手間もかけたくないスタートアップ的な運用という意味では、PC+DropBoxのほうが何かと楽で低コストになりそうなのでは、というお話でした。

LiveShell PROのRTSPサーバー機能を利用して、Wirecastの入力ソースの1つとして扱う方法

まつけんです。

今回は、新たにリリースされた新機能であるLiveShell PROのRTSPサーバ機能を利用して、Wirecastからの利用方法、Windowsの仮想カメラとして認識させる方法等のRTSPサーバの活用方法をご紹介します。

RTSP対応って何?

とりあえず、今回のRTSP対応って何?というところを簡単に説明しておきます。
RTSPっていうのはプロトコルの名前で、そこのところの詳しくはWikipediaで、という感じではありますが、もう少しユースケース寄りの例をだして簡単に説明をば。
いままでLiveShellといえば、Ustrem, ニコ生, 独自のRTMPサーバへの配信という対応を謳っていましたが、並べているサービスを見ても最後に独自のRTMPサーバと書かれている通り、対応しているプロトコルという観点から見ると、要するにRTMPサーバーへ配信できる、という機能をもっていたことになります。詰まるところ、LiveShellシリーズはRTMPプロトコルに対応したクライアントですので、当然配信にはその配信を受け、実際にエンドユーザにライブ配信を届けるサーバを用意するのは必須ということになります。
という状況の中、実際に販売してからのフィードバックの一部として、

  • LiveShellに入力されている映像をiPhone,iPadもしくはPCから直接見たい
  • PCの入力として扱いたい(Wirecastなどのソフトの入力ソースにしたい)

というような要望をたくさんいただきました。
そういったご要望にお応えするための対応としての回答が今回のRTSPの対応になります。もうちょっと言葉を正確にすると、RTSPサーバになり、直接クライアントに動画を配信する機能に対応しました、というのが今回のファームウェアアップデートの具体的な内容、ということになります。つまり、視聴できる人数は限られるものの、ライブを視聴するのに、LiveShell PROだけあれば配信ができるようになったわけです。

今回のアップデートの概要や背景はそういうことでして、じゃあ、実際、RTSPサーバを活用する方法ってどんな感じなの?という部分を下記でご紹介していきます。
※ 直接閲覧に関しては、特にこの記事では言及していませんが、こちらのマニュアルを参考にしていただいて、各OS上でプレーヤをインストールしていただき、URLを入れれば再生ができるはずです。Windows, Mac, iOS, Androidに関しては、VLC, GoodPlayer, MX動画プレーヤーを使って実際に動画を視聴できることを確認しています。

Wirecastへの対応

まず、今回のRTSP対応には、映像のみとはなります(VLCなどのプレーヤを使えば音声も受信/再生ができます。)が、IPカメラのAXIS M1104互換の動作をするような実装も含んでいます。そのため、Wirecast Pro, UstreamProducer Studioが対応しているIPカメラをソースとする機能を使って、入力ソースの1つとして認識させることが可能です。そうすることで、複数のLiveShell PROを用意すれば、Wirecast上でLiveShell PROの映像をスイッチングしながら配信といったことも可能となりますし、WirecastにはVirtualCamera機能もありますので、さらに別のソフトへのソースとすることも可能になってくるかと思います。(ただし、台数分の720pのH.264を受信してデコードすることになりますので、そういった複数台のスイッチングはそれなりに高性能なマシンでWirecastを動作させる必要があります。)
LiveShellPROでこの動作をさせる場合はシンプルで、LiveShell PROをRTSP待ち受け状態にし、Wirecast側でソースとしてIPカメラを追加します。そのときに、IPアドレスにLiveShell PROのIPアドレス(RTSPのURLからIPアドレス部分だけを抜き取ってください。)を入力し、カメラの種類を選択のところで、「AXIS M1104」を選択します。そうすると、ソースとして認識され、プレビューが表示されるはずです。

wirecast_ipcamera

Wirecastを使わずに、Windowsにカメラとして認識させる方法

とこれだけで終わってもあまりおもしろくないというのと、Wirecast Pro, Ustream Producer Studioを用意するのは、$500-$1000程度の費用がかかるのでもうすこし安い方法欲しいよねというような場合もあるかとおもいますので、LiveShell PROをFlash Media Live EncoderやWirecastの入力ソースとして利用できる、もう少し安価な方法を以下でご紹介したいと思います。こうすることで、PCに内蔵されていたりUSBやIEEE1394で接続されたほかのカメラとスイッチしながら、PCで配信するというようなことを実現できます。

さっそく具体的な手順ですが、簡単に箇条書きすると以下のような形になります。

  1. LiveShell PROをRTSPサーバとしてセットアップし、オンラインにして、RTSP接続の待ち受け状態にします。(詳しくは、マニュアルを参照してください。)
  2. PCにe2esoftのVCam,VCam VLC plugin, VLCをインストールします。
  3. VLCにVCam VLC output pluginを認識させ、出力用プラグインとして選択します。
  4. VCamの入力ソースとして、External Sourceを選択してキャプチャを開始します。
  5. FMEを起動し、映像の入力ソースとして、e2esoft VCamを選択します。
  6. これでVCamにLiveShell PROから受信した映像が入力され、キャプチャデバイスとしてVCamが振る舞うため、FMEのソースとして利用できているはずです。

すこし複雑ですが、これが今のところ、試した限りでは一番安定している方法ではないかと思っています。
こういったソフトだと、もうすこしメジャーなWebcamXPやWebcam7などのIPカメラを仮想カメラにしてしまう機能を使えればよかったのですが、どうもH.264/AACだからなのか、ソースとしてうまく認識させられませんでした。RTSP/RTPをうけて、WDMなキャプチャデバイスとして見せられるもっとよいソフトがあれば是非おしえてください。

(追記)ここから —

e2esoft VCAM以外にもvMixという選択肢もあります

上記以外の仮想キャプチャデバイスとして動作するソフトとして、vMixというソフトも問題なく動作するようです。
Free版でもRTSP入力自体は動作するようですので、無料でも始められる分こちらがよいかもしれません。

Add Inputを押して、入力の選択肢のタブから、RTSPを選択肢、URLを下記手順に記載されているのと同様に入力します。設定として、Buffer, Low Latency Mode, TCPの3つがありますが、特にBufferの設定が重要で、できれば800ms程度の値を入力しないと映像が正しくキャプチャできない場合が多かったです。また同様に、Low Latency Modeにチェックを入れてもうまくキャプチャはできないようですのでこちらのチェックも外していただく必要があります。TCPに関しては、チェックのON/OFFどちらでも動作するようですので、環境に合わせて必要であればチェックを入れてください。

こうすると、Inputの一つとして、LiveShell PROがInputとして追加されます。これを選択した状態で、Externalをクリックして有効にすると、キャプチャデバイスとして、vMix Video Deviceというのがあるので、これをFMLEやWirecastの入力として指定すると、映像と音声がキャプチャできるはずです。

こちらのほうが、操作が簡単で、無料なので、e2esoftのVCamよりもオススメかもしれません。e2esoftはVLCを使うので、細かい制御が効きやすく極めて低遅延で受信したいなどの要望がある場合は、VCamのほうが要望にかなうかと思います。

(追記)– ここまで

それでは、それぞれのステップを具体的に解説していきます。

1. LiveShellをRTSPサーバとして利用する

こちらはマニュアルに記載がありますので、そちらを利用してセットアップをおこなってください。RTSPサーバとして起動している状態では、右下のボタンを5回押すと、RTSPサーバとしてのURLが表示されます。この時点ではこのURLをまずはメモしておいてください。VLCなどで、メモしたURLで再生がちゃんとできるかなどを確かめておくとよいかもしれません。

2. e2esoft VCam, VCam VLC output plugin, VLCをインストールします。

肝になるのはこのe2esoft VCam(http://www.e2esoft.cn/vcam/
)です。上記にも挙げたように、ほかにもこういった仮想WebCamのソフトはいくつもあるのですが、こちらで試した限り、RTSP/RTPでH.264/AACをうまく受信してデコードできるようなものが意外に少なく、有料($29)ですが手元ではうまく動いているのでこちらを紹介します。WebcamXPなどがうまく動くかと期待していたのですが、どうも現時点ではうまく動かないようです。

まずは、WaterMarkがついてはしまいますが、Free Trialが可能なのでこちらからダウンロードをして試してみてください。インストール手順などは割愛しますが、そのまま日本語でインストールすると、なかなか奇妙な翻訳にUIがなってしまい、抵抗がなければ英語での利用がよいかもしれません。

$29で買うか、ちょっとお得なセットで、最後に紹介する仮想サウンドカードとセットで買って、$50でシリアルコードを入力すると左上のWaterMarkは表示されなくなります。

3.VLCにVCam VLC Pluginを認識させます

また、同時に、VCam VLC pluginもインストールします。こちらは同梱されているREADMEにも書かれていますが、VLCがインストールされたフォルダー(通常であれば、C:\Program Files (x86)\VideoLAN\VLC\plugins\video_output)に、libvcam_plugin.dllをコピーして、VLCを再起動します。

vlcplugin

その後、VLCのツール→設定を選択し、サイドバーからビデオを選びます。設定項目のなかに、出力を選択できるところがダイアログの上部にあるので、デフォルトとなっているものをe2eSoft VCamとして選択をします。

vlcpluginsetting

この状態で、VLCのメディア→ネットワークストリームを開くから、さきほどメモしたRTSPアドレスを入力して再生をします。

networkopen_rtsp

4.VCamでVLCをExternal Sourceとして取り込みます。

その状態までくると、次はe2esoft VCamを設定します。まずは、VCam側の設定を一部調整します。解像度とFPSをLiveShell PROの設定に合わせます。今回は、光回線/動き有線のプリセットになっているため、解像度を1280×720(16:9)に、FPSを30に設定しました。

VCam_setting

その後、e2esoft VCamのSourceから、External Video Sourceを選択します。その状態からStartボタンを押します。そうすると、VCam側にVLCで再生しているのと同じ映像がプレビューされるはずです。

vcam_external_source

5. FMEを起動して、映像の入力ソースとしてe2eSoft VCamを選択します。

FME

これでLiveShell PROからの映像がFMEに正しく認識されていれば成功です。Wirecastなどを使えばほかのソースとスイッチさせながらつかえるでしょう。

ただ、こういったソフト全般に言えることですが、Webカムなどに比べると、RTSP経由でH.264/AACをデコードした上で、再度メモリ経由でドライバにデータを渡していることになるので、それなりに負荷がかかります。たとえば、手元の環境でためしたところ、Core i5 2500K 3.3GHz程度のデスクトップマシンで、1台のLiveShell PRO(光回線/動き優先)をカメラとして追加して、FMEでUstreamに配信したところ、CPU使用率は30-40%を使うような状況です。実際の利用では高スペックのマシンでの配信をオススメしたいところです。

RTP over RTSPによるNAT越え

また、今回のRTSP対応には、RTP over RTSPの対応の実装も含まれています。そのため、NAT越しに映像を取り込みたいというような場合には、クライアント側でRTP over RTSP(VLC, Wirecastは対応しているようです。)に対応さえしていれば、ルータでポート554番をポートフォワード設定さえしてもらえれば、インターネット越しのアクセスなどにも容易に対応できます。
VLCの場合、「入力/コーデック」→「デマルチプレクサ」→「RTP/RTSP」の「RTPオーバーRTSP(TCP)の使用」を有効にすると、RTP over RTSPが使われるようになり、UDPでの通信ではなく、RTSPの554番だけで通信が完結するようになります。Wirecastは自動的にRTP over RTSPも利用してくれるようです。
これを使えば理論的には遠隔地の複数のカメラを切替ながらWirecastで配信ということも可能になるかとおもいますので、更にバラエティに富んだ配信などにも活用いただけるのではないかと思っています。

今回の手順ではとりあえず映像のみがキャプチャされる形ですが、これに加え、同じe2esoftのVSCという仮想サウンドカードソフトを使えば、VSCを起動した状態で、VLCのサウンド出力をVSCのSpeakerに向け、FME側でVSC Line-inを選択すれば音声も同様にFME側でキャプチャすることができるようになります。すこし手順は複雑ですが、これでLiveShell PROをWebCamと同様にWindowsから扱うことができるようになります。

最後に

以上がRTSPサーバを活用して、さまざまな配信に利用する方法のご紹介になります。
注意点としては、あくまで、通常のUSB接続等のカメラに比べるとどうしても遅延(1秒弱程度)が出てしまう分、用途はすこし狭まりますが、様々な利用方法があるのではないかと考えています。活用方法のアイデアなどありましたら、是非ブログ等で公開いただければ幸いです。

おまけ

ホントは技術ネタを扱うブログですし、なぜRTSP/RTPというプロトコルを選んだか、という長大な文章もこの後書こうかとおもいましたが、あまりも冗長だったので、また機会がありましたらご紹介できればなーとおもっています。

LiveShellシリーズ用、インターネット経由操作APIを公開しました

突然ですが、LiveShell/LiveShell PROのDashboard機能を開発者の皆様にも使っていただけるようAPIとして公開しました。
詳しくは、こちらのドキュメントを見ていただければ詳細が書いているので、是非つかってみてください。また、このAPIをつかった簡単なデモがこちらにありますので、お試しください。Dashboardの一部機能だけを抜き出したものとなります。
と…これだけではシンプルすぎるので、詳細をば。

まず、今回のAPIは、アカウント初期登録やデバイス登録の流れに関しては、既存のDashboardを使っていただく前提で、そういった部分はAPIとしては提供しておらず、それ以降の操作部分に該当する、すでに登録されたLiveShellに対しての様々なリアルタイム操作をWebSocket API経由でもできるようにするものです。ですので、上記にも紹介したデモは機能としては、配信開始/停止や録画開始/停止などの操作ブラウザ上で行うサンプルとして実装しています。
もちろん、ドキュメントには各種設定のコマンドも公開していますので、たとえば、入力の切替、ビットレートの変更など既存機能のほとんどがAPI経由でも操作できる形となっています。
また、技術的な面では、認証や自分のデバイスID取得などはHTTPS経由の取得になりますが、リアルタイム操作の部分はWebSocket接続を利用したものになります。ですので、コマンドの送信やデバイスのステータス取得のためには、WebSocketでの接続が必要となってきます。とはいえ、最近はArduinoなどでもWebSocketはポーティングされていますので、たとえば、録画開始/停止ボタンや今配信中かどうかをLEDなどで表示するというようなフィジカルコンピューティング的なガジェットもこのAPIを使うことで実現できるのではないかと思っています。ぜひどなたかにチャレンジしてみて欲しいなぁとDashboard開発者一同で思っております。

さて、デモに関してはとりあえず作ったという部分も大きく、操作方法など少々分かりづらいかとおもいますので、簡単に下記にご説明します。
また、すこしだけ技術的な解説もいくつか入れていきますので、お付き合いいただければと思います。

まず、上記のデモのURLにアクセスします。
すると、下図のような画面が開かれるかとおもいますので、すでに登録済みのDashboardのアカウント情報として、メールアドレスとパスワードを入力し、get deviceボタンをおします。
websocket_demo1

そうすると、現在、Dashboardに登録されているデバイスIDの一覧が取得できます。複数登録されている場合は、その中から操作したいデバイスを選択します。
※このデモでは、該当デバイスIDのLiveShellはすでに配信中にしておいていただく必要がありますので、その操作は事前に行っておいてください。
websocket_demo2

その後、Connectボタンをおします。
すると、received dataのところに、”websocket connected, sid=18106350271236523123”というようなメッセージがでるかとおもいますので、それがでると接続に成功しています。その状態で、すこし待つと、定期的にデバイスから送られてくるステータス情報が受信され、画面右端のボタンがデバイスの状態にあわせて、操作出来るボタンのみに数が減って変化するかと思います。
websocket_demo3

そして、おそらく配信状態であれば、live stop, pause, recordという形で3つのボタンがならんでいるかとおもいますので、ためしにlive stopをおしてみてください。すると、デバイス側で配信が停止されるはずです。また、sent dataのほうには、実際に送信されたコマンドが表示されているはずです。
{“command”:”BOX_STOP”,
“value”:””,”uid”:”HIphHZ636i8ASXXXXXXX”,
“_dst”:”1810635asdasdasd1231″,
“_channel”:”send_command”,
“protocol”:”tcp”,
“cmd_type”:”basic”}
これらが実際にWebSocket上にながれているコマンドとステータスになります。
websocket_demo4

上記のとおり、LiveShellとのやりとりは1本のWebSocketを利用して、基本的に非同期に行われます。
動作としては、LiveShellは一定秒数毎もしくはコマンド受信直後、本体操作直後にステータスをWebSocketに接続しているクライアントに対して送信します。
また、クライアントは行いたい操作がある場合は、JSON形式のコマンドをWebSocketに対して送信します。
その結果は、その直後に変更されたステータスが差分として送られてきます。それによって、操作の成功/失敗を判定するというような動きを想定しています。

上記のように、いわゆるHTTP APIのみで実現されているわけではありませんが、これでリアルタイムにLiveShellがコントロールできます。是非、ご活用ください。

カテゴリー: Web

AWSを活用して全国ネット番組(NHK)によるアクセス急増を乗り切った具体的手法

まつけんです。

前回のWBSでの紹介に引き続き、今回はNHK「サキどり」にCerevoを紹介いただける機会に恵まれました。というわけで、前回同様に急激なアクセス増が放送日に集中することが予想されるので、いくつかの負荷対策をして乗り切りました、という話の第二弾になります。
今回はある程度の対策する時間があったため、前回の構成よりも急場しのぎでない対策をせっかくなのでやりました。そういった表示の高速化も含めたいくつかのテクニックを紹介したいと思います。
ちなみに、結果だけを先に書きますと、この対策をしている状態であれば、m1.small x 2台(ELBの配下に居ます。)がカスタムオリジンとして存在しているだけで、まったく問題なく放映時のアクセスをCloudFrontが捌いてくれました。(CloudFrontのキャッシュ時間が適切であれば、オリジンの性能があまり関係ないのは当然ですが。)

今回、アクセス増に対応するべきサイトの対象を再度確認しておきます。
・コーポレートサイト
コーポレートサイトに関しては、テレビでは明確にURLが詳細に紹介されるわけではないので、Cerevo,セレボといったキーワードで検索経由でのサイトへのアクセスが増えると考えられます。そうすると、やはり矢面に立つのはコーポレートサイトです。
こちらは実は特に大きな対策は施しておらず、CloudFront + S3 originで構成されており、変なことが起こらない限り、アクセス増大に対応してくれるはずです。こちらのページは下記のような対策ができておらず、できれば近々行うつもりのリニューアル時には同様の対策を入れたいなと思っています。

・LiveShellサイト
おかげさまで先日、HDに対応したLiveShell PROを発表しまして、製品ページは2製品分に増えていますが、LiveShell Dashboardと呼ばれるコントロール部分はLiveShellシリーズ共通です。こちらは製品購入後、ユーザー登録とLiveShellの初期設定がをして始めてアクセスされるページになるので、TV放映のような場合にはこの動的な部分のアクセスはほぼ伸びないというのは前回のWBSにて分かっていたことなので、対策は主に製品ページに集中して行う形にしました。
実際には、動的な部分やリアルタイムコントロールの部分に関しても、それなりのチューニングが施されていて、同時接続数は発売当時にくらべて大分余裕をもてるようにはしています。

というわけで、前回同様、LiveShellの製品ページを中心にアクセス急増に対する対策として以下の4つを行いました。順々に紹介していきます。

CloudFront + カスタムオリジンで対応

一番大きな変化点は、前回のようにS3とEC2でなんとか乗り切るという対応から、いわゆるCDNなCloudFrontを利用する形に軸足を移しました。
いくつかの理由はありますが、そもそもLiveShellの取り扱いが米国、北欧などで伸びてきており、日本以外のアクセスが増えて生きていること、また、カスタムオリジンでnginxからコンテンツを出せるので、Expiresの設定など細かなヘッダを割と自由に制御できることなどが理由です。

まずは、CloudFrontを利用するにあたっての構成について説明をします。
前提として、製品ページは基本的に開発時は動的ページとしてmakoテンプレートで書かれたものをPythonでHTMLとして出力するというかたちになっています。これは、日本語、英語の言語ごとにページを用意するのが大変なので、翻訳キーの置換でどうにかしたいということや下記の画像ファイルのURLにはリビジョンを含めたいなどの要求を満たす手間を考えると、静的ページとして各ページを作成するのは大変というところからそうしています。
そして、実際に本番向けのサイトにデプロイをする時は、自作のスクリプト(擬似的にリクエストを生成してコンテンツを取得するような感じです。)で各言語ごとのページをHTMLファイルとして吐きだしておいた上で、サーバー上にデプロイするという形にしています。
その上で、このファイル群を静的ファイルとして提供するnginxをCloudFrontのオリジンとして設定しておく、という形になっています。(単純にManagement Consoleからカスタムオリジンに指定しているだけです。)
ツッコミどころとして、CloudFrontが前にいてアクセスを捌いてくれる現状では動的なコンテンツ生成のままでも大丈夫なんじゃないかという話がありますが、まあこれはEC2からコンテンツを配信していた名残です。あとは、気持ち的にアクセスごとにほぼ変化しないコンテンツを動的に毎回生成するのはモッタイナイというところもあります。

また、nginx上では、コンテンツに直接アクセスが合った場合は、UserAgentがCloudFront以外であれば、PathはそのままにCloudFront向けのドメインへのrewriteしています。(/だけは特殊で、nginxでブラウザの言語判定などを行って該当のコンテンツにrewriteしています。)
たとえば、以下のような設定です。

location ~ ^/pro/(.*)$ {
    charset utf-8;
    expires 600s;
    index index.html;
    alias /srv/kanda/kanda/static/misc/pro/$1;

    if ( $http_user_agent != "Amazon CloudFront" ) {
        rewrite ^(.*)$ http://static-shell.cerevo.com$1 permanent;
    }
}
# /の言語判定は長くなるので、割愛。

CloudFrontではカスタムオリジンを正しく設定すれば、同一ドメインで静的なコンテンツと動的なコンテンツを配信することは充分可能そうですが、この構成を試しはじめたときにまだ対応していなかったのと、分けておくほうが動的部分の検証もやらないとみたいな話もあるし、という理由で、CloudFrontで配信するコンテンツと動的なコンテンツのドメインは分けて構成しています。

ここからは、CloudFrontを使っても、リクエスト数がそもそも多いと、CloudFrontからオリジンへのアクセスも微量ながら増えますし、表示速度にも影響することも考えて、ページ表示時のリクエストを極力減らす施策をとっていきます。CloudFrontも従量課金なので、リクエストや転送量は少なければ少ないほど幸せという側面もモチロンあります。

JS, CSSは1つのファイルにまとめて、コンパクト化する

これは画面の表示速度にも関わるので、実際にはアクセス急増対策というよりは割と初期から行っていた部分です。
JSに関しては基本的にこのサイトではClosure Libraryが使われており、JS自体の書き方もそのお作法にしたがって書いてくれてます。そうして、本番サイトにデプロイされる時には、Google Closure Compilerでファイルを1つにまとめつつ、compilation_levelをADVANCED_OPTIMIZATIONにしても動くようにということで開発しており、実際にそういった対応してくれています。
CSSに関しても、ほぼ同様の対応で極力、1つのCSSにまとめてしまい、ファイル数を減らすという形の対応をしています。また、同時に記載された画像のURLは下記の通り、リビジョンを含んだものに置換されます。

その上で、コンパイルされたファイルをリビジョン番号を含んだURLに配置し、Expires(Cache-Controlなどのヘッダも含めてExpiresと書いています。)は1年先を設定しています。
これで、JS,CSSに関しては、こちらでページが更新されない限りキャッシュを使ってくれるので大変エコで、従量課金なCloudFront的にも良い感じになります。

画像ファイルはURLにリビジョン番号を含める形にして、Expiresを長大な形に設定

画像ファイルに関しても、JS,CSSのように、できるならExpiresは長大な期間を設定したいところです。とはいえ、JS,CSSのようなコンパイルというような作業を経るわけでもない、というところでどうやってURLにリビジョンを含めるのが管理上でも楽かという観点で以下のような対応としています。
すこしやっつけ感漂うトリックですが、静的ファイルを生成するタイミングで、/static/imgとなっているURLを/static/dst/img/{latest_revision}/に無理矢理置換します。(latest_revisionは実際にはhgのリビジョン番号です。)

しかし、そんなファイルは当然存在しないので、このままでは404になりますが、そこはnginx側でよしなに

location ~ /static/dst/img/\d+/(.*)$ {
    expires 360d;
    alias /srv/kanda/kanda/static/img/$1;
}

という形にしてaliasを張ることで、実体としては、/static/imgにあるファイルを返すようにしておきます。
画像ファイルもMercurial上で管理されており、変更があればリビジョンが上がります。そのため、こうしておけば変更時だけURLが代わり、それ以外ではExpiresに従ってキャッシュを利用してくれるという動作をします。
残念なポイントは、更新時に変更のないファイルまで再度読み込まれるところですが、これはそもそもURL生成をテンプレート内でのURL生成に変えるつもりなので、そのタイミングで、個別ファイルごとにリビジョンを埋め込む形に変えれればいいかなと思っています。
それ以外にも、CSSにもbackgroundなどで画像ファイルが指定されている場合があります、これも上記にも書きましたが、URLを書き換えるためにコンパイル時にCSSを一度読み込んでURLを見つけて置換する形で対応しています。

その他

最後、細かい話をいくつか。
これはアクセス急増に対応するため、というよりは、表示高速化の側面がおおきいですが、facebookのいいねボタン、Twitterのツイートボタンなどの遅延してロードするようにしてもらっています。これらのボタン、PageSpeedなどでみると明らかに遅くて最後にロードしないとストレスが溜まります。
あとは、画像の最適化です。pngファイルは圧縮率を最大にしたり、ということをするだけで結構サイズが変わってきたりします。お手軽な方法としては、PageSpeedをいれると最適化後のファイルをダウンロードすることができるので、とりあえず、PageSpeedで評価したあとに、最適化された画像に入れ替えるというのをやるだけで、ロード時の容量が少しは節約できます。
最後に、もっとも基本かもというところですが、そもそもコンテンツは基本的にgzipで圧縮されたものを提供できるようにすべきです。我々は基本的にnginxのgzip_staticを利用して、事前にgzip圧縮したファイルを.gzを付けたファイルとして用意をしておくことで対応しています。

最後に

以上のような割と恒久的な対策を施したおかげで、TV放映時のアクセスは難なく乗り越えることができました。
この構成になっていると一番良いのは事前にこれといって準備が必要ではないことです。数ドル単位で費用を節約するような運用を心がけている弱小サービスとしては、常に高性能なインスタンスを立ち上げて置くなどをしなくてよく、静的なページをまるっとCloudFrontさん任せたということで、基本的には突発的なアクセス増などにも特に作業が必要になりません。これは運用している自分にとってはありがたいことで、大抵アクセス急増のタイミングというのは、開発以外にもいろいろやることがあって、気にせずになんとかしてくれるならそれに超したことはありません。
今後また放映があったりしても、心配せず寝ていられる程度には安心できる構成にやっと辿り着くことができたかな、と思っています。

おまけ

ちなみに、このブログもWordpress(+Really Static) + CloudFrontを使う形で動作しています。Wordpress自体のチューニングは、私のPHP苦手意識によりできそうになかったので、静的ファイルを生成して、CloudFrontから配信するという形で動かしています。割と簡単にできるので、また機会があれば紹介したいと思っています。

カテゴリー: Web