Découverte de GCP et Kubernetes


Dans ce tutoriel, nous découvrirons Kubernetes (souvent abrégé en “K8s”) et le Google Kubernetes Engine (GKE). Nous découvrirons également quelques autres aspects généraux de l’interface Google Cloud Platform (GCP).

Il y a 3 parties principales dans le tutoriel :

  1. Instancier un cluster K8s sur GCP et déployer un serveur web sur le cluster ;
  2. Travailler avec un équilibreur de charge pour distribuer les requêtes HTTP sur plusieurs instances d’un serveur Web ;
  3. Tirer parti des fonctionnalités de mise à l’échelle automatique des ressources (autoscaling) fournies par Kubernetes.
A travers ce tutoriel, nous soulevons quelques questions auxquelles vous devriez essayer 
de répondre afin de mieux comprendre Google Cloud et Kubernetes.
N'hésitez pas non plus à exécuter des tests supplémentaires pour mieux observer le 
comportement du système.

Vous devez également vous référer à la documentation Kubernetes et au glossaire pour mieux comprendre les différents concepts qui seront utilisés.


Avant de commencer

Pour pouvoir exécuter les instructions présentées dans ce tutoriel, vous devez d’abord :

  1. Activez vos crédits Google Cloud Education comme décrit ici  
  2. Disposer d’une machine sur laquelle les outils requis (Google Cloud SDK et Docker) sont installés et configurés

Avoir une machine configurée

Concernant le deuxième point, deux solutions principales existent :

La description du didacticiel suppose que vous utilisez le cloud shell, mais tout devrait également fonctionner si vous utilisez votre propre machine.

!! En supposant une connection réseau de qualité suffisante, travailler sur sa propre machine, bien configurée, peut s’avérer plus confortable.


AVERTISSEMENT

!!! Si vous ne supprimez pas les ressources que vous allouez, elles continuent à consommer des crédits sur GCP même lorsque vous êtes déconnecté. !!!

Peu importe la rapidité avec laquelle vous progressez dans le tutoriel, vous devez supprimer toutes les ressources que vous avez créées avant de terminer.

Veuillez consulter cette section du tutoriel pour des indications sur la suppression des ressources.


Premières expériences avec Kubernetes

Dans cette première partie, nous allons découvrir K8 et GKE à travers le déploiement d’un simple serveur web.

Cette partie est fortement inspirée du tutoriel GCP suivant : Deploying a containerized web application

Avant de commencer, il est nécessaire d’activer les API compute et container dans GCP. Vous pouvez le faire en utilisant la commande suivante :

gcloud services enable compute.googleapis.com
gcloud services enable container.googleapis.com

L’exécution de ces commandes peut prendre un certain temps. Vous pouvez ensuite utiliser la commande suivante pour vérifier que l’API a été activée :

gcloud services list

Description du serveur web

Le serveur Web avec lequel nous allons jouer est une application simple qui répond à toutes les requêtes HTTP avec un message “Hello World” qui inclut le hostname de la “machine” sur laquelle le serveur s’exécute :

Hello, world!
Version: 1.0.0
Hostname: 9eb6da3d8c54

(Le code source de cette application est disponible ici)

Tester le serveur Web localement

Avant de déployer le serveur web sur un cluster K8s dans le cloud, nous pouvons l’exécuter localement pour vérifier qu’il fonctionne.

L’image Docker du serveur web est déjà publiée avec l’identifiant suivant :

us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0

Pour exécuter le conteneur localement, utilisez la commande suivante :

docker run --rm -p 8080:8080 us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0

La commande montre que le conteneur écoute sur le port (TCP) 8080. Pour faire une requête sur le port 8080, vous pouvez :

Ensuite, exécutez la commande suivante (pendant que le conteneur est toujours en cours d’exécution) :

docker container ls
D'après le résultat de la commande ci-dessus, quel est exactement le nom d'hôte (par défaut) 
utilisé pour la « machine » qui héberge le serveur web ?

Remarque : Vous pouvez essayer de remplacer le nom d’hôte par défaut par un autre, en ajoutant l’option suivante dans la commande docker run ci-dessus avant le nom de l’image : --hostname [CHOSEN_HOSTNAME]

Enfin, vous pouvez arrêter et détruire le conteneur en utilisant :

docker rm -f [CONTAINER_ID]

Création d’un cluster K8s

Configurer la zone par défaut

Avant de déployer un cluster K8s, vous devez sélectionner la zone géographique dans laquelle vous souhaitez travailler. La liste de toutes les zones est disponible ici : https://cloud.google.com/compute/docs/regions-zones#available

Pour obtenir une liste à jour des zones disponibles, exécutez :

gcloud compute zones list

Pour vérifier la zone par défaut actuellement définie pour votre projet, exécutez :

gcloud config get compute/zone

Dans la suite, nous travaillerons dans la zone europe-west6-a.

Pour configurer la zone par défaut de votre projet, exécutez :

gcloud config set compute/zone europe-west6-a

Création d’un cluster GKE

Pour créer un cluster GKE K8s nommé “hello-cluster” à l’aide des options par défaut, exécutez :

gcloud container clusters create hello-cluster

Cela va prendre du temps.

En attendant, ouvrez la page Kubernetes Engine dans la console cloud (accessible via l’outil de recherche). Vous pourrez constater que votre nouveau cluster apparaît et est en cours de création.

Observer le cluster nouvellement créé

Une fois le cluster créé, commencez par passer du temps sur les pages Kubernetes Engine pour observer les informations fournies sur votre cluster. Par exemple, vous devriez pouvoir voir :

- Y a-t-il déjà des pods en cours d'exécution sur les nœuds de votre cluster ?
- Si oui, à quoi correspondent ces pods ?
- Au fait, qu'est-ce qu'un "pod" ?

Vous pouvez également observer l’état de notre cluster via la ligne de commande. Pour ce faire, nous devons d’abord nous assurer que nous sommes connectés à notre cluster GKE :

gcloud container clusters get-credentials hello-cluster

Pour obtenir la liste des nœuds en cours d’exécution appartenant à votre cluster, exécutez :

kubectl get node

Pour obtenir des détails sur un nœud spécifique :

kubectl describe nodes [NODE_ID]

Pour obtenir tous les pods en cours d’exécution :

kubectl get pods --all-namespaces
Qu'observons-nous sur le namespace des pods existants?

Déploiement de notre serveur web

En guise d’introduction à cette étape, nous citons le tutoriel GCP :

Kubernetes represents applications as Pods, which are scalable units holding one or more containers. The Pod is the smallest deployable unit in Kubernetes. Usually, you deploy Pods as a set of replicas that can be scaled and distributed together across your cluster. One way to deploy a set of replicas is through a Kubernetes Deployment.

Déploiement de base

La création d’un Deployment pour notre serveur Web à l’aide de l’image Docker se fait comme suit :

kubectl create deployment hello-app --image=us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0

À ce stade, un nouveau pod devrait apparaître dans le namespace par défaut :

kubectl get pods

Vous pouvez obtenir une description de ce pod :

kubectl describe pods [POD_ID]

Vous pouvez également observer le nouveau déploiement dans l’onglet Workloads de la page Kubernetes Engine dans la console cloud.

- Quelle est l'adresse IP du pod ?
- Selon vous, depuis quelle(s) machine(s) le serveur web est-il accessible ?

Tester l’accès au serveur web

Pour obtenir une réponse plus précise à la question précédente, nous allons effectuer quelques tests.

Essayez d’accéder au serveur Web depuis votre ordinateur local en utilisant son adresse IP :

curl [IP_ADDRESS]:8080

Nous allons maintenant nous connecter au nœud sur lequel le pod est déployé en utilisant SSH avec la commande suivante :

gcloud compute ssh [NODE_ID]

Depuis ce nœud, nous pouvons à nouveau tenter d’accéder au serveur Web en utilisant son adresse IP.

Pour mieux comprendre, vous pouvez même essayer de vous connecter à un autre nœud et réexécuter le test.

Enfin, nous allons créer un autre pod avec une session interactive à partir de laquelle nous pouvons exécuter une commande curl :

kubectl run curl-test --image=radial/busyboxplus:curl -i --tty --rm
Que concluez-vous ?

Notez que le --rm implique que le Pod sera automatiquement supprimé lorsque vous vous déconnecterez. Néanmoins, si vous devez supprimer un pod manuellement, voici la commande à exécuter :

kubectl delete pod [POD_ID]

Observer l’activité des Pods

Nous pouvons accéder aux logs générés par un pod à l’aide de la commande suivante :

kubectl logs [POD_ID]

Nous pouvons même observer en direct les journaux en utilisant la commande suivante :

kubectl logs -f [POD_ID]

Ouvrez un nouvel onglet dans votre shell pour envoyer une requête au serveur en utilisant l’une des méthodes précédentes et observez ce qui se passe dans les journaux.

Observer le déploiement

Jusqu’à présent, nous nous sommes concentrés sur le pod qui a été créé pour instancier le serveur web. Comme nous l’avons mentionné précédemment, il s’agit en réalité d’un Deployment : un concept de niveau supérieur dans Kubernetes, qui permet, entre autres, de gérer un ensemble de réplicas pour un composant. Vous pouvez en savoir plus sur les Deployments ici.

Nous pouvons obtenir des informations sur notre déploiement :

kubectl get deployments

Pour obtenir une description plus détaillée, exécutez :

kubectl describe deployments [DEPLOYMENT_NAME]

À l’aide de la documentation:

Expliquer la signification du paramètre de configuration intitulé `StrategyType` et 
de la valeur choisie pour ce paramètre (`RollingUpdate`).

Réplication

Jusqu’à présent, nous n’avons qu’une seule instance de notre serveur Web. Pour avoir une meilleure tolérance aux pannes, nous aimerions créer plusieurs instances de notre serveur Web, comme suit :

kubectl scale deployment hello-app --replicas=3

Vous pouvez alors observer l’impact de cette commande sur votre Déploiement et sur vos pods.

Kubectl get pods

Connectez-vous à une machine virtuelle du cluster K8s et essayez d’envoyer des requêtes à au moins deux répliques différentes du serveur Web.

Pour obtenir facilement l’adresse IP de chaque pod, vous pouvez exécuter la commande :

kubectl get pods -o yaml | grep podIP:
Au-delà du fait que le serveur web n'est toujours pas accessible depuis l'extérieur du cluster,
quelle est selon vous la limitation majeure de cette configuration ?

Pour gérer plusieurs réplicas, K8s a créé un ReplicaSet. Vous pouvez en savoir plus sur les Replicasets ici.

Pour obtenir des informations sur le ReplicaSet créé, vous pouvez exécuter la commande suivante :

kubectl get rs
kubectl describe rs

À l’aide de la commande suivante, nous pouvons supprimer un pod du ReplicaSet :

kubectl delete pod [PODID]
Observez ce qui s'est passé et expliquez.

Service

Une limitation majeure de notre Deployment est le fait que nous ne pouvons envoyer des requêtes à chaque réplica qu’en utilisant son adresse IP. Ainsi, l’accès au serveur web n’est ni transparent ni flexible.

Pour résoudre ce problème, nous allons créer un Service qui fera apparaître l’ensemble des réplicas composant notre Deployment comme un service unique qui est accessible en utilisant un nom.

Pour créer un service pour les 3 répliques de notre serveur web, exécutez :

kubectl expose deployment/hello-app --port 7000 --target-port 8080

L’option --port définit le port sur lequel le service sera accessible. L’option --target-port définit le port des pods vers lesquels les requêtes seront transmises.

Pour voir le service nouvellement créé, exécutez :

kubectl get services

Pour obtenir des informations détaillées sur le service créé, exécutez :

kubectl describe svc [SERVICE_NAME]
Dans la description du Service :
- A quoi correspond le champ IP ?
- A quoi correspondent les Endpoints ?

Pour voir comment nous pouvons interagir avec le service créé, enfin, nous allons recréer un pod avec une session interactive à partir de laquelle nous pourrons exécuter une commande curl :

kubectl run curl-test --image=radial/busyboxplus:curl -i --tty --rm
- Essayez d'envoyer une requête aux serveurs Web à l'aide des Endpoints. Expliquer.
- Essayez d'envoyer une requête aux serveurs Web en utilisant l'IP du service.
  - Quel port utiliser ?
  - Par quelle réplique du service chaque requête est-elle traitée ?
- Essayez d'envoyer une requête aux serveurs Web en utilisant le nom du service. Expliquer.

Pour envoyer une requête en utilisant le nom du service, vous pouvez utiliser la commande suivante :

curl [SERVICE_NAME]:[PORT_NUMBER]

Exposer un service sur Internet

La limite de notre service pour l’instant est qu’il n’est pas accessible en dehors du cluster K8s. En effet, le type de service créé est « ClusterIP ». Une description de base des ServicesTypes existants est disponible ici.

Il existe plusieurs solutions pour rendre le service accessible de l’extérieur, dont notamment les suivantes :

  1. Utilisation d’un type de service NodePort. Ceci rend le service accessible de l’extérieur via un port ouvert sur chaque nœud de notre cluster K8s. Cela fonctionnera si :

    • Au moins un nœud de votre cluster K8s possède une adresse IP publique
    • Une règle de pare-feu est définie pour autoriser le trafic TCP vers le port ouvert
  2. Utilisation d’un type de service LoadBalancer. Ceci demande au fournisseur de cloud de créer un équilibreur de charge externe pour votre service. Cet équilibreur de charge externe possède une adresse IP publique et transmettra le trafic aux instances de votre service en utilisant ses propres règles d’équilibrage de charge.

  3. Utilisation d’un Ingress qui peut transmettre des requêtes HTTP venant de l’extérieur du cluster vers les services déployés dans le cluster

Dans la suite, nous allons utiliser un équilibreur de charge externe pour rendre notre service accessible depuis l’extérieur.

Tout d’abord, nous commençons par supprimer le service que nous avons créé précédemment :

kubectl delete svc/hello-app

Ensuite, nous recréerons le service avec les bonnes options :

kubectl expose deployment/hello-app --port 7000 --target-port 8080 --type LoadBalancer

Après avoir exécuté la commande, vous devriez pouvoir observer le service LoadBalancer créé dans l’onglet Services & Ingress de la page Kubernetes Engine dans la console cloud.

On peut observer l’état du service créé :

kubectl get svc

Et observer après un peu de temps qu’une adresse IP externe lui a été attribuée.

A partir de ce moment, vous devriez pouvoir accéder à votre service depuis une autre machine dans GCP associée au même projet (par exemple Cloud Shell).

Cependant, pour rendre l’adresse IP externe et le port accessibles depuis n’importe quelle machine connectée à Internet, il est également nécessaire d’effectuer une étape supplémentaire pour modifier les règles du pare-feu GCP.

Pour autoriser le trafic entrant sur les machines dans GCP sur un [HOST_PORT] spécifique, la commande suivante doit être utilisée.

gcloud compute --project=[PROJECT_ID] firewall-rules create default-allow-[HOST_PORT] \
    --direction=INGRESS --priority=1000 --network=default --action=ALLOW \
    --rules=tcp:[HOST_PORT] --source-ranges=0.0.0.0/0

À partir de ce moment, vous devriez pouvoir accéder à votre service depuis n’importe quelle machine connectée à Internet (sauf s’il existe un pare-feu effectuant un filtrage côté client du réseau - notez que wifi-campus et eduroam entrent dans cette catégorie donc l’accès au service ne fonctionnera pas si votre machine client utilise l’un de ces réseaux).

Pour supprimer la règle de pare-feu (afin d’interdire à nouveau le trafic externe entrant), utilisez la commande suivante :

gcloud compute --project=[PROJECT_ID] firewall-rules delete default-allow-[HOST_PORT]

Pour aller plus loin

Si vous souhaitez essayer de rendre votre service accessible via un NodePort, essayez de suivre ce tutoriel.

Si vous souhaitez approfondir les spécificités des différentes approches (NodePort, LoadBalancer, Ingress), vous pouvez consulter le résumé suivant. (Attention, certains détails dans ce document sont spécifiques au contexte de GKE.)


Limites des ressources et mise à l’échelle automatique

Le dernier point que nous souhaitons étudier concerne les capacités d’autoscaling offertes par Kubernetes.

Dans notre configuration actuelle, 3 réplicas de notre service web fonctionnent toujours quelle que soit la charge. C’est un gaspillage de ressources s’il n’y a quasiment aucune demande à traiter.

Nous pouvons observer l’utilisation des ressources des pods en exécutant :

kubectl top pod

Vous devez observer que la quantité de CPU consommée dans la configuration actuelle est de 0 (ou presque 0).

Pour pouvoir appliquer l’autoscaling à un déploiement, nous devons d’abord définir les limites de quantité de ressources (dans la suite, nous nous concentrerons sur le CPU) que chaque pod peut utiliser.

Pour ce faire, nous devons supprimer notre déploiement et en créer un nouveau. Pour supprimer le déploiement, exécutez :

kubectl delete deployment hello-app

Au lieu d’exécuter manuellement les commandes kubectl pour configurer nos déploiements comme nous le faisions jusqu’à présent, nous utiliserons un fichier de configuration yaml qui inclut toutes les informations de configuration sur notre déploiement.

Pour obtenir ce fichier de configuration, exécutez la commande suivante :

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples

Ce dépot git comprend plusieurs exemples d’applications utilisées pour les tutoriels Kubernetes de GCP. Parmi eux, vous pouvez observer que notre application hello-app est présente dans le répertoire kubernetes-engine-samples/quickstarts/hello-app/

Dans le répertoire hello-app, il y a un fichier manifests/helloweb-deployment.yaml qui définit une configuration de base pour le déploiement de notre serveur Web.

Vous pouvez ouvrir ce fichier à l’aide d’un éditeur de texte en ligne de commande ou à l’aide de l’éditeur de texte associé au cloud shell (cliquez sur le bouton Open Editor). Des informations sur la manière de lire ce fichier sont fournies ici.

Prenez le temps d’observer ce fichier et répondez aux questions suivantes :

- Quel est le nom du déploiement qui sera créé à l'aide de ce fichier ?
- Sur quel port les pods créés vont-ils écouter ?

Demande de ressources et limites

La page suivante fournit des informations sur les notions de requests et de limits associées aux ressources dans k8s.

Dans le fichier `yaml`, on observe qu'une requête `cpu: 200m` est définie. 
- Qu’est-ce que cela implique ? 
- Et quelle est la signification précise de cette notation avec un `m` et son intérêt
par rapport à une notation décimale ?

Modifiez le fichier yaml fourni comme suit, pour définir les champs requests et limits pour le CPU à 1 :

    spec:
      containers:
      - name: hello-app
        image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 1
          limits:
            cpu: 1

Pour exécuter le Deployment décrit dans le fichier yaml, exécutez la commande suivante :

kubectl apply -f kubernetes-engine-samples/quickstarts/hello-app/manifests/helloweb-deployment.yaml

Observez le Deployment et le pod créés :

Que constatez-vous concernant le Pod ?

Pour permettre la création du pod, nous devons définir une demande de CPU inférieure. Modifiez le fichier yaml pour définir les valeurs requests et limits pour le CPU à 20m.

Supprimez le Deployment actuel et démarrez-en un nouveau :

kubectl delete deployment helloweb
kubectl apply -f kubernetes-engine-samples/quickstarts/hello-app/manifests/helloweb-deployment.yaml

Pour pouvoir accéder au nouveau Deployment, nous devons supprimer l’ancien service et en créer un nouveau :

kubectl delete service hello-app
kubectl expose deployment/helloweb --port 7000 --target-port 8080 --type LoadBalancer
Dans les exemples ci-dessus, nous avons utilisé la même valeur pour les champs `requests` 
et `limits` mais quels sont les rôles précis de ces deux valeurs?

En plus de la documentation déjà mentionée ci-dessus vou pouvez également consulter la page suivante à ce sujet.

Injection de charge

Dans la suite, pour augmenter la charge sur notre service, nous allons utiliser un pod supplémentaire qui enverra à l’infini des requêtes au service.

Pour lancer le pod load-generator, exécutez :

kubectl run -i --tty load-generator --rm --image=radial/busyboxplus:curl --restart=Never -- /bin/sh -c "while true; do curl helloweb:7000 >/dev/null 2>&1; done"

Après quelques dizaines de secondes, vous devriez pouvoir observer que le pod unique dans notre Deployment utilise des ressources CPU jusqu’à sa limite :

kubectl top pod

Cela signifie que notre service est surchargé et n’arrive pas à traiter toutes les demandes qui lui sont adressées.

Arrêtez le pod load-generator pour l’instant en utilisant Ctrl+C.

Mise à l’échelle automatique

Pour éviter de créer un grand nombre statique de réplicas pour faire face aux pics de charge (ce qui gaspillerait des ressources quand la charge est faible comme nous l’avons vu précédemment), Kubernetes peut adapter le nombre de réplicas à la charge en instanciant un HorizontalPodAutoscaler

Pour créer un HorizontalPodAutoscaler (HPA), exécutez :

kubectl autoscale deployment helloweb --cpu-percent=60 --min=2 --max=5
A quoi correspond le paramètre --cpu-percent=60 ?
Quel est le nombre de pods après avoir exécuté cette commande et pourquoi ?

Notez que pour surveiller l’état du HPA, vous pouvez utiliser la commande suivante :

kubectl get hpa --watch

Enfin, pour observer le comportement du HPA, relancez le pod load-generator.

Observez et expliquez ce qu'il se passe.

Notez que cela peut prendre un peu de temps avant que la HPA n’agisse.


À propos du cluster Kubernetes

Cette section soulève quelques questions liées au fonctionnement de Kubernetes. Essayez de répondre à ces questions peut être un bon moyen de mieux comprendre les composants internes de Kubernetes.

Cet aperçu des composants Kubernetes peut vous aider à répondre aux questions ci-dessous.

A quoi correspond ce numéro ? (Etudier l'état du cluster et des pods au travers de l'interface 
web peut vous aider à répondre à cette question)
kubectl describe node [NODE_ID]

Parmi les informations que nous obtenons grâce à cette commande, il y a des informations sur la Capacity du nœud et sur les ressources allocatable.

Expliquez pourquoi les quantités de ressources allouables sont inférieures à la capacité.

Une explication peut être trouvée ici

Quel est le but des pods Kube-proxy ?

Pour aller plus loin, vous pouvez essayer de répondre à cette question pour les autres services exécutés dans le cluster.

Pourquoi le service kube-dns n'a pas une instance par nœud ?

Pour plus d’informations sur le rôle et la configuration du DNS Kubernetes, consultez cette page.

Enfin, concernant les composants du control plane (décrits ici), pouvez-vous indiquer où ils sont déployés ? Les commandes et liens suivants peuvent vous aider :


Nettoyage

Vous arrivez à la fin de cet atelier. Plusieurs ressources doivent être supprimées avant de vous déconnecter de GCP.

Pour supprimer le HPA, exécutez :

kubectl delete hpa helloweb

Pour supprimer le service, exécutez :

kubectl delete service helloweb

Pour supprimer le déploiement, exécutez :

kubectl delete deployment helloweb

Enfin, pour supprimer le cluster GKE, exécutez :

gcloud container clusters delete hello-cluster