Kubernetes の ephemeral storage

# はじめに

この記事では Kubernetes の ephemeral storage を利用する上での注意事項などについてまとめていきます。

# ephemeral storage とは

Kubernetes で ephemeral storage として扱われるものはつぎの3つになります。

- emptyDir volume
    - Pod でマウントして利用する emptyDir 内で利用するストレージ
- log directory
    - Pod で動作するすべてのコンテナから出力されるログ
- writable layers
    - Pod で動作しているすべてのコンテナのローカルディスクに対しての書き込みした内容

これは Pod.spec.resource.requests/limit で対象となるものも指しています。hostPath や [Local Volume](https://qiita.com/ysakashita/items/67a452e76260b1211920) など emptyDir 以外の Volume は含まないことに注意してください。

# ephemeral storage の resource.requests/limits

Pod.spec.resource.requests/limit に v1.11 から ephemeral storage も設定できるようになっています。利用方法は memory などと同じ要領で設定できるようです。詳しくは https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#requests-and-limits-setting-for-local-ephemeral-storage に記載してあります。

## Pod.spec.resource.requests に ephemeral-storage を設定した Pod はどのようにスケジュールされるのか

https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#how-pods-with-ephemeral-storage-requests-are-scheduled を見ると以下のように書いてあります。

> Pod が作成されたときスケジューラはその Pod を実行させる Node を選択します。
各NodeはPodに提供できるローカルephemeral storageの最大値を持っています。詳しくは、Node Allocatable を見てください。
スケジューラは Node のキャパシティよりスケジュールするすべてのコンテナのリクエストの合計が少ない Node のリソースを確保します。

resource.requests.memory などと同じ挙動をするようです。

## ephemeral-storage limits を持った Pod はどうなるのか

https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#how-pods-with-ephemeral-storage-limits-run を見ると以下のように書いてあります。

> コンテナレベルのアイソレーションでは、コンテナの writable layer と log の使用量がresource.limit の値を超えたとき、Pod は evict されます。
Pod レベルのアイソレーションでは、すべてのコンテナの local ephemeral storage の使用量と Pod の emptyDir Volumes が limit を超えたとき、Pod は evict されます。

### ephemeral-storage の使用状況の監視

上記の evict を行うためには各ストレージの使用状況を監視する必要があります。この監視をどうやって行っているかは https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#monitoring-ephemeral-storage-consumption に以下のように記載されています。

> local ephemeral storage が使われたとき、kubelet によって継続的に監視されています。その監視は 各 emptyDir Volume, log directory、writable layer を定期的にスキャンすることを行っています。

> Kubernetes 1.15 から emptyDir Volume (log directory や writable layer は含まない) クラスタ管理者が projects quotas を使うことによって管理することもできるようになりました。
projects quotas は元々 XFS で実装されていましたが、最近 ext4 でも移植されました。
projects quotas は監視と制限ができます。1.15 では監視のみが alpha として利用可能です。

> Quotas はディレクトリをスキャンするより早く、正確です。ディレクトリがプロジェクトにアサインされると、そのディレクトリの下に作成される全てのファイルはそのプロジェクト内に作成され、カーネルレベルでそのプロジェクトが利用するすべてのファイルがどのくらいブロックを利用しているかをトラックするだけですみます。ファイルが作成されて削除されたが、開いているファイル記述子を使用している場合は、スペースを消費し続けます。 このスペースはクォータによって追跡されますが、ディレクトリスキャンでは表示されません。
Kubernetesは1048576から始まる Project ID を利用します。その ID は/etc/projects と/etc/projid に登録され使われます。もしProject ID のレンジが他のシステムによって使われている場合、それらのプロジェクトのIDは Kubernetes が利用することを防ぐため /etc/projects と /etc/projid に登録しなければなりません。

> Project Quotas を有効にするためには以下の手順をクラスタ管理者が実行しなければなりません。
FeatureGate の LocalStorageCapacityIsolationFSQuotaMonitoring を有効にする.v1.15ではデフォルト無効です。
root パーティション(もしくはruntimeパーティション)をproject quotas が有効の状態で作成する。XFSのファイルシステムは有効になっているが、ext4の場合は指定してビルドしなければならない。
root パーティション(もしくはruntimeパーティション)をproject quotas が有効の状態でマウントする

このように kubelet に含まれている eviction-manager で行っているようです。具体的には https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/eviction_manager.go#L447 で行っています。

# eviction-manager

kubelet の eviction manager は ephemeral storage だけではなく　memory や pid などの Node のリソース状況を監視して、空きリソースが指定した値より少なくなったら evict を行うコンポーネントです。これは cpu 等の圧縮可能なリソースは特に対処は必要ありませんが、メモリやディスクなどの圧縮不可能(incompressible)なリソースは一度 Pod (Container) を停止しないと回収できないためこのようなコンポーネントで evict して回収しています。詳しくは https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/ に記載されています。

この eviction のしきい値は kubelet の起動パラメータの `--eviction-soft` または `--eviction-hard` で指定できます。
soft と hard の違いは evict されるときに指定する猶予時間(grace-period) の時間の差です。soft は Pod に指定した値または `--eviction-max-pod-grace-period` に指定された値の時間まで SIGKILL を送るのを待つためグレースフルにシャットダウンができますが、hard は即時 SIGKILL を送るためグレースフルにシャットダウンされません。`--eviction-soft` には default 値は設定されていないため、リソース不足のときにグレースフルにシャットダウンされないと困る場合は設定したほうが良いでしょう。

## どのように eviction-manager が動作しているか

基本的には https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/eviction_manager.go#L223 のメソッドを定期的に実行することで evict しています。
ここで Node のリソース状況を確認し、https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/eviction_manager.go#L265 でしきい値を超えているリソースを算出します。
そのリソースの中で GC などで回収できるものであれば先に GC を走らせる。それでも回収できないものまたは GC でも解決しないものがあれば evict の処理に移ります。
その後 active pods を取得して、https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/eviction_manager.go#L360 で evict すべき Pod の優先順位付けします。
memory は https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/helpers.go#L670で disk はちょっと複雑で https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/helpers.go#L987 にあるように imageFS を利用している場合などで処理を分けていますが基本的には以下の順番でソートして優先順位を決めています。

- requests の値を超えているか(超えていない場合は evict の対象外)
- Pod priority
- そのリソースの利用量

そのあと算出した activePods を https://github.com/kubernetes/kubernetes/blob/v1.15.1/pkg/kubelet/eviction/eviction_manager.go#L37 で evict します。evict したら return しているので evict される Pod は 1ループで一つのみのようです。

このように evict は実現されているようです。evict 自体はこれだけですが、 memory の場合は OOMKill などもあるので他の停止手段もありますので注意してください。

# まとめ

Kubernetes の ephemeral storage を使った際の注意点(主に evict の挙動)についてまとめました。今回ディスク容量不足によって Pod の hard evict されることを防ぐ方法はないかどうかを調査するためにまとめましたが、`--eviction-soft`を調整するくらいしかないことがわかりました。:cry:
本格的にローカルディスクを利用するには色々と注意事項がありそうなので引き続き調べていきたいと思います。