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
kubelet
devrait ĂȘ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
kubelet
commence Ă Ă©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!
kubelet
Lisez 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 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'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/etcd
dans 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-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. La vérification à l'aide de l'aide curl
montre 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 --kubeconfig
active le mode serveur API, l'absence --kubeconfig
active 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 kubelet
et 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,
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
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
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, 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
mink8s
par 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.