La traduzione dell'articolo è stata preparata in vista dell'inizio del corso .

Se stai leggendo questo, probabilmente hai sentito parlare di Kubernetes (e se non lo hai fatto, come sei arrivato qui?). Ma cosa rappresenta davvero Kubernetes? Questo è ? Или ? Что вообще это значит?
A essere sincero, non sono sicuro al 100%. Ma penso sia interessante esplorare gli interni e vedere cosa succede realmente in Kubernetes sotto i suoi molteplici strati di astrazione. Quindi, per curiosità, vediamo come appare realmente un minimo “cluster Kubernetes”. (Questo sarà molto più semplice di .)
Suppongo che tu abbia conoscenze di base su Kubernetes, Linux e container. Tutto ciò di cui parleremo qui è destinato solo a scopi di esplorazione/studio, non eseguire nulla di tutto ciò in produzione!
Panoramica
Kubernetes contiene molti componenti. Secondo , l'architettura appare come segue:

Qui sono mostrati almeno otto componenti, ma la maggior parte di essi la ignoreremo. Voglio affermare che la cosa minima che si può giustamente definire Kubernetes consiste in tre componenti principali:
- kubelet
- kube-apiserver (che dipende da etcd — il suo database)
- ambiente di esecuzione dei container (in questo caso Docker)
Vediamo cosa dice la documentazione riguardo ciascuno di essi (., .). Prima di tutto kubelet:
L'agente in esecuzione su ogni nodo nel cluster. Si assicura che i container siano avviati nel pod.
Sembra abbastanza semplice. E per quanto riguarda l'ambiente di esecuzione dei container (container runtime)?
L'ambiente di esecuzione dei container è un programma progettato per eseguire i container.
Molto informativo. Ma se conosci Docker, dovresti avere un'idea generale di cosa fa. (I dettagli della suddivisione delle responsabilità tra l'ambiente di esecuzione dei container e kubelet sono in realtà abbastanza sottili e qui non entrerò nei dettagli.)
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 fatto qualcosa con Kubernetes ha dovuto interagire con l'API, sia direttamente che tramite kubectl. È il cuore di ciò che rende Kubernetes quello che è — il cervello che trasforma le montagne di YAML, che tutti conosciamo e amiamo (?), in un'infrastruttura funzionante. È evidente che l'API debba essere presente nella nostra configurazione minima.
Requisiti preliminari
- Una macchina Linux virtuale o fisica con accesso root (sto usando Ubuntu 18.04 su una macchina virtuale).
- E questo è tutto!
Installazione noiosa
Sulla macchina che utilizzeremo, è necessario installare Docker. (Non intendo descrivere in dettaglio come funziona Docker e i container; se siete interessati, ci sono ). Installiamolo semplicemente con apt:
$ sudo apt install docker.io
$ sudo systemctl start docker Dopo questo, dobbiamo ottenere i binari di Kubernetes. In realtà, per avviare il nostro “cluster”, abbiamo bisogno solo di kubelet, poiché per avviare gli altri componenti del server potremo usare kubelet. Per interagire con il nostro cluster dopo che sarà attivo, 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 succede se proviamo semplicemente a eseguire kubelet?
$ ./kubelet
F0609 04:03:29.105194 4583 server.go:254] mkdir /var/lib/kubelet: permesso negato kubelet deve essere eseguito come root. Abbastanza logico, dato che deve gestire tutto il nodo. Diamo un'occhiata alle sue opzioni:
$ ./kubelet -h
$ ./kubelet -h | wc -l
284Wow, quante opzioni! Fortunatamente, ci serviranno solo un paio di esse. Ecco uno dei parametri che ci interessa:
--pod-manifest-path stringIl percorso della cartella contenente i file per i pod statici, o il percorso di 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. .)
Questo parametro ci consente di eseguire — pod che non sono gestiti tramite l'API di Kubernetes. I pod statici sono usati raramente, ma sono molto utili per alzare rapidamente un cluster, ed è proprio ciò di cui abbiamo bisogno. Ignoreremo questo forte avviso (ancora una volta, non eseguitelo in produzione!) e vediamo se possiamo avviare il pod.
Prima creiamo una directory per i pod statici e avviamo kubelet:
$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=podsPoi, in un'altra finestra del terminale/tmux/altrove, 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ì! Vediamo 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 ha letto il manifesto del pod e ha dato a Docker l'istruzione di avviare un paio di contenitori secondo la nostra specifica. (Se sei curioso di sapere di più sul contenitore "pause", questa è una manomissione di Kubernetes — per dettagli, vedi in .) Kubelet avvierà il nostro contenitore busybox con il comando specificato e continuerà a riavviarlo all'infinito fino a quando il pod statico non sarà rimosso.
Complimenti. Abbiamo appena ideato uno dei modi più complicati per visualizzare del testo nel terminale!
Avviamo etcd
Il nostro obiettivo finale è avviare l'API di Kubernetes, ma per farlo dobbiamo prima avviare . Diamo inizio a un cluster etcd minimo, posizionando la sua configurazione nella directory pods (ad 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-dataSe hai mai lavorato con Kubernetes, file YAML come questi ti saranno familiari. Qui ci sono solo due punti da notare:
Abbiamo montato la cartella dell'host /var/lib/etcd in pod, per garantire che i dati etcd vengano conservati dopo un riavvio (se non lo fai, lo stato del cluster verrà cancellato ad ogni riavvio del pod, il che non è buono nemmeno per un'installazione minima di Kubernetes).
Abbiamo installato hostNetwork: true. Questo parametro, com'è ovvio, configura etcd per utilizzare la rete host anziché la rete interna del pod (questo faciliterà al server API la ricerca del cluster etcd).
Un semplice test 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.walAvvio del server API
Avviare il server API Kubernetes è ancora più semplice. L'unico parametro da fornire è --etcd-servers, fa 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 directory pods, e il server API si avvierà. Un controllo con curl mostra che l'API di Kubernetes ascolta sulla porta 8080 con accesso completamente aperto — non è richiesta 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 eseguire questo in produzione! Sono rimasto un po' sorpreso che la configurazione predefinita fosse così insicura. Ma suppongo che sia stato fatto per facilitare lo sviluppo e il testing.)
E, sorpresa piacevole, kubectl funziona out of the box 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
No resources found in default namespace.Problema
Ma se si scava un po' più a fondo, sembra che ci sia qualcosa che non va:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.I pod statici che abbiamo creato sono scomparsi! In realtà, il nostro nodo kubelet non viene nemmeno rilevato:
$ ./kubectl get nodes
No resources found in default namespace.Qual è il problema? Se ricordate, qualche paragrafo fa abbiamo avviato kubelet con un insieme di parametri della riga di comando estremamente semplici, quindi kubelet non sa come collegarsi al server API e comunicargli il suo stato. Consultando la documentazione, troviamo il flag corrispondente:
--kubeconfig string
Percorso del file kubeconfig, in cui è specificato come connettersi al server API. La presenza di --kubeconfig attiva la modalità API server, l'assenza di --kubeconfig attiva la modalità autonoma.
Per tutto questo tempo, senza saperlo, abbiamo eseguito kubelet in modalità ‘autonoma’. (Se fossimo stati pignoli, si potrebbe considerare la modalità autonoma di kubelet come ‘Kubernetes minimamente vitale’, ma sarebbe stata molto noiosa.) Per far funzionare una configurazione ‘reale’, dobbiamo passare il file kubeconfig a kubelet, affinché sappia come comunicare con l'API server. Fortunatamente, è piuttosto semplice (poiché 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 Salvatelo come kubeconfig.yaml, terminate il processo kubelet e riavviate con i parametri necessari:
$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml(Tra l'altro, se provate a contattare l'API tramite curl, quando kubelet non è in esecuzione, scoprirete che funziona ancora! Kubelet non è il ‘genitore’ dei suoi pod, come Docker, è più simile a un “demone di gestione”. I contenitori gestiti da kubelet continueranno a funzionare finché kubelet non li fermerà.)
Dopo pochi minuti 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.6Facciamoci congratulazioni questa volta, sul serio (so che già lo abbiamo fatto) — abbiamo creato un ‘cluster’ Kubernetes minimale, funzionante con un'API completa!
Avviamo il pod
Ora vediamo di cosa è capace l'API. Cominciamo con un pod nginx:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginxQui riceveremo un errore piuttosto interessante:
$ ./kubectl apply -f nginx.yaml
Erro dal server (Forbidden): erro durante la creazione di "nginx.yaml": i pod "nginx" sono
proibiti: errore nella ricerca dell'account di servizio default/default: serviceaccount
"default" non trovato
$ ./kubectl get serviceaccounts
Nessuna risorsa trovata nello spazio dei nomi predefinito.Qui possiamo vedere quanto sia terribilmente incompleta la nostra ambientazione Kubernetes: non abbiamo account di servizio. Proviamo ancora una volta, creando manualmente un account di servizio e vediamo cosa succede:
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
EOS
serviceaccount/default creato
$ ./kubectl apply -f nginx.yaml
Errore dal server (ServerTimeout): errore durante la creazione di "nginx.yaml": Nessun token API
trovato per l'account di servizio "default", riprovare dopo che il token è stato
creato automaticamente e aggiunto all'account di servizioAnche quando abbiamo creato manualmente un account di servizio, il token di autenticazione non viene creato. Continuando a sperimentare con il nostro "cluster" minimalista, scopriremo che la maggior parte delle cose utili che di solito si verificano automaticamente saranno assenti. Il server API Kubernetes è piuttosto minimalista, gran parte delle complesse configurazioni automatiche avviene in vari controller e job in background che non sono ancora in esecuzione.
Possiamo aggirare questo problema impostando l'opzione automountServiceAccountToken per l'account di servizio (dal momento che tanto 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
NOME PRONTO STATO RIAVVII ETÀ
nginx 0/1 In attesa 0 13mFinalmente, il pod è apparso! Ma in realtà non si avvierà, poiché non abbiamo (scheduler) — un altro componente importante di Kubernetes. Ancora una volta, vediamo che l'API Kubernetes è sorprendentemente "stupida": quando si crea un pod nell'API, lo registra, ma non cerca di scoprire su quale nodo eseguirlo.
In realtà, per avviare un pod, non è necessario un pianificatore. È possibile aggiungere manualmente un nodo nel manifesto sotto il 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 delete pod nginx
pod "nginx" eliminato
$ .\/kubectl apply -f nginx.yaml
pod/nginx creato
$ .\/kubectl get pods -owide
NOME PRONTO STATO RIAVVII ETÀ IP NODO NOME NODI NOMINATI PORTI DI PRONTO
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 su nginx!</title>Per assicurarci che la rete tra i pod funzioni correttamente, possiamo eseguire curl da un altro 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 % Ricevuto % Xferd Velocità Media Tempo Tempo Tempo Corrente
Dload Upload Totale Speso Rimanente Velocità
<!DOCTYPE html>
<html>
<head>
<title>Benvenuto su nginx!</title>È piuttosto interessante esplorare questo ambiente e vedere cosa funziona e cosa no. Ho scoperto che ConfigMap e Secret funzionano come ci si aspetterebbe, mentre Service e Deployment no.
Successo!
Questo post sta diventando lungo, quindi annuncerò la vittoria e affermerò che questa è una configurazione valida, che può essere chiamata “Kubernetes”. In sintesi: quattro file binari, cinque parametri della riga di comando e “solo” 45 righe YAML (non così tante secondo gli standard di Kubernetes) e abbiamo molte cose funzionanti:
- I Pod vengono gestiti tramite l'API Kubernetes standard (con alcuni hack)
- È possibile caricare immagini di container 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 il più semplice montaggio degli storage funzionano come previsto
Ma gran parte di ciò che rende Kubernetes davvero utile è ancora assente, per esempio:
- Scheduler dei pod
- Autenticazione / autorizzazione
- Più nodi
- Rete dei servizi
- DNS interno del cluster
- Controller per account di servizio, deployment, integrazione con fornitori di cloud e la maggior parte degli altri “vantaggi” che porta Kubernetes
Quindi, cosa abbiamo realmente ottenuto? L’API Kubernetes, di per sé, è in realtà solo una piattaforma per l'automazione dei container. Non fa molto — questo è compito per vari controller e operatori che utilizzano l'API — ma fornisce un ambiente coerente per l'automazione.
Leggi di più:
Fonte: habr.com
