Minimum levedyktig Kubernetes

Oversettelsen av artikkelen ble utarbeidet like før kursstart "DevOps-praksis og verktøy".

Minimum levedyktig Kubernetes

Hvis du leser dette, har du sikkert hørt noe om Kubernetes (og hvis ikke, hvordan havnet du her?) Men hva er egentlig Kubernetes? Dette "Orkestrering av containere av industrikvalitet"? Eller "Cloud-Native operativsystem"? Hva betyr dette egentlig?

For å være ærlig er jeg ikke 100% sikker. Men jeg synes det er interessant å grave i det indre og se hva som egentlig skjer i Kubernetes under de mange lagene av abstraksjoner. Så bare for moro skyld, la oss ta en titt på hvordan en minimal "Kubernetes-klynge" faktisk ser ut. (Dette vil være mye enklere enn Kubernetes The Hard Way.)

Jeg antar at du har grunnleggende kunnskap om Kubernetes, Linux og containere. Alt vi snakker om her er kun for forsknings-/læringsformål, ikke sett noe av det i produksjon!

Gjennomgå

Kubernetes inneholder mange komponenter. I følge wikipedia, ser arkitekturen slik ut:

Minimum levedyktig Kubernetes

Det er minst åtte komponenter vist her, men vi vil ignorere de fleste av dem. Jeg vil si at minimumstingen som med rimelighet kan kalles Kubernetes består av tre hovedkomponenter:

  • kubelet
  • kube-apiserver (som avhenger av etcd - databasen)
  • container kjøretid (Docker i dette tilfellet)

La oss se hva dokumentasjonen sier om hver av dem (russisk., Norsk.). Først kubelet:

En agent som kjører på hver node i klyngen. Den sørger for at containere kjører i poden.

Høres enkelt nok ut. Hva med container kjøretider (beholder kjøretid)?

En container runtime er et program designet for å kjøre containere.

Veldig informativ. Men hvis du er kjent med Docker, bør du ha en generell ide om hva den gjør. (Detaljene rundt separasjonen av ansvar mellom beholderens kjøretid og kubelet er faktisk ganske subtile, og jeg vil ikke gå inn på dem her.)

И API-server?

API Server er Kubernetes kontrollpanelkomponent som viser Kubernetes API. API-serveren er klientsiden av Kubernetes kontrollpanel

Alle som noen gang har gjort noe med Kubernetes har måttet samhandle med API enten direkte eller gjennom kubectl. Dette er hjertet av det som gjør Kubernetes Kubernetes - hjernen som gjør fjellene i YAML vi alle kjenner og elsker (?) til fungerende infrastruktur. Det virker åpenbart at API bør være til stede i vår minimale konfigurasjon.

Forutsetninger

  • Linux virtuell eller fysisk maskin med root-tilgang (jeg bruker Ubuntu 18.04 på en virtuell maskin).
  • Og det er alt!

Kjedelig installasjon

Vi må installere Docker på maskinen vi skal bruke. (Jeg skal ikke gå i detalj om hvordan Docker og containere fungerer; hvis du er interessert, er det fantastiske artikler). La oss bare installere den med apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

Etter det må vi få Kubernetes-binærene. Faktisk, for den første lanseringen av "klyngen" vår trenger vi bare kubelet, siden for å kjøre andre serverkomponenter vi kan bruke kubelet. For å samhandle med klyngen vår etter at den kjører, vil vi også bruke kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

Hva skjer hvis vi bare løper kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet må kjøre som root. Ganske logisk, siden han må administrere hele noden. La oss se på parameterne:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

Wow, så mange alternativer! Heldigvis trenger vi bare et par av dem. Her er en av parameterne vi er interessert i:

--pod-manifest-path string

Bane til katalogen som inneholder filer for statiske poder, eller bane til en fil som beskriver statiske poder. Filer som begynner med prikker ignoreres. (FORSETT: Dette alternativet må settes i konfigurasjonsfilen som sendes til Kubelet via --config-alternativet. For mer informasjon, se kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Dette alternativet lar oss kjøre statiske poder — pods som ikke administreres via Kubernetes API. Statiske poder brukes sjelden, men de er veldig praktiske for raskt å heve en klynge, og dette er akkurat det vi trenger. Vi vil ignorere denne store advarselen (igjen, ikke kjør dette i produksjon!) og se om vi kan få poden i gang.

Først vil vi lage en katalog for statiske poder og kjøre kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

Så, i et annet terminal/tmux-vindu/whatever, vil vi lage et pod-manifest:

$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet begynner å skrive noen advarsler og det virker som ingenting skjer. Men det er ikke sant! La oss se på Docker:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet Jeg leste pod-manifestet og ga Docker kommandoen om å starte et par containere i henhold til spesifikasjonene våre. (Hvis du lurer på "pause"-beholderen, er det et Kubernetes-hack - se denne bloggen.) Kubelet vil lansere containeren vår busybox med den angitte kommandoen og vil starte den på nytt på ubestemt tid til den statiske poden er slettet.

Gratulere deg selv. Vi har nettopp kommet opp med en av de mest forvirrende måtene å sende ut tekst til terminalen på!

Start osv

Vårt endelige mål er å kjøre Kubernetes API, men for å gjøre det må vi først kjøre osv. La oss starte en minimal etcd-klynge ved å plassere innstillingene i pods-katalogen (for eksempel, pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

Hvis du noen gang har jobbet med Kubernetes, bør disse YAML-filene være kjent for deg. Det er bare to punkter som er verdt å merke seg her:

Vi har montert vertsmappen /var/lib/etcd i poden slik at etcd-dataene blir bevart etter en omstart (hvis dette ikke gjøres, vil klyngetilstanden bli slettet hver gang poden startes på nytt, noe som ikke vil være bra for selv en minimal Kubernetes-installasjon).

Vi har installert hostNetwork: true. Denne innstillingen konfigurerer, ikke overraskende, etcd til å bruke vertsnettverket i stedet for podens interne nettverk (dette vil gjøre det lettere for API-serveren å finne etcd-klyngen).

En enkel sjekk viser at etcd faktisk kjører på localhost og lagrer data til disk:

$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal

Starter API-serveren

Å kjøre en Kubernetes API-server er enda enklere. Den eneste parameteren som må passeres er --etcd-servers, gjør det du forventer:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

Plasser denne YAML-filen i katalogen pods, og API-serveren vil starte. Sjekker med curl viser at Kubernetes API lytter på port 8080 med helt åpen tilgang - ingen autentisering kreves!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(Igjen, ikke kjør dette i produksjon! Jeg ble litt overrasket over at standardinnstillingen er så usikker. Men jeg tipper dette er for å gjøre utvikling og testing enklere.)

Og, hyggelig overraskelse, kubectl fungerer ut av esken uten noen ekstra innstillinger!

$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.

problem

Men hvis du graver litt dypere, ser det ut til at noe går galt:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

De statiske podene vi laget er borte! Faktisk blir ikke kubelet-noden vår oppdaget i det hele tatt:

$ ./kubectl get nodes
No resources found in default namespace.

Hva er i veien? Hvis du husker noen avsnitt siden, startet vi kubelet med et ekstremt enkelt sett med kommandolinjeparametere, slik at kubelet ikke vet hvordan den skal kontakte API-serveren og varsle den om tilstanden. Etter å ha studert dokumentasjonen finner vi det tilsvarende flagget:

--kubeconfig string

Banen til filen kubeconfig, som spesifiserer hvordan du kobler til API-serveren. Tilgjengelighet --kubeconfig aktiverer API-servermodus, nei --kubeconfig aktiverer frakoblet modus.

Hele denne tiden, uten å vite det, kjørte vi kubelet i "frakoblet modus." (Hvis vi var pedantiske, kunne vi tenke på en frittstående kubelet som "minimum levedyktige Kubernetes", men det ville vært veldig kjedelig). For at den "ekte" konfigurasjonen skal fungere, må vi sende kubeconfig-filen til kubelet slik at den vet hvordan den skal snakke med API-serveren. Heldigvis er det ganske enkelt (siden vi ikke har noen problemer med autentisering eller sertifikater):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

Lagre dette som kubeconfig.yaml, drep prosessen kubelet og start på nytt med de nødvendige parameterne:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(Forresten, hvis du prøver å få tilgang til API-en via curl når kubelet ikke kjører, vil du oppdage at den fortsatt kjører! Kubelet er ikke en "forelder" til sine pods som Docker, det er mer som en "kontroll daemon." Beholdere administrert av en kubelet vil fortsette å kjøre til kubelet stopper dem.)

Om et par minutter kubectl skal vise oss podene og nodene slik vi forventer:

$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready    <none>   21h   v1.18.5   10.70.10.228   <none>        Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

La oss virkelig gratulere oss selv denne gangen (jeg vet at jeg allerede har gratulert oss selv) - vi har en minimal Kubernetes "cluster" som kjører med en fullt funksjonell API!

Vi lanserer under

La oss nå se hva API-en er i stand til. La oss starte med nginx-poden:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

Her får vi en ganske interessant feil:

$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.

Her ser vi hvor sørgelig ufullstendig Kubernetes-miljøet vårt er - vi har ingen kontoer for tjenester. La oss prøve igjen ved å opprette en tjenestekonto manuelt og se hva som skjer:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account

Selv når vi opprettet tjenestekontoen manuelt, genereres ikke autentiseringstokenet. Når vi fortsetter å eksperimentere med vår minimalistiske "klynge", vil vi oppdage at det meste av nyttige ting som vanligvis skjer automatisk, vil mangle. Kubernetes API-serveren er ganske minimalistisk, med det meste av tungløftet og den automatiske konfigurasjonen som skjer i forskjellige kontrollere og bakgrunnsjobber som ennå ikke kjører.

Vi kan omgå dette problemet ved å angi alternativet automountServiceAccountToken for tjenestekontoen (siden vi uansett ikke trenger å bruke den):

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

Endelig har poden dukket opp! Men faktisk vil det ikke starte fordi vi ikke har det planlegger (planlegger) er en annen viktig komponent i Kubernetes. Igjen ser vi at Kubernetes API er overraskende "dum" - når du oppretter en Pod i APIen, registrerer den den, men prøver ikke å finne ut hvilken node den skal kjøres på.

Faktisk trenger du ikke en planlegger for å kjøre en pod. Du kan manuelt legge til en node til manifestet i parameteren nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(Erstatte mink8s til navnet på noden.) Etter sletting og bruk ser vi at nginx har startet og lytter til den interne IP-adressen:

$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

For å være sikker på at nettverket mellom pods fungerer som det skal, kan vi kjøre curl fra en annen pod:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Det er ganske interessant å grave i dette miljøet og se hva som fungerer og ikke. Jeg fant ut at ConfigMap og Secret fungerer som forventet, men Service og Deployment gjør det ikke.

Suksess!

Dette innlegget begynner å bli langt, så jeg kommer til å erklære seier og si at dette er en levedyktig konfigurasjon som kan kalles "Kubernetes". For å oppsummere: fire binærfiler, fem kommandolinjeparametere og "bare" 45 linjer med YAML (ikke så mye etter standarder Kubernetes), og vi har ganske mange ting som fungerer:

  • Pods administreres ved hjelp av den vanlige Kubernetes API (med noen få hacks)
  • Du kan laste opp og administrere offentlige beholderbilder
  • Pods forblir i live og starter automatisk på nytt
  • Nettverk mellom pods innenfor samme node fungerer ganske bra
  • ConfigMap, Secret og enkel lagringsmontering fungerer som forventet

Men mye av det som gjør Kubernetes virkelig nyttig mangler fortsatt, for eksempel:

  • Pod-planlegger
  • Autentisering/autorisasjon
  • Flere noder
  • Nettverk av tjenester
  • Klynget intern DNS
  • Kontrollere for tjenestekontoer, distribusjoner, integrasjon med skyleverandører og de fleste andre godbitene som Kubernetes bringer

Så hva fikk vi egentlig? Kubernetes API, som kjører på egen hånd, er egentlig bare en plattform for container automatisering. Det gjør ikke mye - det er en jobb for forskjellige kontrollere og operatører som bruker API - men det gir et konsistent miljø for automatisering.

Lær mer om kurset i det gratis webinaret.

Les mer:

Kilde: www.habr.com

Legg til en kommentar