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