Block Rockin’ Codes

back with another one of those block rockin' codes

HTTP2 時代のサーバサイドアーキテクチャ考察

update

色々と twitter で議論が起こったのでまとめて貼っておきます。

togetter.com

みなさんありがとうございました。

intro

HTTP2 の RFC 化も目前ということで、そろそろ実際に HTTP2 を導入していくにあたってサーバサイドの構成についても、具体的にどう変わっていくかという点を考え始めていく必要があります。

そんな話を @koichik さんとしていたら、色々と考えが膨らんだのでメモしておきます。

前提

今回は、中規模のサービスを想定し、特に HTTP2 のサーバプッシュを踏まえた上でのコンテンツ配信などに、どういう構成が考えられるかを考えていきます。

また、本エントリ内では独自に以下の表記を採用します。

  • HTTP/1.1 = HTTP/1.1 (平文)
  • HTTP/2 = HTTP/2 (平文)
  • HTTPS/1.1 = HTTP/1.1 over TLS
  • HTTPS/2 = HTTP/2 over TLS

HTTPS/1.1 での構成

まず、ベースとして HTTPS/1.1 でコンテンツ配信をしていた場合のサーバ構成を復習します。 小さななサービスであれば別ですが、ある程度のサービスであれば前段に Nginx などのサーバを Reverse Proxy として立て、その後ろにアプリケーションサーバの層がある構成が標準的でしょう。 なお、 Proxy よりも後ろはデータセンタ内の高速なネットワークを想定します。

この場合 Proxy の層は以下のような責務を担います。

  • TLS の終端
  • IP ベースのフィルタリング
  • コンテンツの gzip 圧縮
  • ヘッダの追加/削除
  • 静的ファイルの配信(ELB のようにしない場合もある)
  • etc

重要なのは、 URL が https: なリクエストが来た場合、クライアントから Proxy までは HTTPS/1.1 ですが、 Proxy とアプリケーションサーバは HTTP/1.1 で通信する点です。

C --- https/1.1 ---> P --- http/1.1 ---> S

HTTPS/2 での構成

最近では Proxy のポジションは Nginx が多いと思いますが、まだ HTTP/2 には対応していません。 HTTP2 に対応するには H2O や nghttp2 などがありますが、やはり Nginx と比べると実装の枯れ具合の部分もあるし、 すでに Nginx 使ってればそのまま行きたいという気持ちも有ります。

Nginx は 2015 年中には HTTP2 を実装するというロードマップがあり、 そこでは、 Nginx が nghttp2 の proxy 実装である nghttpx 相当の機能を実装するであろうと予想されるので、 今回は、先取りして Nginx にその辺の機能が実装された前提で考えます。

また HTTP2 の場合は、現在のブラウザの実装状況がこのまま続くと考えると、おのずとクライアントとの間は HTTPS/2 が前提になっていくでしょう。 したがって、サービスは全体的に HTTPS/2 になっていくと予想されます。

その場合も、これまで同様 TLS の終端は Nginx で行うことになるでしょう。

C --- https/2 ---> N --- ??? ---> S

では、そこからアプリケーションサーバの間をどうするのか。

大まかに二つが考えられます。

HTTP/1.1 で中継

Nginx で TLS と HTTP/2 を両方解き、 HTTP/1.1 にしてしまうという考え方です。

C --- https/2 ---> N --- http/1.1 ---> S

この方式は、既存のアプリケーションに手を入れる必要が無く、新規に作るとしても言語自体が HTTP2 の実装を持っている必要がありません。 HTTP2 の持つネットワーク上の最適化は Nginx の層で実施することでインターネット側で発揮され、高速なデータセンタ内では、従来と互換という移行しやすい方式かと思います。

HTTP/1.1 にしてしまうと、例えばサーバプッシュといった新しい機能が使えないように思えるかもしれませんが、 resouce hints という仕様を使うと、 Link ヘッダというヘッダを追加することでプッシュしたいリソースを Proxy に知らせることができます。

例えばアプリが index.html のレスポンスの前に style.css を Push して欲しい場合は index.html のレスポンスヘッダに以下のヘッダを入れます。

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: /style.css; rel=preload
...

すると Proxy は style.css を探して Push してくれるというものです。

この機能はすでに nghttpxh2o には実装されているため、 Nginx にも実装されることが期待できます。

HTTP/2 で中継

Nginx では TLS だけ解き、 HTTP/2 で中継するという考え方です。

C --- https/2 ---> N --- http/2 ---> S

この方式は、アプリケーションの層が HTTP/2 の平文実装に対応する必要があります。 データセンタ内であるため HTTP/2 のメリットはネットワーク最適化というよりは、サーバプッシュなどをよりアグレッシブにアプリケーションサーバから使いたい場合に取る戦略かと思います。

前述の HTTP/1.1 に解いた場合は、 Nginx に Push を伝える方法が Link ヘッダに限られてしまいます。 この場合、例えば時間のかかるリクエストの処理をしているが、結果がどうなろうと(status code が決まる前に)先に Push しておきたいコンテンツがあると言った場合に対応できません。 (Nginx にはレスポンスヘッダで伝えないといけないが、 status code が決まらないとそもそもレスポンスを作れないから)

アプリが直接 HTTP/2 を話せると、アプリから直接コンテンツを Push できるインタフェースを叩ける可能性があるので、よりアグレッシブなプッシュができるでしょう。

また、 gRPC など HTTP/2 の機能をよりふんだんに使った構成を考える場合も、こちらを選ぶことになると思います。

HTTPS/2 で中継?

ところが、 HTTP/2 はもともとおまけのような扱いで始まってしまったきらいがあり、若干軽視されている感があります。 特に言語での実装はどのくらいこなれて行くのか若干不安があります。

すると素直に HTTPS/2 のまま通せば良いのでは?という発想もあるかもしれませんが、あまり現実的とは思えません。

TLS の終端をしないと、アプリごとの証明書管理が必要になったり、アプリ側で OpenSSL のバージョンがバラけていたりした場合は、バグが出た時に対応が難しくなるなどの運用面での懸念があります。

なによりデータセンタ内ではセキュリティは IPSec や MACSec などによる対応が考えられるため、 TLS が必須ではないでしょう。

まとめ

結論として以下のようになるのかなと考えます。

  • 互換性を重視し Resouce Hints レベルで Push する: http/1.1 Forward
  • 積極的に Push する: http/2 Forward

こう考えると、当初「HTTP/2 を HTTPS だけにしない」という理由から付け加えられたような HTTP/2 の平文仕様でしたが、実は結構大事だったことに今更ながら気がついたということです。

特にブラウザはほとんど HTTPS/2 しか実装してないし、する気もなさそうですが、サーバサイドや Proxy を作るような場合は、これの変更考慮して HTTP2 の平文実装もぜひ無視しないであげてください。

また、特に今回書いたような中~大(not 超大)規模くらいのサービスを運用されている方々からのコメントをお待ちしています。