Cloud CDN カスタムオリジン

Seiji Ariga
google-cloud-jp
Published in
26 min readApr 9, 2020

このタイトルだと分かりにくいですが、Google Cloud Platform (GCP) の Cloud CDN が GCP 以外をオリジンとして指定できるようになりました 🎉

CDN におけるオリジンとは、オリジンサーバーなどと呼ばれるように、CDN によってキャッシュ、配信されるコンテンツのオリジナルを持っているサーバーなどを指します。

これまで Cloud CDN ではオリジンとして GCP の HTTP(S) Load Balancing のバックエンドしか指定できなかったので、設定と言っても(コンソール上では)チェックボックスが一つあるだけでした。

クラウドコンソール より

それが、これからは GCP 外のリソースも指定できるようになった、というのが大きなポイントです。

カスタムオリジンのドキュメントより

そこで本エントリでは、Cloud CDN で GCP 外のオリジンを指定するための機能「カスタムオリジン」についてご紹介したいと思います。

なお、カスタムオリジンとは直接関係ないですが、Cloud CDN 自体がどう作られてるかといったアーキテクチャ的な側面に興味がある方は、Cloud Next ’19 のセッション「Livin’ on the (CDN) Edge」をご覧ください。インフラが好きな方はより楽しんでもらえると思います。(英語のセッションですが、自動翻訳の字幕もそこそこちゃんとしてます。)

カスタムオリジン

カスタムオリジンの利用は、Internet network endpoint groups (Internet NEGs) という新しいリソースの作成と、それを HTTP(S) Load Balancing で使う(指定する)、という二段階で実現されています。(Internet NEG の詳細は後述します)

NEG とは Google Cloud において各種エンドポイントをまとめて抽象化するためのリソースです。

ちなみに、他の CDN ですと「CDN」という別種のサービスがあって、それを自分のサービスの手前(エンドユーザーとの間)に置く、といったイメージの方が多いと思います。つまり、CDN サービス → ロードバランサー → バックエンド、みたいなイメージです。そのため CDN の設定というと、「ロードバランサー( → バックエンド)」を指定することになると思います。

しかし、GCP では HTTP(S) Load Balancing 自体が巨大な CDN 機能付きロードバランサーなので、「CDN サービス → ロードバランサー」が一体化しており、CDN の設定というのは「バックエンド」の部分を指定する形になります。

絵にすると ↓ みたいな感じです。少し考え方が異なるので戸惑いがあるかもしれません。

Cloud CDN のデータモデルの特徴

ちなみに、「巨大な CDN 機能付きロードバランサー」の中身の話は同じく Cloud Next ’19 の「Cloud Load Balancing Deep Dive and Best Practices」でご紹介してますので参考にしてください。

具体的な設定方法

Internet NEG の作成

カスタムオリジンを実現するために、HTTP(S) Load Balancing のバックエンドとして、既存のバックエンドに加え今回新たに Internet NEGs がサポートされました。以下がサポートされているバックエンドです。

既存の、Instance groups は VM をグループ化してバックエンドとして指定するもの、Zonal NEG は主に(Google Kubernetes Engine の)コンテナ(Pod)をグループ化してバックエンドとして指定するもの、そして Backend buckets は Google Cloud Storageバケットをバックエンドとして指定するものとして使われています。

そして、新たに作られた Internet NEG は GCP 外のオリジンサーバーを指定するものとなります。「GCP 外のオリジンサーバーを指定する」方法は2つあり、それぞれ異なるエンドポイントタイプを指定します。

  • オリジンの FQDN (完全なドメイン名) → INTERNET_FQDN_PORT
  • オリジンの IPv4 アドレス → INTERNET_IP_PORT

つまり INTERNET_FQDN_PORT もしくは INTERNET_IP_PORT が指定された NEG が Internet NEG、(Kubeternetes の NodePort を指定する) GCE_VM_IP_PORT が指定された NEG が Zonal NEG となります。

具体的な作成方法は以下の通りです。(ここではオリジン自体は指定しておらず、まず NEG という箱を作ります)

  • FQDN で指定したい場合
$ gcloud compute network-endpoint-groups create example-fqdn-neg --network-endpoint-type="internet-fqdn-port" --global
Created [https://www.googleapis.com/compute/projects/project-ongcp/global/networkEndpointGroups/example-fqdn-neg].
Created network endpoint group [example-fqdn-neg].
  • IP アドレスで指定したい場合
$ gcloud compute network-endpoint-groups create example-ip-neg --network-endpoint-type="internet-ip-port" --global
Created [https://www.googleapis.com/compute/projects/project-ongcp/global/networkEndpointGroups/example-ip-neg].
Created network endpoint group [example-ip-neg].

次に、↑ で作った Internet NEG という箱の中でオリジン(エンドポイント)を指定します。

まずは FQDN で指定する場合。

$ gcloud compute network-endpoint-groups update example-fqdn-neg --add-endpoint="fqdn=origin.example.com,port=80" --global
Attaching 1 endpoints to [example-fqdn-neg]....done.

そして IP アドレスで指定する場合。(ただし、IP アドレスの変更を考えると、FQDN での指定をオススメします。)

$ gcloud compute network-endpoint-groups update example-ip-neg --add-endpoint="ip=203.0.113.123,port=80" --global
Attaching 1 endpoints to [example-ip-neg]....done.

--add-endpoint という名前から複数指定できそうにも思えます(し、リファレンスには These flags can be specified multiple times to add/remove multiple endpoints. とあります)が、それは Zonal NEG の場合であって、Internet NEG で指定できるのは一つだけです。すでに設定されてる状態で追加しようとすると Attaching additional 1 endpoints to global network endpoint group with 1 endpoints would exceed limit of 1. というエラーになります。

エンドポイントの指定を忘れて HTTP(S) Load Balancing にアクセスすると、次のようなエラーになります。接続先が無いので 502 Bad Gateway になっています。忘れずに指定しましょう。

HTTP/1.1 502 Bad Gateway
Content-Type: text/html; charset=UTF-8
Referrer-Policy: no-referrer
Content-Length: 332
Date: Tue, 07 Apr 2020 18:41:37 GMT
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>502 Server Error</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Server Error</h1>
<h2>The server encountered a temporary error and could not complete your request.<p>Please try again in 30 seconds.</h2>
<h2></h2>
</body></html>

デフォルトだと HTTP(S) Load Balancing からオリジンへのアクセスが HTTP なら TCP ポート80番、HTTPS なら TCP ポート443番を使いますが、違うポート番号を指定することもできます。(↑ の例では敢えて指定してます)

以上の設定は、もちろんコンソールでもできます。

クラウドコンソールのスクリーンショット

ここまでで、HTTP(S) Load Balancing でバックエンドとして指定できる Internet NEG を作れたので、それを実際に利用していきます。

なお、カスタムオリジンの実体としての Internet NEG を説明する都合上、先に NEG を手動で作っていますが、クラウドコンソールで作る場合は HTTP(S) Load Balancing 作成時に一緒に Internet NEG を作れます。

Internet NEG の HTTP(S) Load Balancing での利用

まず普通に HTTP(S) Load Balancing の作成を選びます。

HTTP(S) Load Balancing の作成

バックエンドサービスの作成画面でバックエンドのタイプとして Internet NEG を選択します。

バックエンドタイプの指定

バックエンドとして、上で作成した Internet NEG を指定します。(繰り返しになりますが、ここで Internet NEG を新規に作ることもできるので、必ずしも先に作っておく必要はありません。)

Internet NEG の指定

オリジン(Internet NEG)へのアクセスに HTTP を使うか HTTPS を使うかはバックエンドの指定の少し上にある「プロトコル」で指定します。

なお、ここで指定する「プロトコル」はエンドユーザーが HTTP(S) Load Balancing にアクセスする際のプロトコルとは独立です。(たとえば、HTTP(S) Load Balancing へのアクセスが HTTP でも、オリジンへのアクセスを HTTPS とすることはできます。)

フロントエンドのプロトコルとバックエンドのプロトコル

また、一番重要な Cloud CDN を有効にするのも忘れないでください。

Cloud CDN の有効化

なお後述の通り、ヘルスチェックは設定できません。

あとは通常通り、HTTP(S) Load Balancing を作っていけば完成です。

動作例と設定のポイント

HTTP(S) Load Balancing からバックエンドへのリクエストの例

GET / HTTP/1.1
Host: www.example.com
User-Agent: curl/7.54.0
Accept: */*
X-Cloud-Trace-Context: 419f5570f83f52965aec3ee9ead49222/1101397825..
Via: 1.1 google
X-Forwarded-For: x.x.x.x, 35.244.246.149
X-Forwarded-Proto: http
Connection: Keep-Alive
CDN-Loop: google

x.x.x.x がクライアント、35.244.246.149 が HTTP(S) Load Balancing の IP アドレスです。

CDN-Loop ヘッダはこちらでも解説されてる RFC8586 のやつですね。また、X-Cloud-Trace-Context ヘッダは Cloud Traceのドキュメントで言及されてますが、この値を使って「HTTP ロードバランサ」のログを検索することで、当該リクエストを見つけることができます。

Cloud Logging の「HTTP ロードバランサ」のログより

ここで一つ重要なポイントに気づかれた方も多いかと思います。そうです、Host ヘッダです。Cloud CDN のカスタムオリジンではオリジンへのアクセス時、INTERNET_IP_PORTを指定した場合はもとよりINTERNET_FQDN_PORT を指定した場合でも、オリジンへアクセスする際の Host ヘッダには、エンドユーザーが HTTP(S) Load Balancing にアクセスした際の名前が指定されます。

オリジンへのアクセス時の Host ヘッダ

つまり、オリジンが特定のホスト名を期待してる場合(たとえば上の例では origin.example.com)は、「ユーザー定義のリクエストヘッダ」を使い、明示的に設定する必要があります。

ユーザー定義のリクエストヘッダ

オリジンへのアクセス時に特定のホスト名の指定が必須な例として、Amazon S3 をオリジンにする場合を考えてみましょう。

とりあえず S3 のバケットとその中にパブリックにアクセスできるオブジェクトを準備します。

$ aws s3 mb s3://my-public-bucket
make_bucket: my-public-bucket
$ echo hello | aws s3 cp - s3://my-public-bucket/hello.txt
$ curl
https://my-public-bucket.s3-ap-northeast-1.amazonaws.com/hello.txt
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>AF7756B100532A69</RequestId><HostId>jYqlMBOrsOLTq9q49gbSjMw9Tn6RJumTIpnmNlGW8wCTxdcoVRJNCZB2cfNK6yHCGEgcUS/tE/4=</HostId></Error>
アクセス権の設定を忘れてました。$ aws s3api put-object-acl --bucket my-public-bucket --key hello.txt --acl public-read
$ curl
https://my-public-bucket.s3-ap-northeast-1.amazonaws.com/hello.txt
hello
後述の通り Cache-Control ヘッダの設定が必要なことを忘れてたので追加します。$ aws s3 cp s3://my-public-bucket/ s3://my-public-bucket/ --cache-control 'public,max-age=60' --recursive --metadata-directive REPLACE --acl public-read$ curl -s -D - https://my-public-bucket.s3-ap-northeast-1.amazonaws.com/hello.txt
HTTP/1.1 200 OK
x-amz-id-2: t+b3CjHITvYxRgSX/2YLXF/RmAgraj2UZOv4XDntEGJRQSryMQJoetjlNxoUtWsdmnjEHWb+nxY=
x-amz-request-id: B130278848526D45
Date: Wed, 08 Apr 2020 01:47:56 GMT
Last-Modified: Wed, 08 Apr 2020 01:47:32 GMT
ETag: "b1946ac92492d2347c6235b4d2611184"
Cache-Control: public,max-age=60
Accept-Ranges: bytes
Content-Type: text/plain
Content-Length: 6
Server: AmazonS3
hello

そして、このバケットをオリジンとして Internet NEG を作成します。

$ gcloud compute network-endpoint-groups create s3-internet-neg --network-endpoint-type="internet-fqdn-port" --global
Created [https://www.googleapis.com/compute/v1/projects/project-ongcp/global/networkEndpointGroups/s3-internet-neg].
Created network endpoint group [s3-internet-neg].
$ gcloud compute network-endpoint-groups update s3-internet-neg --add-endpoint=fqdn=my-public-bucket.s3-ap-northeast-1.amazonaws.com --global
Attaching 1 endpoints to [s3-internet-neg]....done.

ここで本題です。作成した Internet NEG をバックエンドサービスで指定する際に、ユーザー定義のリクエストヘッダも合わせて設定します。

バックエンドサービスの設定

上記画面の少し下にある「高度な構成」を開くと出てくる「カスタムリクエストヘッダー」に、「Host」ヘッダとして S3 のホスト名を入力します。

カスタムリクエストヘッダーの設定

あとの手順は上述の「Internet NEG の HTTP(S) Load Balancing での利用」と同じです。

そんなこんなで、無事 S3 をオリジンとして指定できました。

$ curl -s -D - http://www.example.com/hello.txt
HTTP/1.1 200 OK
X-Amz-Id-2: yRCW91gZnPbp9bU/EbwRfCo0xuuzf/X+tEpES9ZVclKu1A+bg0JfJmVy+6a+EZBblSydgwqCDII=
X-Amz-Request-Id: FE7E099D62053DDB
Date: Wed, 08 Apr 2020 02:10:53 GMT
Last-Modified: Wed, 08 Apr 2020 02:10:48 GMT
ETag: "a10edbbb8f28f8e98ee6b649ea2556f4"
Accept-Ranges: bytes
Content-Type: text/plain
Content-Length: 7
Server: AmazonS3
Via: 1.1 google
Cache-Control: public,max-age=60 (← S3が付与)
Age: 12
(← Cloud CDN が付与)
hello

ちなみにカスタムリクエストヘッダーで Host ヘッダを指定してないと次のようなエラーになります。(S3 が Host ヘッダからバケットを特定するので、そのままだと www.example.com というバケットにアクセスしようとしちゃってますね。)

$ curl -s -D - http://www.example.com/hello.txt
HTTP/1.1 404 Not Found
X-Amz-Request-Id: 106F3CD59B82E251
X-Amz-Id-2: oPSTWZNJksg5Q11KSsdiHeujlVYTErCZkbwt174FPBdiCGWBaWyxULEVH21vR4qGJTzn0Eon6pc=
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Wed, 08 Apr 2020 02:16:06 GMT
Server: AmazonS3
Via: 1.1 google
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message><BucketName>www.example.com</BucketName><RequestId>106F3CD59B82E251</RequestId><HostId>oPSTWZNJksg5Q11KSsdiHeujlVYTErCZkbwt174FPBdiCGWBaWyxULEVH21vR4qGJTzn0Eon6pc=</HostId></Error>

ちゃんとキャッシュするための設定

次にカスタムオリジン特有の話ではなく、Cloud CDN 全般の話ですが、ちゃんとキャッシュするのに必要な設定について触れておきます。

まず、以下の例ではコンテンツは返ってきていますがキャッシュされていません。これはオリジンで Cache-Control ヘッダなどが設定されてないためです。Cloud CDN でキャッシュするための条件はドキュメントでご案内しています

$ curl -s -D - http://www.example.com/
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 07 Apr 2020 18:52:38 GMT
Content-Type: text/html
Content-Length: 4
Last-Modified: Tue, 31 Mar 2020 13:16:17 GMT
ETag: "5e8342a1-4"
Accept-Ranges: bytes
Via: 1.1 google
custom origins

キャッシュからの配信か否かは Age ヘッダの有無で分かります。( Cloud CDN adds an Age header to responses it serves from cache.ドキュメントにあります。)

次の例では Cache-Control が付与されてるのに、まだキャッシュから配信されていません。

$ curl -s -D - http://www.example.com
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 07 Apr 2020 19:01:15 GMT
Content-Type: text/html
Content-Length: 4
Last-Modified: Tue, 31 Mar 2020 13:16:17 GMT
ETag: "5e8342a1-4"
Cache-Control: public, max-age=60
Accept-Ranges: bytes
Via: 1.1 google
custom origins

この場合、バックエンドサービスにて Cloud CDN が有効になっているかを確認してください。万一、無効だった場合は有効にします。

$ gcloud compute backend-services describe backend-service-internet-neg --global --format="value(enableCDN)"
False
$ gcloud compute backend-services update backend-service-internet-neg --global --enable-cdn
Updated [https://www.googleapis.com/compute/v1/projects/project-ongcp/global/backendServices/backend-service-internet-neg].

クラウドコンソール上ではここで確認できます。

バックエンドサービスの設定内容

以上の変更により、ようやくカスタムオリジンをキャッシュできるようになりました。

$ curl -s -D - http://www.example.com/
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 07 Apr 2020 19:12:16 GMT
Content-Type: text/html
Content-Length: 4
Last-Modified: Tue, 31 Mar 2020 13:16:17 GMT
ETag: "5e8342a1-4"
Accept-Ranges: bytes
Via: 1.1 google
Age: 12
Cache-Control: public, max-age=60
custom origins

注意点

負荷分散・可用性に関する注意点

  • (上にも書きましたが)各 Internet NEG にはエンドポイントを一つしか指定できません。つまり、たとえば複数の FQDN や IP アドレスを指定することで負荷分散や可用性を高めよう、といったことはできません。
  • しかも、( INTERNET_FQDN_PORT で指定した) FQDN に複数の IP アドレスを登録したとしても、初めに返ってくる IP アドレスにしか接続にいきません。もしその IP アドレスに接続できないと、次の IP アドレスへ接続することなく、あきらめてクライアントに HTTP 502 (Bad Gateway)を返しちゃいます 😢
  • しかも、現時点ではヘルスチェックもサポートされてないので、名前解決できなかったり、オリジンに接続できなかったりした場合は、同じく HTTP 502 を返します 😥

したがって可用性を高める場合は、マネージドなロードバランサーを使った、そもそも可用性の高いオリジンを指定いただくか、ヘルスチェックによって FQDN に登録する IP アドレスを増減できるようなサービス(e.g. Route 53)を併用いただくのがよいと思います。

上段:サポートしない構成・動作、下段:オススメする構成

ここら辺に関しては今後の Cloud CDN にご期待ください。

オリジンへのアクセスに HTTPS を使用する場合の注意点

  • INTERNET_FQDN_PORTを指定している場合、正しい証明書を使っている(= パブリックなCAに署名されており、NEG で指定した FQDN が SAN (Subject Alternative Name)に含まれている、有効期間内である)ことを確認してください。自己署名の証明書等は使えません。(オススメ)
  • INTERNET_FQDN_PORTを指定している場合、Cloud CDN からオリジンへのHTTPS アクセスは SNI (Server Name Indication)拡張に対応してます。
  • INTERNET_IP_PORT を指定している場合は証明書の検証は行われません。そのため自己署名の証明書も使えます。

その他の注意点

  • (2020年6月2日 更新) オリジンサーバーへアクセスする際のソースIPアドレスの範囲は _cloud-eoips.googleusercontent.com の TXT レコードから取得できます。この範囲をファイアウォール等で許可いただく必要があります。2020年6月2日現在は 34.96.0.0/2034.127.192.0/18 が使われます。
  • (注意点ではないですが) オリジンへのアクセスを HTTP(S) Load Balancing からだけに絞るには、↑ の IP アドレスからのアクセスのみを許可いただく方法や、ユーザー定義のリクエストヘッダを設定いただき、オリジンで当該ヘッダが含まれるリクエストのみ許可する、といった方法もあります。
  • FQDN でも IP アドレスでも指定できるのはインターネット経由で到達可能な IPv4 です。(VPN や Cloud Interconnect は使えません)

以上、Cloud CDN の新しい機能、カスタムオリジンのご紹介になります。

ちょっとクセがあると言えるかもしれませんが、一方、Cloud CDN をご利用いただくことで Google の全世界的なインフラストラクチャーをコンテンツの配信にご利用いただくことができ、全体としてはパフォーマンスや可用性の向上を実現いただけるものと思っております。

ご質問があれば、コメントや GCPUG の Slack 等でいただければと思います。

--

--

Seiji Ariga
google-cloud-jp

Customer Engineer, Network Specialist, Google Cloud (All views and opinions are my own.)