Kubernetes の Garbage Collection

# はじめに

この記事では Kubernetes の Garbage Collection についてまとめていきます。

# Garbage Collection とは

一般的な Garbage Collection(GC) はプログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する機能 (by [WikiPedia](https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3))ですが、Kubernetes で GC と呼ばれているものは以下のものがあります。

- [オブジェクトの GC](https://kubernetes.io/ja/docs/concepts/workloads/controllers/garbage-collection/)
    - API Server (etcd) に保持されている Kubernetes のオブジェクトのうち、かつてオーナーがいたが、現時点でもはやオーナーがいないようなオブジェクトの削除を行う GC
- [Pod の GC](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/podgc/gc_controller.go)
    - 存在しない Node に配置されている Pod とスケジュールされずに Terminating になった Pod を削除する GC
- [Image GC](https://kubernetes.io/docs/concepts/cluster-administration/kubelet-garbage-collection/)
    - Node に保持されているコンテナイメージのうち、かつて起動したが現時点ではもはや利用されなくなったコンテナイメージの削除を行うGC

今回はこれらのうちオブジェクトの GC について詳しく見ていきます。他のものは気が向いたら調査記事を書きたいと思います。

# オブジェクトの GC

Kubernetes はオブジェクトを削除するときにそのオブジェクトが親となっている子オブジェクトを削除する必要があることがありますが、そういった場合に子オブジェクトの削除を GC で行なっています。(e.g. kubectl で Deployment を削除するときは ReplicaSet, Pod の削除されると思いますがこういうケースです)
*Kubernetes 1.8 以前は kubectl やコントローラが直接子オブジェクトの削除を行っていましたが GC によって削除されるように変更されました。詳細は[KEP](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/garbage-collection.md
)を参照ください。*

また、Kubernetes は分散システムで構成されていることもあり様々な処理をアトミックに更新できていないため、処理の途中で失敗するとオーナーがすでに存在しない孤児のオブジェクトが発生したりすることがあります。オブジェクトの GC はこういった孤児（orphan）となったオブジェクトを定期的に回収(削除)します。

では実際に GC がどのように回収していくかを見ていきたいところですが、その前にいくつか GC が利用しているフィールドを見ていきます。

## OwnerReferences

`kubectl get pod -o yaml` などのコマンドを実行すると `metadata` に `ownerReferences` というフィールドが作られていることがあると思います。例えば以下のような例です。

```pod.yaml
apiVersion: v1
kind: Pod
metadata:
  ...
  ownerReferences:
  - apiVersion: apps/v1
    controller: true
    blockOwnerDeletion: true
    kind: ReplicaSet
    name: my-repset
    uid: d9607e19-f88f-11e6-a518-42010a800195
  ...
```

この `ownerReferences` は GC がそのオブジェクトのオーナーを判断するために利用していて、主にコントローラがオブジェクトを作成するときに自動的に設定します。

## Finalizers

Finalizers は CRD(CustomResourceDefinition) を利用されている方には馴染み深いですが、簡単に言うとオブジェクトを削除する前に実行する処理がある場合に削除をブロックするための機能です。これによってそのオブジェクトによって作成された子オブジェクトの削除だったり、Kubernetes の外のリソースの削除などの終了処理を簡単に含めることができます。
こちらも `metadata` の中にある `finalizers` というフィールドに保存され、このフィールドに値がある場合はオブジェクトの削除が行われないという挙動をします。この値の管理はそれを管理するためのコントローラを別に用意することになっており、Finalizer 毎にあるコントローラが責任を持ち終了処理を行います。
詳細は[こちら](https://github.com/kubernetes/community/blob/b3349d5b1354df814b67bbdee6890477f3c250cb/contributors/design-proposals/api-machinery/garbage-collection.md#part-i-the-finalizer-framework)などを見ると良いと思います。

GC が関係する Finalizers は以下のものがあります。

https://github.com/kubernetes/kubernetes/blob/v1.17.0/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L102

- FinalizerOrphanDependents: "orphan"
    - propagationPolicy Orphan で削除したときに設定される
- FinalizerDeleteDependents: "foregroundDeletion"
    - propagationPolicy Foreground で削除したときに設定される

これらの Finalizers が設定された場合後述する GC によって削除が行われ、完了すると `metadata.finalizers` から削除されます。

## GC の動き

ソースコードを読み取った限りでは以下のようなコンポーネントがあり、ざっくり説明すると以下のようになるようです。

- Scanner: APIが現在提供しているリソースの一覧を取得して、[Monitor を生成し](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/graph_builder.go#L179)(①)各リソースの変更を検知する(②)
- DependencyGraphBuilder
    - [Scanner から提供された検知のイベントを元に DAG(Directed acyclic graph) を作成し](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/graph_builder.go#L526)、Delete や Orphan にするオブジェクトがあれば workqueue に入れる(③)
        - ここのグラフの作成時に OwnerReferences を利用してオーナーを判断しています
        - Finalizers は DELETE API の呼び出したときに Option に指定によって API Server が設定し、GC はそれを見て各 worker に処理を渡し子オブジェクトの処理を行います
- Worker
    - DeleteWorker: [workqueue からオブジェクトを取得して依存関係を整理してオブジェクトを削除する](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/garbagecollector.go#L284)④
    - OrphanWorker: [依存関係を整理して Orphan Finalizer を削除する](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/garbagecollector.go#L583)④

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100565/694685c6-2eaf-13cd-ac80-c11f0507170a.png)

## 回収対象

回収対象はドキュメントなどを探してみましたが具体的に記載があるソースは見当たらなかったため、ソースコードから読み取りました。
基本的には [GetDeletableResources](https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/garbagecollector.go#L635) で取得したリソースで Ignore に指定されていないリソースが対象になるようです。読み解いた範囲では以下のようでした。

- `/api` と `/apis` の API から取得できる APIGroup に所属するリソース
- `delete`, `list`, `watch` の verb をサポートするリソース
    - https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/garbagecollector.go#L650
- Innore に指定されていない
    - https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/graph_builder.go#L192

# おまけ

## DynamicSharedInformer

DynamicSharedInformer が追加されたときに GC のためという話を聞いた気がしましたが、実際のソースを見ると https://github.com/kubernetes/kubernetes/blob/v1.17.0/pkg/controller/garbagecollector/graph_builder.go#L127 のようになっており、個別に Informer で Watch しているようです。DynamicSharedInformer は軽く検索した感じでは k/k では利用されていないようだったのでまだ未実装なだけなのかなにか別の理由があるのか知りたいところです。:thinking_face:

# 参考文献

- https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/garbage-collection.md
- https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/synchronous-garbage-collection.md
- https://thenewstack.io/deletion-garbage-collection-kubernetes-objects/
- https://www.firehydrant.io/blog/dynamic-kubernetes-informers/