Kubernetes API

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:

Ď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 statuskube-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.

<