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 ci sei arrivato?). Ma cos'è realmente Kubernetes? È ? Или ? Что вообще это значит?
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 .)
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 , 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 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 (., .). 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 ). 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
284Wow, quante opzioni! Fortunatamente, ne avremo bisogno solo di alcune. Ecco uno dei parametri che ci interessano:
--pod-manifest-path stringIl 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. .)
Questo parametro ci permette di eseguire — 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=podsPoi, 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 .) 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 . 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-dataSe 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.walAvvio 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.6Facciamo 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: nginxQui 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 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 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 <<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.
Leggi di più:
Fonte: habr.com
