Cloud Run で サーバーレス GCS Proxy
なぜこれを作ったか
あるあるニーズを満たすためです
- 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にしているので、好きに変更してください
テストする
ここでは以下のようにしておきます
- Cloud Run のURL https://xxxxxxxxxxx/
- GCS のオブジェクト /foo.jpg と /index.html
- GCS のベース URL https://storage.googleapis.com/my-gcs-xxxxxx/
アクセス
# 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