
Kubernetes | Custom Resource
前两天搞定了Tcp Ingress,但是用的是比较挫的ConfigMap的方法。留了一个问题是有没有更好的方法。能这么问当然是有啦。不过这玩意儿Nginx Ingress Controller也没打算加。我们先来看下解决的办法本身:Custom Resource。
这两天项目里面同事被调走赶另一个项目,我暂时没有任务,赶紧多学一点。看到Oreilly有免费10天会员就赶紧多薅薅,打算把Programming Kubernetes看完,有机会了再买本实体书报答作者……太贵了。
Kubernetes API
K8S是以API为核心管理集群的系统,也就是apiserver为核心,etcd作为存储,kubelet根据apiserver操作容器,用户通过kubelet之类的工具(换句话说就是kubernetes sdk)和apiserver交互。
K8S的API以Resource Definition为形式,有两部分,一部分是Kind(定义),一部分是Object(实例)。有些资料会把两部分分为Schema Level和Object Level,类似于编程语言中的类/类型和变量/实例,只不过K8S中用yaml就可以定义两者。Resource Definition为了避免命名冲突,有一个APIVersion来表明是哪里来的(Group)、什么版本的API(Version),类似于编程语言中的包、模块或者命名空间。
APIServer自带一部分可以让K8S work Out of box的资源定义,APIVersion通常是<version>
或者*.k8s.io/<version>
。比如说v1
的Pod
或者rbac.authorization.k8s.io/v1
的RoleBinding
。其他的Resource Definition一般叫Custom Resource Definition,APIVersion不和K8S的冲突就好,比如说cert-manager中cert-manager.io/v1
的CertificateRequest
。
一般创建了Resource Definition,K8S会村到etcd里面。对Resource做一些操作后(增加删除修改),由一个Controller来根据Resource做出响应。比如说我们增加了Ingress,Nginx Ingress Controller就会更新Nginx的配置。对于大部分的K8S APIServer自带的API,API Server就是Controller,会和相关的组件进行交互,比如说操作Pod啊什么的。
Kubernetes的API可以从自动生成的文档查到。
Custom Resource
APIServer自带的Resource Definition以外,用户建立的Resource Definition都叫
CRDs
,想知道自己的集群有哪些crd的话,kubectl get crds
就行。
Programming Kubernetes给了一个例子是At,长这个样子:
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
但是这个例子有点老了,在新的K8S集群上会警告。
$ kubectl apply -f at-crds.yaml
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/ats.cnat.programming-kubernetes.info created
v1beta1的CustomResourceDefinition在Kubernetes 1.16中已经被标记不推荐,不是不能用,我就是喜欢新的。查了一下文档,稍微改一下就好了,区别不大。v1中subresources这个field在versions中,并且默认可以不写。
apiVersion: apiextensions.k8s.io/v1
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
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
定义好crd以后,就可以正常定义具体的cr了:
apiVersion: cnat.programming-kubernetes.info/v1alpha1
kind: At
metadata:
name: cnrex
spec:
schedule: "2019-07-03T02:00:00Z"
containers:
- name: shell
image: centos:7
command:
- "bin/bash"
- "-c"
- echo "Kubernetes native rocks!"
用kubectl get ats
也可以获取到资源
$ kubectl get ats.cnat.programming-kubernetes.info
NAME AGE
cnrex 12m
其中有两个问题:
- 一个是在crd中,我们并没有规定fields长什么样子,所以At的spec可以容纳任意类型的数据,但是我们一个类型一定是有一个特定的结构的,每个部分有自己的类型,所以看起来不大行,我们需要定义一下类型。
- 另一个问题是我们只是定义了CRD,有一个CR,但是实际上定义了就定义了,就跟摆在墙角的小白菜一样没有人理会,不会有任何影响,谁都不处理它,我们需要想办法让集群对这个资源做出响应。
Schema
刚才提到RD/CRD属于Schema Level,因为类型的定义要定义信息的结构,也就是Schema。CRD用OpenAPI。给At加上Schema以后大概长这个样子:
apiVersion: apiextensions.k8s.io/v1
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
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
required:
- schedule
properties:
schedule:
type: string
pattern: "^\\d{4}-([0]\\d|1[0-2])-([0-2]\\d|3[01])..."
containers:
type: array
items:
type: object
required:
- name
- image
- command
properties:
name:
type: string
image:
type: string
command:
type: array
items:
type: string
type: object
status:
type: object
通过schema就可以定义一个对象应该长什么样子了。如果这个时候我们不按照定义写At,kubectl就会验证不通过。
比如说我们少写了schedule,就会报错:
error: error validating "test.yaml": error validating data: ValidationError(At.spec): missing required field "schedule" in info.programming-kubernetes.cnat.v1alpha1.At.spec; if you choose to ignore these errors, turn validation off with --validate=false
或者schedule字符串不满足pattern定义的正则:
The At "cnrex" is invalid: spec.schedule: Invalid value: "2019-07-0qwer3T02:00:00Z": spec.schedule in body should match '^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])...'
Printer Columns
但是kubectl get ats的时候感觉很冷……什么信息都没有:
kubectl get ats.cnat.programming-kubernetes.info
NAME AGE
cnrex 4m8s
根据K8S文档,加上additionalPrinterColumns就好了
# apiVersion: apiextensions.k8s.io/v1
# 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
versions:
- name: v1alpha1
additionalPrinterColumns:
- name: Schedule
type: date
description: Time to Execute
jsonPath: .spec.schedule
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
# served: true
# storage: true
# schema:
# openAPIV3Schema:
# type: object
# properties:
# apiVersion:
# type: string
# kind:
# type: string
# metadata:
# type: object
# spec:
# required:
# - schedule
# properties:
# schedule:
# type: string
# pattern: "^\\d{4}-([0]\\d|1[0-2])-([0-2]\\d|3[01])..."
# containers:
# type: array
# items:
# type: object
# required:
# - name
# - image
# - command
# properties:
# name:
# type: string
# image:
# type: string
# command:
# type: array
# items:
# type: string
# type: object
# status:
# type: object
加上additionalPrinterColumns以后,kubectl get ats
就会变成
kubectl get ats
NAME SCHEDULE AGE
cnrex 2019-07-03T02:00:00Z 10m
如果Schedule的type设置为date,就会是(写博客时,是2020年1月13日)
kubectl get ats
NAME SCHEDULE AGE
cnrex 559d 11m
Controller
我们要解决的另一个问题就是,这个at资源我们建好以后,k8s是不认识的,我们需要有一个程序通过API去处理这类资源。
一般这种可以和API直接交互的app,我们叫做Controller。Ingress Controller就是一个例子,它可以处理K8S中的Ingress资源,输入Ingress、输出Nginx配置和对应的Service。Controller换个说法也是APIServer的客户端。
这玩意儿说来话长。我们过两天再搞。