KubernetesでCrashLoopBackOffを意図的に発生させ、待機Podを自動スケールする方法【Flask + Python】

この記事では、**CrashLoopBackOff状態を再現する方法**と、**それをトリガーに待機していた別Podを自動でスケールアウト（replicas=1）する構成**を、Python × Flask × Kubernetes環境で構築する手順をまとめます。

この仕組みは、障害発生時の自己復旧ロジックやフェイルオーバー実装のベースとしても活用できます。

## 🎯 目的
- CrashLoopBackOffを**テスト用トリガー**として確実に発生させる  
- Flaskアプリがクラッシュしたら、**standby-app** を自動で1レプリカ起動する  
- 障害検知から自己修復までの**自動監視＆スケーリング**を組み込む  

---

## 🧱 構成イメージ

- **crash-app**（replicas:1、起動後30秒で強制終了→CrashLoopBackOff）  
- **standby-app**（replicas:0で待機）  
- crash-appのPodがCrashLoopBackOffになると、standby-appをreplicas=1で起動  

本番環境を止めずに、予備Podへフェイルオーバーさせる簡易構成です。

---

## 1. CrashLoopBackOff発生用Flaskアプリ例

### ✅ `app.py`

```python
import os
import threading
import time
from flask import Flask

CRASH_DELAY = int(os.getenv("CRASH_DELAY", "30"))

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from crash-app!"

@app.route("/healthz")
def healthz():
    return "OK", 200

def crash_after():
    time.sleep(CRASH_DELAY)
    os._exit(1)  # 即時終了でCrashLoopBackOffを誘発

if __name__ == "__main__":
    threading.Thread(target=crash_after, daemon=True).start()
    app.run(host="0.0.0.0", port=5000)
````

ポイント：環境変数 `CRASH_DELAY` 秒後に `os._exit(1)` でコンテナを異常終了させ、CrashLoopBackOff状態を確実に作ります。

---

## 2. crash-app Deployment設定

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crash-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: crash-app
  template:
    metadata:
      labels:
        app: crash-app
    spec:
      containers:
        - name: crash-app
          image: your-registry/crash-app:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: CRASH_DELAY
              value: "30"
          ports:
            - containerPort: 5000
          livenessProbe:
            httpGet:
              path: /healthz
              port: 5000
            initialDelaySeconds: 10
            periodSeconds: 15
            failureThreshold: 1
```

* **livenessProbe** が `/healthz` に1回でも失敗すると再起動 → CrashLoopBackOffへ移行します。

---

## 3. standby-app Deployment設定

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: standby-app
spec:
  replicas: 0  # ← 初期は待機状態
  selector:
    matchLabels:
      app: standby-app
  template:
    metadata:
      labels:
        app: standby-app
    spec:
      containers:
        - name: standby-app
          image: your-registry/crash-app:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: CRASH_DELAY
              value: "120"  # 長めに設定してCrashLoopBackOffになりにくく
          ports:
            - containerPort: 5000
          readinessProbe:
            httpGet:
              path: /healthz
              port: 5000
            initialDelaySeconds: 5
            periodSeconds: 10
```

`replicas: 0` のまま待機し、必要に応じて監視スクリプトでスケールアウトします。

---

## 4. 監視＆自動スケーリングロジック

### ✅ `scale.py`

```python
from kubernetes import client, config, watch

config.load_incluster_config()
apps_v1 = client.AppsV1Api()
core_v1 = client.CoreV1Api()

def scale_deployment(name: str, replicas: int, namespace: str = "default"):
    patch = {"spec": {"replicas": replicas}}
    apps_v1.patch_namespaced_deployment_scale(name, namespace, patch)
    print(f"{name} を {replicas} レプリカにスケールしました")

def monitor_crash_app():
    w = watch.Watch()
    for event in w.stream(core_v1.list_namespaced_pod,
                          namespace="default",
                          label_selector="app=crash-app"):
        pod = event["object"]
        for cs in pod.status.container_statuses or []:
            if cs.state.waiting and cs.state.waiting.reason == "CrashLoopBackOff":
                scale_deployment("standby-app", 1)
                w.stop()
                return

if __name__ == "__main__":
    monitor_crash_app()
```

* **kubernetes-client** の Watch 機能で `crash-app` PodのCrashLoopBackOffを検出
* 検出時に `standby-app` を1レプリカにスケール

---

## 5. crash-app に監視スクリプトを組み込む

`crash-app` のPod内でSidecarとして動かす場合、以下のようにDeploymentへ追加します。

```yaml
      containers:
        - name: crash-app
          # …（省略）…
        - name: crash-monitor
          image: your-registry/crash-monitor:latest
          command: ["python", "/opt/scale.py"]
      volumeMounts:
        - name: kube-config
          mountPath: /root/.kube
      volumes:
        - name: kube-config
          configMap:
            name: kube-config  # ServiceAccount不要でアクセス可能なら省略可
```

※上記は例です。別PodとしてデプロイしてもOKです。

---

## 6. RBAC設定

`crash-monitor` にDeploymentスケール権限を与えるRoleとRoleBindingを作成します。

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-scaler
  namespace: default
rules:
  - apiGroups: ["apps"]
    resources: ["deployments/scale"]
    verbs: ["get", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bind-deployment-scaler
  namespace: default
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default
roleRef:
  kind: Role
  name: deployment-scaler
  apiGroup: rbac.authorization.k8s.io
```

---

## 🔁 実行フローまとめ

1. `crash-app` 起動 → 30秒後に `os._exit(1)` で異常終了
2. KubernetesがCrashLoopBackOff判定
3. `crash-monitor` (scale.py) が検知 → `standby-app` を1レプリカにスケール
4. standby-appが立ち上がり、あとはモニタリング継続

---

## ✅ 補足：CPU飽和によるCrashLoopBackOff例

```python
# cpu_burn.py
data = []
while True:
    data.extend([0] * 10**6)  # CPUとメモリを使い切る
```

Deploymentに組み込んで `resources.limits.cpu: "100m"` などを設定すると、CPU制限超過でOOMKilled → CrashLoopBackOff もテスト可能です。

---

## ✅ まとめ

| 方法                        | 再現性 | 備考          |
| ------------------------- | --- | ----------- |
| `os._exit(1)` + threading | ◎   | 最も直接的で失敗率ゼロ |
| livenessProbe失敗           | ○   | Probe 設定次第  |
| CPU/メモリ負荷（OOM）            | △   | リソース制限が必要   |

CrashLoopBackOffは**トリガー**として活用し、自動スケールやフェイルオーバー機能のテストと設計に役立てましょう。
これにより、本番環境での**高可用性・自己修復**構成をシンプルに実現できます。