Minimum de Kubernetes viables

La traduction de l'article a été préparée avant le début du cours «DevOps Practices and Tools» .










Si vous lisez ceci, vous avez probablement entendu quelque chose sur Kubernetes (et sinon, comment ĂȘtes-vous arrivĂ© ici?) Mais qu'est-ce que Kubernetes exactement? S'agit-il d'une «orchestration de conteneurs de qualitĂ© industrielle» ? Ou "systĂšme d'exploitation cloud natif" ? Qu'est-ce que cela signifie de toute façon?



Pour ĂȘtre honnĂȘte, je ne suis pas sĂ»r Ă  100%. Mais je pense qu'il est intĂ©ressant de fouiller dans les entrailles et de voir ce qui se passe rĂ©ellement dans Kubernetes sous ses nombreuses couches d'abstraction. Alors juste pour le plaisir, voyons Ă  quoi ressemble rĂ©ellement un "cluster Kubernetes" minimal. (Ce sera beaucoup plus facile que Kubernetes The Hard Way .)



Je suppose que vous avez une connaissance de base de Kubernetes, Linux et des conteneurs. Tout ce dont nous allons parler ici est pour la recherche / Ă©tude uniquement, ne lancez rien de tout cela en production!



Aperçu



Kubernetes contient de nombreux composants. Selon wikipedia , l'architecture ressemble Ă  ceci:







il y a au moins huit composants montrés ici, mais nous ignorerons la plupart d'entre eux. Je tiens à préciser que la plus petite chose que l'on puisse raisonnablement appeler Kubernetes a trois composants principaux:



  • kubelet
  • kube-apiserver (qui dĂ©pend de etcd - sa base de donnĂ©es)
  • runtime du conteneur (dans ce cas Docker)


Voyons ce que dit la documentation sur chacun d'eux ( russe , anglais ). Tout d'abord le kubelet :



un agent s'exĂ©cutant sur chaque nƓud du cluster. Il s'assure que les conteneurs fonctionnent dans le pod.



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



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



TrÚs instructif. Mais si vous connaissez Docker, vous devez avoir une compréhension de base de ce qu'il fait. (Les détails de la séparation des préoccupations entre le runtime du conteneur et le kubelet sont en fait assez subtils et je ne vais pas y entrer ici.)



Et l'API du serveur ?



Serveur d'API - Un composant de tableau de bord Kubernetes qui représente l'API Kubernetes. Le serveur API est le frontal du tableau de bord Kubernetes.



Quiconque a dĂ©jĂ  fait quelque chose avec Kubernetes a dĂ» interagir avec l'API directement ou 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 une infrastructure fonctionnelle. Il semble Ă©vident que l'API devrait ĂȘtre prĂ©sente dans notre configuration minimale.



Conditions préalables



  • Machine virtuelle ou physique Linux enracinĂ©e (j'utilise Ubuntu 18.04 dans une machine virtuelle).
  • Et c'est tout!


Installation ennuyeuse



Docker doit ĂȘtre installĂ© sur la machine que nous utiliserons. (Je ne vais pas entrer dans les dĂ©tails sur le fonctionnement de Docker et des conteneurs; il existe d' excellents articles si vous ĂȘtes intĂ©ressĂ© ). Installons-le simplement avec apt:



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


AprÚs cela, nous devons obtenir les binaires Kubernetes. En fait, pour le lancement initial de notre "cluster", nous n'en avons besoin que kubelet, puisque nous pouvons l'utiliser pour lancer d'autres composants serveur kubelet. Pour interagir avec notre cluster une fois qu'il est 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 passe-t-il si nous nous contentons de lancer kubelet?



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


kubeletdevrait ĂȘtre exĂ©cutĂ© en tant que root. C'est assez logique, car il doit gĂ©rer tout le nƓud. Jetons un coup d'Ɠil Ă  ses paramĂštres:



$ ./kubelet -h
<  ,   >
$ ./kubelet -h | wc -l
284


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



--pod-manifest-path string


Chemin du rĂ©pertoire contenant les fichiers des pods statiques ou chemin du fichier dĂ©crivant les pods statiques. Les fichiers commençant par des points sont ignorĂ©s. (DEPRECATED: ce paramĂštre doit ĂȘtre dĂ©fini dans le fichier de configuration transmis Ă  Kubelet via l'option --config. Pour plus d'informations, consultez kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)



Ce paramÚtre nous permet d'exécuter statique Pods - 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 élever rapidement un cluster, et c'est exactement ce dont nous avons besoin. Nous ignorerons cet avertissement bruyant (encore une fois, ne l'exécutez pas en production!) Et voyons si nous pouvons courir sous.



Tout d'abord, nous allons 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 / ailleurs, nous allons crĂ©er 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


kubeletcommence Ă  Ă©crire des avertissements et il semble que rien ne se passe. Mais ce n'est pas le cas! Jetons un coup d'Ɠil Ă  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!


kubeletLisez le manifeste du pod et demandez Ă  Docker d'exĂ©cuter quelques conteneurs selon nos spĂ©cifications. (Si vous ĂȘtes curieux de connaĂźtre le conteneur «pause», il s'agit du piratage de Kubernetes - consultez ce blog pour plus de dĂ©tails .) Kubelet lancera notre conteneur busyboxavec 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'un des moyens les plus compliqués de sortir du texte sur le terminal!



Exécutez etcd



Notre objectif ultime est d'exécuter l'API Kubernetes, mais pour cela, nous devons d'abord exécuter etcd . Commençons par 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 choses Ă  noter ici:



Nous avons montĂ© le dossier hĂŽte /var/lib/etcddans le pod afin que les donnĂ©es etcd soient enregistrĂ©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 serait mauvais mĂȘme pour une installation minimale de Kubernetes).



Nous avons installé hostNetwork: true. Cette option, sans surprise, 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 fonctionne 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


Lancement du serveur API



Le dĂ©marrage du serveur d'API Kubernetes est encore plus simple. Le seul paramĂštre qui doit ĂȘtre passĂ© --etcd-serversfait 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 podset le serveur API démarrera. La vérification à l'aide de l'aide curlmontre que l'API Kubernetes écoute sur le port 8080 avec un accÚs entiÚrement 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ûr. Mais je suppose que c'est pour faciliter le développement et les tests.)



Et, agréablement, kubectl fonctionne hors de la boßte sans aucun extras. réglages!



$ ./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 vous creusez un peu plus profondément, alors 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 sont partis! En fait, notre nƓud kubelet n'apparaĂźt pas du tout:



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


Quel est le problĂšme? Si vous vous en souvenez, il y a quelques paragraphes, nous avons lancĂ© kubelet avec un ensemble extrĂȘmement simple de paramĂštres de ligne de commande, donc kubelet ne sait pas comment contacter le serveur API et l'informer de son Ă©tat. AprĂšs avoir examinĂ© la documentation, nous trouvons l'indicateur correspondant:



--kubeconfig string



Le chemin d'accÚs au fichier kubeconfig, qui indique comment se connecter au serveur API. La présence --kubeconfigactive le mode serveur API, l'absence --kubeconfigactive le mode hors ligne.



Pendant tout ce temps, sans le savoir, nous exécutions kubelet en "mode hors ligne". (Si nous étions pédants, nous pourrions considérer le mode autonome de kubelet comme «un Kubernetes minimum viable», mais ce serait trÚs ennuyeux). Pour que la configuration "réelle" fonctionne, nous devons transmettre le fichier kubeconfig au kubelet afin qu'il sache comment communiquer avec le serveur API. Heureusement, c'est assez simple (puisque nous n'avons aucun problÚme avec l'authentification ou les 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-le sous kubeconfig.yaml, arrĂȘtez le processus kubeletet redĂ©marrez avec les paramĂštres requis:



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


(Au fait, si vous essayez d'accĂ©der Ă  l'API avec curl lorsque le kubelet est en panne, vous constaterez que cela fonctionne toujours! Kubelet n'est pas le "parent" de ses pods, comme Docker, il ressemble plus Ă  un "dĂ©mon de contrĂŽle". Les conteneurs gĂ©rĂ©s par le kubelet fonctionneront jusqu'Ă  ce que le kubelet les arrĂȘte.)



AprĂšs quelques minutes, kubectldevrait 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


Félicitons-nous vraiment cette fois (je sais que j'ai déjà félicité) - nous avons un "cluster" Kubernetes minimal fonctionnant avec une API entiÚrement fonctionnelle!



Courir 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 assez 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 horriblement incomplet - nous n'avons pas de comptes de service. Essayons à nouveau 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 avons crĂ©Ă© le compte de service manuellement, aucun jeton d'authentification n'est gĂ©nĂ©rĂ©. Au fur et Ă  mesure que nous continuons Ă  expĂ©rimenter avec notre «cluster» minimaliste, nous constaterons que la plupart des choses utiles qui se produisent gĂ©nĂ©ralement automatiquement seront manquantes. Le serveur API Kubernetes est assez minimaliste, la plupart des gros ajustements automatiques en cours 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 une option automountServiceAccountTokenpour 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, sous est apparu! Mais en fait, il ne dĂ©marrera pas, car nous n'avons pas de planificateur (planificateur) - un autre composant important de Kubernetes. Encore une fois, nous pouvons voir 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.



Vous n'avez pas rĂ©ellement besoin d'un planificateur pour exĂ©cuter un pod. Vous pouvez ajouter manuellement le nƓud au manifeste dans le paramĂštre nodeName:



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


(Remplacez mink8spar le nom d'hÎte.) AprÚs suppression et application, nous voyons que nginx a démarré et écoute sur une 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 vérifier que le réseau entre les pods fonctionne correctement, nous pouvons exécuter curl à partir d'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 amusant de fouiller 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 message prend de l'ampleur, je vais donc annoncer la victoire et déclarer qu'il s'agit d'une configuration viable pour appeler «Kubernetes». Pour résumer: quatre binaires, cinq paramÚtres de ligne de commande et «juste» 45 lignes de YAML (pas beaucoup selon les normes Kubernetes) et nous avons beaucoup 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 seul nƓud fonctionne plutĂŽt bien
  • ConfigMap, le montage secret et le plus simple des rĂ©fĂ©rentiels fonctionne comme prĂ©vu


Mais la plupart des éléments qui rendent Kubernetes vraiment utile manquent toujours, par exemple:



  • Planificateur de pod
  • Autorisation d'authentification
  • Plusieurs nƓuds
  • RĂ©seau de service
  • DNS interne en cluster
  • Des contrĂŽleurs pour les comptes de service, les dĂ©ploiements, les intĂ©grations de fournisseurs de cloud et la plupart des autres avantages que Kubernetes apporte


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



Apprenez-en plus sur le cours dans un webinaire gratuit.






Lire la suite:






All Articles