Blog de dada

DevOps, bidouilleur et routard plein de logiciels libres

Archives 2018

Accéder au cluster k8s depuis l'extérieur

Rédigé par dada / 09 novembre 2018 / Aucun commentaire



Perdu ?

On vient de se battre pour mettre en place un cluster Ceph opérationnel, histoire de stocker ses données dans le cluster k8s et de les répliquer un peu. Maintenant que c'est bon, si on s'attaquait à des choses un peu plus visible, hors dashboard ?
Au programme de ce billet : Ingress, Service, Wordpress, des nouveaux pods.

Ingress Nginx

Cette chose-là va être le point d'entrée de notre cluster. Il va permettre à l'extérieur d'aller regarder à l'intérieur. Son rôle va être de comprendre la requête d'entrée et de l'orienter vers le pod qui y répondra. Dans le cas qui va suivre, aller sur l'IP du cluster pour y retrouver un Wordpress.

Installation via Helm

Comme on s'en sert déjà, on va continuer :
helm install stable/nginx-ingress --name my-nginx
Cette installation va faire apparaître les pods suivants :
dada@k8smaster:~$ kubectget pods -n default
NAME                                                      READY   STATUS    RESTARTS   AGE
my-nginx-nginx-ingress-controller-565bc9555b-pcqjz        1/1     Running   2          8h
my-nginx-nginx-ingress-default-backend-5bcb65f5f4-728tx   1/1     Running   2          8h
Et un service. On n'a pas encore parlé de services : je vous redirige vers la documentation officielle si vous voulez en savoir bien plus.
dada@k8smaster:~$ kubectl --namespace default get services -o wide -w my-nginx-nginx-ingress-controller
NAME                                TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE   SELECTOR
my-nginx-nginx-ingress-controller   LoadBalancer   10.100.45.147   <pending>     80:32462/TCP,443:30337/TCP   8s    app=nginx-ingress,component=controller,release=my-nginx
Ça veut dire que l'Ingress Controller est bien installé. Notez le <pending> : ça veut dire que notre controller n'a pas d'IP externe. En gros : il ne sert à rien. Il lui faut une IP pour que nous puissions l'attaquer.
Dans une installation chez des gros fournisseurs d'informatique dans les nuages, nous aurions une sorte de plugin nous fournissant déjà l'IP tant désirée. Avec notre infrastructure sous Virtualbox, il va falloir faire autrement. Et autrement, ça veut dire avec MetalLB.

MetalLB

MetalLB, ce sont des pods que nous allons installer dans notre cluster et qui nous serviront à palier le problème cité juste au dessus. Il ira interroger notre DHCP, la Freebox dans mon cas, pour récupérer une IP publique.

Installer MetalLB

Toujours avec Helm :
helm install --name metallb stable/metallb
Un peu d'attente et vous devriez voir de nouveaux pods :
dada@k8smaster:~$ kubectl get pods -n metallb-system
NAME                         READY   STATUS    RESTARTS   AGE
controller-765899887-ck6bv   1/1     Running   2          8h
speaker-jdqg9                1/1     Running   2          8h
speaker-t4vtk                1/1     Running   2          8h
Il lui manque un fichier de configuration pour nous offrir une adresse IP externe :
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: default
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.0.50-192.168.0.60
Les adresses IP en bas de ce fichier de conf vont permettre à MetalLB d'aller titiller notre DHCP (Freebox pour moi) pour obtenir une adresse. Quelque chose entre 192.168.0.50 et 192.168.0.60 dans mon cas. Vérifiez bien la configuration de votre Freebox avant de choisir une plage d'IP.

On l'applique :
kubectl create -f config.yaml 

Retour à l'Ingress Nginx

Une fois après avoir configuré MetalLB, vous allez vérifier l'état du service :
dada@k8smaster:~/$ kubectl --namespace default get services -o wide -w my-nginx-nginx-ingress-controller
NAME                                TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE    SELECTOR
my-nginx-nginx-ingress-controller   LoadBalancer   10.100.45.147   192.168.0.50   80:32462/TCP,443:30337/TCP   3d3h   app=nginx-ingress,component=controller,release=my-nginx
Vous avez vu ? Une EXTERNAL-IP ! On va pouvoir taper dessus !

La suite ?


Rook pour gérer le système de fichiers

Rédigé par dada / 08 novembre 2018 / 4 commentaires


Un cluster k8s, c'est presque "out-of-the-box" quand on veut s'en servir pour lancer des conteneurs qui n'amènent avec eux qu'un traitement. En gros, votre conteneur contient un script python qui va afficher 42 fois le message "toto" et mourir. C'est un exemple idiot, mais ce n'est pas un détail.
Dans le cadre de ce tuto qui me sert à préparer la migration de ce blog dans un cluster k8s, j'ai besoin de pouvoir stocker le contenu de mes billets quelque part. Le mettre dans un conteneur est une aberration et sera synonyme de perte de données quand celui-ci mourra.

Pour m'en sortir, on m'a parlé de Rook.io, un système de cluster Ceph intégré à Kubernetes. On va voir comment s'en servir maintenant.

Installer Rook

Installer les dépendances

Il va falloir installer des paquets sur vos membres du cluster. À l'exception du Master.
apt-get install ceph-fs-common ceph-common

Récupérer le binaire RBD

cd /bin  
sudo curl -O https://raw.githubusercontent.com/ceph/ceph-docker/master/examples/kubernetes-coreos/rbd 
sudo chmod +x /bin/rbd 
rbd #Command to download ceph images
On va partir sur la version beta de l'outil. C'est la plus stable.

Ajouter le dépôt Rook dans Helm

dada@k8smaster:~$ helm repo add rook-beta https://charts.rook.io/beta
"rook-beta" has been added to your repositories

Récupérer Rook

À la manière de APT :
dada@k8smaster:~$ helm install --namespace rook-ceph-system rook-beta/rook-ceph
NAME:   torrid-dragonfly
LAST DEPLOYED: Sun Nov  4 11:22:24 2018
NAMESPACE: rook-ceph-system
STATUS: DEPLOYED
Le message est bien plus long. Je vous invite à prendre le temps de le décortiquer pour y repérer les infos intéressantes.

On peut y récupérer la commande pour valider la présence du pod Operator :
dada@k8smaster:~$ kubectl --namespace rook-ceph-system get pods -l "app=rook-ceph-operator"
NAME                                 READY   STATUS    RESTARTS   AGE
rook-ceph-operator-f4cd7f8d5-zt7f4   1/1     Running   0          2m25
On y trouve aussi les pods nécessaires au cluster dans nos différents nodes :
dada@k8smaster:~$ kubectl get pods --all-namespaces -o wide | grep rook
rook-ceph-system   rook-ceph-agent-pb62s                   1/1     Running   0          4m10s   192.168.0.30   k8snode1    <none
rook-ceph-system   rook-ceph-agent-vccpt                   1/1     Running   0          4m10s   192.168.0.18   k8snode2    <none>
rook-ceph-system   rook-ceph-operator-f4cd7f8d5-zt7f4      1/1     Running   0          4m24s   10.244.2.62    k8snode2    <none>
rook-ceph-system   rook-discover-589mf                     1/1     Running   0          4m10s   10.244.2.63    k8snode2    <none>
rook-ceph-system   rook-discover-qhv9q                     1/1     Running   0          4m10s   10.244.1.232   k8snode1    <none>
On a la base ! Vous devriez avoir un agent et un discover par node. L'operator, lui, reste tout seul.

Création du cluster

Maintenant que Rook tourne, il lui faut lui faire comprendre que nous voulons un cluster qui va bien pour nos volumes.

Je reprends ci-dessous l'exemple de la documentation : ça marche bien comme ça.
#################################################################################
# This example first defines some necessary namespace and RBAC security objects.
# The actual Ceph Cluster CRD example can be found at the bottom of this example.
#################################################################################
apiVersion: v1
kind: Namespace
metadata:
  name: rook-ceph
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rook-ceph-cluster
  namespace: rook-ceph
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rook-ceph-cluster
  namespace: rook-ceph
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: [ "get", "list", "watch", "create", "update", "delete" ]
---
# Allow the operator to create resources in this cluster's namespace
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rook-ceph-cluster-mgmt
  namespace: rook-ceph
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: rook-ceph-cluster-mgmt
subjects:
- kind: ServiceAccount
  name: rook-ceph-system
  namespace: rook-ceph-system
---
# Allow the pods in this namespace to work with configmaps
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rook-ceph-cluster
  namespace: rook-ceph
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: rook-ceph-cluster
subjects:
- kind: ServiceAccount
  name: rook-ceph-cluster
  namespace: rook-ceph
---
#################################################################################
# The Ceph Cluster CRD example
#################################################################################
apiVersion: ceph.rook.io/v1beta1
kind: Cluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    # For the latest ceph images, see https://hub.docker.com/r/ceph/ceph/tags
    image: ceph/ceph:v13.2.2-20181023
  dataDirHostPath: /var/lib/rook
  dashboard:
    enabled: true
  storage:
    useAllNodes: true
    useAllDevices: false
    config:
      databaseSizeMB: "1024"
      journalSizeMB: "1024"
On apply tout ça :
kubectl create -f cluster.yaml
Si tout s'est bien passé et que vous avez patienté quelques minutes, voici ce que vous devriez avoir maintenant :
dada@k8smaster:~/rook$ kubectl get pods -n rook-ceph -o wide 
NAME                                   READY   STATUS      RESTARTS   AGE     IP             NODE       NOMINATED NODE
rook-ceph-mgr-a-5f6dd98574-tm9md       1/1     Running     0          3m3s    10.244.2.126   k8snode2   <none>
rook-ceph-mon0-sk798                   1/1     Running     0          4m36s   10.244.1.42    k8snode1   <none>
rook-ceph-mon1-bxgjt                   1/1     Running     0          4m16s   10.244.2.125   k8snode2   <none>
rook-ceph-mon2-snznb                   1/1     Running     0          3m48s   10.244.1.43    k8snode1   <none>
rook-ceph-osd-id-0-54c856d49d-77hfr    1/1     Running     0          2m27s   10.244.1.45    k8snode1   <none>
rook-ceph-osd-id-1-7d98bf85b5-rt4jw    1/1     Running     0          2m26s   10.244.2.128   k8snode2   <none>
rook-ceph-osd-prepare-k8snode1-dzd5v   0/1     Completed   0          2m41s   10.244.1.44    k8snode1   <none>
rook-ceph-osd-prepare-k8snode2-2jgvg   0/1     Completed   0          2m41s   10.244.2.127   k8snode2   <none>

Créer le système de fichiers

Le cluster est en place, mais rien ne nous permet de l'utiliser. On va remédier à ça en lui assignant un système de fichiers. Pour l'exemple, j'ai choisi la méthode dite Block Storage. Elle permet de mettre en place des Persistent Volumes (PV) qui pourront être utilisés par un seul pod à la fois. S'il vous faut plusieurs pods accédant à un PV, dirigez-vous vers le Shared File System.

Pour que ça marche, créez la configuration de la StorageClass :
apiVersion: ceph.rook.io/v1beta1
kind: Pool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/bloc
parameters:
  pool: replicapool
  clusterNamespace: rook-ceph
On l'applique :
kubectl create -f storageclass.yaml
On est bon pour le système de fichiers !
On ne va pas créer de pod s'en servant pour le moment. Si vous y allez, vous ne pourrez rien en tirer puisque vous ne pouvez pas encore accéder à votre cluster depuis l'extérieur. C'est la prochaine étape.

Le Dashboard Ceph

Si vous avez pris le temps de lire la configuration du cluster proposée un peu plus haut, vous devriez avoir vu que le Dashboard est activé dans le yaml. Comme pour Kubernetes, Rook intègre un dashboard pour y voir un peu plus clair.

Il est "enable", mais pas activé. Corrigeons ça à l'aide en créant le fichier dashboard-external.yaml :
dada@k8smaster:~/rook$ cat dashboard-external.yaml 
apiVersion: v1
kind: Service
metadata:
  name: rook-ceph-mgr-dashboard-external
  namespace: rook-ceph
  labels:
    app: rook-ceph-mgr
    rook_cluster: rook-ceph
spec:
  ports:
  - name: dashboard
    port: 7000
    protocol: TCP
    targetPort: 7000
  selector:
    app: rook-ceph-mgr
    rook_cluster: rook-ceph
  sessionAffinity: None
  type: NodePort
Récupérez le port exposé :
dada@k8smaster:~/rook$ kubectl -n rook-ceph get service | grep Node
rook-ceph-mgr-dashboard-external   NodePort    10.99.88.135     <none>        7000:31165/TCP   3m41s
Et vous devriez pouvoir joindre le dashboard en utilisant l'IP du master et le port qui va bien : 31165 dans mon cas.

Si tout va bien, vous devriez pouvoir vous promener dans une page web qui ressemble à ça :

Considérations

La gestion du système de fichiers que je vous propose n'est pas sans risque. Les volumes que vous allez créer doivent être configurés sérieusement. Les exemples que vous aller trouver par-ci par-là vous permettront d'avoir un stockage dans votre cluster k8s, certes, mais rendront sans doute vos volumes dépendants de vos pods. Si vous décidez de supprimer le pod pour lequel vous avec un PVC, le PV disparaîtra, et vos données avec.

Prenez le temps de bien réfléchir et de bien plus étudier la question que ce que je vous propose dans mes billets avant de vous lancer dans une installation en production.

La suite ?


Se faciliter la vie avec Helm

Rédigé par dada / 06 novembre 2018 / Aucun commentaire

Perdu ? Retrouvez les épisodes précédents :

Helm ? Rien à voir avec le Gouffre du même nom. Il s'agit ici de passer par un outil permettant à l'utilisateur de k8s d'installer des pods en passant par son dépôt officiel. C'est une sorte de gestionnaire de paquet, un peu comme APT pour Debian. Ça nous facilite la vie et ça nous permet d'utiliser toute la puissance de l'orchestrateur sans se prendre la tête.

Installer Helm

Récupérer les binaires

Helm a besoin de son exécutable pour fonctionner. Allez le récupérer et déposez-le sur votre master :
dada@k8smaster:~$ mkdir helm
dada@k8smaster:~$ cd helm
dada@k8smaster:~/helm$ wget https://storage.googleapis.com/kubernetes-helm/helm-v2.11.0-linux-amd64.tar.gz
dada@k8smaster:~/helm$ tar -zxvf helm-v2.11.0-linux-amd64.tar.gz
Rendez-le utilisable
root@k8smaster:/home/dada/helm# mv linux-amd64/helm  /usr/local/bin/
Vous devriez maintenant pouvoir vous en servir avec votre utilisateur normal (pas root !)
helm version
Client: &version.Version{SemVer:"v2.11.0", GitCommit:"2e55dbe1fdb5fdb96b75ff144a339489417b146b", GitTreeState:"clean"}

Initialiser Helm

Tout simplement avec la commande helm init :
dada@k8smaster:~$ helm init
$HELM_HOME has been configured at /home/dada/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
Happy Helming, comme ils disent !

Si vous avez bien tapé la commande "helm version" donnée quelques lignes plus haut, vous devriez avoir remarqué l'erreur suivante :
Error: could not find tiller
Cachée volontairement jusque là, elle attendait l'initialisation pour disparaître !
dada@k8smaster:~$ helm version
Client: &version.Version{SemVer:"v2.11.0", GitCommit:"2e55dbe1fdb5fdb96b75ff144a339489417b146b", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.11.0", GitCommit:"2e55dbe1fdb5fdb96b75ff144a339489417b146b", GitTreeState:"clean"}
Un nouveau pod est maintenant apparu : tiller !
dada@k8smaster:~$ kubectl get pods --all-namespaces -o wide | grep tiller
kube-system   tiller-deploy-845cffcd48-268tr          1/1     Running   0          4m22s   10.244.2.61    k8snode2    <none>

Configurer les permissions

Je vais rappeler que nous sommes dans le cadre d'un cluster de test. C'est un détail important parce que je ne vais pas m'étendre avec les jeux de permission de Kubernetes. Pour aller plus vite, et pour permettre à Helm d'accéder à l'API à travers laquelle tout transite, je vais vous donner les commandes pour lui donner des droits d'admin. Ce n'est pas à faire en production !

Donner les droits admin à Helm

dada@k8smaster:~$ kubectl create serviceaccount --namespace kube-system tiller
dada@k8smaster:~$ kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
dada@k8smaster:~$ kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

Mettre à jour la configuration

dada@k8smaster:~$ helm init --service-account tiller --upgrade
$HELM_HOME has been configured at /home/dada/.helm.

Tiller (the Helm server-side component) has been upgraded to the current version.
Happy Helming
Helm devrait maintenant pouvoir s'amuser !

La suite ?

Installer Rook pour prendre en charge nos volumes persistants.

Le dashboard de Kubernetes

Rédigé par dada / 04 novembre 2018 / 2 commentaires


Perdu ? Retrouvez les épisodes précédents :

À la fin de la deuxième partie de ce tuto, je dis que les couleurs, c'est quand même mieux. Enfin, c'est surtout assez fatiguant de lutter avec un grand nombre de terminaux pour vérifier le bon comportement du cluster. Pour nous sauver, il y a un Dashboard.

Installer le Dashboard

Pour installer le Dashboard, placez-vous sur votre master et tapez la commande suivante :
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
Kubernetes va aller chercher la configuration nécessaire la mise à en place du Dashboard directement depuis son dépôt Github et le faire apparaître dans la liste des pods de votre cluster.
dada@k8smaster:~$  kubectl get pods --all-namespaces -o wide | grep dashb
kube-system   kubernetes-dashboard-77fd78f978-f8p9l   1/1     Running   0          60s     10.244.1.230   k8snode1    <none>
Il est  "Running", ça veut dire qu'il est disponible, mais pas encore accessible.

Créez un compte utilisateur

Créez un fichier admin-user.yaml avec ce qui suit dedans :
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
Puis créez le rôle qui lui sera attaché : admin-role.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
Chargez ces configurations dans le cluster :
kubectl apply -f admin-user.yaml
kubectl apply -f admin-role.yaml

Récupérer le token de connexion

Pour vous connecter au Dashboard, en plus d'avoir le pod et un utilisateur, il vous faut le token qui va bien. Pour le récupérer :
dada@k8smaster:~$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
Name:         admin-user-token-b8qmq
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: admin-user
              kubernetes.io/service-account.uid: a8a600b1-e010-11e8-96ec-0800273c4560

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJl.........
J'ai tronqué l'affichage du token. Il est d'une longueur dingue. Copiez-le dans un coin maintenant que vous l'avez.

Accéder au Dashboard

Le Dashboard n'est par défaut pas accessible en dehors du cluster. On peut cependant tricher en passant par un proxy et un tunnel SSH.

Le proxy

Ouvrez un nouveau terminal branché sur votre master et tapez la commande suivante :
dada@k8smaster:~$ kubectl proxy
Starting to serve on 127.0.0.1:8001

Le tunnel SSH

Depuis votre PC hôte, lancez le tunnel :
dada@dada-laptop:~$ ssh -L 8001:127.0.0.1:8001 dada@IP_DU_MASTER

Affichez le fameux tant attendu

Si tout s'est bien passé jusqu'ici, vous deviez pouvoir accéder au Dashboard via cette url :


Et voir ceci : 


Vous voici avec une belle interface pour admirer le comportement de votre cluster k8s. Foncez cliquer absolument partout et chercher le pourquoi du comment de telles options à tel endroit !

La suite ?

Démarrer notre cluster kubernetes

Rédigé par dada / 01 novembre 2018 / 4 commentaires



Vous ne comprenez pas d'où vient ce billet ? Retrouvez sa première partie !

Configurez le master

Le master, c'est la machine qui va gérer le cluster. Pour ce tutoriel, nous n'allons utiliser qu'un seul master. Pas la peine de s'ennuyer à aller plus loin pour le moment. Sachez tout de même que c'est une mauvaise pratique : s'il se casse la figure, vous êtes dans la mouise.

Initialisation

Pour initialiser le master (k8smaster), pour faire comprendre à la VM que c'est elle la cheffe, tapez la commande suivante :
kubeadm init --pod-network-cidr=10.244.0.0/16
Elle va initialiser le cluster et lui assigner une plage d'IP privée.

Si vous avez bien suivi la première partie de ce tuto, vous devriez voir afficher les choses (tronquées) suivantes :
root@k8smaster:/home/dada# kubeadm init --pod-network-cidr=10.244.0.0/16
[init] using Kubernetes version: v1.12.2
[preflight] running pre-flight checks
[preflight/images] Pulling images required for setting up a Kubernetes cluster
[preflight/images] This might take a minute or two, depending on the speed of your internet connection
[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'
[..]
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!
Le message affirmant que le master est bien initialisé parle de lui-même : on est bon ! Ou presque. Il faut faire ce que la suite du message vous demande.

Configurez l'utilisateur qui aura le droit de jouer avec k8s

Notez que jongler entre k8s et docker peut-être épuisant : certaines commandes se jouent en root et d'autres en simple utilisateur. Pensez à changer d'utilisateur si rien ne se passe ou si le terminal vous insulte.

On enchaîne donc avec :
To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Je vous laisse placer le fichier de configuration du cluster dans la home de votre utilisateur classique.

Configurer le réseau du cluster

La suite du message d'initialisation :
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/
k8s se sert d'un pod pour gérer son réseau. Il en existe plusieurs et celui que j'ai réussi à faire marcher du premier coup s'appelle Flannel.

Pour l'installer :
dada@k8smaster:~$ kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml
Et le retour qui confirme que tout va bien :
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created
Il est temps de vous montrer une première commande k8s que vous allez passer votre vie à utiliser :
kubectl get pods --all-namespaces -o wide
Elle vous afficher le namespace, le nom, la quantité, le statut, l'âge, le node hôte, etc, des pods de votre cluster. Exemple :
dada@k8smaster:~$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE
kube-system   coredns-576cbf47c7-84x8w            1/1     Running   0          11m   10.244.0.8     k8smaster   <none>
kube-system   coredns-576cbf47c7-v88p4            1/1     Running   0          11m   10.244.0.9     k8smaster   <none>
kube-system   etcd-k8smaster                      1/1     Running   0          79s   192.168.0.19   k8smaster   <none>
kube-system   kube-apiserver-k8smaster            1/1     Running   0          79s   192.168.0.19   k8smaster   <none>
kube-system   kube-controller-manager-k8smaster   1/1     Running   0          79s   192.168.0.19   k8smaster   <none>
kube-system   kube-flannel-ds-amd64-vzrx8         1/1     Running   0          90s   192.168.0.19   k8smaster   <none>
kube-system   kube-proxy-nn7p2                    1/1     Running   0          11m   192.168.0.19   k8smaster   <none>
kube-system   kube-scheduler-k8smaster            1/1     Running   0          78s   192.168.0.19   k8smaster   <none>
Comme seul le master est actif, vous ne voyez pour le moment que les pods system (les trucs de base) de votre cluster sur le node master. Normal.

Ajouter un node dans le cluster

Votre cluster n'a qu'un master. Chiant. Est-on certain de n'avoir qu'un seul node dans ce cluster ?
dada@k8smaster:~$ kubectl get nodes
NAME        STATUS   ROLES    AGE   VERSION
k8smaster   Ready    master   23m   v1.12.2
Oui, la commande get nodes le confirme. Ajoutons un peu d'action dans tout ça en lisant la fin du message d'initialisation :
You can now join any number of machines by running the following on each node
as root:

  kubeadm join 192.168.0.19:6443 --token wdjnql.rm60fa90l0o9qv49 --discovery-token-ca-cert-hash sha256:ede807fb6f732c00acb8d40a891c436aedd3ed88f915135df252c033d55b2e10
Note : ne perdez pas la dernière ligne ! Il faudra reset votre cluster et ainsi tout perdre si vous ne la sauvegardez pas !

Allez lancer une autre VM, disons le k8snode1, et exécutez la commande de join. Si tout va bien, vous devriez avoir le message suivant :
root@k8snode1:/home/dada#   kubeadm join 192.168.0.19:6443 --token wdjnql.rm60fa90l0o9qv49 --discovery-token-ca-cert-hash sha256:ede807fb6f732c00acb8d40a891c436aedd3ed88f915135df252c033d55b2e10
[preflight] running pre-flight checks
[discovery] Trying to connect to API Server "192.168.0.19:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.0.19:6443"
[discovery] Requesting info from "https://192.168.0.19:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.0.19:6443"
[discovery] Successfully established connection with API Server "192.168.0.19:6443"
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.12" ConfigMap in the kube-system namespace
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[preflight] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8snode1" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.
Retournez taper la commande get nodes sur le master :
dada@k8smaster:~$ kubectl get nodes
NAME        STATUS   ROLES    AGE     VERSION
k8smaster   Ready    master   26m     v1.12.2
k8snode1    Ready    <none>   3m20s   v1.12.2
Vous êtes deux !

Amusons-nous à voir ce qu'il vient de se passer au niveau des pods :
dada@k8smaster:~$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                READY   STATUS    RESTARTS   AGE     IP             NODE        NOMINATED NODE
kube-system   coredns-576cbf47c7-84x8w            1/1     Running   0          29m     10.244.0.8     k8smaster   <none>
kube-system   coredns-576cbf47c7-v88p4            1/1     Running   0          29m     10.244.0.9     k8smaster   <none>
kube-system   etcd-k8smaster                      1/1     Running   0          19m     192.168.0.19   k8smaster   <none>
kube-system   kube-apiserver-k8smaster            1/1     Running   0          19m     192.168.0.19   k8smaster   <none>
kube-system   kube-controller-manager-k8smaster   1/1     Running   0          19m     192.168.0.19   k8smaster   <none>
kube-system   kube-flannel-ds-amd64-6qm8n         1/1     Running   0          6m57s   192.168.0.30   k8snode1    <none>
kube-system   kube-flannel-ds-amd64-vzrx8         1/1     Running   0          19m     192.168.0.19   k8smaster   <none>
kube-system   kube-proxy-nn7p2                    1/1     Running   0          29m     192.168.0.19   k8smaster   <none>
kube-system   kube-proxy-phfww                    1/1     Running   0          6m57s   192.168.0.30   k8snode1    <none>
kube-system   kube-scheduler-k8smaster            1/1     Running   0          19m     192.168.0.19   k8smaster   <none>
Des pods se sont installés sur le k8snode1 ! Vous n'êtes plus seul avec votre master. Vous avez un copain !

Motivé ? Vous pouvez ajouter le node k8snode2 précédemment préparé dans le cluster, histoire de ne pas l'avoir fait chauffer pour rien. Si vous avez bien réussi votre coup, get nodes devrait vous répondre ceci :
dada@k8smaster:~$ kubectl get nodes
NAME        STATUS     ROLES    AGE   VERSION
k8smaster   Ready      master   41m   v1.12.2
k8snode1    Ready      <none>   18m   v1.12.2
k8snode2    Ready   <none>   6s    v1.12.

Bien joué !

Ça ne marche pas comme prévu ?

Très rapide abécédaire des commandes que vous pouvez utiliser si ça coince :

- kubeadm reset -f : elle supprime toute la configuration du cluster du node sur laquelle elle est exécutée. Ça permet de repartir de zéro.

- kubectl delete node <nomdunode> : supprime le node passé en paramètre du cluster, en gardant sa configuration.

La suite ?

Affichez le dashboard de k8s, parce que de la couleur et une interface, c'est mieux.