Kubernetes Clientを使ってOpenShift (Kubernetes)に接続する

# はじめに
今回私が紹介するのはKubernetes公式が提供するJavascript版の[Kubernetes Client](https://github.com/kubernetes-client/javascript)になります。
上記はJavascriptまたはTypescriptで使用できるものですが、他にもPython、Go等に対応しているものもあります。

ある用途で、今回はOpenShiftに対して各リソースのステータスを取得したり、作成/変更/削除ができるライブラリを探していました。その際にこのKubernetes Clientを使ってみたところOpenShiftでも十分使えることがわかりましたので紹介させていただきます。
公式ドキュメント等も整理されておりますが、英語であることもあり、本記事で説明させていただきます！

本記事を一通り読むことでKubernetes Clientの導入と利用方法について理解できるようになります。

# ライブラリの使い方
1. kubernetes-clientモジュールのインストール
    以下コマンドを実行してください。
    ```    
    npm install @kubernetes/client-node
    ```

2. 接続先のOpenShiftクラスターの設定
    接続したいクラスターを設定する必要があります。
    READMEにある通り、Javascriptに直して設定する方法もありますが、kubeconfigファイルをそのままコピーして使うのが楽だと思いますので、今回はその方法を紹介します。
    利用している環境によってkubeconfigファイル自体の配置場所は違いますので、それぞれの方法に従ってダウンロードし、適切なディレクトリにコピーしてください。
    
    また、この方法を使えば、ファイル名によって接続先を変えて利用するような複数環境対応も簡単できます。
    詳細は次の章にあるサンプルコードを参考にしてください。

3. サンプルコードでクラスターに問題なく接続できることを確認する
    次の章のサンプルコードを使って問題なく接続できるか確認してください。
    また、公式もexamplesディレクトリ配下にサンプルを用意してくれているので、参考にしてください。

4. 自分が確認したいことの実装方法を確認する
    src/genディレクトリ配下のswagger.jsonを確認すると用意されているAPI一覧を確認できます。
    目的のAPIを見つけたら、実装する際に使うtags(例:core_v1)とoperationId（例：deleteNamespacedPod）を抜き出してください。
    あとはサンプルコードを参考にしながら、makeApiClient(k8s.CoreV1)とk8s.deleteNamespacedPod("＜project名＞")のように書き換えるだけです。
※OpenShiftにおけるprojectはKubernetesのnamespaceにあたります。

# Kubernetes Clientのサンプルコード
今回のサンプルコードはNext.js(Typescript)のApp Routerの中で、このライブラリを使ってAPIリクエストをするような記述します。
Next.jsのApp Routerで実装する場合、http://localhost/3000/api/<ディレクトリ名> でAPIの返却値を確認できます。

## Kubernetesリソースを取得する

`@kubernetes/client-node`を使用することで、k8sのネイティブなリソース(DeploymentやNamespaceなど)の情報を取得することができます。

以下は、k8sのネイティブなリソースであるPodの情報を取得するサンプルコードです。

```ts
import { NextResponse } from "next/server";
import path from "path";
import * as k8s from "@kubernetes/client-node";

// 1. kubeconfig.yamlファイルの読み込みを行います。
// <project_root>/serverにkubeconfig.yamlファイルが配置されていることを想定しています。
const filePath = path.join(process.cwd(), "server", "kubeconfig.yaml");

export function GET() {
  // 2. kubeConfigオブジェクトを作成します。
  const kc = new k8s.KubeConfig();
  kc.loadFromFile(filePath);

  // 3. configを元にAPIリクエストを行うクライアントを作成します
  const k8sApi = kc.makeApiClient(k8s.CoreV1Api);

  // 4. 対象とするnamespaceを定義します
  const namespace = "default";

  try {
    // 5. 指定したnamespaceに存在するPodリソースを取得します
    const response = await k8sApi.listNamespacedPod(namespace);
    // 6. レスポンスを返します
    return NextResponse.json({ body: response.body });
  } catch (err) {
    // 7. エラー処理を行います。
    // 必要に応じてエラー処理を追加してください。
    console.error(err);
    return NextResponse.json({ err });
  }
}
```

## OpenShift固有のリソースの情報を取得する

`@kubernetes/client-node`を使用することで、k8sのネイティブなリソース(DeploymentやNamespaceなど)のみでなく、カスタムリソースを取得することができます。

以下は、OpenShift固有のリソースである`Route`の情報を取得するサンプルコードです。

```typescript
import { NextResponse } from "next/server";
import path from "path";
import * as k8s from "@kubernetes/client-node";

// 1. kubeconfig.yamlファイルの読み込みを行います
// <project_root>/serverにkubeconfig.yamlファイルが配置されていることを想定しています
const filePath = path.join(process.cwd(), "server", "kubeconfig.yaml");

export async function GET() {
  // 2. kubeConfigオブジェクトを作成します
  const kc = new k8s.KubeConfig();
  kc.loadFromFile(filePath);

  // 3. configを元にAPIリクエストを行うクライアントを作成します
  const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi);

  // 4. 取得したいリソース名などを定義します
  // リソース名を確認する方法は「補足」を参照ください
  const resource = "route.openshift.io";
  const version = "v1";
  const resourcePlural = "routes"; // リソース名の複数形を指定します。「route」であれば「routes」になります。
  const namespace = "openshift-console";

  try {
    // 5. 指定したnamespaceに存在するRouteリソースを取得します
    const response = await k8sApi.listNamespacedCustomObject(
      resource,
      version,
      namespace,
      resourcePlural,
    );
    // 6. レスポンスを返します
    return NextResponse.json({ body: response.body });
  } catch (err) {
    // 7. エラー処理を行います
    // 必要に応じてエラー処理を追加してください
    console.error(err);
    return NextResponse.json({ err });
  }
}
```

### 補足

上記サンプルコード内で定義しているカスタムリソース名は以下のいずれか方法で確認できます。
カスタムリソース定義(Custom Resouce Definition)は以下CRDで表記します。

1. クラスターに存在するCRDをリストアップする

    OpenShiftクラスターにログインしている状態で、以下のコマンドを実行するとクラスター上に存在するカスタムリソースの一覧を確認することができます。

    ただし、OpenShift固有の一部リソースは、CRDではなくAPIリソースとして実装されている場合があります。

    その場合は、`2. クラスターに存在するAPIリソースをリストアップする`のコマンドでリソースが存在するかを確認してみてください。

    ```sh
    $ kubectl get crds
    or
    $ oc get crds
    ```

2. クラスターに存在するAPIリソースをリストアップする

    OpenShiftクラスターにログインしている状態で、以下のコマンドを実行するとクラスター上に存在するAPIリソースの一覧を確認することができます。

    ```sh
    $ kubectl api-resources
    or
    $ oc api-resources
    ```

    スペースを見やすく少し調整しましたが、routeを探す場合の実行例を以下に載せておきます。

    実行例

    ```
    $ oc api-resources | grep route

    NAME            SHORTNAMES     APIVERSION                         NAMESPACED   KIND
    egressrouters                  network.operator.openshift.io/v1   true         EgressRouter
    routes                         route.openshift.io/v1              true         Route
    ```

## マニフェストファイルを使ってリソースを作成/更新する
上記のように用意されているAPIを使ってリソースの作成等もできますが、
自分で用意したマニフェストファイルを使って、作成/更新/削除することもできます。
今回はRouteをapplyする例を記述します。

```ts
const routePath = path.join(process.cwd(), "server", 'route.yaml');
const client = k8s.KubernetesObjectApi.makeApiClient(getKubeConfig());

// 作成したいリソース名を指定します
// 実際は本関数の呼び出し元で指定するイメージ
const appDomain = "app-b";

const specRouteString = await fs.readFile(routePath, "utf8");
  const specsRoute: k8s.KubernetesObject[] = yaml.loadAll(specRouteString);
  const createRoute = async function(specs: k8s.KubernetesObject[]) {
    for (const spec of specs) {
      // without metadata out
      spec.metadata = spec.metadata || {};
      spec.metadata.annotations = spec.metadata.annotations || {};
      delete spec.metadata.annotations[
        "kubectl.kubernetes.io/last-applied-configuration"
      ];
      spec.metadata.annotations[
        "kubectl.kubernetes.io/last-applied-configuration"
      ] = JSON.stringify(spec);
      spec.metadata.name = appDomain;
      spec.spec.to.name = appDomain;
  
      try {
        await client.read(spec);
        const response = await client.patch(spec);
        created.push(response.body);
      } catch (e) {
        const response = await client.create(spec);
        created.push(response.body);
      }
    }
  }
  await createRoute(specsRoute);
```
route.yamlの例
```yam
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: app-a
  namespace: turbo
  annotations:
    haproxy.router.openshift.io/timeout: 3m
spec:
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect
  to:
    kind: Service
    name: app-a
    weight: 100
```

# おまけ: Podステータス周りで押さえておくといいポイント
podがダウンする際にステータスが変更になっていることを確認したいケースがあったのですが、ステータスの返却値でrunningが返ってきてしまいました。
OpenShiftマネジメントコンソールやoc get podsで取得するとterminating状態であることがわかるので、他の返却値からterminatingであることを取得できるか調べてみました。

runningと表示されてしまう原因は、[Pod LifeCycle](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/)にある通り、Podのステータスにはterminatingは含まれないからです。
代替手段として、metadata.deletionTimestampが表示されることが分かりましたので、こちらで判別してAPIを返却するといいと思います。
[参考記事](https://qiita.com/superbrothers/items/3ac78daba3560ea406b2)

# 最後に
今回紹介したものはほんの一部になりますので、ご興味ある方は是非ご自身で試してみてください。

余談ではありますが、[OpenShift用のClientライブラリ](https://github.com/nodeshift/openshift-rest-client)を作成している方がいるようなので、気になる方はこちらも確認してみてください。

---
本記事は [いしい](https://qiita.com/al14006) と共同で作成しています。