Xinzhao's Blog

Kubernetes CRD v1 介绍

KubernetesCRD

2021-07-06

前言

CRD API 在 Kubernetes v1.16 版本 GA 了,所以打算来聊一聊关于 CRD v1 版本的一些特性和一些常见的问题;关于 CRD 基础内容可能会一笔带过,不会涉及到太多,基础内容可以在 Kubernetes 官方文档上了解一下(后面参考链接有),可以更多地把这篇文章当作遇到问题时的速查手册。

CRD 是扩展 Kubernetes 最常见和最方便的方法,除了提供定义 custom resource(CR)的基本能力外,CRD 还提供了很多扩展能力,包括 schema、多版本、版本之间的 conversion、subresource 子资源等,本文主要介绍下面四部分的内容:

metadata

最基础但是最重要的部分,metadata 包含的主要内容是:

下面是一个比较完整的例子(注释基本是从 K8s 官方文档上摘抄的,就不翻译了),假设我们要定义一种新的 VolumeSnapshot 资源:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# the name of this CR, must match the spec fields below, and be in the form: <plural>.<group>.
name: volumesnapshots.example.com
spec:
# group is the API group of the defined custom resource.
# The custom resources are served under `/apis/<group>/...`.
# Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
group: example.com
names:
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: VolumeSnapshot
# listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
listKind: VolumeSnapshotList
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: volumesnapshots
# singular name to be used as an alias on the CLI and for display
singular: volumesnapshot
# shortNames allow shorter string to match your resource on the CLI
# kubectl get volumesnapshot/vss
shortNames:
- vss
# either Namespaced or Cluster
scope: Namespaced

其中 group 必须是域名,不然创建的时候就会报下面的错:

The CustomResourceDefinition "volumesnapshots.test" is invalid: spec.group: Invalid value: "test": should be a domain with at least one dot

versions

定义了 CR 的版本信息,包括哪些版本是可用的,每个版本的结构信息,不同版本之间的转换策略等。

目前有两种版本转换的策略:

还是再通过一个例子来看看整体结构:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
...
spec:
...
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1beta1
served: true
storage: true
additionalPrinterColumns:
- name: PVC
type: string
jsonPath: .spec.pvcName
schema:
...
# The conversion section is introduced in Kubernetes 1.13+ with a default value of
# None conversion (strategy sub-field set to None).
conversion:
# None conversion assumes the same schema for all versions and only sets the apiVersion
# field of custom resources to the proper value
strategy: None

关于多版本的常见问题:

schema

version 的一部分,定义了 CR 的结构信息,每个版本都有自己独立的 schema 结构,描述了 CR 有哪些字段,以及每个字段的类型等;schema 使用了 JSON Schema 标准,一个简单的例子如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
...
spec:
...
versions:
- name: v1beta1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
pvcName:
type: string

该 CR 我们只定义了 spec 并且只有一个字段 pvcName,所以创建一个该 CR 的 YAML 文件如下:

apiVersion: example.com/v1beta1
kind: VolumeSnapshot
metadata:
name: s1beta1
namespace: default
spec:
pvcName: pvc1

每个字段都可以设置默认值,这个字段是什么类型的,在 yaml 中 default 的值就要是什么类型的,比如我有下面三种类型的字段:

properties:
type:
type: array
items:
type: string
default:
- "test"
name:
type: string
default: "test"
size:
type: number
default: 1

字段还支持校验,比如 int 可以规定一个最大/最小值的范围,string 支持正则表达式校验和 enum 枚举。具体每种类型支持什么校验可以参考:JSON Schema Validation,一个例子:

properties:
type:
type: array
items:
type: string
minItems: 2
name:
type: string
pattern: '\\w+'
size:
type: number
minimum: 1
maximum: 10

schema 的一些常见问题:

subresource

支持两种类型的 subresource,一个是 Status subresource,一个是 Scale subresource,下面的例子中,status 和 scale 两种 subresource 都启用了:

# subresources describes the subresources for custom resources.
subresources:
# status enables the status subresource.
status: {}
# scale enables the scale subresource.
scale:
# specReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Spec.Replicas.
specReplicasPath: .spec.replicas
# statusReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Replicas.
statusReplicasPath: .status.replicas
# labelSelectorPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Selector.
labelSelectorPath: .status.labelSelector

status subresource

开启 status subresource 之后,整个 CR 的内容将被分为 specstatus 两部分,分别表示资源期望的状态和资源的实际状态,然后该资源的 API 会暴露一个新的 /status 接口,这个 CR 的创建/更新接口会校验整个内容(包括 status 的),但是 status 整个会被忽略,status 只能通过 /status 接口来更新,/status 接口只会更新/校验 status 的内容,其它的会被忽略。

针对 client-go,只要 type 定义了 Status 就会生成 UpdateStatus 方法:

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Snapshot is the snapshot object of the specified PVC.
type Snapshot struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: <https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata>
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SnapshotSpec `json:"spec,omitempty"`
Status SnapshotStatus `json:"status,omitempty"`
}

// SnapshotInterface has methods to work with Snapshot resources.
type SnapshotInterface interface {
Create(*v1beta1.Snapshot) (*v1beta1.Snapshot, error)
Update(*v1beta1.Snapshot) (*v1beta1.Snapshot, error)
UpdateStatus(*v1beta1.Snapshot) (*v1beta1.Snapshot, error)
...
}

// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *snapshots) UpdateStatus(snapshot *v1beta1.Snapshot) (result *v1beta1.Snapshot, err error) {
result = &v1beta1.Snapshot{}
err = c.client.Put().
Namespace(c.ns).
Resource("snapshots").
Name(snapshot.Name).
SubResource("status").
Body(snapshot).
Do().
Into(result)
return
}

scale subresource

和 status subresource 类似,启用之后也会暴露一个新的 /scale 接口,但是我基本没使用过这个,就不过多介绍了,可以看下官方的文档,scale subresource 启用之后还可以使用 kubectl scale 来单独控制资源的副本数量:

kubectl scale --replicas=5 crontabs/my-new-cron-object
crontabs "my-new-cron-object" scaled

kubectl get crontabs my-new-cron-object -o jsonpath='{.spec.replicas}'
5

参考链接


Powered by ☕️, 🍟 and 🍦