Vimでk8sを操作するプラグインを作った
初めに
こんにちは、森に帰省中のゴリラです。
最近、完全ガイドでkubernetesの勉強をしていますが、覚えること、理解することがなかなか多くてたいへんです。
さて、kubernetesを使ったことがある人なら、一度くらいk9sを使ったことがあるかと思います。
これはとても便利なもので、podを見たりコンテナにアタッチしたりと、いろいろできます。
ただ、自分は普段Vimをメインに使っているので、できればVim上で同じことをやりたいと思って、k8s.vim
というプラグインを作りました。
k8s.vim
の概要
UIはこんな感じになっています。
基本的にk8s://*
という仮想バッファを開くとリソースを見れたり、操作できたりします。
現時点では次のことができます。
- Pod関連
- 一覧
- コンテナ一覧
- describeの出力
- yaml出力
- Node関連
- 一覧
- describeの出力
たとえば、podのコンテナでシェルを実行したい場合は、こんな感じになります。
実装
k8s.vim
はdenops.vim
で動くプラグインなので、Denoで動いています。
一応、公式製のclientライブラリをDenoで使えないか検証したんですが、
esm.shとskypack.dev経由で動かなかったので断念しました。
そして、PoC時点ではサードパーティ製のライブラリを使おうとしていました。
しかし、次のメリット・デメリットから、シンプルにkubectl
をラップする形で実装しました。
サードパーティのライブラリを使う場合
- デメリット
- 認証とコンテキスト周りの管理が面倒そう
- 公式のライブラリではないため、メンテナンス継続性と信頼性が低い
- メリット
- APIと直接おしゃべりするので、処理のオーバーヘッドが少ない
kubectlをラップする場合
- デメリット
- 外部プロセス起動のオーバーヘッドがある
- メリット
- 公式CLIなので安心・信頼がある
- kubernetesを使っている人で
kubectl
を使っていない人は居ない(はず)ので追加の導入コストがない - 認証やコンテキスト周りの管理をしなくて済む
-
kubectl
の機能を把握できる(学習観点)
実際にこんな感じでkubectl
を叩いて、その出力を取っています。
ありがたいことにkubectl
は-o json
で出力フォーマットを指定できるので、データの加工は簡単でした。
cli.ts
export async function getResourceAsObject<T>(
resource: string,
opts: ResourceOptions,
): Promise<T> {
opts.format = "json";
const output = await getResourceAsText(resource, opts);
return JSON.parse(output);
}
export async function getResourceAsText(
resource: string,
opts: ResourceOptions,
): Promise<string> {
const cmd = [
"kubectl",
"get",
resource,
];
if (opts.all) {
cmd.push("-A");
}
if (opts.namespace) {
cmd.push("-n", opts.namespace);
}
if (opts.format) {
cmd.push("-o", opts.format);
}
const output = await run(cmd);
return output;
}
export async function run(cmd: string[]): Promise<string> {
const opt: Deno.RunOptions = {
cmd: cmd,
stdin: "null",
stdout: "piped",
stderr: "piped",
};
const p = Deno.run(opt);
const result = dec.decode(await p.output());
const status = await p.status();
if (!status.success) {
const error = dec.decode(await p.stderrOutput());
throw new Error(
`failed to execute command: ${cmd.join(" ")}, error: ${error}`,
);
}
p.stderr!.close();
p.stdout!.close();
p.close();
return result;
}
実装を見てPodなどの型情報はどうするの?って思う方がいるかも知れません。
実は型情報は自動生成してそれを使っています。
型情報の生成について
kubernetesのAPIからJSON形式のAPI定義情報を抜き出すことができます。
$ kubectl get --raw /openapi/v2
この定義はOpenAPIなので、openapi-generator-cliを使ってコードを自動生成できます。
docker run --rm -v $PWD:/local -w /local openapitools/openapi-generator-cli generate \
--skip-operation-example \
--additional-properties=platform=deno \
--global-property models \
-g typescript \
-i denops/k8s/swagger.json \
-o /local/denops/k8s
ちなみに、データの型定義だけあればいいので--global-property models
を付けています。
これを付けないと、クライアントのコードまで生成されてしまいます。
またDenoで動くように--additional-properties=platform=deno
も使っています。
他のオプションはこちらを参照してください。
これで、データの構造が型レベルで分かるようになるので、こんな感じでimportして使っています。
import { IoK8sApiCoreV1Pod } from "./models/IoK8sApiCoreV1Pod.ts";
export function renderPodList(pods: IoK8sApiCoreV1Pod[]): string[] {
const body = pods.map((pod) => {
const podIPs = pod.status?.podIPs?.map((podip) => podip.ip ?? "");
return [
pod.metadata?.namespace ?? "<unknown>",
pod.metadata?.name ?? "<unknown>",
pod.status?.phase ?? "<unknown>",
podIPs?.join(" ") ?? "<unknown>",
pod.spec?.nodeName ?? "<unknown>",
pod.status?.startTime?.toLocaleString() ?? "<unknown>",
];
});
const header = ["NAMESPACE", "NAME", "STATUS", "IP", "NODE", "START TIME"];
const table = new Table();
table.header(header)
.body(body);
return table.toString().split("\n");
}
最後に
kubernetesをはじめて数日のド素人レベルですが、とりあえず作ってみました。
これから学習しつつ、便利な機能を追加していく予定です。
興味ある方は、ぜひ使ってみてください。
機能の要望など受け付けています。
Discussion