こんにちは、インフラエンジニアの小林です。
現在LCLでは、EC2構成のシステムをECSに移行するプロジェクトが進行中です。
その際に EC2 内に入っていた Varnish を SaaS である Fastly に移行しましたが、その際に手こずったポイントをご紹介します。
Varnish と Fastly CDN とは?
Varnish と Fastly CDN はどちらもCDN(Content Delivery Network)ですが、Varnish はサーバ上で動作するオープンソースのソフトウェアであり、Fastly はSaaS 型の CDN です。
なお、Varnish とそのライフサイクルをご存じない方は、以前私が書いたこちらの記事を参照いただけると、本記事の理解がより深まるかもしれません。
https://blog.kasei-san.com/entry/2019/01/24/000222blog.kasei-san.com
弊社の一部の web システムでは、1台のEC2上に Varnish -> Nginx -> Unicorn
という構成をつくり、アプリケーションに到達する前のリクエストをキャッシュしていました。
しかし、そのようなの構成では以下のような運用上の課題がありました。
- 複数サーバを運用する場合に、同一内容のキャッシュが保持されてしまい効率が悪い。
- キャッシュサーバを自前で運用するため、運用コストが高い。
- 大量アクセス時に Varnish がボトルネックになることがある。
そのため、システムをECSに移行するにあたって CDN を Fastly CDN に移行いたしました。
Fastly CDN は、Fastly が提供する SaaS 型の CDN で、以下のような特徴があります。
- 世界中にエッジサーバがあり、ユーザーに最も近いサーバーからキャッシュされたコンテンツが提供されるため、アクセス速度が早い。
- Varnishをベースにしているため、Varnishと同一の VCL ファイルが使用可能。
- SaaSであるため、運用コストが最低限。
Amazon CloudFront など他に CDN を提供するサービスはありますが、コストや移植容易性を鑑み Fastly を採用しました。
Varnish から Fastly CDN へ VCL への移行方法
カスタムVCLを使用するのが効率が良いです。カスタムVCLでは、複数の VCL ファイルを Fastly にアップロード使用できます。
カスタムVCLは Terraform でも以下のように簡単に記述できます。
vcl { name = "main" content = file("${path.module}/vcl/main.vcl") main = true } vcl { name = "mobile_detect.vcl" content = file("${path.module}/vcl/mobile_detect.vcl") } vcl { name = "idou.vcl" content = file("${path.module}/vcl/idou.vcl") }
main: true
の VCL が main となり、他のVCLは main から読み込まれる構成となります。上記の例では、既存のファイル構成をそのまま Fastly に持ち込みたかったため、3つのファイルに分けてアップロードしています。
Varnish の VCL と Fastly CDN の VCL の違い
Fastly CDN は Varnish からフォークしており、同一の設定ファイル(Varnish Configuration Language)を使用できます。
しかし、Fastly CDN の VCL は Varnish 2.1 系から派生しており、LCLで使用してした Varhish は 4.1.1 であったため、いくつか修正が必要となりました。
ただ、基本的にはアップロードしたカスタムVCLのコンパイルエラーのメッセージを元に修正していけば、問題ありませんでした。
以下に、コンパイル時に発生したエラーと、その修正方法を紹介いたします。
pipe is disabled
Fastly の VCL では、return (pipe);
は使用できません。弊システムの場合、return (pass);
でも問題ない内容でしたので、return (pass);
に変更しました。
Fastly マクロを入れないと、Fastlyの独自の実装や、動的スニペットが無視される
Fastly の VCL では、各ライフサイクルのサブルーチンに独自のマクロを追記する必要があります。(例えば、sub vcl_recv
の次の行に #FASTLY recv など。) これらのマクロにより、コンパイル時にFastlyの独自の実装が VCL 内に追加されます。
意図的にそうする必要が無い限りは、マクロを入れておかないと想定外の挙動をしてしまうため、注意が必要です。
Fastly マクロについては、以下のドキュメントにも書かれていますので、ご参照ください。
vcl_hash
は独自実装すると、#FASTLY hash
のデフォルトの挙動が消える
おなじく、Fastly マクロ関係の問題です。
VCL では vcl_hash
というサブルーチンで、キャッシュのキーを設定できます。これにより、例えば、機種の判別を hash に追加することで、同一URLでもPCとスマホで異なる内容のキャッシュを保持することができます。
vcl_hash
は、独自実装しない場合、以下のようなコードが Fastlyマクロから展開されます。
sub vcl_hash { #--FASTLY HASH BEGIN #if unspecified fall back to normal { set req.hash += req.url; set req.hash += req.http.host; set req.hash += req.vcl.generation; return (hash); } #--FASTLY HASH END }
しかし、 vcl_hash を独自実装した場合、上記のコードは消えてしまうため、req.url
や req.http.host
を hash に加えたい場合は、それぞれ自前で実装する必要があります。
set req.hash += req.url; set req.hash += req.http.host;
マクロ展開時のコメントに、その旨書かれているのですが、見落としてしまいかなりハマりました……
vcl_hash
の戻り値は、return (lookup);
ではなく return(hash);
こちらは、VCLのバージョン違いによる問題です。 vcl_hash
の戻り値は return(hash);
にする必要があります。
vcl_backend_response
は、vcl_fetch
に修正する
こちらも、VCLのバージョン違いによる問題です。
Varnish 4.0 でサブルーチン vcl_fetch
は vcl_backend_response
にリネームされました。そのため、Fastlyでは vcl_fetch
に戻す必要があります。
beresp.uncacheable
は使えないので、beresp.cacheable
に変更
同じく beresp.uncacheable
は Varnish 4.0 で実装されたため set beresp.uncacheable = true;
のかわりに set beresp.cacheable = false;
を使う必要があります。
std.duration
も使えないので、代わりに parse_time_delta
を使う
std.duration
は、文字列を秒に変換する処理です。以下のような形で、キャッシュ時間を動的に変更するために使用していました。
if (bereq.http.X-set-ttl) { set beresp.ttl = std.duration(bereq.http.X-set-ttl,beresp.ttl); unset bereq.http.X-set-ttl; }
こちらも(おそらく)バージョン違いのため、使用することができず、代わりに parse_time_delta
を使用しました。
if (bereq.http.X-set-ttl) { set beresp.ttl = parse_time_delta(bereq.http.X-set-ttl); unset bereq.http.X-set-ttl; }
まとめ
今回の記事では、Varnish から Fastly CDN への移行時に直面したいくつかの課題とその解決策について紹介しました。VCLのバージョン差異やFastly特有の仕様に対応するための修正が必要でしたが、全体としては大きな変更なく移行を進めることができました。
Fastly CDNも、Varnishもドキュメントが充実しているため、困ったことが合った場合は、ドキュメントに当たればすぐに解決することができました。やはり、公式のドキュメントは重要ですね……。
本記事が皆様の Fastly 移行へのご一助になれば幸いです。