Kubernetes minimo vitale

La traduzione dell'articolo è stata preparata in vista dell'inizio del corso «Pratiche e strumenti DevOps».

Kubernetes minimo vitale

Se stai leggendo questo, probabilmente hai sentito parlare di Kubernetes (e se non lo hai fatto, come ci sei arrivato?). Ma cos'è realmente Kubernetes? È “Orchestrazione di container a livello industriale”? Или «Sistema Operativo Cloud-Native»? Что вообще это значит?

A dire il vero, non ne sono sicuro al 100%. Ma penso sia interessante esplorare i dettagli e vedere cosa succede realmente in Kubernetes sotto i suoi tanti strati di astrazione. Quindi, per curiosità, diamoci un'occhiata a come appare realmente un “cluster Kubernetes” minimale. (Questo sarà molto più semplice di Kubernetes The Hard Way.)

Presumo tu abbia una conoscenza di base di Kubernetes, Linux e container. Tutto ciò di cui parleremo qui è destinato solo a scopi di esplorazione/studio, non avviare nulla di tutto ciò in produzione!

Panoramica

Kubernetes contiene molti componenti. Secondo Wikipedia, l'architettura appare come segue:

Kubernetes minimo vitale

Qui sono mostrati almeno otto componenti, ma la maggior parte di essi la ignoreremo. Voglio affermare che la cosa minima che può essere ragionevolmente chiamata Kubernetes è composta da tre componenti principali:

  • kubelet
  • kube-apiserver (che dipende da etcd — il suo database)
  • runtime del container (in questo caso Docker)

Vediamo cosa ne dice la documentazione su ognuno di essi (rus., eng.). Per cominciare, kubelet:

Agente che opera su ciascun nodo del cluster. Si occupa di garantire che i container siano avviati nel pod.

Sembra abbastanza semplice. E riguardo a runtime dei container (container runtime)?

Il runtime del container è un programma progettato per eseguire container.

Molto informativo. Ma se conosci Docker, dovresti avere una comprensione generale di cosa fa. (I dettagli sulla divisione delle responsabilità tra il runtime dei container e kubelet sono in realtà piuttosto sottili e qui non mi addentrerò in essi.)

E API server?

Il server API è un componente del pannello di controllo di Kubernetes che rappresenta l'API di Kubernetes. L'API server è la parte client del pannello di controllo di Kubernetes.

Chiunque abbia mai lavorato con Kubernetes ha avuto a che fare con l'API, sia direttamente che tramite kubectl. Questo è il cuore di ciò che rende Kubernetes Kubernetes: il cervello che trasforma le montagne di YAML che tutti conosciamo e amiamo (?) in un'infrastruttura funzionante. È ovvio che l'API dovrebbe essere presente nella nostra configurazione minima.

Requisiti preliminari

  • Una macchina virtuale o fisica Linux con accesso root (utilizzo Ubuntu 18.04 su una macchina virtuale).
  • E questo è tutto!

Installazione noiosa

Sulla macchina che utilizzeremo, è necessario installare Docker. (Non intendo spiegare in dettaglio come funziona Docker e i container; se siete interessati, ci sono articoli fantastici). Installiamolo semplicemente con apt:

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

Dopo di che, dobbiamo ottenere i binari di Kubernetes. In realtà, per avviare il nostro "cluster" inizialmente abbiamo bisogno solo di kubelet, poiché per eseguire altri componenti server potremo usare kubelet. Per interagire con il nostro cluster una volta avviato, utilizzeremo anche 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

Cosa succederà se semplicemente avviamo kubelet?

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

kubelet deve essere eseguito come root. È abbastanza logico, dato che deve gestire l'intero nodo. Vediamo quali sono i suoi parametri:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, quante opzioni! Fortunatamente, ne avremo bisogno solo di alcune. Ecco uno dei parametri che ci interessano:

--pod-manifest-path string

Il percorso alla directory contenente i file per i pod statici, o il percorso a un file con la descrizione dei pod statici. I file che iniziano con un punto vengono ignorati. (OBSOLETO: questo parametro dovrebbe essere impostato nel file di configurazione passato a Kubelet tramite l'opzione —config. Per ulteriori informazioni vedere. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Questo parametro ci permette di eseguire pod statici — pod non gestiti tramite l'API di Kubernetes. I pod statici sono utilizzati raramente, ma sono molto utili per avviare rapidamente un cluster, ed è esattamente ciò di cui abbiamo bisogno. Ignoreremo questo avviso forte (ancora una volta, non eseguite questo in produzione!) e vediamo se riusciamo ad avviare il pod.

Innanzitutto creeremo una directory per i pod statici e avvieremo kubelet:

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

Poi, in un altro terminale/finestra tmux/da un'altra parte, creeremo il manifesto del pod:

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

kubelet inizia a scrivere alcuni avvisi e sembra che non stia succedendo nulla. Ma non è così! Diamo un'occhiata a 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 Ho letto il manifesto del pod e ho dato a Docker l'istruzione di avviare un paio di contenitori secondo le nostre specifiche. (Se ti interessa sapere del contenitore “pause”, è un hack di Kubernetes — dettagli nel questo blog.) Kubelet avvierà il nostro contenitore busybox con il comando specificato e lo riavvierà indefinitamente finché il pod statico non sarà rimosso.

Congratulati. Abbiamo appena inventato uno dei modi più complicati per stampare testo nel terminale!

Avviamo etcd

Il nostro obiettivo finale è avviare l'API di Kubernetes, ma prima dobbiamo avviare etcd. Creiamo un cluster etcd minimo, collocando la sua configurazione nella cartella pods (per esempio, 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

Se hai mai lavorato con Kubernetes, file YAML come questi dovrebbero esserti familiari. Qui vale la pena sottolineare solo due punti:

Abbiamo montato la cartella dell'host /var/lib/etcd in un pod, affinché i dati etcd vengano mantenuti dopo il riavvio (se non lo fai, lo stato del cluster verrà cancellato ad ogni riavvio del pod, il che non è ideale nemmeno per una minima installazione di Kubernetes).

Abbiamo installato hostNetwork: true. Questo parametro, non sorprende, configura etcd per utilizzare la rete host invece della rete interna del pod (questo faciliterà al server API la ricerca del cluster etcd).

Una semplice verifica mostra che etcd è effettivamente in esecuzione su localhost e sta salvando i dati su disco:

$ 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

Avvio del server API

Avviare il server API di Kubernetes è ancora più semplice. L'unico parametro che devi passare è --etcd-servers, esegue ciò che ti aspetti:

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

Posiziona questo file YAML nella cartella pods, e il server API verrà avviato. Verifica con curl indica che l'API di Kubernetes sta ascoltando sulla porta 8080 con accesso completamente aperto — non è richiesta alcuna autenticazione!

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

(Ancora una volta, non eseguite questo in produzione! Sono rimasto un po' sorpreso che la configurazione predefinita sia così insicura. Ma suppongo che sia stata fatta per facilitare lo sviluppo e il testing.)

E, sorpresa gradevole, kubectl funziona da subito senza ulteriori configurazioni!

$ ./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
Nessuna risorsa trovata nello spazio dei nomi predefinito.

Problema

Ma se scavi un po' più a fondo, sembra che qualcosa non vada:

$ ./kubectl get pod -n kube-system
Nessuna risorsa trovata nello spazio dei nomi kube-system.

I pod statici che abbiamo creato sono scomparsi! In realtà, il nostro nodo kubelet non viene nemmeno rilevato:

$ ./kubectl get nodes
Nessuna risorsa trovata nello spazio dei nomi predefinito.

Qual è il problema? Se ricordi, qualche paragrafo fa abbiamo avviato kubelet con un set di parametri da riga di comando molto semplice, quindi kubelet non sa come connettersi al server API e comunicargli il suo stato. Esaminando la documentazione, troviamo il flag corrispondente:

--kubeconfig string

Percorso del file kubeconfig, che specifica come connettersi al server API. La presenza di --kubeconfig attiva la modalità del server API, la sua assenza --kubeconfig attiva la modalità autonoma.

Per tutto questo tempo, senza nemmeno saperlo, stiamo eseguendo kubelet in "modalità autonoma". (Se fossimo stati pignoli, si potrebbe considerare la modalità autonoma di kubelet come "Kubernetes minimamente funzionante", ma sarebbe stato molto noioso). Affinché la configurazione "vera" funzioni, dobbiamo passare il file kubeconfig a kubelet affinché sappia come comunicare con il server API. Fortunatamente, è abbastanza semplice (dato che non abbiamo problemi di autenticazione o certificati):

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

Salvalo come kubeconfig.yaml, termina il processo kubelet e riavvia con i parametri richiesti:

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

(Tra l'altro, se provi a collegarti all'API tramite curl quando kubelet non è in esecuzione, scoprirai che funziona ancora! Kubelet non è il "genitore" dei suoi pod, come Docker, ma è più simile a un “demonio di gestione”. I contenitori gestiti da kubelet continueranno a funzionare fino a quando kubelet non li fermerà.)

Dopo qualche minuto kubectl dovrebbe mostrarci i pod e i nodi, come ci aspettiamo:

$ ./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       21h   v1.18.5   10.70.10.228           Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Facciamo i complimenti a noi stessi davvero questa volta (so che già lo abbiamo fatto) — abbiamo realizzato un "cluster" Kubernetes minimo, funzionante con un API completamente funzionale!

Avviamo il pod

Ora vediamo di cosa è capace l'API. Iniziamo con il pod nginx:

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

Qui otterremo un errore piuttosto interessante:

$ ./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.

Здесь мы видим насколько ужасающе неполна наша среда Kubernetes — у нас нет учетных записей для служб. Давайте попробуем еще раз, создав учетную запись службы вручную, и посмотрим, что произойдет:

$ 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

Даже когда мы создали учетную запись службы вручную, токен аутентификации не создается. Продолжая экспериментировать с нашим минималистичным «кластером», мы обнаружим, что большинство полезных вещей, которые обычно происходят автоматически, будут отсутствовать. Сервер Kubernetes API довольно минималистичен, большая часть тяжелых автоматических настроек происходит в различных контроллерах и фоновых заданиях, которые еще не выполняются.

Мы можем обойти эту проблему, установив опцию automountServiceAccountToken per l'account di servizio (dal momento che non dovremo usarlo):

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

Finalmente, il pod è apparso! Ma in realtà non si avvierà, poiché non abbiamo uno scheduler (scheduler) — un altro componente importante di Kubernetes. Ancora una volta, vediamo che l'API di Kubernetes è sorprendentemente "stupida" — quando crei un pod nell'API, lo registra, ma non cerca di capire su quale nodo eseguirlo.

In realtà, per avviare un pod non è necessario uno scheduler. Puoi aggiungere manualmente un nodo nel manifesto nel parametro nodeName:

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

(Sostituisci mink8s con il nome del nodo.) Dopo delete e apply vediamo che nginx è partito e ascolta l'indirizzo IP interno:

$ ./kubectl elimina pod nginx
pod "nginx" eliminato
$ ./kubectl applica -f nginx.yaml
pod/nginx creato
$ ./kubectl ottieni pod -owide
NOME   PRONTO   STATO   RIAVVII   ETÀ   IP           NODO     NODO NOMINATO   GATES DI PRONTEZZA
nginx  1/1     In esecuzione   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Benvenuto in nginx!</title>

Per assicurarci che la rete tra i pod funzioni correttamente, possiamo lanciare curl da un altro pod:

$ cat &lt;&lt;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
  % Totale    % Ricevuto % Xferd  Velocità Media   Tempo    Tempo     Tempo  Corrente
                                 Dload  Upload   Totale   Speso    Rimasto  Velocità
<!DOCTYPE html>
<html>
<head>
<title>Benvenuto in nginx!</title>

È piuttosto interessante esplorare questo ambiente e vedere cosa funziona e cosa no. Ho scoperto che ConfigMap e Secret funzionano come previsto, ma Service e Deployment no.

Successo!

Questo post sta diventando lungo, quindi annuncio la vittoria e dichiaro che questa è una configurazione praticabile che possiamo chiamare 'Kubernetes'. In sintesi: quattro file binari, cinque parametri della riga di comando e 'solo' 45 righe di YAML (non così tante secondo gli standard di Kubernetes), e abbiamo già diverse cose che funzionano:

  • I pod sono gestiti utilizzando l'API Kubernetes standard (con alcuni hack)
  • È possibile caricare immagini di contenitori pubbliche e gestirle
  • I pod rimangono attivi e vengono riavviati automaticamente
  • La rete tra i pod all'interno di un singolo nodo funziona piuttosto bene
  • ConfigMap, Secret e la semplice montatura dello storage funzionano come dovrebbero

Ma gran parte di ciò che rende Kubernetes davvero utile è ancora assente, ad esempio:

  • Il pianificatore dei pod
  • Autenticazione / autorizzazione
  • Molti nodi
  • Rete dei servizi
  • DNS interno al cluster
  • Controller per account di servizi, distribuzione, integrazione con fornitori di cloud e molte altre funzionalità offerte da Kubernetes.

Quindi, cosa abbiamo effettivamente ottenuto? L'API di Kubernetes, che funziona da sola, è in realtà solo una piattaforma per l'automazione dei container.Non fa molto — è compito di vari controller e operatori utilizzare l'API — ma offre un ambiente coerente per l'automazione.

Scopri di più sul corso durante il webinar gratuito.

Leggi di più:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster