Kubernetes minimal viable

La traduction de l'article a été préparée en prévision du lancement du cours « Pratiques et outils DevOps ».

Kubernetes minimal viable

Si vous lisez ceci, vous avez probablement entendu parler de Kubernetes (et si ce n’est pas le cas, comment ĂȘtes-vous ici ?) Mais qu'est-ce que Kubernetes, en rĂ©alitĂ© ? C'est « Orchestration de conteneurs de niveau industriel »? ИлО « SystĂšme d'exploitation cloud natif »? Đ§Ń‚ĐŸ ĐČĐŸĐŸĐ±Ń‰Đ” ŃŃ‚ĐŸ Đ·ĐœĐ°Ń‡ĐžŃ‚?

HonnĂȘtement, je ne suis pas sĂ»r Ă  100 %. Mais je pense qu'il est intĂ©ressant d'explorer et de voir ce qui se passe rĂ©ellement dans Kubernetes sous ses nombreux niveaux d'abstraction. Donc, par curiositĂ©, voyons Ă  quoi ressemble rĂ©ellement un "cluster Kubernetes" minimal. (C'est bien plus simple que Kubernetes The Hard Way.)

Je suppose que vous avez des connaissances de base en Kubernetes, Linux et en conteneurs. Tout ce dont nous allons parler ici est destiné uniquement à des fins d'exploration / d'étude, ne lancez rien de cela en production !

Aperçu

Kubernetes contient de nombreux composants. Selon Wikipedia, l'architecture apparaĂźt comme suit :

Kubernetes minimal viable

Ici, au moins huit composants sont montrés, mais nous en ignorerons la plupart. Je tiens à déclarer que la chose minimale que l'on peut raisonnablement qualifier de Kubernetes est composée de trois composants principaux :

  • kubelet
  • kube-apiserver (qui dĂ©pend d'etcd — sa base de donnĂ©es)
  • l'environnement d'exĂ©cution des conteneurs (dans ce cas, Docker)

Voyons ce que dit la documentation Ă  leur sujet (rus., angl.). D'abord kubelet:

Un agent s'exĂ©cutant sur chaque nƓud du cluster. Il veille Ă  ce que les conteneurs soient lancĂ©s dans un pod.

Cela semble suffisamment simple. Qu'en est-il de l'environnement d'exécution des conteneurs ? (container runtime) ?

L'environnement d'exécution des conteneurs est un programme conçu pour exécuter des conteneurs.

TrĂšs informatif. Mais si vous ĂȘtes familier avec Docker, vous devriez avoir une idĂ©e gĂ©nĂ©rale de ce qu'il fait. (Les dĂ©tails de la sĂ©paration des responsabilitĂ©s entre l'environnement d'exĂ©cution des conteneurs et kubelet sont en rĂ©alitĂ© assez dĂ©licats et je ne m'y attarderai pas ici.)

Et API-server?

Le serveur API est le composant du tableau de bord Kubernetes qui expose l'API Kubernetes. Le serveur API est la partie cliente du tableau de bord Kubernetes.

Quiconque ayant dĂ©jĂ  utilisĂ© Kubernetes a dĂ» interagir avec l'API, que ce soit directement ou via kubectl. C'est le cƓur mĂȘme de ce qui fait de Kubernetes un Kubernetes — le cerveau qui transforme les montagnes de YAML, que nous connaissons et aimons tous (?), en une infrastructure fonctionnelle. Il semble Ă©vident que l'API doit ĂȘtre prĂ©sente dans notre configuration minimale.

Conditions préalables

  • Une machine virtuelle ou physique Linux avec accĂšs root (j'utilise Ubuntu 18.04 sur une machine virtuelle).
  • Et c'est tout !

Installation ennuyeuse

Docker doit ĂȘtre installĂ© sur la machine que nous allons utiliser. (Je ne vais pas expliquer en dĂ©tail comment fonctionne Docker et les conteneurs ; si cela vous intĂ©resse, il y a des articles remarquables). Installons-le simplement avec apt:

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

AprÚs cela, nous devons obtenir les binaires de Kubernetes. En réalité, pour le démarrage initial de notre "cluster", nous avons seulement besoin de kubelet, car pour exécuter les autres composants serveur, nous pourrons utiliser kubelet. Pour interagir avec notre cluster une fois qu'il sera opérationnel, nous utiliserons également 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

Que se passera-t-il si nous exécutons simplement kubelet?

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

kubelet doit fonctionner en tant que root. C'est assez logique, car il doit gĂ©rer tout le nƓud. Regardons ses paramĂštres :

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, tant d'options ! Heureusement, nous n'en aurons besoin que de quelques-unes. Voici l'un des paramÚtres qui nous intéresse :

--pod-manifest-path string

Le chemin vers le rĂ©pertoire contenant les fichiers pour les pods statiques, ou le chemin vers un fichier dĂ©crivant des pods statiques. Les fichiers commençant par un point sont ignorĂ©s. (OBSOLETE : ce paramĂštre devrait ĂȘtre dĂ©fini dans le fichier de configuration passĂ© Ă  Kubelet via l'option —config. Pour plus d'informations, consultez kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Ce paramĂštre nous permet de lancer des pods statiques — des pods qui ne sont pas contrĂŽlĂ©s par l'API Kubernetes. Les pods statiques sont rarement utilisĂ©s, mais ils sont trĂšs pratiques pour le dĂ©marrage rapide d'un cluster, justement ce dont nous avons besoin. Nous allons ignorer cet avertissement bruyant (une fois de plus, ne faites pas cela en production !) et voir si nous pouvons lancer le pod.

Tout d'abord, nous allons créer un répertoire pour les pods statiques et lancer kubelet:

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

Ensuite, dans un autre terminal/une autre fenĂȘtre tmux/de quelque part d'autre, nous allons crĂ©er le manifeste du pod :

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

kubelet commence à écrire quelques avertissements et semble que rien ne se passe. Mais ce n'est pas vrai ! Voyons cela chez 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 a lu le manifeste du pod et a donnĂ© Ă  Docker l'ordre de lancer quelques conteneurs selon notre spĂ©cification. (Si vous ĂȘtes curieux d'en savoir plus sur le conteneur "pause", c'est un truc de Kubernetes — plus de dĂ©tails dans ce blog.) Kubelet lancera notre conteneur busybox avec la commande spĂ©cifiĂ©e et le redĂ©marrera indĂ©finiment tant que le pod statique ne sera pas supprimĂ©.

Félicitez-vous. Nous venons d'inventer l'une des maniÚres les plus compliquées d'afficher du texte dans le terminal !

Lançons etcd

Notre objectif final est de lancer l'API Kubernetes, mais pour cela, nous devons d'abord lancer etcd. Créons un cluster etcd minimal en plaçant ses configurations dans le répertoire pods (par exemple, 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

Si vous avez dĂ©jĂ  travaillĂ© avec Kubernetes, de tels fichiers YAML devraient vous ĂȘtre familiers. Il convient de noter seulement deux points :

Nous avons montĂ© le dossier hĂŽte /var/lib/etcd dans le pod, pour que les donnĂ©es etcd soient conservĂ©es aprĂšs le redĂ©marrage (si cela n'est pas fait, l'Ă©tat du cluster sera effacĂ© Ă  chaque redĂ©marrage du pod, ce qui n'est pas souhaitable mĂȘme pour une installation minimale de Kubernetes).

Nous avons installé hostNetwork: true. Ce paramÚtre, ce qui n'est pas surprenant, configure etcd pour utiliser le réseau hÎte au lieu du réseau interne du pod (cela facilitera la recherche du cluster etcd par l'API serveur).

Une simple vérification montre qu'etcd fonctionne bien sur localhost et sauvegarde les données sur disque :

$ 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

Démarrer l'API serveur

Démarrer l'API serveur Kubernetes est encore plus simple. Le seul paramÚtre à passer est --etcd-servers, qui fait ce à quoi vous vous attendez :

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

Placez ce fichier YAML dans le rĂ©pertoire pods, et l'API serveur dĂ©marrera. Une vĂ©rification avec curl montre que l'API Kubernetes Ă©coute sur le port 8080 avec un accĂšs totalement libre — aucune authentification n'est requise !

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

(Encore une fois, ne lancez pas cela en production ! J'ai été un peu surpris que la configuration par défaut soit si peu sécurisée. Mais je suppose que c'est fait pour faciliter le développement et les tests.)

Et, agréable surprise, kubectl fonctionne hors de la boßte sans aucune configuration supplémentaire !

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

Le problĂšme

Mais si l'on creuse un peu plus, il semble que quelque chose ne va pas :

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

Les pods statiques que nous avons créés ont disparu ! En rĂ©alitĂ©, notre nƓud kubelet n'est mĂȘme pas dĂ©tectĂ© :

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

Quel est le problÚme ? Si vous vous souvenez, il y a quelques paragraphes, nous avons lancé kubelet avec un ensemble de paramÚtres de ligne de commande trÚs simple, donc kubelet ne sait pas comment se connecter au serveur API et l'informer de son état. En consultant la documentation, nous trouvons le drapeau approprié :

--kubeconfig string

Chemin vers le fichier kubeconfig, qui indique comment se connecter au serveur API. La présence de --kubeconfig active le mode serveur API, alors que son absence --kubeconfig active le mode autonome.

Tout ce temps, sans le savoir, nous avons exécuté kubelet en mode « autonome ». (Si nous étions minutieux, nous pourrions considérer le mode autonome de kubelet comme un « Kubernetes minimal viable », mais ce serait trÚs ennuyeux). Pour obtenir une configuration « réelle », nous devons passer le fichier kubeconfig à kubelet, afin qu'il sache comment communiquer avec le serveur API. Heureusement, c'est assez simple (étant donné que nous n'avons pas de problÚmes d'authentification ou de certificats) :

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

Enregistrez cela sous kubeconfig.yaml, tuez le processus kubelet et redémarrez avec les paramÚtres nécessaires :

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

(Au fait, si vous essayez d'accĂ©der Ă  l'API via curl lorsque kubelet ne fonctionne pas, vous constaterez qu'il fonctionne toujours ! Kubelet n'est pas le « parent » de ses pods, comme Docker, il est plus semblable Ă  un “dĂ©mon de gestion”. Les conteneurs gĂ©rĂ©s par kubelet fonctionneront tant que kubelet ne les arrĂȘte pas.)

Dans quelques minutes, kubectl devrait nous montrer les pods et les nƓuds, comme nous nous y attendions :

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

Cette fois, fĂ©licitons-nous vraiment (je sais que je l'ai dĂ©jĂ  fait) — nous avons rĂ©ussi Ă  crĂ©er un « cluster » Kubernetes minimal avec une API fonctionnelle !

Lançons un pod

Voyons maintenant ce que l'API peut faire. Commençons par un pod nginx :

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

Ici, nous allons recevoir une erreur assez intéressante :

$ ./kubectl apply -f nginx.yaml
Erreur du serveur (Interdit) : erreur lors de la création de "nginx.yaml" : les pods "nginx" sont
interdits : erreur lors de la recherche du compte de service default/default : le compte de service
"default" est introuvable
$ ./kubectl get serviceaccounts
Aucune ressource trouvée dans l'espace de noms par défaut.

Ici, nous voyons à quel point notre environnement Kubernetes est désespérément incomplet - nous n'avons pas de comptes de service. Essayons encore une fois, en créant un compte de service à la main, et voyons ce qui se passe :

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default créé
$ ./kubectl apply -f nginx.yaml
Erreur du serveur (ServerTimeout) : erreur lors de la création de "nginx.yaml" : Aucun jeton API
trouvé pour le compte de service "default", réessayez aprÚs que le jeton soit
automatiquement créé et ajouté au compte de service.

MĂȘme lorsque nous avons créé le compte de service manuellement, le jeton d'authentification n'est pas gĂ©nĂ©rĂ©. En continuant Ă  expĂ©rimenter avec notre "cluster" minimaliste, nous dĂ©couvrirons que la plupart des choses utiles qui se produisent gĂ©nĂ©ralement automatiquement seront absentes. Le serveur API Kubernetes est assez minimaliste, la plupart des configurations lourdes automatiques se produisent dans divers contrĂŽleurs et tĂąches d'arriĂšre-plan qui ne sont pas encore exĂ©cutĂ©es.

Nous pouvons contourner ce problÚme en définissant l'option automountServiceAccountToken pour le compte de service (puisque nous n'avons pas besoin de l'utiliser) :

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configuré
$ ./kubectl apply -f nginx.yaml
pod/nginx créé
$ ./kubectl get pods
NOM    PRÊT   STATUT    RESTARTS   ÂGE
nginx   0/1     En attente   0          13m

Enfin, le pod est apparu ! Mais en fait, il ne dĂ©marrera pas, car nous n'avons pas de planificateur (scheduler) - un autre composant important de Kubernetes. Encore une fois, nous voyons que l'API Kubernetes est Ă©tonnamment « bĂȘte » - lorsque vous crĂ©ez un pod dans l'API, il l'enregistre, mais n'essaie pas de dĂ©terminer sur quel nƓud le lancer.

En rĂ©alitĂ©, pour exĂ©cuter un pod, le planificateur n'est pas nĂ©cessaire. Vous pouvez ajouter manuellement le nƓud dans le manifeste en utilisant le paramĂštre nodeName:

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

(Remplacez mink8s par le nom du nƓud.) AprĂšs suppression et application, nous voyons que nginx a dĂ©marrĂ© et Ă©coute l'adresse IP interne :

$ ./kubectl delete pod nginx
pod "nginx" supprimé
$ ./kubectl apply -f nginx.yaml
pod/nginx créé
$ ./kubectl get pods -owide
NOM    PRÊT   ÉTAT    REDÉMARRAGES   ÂGE   IP           NŒUD     NŒUD NOMMÉ   PORTES DE PRÊT
nginx   1/1     En cours d'exécution   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Bienvenue sur nginx!</title>

Pour s'assurer que le réseau entre les pods fonctionne correctement, nous pouvons exécuter curl depuis un autre pod :

$ chat &lt;&lt;EOS | .\/kubectl apply -f -\napiversion: v1\nkind: Pod\nmetadata:\n  name: curl\nspec:\n  containers:\n  - image: curlimages\/curl\n    name: curl\n    command: ["curl", "172.17.0.2"]\n  nodeName: mink8s\nEOS\npod\/curl créé\n$ .\/kubectl logs curl | head -6\n  % Total    % Reçu % Transf.  Vitesse Moyenne   Temps    Temps     Temps  Actuel\n                                 Dload  Upload   Total   Dépensé   Restant  Vitesse
<!DOCTYPE html>
<html>
<head>
<title>Bienvenue sur nginx!</title>

C'est assez intéressant d'explorer cet environnement et de voir ce qui fonctionne et ce qui ne fonctionne pas. J'ai découvert que ConfigMap et Secret fonctionnent comme prévu, mais Service et Deployment ne fonctionnent pas.

SuccĂšs !

Ce post devient long, donc je vais annoncer le gagnant et dĂ©clarer que c'est une configuration viable qui peut ĂȘtre appelĂ©e "Kubernetes". En rĂ©sumĂ© : quatre fichiers binaires, cinq paramĂštres de ligne de commande et "juste" 45 lignes YAML (pas tant que ça par rapport aux normes de Kubernetes) et nous avons beaucoup de choses qui fonctionnent :

  • Les pods sont gĂ©rĂ©s via l'API Kubernetes standard (avec quelques hacks)
  • On peut charger des images de conteneurs publiques et les gĂ©rer
  • Les pods restent actifs et redĂ©marrent automatiquement
  • Le rĂ©seau entre les pods sur un mĂȘme nƓud fonctionne assez bien
  • ConfigMap, Secret et le montage de stockage le plus simple fonctionnent comme prĂ©vu

Mais la grande partie de ce qui rend Kubernetes vraiment utile est toujours absente, par exemple :

  • Le planificateur de pods
  • Authentification / autorisation
  • Plusieurs nƓuds
  • RĂ©seau de services
  • DNS interne de cluster
  • ContrĂŽleurs pour les comptes de service, les dĂ©ploiements, l'intĂ©gration avec les fournisseurs de cloud et la plupart des autres "bonus" que Kubernetes apporte

Alors, qu'avons-nous rĂ©ellement obtenu ? L'API Kubernetes, fonctionnant indĂ©pendamment, est en fait juste une plateforme pour l'automatisation des conteneurs. Elle ne fait pas beaucoup — c'est le travail de divers contrĂŽleurs et opĂ©rateurs utilisant l'API — mais elle fournit un environnement cohĂ©rent pour l'automatisation.

Pour en savoir plus sur le cours lors du webinaire gratuit.

Lire plus :

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster