プログラマのための IT 教科書

クラウドの基礎

この部の 2 / 5 章 ・ 全体で 71 / 76 章 ・ 読了目安 40 分

この章を読むとできるようになること
  • プロジェクト・IAM・ネットワークの関係を説明できる
  • 権限エラーが出た時にどこを見るか分かる
  • コストがどこで発生しているか追える

コンテナと Kubernetesで扱った Kubernetes は、何もない空間には浮いていません。 その下には必ず、マシン・ネットワーク・課金の主体があります。それがクラウドです。

この章では GCP を題材にしますが、名前が違うだけで考え方はどのクラウドでも同じです。 新人に求められるのは基盤を設計することではなく、自分の権限がなぜ足りないのか、 そのお金がどこから出ているのかを説明できることです。

なぜクラウドなのか

「サーバーを買わなくていい」は結論の半分

よく言われるのは「機材を買わなくていい」「初期費用がかからない」です。 それは事実ですが、本質はそこではありません。

クラウドの本質は、インフラがAPI で操作できるようになったことです。

# サーバーが1台増える。所要時間30秒
gcloud compute instances create worker-01 --machine-type=e2-medium
 
# 消える。所要時間30秒
gcloud compute instances delete worker-01

これがなぜ効くのか。API で作れるということは、次がすべて可能になるということです。

できるようになること意味
コードで書けるインフラ構成を Git で管理し、レビューできる(Terraform など)
使い捨てにできる検証用の環境を丸ごと作って、終わったら消せる
自動で増やせる負荷に応じてスケールする。コンテナと Kubernetesの HPA はこの上に乗っている
同じものを再現できるdev と prod を同じ定義から作れる

つまりクラウドとは「安いレンタルサーバー」ではなく、 インフラをソフトウェアとして扱えるようにする仕組みです。

作れるということは、消し忘れられるということ

同じ理由で、事故も起きます。

30秒で作れるものは、30秒で忘れられます。 誰も使っていないのに課金され続けるリソースは、どの現場にも必ずあります。 この章の後半で扱うコストの話は、すべてこの裏返しです。

オンプレとの一番大きな違い

オンプレミス(自社の機材)では、サーバーを追加するのに稟議と納期が必要でした。 だから「最初に多めに買って、余らせておく」のが正解でした。

クラウドでは逆です。必要になってから足すのが正解で、 先に大きく確保しておくのは、ただの無駄遣いになります。

「余裕をもって大きめのマシンにしておきました」は、 オンプレの発想が抜けていないサインです。

プロジェクト — すべての境界

GCP でリソースを作るときは、必ずどれかのプロジェクトの中に作ります。 プロジェクトは単なるフォルダ分けではなく、3つの境界を同時に持つ単位です。

境界意味
課金請求はプロジェクト単位で集計される。「どこにいくらかかったか」の単位
権限IAM の付与はプロジェクト単位が基本。ここに入れた人は中のリソースを触れる
リソースネットワーク、VM、GKE クラスタ、DB。すべてどれかのプロジェクトに属する
組織 (example.com)
└── フォルダ (プロダクトA)
    ├── プロジェクト  myapp-dev       ← 開発。壊してよい
    ├── プロジェクト  myapp-staging   ← 本番と同じ構成で検証
    └── プロジェクト  myapp-prod      ← 本番。触れる人を絞る

環境はプロジェクトごと分ける

dev / staging / prod を1つのプロジェクトの中で名前だけ変えて共存させる構成は、 小さく始めるときは楽ですが、実務ではまず採りません。理由は3つです。

  1. 権限を分けられない — 開発者に dev の権限を渡すと、prod も触れてしまう
  2. コストを分けられない — 請求が混ざり、どの環境が高いのか分からない
  3. 事故が波及する — 検証中の設定変更やクォータ超過が本番に届く

プロジェクトを分ければ、この3つが設定ではなく構造として分離されます。

プロジェクト名とプロジェクト ID は別物

コンソールに表示される名前(MyApp Prod)は後から変えられますが、 コマンドや API が使うプロジェクト ID(myapp-prod-4f21)は作成後に変更できません。

そして ID は全世界でユニークなので、myapp-prod のような短い名前は たいてい他人に取られていて、末尾に乱数が付いた形になります。

結果、myapp-prod-4f21 と myapp-prd-4f21 のような紛らわしい ID が並ぶことになります。 コマンドを打つ前に、今どのプロジェクトを向いているかを必ず確認してください。

IAM — 誰が、何を、どこで

IAM(Identity and Access Management)は、権限の仕組みです。 覚えることは1つだけで、権限は必ず3点セットで決まります。

プリンシパル(誰が) × ロール(何をしてよいか) × リソース(どこで)

この3つのどれか1つでも欠けたり、ズレていたりすると権限エラーになります。 逆に言えば、権限エラーの原因は必ずこの3つのどれかです。

プリンシパル(誰が)

種類例用途
ユーザーyou@example.com人間
グループbackend-team@example.com人間の集合。個人に直接付けず、これに付ける
サービスアカウントapi@myapp-prod.iam.gserviceaccount.comプログラムの身元

ロール(何をしてよいか)

種類例使うか
基本ロールroles/owner roles/editor roles/viewer本番では使わない。粗すぎる
事前定義ロールroles/storage.objectViewerこれが基本。用途ごとに用意されている
カスタムロール自分で権限を列挙する事前定義で足りない時だけ

roles/editor は「プロジェクト内のほぼ全部を書き換えられる」権限です。 一見便利ですが、これを配るとIAM を分けている意味がなくなります。

リソース(どこで)と継承

IAM は階層で継承されます。上で付けた権限は下にすべて効きます。

組織         ← ここで付けると全プロジェクトに効く(事故ると影響が大きい)
 └ フォルダ
    └ プロジェクト   ← 実務でよく使う粒度
       └ 個別リソース(バケット、Spanner インスタンスなど)  ← 最も絞れる

下で拒否しても、上で許可されていれば通ります。 「バケット単位では付けていないのに読めてしまう」時は、たいてい上位に権限が付いています。

サービスアカウントとは何か

プログラム自身のアカウントです。人間のアカウントとは別物として扱います。

人間のアカウント        サービスアカウント
you@example.com        api@myapp-prod.iam.gserviceaccount.com
  パスワード + MFA         鍵、または実行環境に紐づく身元
  退職したら消える         サービスが動く限り存在する

GKE の Pod や Cloud Run のサービスには、このサービスアカウントが紐づきます。 アプリが Spanner を読めるかどうかは、あなたの権限ではなく、 そのサービスアカウントの権限で決まります。

人間のアカウントで本番を触ってはいけない理由

「自分の権限で動かせば早い」は、次の4つを同時に壊します。

  1. 退職・異動でサービスが止まる — アカウントが消えた瞬間に本番が動かなくなる
  2. 監査ログが意味を失う — 「誰がやったか」が全部あなたの名前になり、 人間の操作とプログラムの動作を区別できない
  3. 権限が過剰になる — 人間には開発用の広い権限が付いていることが多い
  4. 認証情報が漏れた時の影響が最大化する — あなたが触れる全リソースが巻き添えになる

プログラムの権限はサービスアカウントに、人間の権限は人間のアカウントに。 この分離が、あとから直すのが最も面倒な部分です。最初から分けてください。

最小権限

「とりあえず動かすために roles/editor を付ける」は、実務で最も多い妥協です。 そして一度動いてしまうと、誰も権限を削りに戻りません。

正しい順番はこうです。

# 1. まず必要そうな最小のロールだけ付ける
gcloud projects add-iam-policy-binding myapp-dev \
  --member="serviceAccount:api@myapp-dev.iam.gserviceaccount.com" \
  --role="roles/spanner.databaseReader"
 
# 2. 動かす。足りなければエラーメッセージに不足している権限名が出る
#    PERMISSION_DENIED: spanner.databases.write
 
# 3. その権限を含むロールだけを足す

エラーメッセージには足りない権限の正確な名前が書いてあります。 それを読んで足すのが、最短かつ最小の道です。

権限エラーが出た時に見る順番

この節が実務で最も使います。 PERMISSION_DENIED や 403 を見たら、上から順に確認します。

# 0. まず、今どのプロジェクトで、誰として実行しているかを確認する
gcloud config list
gcloud auth list          # ACTIVE の行が今の身元
 
# 1. エラーメッセージから「不足している権限名」を読む
#    例) Permission 'storage.objects.get' denied on resource ...
 
# 2. その身元に、どのロールが付いているかを見る
gcloud projects get-iam-policy myapp-dev \
  --flatten="bindings[].members" \
  --filter="bindings.members:api@myapp-dev.iam.gserviceaccount.com" \
  --format="table(bindings.role)"
 
# 3. アプリの場合、実際に使われている身元を確認する
#    (自分の権限ではなく、サービスアカウントの権限で動いている)
kubectl get serviceaccount api -o yaml       # GKE の場合
gcloud run services describe api --format="value(spec.template.spec.serviceAccountName)"

チェックリストとしてはこうです。

#確認することよくある原因
1プロジェクトは合っているかdev のつもりが prod、あるいはその逆
2誰として実行しているか自分の権限で通っていたが、本番ではサービスアカウントだった
3どの権限が足りないかエラー文字列に権限名が書いてある。読む
4ロールは付いているかそもそも付与漏れ
5リソースの階層は合っているかプロジェクトには付いているが、対象は別プロジェクトのリソース
6反映されているかIAM の変更は反映に数十秒〜数分かかることがある

特に6番は見落とされます。IAM の変更は即座に全体へ伝播するとは限らないので、 付与直後に失敗しても1〜2分待ってもう一度試してください。

ここで焦って roles/editor を付けてしまい、そのまま本番に残る—— というのが、権限が過剰になっていく典型的な経路です。

Cloud Run 上のアプリが Spanner の読み取りで PERMISSION_DENIED になりました。あなたの手元では gcloud で同じデータを読めています。最初に確認すべきことは?

ネットワーク

構成要素

要素役割
VPCプロジェクト内のプライベートなネットワーク。この中は互いに通信できる
サブネットVPC をリージョンごとに区切ったもの。IP アドレスの範囲を持つ
ファイアウォールどの通信を通すかのルール。デフォルトは受信拒否
ロードバランサ外からのアクセスを受け、複数の宛先へ振り分ける
Cloud NAT外向き通信だけをまとめて出すための出口
インターネット
     ↓
ロードバランサ(グローバル IP を持つのはここだけ)
     ↓
┌─────────── VPC ───────────────┐
│  サブネット (asia-northeast1)   │
│    GKE ノード / Cloud Run       │
│         ↓ 内部 IP のみ           │
│    Spanner / Cloud SQL          │  ← 外から直接は届かない
└────────────────────────────────┘

なぜ DB にグローバル IP を付けてはいけないか

「開発中に手元から繋ぎたいから」という理由で、 DB にグローバル IP を付けて 0.0.0.0/0 を許可する——これは最も危険な近道です。

理由は「危ないから」では足りません。具体的にはこうなります。

  1. 公開した瞬間からスキャンされます。 インターネット上の全 IP は常時スキャンされており、 よく使うポート(5432、3306、6379 など)は数分以内に接続を試されます
  2. 守りがパスワード1枚だけになります。 ネットワークの層が消えるので、 パスワードが漏れた、あるいは弱かった時点で終わりです
  3. 脆弱性が出た時に即死します。 DB 本体に認証回避の脆弱性が見つかった時、 ネットワークで隔離されていれば猶予がありますが、公開していると猶予がありません
  4. 気づけません。 攻撃者は壊すのではなく、静かに読んで出ていきます

正しいやり方は、内部 IP のままにして、経路の方を用意することです。

  • アプリからは VPC 内の内部 IP で繋ぐ(そもそも外に出さない)
  • 開発者が繋ぐ時は 踏み台・プロキシ・IAP トンネルを経由する
  • ファイアウォールは送信元を絞る(0.0.0.0/0 は本番では書かない)
# 手元から Cloud SQL に繋ぐ。グローバル IP を付ける必要はない
cloud-sql-proxy myapp-prod:asia-northeast1:main-db
 
# IAP 経由で踏み台に入る(VM にグローバル IP は不要)
gcloud compute ssh bastion --tunnel-through-iap
0.0.0.0/0 を書く前に一度止まる

ファイアウォールの送信元に 0.0.0.0/0 と書くのは、 「全世界から接続してよい」と宣言することです。

一時的な検証のつもりで開けたルールは、まず消されません。 「後で閉じます」と言って閉じられた例を、私は見たことがありません。

外部に公開してよいのは、原則としてロードバランサだけです。 それ以外に穴を開けたくなったら、代わりに使える経路(プロキシ・IAP・踏み台)が 本当に無いのかを先に確認してください。

コンピュートの選択肢

「マイクロサービスだから全部 GKE」は、判断を放棄した結果です。 選択肢はそれぞれ運用の重さと引き換えに自由度を得るという関係にあります。

Cloud RunGKECompute Engine
単位コンテナ1つクラスタ + Pod仮想マシン
スケール自動。ゼロまで落ちる自動(設定が必要)手動 or MIG
運用の重さほぼ不要クラスタとアップグレードの面倒を見るOS もパッチも自分
起動の速さ数秒(コールドスタートあり)常駐分単位
常時稼働のコストリクエストが無ければほぼゼロノードが立っている間ずっと起動中ずっと
できることHTTP/gRPC のリクエスト応答が中心ほぼ何でも何でも
向いているAPI、Web、バッチ処理の受け口相互通信する多数のサービス、細かい制御特殊なミドルウェア、レガシー移行

判断軸

上から順に問い、最初に「はい」になったところで止まるのが実務的です。

  1. 既存クラスタに載せる話か? → 既に GKE があるなら GKE。運用は既に払っている
  2. リクエストに応答するだけか? → Cloud Run。運用コストが桁で違う
  3. Pod 間の細かい制御、サイドカー、Operator、特殊なスケジューリングが要るか? → GKE
  4. OS レベルの制御や、コンテナ化できないものがあるか? → Compute Engine
Cloud Run を軽く見ない

新人ほど「本番は Kubernetes でやるものだ」と思いがちですが、 HTTP/gRPC を受けて返すだけのサービスなら、Cloud Run の方が正解であることは多いです。

GKE を選ぶということは、クラスタのバージョンアップ、ノードプールの管理、 ネットワークポリシー、リソース設計——その全部を継続的に見る約束をするということです。

コンテナと Kubernetesで学んだ内容は、その約束を果たすための知識でした。 知識があることと、常に使うべきことは別です。

マネージドサービスを使うか、自前で立てるか

「Redis なら VM に立てれば安いのでは」——構築だけを見ればその通りです。 コストは構築ではなく、運用に出ます。

観点自前で立てるマネージド
構築1日30分
バージョンアップ自分で計画・検証・実施ボタン or 自動
バックアップ仕組みを作り、復元を定期的に試す標準機能
監視メトリクスの収集から作る最初から付いている
障害対応深夜に自分が起きる多くはクラウド側が対応
可用性冗長構成を自分で設計設定項目
費用安く見える高く見える

金額だけを比べると自前が勝ちます。しかし比べるべきは、 「そのサービスを3年間動かし続けるのに必要な人の時間」を含めた総額です。

判断の目安はこうです。

  • その技術が自社の競争力そのものでないなら、マネージドを使う
  • 深夜に起きて対応する覚悟が無いなら、マネージドを使う
  • 自前にするのは、マネージドでは要件を満たせないと具体的に説明できる時だけ

インフラの費用比較で自前が安く見えるのは、たいてい人件費を数えていないからです。 エンジニアが月に数時間その面倒を見るだけで、多くのマネージドサービスの差額を上回ります。 「安い」と言うときは、何を数えていないのかを確認してください。

コストはどこで出るか

請求書が跳ねるのは、たいてい計算資源そのものではないところです。

見落とされやすい3つ

種類何が起きているか
Egress(外向き通信)クラウドから外へ出るデータに課金される。入る方(Ingress)は無料なことが多い
アイドル課金使っていなくても、存在するだけで課金されるもの
ログとメトリクスの保存量出した量 × 保存期間で効いてくる

Egress は特に直感に反します。 リージョンをまたぐ通信、 別プロジェクトへの通信、インターネットへの応答——これらは全部お金です。

同一ゾーン内           → 無料 or ごく安い
リージョン内の別ゾーン   → 少し課金
リージョンをまたぐ      → 課金
インターネットへ出る     → 最も高い(かつ、宛先の地域で単価が違う)

だから「アプリは東京、DB は米国」のような構成は、 遅いだけでなく、通信量に比例して課金され続けます。

存在するだけで課金されるもの

リソース止めても課金されるか
VM(停止中)本体は止まるが、アタッチされたディスクは課金され続ける
未使用の静的 IP使っていない方が高い(使用中は無料のことがある)
ロードバランサ転送ルールが存在するだけで時間課金
GKE クラスタノードが1台も無くても、クラスタ管理費がかかる場合がある
スナップショット・古いイメージ消さない限り増え続ける

新人がやりがちな高額請求

やりがちなこと何が起きるか
検証環境の消し忘れGKE クラスタや大きめの VM が動きっぱなし。最も金額が大きい
ロードバランサの放置検証で作った転送ルールが残り、毎月定額で溶ける
debug ログを本番で出すログ量が10倍になり、保存料と取り込み料が跳ねる
リクエストごとに全件 SELECTDB の読み取り課金 + Egress の両方が増える
画像や動画を毎回オリジンから配るCDN を挟まないと Egress が直撃する
ループの中の外部 API 呼び出し単価は小さくても回数で効く
ログは「出したら勝ち」ではない

読まれるコードを書くで「後から調べるためにログを書く」と学びました。 それは正しいのですが、量には値段が付きます。

debug レベルを本番で出しっぱなしにすると、 取り込み料・保存料の両方が効いて、月の請求で目に見える金額になります。

  • 本番のログレベルは info 以上を既定にする
  • 大量に出るログはサンプリングする(全部の成功リクエストを記録しない)
  • 保存期間を決める(多くの調査は直近数日で足りる)

「ログを減らす」ではなく、必要なログを、必要な期間だけ残すが答えです。

自分で追えるようにする

# ラベルを付けておくと、請求を分解できる
gcloud compute instances create worker-01 \
  --labels=env=dev,team=backend,service=api

リソースにはラベルを付けます。 付けていないと、請求書を見ても「この2万円は誰のものか」が永久に分かりません。 予算アラートも合わせて設定し、閾値を超えたら気づける状態にしておきます。

本番を壊さないための作法

まず read-only で見る

いきなり変更を打たない。これだけで事故の大半は防げます。

# 見る(安全)
gcloud compute instances list
gcloud run services describe api
kubectl get deployment api -o yaml
 
# 変える(ここからは慎重に)
gcloud compute instances delete worker-01

--dry-run と差分確認

多くのツールには「実行せずに、何が起きるかだけ見る」手段があります。

kubectl apply -f deploy.yaml --dry-run=server   # サーバー側で検証だけする
kubectl diff -f deploy.yaml                     # 今の状態との差分を見る
terraform plan                                  # 適用前に変更内容を確認する

plan や diff の出力を読まずに apply するのは、目をつぶって運転するのと同じです。 「削除される予定のリソース」が混ざっていないかは、必ず確認してください。

環境の取り違えを構造で防ぐ

新人の事故で最も多いのがプロジェクトの切り替え忘れです。 「dev で試すつもりが prod だった」は、注意力では防げません。仕組みで防ぎます。

# 名前付きの設定を環境ごとに作る
gcloud config configurations create dev
gcloud config set project myapp-dev
 
gcloud config configurations create prod
gcloud config set project myapp-prod
 
# 切り替え
gcloud config configurations activate dev
 
# 今どこを向いているかを常に確認
gcloud config list
kubectl config current-context

さらに効くのは次の2つです。

  • シェルのプロンプトに現在のプロジェクト / コンテキストを表示する(見えていれば間違えない)
  • 危険なコマンドでは --project を明示する(設定に依存させない)
# 設定がどうなっていようと、対象が一意に決まる
gcloud compute instances delete worker-01 --project=myapp-dev

そして本番のプロジェクトに入った時は、最初に打つのを必ず確認系にする。 gcloud config list と gcloud auth list の2つです。

これは儀式ではありません。手が覚えている操作ほど、環境を確認せずに実行してしまうからです。 kubectl delete pod を dev の感覚で prod に打った事故は、どの現場にもあります。

実務の落とし穴まとめ

  1. roles/editor で通す — 一度動くと誰も権限を削りに戻らない
  2. 人間のアカウントでアプリを動かす — 退職で止まり、監査ログが意味を失う
  3. アプリの権限を自分の権限で確かめる — 実際に動くのはサービスアカウント
  4. DB にグローバル IP を付ける — 数分でスキャンされ、守りがパスワード1枚になる
  5. 0.0.0.0/0 の一時ルール — 一時的なつもりのルールは閉じられない
  6. 検証用の GKE クラスタとロードバランサの放置 — 請求が跳ねる代表格
  7. 本番で debug ログ — 取り込み料と保存料の両方が効く
  8. プロジェクトの切り替え忘れ — 注意力ではなく gcloud config とプロンプト表示で防ぐ

まとめ

  • クラウドの本質は安さではなく、インフラを API とコードで扱えること
  • プロジェクトは課金・権限・リソースの境界。環境はプロジェクトごと分ける
  • IAM は プリンシパル × ロール × リソース。権限エラーの原因は必ずこの3つのどれか
  • プログラムはサービスアカウントで動く。人間のアカウントで本番を触らない
  • 権限エラーは プロジェクト → 実行者 → 不足権限名 → ロール → 階層 → 反映待ち の順で見る
  • 外に出してよいのは原則ロードバランサだけ。DB は内部 IP のまま、経路を用意する
  • コンピュートは運用の重さと自由度のトレードオフ。「全部 GKE」は判断ではない
  • 自前構築のコストは構築ではなく運用に出る。人の時間を勘定に入れる
  • 請求が跳ねるのは Egress・アイドル課金・ログ量。ラベルと予算アラートで追える状態にする
  • 本番では read-only で見る → plan / diff を読む → --project を明示する

公式ドキュメント

迷ったら一次情報に戻ってください。

対象リンク
Google Cloud ドキュメントhttps://cloud.google.com/docs?hl=ja
Google Cloud アーキテクチャセンターhttps://cloud.google.com/architecture?hl=ja
AWS ドキュメントhttps://docs.aws.amazon.com/ja_jp/
AWS Well-Architected フレームワークhttps://aws.amazon.com/jp/architecture/well-architected/

章末問題

検証のために Cloud SQL にグローバル IP を付け、ファイアウォールで 0.0.0.0/0 からの接続を許可しました。最も深刻な問題はどれですか。

次の章では、ここまで見た構成要素をコードとして管理する方法を扱います。 画面をクリックして作ったインフラは、再現も追跡もできません。

読み終わったら記録しておくと、目次で進み具合が分かります。