GKE Autopilotで大きなファイルを扱う

# はじめに
Google Kubernetes Engine（GKE）Autopilotの環境で大きなファイルを扱う方法について紹介します。データは永続化目的ではなく、処理の過程で一時的に必要となるものを想定しています。今回はDigdagをGKE上で利用するケースを例に紹介します。Digdag と GKE Autopilotの詳細な構成は以下のテックブログに記載されています。

https://techblog.zozo.com/entry/digdag-on-gke-autopilot


# やりたいこと
現在、DBから抽出したデータを加工して一つのファイルにZIP圧縮し、Google Cloud Storage（GCS）に配置して他システムと連携している既存のアプリケーションがあります。具体的には、DigdagワークフローのKubernetesCommandExecutorのPodから、py>オペレータを使用してPythonアプリケーションを動かしています。このアプリケーションでは、ブートディスクのエフェメラルストレージを使用していましたが、GKE Autopilot環境では10GiBまでのディスク容量制限があるため、ディスクスペース不足の問題を解決する必要があります。


# 方法1： プログラムでローカルディスクを使用せず、GCS上で直接読み書きする
PythonのプログラムからGCS上のファイルを[直接操作](https://cloud.google.com/python/docs/reference/storage/latest/google.cloud.storage.blob.Blob)することができます。io.BytesIOなどの[ストリーム](https://cloud.google.com/storage/docs/samples/storage-stream-file-upload)を組み合わせることで、大きなファイルもローカルのディスクスペースに依存することなく読み書きすることが可能です。

また、単純なファイル結合であれば[compose](https://cloud.google.com/storage/docs/composing-objects?hl=ja#storage_compose_object-python)を使用することで、GCS上で完結できます。こちらはコマンドラインから[gsutil](https://cloud.google.com/storage/docs/composing-objects?hl=ja#create-composite-cli)でも使用できます。


# 方法2： ブートディスクではなく、専用のエフェメラルボリュームを利用する
エフェメラルストレージについて、デフォルトではemptyDirを使用してマウントしたボリュームから割り当てられますが、Autopilotでは10 MiB～10 GiBの範囲という制限があります。そのため、より大きなサイズが必要な場合は、専用のエフェメラルボリュームを別途マウントしてリクエストする必要があります。これにより、Autopilotでも最大64 TiBまで使用することができるようになります。この方法はインフラのみの変更のため、アプリケーションには影響を与えません。


# 結果
今回は運用中のプログラムが既にあることから、方法2で実現しました。性能と運用コストに問題がなかったため、既存のロジックを全く変更することなく、開発・テスト工数を抑えて実現することができました。備忘録を兼ねて、以下にKubernetesのvolume周りに関する基本的な情報と設定サンプルを記載します。


### PersistentVolume（PV）とPersistentVolumeClaim（PVC）
**PersistentVolume（PV）** は、クラスタ内で永続的なストレージを管理するためのリソースです。PVはクラスタ管理者によって事前に作成されるか、動的にプロビジョニングされます。PVはPodのライフサイクルとは独立して存在し、データの永続性を保証します。

**PersistentVolumeClaim（PVC）** は、PVを要求するためのリソースです。PVCは特定のサイズ、アクセスモード、およびStorageClassを指定してPVを要求します。PVCが要求を満たすPVを見つけると、そのPVにバインドされ、PodがそのPVをボリュームとして使用できるようになります。

### StorageClass
**StorageClass** は、ストレージのプロビジョニング方法を定義するリソースです。StorageClassを使用することで、異なるストレージタイプ（例えば、pd-standardやpd-ssd）を簡単に管理できます。
また、WaitForFirstConsumer ボリュームを設定することでKubernetes は Pod がスケジュールされているのと同じゾーンに永続ディスクをプロビジョニングします。

以下は、pd-balancedを使用するStorageClassの例です：
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ephemeral-ssd
provisioner: pd.csi.storage.gke.io
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: pd-balanced
```

**volumeClaimTemplate** は、KubernetesのStatefulSetで使用されるテンプレートで、各Podに対して個別のPersistentVolumeClaim（PVC）を自動的に作成するために使用されます。これにより、StatefulSet内の各Podが独自の永続ストレージを持つことができます。

以下は、上記のStorageClassを使用してPVCを作成する例です：

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: ephemeral-ssd
```

### 設定サンプル
以下は、デフォルトの10GiBの制限があるDigdag KubernetesCommandExecutorでの設定例です。10GiBを超えてリクエストすると、割り当ての際にエラーとなります。

```digdag
kubernetes:
  Pod:
    tolerations:
      - key: node-type
        operator: Equal
        value: non-kube-system
        effect: NoSchedule
    resources:
      requests:
        cpu: 1000m
        memory: 4Gi
        ephemeral-storage: 10Gi
      limits:
        cpu: 1000m
        memory: 4Gi
        ephemeral-storage: 10Gi
```

以下は、方法2で専用の永続ディスクをエフェメラルボリュームとして使用する設定例です。この方法では、最大64TiBまでのディスク容量を指定できます。ここでは、Digdag KubernetesCommandExecutorの設定例を示します。resourcesセクションでのephemeral-storageの代わりにvolumeMountsセクションが追加され、16GiBの容量をリクエストしています。


```digdag
kubernetes:
  Pod:
    tolerations:
      - key: node-type
        operator: Equal
        value: non-kube-system
        effect: NoSchedule
    resources:
      requests:
        cpu: 1000m
        memory: 4Gi
        # ephemeral-storage: 10Gi
      limits:
        cpu: 1000m
        memory: 4Gi
        # ephemeral-storage: 10Gi
    volumeMounts:
    - mountPath: "/tmp"
      name: ephemeral-volume
    volumes:
    - name: ephemeral-volume
      ephemeral:
        volumeClaimTemplate:
          metadata:
            labels:
              type: ephemeral
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: "ephemeral-ssd"
            resources:
              requests:
                storage: 16Gi
```


### 悩んだところ
既存のプログラムは `/tmp` ディレクトリをフルパスで指定してファイルを加工しているため、そのディスクスペースを拡張する必要がありました。Kubernetesでボリュームをマウントする際に、マウント先をどうしようか悩んでいましたが、既存の `/tmp` を隠す形でそのままマウントすることができたため（アンマウントすれば元の/tmpに戻る）、プログラムの変更は不要でした。

また、STG環境や本番環境とは違い、ローカルの開発環境では DockerCommandExecutor を使用しているため、これらのボリューム設定は反映されませんが、コンテナの `/tmp` がそのまま使用できるので、開発環境にも影響がなくて済みました。



#### 参考
- https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes
- https://cloud.google.com/kubernetes-engine/docs/how-to/generic-ephemeral-volumes
- https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests#min-max-requests


# おわりに
Google Cloudのドキュメントなどで専用のエフェメラルボリュームを使用する方法は見つけられますが、それをDigdag のKubernetesCommandExecutorで実際にどう指定したら良いか自分が悩んだので共有してみました。

中途入社してちょうど1年が経ちました。最近、いい歳してUSキーボードに乗り換えました。最初はスケートボードでスイッチスタンスを練習しているかのような楽しさ（？）がありましたが、割とすぐに馴染めてホッとした反面、少し寂しさすら感じています。この1年間は環境や技術など自分には新鮮なことが多くて楽しめたので、今後もまた新しいことに興味を持って取り組んでいこうと思います！