Cloud Storageで静的サイトをホストする時によく使うgsutilコマンド

Google Cloud Platformの無料クーポンを使い切った（期限切れになった）後で、Google Cloud Storageを用いた静的Webサイトのホスティングを開始しました。

ひとまず、Webサイトを公開するまでの初期設定やDNS設定などについては、公式ドキュメントに従いました。
cf . [Hosting a Static Website - Cloud Storage — Google Cloud Platform](https://cloud.google.com/storage/docs/hosting-static-website)

以下は、Webサイトをチュートリアルガイドに従って試しに立ち上げてみた後で、本番運用のために`gsutil`を使おうとして試行錯誤した結果落ち着いたコマンド集のようなものになります。

- 価格情報は2016/05/06現在。
- 全オプションや機能を解説するものではないです。個人的に使っているものだけ。
- Cloud Storage上には生成物を置くだけなので、バージョン管理機能は使っていません。

# 基本はgsutil help

```
$ gsutil help [command]
```

全体ヘルプあるいは各コマンドのヘルプが表示されます。
Webの公式ドキュメントと同じクオリティが担保されていて、良いです。

# バケット作成

Cloud Storageでは、バケットという大きな枠に、ファイルやフォルダを格納します。
Webサイトとして運用する際には、サブドメインを含めたホスト名にする必要があります。(gs://www.domain.com)

```
$ gsutil md -c durable_reduced_availability -l asia-east1 gs://bucket-name
```

- `c`オプションを付けない場合は`standard`クラス($0.026/GB月)になります。テスト的に触る場合は可用性とレイテンシの質を落とす代わりに若干安い`dra`(または`durable_reduced_availability`)クラス($0.02/GB月)を用いると良いでしょう。さらに廉価な`nearline`($0.01/GB月)もありますがこれは2〜5秒ものレイテンシなので本当の巨大データバックアップ用でしょう。
- `l`オプションでバケットの地理情報を指定します。デフォルトは`US`マルチリージョン(他にASIA、EU)です。Webサイトとして扱う際には、個別のリージョン(asia-east1など)を指定することでより低レイテンシなバケットとなるそうです。[^1]

# デフォルトのアクセス権限を設定

いわゆるACL(Access Control List: アクセス制御リスト)です。
静的Webサイトのホスティングとしては、誰でも閲覧できるが、編集はできないようにしておくのが良いでしょう。

```
$ gsutil defacl ch -u AllUsers:R gs://bucket-name
```

このあたりはオプションの意味を抑えずとも、とりあえずそのまま使っていいのではないかと思います。

# デフォルトページと404ページの指定

ホスト名でアクセスされた時や、誤ったURLにアクセスされた時のために、デフォルトページと404ページを指定しておきましょう。

この指定を行わない場合、ページが見当たらない場合などにはCloud Storageのエラーを示すXMLが返ってきます。

```
$ gsutil web set -m index.html -e 404.html gs://bucket-name
```

- `m`オプションでデフォルトページを指定します。
- `e`オプションでHTTP 404時に表示するページを指定します。

# ファイルのアップロード

## 簡単なgsutil cp

ちょっとしたファイルやディレクトリのアップロードなら、`cp`コマンドを用いるのが簡単でしょう。

```
$ gsutil -m -h "Content-Language:ja" -h "Content-Type:text/html; charset=utf-8" \
 -h "Cache-Control:public, max-age=31536000" cp -r -z html \
public/*.html public/blog public/tags gs://bucket-name
```

まず、`gsutil`コマンドのオプション(ヘルプは`gsutil help options`)から。

- `m`オプションは処理を並列化します。大量のファイルをアップロードする際などにはこれを付けると良いでしょう。(付けないと1ファイル毎に処理を待つことになります。)
- `h`オプションはメタ情報としてHTTPリクエストへのレスポンスヘッダを`キー:値`の形式で登録するものです。指定できるヘッダには制限があります。[^2]

続いて、`cp`のオプションです。

- `r`オプションは再帰的なコピーを行う際に用います。
- `z`オプションを付けると、指定された拡張子(複数の場合は半角スペースを**置かずに**カンマ区切り。例: html,js,css)のファイルはgzip圧縮された状態でバケット内に置かれ、`Content-Encoding:gzip`が付与された状態になります。テキストファイルでは基本的に付けると良いでしょう。

上記コマンドでは複数ディレクトリをバケットにアップロードしています。
また、Cloud StorageのデフォルトHTTPレスポンスヘッダでは以下のような不満があるためため、一部を修正しています。

- ファイルの拡張子から自動で最適なContent-Typeを追加してくれますが、charsetが指定されない
- Content-Language:enが自動付与される
- Cache-Controlのmax-ageは3600秒

## 便利なgsutil rsync

`gsutil cp`は便利ですが、バケット内ファイルとの新旧比較は行わないので、Webサイトの更新時に再度一括アップロードを行う(頻繁に更新するコンテンツだと、転送コストや処理時間が気になる)か、シェルスクリプトなどで自前でアップロードファイルの取捨選択を制御する必要があります。

そこで、代わりに`gsutil rsync`が提供されています。
これはディレクトリ間で同期をとってくれる機能です。
ファイルサイズとチェックサムで更新判定を行います。

```
$ gsutil -m -h "Content-Encoding:gzip" -h "Cache-Control:public, max-age=31536000" \
rsync -d -x ".*\.md$" -r public/css gs://bucket-name/css
```

- `d`オプションは同期先にあるファイルが同期元には無い場合、同期先のファイルを消します。操作ミスで空ディレクトリと同期してしまったりすることを防ぐため、実運用時には予行演習用に`n`オプションを付けて、まずどういった処理になるかを確認しましょう。
- `x`オプションはPythonの正規表現を用いて、除外したいファイルを指定します。ここでは各フォルダにドキュメントとしてmarkdown(.md)ファイルがあった場合の例として、.mdファイルを除外しています。
- `r`オプションはサブディレクトリ以下も再帰的に同期する場合に指定します。

`gsutil rsync`には1点だけ不便な点があります。
それは`gsutil cp`の`z`オプションに相当する機能が提供されていないということです。
`rsync`という機能にとっては混乱の元になると判断されたのかもしれません。

そのため、`gsutil rsync`を用いてテキストファイルのgzip圧縮を活用するには、あらかじめ自前で(圧縮前と同名の)圧縮ファイルを用意することに注意です。この辺りの部分はmakeやrake、gulpなどお好みのビルドツールで自動化しておくと良いかもしれないですね。（自分はgulpを使っています。)

# バケット内の確認

アップロードが完了したら、バケットの状態を確認してみましょう。

```
$ gsutil ls -lr gs://bucket-name
```

- `l`オプションはファイルサイズとタイムスタンプを追加表示します。
- `r`オプションはサブディレクトリ以下のファイルも表示します。

## メタ情報の確認

各ファイルに付与されているHTTPリクエストヘッダなどのメタ情報を確認してみましょう。
こちらも`gsutil ls`で行います。

```
$ gsutil ls -L gs://bucket-name/css/style.css
```

- `L`オプションを付けると、各種メタ情報がJSON形式で表示されます。

個別ファイルの確認を例としましたが、複数ファイルの一括表示も可能です。

# ファイルの削除

`gsutil rsync`を使っていると、必要な場面は多くないですが、個別のファイルやディレクトリを削除するコマンドも提供されています。

```
$ gsutil -m rm -r gs://bucket-name/css
```

- `r`オプションはサブディレクトリ以下も再帰的に処理します。

尚、`r`オプションを付けているときに、バケット名までしか指定しないと、全ファイルが削除された後に、バケット自体も削除されます。
もし全ファイルを削除した後でバケットは残したいときには

```
$ gsutil -m rm "gs://bucket-name/**"
```

とするのが良いでしょう。

# CORSの設定

クッキーフリードメインなどとして一部のリソースを別ドメイン(別バケット)で管理している場合、外部ドメインからデータを利用するためにCORSの設定が必要となることがあります。
そうした場合、例えば以下のような感じで外部からアクセスできるような設定ファイルを作ります。

```json
[
  {
    "origin": ["http://another.bucket.xxx", "http://localhost:8888"],
    "responseHeader": ["Content-Type"],
    "method": ["GET", "HEAD", "DELETE"],
    "maxAgeSeconds": 2592000
  }
]
```

設定ファイルを適当な名前（ここではcors-json-file.json)で保存したら、以下のコマンドでバケットに登録します。

```
$ gsutil cors set cors-json-file.json gs://bucket-name
```

これで、無事に指定したURLからリソースが利用できるようになりました。

---

まだ、試行錯誤していますが、普段使っているコマンドはこんな感じです。

尚、Cloud Storage、なかなか高速でいい感じです。コストも毎日算出されてますが、ちょっとしたサイトならDigital OceanなどのVPSより安く済むのではないかなと思います。

[^1]: cf. [Bucket Locations - Cloud Storage — Google Cloud Platform](https://cloud.google.com/storage/docs/bucket-locations#regional)

[^2]: cf. [Working With Object Metadata - Cloud Storage — Google Cloud Platform](https://cloud.google.com/storage/docs/gsutil/addlhelp/WorkingWithObjectMetadata)