はじめに
Kubernetes を使い始めると、
多くの人が HPA(Horizontal Pod Autoscaler) に大きな期待を寄せます。
- トラフィックが増えたら自動で Pod が増える
- 負荷が下がったら自動で Pod が減る
- 人が張り付かなくても安定運用できる
一見すると、HPA を設定するだけで
“スケール問題はすべて解決する” ように思えるかもしれません。
しかし、実務の現場では次のような声をよく聞きます。
- HPA を設定したのに Pod が増えない
- スケールはするがレスポンスが改善しない
- 急に Pod が落ちてサービスが不安定になる
- CPU 使用率の数字が信用できない
これらの原因は、HPA の設定ミスというよりも
HPA とリソース制限(requests / limits)を前提にした設計ができていないことにあります。
本記事では、
「HPA を使う」ではなく「HPA が正しく機能する設計を作る」
という視点で、考え方からハンズオンまでを丁寧に解説します。
HPA とは何かを改めて整理する
HPA(Horizontal Pod Autoscaler)は、
Pod の負荷状況を監視し、レプリカ数を自動で増減させる仕組みです。
重要なのは、HPA がやっていることは非常にシンプルだという点です。
- CPU 使用率が高い → Pod を増やす
- CPU 使用率が下がった → Pod を減らす
👉 HPA は「Pod を増やすだけ」で、アプリを速くはしてくれません
つまり、
- 1 Pod あたりの性能
- Pod の起動速度
- Pod が落ちたときの挙動
これらはすべて 設計者の責任になります。
HPA が正しく動くための前提条件
HPA は万能ではなく、
いくつかの前提条件を満たして初めて意味を持つ仕組みです。
前提①:リソース requests が正しく設定されている
resources:
requests:
cpu: "100m"
memory: "128Mi"
HPA は CPU 使用率 / requests を基準に
「負荷が高いかどうか」を判断します。
requests が設定されていない、または適当だと、
- 使用率が異常に高く見える
- 逆に全然スケールしない
といった問題が発生します。
前提②:Metrics Server が動作している
HPA はメトリクスがなければ動きません。
- CPU 使用率
- メモリ使用量
これらは Metrics Server が提供しています。
👉 「HPA が動かない」原因の多くは Metrics Server 未導入
前提③:アプリがスケール前提で作られている
HPA は Pod を増やしますが、
アプリがスケールを前提としていなければ意味がありません。
- セッションを Pod 内に持っていないか
- ファイルをローカルに保存していないか
- 起動に何十秒もかからないか
Kubernetes の世界では、
Pod は消耗品 という考え方が基本です。
requests / limits の役割を正しく理解する
requests:最低保証ライン
requests は、
- この Pod が最低限必要なリソース
- スケジューラが Pod を配置する判断材料
- HPA の計算基準
という、非常に重要な値です。
limits:絶対に超えてはいけない上限
limits は、
- CPU:超えるとスロットリング
- メモリ:超えると即 OOM Kill
という 強制的な制限です。
特にメモリは、
一度超えたら容赦なく Pod が再起動します。
実務で多い誤解
- 「limits だけ設定すればいい」
- 「requests は小さい方がお得」
👉 どちらも危険な考え方です。
requests が小さすぎると、
HPA は「常に高負荷」と誤認し、
無意味なスケールを繰り返します。
HPA × Dockerfile × アプリ設計はセットで考える
HPA は Kubernetes の機能ですが、
Dockerfile とアプリ設計が HPA の成否を決定します。
なぜ Dockerfile が重要なのか?
- イメージが大きい → 起動が遅い
- 起動が遅い → スケールに追従できない
- SIGTERM 未対応 → スケールダウン時に事故
👉 HPA は「速く増えて、きれいに減る」ことが前提
ハンズオン①:HPA 前提のサンプルアプリ
アプリ構成
hpa-app/
├─ Dockerfile
├─ package.json
└─ index.js
index.js(CPU 負荷を意図的に発生させる)
const http = require("http");
function heavyTask() {
let sum = 0;
for (let i = 0; i < 1e7; i++) {
sum += i;
}
return sum;
}
const server = http.createServer((req, res) => {
heavyTask();
res.end("Hello HPA");
});
server.listen(3000);
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down...");
server.close(() => process.exit(0));
});
このように
CPU を使う処理を意図的に入れることで、
HPA の挙動を確認しやすくします。
ハンズオン②:HPA 向け Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --only=production
COPY index.js .
USER node
EXPOSE 3000
CMD ["node", "index.js"]
ポイント
- 軽量イメージ
- 非 root 実行
- 起動が速い
👉 HPA との相性が良い Dockerfile
ハンズオン③:Deployment(リソース設計)
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-app
spec:
replicas: 1
selector:
matchLabels:
app: hpa-app
template:
metadata:
labels:
app: hpa-app
spec:
containers:
- name: app
image: hpa-app:1.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
ハンズオン④:HPA 定義
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-app
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
ハンズオン⑤:スケール挙動を確認する
kubectl apply -f deployment.yaml
kubectl apply -f hpa.yaml
kubectl get hpa
負荷をかけます。
kubectl run -it --rm load \
--image=busybox \
--restart=Never -- sh
while true; do wget -q -O- http://hpa-app; done
別ターミナルで:
kubectl get pods -w
👉 Pod が段階的に増えていくことを確認してください。
HPA 設計で頻発する失敗パターン
① requests が適当
- 小さすぎて常に高負荷判定
- 大きすぎてスケールしない
② limits が厳しすぎる
- CPU スロットリング
- レスポンス劣化
- スケールしても改善しない
③ アプリがステートフル
- セッションが Pod 固有
- スケール=ユーザー切断
実務向け設計の考え方
- requests:平均的な通常負荷
- limits:想定ピーク + 余裕
- HPA:段階的に増える設定
- Pod:いつでも消える前提
チェックリスト(本番前確認用)
- requests / limits を設定している
- HPA の target が妥当
- Pod 起動が速い
- SIGTERM に対応
- ステートレス設計
まとめ
HPA とリソース制限は、
- Kubernetes の自動化の心臓部
- 設計ミスが即障害につながる
- Dockerfile / アプリ設計と不可分
という特徴を持っています。
「HPA を有効にした」ではなく
「HPA が意味を持つ設計をした」
この意識が、
Kubernetes 本番運用を安定させる最大のポイントです。
コメントを残す