はじめに
DX開発事業部の田村です。
前回Cloud Run の SSH を試した記事を作成しました。

そのとき気になったのが、Cloud Run Instances は SSH が最初から有効だという点です。もしかしたら使うときだけ起動できるので、プライベート IP しかない Cloud SQL を見るための踏み台に使えるのではと思って今回試してみました。
この記事では手元の psql からつなぐところまでを実施しました。

TL;DR

踏み台が必要な理由
プライベート IP だけの Cloud SQL には、VPC の外から直接はつながりません。Cloud SQL Auth Proxy を手元で動かしても同じでドキュメントにも、Auth Proxy は同じ VPC に届くリソースの上で動かす必要があると書かれています。
そのためVPN を引いていない限りは基本的にVPC の中に何か1台置くことになります。定番は外部 IP を持たない小さな Compute Engine(GCE)の VM を立てて、Identity-Aware Proxy(IAP)経由の SSH でポートを転送する構成です。
今回は、その1台を Cloud Run Instances に置き換えてみました。
検証環境
東京リージョンに、検証用の VPC から作りました。gcloud は 586.0.0(手元)で試しています。
| リソース | 中身 |
|---|---|
| VPC / サブネット | bastion-vpc / bastion-subnet(10.200.0.0/24) |
| Cloud SQL | PostgreSQL 16、db-f1-micro、プライベート IP のみ(プライベート サービス アクセス) |
| Cloud Run Instance | bastion-box。postgres:16 のイメージを sleep infinity で起動。1 vCPU / 512MiB |
イメージを postgres:16 にしたのは、中に psql が入っているためです。Instances は永続ディスクがないので、止めたり更新したりすると中身が消えるため、使うツールはあらかじめイメージに入れておく必要があります。
やってみた
gcloud beta run instances create bastion-box \ --region=asia-northeast1 \ --image=postgres:16 \ --command=sleep --args=infinity \ --service-account=bastion-runtime@PROJECT_ID.iam.gserviceaccount.com \ --network=bastion-vpc --subnet=bastion-subnet \ --vpc-egress=private-ranges-only \ --cpu=1 --memory=512Mi \ --no-default-url
Instances のドキュメントの設定ページには VPC の項目がありませんでしたが、gcloud にはフラグがあり指定すると Direct VPC egress が付きました。

踏み台サーバーはHTTP を受ける必要はないので、--no-default-url で run.app の URL も無効にしました。
ssh コマンドの設定
gcloud beta run instances ssh bastion-box で中には入れますが、ポートを転送したいので、ssh コマンドから使えるようにします。
Host bastion HostName cloud-run-default User root IdentityFile ~/.ssh/google_compute_engine CertificateFile ~/.ssh/google_compute_engine_cert/PROJECT_ID_REGION_instance_INSTANCE-cert.pub HostKeyAlias cloud-run-default StrictHostKeyChecking no UserKnownHostsFile /dev/null LogLevel ERROR ProxyCommand gcloud beta run start-iap-tunnel --project_number=PROJECT_NUMBER --project_id=PROJECT_ID --workload_type=instance --deployment_name=INSTANCE --region=REGION --iap-tunnel-url-override=wss://REGION.ssh.run.app/v4

サービスでは --deployment_name にリビジョン名まで要りましたが、Instances はインスタンス名だけで済みます。デプロイし直しても書き換えが要らないので、踏み台としてはこちらのほうが扱いやすいかなと思います。
なお、ProxyCommand の --iap-tunnel-url-override は gcloud のヘルプに載っていない非公開のフラグ(gcloud のコード上は integration testing 用)なので、今後変わるかもしれません。
証明書は、前回と同じく gcloud beta run instances ssh を一度実行すると作成されます。
ssh で接続してみる
ssh -F ~/.ssh/bastion_config -f -N -L 127.0.0.1:15432:SQL_PRIVATE_IP:5432 bastion psql "host=127.0.0.1 port=15432 user=postgres sslmode=require"

見事接続でき、ダミーのテーブルの3行が手元の psql で取れているところまで確認ができました!
なお、Cloud SQL 側で inet_client_addr() を見てみると、接続元は Instance のサブネットの IP となっていることがわかり、転送した通信も Direct VPC egress を通っていました。
手元の Mac でも同じようにトンネルを張り、GUI のクライアント(TablePlus)から 127.0.0.1:15432 に接続しました。
TablePlus に組み込まれた SSH の機能は、ProxyCommand は読むものの、OS Login の証明書を使った認証が通らなかったため、トンネルは手元のターミナルで張っておきます。

転送先の確認
試しに転送先をホスト名にしたものと IP にしたものを並べて確認します。

ホスト名のほうは channel 2: open failed: connect failed: Temporary failure in name resolution で失敗しましたが、IP のほう(1.1.1.1)は、Cloudflare の 403 が返ってきているので届いています。--vpc-egress=private-ranges-only はプライベートの範囲だけを VPC に流す設定なので、1.1.1.1 のような公開の宛先には、通常の経路で外に出ます。
コンテナの中のシェルでは、getent hosts example.com で名前が引けます。前回見たとおり、sshd は SSH のときだけ後から持ち込まれるので、転送の名前解決はコンテナとは別の環境で行われているようです。
Cloud SQL は大抵の場合はプライベート IP で指定するので困りませんが、社内 DNS の名前で転送したい場合は注意が必要かと思います。
踏み台としての運用
止めているあいだは SSH できない
gcloud beta run instances stop bastion-box --quiet
止めたまま SSH すると以下の通り表示されました。

4003: 'failed to connect to backend' です。サービスなら「0台のときは SSH のために起動する」のですが、Instances は SSH しても起動しません。

start は14〜19秒ほどで返り、続けて SSH で入ることができました。止めた状態から20秒前後で作業を始められますので、使うときに起動して終わったら止めるといった運用は可能ですね。
Cloud Monitoring の課金対象時間(container/billable_instance_time)を見てみると、当然ですがインスタンスを止めているあいだは 0 になっていました。

トンネルを張って運用
証明書は5分で失効します。トンネルを張ったまま待ってみると、失効から3分半たってもトンネル経由の新しい DB 接続は通りました。
一方で失効後に新しく ssh すると Permission denied (publickey) になります。作業中に認証し直す必要はなくつなぎ直すときだけ証明書を作り直せば済むかなと思います。
なお、SSH のセッションは最大24時間で、操作がないと1時間で切れるとドキュメントにありました。
踏み台として気をつけること
踏み台として置くなら、次の3点は先に決めておきましょう。
- 入れる人:SSH の権限は Cloud Run Developer にも入っていて、中は root です。ただ、入るには IAP のロール(
roles/iap.tunnelResourceAccessor)も要るので、これを踏み台の Instance にだけ付けて絞ります。 - 記録:OS Login のログに残るのは「誰が入ったか」まで。誰がどの DB を見たかは、DB のユーザーを人ごとに分けて追います。
- 起動:長く止めたあとの最初の start が失敗することがありました(3回中2回)。スクリプトには再試行を入れます。
Compute Engine の踏み台と比べる
東京リージョンの料金は、Cloud Billing の料金表から計算しました。(2026年10月)
Instances は Cloud Run のサービスとは別の料金で、東京では CPU が 1 vCPU 秒あたり約 0.00000027 ドル、メモリが 1 GiB 秒あたり約 0.0000019 ドルです。CPU はサービスの単価よりかなり安く設定されています。月額は、この単価に 730 時間を掛けて計算しました。
| Cloud Run Instances | Compute Engine(e2-micro)+ IAP | |
|---|---|---|
| 提供状況 | Instances、SSH ともに Preview | GA |
| ポートの転送 | ssh -L で通り、転送先は IP で指定する |
gcloud compute ssh --tunnel-through-iap -- -L |
| OS の管理 | イメージを差し替えるだけ | OS のパッチ当てが要る |
| 停止中の SSH | 起動しない(start が要る) | 起動しない(start が要る) |
| 月額(常時起動) | 約3.2ドル(1 vCPU / 512MiB) | 約9.1ドル(ディスク 10GiB 込み) |
| 月額(平日8時間だけ) | 約0.8ドル | 約3.2ドル(ディスクは止めても課金) |
| コンソール | 画面なし(2026年10月時点) | あり |
料金と手間では Instances のほうが軽いです。ただ Preview なので、本番の運用手順に組み込むのはまだ早いと思います。
もちろん料金や手間以上の運用の容易さを考えると素直にVMを立てた方が良い場合もあるかなと思います。
まとめ
Cloud Run Instances に Direct VPC egress を付けると、プライベート IP の Cloud SQL への踏み台として使うことはできました。ssh の設定を1つ用意すれば、手元の psql から ssh -L でつながり、止めれば課金も止めることはできます。
転送先を IP で書く必要があるのと、止めたままでは SSH できないのはもちろんのこと、ログインユーザーや監査周りの設定はしっかり設計して運用していく必要があると思いますが、今までの Compute Engine を利用した踏み台サーバーに代わる新しい形態になるのではと面白かったです。
この記事が皆様の開発の参考になれば幸いです。