owned this note
owned this note
Published
Linked with GitHub
# 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