第03章:“設計”の超入門:本番で死にやすい3つ ☠️➡️✅
この章はね、「ローカルだと動くのに、本番に出した瞬間に事故る😇」を先回りで潰す回だよ〜!🚀 コンテナをWebへ出すと、実行環境が“やさしくない世界”になるのがポイント🥶
まず結論:本番で死にやすい3つ 💥💥💥
- ステートレスじゃない(メモリ・ローカルファイルに頼ってる)🧠📁
- 設定を埋め込んでる(環境変数/Secretsに逃がしてない)🧩🔑
- 落ちたとき戻れない(ヘルスチェック/自己復旧の前提がない)🩺🔁
この3つを「最低限」押さえるだけで、Cloud Runみたいな環境(= いつでも増える/消える)でも生き残りやすくなるよ✨ (Cloud Run は “stateless container” を前提にした世界観だよ、というのが公式トレーニングの説明にも出てくるよ)(Google Cloud Documentation)
1) ステートレス:コンテナは“使い捨て”のつもりで 🧻🐳
何が起きるの?😵
本番(特にサーバレス系)は、ざっくりこう👇
- アクセス増える → インスタンスが増える📈
- 暇になる → インスタンスが消える💨
- 何か起きる(メモリ不足など)→ インスタンスが強制終了💥(処理中リクエストは 500 になりうる)(Google Cloud Documentation)
つまり… **「このコンテナのメモリやディスクに置いた状態は、いつでも消える」**が基本ルールだよ🫠
ステート(状態)ってどれ?🧠
初心者がやりがちなのはこの辺👇
- メモリ変数にカウンタ、ログイン状態、キャッシュを持つ🧠
uploads/にアップロードファイルを保存する📁- セッションをメモリに置く(再起動でログアウト祭り)🍂
正しい逃がし先(ざっくり)🏃♂️💨
- 永続データ → DB(例:Cloud SQL / Firestore など)🗄️
- ファイル → オブジェクトストレージ(例:Cloud Storage)🪣
- セッション → 外部ストア or 署名付きトークン🍪
💥ミニ実験:メモリに状態があると“増えた瞬間”壊れる
「同じアプリを2つ起動」して、カウンタがズレるのを見てみよう😈
① わざと“地雷コード”を用意する💣
// src/index.ts
import express from "express";
const app = express();
const port = Number(process.env.PORT ?? 8080);
// 🚨地雷:メモリに状態(インスタンスごとに別)
let counter = 0;
app.get("/", (_req, res) => {
res.send("OK 😺");
});
app.get("/counter", (_req, res) => {
counter += 1;
res.json({ counter, pid: process.pid });
});
app.listen(port, "0.0.0.0", () => {
console.log(`listening on ${port}`);
});
② “2個のコンテナ”を別ポートで動かす🐳🐳
(Dockerfileは後の章でちゃんとやるけど、この実験だけの最小版👇)
## Dockerfile (実験用ミニ)
FROM node:24-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
## PowerShell(VS CodeのターミナルでOK)
docker build -t ch3-demo .
docker run --rm -p 3001:8080 ch3-demo
## 別ターミナルでもう1個
docker run --rm -p 3002:8080 ch3-demo
③ 叩いてみる🔨
curl http://localhost:3001/counter
curl http://localhost:3001/counter
curl http://localhost:3002/counter
curl http://localhost:3002/counter
結果はだいたいこうなる👇
3001側の counter が 1,2…3002側の counter が 1,2…(別世界)
👉 これが本番で“勝手に増えた瞬間”起きる事故だよ😇
2) 設定外出し:同じイメージで、環境だけ変える 🧩🌍
なんで必要?🤔
本番って、
- URL が違う
- DB が違う
- APIキーが違う
- ログの出し方が違う
みたいに「環境ごとに違い」が出るよね🧠 それをコードにベタ書きしてると、事故る or 管理不能になるやつ😵💫
最低限の正解:環境変数(Env)に逃がす🌱
Cloud Run でも「環境変数を設定してデプロイ」が公式の基本導線だよ(Google Cloud Documentation)
例👇
// src/config.ts
function must(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`Missing env: ${name}`);
return v;
}
export const APP_ENV = process.env.APP_ENV ?? "local";
export const DATABASE_URL = must("DATABASE_URL"); // 本番は必須
💡ポイント
- “無いなら落ちる”(= 変な設定で動き続けない)🧯
APP_ENVみたいな“切り替えスイッチ”は便利🎛️
Secrets(秘密)は別枠!🔑🙅♂️
APIキーやDBパスワードを .env や Git に置きっぱなし…は超危険⚠️
Cloud Run は Secret Manager の秘密を「環境変数」または「ファイルマウント」で渡せるよ(Google Cloud Documentation)
🤖AIに投げると強いプロンプト例(コピペ用)✨
- 「このNode/TSプロジェクトで、環境変数にすべき値を一覧化して。名前案も付けて」🧩
- 「Secretsにすべき値と、通常の設定値を分けて提案して」🔑
- 「起動時に“必須の環境変数がないと落ちる”チェックを追加して」🧯
※ただし 秘密(実キー)を貼らないのは絶対守ってね🙅♂️🙅♂️🙅♂️
3) 落ちても復帰:ヘルスチェックで“自己回復”前提に 🩺🔁
そもそもヘルスチェックって?👀
「このコンテナ、ちゃんと生きてる?」「準備できた?」を外から判断する仕組みだよ🩺 Cloud Run は startup / liveness / readiness みたいなプローブを用意してて、
- 起動できた?(startup)
- 死んでない?(liveness → 必要なら再起動)
- 受け付けていい?(readiness) を調整できるよ(Google Cloud Documentation)
(Readiness は Preview 扱いとして明記されてるよ)(Google Cloud Documentation)
最小の実装:/healthz を作る ✅
app.get("/healthz", (_req, res) => {
// ⚠️重い処理しない(DB全件読み込みとか絶対ダメ😇)
res.status(200).send("ok");
});
ちょい上級:/readyz(依存先OKなら ready)🔧
DBが必要なアプリなら「DBに繋がるか」まで見たいことがあるよね🗄️ 超ざっくり例👇(雰囲気だけ掴めればOK)
let ready = false;
async function warmup() {
// ここでDB接続チェックなどをする想定(実装は後の章でOK)
ready = true;
}
warmup();
app.get("/readyz", (_req, res) => {
res.status(ready ? 200 : 503).send(ready ? "ready" : "not-ready");
});
💡Cloud Run 側も、プローブ失敗が続くと“暴走再起動ループ”を抑える動きがあるよ、って公式にも書かれてる🧯(Google Cloud Documentation)
ここまでを“1枚のチェックリスト”にする ✅📝
✅あなたのアプリ、今すぐ自己点検(Yes/NoでOK)
- メモリ変数に「ログイン状態」「カウンタ」「キュー」を持ってない?🧠
-
uploads/やtmp/に“残したいデータ”を書いてない?📁 - 画像/CSVなどのファイルは外部ストレージに逃がす前提?🪣
- 設定値(URL/モード/閾値)がコードに直書きされてない?🧩
- APIキー/パスワードがGit、Dockerfile、ログに混ざってない?🔑
-
/healthzはある?🩺 - “依存先が死んでる時”に、落ち方が分かりやすい?(ログ/ステータス)😵💫
- 環境変数が無いとき、起動直後に気づける?🧯
ミニ課題(やると一気に強くなる)🔥
あなたの今のアプリ(または作りたいアプリ)について👇を作ってみて!
-
「状態」を洗い出す🔎
- メモリにあるもの
- ローカルファイルにあるもの
- DB等にあるもの
-
設定値とSecretsを分ける🧩🔑
- 設定(環境で変わるけど秘密じゃない)
- Secrets(漏れたら即死)
-
/healthzを入れる🩺✅- まずは軽いOK応答だけでOK!
おまけ:Cloud Runで“基本の約束”っぽいやつ(1個だけ覚えればOK)📌
Cloud Run のサービスは、PORT 環境変数で渡されるポートに 0.0.0.0 で待ち受けるのが契約だよ(127.0.0.1待ち受けはNG)(Google Cloud Documentation)
これ、後の章で必ず効いてくるやつ😵💫➡️😆
次の章に行く前に、もしよければ👇だけ教えて!😊✨ あなたが「そのままデプロイ」したいのって、どっち寄り?
- **API(Express/Fastify)**🧩
- **Web(Next/Vite + SSRなど)**🌐
- バッチ/ジョブ寄り⚙️
(どれでも進められるけど、例がより刺さるように寄せられるよ〜!🎯)