Kubernetes minimum viable

La traduction de l'article a été préparée à la veille du début du cours "Pratiques et outils DevOps".

Kubernetes minimum viable

Si vous lisez ceci, vous avez probablement entendu parler de Kubernetes (et sinon, comment êtes-vous arrivé ici ?) Mais qu'est-ce que Kubernetes exactement ? Ce « Orchestration de conteneurs de qualité industrielle »? Ou "Système d'exploitation cloud natif"? Qu’est-ce que cela signifie ?

Pour être honnête, je ne suis pas sûr à 100%. Mais je pense qu'il est intéressant de creuser les entrailles et de voir ce qui se passe réellement dans Kubernetes sous ses nombreuses couches d'abstractions. Alors juste pour le plaisir, regardons à quoi ressemble réellement un « cluster Kubernetes » minimal. (Ce sera beaucoup plus facile que Kubernetes à la dure.)

Je suppose que vous avez des connaissances de base sur Kubernetes, Linux et les conteneurs. Tout ce dont nous parlons ici est uniquement à des fins de recherche/apprentissage, n’en mettez rien en production !

vue d'ensemble

Kubernetes contient de nombreux composants. Selon Wikipedia, l'architecture ressemble à ceci :

Kubernetes minimum viable

Il y a au moins huit composants présentés ici, mais nous ignorerons la plupart d'entre eux. Je tiens à préciser que la chose minimale que l'on peut raisonnablement appeler Kubernetes se compose de trois composants principaux :

  • Kubelet
  • kube-apiserver (qui dépend d'etcd - sa base de données)
  • runtime du conteneur (Docker dans ce cas)

Voyons ce que dit la documentation sur chacun d'eux (Russe., Anglais.). D'abord Kubelet:

Un agent exécuté sur chaque nœud du cluster. Il garantit que les conteneurs s'exécutent dans le pod.

Cela semble assez simple. Qu'en est-il de environnements d'exécution des conteneurs (exécution du conteneur) ?

Un runtime de conteneur est un programme conçu pour exécuter des conteneurs.

Très instructif. Mais si vous connaissez 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 le runtime du conteneur et le kubelet sont en fait assez subtils et je n'y reviendrai pas ici.)

И Serveur API?

API Server est le composant du panneau de configuration Kubernetes qui expose l'API Kubernetes. Le serveur API est le côté client du panneau de configuration Kubernetes

Quiconque a déjà fait quoi que ce soit avec Kubernetes a dû interagir avec l'API soit directement, soit via kubectl. C'est le cœur de ce qui fait de Kubernetes Kubernetes - le cerveau qui transforme les montagnes de YAML que nous connaissons et aimons tous (?) en infrastructure fonctionnelle. Il semble évident que l'API soit présente dans notre configuration minimale.

Prérequis

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

Installation ennuyeuse

Nous devons installer Docker sur la machine que nous utiliserons. (Je ne vais pas entrer dans les détails sur le fonctionnement de Docker et des conteneurs ; si cela vous intéresse, il y a articles merveilleux). Installons-le simplement avec apt:

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

Après cela, nous devons récupérer les binaires Kubernetes. En effet, pour le premier lancement de notre « cluster », il nous suffit kubelet, puisque pour exécuter d'autres composants du serveur, nous pouvons utiliser kubelet. Pour interagir avec notre cluster après son exécution, 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 passe-t-il si nous courons kubelet?

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

kubelet doit s'exécuter en tant que root. Assez logique, puisqu'il doit gérer l'ensemble du nœud. Regardons ses paramètres :

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

Wow, tellement d'options ! Heureusement, nous n’en avons besoin que de quelques-uns. Voici un des paramètres qui nous intéresse :

--pod-manifest-path string

Chemin d'accès au répertoire contenant les fichiers des pods statiques ou chemin d'accès à un fichier décrivant les pods statiques. Les fichiers commençant par des points sont ignorés. (OBSERVÉ : cette option doit être définie dans le fichier de configuration transmis au Kubelet via l'option --config. Pour plus d'informations, consultez kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Cette option nous permet d'exécuter modules statiques — des pods qui ne sont pas gérés via l'API Kubernetes. Les pods statiques sont rarement utilisés, mais ils sont très pratiques pour monter rapidement un cluster, et c'est exactement ce dont nous avons besoin. Nous ignorerons ce gros avertissement (encore une fois, ne l'exécutez pas en production !) et verrons si nous pouvons faire fonctionner le pod.

Nous allons d’abord créer un répertoire pour les pods statiques et exécuter kubelet:

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

Ensuite, dans un autre terminal/fenêtre tmux/quoi que ce soit, nous créerons un manifeste de pod :

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

kubelet commence à écrire des avertissements et il semble que rien ne se passe. Mais ce n'est pas vrai ! Regardons 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 J'ai lu le manifeste du pod et donné à Docker la commande de lancer quelques conteneurs selon nos spécifications. (Si vous vous interrogez sur le conteneur "pause", c'est un hack Kubernetes - voir ce blog.) Kubelet lancera notre conteneur busybox avec la commande spécifiée et le redémarrera indéfiniment jusqu'à ce que le pod statique soit supprimé.

Félicitez-vous. Nous venons de trouver l’une des façons les plus déroutantes d’afficher du texte sur le terminal !

Lancer etcd

Notre objectif ultime est d'exécuter l'API Kubernetes, mais pour ce faire, nous devons d'abord exécuter etcd. Commençons un cluster etcd minimal en plaçant ses paramètres 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, ces fichiers YAML devraient vous être familiers. Il n’y a que deux points à noter ici :

Nous avons monté le dossier hôte /var/lib/etcd dans le pod afin que les données etcd soient préservées après un redémarrage (si cela n'est pas fait, l'état du cluster sera effacé à chaque redémarrage du pod, ce qui ne sera pas bon même pour une installation minimale de Kubernetes).

Nous avons installé hostNetwork: true. Sans surprise, ce paramètre configure etcd pour utiliser le réseau hôte au lieu du réseau interne du pod (cela permettra au serveur API de trouver plus facilement le cluster etcd).

Une simple vérification montre qu'etcd s'exécute effectivement sur localhost et enregistre les données sur le 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émarrage du serveur API

Exécuter un serveur API Kubernetes est encore plus simple. Le seul paramètre à passer est --etcd-servers, fait ce que 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 le serveur API démarrera. Vérifier avec curl montre que l'API Kubernetes écoute sur le port 8080 avec un accès totalement ouvert - aucune authentification 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 l'exécutez pas en production ! J'ai été un peu surpris que le paramètre par défaut soit si peu sécurisé. Mais je suppose que c'est pour faciliter le développement et les tests.)

Et, agréable surprise, kubectl fonctionne immédiatement sans aucun paramètre 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.

problème

Mais si l’on creuse un peu plus, 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 fait, notre nœud kubelet n’est pas du tout découvert :

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

Quel est le problème? Si vous vous souvenez d'il y a quelques paragraphes, nous avons démarré le kubelet avec un ensemble extrêmement simple de paramètres de ligne de commande, donc le kubelet ne sait pas comment contacter le serveur API et l'informer de son état. Après étude de la documentation, on retrouve le flag correspondant :

--kubeconfig string

Le chemin d'accès au fichier kubeconfig, qui spécifie comment se connecter au serveur API. Disponibilité --kubeconfig active le mode serveur API, non --kubeconfig active le mode hors ligne.

Pendant tout ce temps, sans le savoir, nous faisions fonctionner le kubelet en « mode hors ligne ». (Si nous étions pédants, nous pourrions considérer un kubelet autonome comme un "Kubernetes minimum viable", mais ce serait très ennuyeux). Pour que la "vraie" configuration fonctionne, nous devons transmettre le fichier kubeconfig au kubelet afin qu'il sache comment parler au serveur API. Heureusement, c'est assez simple (puisque nous n'avons aucun problème d'authentification ou de certificat) :

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 ceci sous kubeconfig.yaml, tue le processus kubelet et redémarrez avec les paramètres nécessaires :

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

(D'ailleurs, si vous essayez d'accéder à l'API via curl lorsque le kubelet n'est pas en cours d'exécution, vous constaterez qu'il est toujours en cours d'exécution ! Kubelet n'est pas un « parent » de ses pods comme Docker, il s'agit plutôt d'un « contrôle » démon." Les conteneurs gérés par un kubelet continueront à fonctionner jusqu'à ce que le kubelet les arrête.)

Dans quelques minutes kubectl devrait nous montrer les pods et les nœuds comme prévu :

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

Cette fois-ci, félicitons-nous vraiment (je sais que je me suis déjà félicité) : nous avons un "cluster" Kubernetes minimal fonctionnant avec une API entièrement fonctionnelle !

Nous lançons sous

Voyons maintenant de quoi l'API est capable. Commençons par le pod nginx :

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

Ici, nous obtenons une erreur plutôt intéressante :

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

Nous voyons ici à quel point notre environnement Kubernetes est terriblement incomplet : nous n'avons aucun compte pour les services. Réessayons en créant manuellement un compte de service et voyons ce qui se passe :

$ 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

Même lorsque nous créons manuellement le compte de service, le jeton d'authentification n'est pas généré. À mesure que nous continuons à expérimenter notre « cluster » minimaliste, nous constaterons que la plupart des éléments utiles qui se produisent habituellement automatiquement seront manquants. Le serveur API Kubernetes est assez minimaliste, la plupart des tâches lourdes et de la configuration automatique étant effectuées dans divers contrôleurs et tâches en arrière-plan qui ne sont pas encore en cours d'exécution.

Nous pouvons contourner ce problème en définissant l'option automountServiceAccountToken pour le compte de service (puisque nous n'aurons pas à l'utiliser de toute façon) :

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

Enfin, le pod est apparu ! Mais en fait, ça ne démarre pas parce que nous n'avons pas planificateur (planificateur) est un autre composant important de Kubernetes. Encore une fois, nous voyons que l'API Kubernetes est étonnamment « stupide » : lorsque vous créez un pod dans l'API, elle l'enregistre, mais n'essaie pas de déterminer sur quel nœud l'exécuter.

En fait, vous n'avez pas besoin d'un planificateur pour exécuter un pod. Vous pouvez ajouter manuellement un nœud au manifeste dans le paramètre nodeName:

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

(Remplacer mink8s au 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" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Pour nous assurer que le réseau entre les pods fonctionne correctement, nous pouvons exécuter curl depuis un autre 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    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

C'est assez intéressant de se plonger dans cet environnement et de voir ce qui fonctionne et ce qui ne fonctionne pas. J'ai trouvé que ConfigMap et Secret fonctionnent comme prévu, mais pas le service et le déploiement.

Succès!

Ce post devient long, je vais donc crier victoire et dire que c'est une configuration viable que l'on peut appeler "Kubernetes". Pour résumer : quatre binaires, cinq paramètres de ligne de commande et "seulement" 45 lignes de YAML (pas autant par rapport aux standards Kubernetes) et nous avons pas mal de choses qui fonctionnent :

  • Les pods sont gérés à l'aide de l'API Kubernetes standard (avec quelques hacks)
  • Vous pouvez télécharger et gérer des images de conteneurs publics
  • Les pods restent en vie et redémarrent automatiquement
  • La mise en réseau entre les pods au sein d'un même nœud fonctionne plutôt bien
  • ConfigMap, le montage du stockage secret et simple fonctionne comme prévu

Mais une grande partie de ce qui rend Kubernetes vraiment utile manque encore, comme :

  • Planificateur de pods
  • Autorisation d'authentification
  • Plusieurs nœuds
  • Réseau de services
  • DNS interne en cluster
  • Contrôleurs pour les comptes de service, les déploiements, l'intégration avec les fournisseurs de cloud et la plupart des autres avantages apportés par Kubernetes

Alors, qu’avons-nous réellement obtenu ? L'API Kubernetes, fonctionnant seule, n'est en réalité qu'une plate-forme pour automatisation des conteneurs. Cela ne fait pas grand-chose - c'est un travail pour différents contrôleurs et opérateurs utilisant l'API - mais cela fournit un environnement cohérent pour l'automatisation.

Apprenez-en davantage sur le cours grâce au webinaire gratuit.

Lire la suite:

Source: habr.com

Ajouter un commentaire