🌊

Cloud Run で サーバーレス GCS Proxy

2022/03/12に公開

なぜこれを作ったか

あるあるニーズを満たすためです

  • Google Cloud Storage(GCS) に画像などのオブジェクトを格納しているが、Google Cloud Load Balancer(GCLB)経由でアクセスしたい
  • GCSへのダイレクトアクセスは拒否したい

GCLB 経由であれば、カスタムドメインはもちろん、Cloud Armor や Cloud CDN も使えるし、いいですよね
なので GCLB をバイパスして、GCS にダイレクトアクセスするのを禁止したいのです


これはさせたくないってことですね

準備できてれば、5分程度でさくっとできるのでお手軽です

構成

GCLB と GCS の間に Proxy を設置して、アクセス制御します

  • 運用を楽にしたいので、Cloud Run を使います
    ここでは Preview の 第2世代を使ってます
  • (気は進みませんが)1コンテナに nginx と cron の2つを動かします(supervisor 経由)
    Cloud Run にサイドカーなどがないため
  • cron を常時動かすため、CPU Allocation(CPU Always On)を使います
    くわしくはこちら
  • Cold Start は cron による Access Token の更新に問題があるため、避ける必要があります
    最小/最大インスタンス設定でインスタンス数を固定します
  • Cloud Run には ingress 設定を使って、ロードバランサと内部からしかアクセスできないようにします

なお今回はシンプルにするため、Google Container Registry を使いますが、今後は Artifact Registry をオススメします

事前に用意しておく項目

  • Google Cloud プロジェクト(ここでは my-project)
    gcloud で、必要な権限をもったアカウントでログインしておきましょう
    Cloud Shell を使うと環境が整っているので便利です
  • GCS のバケット(ここでは my-gcs-xxxxxx)
  • GCLB と サーバーレス NEG で接続された Cloud Run
    準備ができてない方は 末尾のおまけを参考にしてください

とりあえず すぐやってみる

ソースを取得

git clone https://github.com/shin5ok/gcs-proxy.git

GCPのプロジェクトやリージョンが設定されているか確認して

gcloud config configurations list

必要なAPIを有効化

gcloud services enable run.googleapis.com cloudbuild.googleapis.com

ビルド & プッシュ そして Cloud Run デプロイをスクリプトで実行します

cd gcs-proxy/
PROJECT=my-project GCS_BUCKET=my-gcs-xxxxxx sh ./deploy.sh

なお、コンテナインスタンス数は 2にしているので、好きに変更してください

テストする

ここでは以下のようにしておきます

アクセス

# GCLB 経由
curl https://xxxxxxxxxxx/foo.jpg # OK
curl https://xxxxxxxxxxx/index.html # OK
curl https://xxxxxxxxxxx/bar # 404
# ダイレクトアクセス
curl https://storage.googleapis.com/my-gcs-xxxxxx/index.html # 403

確認できたら終わりです

Cloud Run上で動作する コンテナの内容

Dockerfile

FROM google/cloud-sdk:latest

RUN apt update && apt install -y libnginx-mod-http-lua cron supervisor procps nginx
COPY ./default /etc/nginx/sites-enabled/
COPY supervisord.conf /etc/supervisor/
COPY renew-token /etc/cron.d/
RUN rm -Rf /var/log/nginx/*.log ; ln -s /dev/stderr /var/log/nginx/error.log && ln -s /dev/stdout /var/log/nginx/access.log
CMD ["supervisord"]
  • gcloud を使いたいので、google/cloud-sdk を利用
  • Nginx を lua サポート付きでインストール、複数プロセスを制御するためのsupervisorも追加
  • Nginx の設定テンプレートをコピー
  • Nginx は標準入出力にログを出すように調整
  • supervisor で nginx と cron を起動

nginxの設定

ポートは 8080
{GCS_BUCKET}の部分を環境変数で設定した $GCS_BUCKET で置き換え
cronで定期的に更新しているトークンを Authorization ヘッダにつけて、バックエンドの GCS に中継

server {
	listen 8080 default_server;
	server_name _;
	location / {
                resolver 169.254.169.254 valid=2s ipv6=off;
                set $upstream "storage.googleapis.com";
                set $yourbucket "{GCS_BUCKET}";
                rewrite ^/(.*)$ /$yourbucket/$1 break;
                proxy_pass https://$upstream;
                proxy_set_header HOST $upstream;
                proxy_set_header  X-Forwarded-Host  $host;
                proxy_set_header  X-Real-IP         $remote_addr;
                proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
                proxy_set_header  X-Forwarded-Proto $scheme;
                proxy_connect_timeout 2s;
                rewrite_by_lua_block {
                        local file = io.open("/tmp/token")
                        io.input(file)
                        local data = io.read()
                        io.close()
                        ngx.req.set_header("Authorization", "Bearer "..data);
                }
        }

	location = /thisistest {
		return 200 "ok";
	}

}

cronでやってること

GCS のアクセスに必要なアクセストークンを毎分更新して、/tmp/token に作成

supervisorでやってること

  • 起動時に Nginx のテンプレを書き換え
  • cron をセット
  • Nginx と cron を起動

検討できること

  • Cloud Run のインスタンス数を固定しているので、超高速スケールの特性は利用できません
    Nginx と Cloud Run の並行処理のパラメータを調整すれば、静的ファイルの配信には十分かもしれません
  • コンテナを軽量化する(ベースイメージがでかい)
    ただし Cloud Run はパフォーマンス観点のメリットは少ないです
    セキュリティ面、つまり Attack Surface を小さくする意義はあります
  • Access Token の取得を工夫
    Cloud Run のインスタンスの数がすごく多いと Access Token の取得が同時にたくさん走ってしまうため、何かしら問題がでるかも?
    キャッシュストアを使う、時間ずらす、などの工夫で回避は可能ですね
  • Cloud Run には リクエスト、レスポンスのサイズ制限がありますので、オブジェクトが大きくなると Google Kubernetes Engine / Google Compute Engine などをご利用下さい

cron を少し書き換えれば、Identity Token を要求する Cloud Functions のようなサービスにも中継できます
楽ちんです

おまけ

GCLB とマネージド証明書、サーバーレスNEGなどの準備です
マネージド証明書をつくるので、待ち時間が10分以上かかるとおもいます

ドキュメントの通りなのですが、さくっと簡易につくるために utils ディレクトリにシェルスクリプトをおいてます

cd utils/
sh ./1-reserve-ip.sh

で、my-external-ip というロードバランサに設定するためのIPが予約されるので、表示されたIPアドレスに使いたい FQDN を DNS に設定します
そして ロードバランサを設定します(マネージド証明書の取得も行っています)

sh ./2-https-gclb.sh DNSに設定したFQDN

マネージド証明書の準備が完了するまで下記コマンドで確認しましょう

gcloud compute ssl-certificates list

以上ですべてのリクエストを Cloud Run にフォワードする GCLB が設定されます

Discussion