bells17
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Programming Kubernetesの輪読会 二回目 <br/>第四章 Using Custom Resources --- ## 全体の章立て 0. Preface 序章 1. Introduction Kubernetesがなぜ今のアーキテクチャを採用しているのか 2. Kubernetes API Basics API Objectの説明 3. Basics of client-go Informer, API Machinery, Schemeなどの説明 4. Using Custom Resources →今ここ CRDと機能(Validation, SubResource)の説明 5. Automating Code Generation code-generatorの説明 6. Solutions for Writing Operators Controller実装方法の説明 7. Shipping Controllers and Operators Controller/Operatorのデリバリ 8. Custom API Servers Custom API Serverの説明と実装・デプロイ 9. Advanced Custom Resources convert, admission webhook, Structural Schemasなど --- # 4. Using Custom Resources ## この章で話すこと - Custome resource について - CRD について - クライアントについて ## この章の概要 - Custom resources について - Custom resources が利用できる kubernetes バージョンについて - Custom resources の保存場所とアクセス方法について - CRD について - CRD は Kubernetes リソースそのものである - CRD の構成について - kubectl がリソースを発見するために利用している discovery information API - `/apis` の discovery information API を利用して既存の API group にアクセスする方法について - discovery information のキャッシュについて - Type Definitions - CRD のスキーマ定義について - CRD 作成を行うときの処理フローについて - Validating Custom Resources - Custome resource の作成・更新にできる検証について - `spec.Validation` の `OpenAPI v3 schema` による検証と `admission webhook` による複雑な検証について - Kubernetes ~1.14 と 1.15~ における OpenAPI spec の公開への変更について - OpenAPI spec を含めた CRD リソースの自動生成方法について - Short Names and Categories - CRD のリソースに短い名前を付ける方法とエイリアスの確認方法 - Printer Columns - kubectl get `<crd name>` で表示される列情報の追加方法について - Subresources - サブリソースという特別な http エンドポイントについて - CRD がサポートするサブリソースについて - STATUS SUBRESOURCE - status をサブリソース化している理由について - 有効化の方法について - 後から安全に status サブリソースを有効化する方法について - `spec` と `status` のリソースバージョンの管理方法について - SCALE SUBRESOURCE - `/scale` リソースについて - `replica` の値が - 実例による scale サブリソースの説明 - A Developer’s View on Custom Resources - Custome resource にアクセスするための Golang クライアントについて - Dynamic Client - 動的なクライアントについて - client-go による動的クライアントの説明 - Typed Clients - 型付きのクライアントについて - ANATOMY OF A TYPE - 型定義の方法について - `TypeMeta` と `ObjectMeta` について - GOLANG PACKAGE STRUCTURE - Go言語の構造体の置き場や、コードジェネレータについて - 型定義のデータ構造について - TYPED CLIENT CREATED VIA CLIENT-GEN - client-gen によるクライアントコード生成について - 生成されるコードについて - controller-runtime Client of Operator SDK and Kubebuilder - controller-runtime について - controller-runtime の runtimeclient について ## Introduce ### Custom resources について - Custom resources v1.7以上の Kubernetes で利用可能 - Custom resources は Kubernetes の他の API resources と同様の etcd によって保存される - また Custom resources の API は他の API resources と同様の Kubernetes API server によって提供される - apiextensions-apiserver は CRD(Custom resource definition) によって定義されたリソースを提供する - API server へのリクエストは以下のどちらでもない場合に apiextensions-apiserver にフォールバックされる - aggregated API servers によってハンドリングされるもの - Native Kubernetes resources ![](https://i.imgur.com/K2lnAZR.png) NOTE: api-aggregator に関する 情報 https://github.com/kubernetes/community/blob/a3e65c4bafeddf697bd09621d71b0f8af99f5e44/contributors/design-proposals/api-machinery/aggregated-api-servers.md https://github.com/kubernetes/enhancements/issues/263 https://github.com/kubernetes/website/pull/4173 https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/ https://kubernetes.io/docs/tasks/access-kubernetes-api/configure-aggregation-layer/ https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server/ ### CRD について - CRD は Kubernetes リソースそのもの - Chapter 1 の “A Motivational Example” に登場した ```yaml apiVersion: cnat.programming-kubernetes.info/v1alpha1 kind: At metadata: name: example-at spec: schedule: "2019-07-03T02:00:00Z" status: phase: "pending" ``` のような yaml の場合 CRD は以下のようになる ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} version: v1alpha1 versions: - name: v1alpha1 served: true storage: true ``` - CRD の構成は以下のようになっている - CRD の名前は plural name の後にグループ名が続くようになる必要がある - 上記の例の場合 - `metadata.name` が `ats.cnat.programming-kubernetes.info` - `spec.group` が `cnat.programming-kubernetes.info` - `spec.names.plural` が `ats` となっており - `<spec.names.plural>.<spec.group>` という構成となっている - また `spec.names.kind` で `At` という `kind` の Custome resources を定義している - 更に `spec.scope` が `Namespaced` となっているのでこの Custome resources は名前空間レベルで利用されるように定義されている(`spec.scope: Namespaced` に定義しなければこの Custome resources はクラスタレベルとして定義される) - 上記の CRD がクラスタで作成され(更にこの CR が作成され)ると kubectl は自動的にこの Custome resources を検出してユーザーは以下のようにリソースにアクセスすることができる ```shell= $ kubectl get ats NAME CREATED AT ats.cnat.programming-kubernetes.info 2019-04-01T14:03:33Z ``` ## Discovery Information - `kubectl` は API server からの Discovery Information API を利用して新しいリソースを見つけ出す - この情報発見のメカニズムについてもう少し詳しく見ていく - `kubectl` の verbose level を上げるとそれがどのように新しいリソースタイプを見つけているかを見ることができる ```shell $ kubectl get ats -v=7 ... GET https://XXX.eks.amazonaws.com/apis/cnat.programming-kubernetes.info/ v1alpha1/namespaces/cnat/ats?limit=500 ... Request Headers: ... Accept: application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json User-Agent: kubectl/v1.14.0 (darwin/amd64) kubernetes/641856d ... Response Status: 200 OK in 607 milliseconds NAME AGE example-at 43s ``` - 詳細な検出手順は以下のようになっている - 最初、`kubectl` は `ats` について知らない - そのため `kubectl` は `/apis` の API発見エンドポイントを通して API server に既存のすべてのAPIグループについてに問い合わせる - 次に `kubectl` は `/apis/group version` のグループ発見エンドポイントを通して API server に既存のすべてのAPIグループのリソースについて問い合わせる - そして `kubectl` は `ats` を次の3つに変換する - Group (`cnat.programming-kubernetes.info`) - Version (`v1alpha1`) - Resource (`ats`) - 発見エンドポイントは最後のステップで変換を行うために必要なすべての情報を提供する - これらはすべてディスカバリー `RESTMapper` によって全て実装されている(`RESTMapper` については3章にあるようなので詳しく調べてない) ```yaml $ http localhost:8080/apis/ { "groups": [{ "name": "at.cnat.programming-kubernetes.info", "preferredVersion": { "groupVersion": "cnat.programming-kubernetes.info/v1", "version": "v1alpha1“ }, "versions": [{ "groupVersion": "cnat.programming-kubernetes.info/v1alpha1", "version": "v1alpha1" }] }, ...] } $ http localhost:8080/apis/cnat.programming-kubernetes.info/v1alpha1 { "apiVersion": "v1", "groupVersion": "cnat.programming-kubernetes.info/v1alpha1", "kind": "APIResourceList", "resources": [{ "kind": "At", "name": "ats", "namespaced": true, "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ] }, ...] } ``` NOTE: 実際に確認してみた ```shell= $ kubectl proxy 8080 # 別ターミナルで実行 $ curl http://127.0.0.1:8001/apis { "kind": "APIGroupList", "apiVersion": "v1", "groups": [ ... { "name": "extensions", "versions": [ { "groupVersion": "extensions/v1beta1", "version": "v1beta1" } ], "preferredVersion": { "groupVersion": "extensions/v1beta1", "version": "v1beta1" } }, ... ] } $ curl http://127.0.0.1:8001/apis/extensions/v1beta1 { "kind": "APIResourceList", "groupVersion": "extensions/v1beta1", "resources": [ { "name": "daemonsets", "singularName": "", "namespaced": true, "kind": "DaemonSet", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "ds" ] }, ... ] } $ curl http://127.0.0.1:8001/apis/extensions/v1beta1/daemonsets { "kind": "DaemonSetList", "apiVersion": "extensions/v1beta1", "metadata": { "selfLink": "/apis/extensions/v1beta1/daemonsets", "resourceVersion": "133384" }, "items": [ { "metadata": { "name": "kube-proxy", "namespace": "kube-system", "selfLink": "/apis/extensions/v1beta1/namespaces/kube-system/daemonsets/kube-proxy", "uid": "625a5412-c0b7-11e9-888d-025000000001", "resourceVersion": "112343", "generation": 1, "creationTimestamp": "2019-08-17T06:22:27Z", "labels": { "k8s-app": "kube-proxy" } }, "spec": { "selector": { "matchLabels": { "k8s-app": "kube-proxy" } }, ... }, ... ] } ``` NOTE: https://github.com/kubernetes/community/blob/a3e65c4bafeddf697bd09621d71b0f8af99f5e44/contributors/design-proposals/api-machinery/aggregated-api-servers.md#constraints をみると Discovery Information は > Each API server should support the kubernetes discovery API とあるので Discovery Information は kubernetes discovery API かなと思うので API だと理解して良さそう ### WARNING - `kubectl` CLI は `~/.kube` ディレクトリの中でリソースタイプのキャッシュを保持している - そのため全てのアクセス毎に発見情報を取得する必要がない - このキャッシュは10分毎に無効化される - そのためCRDが変更された場合でも遅くとも10分後にはそれぞれのユーザの CLI に表示される NOTE: キャッシュデータについて実際に見てみた ```shell= $ ls -1 ~/.kube/cache/discovery/127.0.0.1_62641/ admissionregistration.k8s.io apiextensions.k8s.io apiregistration.k8s.io apps authentication.k8s.io authorization.k8s.io autoscaling batch certificates.k8s.io cnat.programming-kubernetes.info coordination.k8s.io events.k8s.io extensions networking.k8s.io node.k8s.io policy rbac.authorization.k8s.io scheduling.k8s.io servergroups.json storage.k8s.io v1 $ cat ~/.kube/cache/discovery/127.0.0.1_62641/cnat.programming-kubernetes.info/v1alpha1/serverresources.json | jq . { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "cnat.programming-kubernetes.info/v1alpha1", "resources": [ { "name": "ats", "singularName": "at", "namespaced": true, "kind": "At", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ], "storageVersionHash": "F80EP5Fp7c4=" }, { "name": "ats/status", "singularName": "", "namespaced": true, "kind": "At", "verbs": [ "get", "patch", "update" ] } ] } $ kubectl proxy 8080 # 別ターミナルで $ curl http://127.0.0.1:8001/apis/cnat.programming-kubernetes.info/v1alpha1 { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "cnat.programming-kubernetes.info/v1alpha1", "resources": [ { "name": "ats", "singularName": "at", "namespaced": true, "kind": "At", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ], "storageVersionHash": "F80EP5Fp7c4=" }, { "name": "ats/status", "singularName": "", "namespaced": true, "kind": "At", "verbs": [ "get", "patch", "update" ] } ] } ``` ## Type Definitions - CRD は Kubernetes API server プロセスの中の `apiextensions-apiserver` が提供する `apiextensions.k8s.io/v1beta1` API の中の Kubernetes resource - CRD のスキーマは以下のようになっている ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: name spec: group: group name version: version name names: kind: uppercase name plural: lowercase plural name singular: lowercase singular name # defaulted to be lowercase kind shortNames: list of strings as short names # optional listKind: uppercase list kind # defaulted to be kindList categories: list of category membership like "all" # optional validation: # optional openAPIV3Schema: OpenAPI schema # optional subresources: # optional status: {} # to enable the status subresource (optional) scale: # optional specReplicasPath: JSON path for the replica number in the spec of the custom resource statusReplicasPath: JSON path for the replica number in the status of the custom resource labelSelectorPath: JSON path of the Scale.Status.Selector field in the scale resource versions: # defaulted to the Spec.Version field - name: version name served: boolean whether the version is served by the API server # defaults to false storage: boolean whether this version is the version used to store object - ... ``` - 多くのフィールドはオプショナルかデフォルト値が設定されている - CRD が作られると `kube-apiserver` 内部の `apiextensions-apiserver` は CRD の名前をチェックし、その CRD が他のリソースと競合していないかや一貫性があるかを判断する - 少しすると以下のようにその CRD の判断結果のステータスがレポートされる ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} validation: openAPIV3Schema: type: object properties: apiVersion: type: string kind: type: string metadata: type: object spec: properties: schedule: type: string type: object status: type: object version: v1alpha1 versions: - name: v1alpha1 served: true storage: true status: acceptedNames: kind: At listKind: AtList plural: ats singular: at conditions: - lastTransitionTime: "2019-03-17T09:44:21Z" message: no conflicts found reason: NoConflicts status: "True" # ← True type: NamesAccepted # 競合がない - lastTransitionTime: null message: the initial names have been accepted reason: InitialNamesAccepted status: "True" type: Established # ← 指定されたリソースを提供することを説明 storedVersions: - v1alpha1 ``` - このように `spec` で定義していなかったフィールドにデフォルト値が設定され - ステータスが `True` への設定されている - 更に次の条件が設定されている - NamesAccepted: spec で設定した名前に一貫性があり、競合がないかどうかを説明している - Established: APIサーバーが `status.acceptedNames` 内の名前で指定されたリソースを提供することを説明している - 特定のフィールドは、CRDが作成された後でも長く変更できることに注意すること - たとえば、短い名前または列を追加できる - この場合、仕様名に矛盾がありますが、CRDを確立できる(つまり、古い名前で提供される) - したがって、NamesAccepted条件は偽になり、仕様名と受け入れられる名前は異なる ## Advanced Features of Custom Resources - このセクションでは、検証やサブリソースなどのカスタムリソースの高度な機能について説明する ### Validating Custom Resources - Custome resource は作成時や更新時に検証することができる - 検証は CRD の `spec` の `Validation` フィールドで指定された [OpenAPI v3 schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) に基づいて行われる - Custome resource の作成や変更のリクエストが行われた際に、リクエストされた Custome resource の `spec` に対して CRD の `Validation` の JSON object の検証が行われる - 検証でエラーが発生すると、HTTP status 400 となり、その中で競合が発生したフィールドが返される - 更に、 `admission webhook` を利用するとより複雑な検証を実行できる - `admission webhook` による検証は OpenAPI に基づく検証の後に行われる ![](https://i.imgur.com/iBS8xnh.png) - OpenAPI Schema は JSON Schema に基づいており、JSON/YAMLを使って表現できる ```yaml type: object properties: apiVersion: type: string kind: type: string metadata: type: object spec: type: object properties: schedule: type: string pattern: "^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])..." command: type: string required: - schedule - command status: type: object properties: phase: type: string required: - metadata - apiVersion - kind - spec ``` #### OPENAPI V3 SCHEMAS, COMPLETENESS, AND THEIR FUTURE - Kubernetes 1.14 までは OpenAPI v3 schemas はオプションでサーバサイドでの検証のみに使用されていた - Kubernetes 1.15 以降では CRD のスキーマは Kubernetes API server の OpenAPI spec の一部として公開される ```shell= $ kubectl proxy 8080 $ curl -i http://127.0.0.1:8001/openapi/v2 ``` のようにして確認できる NOTE: 確認してみた v1.14 ```shell= cat << EOS | kubectl apply -f - apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} version: v1alpha1 versions: - name: v1alpha1 served: true storage: true validation: openAPIV3Schema: type: object properties: spec: type: object properties: foo: type: string EOS $ kubectl proxy 8080 $ curl -s http://127.0.0.1:8001/openapi/v2 | jq . | grep programming-kubernetes ``` v1.15 ```shell= cat << EOS | kubectl apply -f - apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} version: v1alpha1 versions: - name: v1alpha1 served: true storage: true validation: openAPIV3Schema: type: object properties: spec: type: object properties: foo: type: string EOS $ kubectl proxy 8080 $ curl -s http://127.0.0.1:8001/openapi/v2 | jq '.paths."/apis/cnat.programming-kubernetes.info/v1alpha1/ats"' { "get": { "description": "list objects of kind At", "consumes": [ "*/*" ], "produces": [ "application/json", "application/yaml" ], "schemes": [ "https" ], "tags": [ "cnatProgrammingKubernetesInfo_v1alpha1" ], "operationId": "listCnatProgrammingKubernetesInfoV1alpha1AtForAllNamespaces", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/info.programming-kubernetes.cnat.v1alpha1.AtList" } }, "401": { "description": "Unauthorized" } }, "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "cnat.programming-kubernetes.info", "kind": "At", "version": "v1alpha1" } }, "parameters": [ { "uniqueItems": true, "type": "boolean", "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.\n\nThis field is alpha and can be changed or removed without notice.", "name": "allowWatchBookmarks", "in": "query" }, ... ] } ``` - 将来的には [Custome Resource Instance が削除](https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190425-structural-openapi.md)されるとのこと NOTE: 多分 KEP 的には https://github.com/sttts/enhancements/blob/c12a7acc367b71da5d8ee87ad1e2e47fe9fa0afe/keps/sig-api-machinery/20180731-crd-pruning.md のほうがリンクが正しい気がする OpenAPI Schema で定義されていないフィールドは自動的に削除されるようになるということだと思う - OpenAPI schema を手動で生成するのは面倒なので [controller-tools](https://github.com/kubernetes-sigs/controller-tools/blob/a653df4cae21d140ed307073ccbfb1d7e245d3a6/cmd/controller-gen/main.go#L130-L141) を利用すると良い - controller-tools を fork して CRD 生成機能を切り出した [crd-schema-gen](https://github.com/openshift/crd-schema-gen) を開発している NOTE: controller-tools による CRD 生成方法例は以下のような感じ ```shell= $ mkdir $GOPATH/src/github/kubernetes-sigs $ cd $GOPATH/src/github/kubernetes-sigs $ git clone git@github.com:kubernetes-sigs/controller-tools.git $ cd controller-tools $ export GO111MODULE=on $ go run cmd/controller-gen/main.go crd paths=./pkg/crd/testdata/cronjob_types.go output:stdout --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: creationTimestamp: null name: cronjobs.testdata.kubebuilder.io spec: group: testdata.kubebuilder.io names: kind: CronJob plural: cronjobs scope: Namespaced version: v1 versions: ... ``` ## Short Names and Categories - CRD のリソースには短い名前を付けることができる - 短い名前(short names)はエイリアス(aliases)とも言える - エイリアスはいくつでも付けることができる - `kubectl api-resources` でエイリアスを確認することができる ```shell= $ kubectl api-resources NAME SHORTNAMES APIGROUP NAMESPACED KIND bindings true Binding componentstatuses cs false ComponentStatus configmaps cm true ConfigMap endpoints ep true Endpoints events ev true Event limitranges limits true LimitRange namespaces ns false Namespace nodes no false Node persistentvolumeclaims pvc true PersistentVolumeClaim persistentvolumes pv false PersistentVolume pods po true Pod statefulsets sts apps true StatefulSet ... ``` - 短い名前は CRD の `spec.names.shortNames` でリスト形式で定義することができる - `spec.categories` で Custome resource をカテゴリに参加させることができる - 例えば `all` カテゴリに所属すると `kubectl get all` で Custome resource が表示されるようになる NOTE: やってみたけど出てこない ```shell= $ cat << EOS | kubectl apply -f - apiVersion: cnat.programming-kubernetes.info/v1alpha1 kind: At metadata: name: sample spec: foo: bar EOS $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13d $ cat << EOS | kubectl apply -f - apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at categories: - all scope: Namespaced subresources: status: {} version: v1alpha1 versions: - name: v1alpha1 served: true storage: true EOS customresourcedefinition.apiextensions.k8s.io/ats.cnat.programming-kubernetes.info configured $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13d $ kubectl get at NAME AGE sample 3m6s ``` ### Printer Columns - CRD の `spec.additionalPrinterColumns` で `kubectl get <resource name>` で出力されるカラム情報を追加することができる ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: additionalPrinterColumns: (optional) - name: kubectl column name type: OpenAPI type for the column format: OpenAPI format for the column (optional) description: human-readable description of the column (optional) priority: integer, always zero supported by kubectl JSONPath: JSON path inside the CR for the displayed value ``` NOTE: -o wide 付けると出てきた ```shell= $ kubectl get at -o wide NAME AGE sample 8m53s $ cat << EOS | kubectl apply -f - apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} version: v1alpha1 versions: - name: v1alpha1 served: true storage: true additionalPrinterColumns: - name: foo type: string priority: 1 JSONPath: .spec.foo EOS $ kubectl get at -o wide NAME FOO sample bar ``` ## Subresources - サブリソースは特別な http エンドポイント - 例えば pod も以下のようなサブリソースを持っている - /api/v1/namespace/namespace/pods/name/logs - /api/v1/namespace/namespace/pods/name/portforward - /api/v1/namespace/namespace/pods/name/exec - /api/v1/namespace/namespace/pods/name/status - サブリソースのエンドポイントはメインリソースとは異なるプロトコルを使用する - Programming Kubernetes 執筆時点で CRD がサポートしているサブリソースは以下 - /scale - /status - 上記は両方オプトインで CRD で明示的に有効にする必要がある ## STATUS SUBRESOURCE - `/status` サブリソースは controller-provided のステータスから Custome resource インスタンスの user-provided の仕様を分割するために使用される? - 主なモチベーションの動機は特権の分離 - ユーザーは通常 `status` フィールドに書き込むべきではない - コントローラは `spec` フィールドに書き込むべきではない - アクセスコントロールのための RBAC のメカニズムは上記のような詳細なレベルのルールでの制御を許可しない - これらのルールはリソース単位で設定される - `/status` サブリソースは独自したリソースである2つのエンドポイントを提供することでこれらの問題を解決する - それぞれは RBAC のルールで独立してコントロールできる - これらは `spec-status split` と呼ばれる ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: ... rules: - apiGroups: [""] resources: ["ats/status"] verbs: ["update", "patch"] ``` NOTE: ここよく意味わからなかったので Google 翻訳の翻訳そのまま書いた - /statusサブリソースを持つリソース(カスタムリソースを含む)は、メインリソースエンドポイントのセマンティクスも変更されました。 - 作成中のメインHTTPエンドポイントのステータスの変更(ステータスは作成中にドロップされるだけです)と更新を無視します。 - 同様に、/statusサブリソースエンドポイントは、ペイロードのステータス以外の変更を無視します。/statusエンドポイントでの作成操作はできません。 - 変更のmetadata外側と外側の何かstatus(特に仕様の変更を意味する)のたびに、メインリソースエンドポイントはmetadata.generation値を増やします。これは、ユーザーの希望が変わったことを示すコントローラーのトリガーとして使用できます。 - 通常 `spec` と `status` は両方更新リクエストで送られるが、技術的にはリクエストペイロードの他の部分で送られないように除外することができることに注意する - また /status エンドポイントはラベルやアノテーションといった metadata の変更などの、 status 以外の全てを無視することに注意する - Custome resource の spec-status split は以下のように有効にできる ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition spec: subresources: status: {} ... ``` ### WARNING - `spec-status split` を有効にすることは API にとって破壊的変更 - 古いコントローラはメインエンドポイントに(`status`を)書き込む - そのため split が有効になった時点からステータスが無視されていることに気づくことができない(通知を watch で受け取ることができないということ?) - Kubernetes 1.13以降ではバージョン別にサブリソースすることができる - これによって破壊的変更無しに `/status` サブリソースを導入できるようになった ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition spec: ... versions: - name: v1alpha1 served: true storage: true - name: v1beta1 served: true subresources: status: {} ``` - 上記では `v1beta` では `/status` サブリソースが有効になるが、 `v1alpha` では有効にならない ### NOTE - 楽観的並行性のセマンティック([楽観的並行性を参照](https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch01.html#optimistic-concurrency))はメインリソースのエンドポイントと同じ - つまり `status` と `spec` はリソースバージョンカウンターを共有している - `/status` への書き込みはメインリソースへの書き込みと競合することがあり、その逆もまたある - つまりストレージレイヤーでは `status` と `spec` は分割されていない NOTE: とりあえず status サブリソースを試してみた 返ってくる値自体は変わらないみたい ```shell= $ curl -s http://127.0.0.1:8001/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample | jq . { "apiVersion": "cnat.programming-kubernetes.info/v1alpha1", "kind": "At", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cnat.programming-kubernetes.info/v1alpha1\",\"kind\":\"At\",\"metadata\":{\"annotations\":{},\"name\":\"sample\",\"namespace\":\"default\"},\"spec\":{\"foo\":\"bar\"}}\n" }, "creationTimestamp": "2019-08-30T16:08:52Z", "generation": 1, "name": "sample", "namespace": "default", "resourceVersion": "24677", "selfLink": "/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample", "uid": "68d7dc5f-b91f-4c0d-b1ba-5c2ecb8241b4" }, "spec": { "foo": "bar" } } $ curl -s http://127.0.0.1:8001/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample/status | jq . { "apiVersion": "cnat.programming-kubernetes.info/v1alpha1", "kind": "At", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cnat.programming-kubernetes.info/v1alpha1\",\"kind\":\"At\",\"metadata\":{\"annotations\":{},\"name\":\"sample\",\"namespace\":\"default\"},\"spec\":{\"foo\":\"bar\"}}\n" }, "creationTimestamp": "2019-08-30T16:08:52Z", "generation": 1, "name": "sample", "namespace": "default", "resourceVersion": "24677", "selfLink": "/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample/status", "uid": "68d7dc5f-b91f-4c0d-b1ba-5c2ecb8241b4" }, "spec": { "foo": "bar" } } ``` ## SCALE SUBRESOURCE - `/scale` はスケールアップ/スケールダウンのためのリソース - `kubectl scale` コマンドは `/scale` サブリソースを利用する - 例えば以下は指定されたインスタンスの指定されたレプリカ値を変更する ```shell= $ kubectl scale --replicas=3 your-custom-resource -v=7 I0429 21:17:53.138353 66743 round_trippers.go:383] PUT https://host/apis/group/v1/your-custom-resource/scale ``` ```yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition spec: subresources: scale: specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas labelSelectorPath: .status.labelSelector ... ``` - これにより `replica` の値が `spec.replicas` に書き込まれ、 GET リクエストで返されるようになる - `/status` サブリソースを通してラベルセレクターを変更することはできず、読み込み専用となっている - その目的は対応するオブジェクトをカウントするための情報をコントローラーに提供すること - 例えば `ReplicaSet` コントローラはセレクターを満たした pod をカウントしている - ラベルセレクターはオプションなので、もし Custome resource セマンティックがラベルセレクタにフィットしないのであればラベルセレクターを JSON パスに指定しないようにする - 上記の例では `spec.replicas` を利用したが、 `spec.instances` や `spec.size` といったコンテキストに応じた適切なシンプルな JSON パスを利用することもできる NOTE: ↓のように試してみると実際に replicas が更新されるのがわかる ```shell= $ cat << EOS | kubectl apply -f - apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ats.cnat.programming-kubernetes.info spec: group: cnat.programming-kubernetes.info names: kind: At listKind: AtList plural: ats singular: at scope: Namespaced subresources: status: {} scale: specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas labelSelectorPath: .status.labelSelector version: v1alpha1 versions: - name: v1alpha1 served: true storage: true EOS cat << EOS | kubectl apply -f - apiVersion: cnat.programming-kubernetes.info/v1alpha1 kind: At metadata: name: sample spec: foo: bar replicas: 1 EOS $ kubectl scale --replicas=3 ats sample -v=7 I0831 01:28:27.575244 51444 loader.go:359] Config loaded from file /Users/bells17/.kube/kind-config-kind I0831 01:28:27.576610 51444 round_trippers.go:416] GET https://127.0.0.1:62641/api?timeout=32s I0831 01:28:27.576621 51444 round_trippers.go:423] Request Headers: I0831 01:28:27.576625 51444 round_trippers.go:426] Accept: application/json, */* I0831 01:28:27.576630 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:27.592591 51444 round_trippers.go:441] Response Status: 200 OK in 15 milliseconds I0831 01:28:27.609539 51444 round_trippers.go:416] GET https://127.0.0.1:62641/apis?timeout=32s I0831 01:28:27.609584 51444 round_trippers.go:423] Request Headers: I0831 01:28:27.609598 51444 round_trippers.go:426] Accept: application/json, */* I0831 01:28:27.609603 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:27.613274 51444 round_trippers.go:441] Response Status: 200 OK in 3 milliseconds I0831 01:28:27.628950 51444 round_trippers.go:416] GET https://127.0.0.1:62641/apis/cnat.programming-kubernetes.info/v1alpha1?timeout=32s I0831 01:28:27.629090 51444 round_trippers.go:423] Request Headers: I0831 01:28:27.629127 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:27.629153 51444 round_trippers.go:426] Accept: application/json, */* ... I0831 01:28:28.110093 51444 round_trippers.go:416] GET https://127.0.0.1:62641/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample I0831 01:28:28.110126 51444 round_trippers.go:423] Request Headers: I0831 01:28:28.110135 51444 round_trippers.go:426] Accept: application/json I0831 01:28:28.110142 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:28.115197 51444 round_trippers.go:441] Response Status: 200 OK in 5 milliseconds I0831 01:28:28.118032 51444 round_trippers.go:416] GET https://127.0.0.1:62641/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample/scale I0831 01:28:28.118087 51444 round_trippers.go:423] Request Headers: I0831 01:28:28.118099 51444 round_trippers.go:426] Accept: application/json, */* I0831 01:28:28.118103 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:28.121426 51444 round_trippers.go:441] Response Status: 200 OK in 3 milliseconds I0831 01:28:28.123662 51444 round_trippers.go:416] PUT https://127.0.0.1:62641/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample/scale I0831 01:28:28.123679 51444 round_trippers.go:423] Request Headers: I0831 01:28:28.123683 51444 round_trippers.go:426] Accept: application/json, */* I0831 01:28:28.123687 51444 round_trippers.go:426] User-Agent: kubectl/v1.14.3 (darwin/amd64) kubernetes/5e53fd6 I0831 01:28:28.136220 51444 round_trippers.go:441] Response Status: 200 OK in 12 milliseconds at.cnat.programming-kubernetes.info/sample scaled $ curl -s http://127.0.0.1:8001/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample/status | jq .spec { "foo": "bar", "replicas": 3 } ``` ### THE REPLICA INTEGER VALUE VERSUS THE CONTROLLER THAT CREATES AND DELETES REPLICAS - ここでは Custome resource を読み取ったり設定したりすることだけについて書いてあるが、実際にレプリカのインスタンスの作成/削除を行うには Custome Controller の実装が必要(これについては [Controllers and Operators](https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch01.html#ch_controllers-operators) を参照) ### --- - エンドポイントに読み書きを行うオブジェクトの種類は `autoscaling/v1` API グループの `Scale` となる - 例となるのは以下のようなもの ```go type Scale struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec ScaleSpec `json:"spec,omitempty"` Status ScaleStatus `json:"status,omitempty"` } type ScaleSpec struct { Replicas int32 `json:"replicas,omitempty"` } type ScaleStatus struct { Replicas int32 `json:"replicas"` Selector string `json:"selector,omitempty"` } ``` - インスタンスは以下のようなもの ```yaml metadata: name: cr-name namespace: cr-namespace uid: cr-uid resourceVersion: cr-resource-version creationTimestamp: cr-creation-timestamp spec: replicas: 3 status: replicas: 2 selector: "environment = production" ``` - `/status` 同様にメインリソースとの競合に注意する ## A Developer’s View on Custom Resources - カスタムリソースは以下のような Golang クライアントを利用してアクセスできます - `client-go` のダイナミッククライアント([Dynamic Client を参照](https://learning.oreilly.com/library/view/Programming+Kubernetes/9781492047094/ch04.html#dynamic-client)) - 型付きクライアント - kubernetes-sigs/controller-runtime - Operator SDK - Kubebuilder - (上記3つは詳しくは [“controller-runtime Client of Operator SDK and Kubebuilder” を参照](https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch04.html#controller-runtime)) - k8s.io/client-go/kubernetes のような `client-gen` によって生成されたクライアント(詳しくは https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch04.html#clientgen-client) - k8s.io/client-go/kubernetes のような `client-gen` によって生成されたクライアント(詳しくは [TYPED CLIENT CREATED VIA CLIENT-GEN を参照](https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch04.html#clientgen-client)) - 使用するクライアントは記述するコードのコンテキストに依存する - 特に実装するロジックや要件に依存する(例えばコンパイル時には不明な動的な GKV のバージョンをサポートするなど) - NOTE: ここよくわからん - 前述のクライアントのリスト: - 未知の GVK を処理する柔軟性が低下 - 型安全性の向上 - Kubernetes API が提供する機能の完全性が向上 ### Dynamic Client - k8s.io/client-go/dynamic のダイナミッククライアントは既知の GVK に依存しない - `unstructured.Unstructured` 以外の Go の型も使用せず、`json.Unmarshal` でラップしてアウトプットする - ダイナミッククライアントはスキーマも RESTMapper もどちらも使用しない - 開発者は GVR の形式でリソースを提供することにより、型に関するすべての知識を手動で提供する必要がある([Resource を参照](https://learning.oreilly.com/library/view/programming-kubernetes/9781492047094/ch03.html#resources)) ```go schema.GroupVersionResource{ Group: "apps", Version: "v1", Resource: "deployments", } ``` - もし REST クライアントの構成が可能であれば、ダイナミッククライアントが以下の1行で作成可能 ```go client, err := NewForConfig(cfg) ``` - 特定の GVR への REST アクセスも以下のようにシンプル ```go client.Resource(gvr). Namespace(namespace).Get("foo", metav1.GetOptions{}) ``` - これにより指定された名前空間にデプロイメント `foo` が提供される #### NOTE - リソースのスコープを知る必要がある(名前空間レベルなのか、クラスターレベルなのか) - クラスタースコープは名前空間を省略します ### --- - ダイナミッククライアントの入出力は *unstructured.Unstructured - つまり、json.Unmarshalがアンマーシャリング時に出力するのと同じデータ構造を含むオブジェクト - オブジェクトは map[string]interface{} で表現される - Array は[]interface{} で表現される - プリミティブ型は string, bool, float64, int64. - この UnstructuredContent() メソッドはこの構造化されたデータの内側にある、非構造化データへのアクセス方法を提供する(また、Unstructured.Object によってもアクセスできる) - これらは同じパッケージのヘルパーで欠落したフィールド(に対するアクセス?)を簡単にして、オブジェクトにたいする巧みな扱いを可能にします - 以下がその例 ```go name, found, err := unstructured.NestedString(u.Object, "metadata", "name") ``` - これは Deployment の name を返します(この場合は “foo”) - found は実際にフィールドが見つかったら true を返します(空の場合だけでなく、実際に存在する場合にも?) - err は存在するフィールドの型が想定外の場合、それをレポートします(この場合では文字列型ではない場合) (edited) - 他のヘルパーは一般的なもので、一つは結果をディープコピーするもので、一つは除外するもの ```go func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) ``` - 以下は異なる型をキャストして返し、またエラーがあればそれを返します ```go func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) ``` - 最後に一般的なセッター func SetNestedField(obj, value, path...) - ダイナミッククライアントはKubernetes自身でもコントローラで、親オブジェクトが削除されたオブジェクトを削除するガベージコレクションコントローラなどで一般的に使われている - ガベージコレクションコントローラはシステムのどんなリソースに対しても動作するので、ダイナミッククライアントを広範囲で使用します NOTE: 試した ```go package main import ( "os/user" "path/filepath" "github.com/k0kubun/pp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/clientcmd" ) var ( runtimeClassGVR = schema.GroupVersionResource{ Group: "cnat.programming-kubernetes.info", Version: "v1alpha1", Resource: "ats", } ) func main() { config, _ := clientcmd.BuildConfigFromFlags("", userConfig()) client, _ := dynamic.NewForConfig(config) res := client.Resource(runtimeClassGVR) list, _ := res.List(metav1.ListOptions{}) pp.Print(list) } func userConfig() string { usr, _ := user.Current() return filepath.Join(usr.HomeDir, ".kube", "config") } ``` ```shell= $ go run main.go &unstructured.UnstructuredList{ Object: map[string]interface {}{ "kind": "AtList", "metadata": map[string]interface {}{ "continue": "", "resourceVersion": "170970", "selfLink": "/apis/cnat.programming-kubernetes.info/v1alpha1/ats", }, "apiVersion": "cnat.programming-kubernetes.info/v1alpha1", }, Items: []unstructured.Unstructured{ unstructured.Unstructured{ Object: map[string]interface {}{ "apiVersion": "cnat.programming-kubernetes.info/v1alpha1", "kind": "At", "metadata": map[string]interface {}{ "uid": "7b651590-cb3d-11e9-b83e-025000000001", "annotations": map[string]interface {}{ "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cnat.programming-kubernetes.info/v1alpha1\",\"kind\":\"At\",\"metadata\":{\"annotations\":{},\"name\":\"sample\",\"namespace\":\"default\"},\"spec\":{\"foo\":\"bar\"}}\n", }, "creationTimestamp": "2019-08-30T15:47:33Z", "generation": 1, "name": "sample", "namespace": "default", "resourceVersion": "164101", "selfLink": "/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample", }, "spec": map[string]interface {}{ "foo": "bar", }, }, }, unstructured.Unstructured{ Object: map[string]interface {}{ "kind": "At", "metadata": map[string]interface {}{ "generation": 1, "name": "sample2", "namespace": "default", "resourceVersion": "164716", "selfLink": "/apis/cnat.programming-kubernetes.info/v1alpha1/namespaces/default/ats/sample2", "uid": "a71918bb-cb3e-11e9-b83e-025000000001", "annotations": map[string]interface {}{ "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cnat.programming-kubernetes.info/v1alpha1\",\"kind\":\"At\",\"metadata\":{\"annotations\":{},\"name\":\"sample2\",\"namespace\":\"default\"},\"spec\":{\"foo\":\"bar2\"}}\n", }, "creationTimestamp": "2019-08-30T15:55:56Z", }, "spec": map[string]interface {}{ "foo": "bar2", }, "apiVersion": "cnat.programming-kubernetes.info/v1alpha1", }, }, }, } ``` ## Typed Clients - 型付きのクライアントは map[string]interface{} のような汎用的なデータ構造を使わず、GVK毎に指定した異なる実際のGo言語の型を使用する - これは使いやすく、型安全性がかなり高まり、コードを簡潔で読みやすいものにする - マイナス面は柔軟性を減らすこと、何故ならば処理された型はコンパイル時に知られている必要があり、クライアントは生成されていて、複雑さを追加するからだ - 2つの型付きクライアントに行く前に、Go言語の型システムの種類を見てみましょう ### ANATOMY OF A TYPE - 種類はGo言語の構造体で表現される - 一般的に構造体には種類の名前がついていて、手元の GVK のバージョンとグループに対応するパッケージに置かれている - 共通の慣例はGoのパッケージの中に GVK を `group/version.Kind` と置くこと ```go pkg/apis/group/version ``` - `types.go` の中に `Kind` の Go の構造体が定義されている - 全ての GVK に対応する Go の構造体は `k8s.io/apimachinery/pkg/apis/meta/v1` パッケージで `TypeMeta` 構造体が埋め込まれている - `TypeMeta` は `Kind` と `ApiVersion` で成り立っている ```go type TypeMeta struct { // +optional APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` // +optional Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` } ``` - 加えて、すべてのトップレベルの種類-つまり、独自のエンドポイントを持つため、対応する1つ(または複数)のGVR(「RESTマッピング」を参照)に名前、名前空間付きリソースの名前空間、およびさらに多くのメタレベルフィールドを保存する必要があります - これらは全て `k8s.io/apimachinery/pkg/apis/meta/v1` パッケージの `ObjectMeta` 構造体に保存されます ```go type ObjectMeta struct { Name string `json:"name,omitempty"` Namespace string `json:"namespace,omitempty"` UID types.UID `json:"uid,omitempty"` ResourceVersion string `json:"resourceVersion,omitempty"` CreationTimestamp Time `json:"creationTimestamp,omitempty"` DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"` Labels map[string]string `json:"labels,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` ... } ``` - 広範なインラインドキュメントを一読することを強くお勧めします - それは、Kubernetesオブジェクトのコア機能の良い全体像を提供するからです。 - https://github.com/kubernetes/apimachinery/blob/0aa9751e8aaff1b6afa1ca5270d8e280878797e4/pkg/apis/meta/v1/types.go#L94 - Kubernetesのトップレベルの型(つまり、埋め込まれたTypeMetaと埋め込まれたObjectMetaを持ち、この場合はetcdに永続化されている型)は、通常 spec と satus を持っているという意味で互いに非常に似ています。 - k8s.io/kubernetes/apps/v1/types.go の以下の Deployment の例を見てください ```go type Deployment struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec DeploymentSpec `json:"spec,omitempty"` Status DeploymentStatus `json:"status,omitempty"` } ``` - spec と status の型の実際の内容は型によって大きく異なりますが、 spec と status に分割することは、Kubernetesの一般的なテーマまたは慣習です。ただし、技術的には必須ではありません。 - したがって、CRDのこの構造にも従うことをお勧めします - 一部のCRD機能には、この構造も必要です - たとえば、Custome resource の `/status` サブリソースを有効にすると、常に Custome resource インスタンスの status のサブ構造にのみ適用されます。 名前を変更することはできません。 ### GOLANG PACKAGE STRUCTURE - Go言語の構造体は伝統的に `pkg/apis/group/version` の `types.go` に置かれる - そのファイルに加えて、今からやりたいファイルがいくつかあります。 - 開発者が手動で作成したものもあれば、コードジェネレーターで生成したものもあります - 詳細は Chapter 5 で参照できる - `doc.go` ファイルは、APIの目的を説明し、多数のパッケージグローバルコード生成タグが含まれています。 ```go // Package v1alpha1 contains the cnat v1alpha1 API group // // +k8s:deepcopy-gen=package // +groupName=cnat.programming-kubernetes.info package v1alpha1 ``` - 次に、`register.go` には、Golangのカスタムリソースタイプをスキームに登録するヘルパーが含まれています(「スキーム」を参照)。 ```go package version import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" group "repo/pkg/apis/group" ) // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{ Group: group.GroupName, Version: "version", } // Kind takes an unqualified kind and returns back a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // Resource takes an unqualified resource and returns a Group // qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) AddToScheme = SchemeBuilder.AddToScheme ) // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &SomeKind{}, &SomeKindList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } ``` - 次に、`zz_generated.deepcopy.go`は、カスタムリソースGolangのトップレベルの型でディープコピーメソッドを定義します。 - この例では `doc.go` で `+k8s:deepcopy-gen=package` タグを使用しているため、ディープコピー生成はオプトアウトベースです。 つまり、DeepCopyメソッドは、`+k8s:deepcopy-gen=false` でオプトアウトしないパッケージのすべてのタイプに対して生成されます。 詳細については、第5章、特に「deepcopy-genタグ」を参照してください。 ### TYPED CLIENT CREATED VIA CLIENT-GEN - APIパッケージ `pkg/apis/ group/version` を配置すると、クライアントジェネレーターclient-genは、デフォルトで `pkg/generated/clientset/versioned` に型付きクライアントを作成します(詳細については、特に「client-genタグ」を参照)。 (旧バージョンのジェネレーターでは `pkg/client/clientset/versioned` )。 - より正確には、生成された最上位オブジェクトはクライアントセットです。 多数のAPIグループ、バージョン、およびリソースが含まれます。 - 最上位ファイルは次のようになります ```go // Code generated by client-gen. DO NOT EDIT. package versioned import ( discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" cnatv1alpha1 ".../cnat/cnat-client-go/pkg/generated/clientset/versioned/ ) type Interface interface { Discovery() discovery.DiscoveryInterface CnatV1alpha1() cnatv1alpha1.CnatV1alpha1Interface } // Clientset contains the clients for groups. Each group has exactly one // version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient cnatV1alpha1 *cnatv1alpha1.CnatV1alpha1Client } // CnatV1alpha1 retrieves the CnatV1alpha1Client func (c *Clientset) CnatV1alpha1() cnatv1alpha1.CnatV1alpha1Interface { return c.cnatV1alpha1 } // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { ... } // NewForConfig creates a new Clientset for the given config. func NewForConfig(c *rest.Config) (*Clientset, error) { ... } ``` - クライアントセットは、インターフェイスInterfaceで表され、各バージョンのAPIグループクライアントインターフェイスへのアクセスを提供します。たとえば、このサンプルコードのCnatV1alpha1Interfaceです。 ```go type CnatV1alpha1Interface interface { RESTClient() rest.Interface AtsGetter } // AtsGetter has a method to return a AtInterface. // A group's client should implement this interface. type AtsGetter interface { Ats(namespace string) AtInterface } // AtInterface has methods to work with At resources. type AtInterface interface { Create(*v1alpha1.At) (*v1alpha1.At, error) Update(*v1alpha1.At) (*v1alpha1.At, error) UpdateStatus(*v1alpha1.At) (*v1alpha1.At, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error Get(name string, options v1.GetOptions) (*v1alpha1.At, error) List(opts v1.ListOptions) (*v1alpha1.AtList, error) Watch(opts v1.ListOptions) (watch.Interface, error) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.At, err error) AtExpansion } ``` - クライアントセットのインスタンスは、NewForConfigヘルパー関数を使用して作成できます。 これは、「クライアントの作成と使用」で説明したコアKubernetesリソースのクライアントに類似しています。 ```go import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" client "github.com/.../cnat/cnat-client-go/pkg/generated/clientset/versioned" ) kubeconfig = flag.String("kubeconfig", "~/.kube/config", "kubeconfig file") flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) clientset, err := client.NewForConfig(config) ats := clientset.CnatV1alpha1Interface().Ats("default") book, err := ats.Get("kubernetes-programming", metav1.GetOptions{}) ``` - ご覧のとおり、コード生成機構により、コアKubernetesリソースの場合とまったく同じ方法でカスタムリソースのロジックをプログラミングできます。 情報提供者のような高レベルのツールも利用できます。 第5章のinformer-genを参照してください。 ### controller-runtime Client of Operator SDK and Kubebuilder - 完全を期すために、 A Developer’s View on Custom Resources の2番目のオプションとしてリストされている3番目のクライアントを簡単に見てみたいと思います。 - コントローラーランタイムプロジェクトは、第6章で説明したオペレーターソリューションOperator SDKとKubebuilderの基礎を提供します。 Anatomy of a type で説明したGoタイプを使用するクライアントが含まれています。 - 前の TYPED CLIENT CREATED VIA CLIENT-GEN の client-gen で生成されたクライアントとは異なり、「動的クライアント」と同様に、このクライアントは1つのインスタンスであり、特定のスキームに登録されたあらゆる種類を処理できます 。 - コントローラーランタイムの使用方法の簡単な例を次に示します。 ```go import ( "flag" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/clientcmd" runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" ) kubeconfig = flag.String("kubeconfig", "~/.kube/config", "kubeconfig file path") flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) cl, _ := runtimeclient.New(config, client.Options{ Scheme: scheme.Scheme, }) podList := &corev1.PodList{} err := cl.List(context.TODO(), client.InNamespace("default"), podList) ``` - クライアントオブジェクトのList()メソッドは、指定されたスキームで登録されたすべてのruntime.Objectを受け入れます。この場合、登録されているすべての標準Kubernetesの種類でclient-goから借用したオブジェクトです。 内部的に、クライアントは指定されたスキームを使用して、Golangタイプ* corev1.PodListをGVKにマッピングします。 2番目のステップで、List()メソッドはディスカバリー情報を使用してポッドのGVRを取得します。これはschema.GroupVersionResource {""、 "v1"、 "pods"}であるため、/ api / v1 / namespace / default /にアクセスします ポッドを使用して、渡されたネームスペース内のポッドのリストを取得します。 - 同じロジックをカスタムリソースで使用できます。 主な違いは、渡されたGoタイプを含むカスタムスキームを使用することです。 ```go import ( "flag" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/clientcmd" runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" cnatv1alpha1 "github.com/.../cnat/cnat-kubebuilder/pkg/apis/cnat/v1alpha1" ) kubeconfig = flag.String("kubeconfig", "~/.kube/config", "kubeconfig file") flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) crScheme := runtime.NewScheme() cnatv1alpha1.AddToScheme(crScheme) cl, _ := runtimeclient.New(config, client.Options{ Scheme: crScheme, }) list := &cnatv1alpha1.AtList{} err := cl.List(context.TODO(), client.InNamespace("default"), list) ``` - List()コマンドの呼び出しがまったく変わらないことに注意してください。 - このクライアントを使用して多くの異なる種類にアクセスする演算子を書くと想像してください。 TYPED CLIENT CREATED VIA CLIENT-GEN の型付きクライアントでは、多くの異なるクライアントをオペレーターに渡す必要があり、配管コードが非常に複雑になります。 対照的に、ここで紹介するコントローラーランタイムクライアントは、すべての種類が1つのスキーム内にあると仮定して、すべての種類の1つのオブジェクトにすぎません。 - 3種類すべてのクライアントには用途があり、使用されるコンテキストに応じて利点と欠点があります。 未知のオブジェクトを処理する汎用コントローラーでは、動的クライアントのみを使用できます。 型の安全性がコードの正確性を強化するのに非常に役立つコントローラーでは、生成されたクライアントが最適です。 Kubernetesプロジェクト自体には非常に多くの貢献者がいるため、コードの安定性は非常に多くの人々によって拡張および書き換えられた場合でも非常に重要です。 最小限の配管で利便性と高速性が重要な場合、コントローラー実行時クライアントが適しています。 ## Summary - この章では、Kubernetesエコシステムで使用される中心的な拡張メカニズムであるカスタムリソースを紹介しました。 ここまでで、利用可能なクライアントだけでなく、それらの機能と制限について十分に理解する必要があります。 - 次に、上記のリソースを管理するためのコード生成に移りましょう。 ## 次の話題 - 5. Automating Code Generation https://github.com/kubernetes/community/blob/b3349d5b1354df814b67bbdee6890477f3c250cb/contributors/design-proposals/api-machinery/thirdpartyresources.md

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully