Kube API je asi najdôležitejšiou komponentou Kubernetes architektúry. Ako toto API môžme používať? Aké koncepty API dizajnu nás môže naučiť?
O jeho dôležitej úlohe som už písal v článku o Kubernetes architektúre. V tomto článku sa pozrieme na API skôr prakticky a povieme si niečo bližšie o konceptoch ako server-side apply alebo watching. Tieto znalosti sa hodia ak plánujete robiť automatizáciu pre Kubernetes, alebo sa chcete venovať operátorom. Tieto koncepty môžu byť zaujimavé aj pre programátorov, ktorí sa venujú API dizajnu.
Kube API je HTTPS server, ktorý vystavuje endpointy, kde môžem posielať HTTP požiadavky. HTTP požiadavky môžu byť typu GET
, POST
, PUT
, PATCH
a DELETE
. Požiadavky ale aj odpovede môžu mať formu YAML, JSON. V špecialných prípadoch sa používa Protobuf. To, aký formát chcem používať špecifikujeme klasicky pomocou Content-Type
v HTTP hlavičke. Dá sa povedať, že ide o klasické RESTovské API.
Skôr ako začnem komunikovať s Kube API, budem potrebovať nejaký ten cluster. Ja si vytvorím môj obľúbený Kind cluster ako democluster.yaml
:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "127.0.0.1"
apiServerPort: 6443
Cluster následne spustím príkazom:
$ kind create cluster --config ./democluster.yaml
Cluster by mi mal bežať a Kube API bude dostupné na porte 6443
.
Prvý dotaz
Teraz môžem odoslať svoju prvú jednoduchú požiadavku:
$ curl -k https://localhost:6443
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
Výsledkom je HTTP kód 403
, čo znamená neautorizovaný prístup. Všetky endpointy prechádzajú procesom autentifikácie a autorizácie. Autorizácia a autentifikácia je téma na samostatný článok. Existuje niekoľko možnosti ako potešiť autorizáciu. Pre jednoduchosť budem ďalej používať kubectl proxy
, ktorá za mna vyrieši celý proces autorizácie.
kubectl proxy --port=8080
Ku Kube API teraz môžem beztrestne pristupovať skrz HTTP a port 8080
. Zopakujem teda požiadavku, ale teraz cez proxy:
$ curl http://localhost:8080
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
"/apis/admissionregistration.k8s.io/v1",
"/apis/apiextensions.k8s.io",
"/apis/apiextensions.k8s.io/v1",
"/apis/apiregistration.k8s.io",
"/apis/apiregistration.k8s.io/v1",
...
Môj dotaz vrátil zoznam endpointov pre Kube API.
Organizácia endpointov
Zo zoznamu sa dá odpozorovať, že endpointy maju svoju štruktúru organizácie. Základom sú 2 veľké skupiny:
/api/v1/*
- základna skupina, ktorá sa označuje aj ako pôvodná (legacy). Tu sa nachádzaju enpointy pre základné objekty akoPod
,Node
,Namespace
alebo ajConfigMap
./apis/*
Skupina, kde je sú všetky ostatné objekty. Sem napríkald patria aj objekty, ktoré by človek nejak intuitívne priradil do základnej skupiny. NapríkladDeployment
.
Ďalej sa cesta endpointu skladá z:
/apis/{group}/{version}/namespaces/{namespace}/{resource}
Časť {group}/{version}
reflektuje to, čo sa nachádza v manifestoch na začiatku ako apiVersion
. Všetky objekty vo svete Kubernetesu sú organizované vo svojich skupinách a su verzované. Meno skupiny používa bodkovú notáciu (príklad networking.k8s.io
). Pre základné objekty v /api/v1/
sa táto časť vynecháva. Tie niesu súčasťou žiadnej skupiny.
Následuje namespaces/{namespace}
. Ako je asi známe, objekty môžu patriť do nejakého menného priestoru (namespaced), alebo môžu byť dostupné pre celý cluster (cluster-wide). Tu platí, že pre objekty dostupné v celom clustri (cluster-wide) sa táto časť sa vynecháva.
V časti {resource}
zase platí, že sa tu používa názov typu objektu v množnom čísle. Pre Ingress
to bude */namespaces/{namespace}/ingresses
. To je preto, lebo samotný endpoint vracia list objektov. Ak chcem pracovať s konkrétnym objektom, tak endpoint vyskladám ako */{resource}/{name}
.
Ako to celé bude vyzerať v praxi? Nahodim si nejaký jednoduchý dummy
Ingress objekt do default
menného priestoru. Aby som tam niečo mal. Použijem klasicky kubectl:
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dummy
namespace: default
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
EOF
Ingress je v skupine networking.k8s.io
s verziou v1
. Čiže celý názov je networking.k8s.io/v1
. Povedzme že chcem zoznam všetkých ingress objektov pre default
. Endpoint bude mať tvar /apis/networking.k8s.io/v1/namespaces/default/ingresses
.
$ curl http://localhost:8080/apis/networking.k8s.io/v1/namespaces/default/ingresses
{
"kind": "IngressList",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"resourceVersion": "61619"
},
"items": [
{
"metadata": {
"name": "dummy",
"namespace": "default",
...
},
]
}
Kube API mi vráti IngressList
, kde v časti items
nájdem zoznam Ingress objektov pre default
menný priestor. V mojom prípade to je len tento jeden objekt.
Ak budem chcieť robiť niečo s konkrétnym objektom, tak použijem pri konštrukcii endpointu jeho meno, čiže apis/networking.k8s.io/v1/namespaces/default/ingresses/dummy
$ curl http://localhost:8080/apis/networking.k8s.io/v1/namespaces/default/ingresses/dummy
{
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"name": "dummy",
"namespace": "default",
...
Kube API mi teraz vracia namiesto listu konkrétny Ingress
objekt.
Doteraz sme sa bavili o skupine /apis/*
. Ale čo pôvodna skupina /apis/v1/*
?Ako som spomínal, v tomto prípade platia rovnaké pravidlá konštrukcie endpointov, akurát tu odpadá skupina, pretože je ňou samotná /apis/v1
. Čiže list Podov v mennom priestore kube-system
získam cez endpoint /api/v1/namespaces/kube-system/pods
curl http://localhost:8080/api/v1/namespaces/kube-system/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "65914"
},
"items": [
{
"metadata": {
"name": "coredns-5d78c9869d-d5kwt",
"generateName": "coredns-5d78c9869d-",
"namespace": "kube-system",
...
...
]
}
To isté platí aj pre konrétny pod. Endpoint bude napríklad api/v1/namespaces/kube-system/pods/coredns-5d78c9869d-d5kwt
:
curl http://localhost:8080/api/v1/namespaces/kube-system/pods/coredns-5d78c9869d-d5kwt
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "coredns-5d78c9869d-d5kwt",
"generateName": "coredns-5d78c9869d-",
"namespace": "kube-system",
...
Vytvorenie a mazanie objektov
Posuňme sa ale k niečomu zaujímavejšiemu. Doteraz som listoval a GET
-oval. Ako ale môžem objekt vytvoriť? Stačí poslať POST
požiadavku na skupinu objektov. Povedzme že chcem vytvoriť nový Deployment
. Aby som z toho mal viac Kubernetes pocit, tak namiesto JSON budem používať YAML.
curl -X POST http://localhost:8080/api/v1/namespaces/default/pods \
-H 'Content-Type: application/yaml' \
--data-binary @- << EOF
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
Výsledkom bude nový Pod
objekt:
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "demo",
"namespace": "default",
"uid": "502f0798-349c-4ab9-af82-45f814535920",
"resourceVersion": "1137",
...
Overiť to môžeme aj cez kubectl
:
$ kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
demo 1/1 Running 0 62s
Zmazať objekt je taktiež triviálna HTTP požiadavka typu DELETE na konkrétny endpoint Podu. V tomto prípade to bude:
curl -X DELETE http://localhost:8080/api/v1/namespaces/default/pods/demo
Modifikácia objektov
Na modifikáciu objektov môžem použiť 2 typy HTTP požiadaviek - PUT
a PATCH
. Kym v prípade PUT
posielame celý objekt, tak pomocou PATCH
viem zmeniť len určitú časť objektu.
Skôr ako sa pustím do zmeny objektu, tak si najprv nejaký použiteľný vytvorím. Nepoužijem Pod. Ten sa nedá meniť - je tzv. immutable. Použijem napríklad Deployment
:
curl -X POST http://localhost:8080/apis/apps/v1/namespaces/default/deployments \
-H 'Content-Type: application/yaml' \
--data-binary @- << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
$ kubectl get deployments -n default
NAME READY UP-TO-DATE AVAILABLE AGE
demo-deployment 1/1 1 1 22s
Mám vytvorený Deployment
s názvom demo-deployment
a počtom replík 1
. Ako zmenim počet replík na 3
? Na endpoint tohto konkrétneho objektu pošlem PUT
požiadavku z celým objektom, kde bude zmenená hodnota replicas
:
curl -X PUT http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment \
-H 'Content-Type: application/yaml' \
--data-binary @- << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
PUT
má tú výhodu(alebo nevýhodu), že dochádza k zmene celého objektu. Ak chcem zmeni+t objekt, tak najprv musím získať stav objektu pomocou GET
, modifikovať parameter replík a opäť pomocou PUT
odoslať zmenený ale celý stav objektu. Túto metódu používa napríklad kubectl replace
.
Čo ak nechcem zmeniť celý objekt? Aby som nemusel ťahať kopec dát po sieti hore-dole, tak Kube API poskytuje možnosť parcialne zmeniť dáta objektu. K tomu slúži PATCH
metóda. Ako to funguje? Na začiatok skusim jednoduchú zmenu replík naspäť na 1
:
curl -X PATCH http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment \
-H 'Content-Type: application/strategic-merge-patch+json' \
--data-binary @- << EOF
{
"spec": {
"replicas": 1
}
}
EOF
Pozorné oko si všimlo, že Content-Type
je nastavený na application/strategic-merge-patch+json
. Parciálna zmena je jednoduchá pre parametre jednoduchých typov. Ale čo ak chcem spraviť zmenu v parametri, ktorý je poľom? Napríklad čo sa udeje ak by môj PATCH
vyzeral takto:
{
"spec": {
"containers": [
{
name: "nginx",
image: "nginx:latest"
}
]
}
}
Takýto PATCH
môže znamenať 2 veci. Buď chcem zmeniť existujúci kontajner, alebo možno že chcem pridať nový kontajner do poľa.
Samozrejme pre človeka je jasné, že ide o zmenu existujúceho kontajnera. Lenže stroj to nemá ako vedieť a treba mu to nejak povedať. Je niekoľko štandardov, ktoré riešia konflikty dát. Napríklad SON Patch (RFC 6902). Kube API preto podporuje niekoľko typov PATCH-ovania. Preto je potrebné určiť o ktorý typ pôjde skrz Content-Type
. V mojom prípade som použil Strategic Merge Patch. Je to akási forma automatického patchovania.
PATCH
je oproti PUT
pomerne komplexná metóda s rôznymi možnostami. Drviva väčšina controllerov a operátorov svoje zmeny realizuje práve pomocou PATCH
metódy. Dokonca aj legendárny kubectl apply -f
používa PATCH
metódu pre už existujúci objekt.
Konflikty pri súbežnom spracovaní
Zoberme si takú klasickú situáciu. Chcem zmeniť počet replík. Nie však parciálne ale tak, že si najprv získam aktuálny stav objektu pomocou GET
požiadavky. Potom zmenim hodnotu replík a odošlem modifikovaný objekt pomocou PUT
požiadavky.
Čo ale v situácii, keď niekto medzičasom zmenil môj objekt? V databázach je to učebnicová situácia konfliktu súbežného spracovania. Tento konflikt sa rieši pomocou dvoch mechanizmov - Optimistický a pesimistický zámok.
Kube API používa svoju REST variantu optimistického zámku. Každý objekt má vo svojích metadata
okrem iného aj resourceVersion
. Ide o verziu objektu. Ak chcem predísť konfliktom, tak by som zmenu mal robiť tak, že získam pomocou GET
požiadavky nielen stav objektu ale aj jeho resourceVersion
.
$ kubectl get deployment demo-deployment -o json | jq -r .metadata.resourceVersion
3080
V mojom prípade je posledná verzia 3080
. Ak by som vytvoril PUT požiadavku, s resourceVersion: 3080
, tak požiadavka prejde. Ja si však nasimulujem situáciu, že niekto iný zmenil počet replík v objekte.
$ kubectl scale --replicas=2 deployment/demo-deployment
$ kubectl get deployment demo-deployment -o json | jq -r .metadata.resourceVersion
4787
Parameter resourceVersion
sa teraz zmenil na hodnotu 4787
. Čo sa stane, keď pošlem PUT
požiadavku s pôvodným resourceVersion
?
curl -X PUT http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment \
-H 'Content-Type: application/yaml' \
--data-binary @- << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
resourceVersion: "3080"
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
Výsledkom bude chybová správa o tom, že objekt bol medzitým zmenený:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Operation cannot be fulfilled on deployments.apps \"demo-deployment\": the object has been modified; please apply your changes to the latest version and try again",
"reason": "Conflict",
"details": {
"name": "demo-deployment",
"group": "apps",
"kind": "deployments"
},
"code": 409
}
Toto bol jednoduchý príklad optimistického zámku v Kube API. Toto však nieje jediný a mechanizmus ako môžeme riešiť rôzne konflikty. Rozhodne to nieje ani všeliek na konflikty. Kube API preto poskytuje aj iné, menej “štandardné” mechanizmy.
Server-side apply
Zatial je Kube API klasické RESTovské API. Základne operácie sa nelíšia od žiadného iného štandardného REST API. Poďme sa ale pozrieť na menej štandardné koncepty Kube API.
Server-side apply (ďalej len SSA) je koncept, ktorý tiež rieši konflikty. Okrem toho ale aj zlepšuje dohľadateľnosť - čiže kto, čo zmenil. Na SSA môžeme pozerať ako na akúsi formu pesimistického zámku.
Zamikať nebudem celý objekt, ale len jeho určité časti.A to tak, že parametrom ktore mením nastavím tzv. field manažéra. Field manažér je informácia o tom, kto vlastní, a teda môže meniť dané parametre.
To aký parameter je vlastnený akým field manažérom viem pozrieť cez metadata.managedFields
. Tu nájdem nielen ktoré parametre vlastní ktorý manažér, ale aj kedy robil poslednú zmenu a aka to zmena bola.
Skúsim sa pozrieť na metadata.managedFields
môjho demo-deployment
:
$ kubectl get deployment demo-deployment -o json --show-managed-fields | jq ".metadata.managedFields"
[
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
...
},
"manager": "curl",
"operation": "Update",
"time": "2023-08-12T19:37:14Z"
},
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
...
"f:status": {
...
}
},
"manager": "kube-controller-manager",
"operation": "Update",
"subresource": "status",
"time": "2023-08-12T19:37:14Z"
}
Bežne mi kubectl
túto informáciu nevracia, aby som ju však videl, tak som použil --show-managed-fields
.
Na výstupe vidím, že takmer všetky parametre maju nastavený field manažér curl
. Jedine status
má kube-controller-manager
. To je preto, lebo o celú biznis logiku Deployment
objektov sa stará práve Controller manager.
Ako to funguje teda Server-side apply? Ako nastav9m field manažéra?
Na to mi stači použiť PATCH
metódu s Content-Type
nastaveným na application/apply-patch+yaml
. V URL nastavím dvojicu parametrov ?fieldManager=okontajneroch&force=true
. To zabezpečí, že parametre, ktoré mením budú mať môjho nového field manažéra:
curl -X PATCH http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment?fieldManager=okontajneroch&force=true \
-H 'Content-Type: application/apply-patch+yaml' \
--data-binary @- << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
spec:
replicas: 2
EOF
Zmena replík bola opäť úspešne vykonaná. Pozriem sa na metadata.managedFields
kubectl get deployment demo-deployment -o json --show-managed-fields | jq ".metadata.managedFields"
[
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:replicas": {}
}
},
"manager": "okontajneroch",
"operation": "Apply",
"time": "2023-08-12T19:48:02Z"
},
...
]
Okrem curl
a kube-controller-manager
mi pribudol nový záznam pre okontajneroch
. Tento field manažér teraz kontroluje parameter replicas
. To znamená, že tento parameter budem môcť zmeniť len pomocou SSA s field manažérom okontajneroch
.
Skusim znovu zopakovať Server-side apply, ale teraz s iným field manažérom a bez force=true
:
curl -X PATCH http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment?fieldManager=foo& \
-H 'Content-Type: application/apply-patch+yaml' \
--data-binary @- << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
spec:
replicas: 1
EOF
Kubernetes API identifikuje, že tento parameter patrí inému field manažérovi a tak zmenu nevykoná a vyhlási konflikt:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Apply failed with 1 conflict: conflict with \"okontajneroch\": .spec.replicas",
"reason": "Conflict",
"details": {
"causes": [
{
"reason": "FieldManagerConflict",
"message": "conflict with \"okontajneroch\"",
"field": ".spec.replicas"
}
]
},
"code": 409
}%
Skúsim teraz zmeniť parameter ale pomocou strategic merge patch.
curl -X PATCH http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment \
-H 'Content-Type: application/strategic-merge-patch+json' \
--data-binary @- << EOF
{
"spec": {
"replicas": 1
}
}
EOF
Zmena prešla. To znamená, že táto kontrola funguje len ak zmeny sú realizované pomocou Server-side apply. Zaujíma ma, čo sa stalo s replicas
a jeho field manažérom. Pozriem sa na metadata.managedFields
.
$ kubectl get deployment demo-deployment -o json --show-managed-fields | jq ".metadata.managedFields"
[
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:replicas": {},
...
}
...
},
...
]
Parameter sa vrátil pod curl
field manažéra.
Watching
Posledným konceptom ktorý chcem predstaviť je watch. Vďaka tomuto mechanizmu môžeme sledovať a reagovať na zmeny v objektoch.
V článku o Kubernetes architektúre) som písal, že Kubernetes je hladinovo riadený systém. Prečo potom potrebujeme takýto mechanizmus? Watch totiž zamedzuje neustálemu dotazovaniu sa na stav objektov cez GET
. To by spôsobovalo výkonnostné problémy na strane Kube API. Watching tak doplňa štandardný GET
a spolu tvoria spoľahlivý spôsob riadenia.
Z pohľadu API watching funguje veľmi jednoducho. Medzi klientom a Kube API sa vytvorí trvalé HTTP spojenie, cez ktoré server posiela udalosti o zmenách. Ak sa spojenie z akéhokoľvek dôvodu ukončí, klient môže pokračovať od poslednej resourceVersion
ktorú spracoval.
Niektorí z vás v tom asi teraz spozorovali event sourcing. A nieje to ďaleko od pravdy. Je tu však jedná vec. V Kubernetese je okno udalosti ale limitované a história udalosti po určitom čase zmizne. Veľmi rýchlo môžem dostať 410 Gone
. Človek tak nemôže spraviť rekonštrukciu historie ako to event sourcing sľubuje.
Poďme sa ale pozrieť na jednoduchý watching v akcii.
Najprv si ziskam stav môjho demo
objektu z predchadzajúceho experimentovania. Potrebujem poznať resourceVersion
. Potrebuje totiž povedať Kube API odkiaľ začnem sledovať zmeny:
$ curl -s http://localhost:8080/apis/apps/v1/namespaces/default/deployments/demo-deployment | jq ".metadata.resourceVersion"
"575288"
Vytvorím si dlhodobé HTTP spojenie, cez ktoré mi budu prichádzať zmeny pre objekt s resourceVersion
.
curl -s http://localhost:8080/apis/apps/v1/namespaces/default/deployments?watch=true&resourceVersion=575288
Výsledkom toho by nemala byť žiadna odpoveď. Môj curl
bude bežať s otvoreným spojením. Program necham takto bežať a v druhom terminály spravím zmenu.
kubectl scale --replicas=3 deployment/demo-deployment
Môj curl
, kde som nadviazal watch spojeni teraz bude obsahovať sériu JSON dát. Každý JSON reprezentuje nejaku zmenu:
{
"type":"MODIFIED",
"object": {
"kind": "Deployment",
"metadata": {
"name":"demo-deployment",
},
...
}
}
{
"type":"MODIFIED",
"object": {
"kind": "Deployment",
"metadata": {
"name":"demo-deployment",
},
...
}
}
{
"type":"MODIFIED",
"object": {
"kind": "Deployment",
"metadata": {
"name":"demo-deployment",
},
...
}
}
Ide o jednoduchý JSON, kde type
hovorí o tom, aka zmena nastala a potom v object
mam samotný zmenený objekt.
Prečo zmena replík vyvolá viacero udalosti? Toto je zaujimavé sledovať. Napovie nám to ako presne funguje škálovanie v Kubernetese. Tak prvá udalosť bola moja zmena replík.
{
"type": "MODIFIED",
"object": {
"spec": {
"replicas": 3
}
"status": {
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
}
}
}
Za ňou však nasleduje séria udalosti zmien, ktoré vykonal controller-manager
. Ten totiž na zmenu replík zareaguje tak, že začne meniť počet Podov aby sedel s nastavením replík Deployment
objektu. Komponent controller-manager
priebežne modifikuje status
časť môjho demo-deployment
, až kým sa nedosiahne stav replík 3
.
{
"type": "MODIFIED",
"object": {
...
"status": {
"replicas": 3,
"updatedReplicas": 3,
"readyReplicas": 1,
"availableReplicas": 1
...
}
}
}
{
"type": "MODIFIED",
"object": {
...
"status": {
"replicas": 3,
"updatedReplicas": 3,
"readyReplicas": 2,
"availableReplicas": 2,
. "unavailableReplicas": 1
...
}
}
}
{
"type": "MODIFIED",
"object": {
...
"status": {
"replicas": 3,
"updatedReplicas": 3,
"readyReplicas": 3,
. "availableReplicas": 3
...
}
}
}
Bookmark
Čo sa týka typu samotných udalosti, tak tu môžem očakávať ADDED
, MODIFIED
, DELETED
a BOOKMARK
. Prvé tri asi netreba vysvetlovať. Je tu ale BOOKMARK
. Čo robí?
Vyžšie som spomínal, že ak dôjde k prerušeniu spojenia, kód ktorý spracováva udalosti by mal pokračovať ďalej od poslednej resourceVersion
. Toto je však veľmi fragmentované pre samotný Kube API.
Kube API potrebuje nejaký bod v histórii, kde si môže povedať - OK tak tieto udalosti som už poslal klientskému kódu a boli spracované. Následne môže robiť svoje interné veľmi potrebné optimalizácie. A práve to je úlohou BOOKMARK
typu. Kube API vlastne takto oznamuje klientskému kódu: spolieham sa že si všetky udalosti spracoval.
Je tu však niečo, čo na prvý pohľad môže prekážať. Klientský kód nemá žiaden mechanizmus ako potvrdiť, že udalosti boli spracované. To, kedy sa generuje BOOKMARK
je plne v réžii Kube API. Aby to bolo ešte zaujimavejšie, tak to nieje ani žiaden fixný časový interval. Celý mechanizmus udalosti je veľmi jednosmerný a klientský kód sa musí len prispôsobiť.
Na druhej strane toto je aj pochopitelné. Treba si uvedomiť, že Kube API je srdcom našich clustrov. Zo sieťou je doslova achylovou pätou Kubernetesu. Jeho výkonnostné problémy vedia značne ovplyvniť stabilitu celého clustra. Watching je len jeden kúsok v skladačke udalostí. Systém udalosti ide cez všetky vrstvy - od Etcd, cez server, až po správne implementovaný klientský kód. Život udalosti by si však zaslúžil vlastný článok. Možno niekedy.
Terminológia
Tento článok ukončím trochu netradične - mojou osobnou veľkou dilemou. Písanie článkov v slovenčine obnáša jeden probĺém - Ako sa vysporiadať s terminológiou. Dlho som rozmýšľal ako sa k tejto otázke postaviť. Nakoniec som sa rozhodol preferovať anglické termíny a nehľadať slovenské ekvivalenty pre všetko. Je to z dôvodu, aby človek potom vedel čítať aj zahraničné články, a vedel sa v nich zorientovať. Zástancovia slovenských ekvivalentov mi snaď odpustia tento prehrešok. Budem si ale dávať pozor, aby sa články nepremenili na anglicko-slovenský miš-maš.
Záver
V tomto článku som sa dotkol len niektorých konceptov Kube API. Treba si otvorene povedať že je toho viac. Snažil som sa však pokryť tie, ktoré su podľa mňa dôležité a zaujimavé aj z pohľadu API dizajnu iných systémov.
Na záver možno jedná rada pre programátorov. Hoci Kube API je RESTovské API, nesnažte sa s API komunikovať priamo. Nesnažte sa písať si vlastnú klientskú knižnicu. Stále sa pozrite po existujúcom klientovy pre daný jazyk. Dôvod je veľmi jednoduchý: Kube API je kritickou časťou clustrov. Jeho nestabilita znamená nestabilitu celého clustra. Nesprávnou komunikáciou vieme Kube API dosť potrápiť. Tieto klienstké knižnice niesu len vygenerované objekty pre REST API. Často zohľadnujú rôzne optimalizácie na strane Kube API a uľahčia vám v tomto život.