KueueのハンズオンをGPTに作ってもらった

## はじめに
Kueueを使っている環境にアサインされました。んで、Kueue周りのあれこれを整備しないといけなくなりました。
キャッチアップのためにググってみましたが、Kueueは2022年にリリースされたばかりで、2024年夏現在もあまりインターネット上のラーニングリソースが豊富ではありません。
Kubernetesを実務で使うのも初めてだった僕にはこのキャッチアップは大変ハードルが高いです。

というわけで、ChatGPTさんにチュートリアルを作ってもらって、それをやっていくことで履修しようという試みです。
この記事はGPTさんが作ってくれたチュートリアルコースをやってみながら修正した結果なものです。
とりあえず2024年9月現在動くことは確認してますが、リリースサイクルが早いので時間がたったら動かなくなるかも。
それまでに何かいい資料が出てきていることを祈っています。

## チュートリアルをやる前の事前知識
以下の記事が大変参考になりました。

https://speakerdeck.com/bells17/kueue-introduction

https://gihyo.jp/article/2022/10/kubernetes-cloudnative-topics-03

## チュートリアルをやる前の環境
- Minikubeが実行できる必要があります
- yamlファイルをいくつか作成するので、ファイルを作るのに都合がいいディレクトリではじめてください

## チュートリアルおしながき
1. 基本編
1. ResourceFlavor編

## 基本編

### 1. インストールと前準備

まずはminikube起動。 
```
minikube start
```

次に最新のリリースバージョンのkueueをインストールします。2024年9月現在 `v0.8.0` が最新のようなのでそれを使います。
下のリンクからバージョンを確認できるので、確認しておくといいかもしれません

https://kueue.sigs.k8s.io/docs/installation/#install-a-released-version

```
kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.8.0/manifests.yaml
```

apply実行後は、以下のコマンドでpodsの様子を確認しましょう。
```
kubectl get pods -n kueue-system
```

STATUSが `Running` になったら次に行きましょう。
こんなかんじになります

```
C:\Users\User>kubectl get pods -n kueue-system
NAME                                        READY   STATUS    RESTARTS   AGE
kueue-controller-manager-5cc87c9d87-snmrm   2/2     Running   0          4m9s
```

最後に、KueueのLocalQueueが使うnamespaceを作っておきます。

```
kubectl create namespace team-a
```

### 2. Queueの準備

#### 空のResourceFlavorの作成

ResourceFlavorを使わないときにも、空のResourceFlavorの定義が必要になるようです。下のページに従って、空のファイルを作ってapplyしておきます。

https://kueue.sigs.k8s.io/docs/concepts/resource_flavor/#empty-resourceflavor

```yaml:empty-resource-flavor.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: default-flavor
```

```
kubectl apply -f empty-resource-flavor.yaml
```

#### ClusterQueueの作成

ここから実際にいろいろやっていきます。
まずはClusterQueueを作りましょう。
以下のコードを `cluster-queue.yaml` として保存します。

yamlは↓のページから持ってきたものなので、それぞれの項目の意味とかについては本家を読んでください。

https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/

```yaml:cluster-queue.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "cluster-queue"
spec:
  namespaceSelector: {} # match all.
  resourceGroups:
  - coveredResources: ["cpu", "memory", "pods"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 9
      - name: "memory"
        nominalQuota: 36Gi
      - name: "pods"
        nominalQuota: 5
```

ファイルを保存したらapplyしてください。

```
kubectl apply -f cluster-queue.yaml
```

#### LocalQueueの作成

次にLocalQueueを用意します。出典と詳細は↓。

https://kueue.sigs.k8s.io/docs/concepts/local_queue/

```yaml:local-queue.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-a 
  name: team-a-queue
spec:
  clusterQueue: cluster-queue 
```

こちらもファイルを保存してからapplyしてください。

```
kubectl apply -f local-queue.yaml
```

ちなみに、LocalQueueは `namespace` に属し、その空間内のリソース・スケジュール管理を行うという都合上、名前空間の指定が必須です。yamlで指定した名前空間が存在しないとこういうエラーが出ます。

```
C:\Users\User>kubectl apply -f local-queue.yaml
Error from server (NotFound): error when creating "local-queue.yaml": namespaces "team-a" not found
```

ClusterQueueは名前の通りクラスターを対象にするため、名前空間の指定が無くても動きます。

### 3. ジョブをスケジューリングする

作成したキューを使ってジョブをスケジューリングしていきます。

まず、以下のジョブファイルを保存します。これはGPTさんが作ってくれた。

```yaml:example-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: example-job
  namespace: team-a
  labels:
    kueue.x-k8s.io/queue-name: team-a-queue
spec:
  parallelism: 1
  completions: 1
  template:
    spec:
      containers:
      - name: example-container
        image: busybox
        command: ["sh", "-c", "echo Hello, Kubernetes! && sleep 30"]
      restartPolicy: Never
```

このジョブでは、先ほど作ったLocalQueueを使うように指定してあります。`metadata.namespace`と`metadata.labels.queue-name`がLocalQueueと一致していることを確認してください。

保存したらapply。

```
kubectl apply -f example-job.yaml
```

apply後は以下のコマンドでジョブが実行されている様子を確認できます。

```
kubectl get workloads -n team-a
```

Kueueに渡されたJobは、Kueue内部でWorkloadに変換されます。WorkloadはKueueによって生成されるリソースなので、ユーザーが管理する必要はありません。

何回か実行して、FINISHEDが`True`に変わったことを確認します。

```
C:\Users\User\Documents\learning_k8s\learning-kueue>kubectl get workloads -n team-a
NAME                    QUEUE          RESERVED IN     ADMITTED   FINISHED   AGE
job-example-job-ca089   team-a-queue   cluster-queue   True       True       18m
```

では、ほんとに正常に実行できていたのか確認しましょう。logsからexample-jobのログを表示します。

```
kubectl logs job/example-job -n team-a
```

うまくいっていれば、echoした文字列が表示されているはずです。
```
C:\Users\User>kubectl logs job/example-job -n team-a
Hello, Kubernetes!
```

さらに、kueue-controller-managerのログも見ておきましょう。これはKueueのコントローラーマネージャー、つまり大元です。
これを見れば、Kueueがどのように一連のスケジューリングやジョブ実行をしたのかの流れを追うことができます。
うまく動作しているか確認したいときや、トラブルシュートをする必要があるときに見るところです。

```
kubectl logs -n kueue-system deployment/kueue-controller-manager
```

うむ、文字いっぱい。ジョブで指定したQueueの名前も含まれているので、うまくいってたんだろうなという感じがします。たぶん。

ここまで完了出来たら、以下の手順で一度minikube環境をリセットします。

```
minikube stop
minikube delete
```

## ResourceFlavor編

次のハンズオンではResourceFlavorを使っていきます。

https://kueue.sigs.k8s.io/docs/concepts/resource_flavor/

### 0. 準備
このハンズオンでは2つのノードを使うので、以下の方法でminikubeを2ノードの状態で立ち上げます。

```
minikube start --nodes 2
kubectl get nodes
```

次に、それぞれのノードにラベルを付けます。

```
kubectl label nodes minikube node-type=high-performance
kubectl label nodes minikube-m02 node-type=low-cost
```

もう一度インストールとnamespaceの作成も。
```
kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.8.0/manifests.yaml
kubectl create namespace team-a
```


### 1. ResourceFlavorの作成

まずは、2つのリソースフレーバーを定義した `resource-flavors.yaml` ファイルを保存します。
このファイルでは`hihg-performance`と`low-cost`の2つのリソースフレーバーを定義しています。

```yaml:resource-flavors.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: high-performance
spec:
  nodeLabels:
    node-type: high-performance
  nodeTaints:
  - effect: NoSchedule
    key: high-performance
    value: "true"
  tolerations:
  - key: "high-performance-taint"
    operator: "Exists"
    effect: "NoSchedule"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: low-cost
spec:
  nodeLabels:
    node-type: low-cost
  nodeTaints:
  - effect: NoSchedule
    key: low-cost
    value: "true"
  tolerations:
  - key: "low-cost-taint"
    operator: "Exists"
    effect: "NoSchedule"

```

作ったらapply。

```
kubectl apply -f resource-flavors.yaml
```

### 2. ClusterQueueの作成

今回は2つのResourceFlavorを使ったので、ClusterQueueも2つ作っていきます。
別に同じキューに両方のFlavorを入れたりすることもできるんですけど、ハンズオンなのでキュー類は分離してます。わかりやすさ。

```yaml:rf-cluster-queues.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: high-performance-queue
spec:
  namespaceSelector: {}
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "high-performance"
      resources:
      - name: "cpu"
        nominalQuota: 15
      - name: "memory"
        nominalQuota: 30Gi
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: low-cost-queue
spec:
  namespaceSelector: {}
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "low-cost"
      resources:
      - name: "cpu"
        nominalQuota: 10
      - name: "memory"
        nominalQuota: 20Gi

```

```
kubectl apply -f rf-cluster-queues.yaml
```

### 3. LocalQueueの作成

LocalQueueも2つずつ作ります。

```yaml:rf-local-queues.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  name: high-performance-localqueue
  namespace: team-a
spec:
  clusterQueue: high-performance-queue
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  name: low-cost-localqueue
  namespace: team-a
spec:
  clusterQueue: low-cost-queue
```

```
kubectl apply -f rf-local-queues.yaml
```

### 4. 各リソースの確認

ここまで作ったリソースを確認しておきます。念のため。
すべて`kubectl get`コマンドで確認できます。

ResourceFlavorの確認

```
kubectl get resourceflavors
```

ClusterQueueの確認

```
kubectl get clusterqueues -n team-a
```

LocalQueueの確認

```
kubectl get localqueues -n team-a
```

それぞれに、high-performanceとlow-costの2種類のリソースができていることを確認してください。

リソース名の指定と `-o` コマンドの指定をすることで、さらなる詳細を見ることもできます。

```
kubectl get clusterqueue high-performance-queue -o yaml
```

### 5. ジョブの作成

最後に、実際に実行するジョブを投入します。

```yaml:rf-jobs.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: high-performance-job
  namespace: team-a
  labels:
    kueue.x-k8s.io/queue-name: high-performance-localqueue
spec:
  template:
    spec:
      containers:
      - name: example-container
        image: busybox
        command: ["sh", "-c", "echo Running on high-performance resources! && sleep 30"]
        resources:
          requests:
            cpu: "4"
            memory: "8Gi"
      nodeSelector:
        node-type: high-performance
      restartPolicy: Never
  backoffLimit: 4
---
apiVersion: batch/v1
kind: Job
metadata:
  name: low-cost-job
  namespace: team-a
  labels:
    kueue.x-k8s.io/queue-name: low-cost-localqueue
spec:
  template:
    spec:
      containers:
      - name: example-container
        image: busybox
        command: ["sh", "-c", "echo Running on low-cost resources! && sleep 30"]
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
      nodeSelector:
        node-type: low-cost
      restartPolicy: Never
  backoffLimit: 4

```

```
kubectl apply -f rf-jobs.yaml
```

applyしたら`kubectl get workload -n team-a`で状況を確認します。ワークロードが受け入れられたことを確認できます。

```
C:\Users\User>kubectl -n team-a get workloads
NAME                             QUEUE                         RESERVED IN              ADMITTED   FINISHED   AGE
job-high-performance-job-ab2f1   high-performance-localqueue   high-performance-queue   True       True       18m
job-low-cost-job-9ad86           low-cost-localqueue           low-cost-queue           True       True       18m
```

describeでも状況確認ができます。

```
kubectl describe workload job-high-performance-job-ab2f1 -n team-a
```

## WIP
とりあえずなんとなく基本はわかったような気がしますがまだわからないことも多いです。稚拙な内容ご容赦。
勉強しながらこの記事を適宜更新していくつもりです。まだまだ学習中なので間違ってるところとかあったらコメントください。