V tomto článku sa pozrieme na to, ako zadefinovať objekt na strane Kubernetesu, a to pomocou CRD - Custom Resource Definition.
Je to podobné ako s databázou. Kym si nezadefinujeme tabuľku a jej štruktúru, nemôžeme do nej INSERT-ovať alebo SELECT-ovať dáta. Namiesto tabuliek Kubernetes API umožňuje definovať typy objektov, a to práve pomocou Custom Resource Definition - CRD.
Vráťme sa v rýchlosti k minulému článku. V Go som si zadefinoval môj vlastný typ
Starfighter
. Ten som vedel serializovať a deserializovať na štandardný
Kubernetes YAML manifest. Čo by sa ale stalo, ak by som tento YAML aplikoval
cez kubectl apply
?
$ kubectl apply -f - << EOF
apiVersion: starwars.okontajneroch.sk/v1
kind: Starfighter
metadata:
name: x-wing-1
namespace: default
spec:
faction: rebellion
type: x-wing
pilot: Luke Skywalker
EOF
error: resource mapping not found for name: "x-wing-1" namespace: "default" from "./xwing.yaml": no matches for kind "Starfighter" in version "starwars.okontajneroch.sk/v1"
ensure CRDs are installed first
Kubernetes totiž ešte nema znalosť o tomto objekte. Pre svoj Starfighter
potrebujem vytvoriť a aplikovať jeho CRD - Custom Resource Definition. V ňom
popíšem štrukturu dát, aké má mať vlastnosti a aké akcie nad objektom môžem
vykonávať. CRD nieje nič iné ako ďalší resource, podobne ako Pod
alebo
Deployment
. CRD pre môj Starfighter
bude vyzerať následovne:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: starfighters.starwars.okontajneroch.sk
spec:
group: starwars.okontajneroch.sk
scope: Namespaced
names:
plural: starfighters
singular: starfighter
kind: Starfighter
shortNames:
- sf
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
faction:
type: string
pilot:
type: string
type:
type: string
status:
type: object
properties:
phases:
items:
type: string
type: array
Trojica parametrov apiVersion
, kind
a metadata
su chronický známa. To čo
treba zmieniť je, že parameter name
by mal dodržiavať konvenciu
<množnéčíslo>.<skupina>
, a to bez verzie. V mojom prípade ide o starfighters.starwars.okontajneroch.sk
.
Opäť ide o inú formu zápisu GVR/GVK.
V časti spec
sa nachádza niekoľko parametrov. Parameter group
je skupina,
ktorej typ objektu bude patriť. Parameter scope
môže mať hodnoty Namespaced
alebo Cluster
. To určuje, či objekt bude musieť byť súčasťou nejakého menného
priestoru, alebo bude definovaný pre celý cluster. Od tohto sa odvýja aj samotný
endpoint objektov.
Potom nasleduje names
, kde definujem rôzne formy mena môjho typu. Formy
singular
a plural
používa Kubernetes API na registráciu endpointov. No
častejšie sa s tymito menami stretávame pri kubectl
. Napríklad
kubectl get starfighters
, alebo kubectl describe starfighter
.
Parameter kind
už asi nepotrebuje nejake extra vysvetlovanie. Meno by malo byť jednodné číslo, a malo by začínať veľkým písmenom.
Posledným parametrom je shotNames
. Ide o zoznam tzv. skratených mien. Skrátene mená su známe opäť pri kubectl
. Ide o pomôcku aby sme nemuseli vypisovať dlhé mená. Napríklad namiesto kubectl get HorizontalPodAutoscalers
mi stačí použiť kubectl get hpa
.
Časť versions:
je najkomplexnejšia. CRD môže definovať viacero verzii. Každá verzia ma svoju schému. Schéma definuje samotnú štrukturu objektu a je v CRD definovaná pomocou Open API špecifikácie. Kubernetes podporuje Open API verzie 2 a 3. Odporúča sa ale používať Open API v3. Preto aj ja používam openAPIV3Schema
. V tejto časti sa definuje samotná štruktúra objektu, čiže to, čo bude tvoriť spec
môjho Starfighter
objektu.
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
faction:
type: string
pilot:
type: string
type:
type: string
Tu je vidieť, že spec
bude mať 3 parametre: faction
, pilot
a type
.
Všetky parametre sú typu ľubovolný string
, bez nejakej dodatočnej validácie.
Pre definovanie pokročilejších štruktúr doporučujem zabdrnuť do dokumentácie k
Open API.
Ďalšia časť je status
z jediným parametrom phases
. Tento parameter je pole reťazcov.
status:
type: object
properties:
phases:
items:
type: string
type: array
To ako presne vytvoriť komplexnejšie štruktuy je mimo Kubernetesu a bolo by to na samotný článok. Ako som už vyžšie spomínal, CRD sa spolieha na OpenAPI V3 špecifickáciu. Preto doporučujem zabdrnuť do dokumentácie k Open API. Napríklad [schema object](1. Schema Object)
Takto vytvorené CRD použijem pomocou kubectl apply
:
$ kubectl apply -f ./starfighter_crd.yaml
Po tomto kroku cluster už bude poznať objekt Starfighter
. Overiť si to môžem pomocou kubectl api-resources
:
$ kubectl api-resources --api-group='starwars.okontajneroch.sk'
NAME SHORTNAMES APIVERSION NAMESPACED KIND
starfighters sf starwars.okontajneroch.sj/v1 true Starfighter
$ kubectl apply -f - << EOF
apiVersion: starwars.okontajneroch.sk/v1
kind: Starfighter
metadata:
name: x-wing-1
namespace: default
spec:
faction: rebellion
type: x-wing
pilot: Luke Skywalker
EOF
Generovanie CRD z Go typov
Existuje ale aj možnosť - vlastne 2 možnosti, ako vygenerovať CRD z môjho Go kódu. A to sú nástroje controller-gen
, ktorý patri pod projekt Kubebuilder, a code-generator
, ktorý je priamo nástrojom Kubernetesu. Ja pôjdem cestou controller-gen
, kedže je jednoduchší. Nainštalujem si ho pomocou go install
:
$ go install sigs.k8s.io/controller-tools/cmd/controller-gen
V ďalšiom kroku označím pomocou markera +kubebuilder:object:generate=true
ten typ, pre ktorý chcem generovať CRD. V súbore ./api/v1/types.go
pridám marker pre Starfighter
:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:generate=true
type Starfighter struct {
...
}
Generátor potrebuje ešte vedieť, do akej skupiny (group
) budú objekty patriť. To nastavím pre celú knižnicu a to tak, že opäť pridam špecialný komentár do api/v1/doc.go
:
// +k8s:deepcopy-gen=package
// +groupName=starwars.okontajneroch.sk
package v1
Pre takto upravený kód teraz môžem spustiť controller-gen
, ktorý sa postará o vygenerovanie potrebných CRD:
$ controller-gen crd paths=./api/... output:crd:dir=./k8s
Ak všetko prebehlo správne, tak v adresári ./k8s
najdem vygenerovaný súbor starwars.okontajneroch.sk_starfighters.yaml
, ktorého obsahom bude práve CRD pre môj typ Starfighter
:
$ cat ./k8s/`starwars.okontajneroch.sk_starfighters.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.13.0
name: starfighters.starwars.okontajneroch.sk
spec:
group: starwars.okontajneroch.sk
names:
kind: Starfighter
listKind: StarfighterList
plural: starfighters
singular: starfighter
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
...
Ide o trochu ukecanejšiu verziu CRD, ktorý sme si pred tým ručne napisali. Ak sa však pozornejšie pozrieme na CRD, tak napríklad momentálne mi chyba shortNames
. Ako povedať controller-gen
aby vygeneroval CRD s krátkym menom sf
? Stačí opäť môj typ obohatiť o špecialný komentár:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:generate=true
// +kubebuilder:resource:shortName={"sf"}
type Starfighter struct {
...
Takýchto markerov, ktoré vedia ovplyvniť generovanie CRD je viacero. Celý zoznam sa dá pozrieť na https://book.kubebuilder.io/reference/markers/crd.
Generovanie Go typov z CRD
Predstavme si teraz inú situáciu: Mam k dispozícii už hotové CRD, napríklad CRD operátora, ktorý bol napísaný v inom jazyku. A ja by som chcel robiť nejakú automatizáciu v Go. Potrebujem teda z CRD vygenerovať Go typy.
Treba hneď na začiatok povedať, že toto nieje štandardný prípad. Takýmto prístupom chcem zasahovať do objektov, ktoré patria úplne inému programu. Tu ja osobne nepoznám nejake spoľahlivé a jednoduché riešenie. Preto ak môžem, tak sa takémuto prípadu snažim vyvarovať.
Ale rozoberiem aspoň tie spôsoby, ktoré su mne známe.
Asi najlepšie je nájsť Go knižnicu s typmi a jednoducho si ju pridať do svojho projektu ako závislosť.
Druhou dobrou možnosťou je napísať si typy ručne. Toto je však vhodné len pre objekty s jednoduchou štrukturou. Pri komplikovanejších objektoch to môže byť problém.
Inou možnosťou je, si nechať typy vygenerovať cez generátor crd-codegen
od RedHatu. Tu však musím upozorniť, že tento projekt je experimentálny a v dobe písania tohto článku, 3 roky neudržiavaný.
No a posledná z možnosti je pozerať na CRD ako Open API. Čiže prekonvertovať CRD YAML na OpenAPI, a následne aplikovať OpenAPI generátor (napríklad oapi-codegen
), ktorý vygeneruje Go typy.
Uvedené možnosti dobre ilustruju fakt, že ide o neštandardný prípad. To je ale môj osobný prístup. Samozrejme ak niekto pozná lepší, spoľahlivejší a jednoduchší spôsob ako vygenerovať typy z CRD, budem rád ak mi napiše.
Záver
Pre mnohých zrejme CRD nieje nič nové. Stretol sa s nim asi každý, čo sa o trošku viac šuchol o Kubernetes. Tento článok bol taka povinná jazda. Teraz už mám môj fitkívny objekt Starfighter
k dispozícii ako typ v Go, tak aj na strane klastra. Ďalší krok je teda logicky samotná komunikácia s Kubernetes API, čiže sa pozriem na Client-Go knižnicu.
Tak ako aj v minulom článku, kompletný kód a projekt je dostupny na GitHube github.com/okontajneroch
Ak sa ti článok páčil a chceš ma podporiť, tak budem rád za lajk, zdielanie alebo followovanie na LinkedIn. Môj LinkedIn je tiež otvorený každému, kto má akukoľvek otázku k tejto téme.