tjtjtjのメモ

自分のためのメモです

cuelang kubernetes チュートリアル#2

cuelang kubernetes チュートリアル#2

前回はディレクトリに散らばった yaml を cue にした。今回は cue をスリムにしていく。クイックダーティーコンバージョン

https://github.com/cuelang/cue/tree/master/doc/tutorial/kubernetes#quick-n-dirty-conversion

cue eval -c ./... > snapshot

ディレクトリ下の cue ファイルを評価し snapshot を得る。改変しても差異がないことを検証するため取っておく。

Usage:
  cue eval [flags]

Flags:
  -c, --concrete                 require the evaluation to be concrete
                                 評価を具体的にする必要がある

snapshot

service: {
    bartender: {
        apiVersion: "v1"
        kind:       "Service"
         metadata: {
            name: "bartender"
            labels: {
                component: "frontend"
                app:       "bartender"
                domain:    "prod"
            }
:

cp frontend/breaddispatcher/kube.cue .

テンプレートを作成するため deployment と service を含むファイルをコピーする。kube.cue をシンプル化していくのだが変更前の状態を確認しておく。

frontend/breaddispatcher/kube.cue

package kube

service: breaddispatcher: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: "breaddispatcher"
        labels: {
            app:       "breaddispatcher"
            component: "frontend"
            domain:    "prod"
        }
    }
    spec: {
        ports: [{
            port:       7080
            targetPort: 7080
            protocol:   "TCP"
            name:       "client"
        }]
        selector: {
            app:       "breaddispatcher"
            component: "frontend"
            domain:    "prod"
        }
    }
}
deployment: breaddispatcher: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    metadata: name: "breaddispatcher"
    spec: {
        replicas: 1
        template: {
            metadata: {
                labels: {
                    app:       "breaddispatcher"
                    component: "frontend"
                    domain:    "prod"
                }
                annotations: {
                    "prometheus.io.scrape": "true"
                    "prometheus.io.port":   "7080"
                }
            }
            spec: containers: [{
                name:  "breaddispatcher"
                image: "gcr.io/myproj/breaddispatcher:v0.3.24"
                ports: [{
                    containerPort: 7080
                }]
                args: [
                    "-etcd=etcd:2379",
                    "-event-server=events:7788",
                ]
            }]
        }
    }
}

次のように書き換える。kind:Service と kind:deployment のテンプレートができた。

package kube

service: [ID=_]: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: ID
        labels: {
            app:       ID    // by convention
            domain:    "prod"  // always the same in the given files
            component: string  // varies per directory
        }
    }
    spec: {
        // Any port has the following properties.
        ports: [...{
            port:       int
            protocol:   *"TCP" | "UDP"      // from the Kubernetes definition
            name:       string | *"client"
        }]
        selector: metadata.labels // we want those to be the same
    }
}

deployment: [ID=_]: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    metadata: name: ID
    spec: {
        // 1 is the default, but we allow any number
        replicas: *1 | int
        template: {
            metadata: labels: {
                app:       ID
                domain:    "prod"
                component: string
            }
            // we always have one namesake container
            spec: containers: [{ name: ID }]
        }
    }
}

この辺りを見ながら復習

cue eval ./... -c > snapshot2

再びディレクトリ下の cue ファイルを評価し snapshot2 を生成。が alert が出る。

$ cue eval ./... -c > snapshot2
/// .../kubernetes/tmp/services/mon/alertmanager
service.alertmanager.metadata.labels.component: incomplete value (string):
    ./kube.cue:11:24
service.alertmanager.spec.selector.component: incomplete value (string):
    ./kube.cue:11:24
deployment.alertmanager.spec.template.metadata.labels.component: incomplete value (string):
    ./kube.cue:36:28
:

kubernetes/tmp/services/mon/alertmanager/kube.cue を確認する。component がない。

package kube

service: alertmanager: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        annotations: {
            "prometheus.io/scrape": "true"
            "prometheus.io/path":   "/metrics"
        }
        labels: name: "alertmanager"
        name: "alertmanager"
    }
    spec: {
        selector: app: "alertmanager"
        // type: ClusterIP
        ports: [{
            name:       "main"
            protocol:   "TCP"
            port:       9093
            targetPort: 9093
        }]
    }
}

次にチュートリアルでは sed でゴニョゴニョする。

  • kube.cue を置換
    • component: string -> component: #Component
  • kube.cue の末尾に次を追加
    • #Component: string
  • frontend,infra ... の各ディレクトリにコンポ―メント名を指定したがファイルを生成
    • #Component: "frontend"
  • cue ファイルをtrim

diff をとると spec.selector の domain, component が揃っていることが分かる。

snapshot2 の mon alertmanager service

service: {
    alertmanager: {
        apiVersion: "v1"
        kind:       "Service"
        metadata: {
            name: "alertmanager"
            labels: {
                name:      "alertmanager"
                app:       "alertmanager"
                domain:    "prod"
                component: "mon"
            }
            annotations: {
                "prometheus.io/scrape": "true"
                "prometheus.io/path":   "/metrics"
            }
        }
        spec: {
            ports: [{
                name:       "main"
                port:       9093
                protocol:   "TCP"
                targetPort: 9093
            }]
            selector: {
                name:      "alertmanager"
                app:       "alertmanager"
                domain:    "prod"
                component: "mon"
            }
        }
    }
}

部分 eval を試す。

$ cue eval ./mon/alertmanager -e service
alertmanager: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: "alertmanager"
        labels: {
            name:      "alertmanager"
            app:       "alertmanager"
            domain:    "prod"
            component: "mon"
        }
        annotations: {
            "prometheus.io/scrape": "true"
            "prometheus.io/path":   "/metrics"
        }
    }
    spec: {
        ports: [{
            name:       "main"
            port:       9093
            protocol:   "TCP"
            targetPort: 9093
        }]
        selector: {
            name:      "alertmanager"
            app:       "alertmanager"
            domain:    "prod"
            component: "mon"
        }
    }
}

service.alertmanager を yaml で得る。

$ cue eval ./mon/alertmanager -e service.alertmanager --out yaml
apiVersion: v1
kind: Service
metadata:
  name: alertmanager
  labels:
    name: alertmanager
    app: alertmanager
    domain: prod
    component: mon
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/path: /metrics
spec:
  ports:
    - name: main
      port: 9093
      protocol: TCP
      targetPort: 9093
  selector:
    name: alertmanager
    app: alertmanager
    domain: prod
    component: mon

cue trim ./...

trim 作用はうまく説明できない。cue はこんなふうに矛盾しない限り合わせていくのだが、

trim したときどっちに寄るんだろうとか。いい感じにしてくれるということで。

trim の help を見る

$ cue help trim
trim removes fields from structs that can be inferred from constraints

A field, struct, or list is removed if it is implied by a constraint, such
as from an optional field maching a required field, a list type value,
a comprehension or any other implied content. It will modify the files in place.


Limitations

Removal is on a best effort basis. Some caveats:
- Fields in implied content may refer to fields within the struct in which
  they are included, but are only resolved on a best-effort basis.
- Disjunctions that contain structs in implied content cannot be used to
  remove fields.
- There is currently no verification step: manual verification is required.

Examples:

        $ cat <<EOF > foo.cue
        light: [string]: {
                room:          string
                brightnessOff: *0.0 | >=0 & <=100.0
                brightnessOn:  *100.0 | >=0 & <=100.0
        }

        light: ceiling50: {
                room:          "MasterBedroom"
                brightnessOff: 0.0    // this line
                brightnessOn:  100.0  // and this line will be removed
        }
        EOF

        $ cue trim foo.cue
        $ cat foo.cue
        light: [string]: {
                room:          string
                brightnessOff: *0.0 | >=0 & <=100.0
                brightnessOn:  *100.0 | >=0 & <=100.0
        }

        light: ceiling50: {
                room: "MasterBedroom"
        }

It is guaranteed that the resulting files give the same output as before the
removal.

トップレベルテンプレートの編集

ここからはチュートリアル見るべき。。。

  • kind 毎の共通構造と spec 構造をトップレベルテンプレートに抽出
  • port 定義簡略化の工夫
  • サブレベルテンプレート作成
  • cue trim --simplify Folding of Single-Field Structs https://cuelang.org/docs/tutorials/tour/intro/fold/
  • サブレベル毎のカスタマイズ

そうこうしているうちに

そうこうしているうちに v0.3.0-alpha3 がでている。

https://github.com/cuelang/cue/releases/tag/v0.3.0-alpha3