🍱

Kubernetesエンジニア向け開発ツール欲張りセット2022

2022/06/12に公開

はじめに

本記事では、筆者や筆者の同僚がKubernetes関連の開発をしているときによく利用しているツールを紹介します。
主にKubernetes上で動くプログラムをGoで書いたり、マニフェストのYAMLを書いたりするエンジニアが対象となります。

本記事の内容は極力環境依存を減らし、Linux, WSL2, Macなどの環境で利用可能となっています。
WSL2(Ubuntu 20.04)、およびM1 Macで動作確認していますが、環境によって多少の違いが生じることもあるのでご了承ください。

基本となるツール

make & brew

本記事ではmakeコマンドを利用します。

Ubuntuの場合は以下のコマンドでインストールをおこなってください。

sudo apt update
sudo apt install build-essential

Macの場合は、以下のページを参考にHomebrewとCommand line tools for Xcodeをインストールしてください。

https://brew.sh

Docker Desktop

コンテナ実行環境としてDocker Desktopを利用します。
以下のページを参考にインストールをおこなってください。

https://docs.docker.com/get-docker/

なお、Docker Desktopは大きな組織での商用利用は有償となります。
Dockerの代替となる無償ツールもいくつか存在しますが、Dockerのほうがハマりどころも少なく無難に利用することができるのでおすすめです。

VSCode

筆者はIntelliJ IDEAを利用することも多いのですが、本記事ではVisual Studio Code(以下VSCode)を利用していきたいと思います。
以下のページからダウンロードしてインストールをおこなってください。

https://code.visualstudio.com

あわせて、以下のExtensionのインストールもおすすめします。

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack

このExtensionを利用すると、VM,コンテナ、WSL2,クラウド上のインスタンスなど、どこにファイルが置いてあっても、ローカルで開発しているようにファイルを操作することができます。

aqua

aquaはCLIツールのインストールを管理するためのツールです。
以下のページのQuick Startからインストールをおこなってください。

https://aquaproj.github.io

aquaを利用することで以下のようなメリットが得られます。

  • CIと開発環境でCLIのバージョンを一致させることができる
  • LinuxやMacなど異なる環境でも同じようにツールをインストールできる
  • チーム間で利用しているツールのバージョンを共有することができる

サンプルプロジェクト

本記事で紹介しているツールを利用して簡単なプログラムの開発を体験できるサンプルリポジトリを用意しました。

https://github.com/zoetrope/k8s-tools

まずはリポジトリを取得してください。

git clone https://github.com/zoetrope/k8s-tools.git

以下のコマンドを実行すると、必要なツールがインストールされます。

cd k8s-tools/
aqua i -l

Kubernetesのマニフェスト(YAML)関連ツール

Kubernetesでの開発をおこなう際には非常にたくさんのYAMLを書くことになります。
ここでは、YAMLを書く際に便利なVSCodeの拡張、YAMLを生成するためのツール、検証するためのツールなどを紹介していきます。

VSCode Extensions

VSCodeでYAMLを書く際には以下のExtensionが便利です。

https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml

VSCodeのsettings.jsonに以下のような設定を追加すると、Kubernetes標準リソースのマニフェストを検証したり、コード補完が利用可能になります。

{
    "yaml.schemas": {
        "kubernetes": [
            "*.yml",
            "*.yaml"
        ],
    }
}

Microsoft製のVisual Studio Code Kubernetes ToolsというExtensionもあります。
こちらはHelmファイルを扱うことができたり、Kubernetesクラスタを操作することができるなど機能が豊富です。
好みのものを利用しましょう。(筆者はRed Hatのものを利用しています)

また、巨大なYAMLを書くときにはインデントの深さが分かりにくくなりがちです。
そんなときには以下の拡張を入れてインデントに色づけするとよいでしょう。

https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow

YAML生成ツール

大量のマニフェストを効率的に書くためには、モジュール化や条件分岐、パラメータによる変更などの機能が必要となります。
そのような機能を備えたKustomize, Helm, Jsonnet, CUE, kpt, yttなど非常にたくさんのツールが存在します。

ツールの選択は好みもあると思いますが、筆者は以下のように使い分けています。

  • Kustomize: 開発環境と本番環境など、環境に応じてマニフェストの一部を書き換えたい場合などに利用する。
  • Helm: 自分が開発したカスタムコントローラーなどをOSSとして公開し、広く利用してもらいたい場合に利用する。
  • Jsonnet: KustomizeやHelmでは表現できないような複雑なロジックが必要な場合や、様々なパターンのマニフェストを生成したい場合などに利用する。

今回はJsonnetを紹介していきたいと思います。

Jsonnet

JsonnetはJSONを生成するためのテンプレート言語です。

https://jsonnet.org

JsonnetはJSON向けのツールではあるのですが、Ksonnet(Kubernetes向けJsonnet)の登場によりKubernetesユーザーにも利用されるようになりました。
Ksonntプロジェクトは残念ながら終了してしまったのですが、その成果はGrafana Tankaというフレームワークに引き継がれて現在でも広く利用されています。

今回は以下の3つのツールを利用します。

  • jsonnet: 本体
  • jsonnetfmt: フォーマッター
  • jsonnet-bundler (jb): パッケージマネージャー

また、VSCodeに以下のExtensionをインストールすると、Jsonnetのフォーマット、コードハイライト、コードジャンプが可能になります。

https://marketplace.visualstudio.com/items?itemName=Grafana.vscode-jsonnet

Jsonnetの利用例

Jsonnetでは、jb(jsonnet-bundler)というツールを利用することで、再利用可能なライブラリを取得することができます。
Jsonnet向けのライブラリは以下で公開されています。

ここではKubernetesの標準リソースを生成する際に便利なk8s-libsonnetを利用してみましょう。

適当なディレクトリで下記のコマンドを実行すると、jsonnetfile.jsonが作成され、./vendorディレクトリにライブラリがダウンロードされます。

jb init
jb install github.com/jsonnet-libs/k8s-libsonnet/1.23@main

次に以下のような内容でmain.jsonnetというファイルを作成します。
今回は簡単なDeploymentリソースを生成することにしましょう。

local k = import 'github.com/jsonnet-libs/k8s-libsonnet/1.23/main.libsonnet';
local deploy = k.apps.v1.deployment;
local container = k.core.v1.container;
local port = k.core.v1.containerPort;

deploy.new(name='sample', replicas=3, containers=[
  container.new('hello', 'hello:v1.0.0')
  + container.withPorts([port.newNamed(8000, 'http')])
  + container.resources.withRequests({ cpu: '100m', memory: '128Mi' })
  + container.resources.withLimits({ cpu: '100m', memory: '128Mi' })
  + container.securityContext.withReadOnlyRootFilesystem(true)
  + container.securityContext.withRunAsNonRoot(true),
])

以下のコマンドでjsonnetファイルからYAMMLを生成することができます。
-Jオプションで、jbで取得したライブラリのパスを指定します。
yqコマンドでは、JSON形式の出力をYAML形式に変換しています。

jsonnet -J ./vendor main.jsonnet | yq -P -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample
spec:
  replicas: 3
  selector:
    matchLabels:
      name: sample
  template:
    metadata:
      labels:
        name: sample
    spec:
      containers:
        - image: hello:v1.0.0
          name: hello
          ports:
            - containerPort: 8000
              name: http
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
          securityContext:
            readOnlyRootFilesystem: true
            runAsNonRoot: true

Jsonnetでは変数や条件分岐、関数など様々な機能を提供しています。
これらの機能を利用して、開発用と本番用のマニフェストを切り替えられるようにしてみましょう。

local k = import 'github.com/jsonnet-libs/k8s-libsonnet/1.23/main.libsonnet';
local deploy = k.apps.v1.deployment;
local container = k.core.v1.container;
local port = k.core.v1.containerPort;

function(dev=false)
  // 開発時はlatestタグのイメージを利用する
  local hello = container.new('hello', 'hello:' + if dev then 'latest' else 'v1.0.0');

  local opts = if dev == false then
    container.withPorts([port.newNamed(8000, 'http')])
    + container.resources.withRequests({ cpu: '100m', memory: '128Mi' })
    + container.resources.withLimits({ cpu: '100m', memory: '128Mi' })
    + container.securityContext.withReadOnlyRootFilesystem(true)
    + container.securityContext.withRunAsNonRoot(true)
  else
    // デバッグ用のポートを追加する
    container.withPorts([port.newNamed(8000, 'http'), port.newNamed(2345, 'debug')]);
  // 開発時はtiltやdlvがメモリをたくさん使うのでlimitsを設定しない
  // tiltはrootでの実行を要求するのでsecurityContextを設定しない

  // 開発時はレプリカ数を1にする。
  deploy.new(name='sample', replicas=if dev then 1 else 3, containers=[hello + opts])

--tla-code dev=trueのように引数を指定することで、生成する内容を切り替えることができます。

jsonnet -J ./vendor --tla-code dev=true main.jsonnet | yq -P -

yq

yqはjqと似たツールで、簡単なクエリを用いてYAMLファイルから必要な情報を抜き出したり、書き換えたりすることができます。

https://mikefarah.gitbook.io/yq/

jqとは以下のような違いがあります。

  • yqはYAMLだけでなく、JSON, XML, CSV, TSVなど複数の形式のファイルを扱える
  • yqはYAML特有の操作が可能(コメント、アンカー、タグなど)
  • yqは活発に開発がおこなわれている(jqは2018年以来リリースなし)
  • 提供されている機能はjqのほうが豊富

そのため、JSONとYAMLの相互変換をするために使ったり、sedの代わりにYAMLファイルの一部を書き換えるために使ったりすることが多いです。

これまでにも紹介しましたが、以下のようにjsonnetのJSON形式の出力をYAMLに変換することができます。

jsonnet -J ./vendor ./main.jsonnet | yq -P -

また-iオプションを指定すると、YAMLファイルを直接書き換えることもできます。
以下の例では、Deploymentリソースの中にあるすべてのコンテナのimagePullPolicyをAlwaysに書き換えています。

yq -i '(.. | select(has("image"))).imagePullPolicy = "Always"' deployment.yaml

yqの応用例

yqをkubectlコマンドと組み合わせることで様々な分析をおこなうことができます。

例えばKubernetesクラスタの中で、latestタグ、またはタグ指定がないimageを持ったPodをすべて取得するクエリは以下のように書くことができます。

kubectl get pod -A -o yaml | yq '.items[] 
	| select((.spec.containers *+ .spec.initContainers // [])[] | (.image == ("*:latest") or (.image | contains(":") | not))) 
	| { .metadata.namespace + "/" +.metadata.name: [(.spec.containers *+ .spec.initContainers // [])[] | .image ]}'
default/nginx-5dd6786c6b-qqdlc:
  - nginx:latest
default/sample-79695cf555-8p5h5
  - hello

ノードごとにデプロイされているPodの一覧を表示することもできます。

kubectl get pod -A -o yaml | yq '[.items[] 
	| {"namespace":.metadata.namespace,"name":.metadata.name,"nodeName":.spec.nodeName}] 
	| group_by(.nodeName) 
	| .[] 
	| {(.[0].nodeName): [.[] | .namespace + "/" + .name]}'
k8s-tools-control-plane:
  - kube-system/coredns-64897985d-pnwcc
  - kube-system/coredns-64897985d-qshdv
  - kube-system/etcd-k8s-tools-control-plane
  - kube-system/kindnet-nb6cg
  - kube-system/kube-apiserver-k8s-tools-control-plane
  - kube-system/kube-controller-manager-k8s-tools-control-plane
  - kube-system/kube-proxy-2vw6q
  - kube-system/kube-scheduler-k8s-tools-control-plane
  - local-path-storage/local-path-provisioner-66b445c94-4g9tc
k8s-tools-worker:
  - default/sample-79695cf555-8p5h5
  - kube-system/kindnet-69l2t
  - kube-system/kube-proxy-7j9js
k8s-tools-worker2:
  - default/nginx-5dd6786c6b-qqdlc
  - kube-system/kindnet-dtxdp
  - kube-system/kube-proxy-jnchj

yqでKubernetesのYAMLを操作する方法をより詳しく知りたい場合は、以下の記事も参考にしてみてください。

https://yokaze.github.io/2021/04/17/

マニフェストの検証ツール

Kubeconform

Kubeconformは、KubernetesのマニフェストのSchema検証ツールです。

https://github.com/yannh/kubeconform

指定した値の型が間違っていたり、必須フィールドを指定していなかったり、存在しないフィールドを指定した場合に、以下のよう指摘してくれます。

$ kubeconform -strict sample.yaml
sample.yaml - Deployment sample is invalid: For field spec: selector is required - For field spec.replicas: Invalid type. Expected: [integer,null], given: string - For field spec.template.spec.containers.0: Additional property unknown is not allowed

さらに、KubeconformはKubernetesの標準リソースだけでなく、カスタムリソースの検証もおこなうことが可能です。

カスタムリソースを検証するためには、CRDファイルからJSON Schemaファイルを生成する必要があります。
サンプルプロジェクトで変換プログラム用のDockerfileを用意したので、以下のようにコンテナイメージを作成してください。

cd k8s-tools
docker build -t openapi2jsonschema -f Dockerfile.openapi2jsonschema .

CRDファイルのパスかURLを指定して、JSON Scehmaを生成します。
今回はArgo CDのApplicationリソースのJSON Schemaを生成してみましょう。

docker run --rm -v $(pwd)/schema:/work openapi2jsonschema https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/crds/application-crd.yaml

./schemaディレクトリに、JSON Schemaのファイルが生成されていればOKです。

生成されたファイルを利用して、Applicationリソースの検証をおこなうことができます。

$ kubeconform -strict -schema-location './schema/{{ .ResourceKind }}_{{ .ResourceAPIVersion }}.json' ./sample/app.yaml
./sample/app.yaml - Application guestbook is invalid: For field spec: Additional property unknown is not allowed

KubeLinter

kubeconformはマニフェストのSchema検証をおこなうツールでしたが、KubeLinterではセキュリティの観点やプロダクションレディかどうかの観点でマニフェストをチェックするためのツールです。

https://github.com/stackrox/kube-linter

例えばlatestタグのイメージを利用したり、CPUやMemoryのrequests/limitsを指定していない場合に、以下のように指摘してくれます。

$ kube-linter lint sample.yaml
KubeLinter 0.3.0-0-gbf3a4dbd96

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) The container "hello" is using an invalid container image, "hello:latest". Please use images that are not blocked by the `BlockList` criteria : [".*:(latest)$" "^[^:]*$" "(.*/[^:]+)$"] (check: latest-tag, remediation: Use a container image with a specific tag other than latest.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" does not have a read-only root file system (check: no-read-only-root-fs, remediation: Set readOnlyRootFilesystem to true in the container securityContext.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" is not set to runAsNonRoot (check: run-as-non-root, remediation: Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" has cpu request 0 (check: unset-cpu-requirements, remediation: Set CPU requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" has cpu limit 0 (check: unset-cpu-requirements, remediation: Set CPU requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" has memory request 0 (check: unset-memory-requirements, remediation: Set memory requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.)

<standard input>: (object: <no namespace>/sample apps/v1, Kind=Deployment) container "hello" has memory limit 0 (check: unset-memory-requirements, remediation: Set memory requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.)

Error: found 7 lint errors

Go & Kubernetes関連ツール

Go

Goのインストール

下記のページからGoをダウンロードしてインストールしてください。

https://go.dev

Goは後方互換性があるので、基本的に最新版を入れておけば問題ないでしょう。
ただし、後述するgolangci-lintで利用されている一部のLinterに関してはGo 1.18対応されていないものがあるので注意しましょう。

Go for Visual Studio Code

VSCodeにGo用のExtensionをインストールします。下記のページのQuick Startの手順どおりに設定をおこなってください。

https://marketplace.visualstudio.com/items?itemName=golang.go

設定はデフォルトのままで問題ありません。

golangci-lint

golangci-lintはGo用のLinterです。
たくさんのLinterを組み合わせて利用することが可能になっています。

https://golangci-lint.run

.golangci.yamlという名前で以下のようなファイルを作成します。
ここでは、Go 1.18に対応していないstructcheckを無効化しています。
また必要に応じてenableなlinterを追加しましょう。

run:
  go: '1.18'
  timeout: 5m
linters:
  enable:
    - dupl
    - gocognit
    - goconst
    - gocyclo
    - godot
    - gofmt
    - revive
  disable:
    - structcheck

以下のコマンドでGoのコードの検証をおこなうことができます。

golangci-lint run

開発&デバッグ

kind

kindはローカル環境に、テストや開発用のKubernetesクラスタを構築するためのツールです。
KubernetesノードをDockerコンテナとして立ち上げるため、すばやくKubernetesクラスタを構築することが可能になっています。

https://kind.sigs.k8s.io

Tilt

Tiltは、Kubernetes上でのアプリケーション開発をサポートするためのツールです。
ソースコードやマニフェストの変更を監視して、コンテナイメージの再ビルド、Kubernetesクラスタへのマニフェストの適用、Podの再起動などを自動的におこなってくれます。

https://tilt.dev

サンプルプロジェクトでTiltが使えるようになっていますので、以下のコマンドを実行してkindとTiltを立ち上げてください。

cd k8s-tools
make start
tilt up

ブラウザでhttp://localhost:10350/を開き、セットアップが完了するのを待ちます。

この状態でGoのプログラムやJsonnetのファイルを書き換えてみましょう。
わずか数秒でKubernetesクラスタ上に変更が反映されていることが分かるかと思います。

開発が終わったら、以下のコマンドでTiltとkindを終了してください。

tilt down
make stop

Tiltの公式VSCode Extensionも用意されています。
ただし、このExtensionを利用する場合は、Tiltをaquaではなく通常の方法でインストールする必要があります。
https://marketplace.visualstudio.com/items?itemName=tilt-dev.Tiltfile

また、TiltでKubernetesのカスタムコントローラーの開発をしたい場合は、以下の記事を参考にしてください。
https://zenn.dev/zoetro/articles/fba4c77a7fa3fb

delve

delveはGo言語用のデバッガです。

https://github.com/go-delve/delve

Kubernetes上で動いているプログラムをデバッグするためには、Pod内でdelveを立ち上げてリモートで接続する必要があります。
サンプルプロジェクトでは、Tiltに--debugオプションを指定することでPod内にdelveを立ち上げることができるようになっています。

cd k8s-tools/
make start
tilt up -- --debug

対象のPodが立ち上がったら、VSCodeのメニューからRun→Start Debuggingを選択してください。
.vscode/launch.jsonの内容は以下の通りです。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Connect to server",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "remotePath": "${workspaceFolder}",
            "port": 2345,
            "host": "127.0.0.1"
        }
    ]
}

これでGoのソースコードにブレイクポイントを張り、プログラムのステップ実行などが可能になりました。

Kubernetes関連ツール

stern

sternは、一度に複数のPodやコンテナのログを見ることのできるツールです。
出力されるログはコンテナごとに色分けされているので見分けやすくなっています。
特定のnamespace内の全Podのログを見たい場合や、Deploymentで複数のPodが動いている場合などに便利です。

https://github.com/stern/stern

krew

krewはkubectl用のプラグインマネージャーです。

https://krew.sigs.k8s.io

ここではnsというプラグイン(デフォルトで操作するnamespaceを切り替えられる)をインストールしてみます。

krew install ns

インストールしたプラグインは以下のようにして利用することができます。

kubectl ns

他にも便利なプラグインがあるので、以下のページから探してみるとよいでしょう。

https://krew.sigs.k8s.io/plugins/

まとめ

いかがだったでしょうか?
ひとつでも皆様の役に立つツールがご紹介できたなら幸いです。

Discussion