Pulumi を使って Kubernetes へ CRD を登録

PulumiJavaScriptPython・Go のようなプログラミング言語で Infrastructure as Code するためのツールです。

今回は、この Pulumi を使って Kubernetes(k3s を使用)へカスタムリソースを登録してみます。

ソースは http://github.com/fits/try_samples/tree/master/blog/20190825/

はじめに

今回は、k3s(Lightweight Kubernetes) がインストール済みの Ubuntu 環境を使います。※

 ※ ただし、k3s の前に microk8s をインストールしたりしているので
    クリーンな環境とは言えないかもしれない
    (Istio と Knative のために Helm 等をインストールしていたりもする)

Pulumi を以下のようにインストールします。

Pulumi インストール例
$ curl -fsSL https://get.pulumi.com | sh

$HOME/.pulumi ディレクトリへ各種ファイルが展開され、.bashrc ファイルへ PATH の設定が追加されました。

なお、このままだと pulumi コマンド実行時に kubeconfig を参照できないようだったので、とりあえず /etc/rancher/k3s/k3s.yaml$HOME/.kube/config ファイルとしてコピーし chown しています。

動作確認
$ pulumi version

v0.17.28

Kubernetes へ CRD を登録(Node.js 使用)

準備

プロジェクトを作成する前に、まずは Pulumi でログインを実施しておきます。

今回はローカル環境の Kubernetes を使うので --local を指定して login を実施しました。

ログイン
$ pulumi login --local

プロジェクト作成

適当なディレクトリを用意し、テンプレートを指定してプロジェクトのひな型を作成します。

今回は Kubernetes を対象とした Node.js 用のプロジェクトを作成するため kubernetes-javascript を指定しました。

プロジェクト作成
$ mkdir sample
$ cd sample
$ pulumi new kubernetes-javascript

・・・
project name: (sample)
project description: (A minimal Kubernetes JavaScript Pulumi program)
Created project 'sample'

stack name: (dev)
Enter your passphrase to protect config/secrets:
Re-enter your passphrase to confirm:
Created stack 'dev'

Enter your passphrase to unlock config/secrets
    (set PULUMI_CONFIG_PASSPHRASE to remember):
Installing dependencies...
・・・

実装

上記で生成された index.js ファイルに Kubernetes へ登録する内容を実装していきます。

今回は下記のようなカスタムリソースの登録を実装します。

例. カスタムリソース登録内容
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: items.example.com
spec:
  group: example.com
  version: v1alpha1
  scope: Namespaced
  names:
    kind: Item
    plural: items
    singular: item
  preserveUnknownFields: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        spec:
          type: object
          properties:
            value:
              type: integer
            note:
              type: string

---

apiVersion: "example.com/v1alpha1"
kind: Item
metadata:
  name: item1
spec:
  value: 100
  note: sample item 1

---

apiVersion: "example.com/v1alpha1"
kind: Item
metadata:
  name: item2
spec:
  value: 20
  note: sample item 2

上記のカスタムリソースを実装したコードが以下です。

Pulumi でカスタムリソースを定義する場合、@pulumi/kubernetesCustomResourceDefinitionCustomResource を使えば良さそうです。

YAML と同等の内容を JavaScript の Object で表現して、CustomResourceDefinition 等のコンストラクタの第 2引数として渡すだけです。

index.js (カスタムリソース登録内容の実装)
'use strict'
const k8s = require('@pulumi/kubernetes')

const capitalize = s => `${s[0].toUpperCase()}${s.slice(1)}`

const crdName = 'item'
const crdGroup = 'example.com'
const crdVersion = 'v1alpha1'

const props = {
    value: 'integer',
    note: 'string'
}

const items = [
    { name: 'item1', value: 100, note: 'sample item 1' },
    { name: 'item2', value:  20, note: 'sample item 2' }
]

const crdKind = capitalize(crdName)
const crdPlural = `${crdName}s`

new k8s.apiextensions.v1beta1.CustomResourceDefinition(crdName, {
    metadata: { name: `${crdPlural}.${crdGroup}` },
    spec: {
        group: crdGroup,
        version: crdVersion,
        scope: 'Namespaced',
        names: {
            kind: crdKind,
            plural: crdPlural,
            singular: crdName
        },
        preserveUnknownFields: false,
        validation: {
            openAPIV3Schema: {
                type: 'object',
                properties: {
                    spec: {
                        type: 'object',
                        properties: Object.fromEntries(
                            Object.entries(props).map(([k, v]) => 
                                [k, { type: v }]
                            )
                        )
                    }
                }
            }
        }
    }
})

items.forEach(it => 
    new k8s.apiextensions.CustomResource(it.name, {
        apiVersion: `${crdGroup}/${crdVersion}`,
        kind: crdKind,
        metadata: {
            name: it.name
        },
        spec: Object.fromEntries(
            Object.keys(props).map(k => [k, it[k]])
        )
    })
)

デプロイ

pulumi up でデプロイします。

デプロイ
$ pulumi up

・・・
Previewing update (dev):

     Type                                                         Name        P
 +   pulumi:pulumi:Stack                                          sample-dev  c
 +   tq kubernetes:example.com:Item                               item2       c
 +   tq kubernetes:example.com:Item                               item1       c
 +   mq kubernetes:apiextensions.k8s.io:CustomResourceDefinition  item        c

Resources:
    + 4 to create

Do you want to perform this update?
  yes
> no
  details

Do you want to perform this update?yes を選択すると実際にデプロイが実施されます。

Do you want to perform this update? yes
Updating (dev):

     Type                                                         Name        S
 +   pulumi:pulumi:Stack                                          sample-dev  c
 +   tq kubernetes:apiextensions.k8s.io:CustomResourceDefinition  item        c
 +   tq kubernetes:example.com:Item                               item1       c
 +   mq kubernetes:example.com:Item                               item2       c

Resources:
    + 4 created
・・・

ちなみに、details を選ぶと登録内容(YAML)を確認できます。

正常に登録されたか、kubectl コマンドで確認してみます。(k3s の一般的な環境では k3s kubectl とする必要があるかもしれません)

CRD の確認
$ kubectl get crd | grep items

items.example.com                                    2019-08-14T08:57:22Z
カスタムリソースの確認
$ kubectl get item

NAME    AGE
item1   13m
item2   13m
カスタムリソース詳細1
$ kubectl describe item item1

Name:         item1
Namespace:    default
Labels:       app.kubernetes.io/managed-by=pulumi
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"example.com/v1alpha1","kind":"Item","metadata":{"labels":{"app.kubernetes.io/managed-by":"pulumi"},"name":"item1"},"spec":{...
API Version:  example.com/v1alpha1
Kind:         Item
Metadata:
  Creation Timestamp:  2019-08-14T08:57:22Z
  Generation:          1
  Resource Version:    19763
  Self Link:           /apis/example.com/v1alpha1/namespaces/default/items/item1
  UID:                 87503274-be71-11e9-aeea-025c19d6acb9
Spec:
  Note:   sample item 1
  Value:  100
Events:   <none>
カスタムリソース詳細2
$ kubectl describe item item2

Name:         item2
Namespace:    default
Labels:       app.kubernetes.io/managed-by=pulumi
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"example.com/v1alpha1","kind":"Item","metadata":{"labels":{"app.kubernetes.io/managed-by":"pulumi"},"name":"item2"},"spec":{...
API Version:  example.com/v1alpha1
Kind:         Item
Metadata:
  Creation Timestamp:  2019-08-14T08:57:22Z
  Generation:          1
  Resource Version:    19764
  Self Link:           /apis/example.com/v1alpha1/namespaces/default/items/item2
  UID:                 875af773-be71-11e9-aeea-025c19d6acb9
Spec:
  Note:   sample item 2
  Value:  20
Events:   <none>

問題なく登録できているようです。

アンデプロイ

デプロイ内容を削除(アンデプロイ)する場合は destroy を実行します。

アンデプロイ
$ pulumi destroy

・・・
Do you want to perform this destroy? yes
Destroying (dev):

     Type                                                         Name        S
 -   pulumi:pulumi:Stack                                          sample-dev  d
 -   tq kubernetes:apiextensions.k8s.io:CustomResourceDefinition  item        d
 -   tq kubernetes:example.com:Item                               item2       d
 -   mq kubernetes:example.com:Item                               item1       d

Resources:
    - 4 deleted
・・・