GKE Autopilot を安定稼働させるための設定リスト

GKE Autopilot を安定稼働させるための設定リストを紹介します。
Kubernetes 全般で考慮すべき事項も含んでいますのでチェックしてみてください。

- GKE 関連
  - コネクションドレインで既存リクエストを完了させる
  - Release channel で環境に応じたバージョン戦略を採る
  - メンテナンスウィンドウで自動メンテナンスの影響を最小限にする

- Kubernetes 全般
  - HPA で負荷に応じて Pod をスケールさせる
  - PDB で Node 排出時のサービス継続を担保する
  - PriorityClass で重要な Pod を確実に起動させる
  - ヘルスチェックでアプリケーションの正常性を担保する

# GKE 関連
## コネクションドレインで既存リクエストを完了させる
[コネクションドレインのタイムアウト](https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features?hl=ja#draining_timeout)は Pod が削除された際に既存のリクエストを完了させるために設ける猶予時間です。
これによりデプロイによる Pod 入れ替わりのタイミングで502エラーが発生するのを抑制できる場合があります。

```yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: sample-api
spec:
  timeoutSec: 300
  connectionDraining:
    drainingTimeoutSec: 30
```

:::note info
同時に Pod の終了処理への考慮も必要です。[Googoleのドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing#traffic_does_not_reach_endpoints)が参考になります。
Pod の終了に関しては[アルパカでもわかる安全なPodの終了](https://zenn.dev/hhiroshell/articles/kubernetes-graceful-shutdown)が分かりやすかったです。
:::

## Release channel で環境に応じたバージョン戦略を採る
GKE は [Release channel](https://cloud.google.com/kubernetes-engine/docs/concepts/release-channels?hl=ja) を使ってクラスタのバージョンアップを自動化できます。
チャネルには `Rapid`, `Regular`, `Stable` の3種あります。
クラスタが所属するチャネルによって Kubernetes 本家のバージョンへの追従速度が変わります。

例えば、環境毎に次のように設定する運用が考えられます。

|環境  |チャネル  |説明 |
|---|---|---|
|Dev |Rapid |最悪壊れてもよいので先行して新バージョンを試していく |
|Stg |Regular |Prd と同じチャネルにしておく |
|Prd |Regular  |Regular でもよいが Stable でもよさそう |

[アップグレードの通知](https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-notifications?hl=ja)を Slack に流すことでアップデートの準備を進められます。

>理想的には、UpgradeAvailableEvent 通知タイプを使用することで、今後のアップグレードを認識し、事前にアップグレードすることや、メンテナンスの時間枠の設定などの準備に必要な対策を講じることができます。
https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-notifications?hl=ja#upgradeavailable

## メンテナンスウィンドウで自動メンテナンスの影響を最小限にする
GKE ではバージョンアップグレードを始めとした自動メンテナンスが実行されます。
サービスへの影響を最小限に抑えるために[メンテナンスウィンドウ](https://cloud.google.com/kubernetes-engine/docs/concepts/maintenance-windows-and-exclusions#maintenance_windows)を設定しましょう。

トラフィックを分析して最もリクエストの少ない時間帯に設定しておくとよさそうです。

# Kubernetes 全般

## HPA でリソース状況に応じて Pod をスケールさせる
[HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) は、特定の指標を基に自動的に Pod をスケールさせるリソースです。
下の例では、CPU 使用率をオートスケールの指標とし目標値を60%に定めています。

```yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
...(中略)
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-api
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  minReplicas: 3
  maxReplicas: 10
```

## PDB で Node 排出時のサービス継続を担保する

[PDB](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) は Node の排出処理の際に停止してもよい（or 起動させておきたい） Pod 数を指定するリソースです。
下の例では、停止してもよい Pod 数を1台までに制限しています。（%での指定も可）

```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
...(中略)
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: sample-api
```

## PriorityClass で重要な Pod を確実に起動させる

[PriorityClass](https://kubernetes.io/ja/docs/concepts/configuration/pod-priority-preemption/) は、Pod の起動に優先順位をつけるためのリソースです。
下の例では、nginx の Pod の優先度を1000に設定しています。`priorityClassName` が未設定の Pod は優先度が0(default)なので、nginx の Pod は他の Pod をプリエンプトしてスケジューリングされます。

```yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000
globalDefault: false
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  priorityClassName: high-priority
```

:::note info
Daemonset を使う場合は PriorityClass が有効です。
:::

## ヘルスチェックでアプリケーションの正常性を担保する
Kubernetes には3種のヘルスチェックが用意されています。
適切に設定することでアプリケーションの正常性を担保できます。

3種の使い分けはざっくり次の通りです。

|種類  |使い分け  |失敗時の動作  |
|---|---|---|
|Liveness Probe |コンテナの生存確認 |コンテナを再起動 |
|Readiness Probe |外部からの振る舞い確認 |Serviceからルーティングされない|
|Startup Probe |Podの起動を確認  |他のProbeを無効にする |

下の例では、`startupProbe` で 300秒間 (`failureThreshold * periodSeconds`) Pod の起動を待ち、 `readinessProbe` が2回連続失敗したらルーティング先から外し、`livenessProbe` が3回連続失敗したらコンテナを再起動するという設定をしています。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    startupProbe:
      httpGet:
        path: /health
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 0
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 30
      timeoutSeconds: 1
    readinessProbe:
      httpGet:
        path: /health
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 0
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 2
      timeoutSeconds: 1
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 0
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 3
      timeoutSeconds: 1
```

# その他
Key は限られますが [Node Affinity](https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=ja#node_selectors_and_node_affinity) や [Pod Affnitiy](https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=ja#pod_affinity_and_anti-affinity) が Autopilot でも利用できます。
ワークロードを分離したい場合や、特定の Pod がある Node にデプロイしたい場合などに使えそうです。