Moderniser les applications pour Kubernetes

introduction

Les applications modernes sans état sont conçues et conçues pour s'exécuter dans des conteneurs logiciels tels que Docker et être gérées par des grappes de conteneurs comme Kubernetes. Ils sont développés en utilisant les principes et modèlesCloud Native etTwelve Factor, pour minimiser les interventions manuelles et maximiser la portabilité et la redondance. La migration des applications de machine virtuelle ou de métal nu dans des conteneurs (appelés «conteneurisation») et leur déploiement à l'intérieur de clusters impliquent souvent des changements importants dans la manière dont ces applications sont construites, empaquetées et livrées.

En nous appuyant surArchitecting Applications for Kubernetes, dans ce guide conceptuel, nous aborderons les étapes de haut niveau pour la modernisation de vos applications, l'objectif final étant de les exécuter et de les gérer dans un cluster Kubernetes. Bien que vous puissiez exécuter des applications avec état, telles que des bases de données sur Kubernetes, ce guide porte sur la migration et la modernisation d'applications sans état, les données persistantes étant déchargées dans un magasin de données externe. Kubernetes fournit des fonctionnalités avancées permettant de gérer et de faire évoluer efficacement les applications sans état. Nous explorerons également les modifications d’application et d’infrastructure nécessaires pour exécuter des applications évolutives, observables et portables sur Kubernetes.

Préparation de l'application pour la migration

Avant de conteneuriser votre application ou d'écrire des fichiers de configuration Kubernetes Pod et Deployment, vous devez implémenter des modifications au niveau de l'application pour optimiser la portabilité et l'observabilité de votre application dans Kubernetes. Kubernetes est un environnement hautement automatisé capable de déployer et de redémarrer automatiquement les conteneurs d’application défaillants. Il est donc important d’intégrer la logique d’application appropriée pour communiquer avec l’orchestrateur de conteneur et lui permettre de redimensionner automatiquement votre application si nécessaire.

Extraire les données de configuration

L'une des premières modifications à implémenter au niveau de l'application consiste à extraire la configuration de l'application à partir du code de l'application. La configuration comprend toutes les informations qui varient selon les déploiements et les environnements, telles que les noeuds finaux de service, les adresses de base de données, les informations d'identification, ainsi que divers paramètres et options. Par exemple, si vous avez deux environnements, disonsstaging etproduction, et que chacun contient une base de données distincte, votre application ne doit pas avoir le point de terminaison de base de données et les informations d'identification explicitement déclarés dans le code, mais stockés dans un autre emplacement, sous forme de variables dans l'environnement en cours d'exécution, de fichier local ou de magasin de clés-valeurs externe, à partir duquel les valeurs sont lues dans l'application.

Le codage en dur de ces paramètres dans votre code présente un risque pour la sécurité, car ces données de configuration consistent souvent en des informations sensibles que vous archivez ensuite dans votre système de contrôle de version. Cela accroît également la complexité du fait que vous devez maintenant gérer plusieurs versions de votre application, chacune comprenant la même logique applicative principale, mais présentant des configurations légèrement différentes. Au fur et à mesure que les applications et leurs données de configuration se développent, la configuration du codage en dur en code d'application devient rapidement difficile à manier.

En extrayant les valeurs de configuration de votre code d'application et en les récupérant à partir de l'environnement en cours d'exécution ou de fichiers locaux, votre application devient un package portable générique pouvant être déployé dans n'importe quel environnement, à condition de lui fournir les données de configuration correspondantes. Les logiciels de conteneur tels que Docker et les logiciels de cluster tels que Kubernetes ont été conçus autour de ce paradigme, intégrant des fonctionnalités permettant de gérer les données de configuration et de les injecter dans des conteneurs d'applications. Ces fonctionnalités seront traitées plus en détail dans les sectionsContainerizing etKubernetes.

Voici un exemple rapide montrant comment externaliser deux valeurs de configurationDB_HOST etDB_USER à partir du code d'une simple application PythonFlask. Nous les rendrons disponibles dans l'environnement d'exécution de l'application sous forme de vars, à partir desquels l'application les lira:

hardcoded_config.py

from flask import Flask

DB_HOST = 'mydb.mycloud.com'
DB_USER = 'sammy'

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

L'exécution de cette application simple (consultez lesFlask Quickstart pour savoir comment) et la visite de son point de terminaison Web affichera une page contenant ces deux valeurs de configuration.

Maintenant, voici le même exemple avec les valeurs de configuration externalisées dans l’environnement en cours d’application:

env_config.py

import os

from flask import Flask

DB_HOST = os.environ.get('APP_DB_HOST')
DB_USER = os.environ.get('APP_DB_USER')

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

Avant d'exécuter l'application, nous définissons les variables de configuration nécessaires dans l'environnement local:

export APP_DB_HOST=mydb.mycloud.com
export APP_DB_USER=sammy
flask run

La page Web affichée doit contenir le même texte que dans le premier exemple, mais la configuration de l’application peut maintenant être modifiée indépendamment du code de l’application. Vous pouvez utiliser une approche similaire pour lire les paramètres de configuration à partir d’un fichier local.

Dans la section suivante, nous aborderons le déplacement de l’état de l’application en dehors des conteneurs.

Décharger l'état de l'application

Les applications Cloud Native s'exécutent dans des conteneurs et sont orchestrées de manière dynamique par un logiciel de cluster tel que Kubernetes ou Docker Swarm. Une application ou un service donné peut être équilibré entre plusieurs réplicas et tout conteneur d'application individuel doit pouvoir échouer, avec une interruption minimale ou nulle du service pour les clients. Pour permettre cette mise à l'échelle horizontale et redondante, les applications doivent être conçues de manière sans état. Cela signifie qu'ils répondent aux demandes des clients sans stocker localement les données persistantes des clients et des applications. À tout moment, si le conteneur d'applications en cours d'exécution est détruit ou redémarré, les données critiques ne sont pas perdues.

Par exemple, si vous exécutez une application de carnet d'adresses et que votre application ajoute, supprime et modifie les contacts d'un carnet d'adresses, le magasin de données du carnet d'adresses doit être une base de données externe ou un autre magasin de données, et les seules données conservées dans la mémoire du conteneur doivent être à court terme et jetable sans perte critique d'informations. Les données qui persistent lors des visites des utilisateurs, telles que les sessions, doivent également être déplacées vers des magasins de données externes tels que Redis. Dans la mesure du possible, vous devez décharger tout état de votre application vers des services tels que des bases de données gérées ou des caches.

Pour les applications avec état qui nécessitent un magasin de données persistant (comme une base de données MySQL répliquée), Kubernetes crée des fonctionnalités permettant de lier des volumes de stockage de blocs persistants à des conteneurs et des pods. Pour garantir qu'un pod puisse conserver son état et accéder au même volume persistant après un redémarrage, le workload StatefulSet doit être utilisé. StatefulSets est idéal pour déployer des bases de données et d'autres magasins de données de longue durée sur Kubernetes.

Les conteneurs sans état permettent une portabilité maximale et une utilisation optimale des ressources cloud disponibles, ce qui permet au planificateur Kubernetes d'adapter rapidement votre application et de lancer des pods là où les ressources sont disponibles. Si vous n'avez pas besoin des garanties de stabilité et de commande fournies par la charge de travail StatefulSet, vous devez utiliser la charge de travail de déploiement pour gérer et faire évoluer vos applications.

Pour en savoir plus sur la conception et l'architecture des microservices Cloud Native sans état, consultez nosKubernetes White Paper.

Mettre en œuvre des bilans de santé

Dans le modèle Kubernetes, le plan de contrôle du cluster peut être utilisé pour réparer une application ou un service défectueux. Pour ce faire, il vérifie l'état des pods d'application, puis redémarre ou replanifie des conteneurs insalubres ou ne répondant pas. Par défaut, si votre conteneur d'applications est en cours d'exécution, Kubernetes considère votre pod comme «sain». Dans de nombreux cas, il s'agit d'un indicateur fiable de la santé d'une application en cours d'exécution. Toutefois, si votre application est bloquée et n'effectue aucun travail significatif, le processus et le conteneur de l'application continueront de s'exécuter indéfiniment. Par défaut, Kubernetes maintiendra le conteneur bloqué en vie.

Pour communiquer correctement l'intégrité des applications au plan de contrôle Kubernetes, vous devez implémenter des vérifications de l'intégrité des applications personnalisées indiquant le moment où une application est à la fois en cours d'exécution et prête à recevoir du trafic. Le premier type de vérification de l'état s'appelle unreadiness probe et permet à Kubernetes de savoir quand votre application est prête à recevoir du trafic. Le deuxième type de vérification est appeléliveness probe et permet à Kubernetes de savoir quand votre application est saine et en cours d'exécution. L'agent de noeud Kubelet peut effectuer ces sondes sur des pods en cours d'exécution à l'aide de 3 méthodes différentes:

  • HTTP: la sonde Kubelet exécute une requête HTTP GET sur un point de terminaison (comme/health) et réussit si l'état de la réponse est compris entre 200 et 399

  • Commande de conteneur: La sonde Kubelet exécute une commande à l'intérieur du conteneur en cours d'exécution. Si le code de sortie est 0, la vérification aboutit.

  • TCP: La sonde Kubelet tente de se connecter à votre conteneur sur un port spécifié. S'il peut établir une connexion TCP, la sonde réussit.

Vous devez choisir la méthode appropriée en fonction des applications en cours d'exécution, du langage de programmation et de la structure. Les sondes de disponibilité et d’activité peuvent utiliser la même méthode de sonde et effectuer le même contrôle, mais l’inclusion d’une sonde de disponibilité garantira que le pod ne recevra pas de trafic tant que la sonde n'aura pas réussi.

Lorsque vous planifiez et envisagez de conteneuriser votre application et de l'exécuter sur Kubernetes, vous devez prévoir un temps de planification pour définir les notions de "sain" et "prêt" pour votre application particulière, ainsi que de temps de développement pour la mise en oeuvre et le test des terminaux et / ou des commandes de contrôle.

Voici un critère de santé minimal pour l’exemple de flacon référencé ci-dessus:

env_config.py

. . .
@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

@app.route('/health')
def return_ok():
    return 'Ok!', 200

Une sonde d'activité Kubernetes qui vérifie ce chemin ressemblerait alors à ceci:

pod_spec.yaml

. . .
  livenessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 2

Le champinitialDelaySeconds spécifie que Kubernetes (en particulier le Node Kubelet) doit sonder le point de terminaison/health après avoir attendu 5 secondes, etperiodSeconds dit au Kubelet de sonder/health toutes les 2 secondes.

Pour en savoir plus sur les sondes de vivacité et de préparation, consultez lesKubernetes documentation.

Code d'instrument pour la journalisation et la surveillance

Lorsque vous exécutez votre application conteneurisée dans un environnement tel que Kubernetes, il est important de publier des données de télémétrie et de journalisation afin de surveiller et de déboguer les performances de votre application. La création de fonctionnalités pour publier des indicateurs de performance tels que la durée de réponse et les taux d'erreur vous aidera à surveiller votre application et à vous alerter lorsque celle-ci est malsaine.

Un outil que vous pouvez utiliser pour surveiller vos services estPrometheus, une boîte à outils open-source de surveillance et d'alerte des systèmes, hébergée par la Cloud Native Computing Foundation (CNCF). Prometheus fournit plusieurs bibliothèques client pour instrumenter votre code avec différents types de métriques afin de compter les événements et leurs durées. Par exemple, si vous utilisez le framework Flask Python, vous pouvez utiliser les PrometheusPython client pour ajouter des décorateurs à vos fonctions de traitement des requêtes afin de suivre le temps passé à traiter les requêtes. Ces métriques peuvent ensuite être récupérées par Prometheus à un point de terminaison HTTP comme/metrics.

Une méthode utile à utiliser lors de la conception des instruments de votre application est la méthode RED. Il comprend les trois métriques de demande de clé suivantes:

  • Évaluer: le nombre de demandes reçues par votre application.

  • Erreurs: nombre d'erreurs émises par votre application.

  • Durée: durée nécessaire à votre demande pour répondre à une question.

Cet ensemble minimal de métriques devrait vous donner suffisamment de données pour alerter lorsque les performances de votre application se dégradent. La mise en œuvre de cette instrumentation avec les contrôles de santé décrits ci-dessus vous permettra de détecter rapidement et de récupérer une application défaillante.

Pour en savoir plus sur les signaux à mesurer lors de la surveillance de vos applications, consultezMonitoring Distributed Systems du livre Google Site Reliability Engineering.

Outre la conception et la conception de fonctionnalités pour la publication de données de télémétrie, vous devez également planifier la manière dont votre application se connectera dans un environnement distribué basé sur des clusters. Vous devriez idéalement supprimer les références de configuration codées en dur dans les fichiers journaux locaux et les répertoires de journaux, et vous connecter directement à stdout et stderr. Vous devez traiter les journaux comme un flux d'événements continu ou une séquence d'événements ordonnés dans le temps. Ce flux de sortie est ensuite capturé par le conteneur qui enveloppe votre application, à partir duquel il peut être transféré vers une couche de journalisation telle que la pile EFK (Elasticsearch, Fluentd et Kibana). Kubernetes offre une grande flexibilité dans la conception de votre architecture de journalisation, que nous étudierons plus en détail ci-dessous.

Construire la logique d'administration dans l'API

Une fois que votre application est conteneurisée et opérationnelle dans un environnement de cluster tel que Kubernetes, vous pouvez ne plus avoir d'accès shell au conteneur exécutant votre application. Si vous avez mis en place une vérification, une journalisation et une surveillance de l’état appropriées, vous pouvez rapidement être alerté et résoudre les problèmes de production, mais il peut s'avérer difficile de prendre des mesures autres que le redémarrage et le redéploiement des conteneurs. Pour des correctifs opérationnels et de maintenance rapides tels que le vidage des files d'attente ou l'effacement d'un cache, vous devez implémenter les points de terminaison d'API appropriés afin de pouvoir effectuer ces opérations sans avoir à redémarrer des conteneurs ouexec dans des conteneurs en cours d'exécution et exécuter une série de commandes. Les conteneurs doivent être traités comme des objets immuables et l’administration manuelle doit être évitée dans un environnement de production. Si vous devez effectuer des tâches administratives ponctuelles, telles que la suppression des caches, vous devez exposer cette fonctionnalité via l'API.

Sommaire

Dans ces sections, nous avons traité des modifications au niveau de l'application que vous souhaiterez peut-être mettre en œuvre avant de conteneuriser votre application et de la déplacer vers Kubernetes. Pour une présentation plus approfondie de la création d'applications Cloud Native, consultezArchitecting Applications for Kubernetes.

Nous allons maintenant aborder certaines considérations à prendre en compte lors de la création de conteneurs pour vos applications.

Conteneurisation de votre application

Maintenant que vous avez implémenté la logique d'application pour optimiser sa portabilité et son observabilité dans un environnement en nuage, il est temps de conditionner votre application dans un conteneur. Pour les besoins de ce guide, nous utiliserons les conteneurs Docker, mais vous devez utiliser la mise en œuvre de conteneur qui convient le mieux à vos besoins de production.

Déclarer explicitement les dépendances

Avant de créer un fichier Dockerfile pour votre application, l'une des premières étapes consiste à dresser un inventaire des dépendances du logiciel et du système d'exploitation dont votre application a besoin pour fonctionner correctement. Dockerfiles vous permet de mettre à jour de manière explicite tous les logiciels installés dans l'image. Vous devez en tirer parti en déclarant explicitement les versions de l'image parent, de la bibliothèque de logiciels et du langage de programmation.

Évitez autant que possible les baliseslatest et les packages non versionnés, car ceux-ci peuvent se déplacer, ce qui pourrait endommager votre application. Vous souhaiterez peut-être créer un registre privé ou un miroir privé d'un registre public pour mieux contrôler la gestion des versions des images et empêcher les modifications en amont de perturber involontairement vos générations d'images.

Pour en savoir plus sur la configuration d'un registre d'images privé, consultezDeploy a Registry Server de la documentation officielle de Docker et la sectionRegistries ci-dessous.

Gardez les tailles d'image petites

Lors du déploiement et de l'extraction des images de conteneur, les grandes images peuvent considérablement ralentir le processus et augmenter les coûts de bande passante. L'intégration d'un ensemble minimal d'outils et de fichiers d'application dans une image présente plusieurs avantages:

  • Réduire les tailles d'image

  • Accélération de la génération d'images

  • Réduire le délai de démarrage du conteneur

  • Accélérer les temps de transfert d'image

  • Améliorer la sécurité en réduisant la surface d'attaque

Quelques étapes à considérer lors de la création de vos images:

  • Utilisez une image de base minimale du système d'exploitation commealpine ou compilez à partir descratch au lieu d'un système d'exploitation complet commeubuntu

  • Nettoyer les fichiers et les artefacts inutiles après l'installation du logiciel

  • Utilisez des conteneurs distincts "build" et "runtime" pour conserver les conteneurs d'applications de production de petite taille

  • Ignorer les artefacts de construction et les fichiers inutiles lors de la copie dans des répertoires volumineux

Pour un guide complet sur l'optimisation des conteneurs Docker, y compris de nombreux exemples illustratifs, consultezBuilding Optimized Containers for Kubernetes.

Configuration d'injection

Docker fournit plusieurs fonctionnalités utiles pour l’injection de données de configuration dans l’environnement en cours d’exécution de votre application.

Une option pour ce faire est de spécifier les variables d'environnement et leurs valeurs dans le Dockerfile à l'aide de l'instructionENV, de sorte que les données de configuration soient intégrées aux images:

Dockerfile

...
ENV MYSQL_USER=my_db_user
...

Votre application peut ensuite analyser ces valeurs à partir de son environnement d'exécution et configurer ses paramètres de manière appropriée.

Vous pouvez également transmettre des variables d'environnement en tant que paramètres lors du démarrage d'un conteneur à l'aide dedocker run et de l'indicateur-e:

docker run -e MYSQL_USER='my_db_user' IMAGE[:TAG]

Enfin, vous pouvez utiliser un fichier env contenant une liste de variables d’environnement et leurs valeurs. Pour ce faire, créez le fichier et utilisez le paramètre--env-file pour le transmettre à la commande:

docker run --env-file var_list IMAGE[:TAG]

Si vous modernisez votre application pour l'exécuter à l'aide d'un gestionnaire de cluster tel que Kubernetes, vous devez externaliser davantage votre configuration à partir de l'image et gérer la configuration à l'aide des objetsConfigMap etSecrets intégrés de Kubernetes. Cela vous permet de séparer la configuration des manifestes d'images, de sorte que vous puissiez la gérer et la version séparément de votre application. Pour savoir comment externaliser la configuration à l'aide de ConfigMaps et Secrets, consultez lesConfigMaps and Secrets section ci-dessous.

Publier une image dans un registre

Une fois que vous avez créé vos images d’application, vous devez les télécharger dans un registre d’images de conteneur pour les rendre disponibles à Kubernetes. Les registres publics commeDocker Hub hébergent les dernières images Docker pour les projets open source populaires commehttps://hub.docker.com//node/[Node.js] and https://hub.docker.com/ / nginx / [nginx]. Les registres privés vous permettent de publier vos images d'application internes, en les mettant à la disposition des développeurs et de l'infrastructure, mais pas dans le monde entier.

Vous pouvez déployer un registre privé en utilisant votre infrastructure existante (par exemple, en plus du stockage d'objets dans le cloud), ou utilisez éventuellement l'un des nombreux produits de registre Docker tels queQuay.io ou des plans Docker Hub payants. Ces registres peuvent s'intégrer à des services de contrôle de version hébergés tels que GitHub. Ainsi, lorsqu'un fichier Dockerfile est mis à jour et poussé, le service de registre extrait automatiquement le nouveau fichier Dockerfile, crée l'image du conteneur et met l'image mise à jour à la disposition de vos services.

Pour exercer davantage de contrôle sur la création et le test de vos images de conteneur, ainsi que sur leur marquage et leur publication, vous pouvez implémenter un pipeline d'intégration continue (CI).

Implémenter un pipeline de construction

Construire, tester, publier et déployer manuellement vos images en production peut être sujet à des erreurs et ne pas évoluer correctement. Pour gérer les générations et publier en continu des conteneurs contenant vos dernières modifications de code dans votre registre d'images, vous devez utiliser un pipeline de génération.

La plupart des pipelines de génération remplissent les fonctions principales suivantes:

  • Surveiller les référentiels de code source pour les modifications

  • Exécuter des tests de fumée et unitaires sur du code modifié

  • Construire des images de conteneur contenant du code modifié

  • Exécuter des tests d'intégration supplémentaires à l'aide d'images de conteneur construites

  • Si les tests réussissent, marquez et publiez les images dans la base de registre.

  • (Facultatif, dans les configurations de déploiement continu) Mettre à jour les déploiements Kubernetes et déployer des images sur des clusters de transfert / production

De nombreux produits d'intégration continue payants intègrent des services de contrôle de version populaires tels que GitHub et des registres d'images tels que Docker Hub. Une alternative à ces produits estJenkins, un serveur d'automatisation de build gratuit et open-source qui peut être configuré pour exécuter toutes les fonctions décrites ci-dessus. Pour savoir comment configurer un pipeline d'intégration continue Jenkins, consultezHow To Set Up Continuous Integration Pipelines in Jenkins on Ubuntu 16.04.

Mettre en œuvre la journalisation et la surveillance des conteneurs

Lorsque vous travaillez avec des conteneurs, il est important de penser à l'infrastructure de journalisation que vous utiliserez pour gérer et stocker les journaux de tous vos conteneurs en cours d'exécution et arrêtés. Vous pouvez utiliser plusieurs modèles au niveau du conteneur pour la journalisation, ainsi que plusieurs modèles au niveau de Kubernetes.

Dans Kubernetes, les conteneurs par défaut utilisent le Dockerjson-filelogging driver, qui capture les flux stdout et stderr et les écrit dans des fichiers JSON sur le nœud sur lequel le conteneur est exécuté. Parfois, la journalisation directe sur stderr et stdout peut ne pas être suffisante pour votre conteneur d'application, et vous pouvez coupler le conteneur d'application avec un conteneur de journalisationsidecar dans un pod Kubernetes. Ce conteneur sidecar peut ensuite extraire les journaux du système de fichiers, d'un socket local ou du journal systemd, ce qui vous donne un peu plus de flexibilité que d'utiliser simplement les flux stderr et stdout. Ce conteneur peut également effectuer certains traitements, puis transférer les journaux enrichis vers stdout / stderr, ou directement vers un backend de journalisation. Pour en savoir plus sur les modèles de journalisation Kubernetes, consultez lessection de journalisation et de surveillance Kubernetes de ce didacticiel.

La manière dont votre application se connecte au niveau du conteneur dépend de sa complexité. Pour des microservices simples et à usage unique, se connecter directement à stdout / stderr et laisser Kubernetes récupérer ces flux est l'approche recommandée, car vous pouvez ensuite utiliser la commandekubectl logs pour accéder aux flux de journaux à partir de vos conteneurs déployés par Kubernetes.

Comme pour la journalisation, vous devriez commencer à penser à la surveillance dans un environnement basé sur des conteneurs et des clusters. Docker fournit la commandedocker stats utile pour récupérer des métriques standard telles que l'utilisation du processeur et de la mémoire pour exécuter des conteneurs sur l'hôte, et expose encore plus de métriques via lesRemote REST API. En outre, l'outil open sourcecAdvisor (installé sur les nœuds Kubernetes par défaut) fournit des fonctionnalités plus avancées telles que la collecte de métriques historiques, l'exportation de données de métriques et une interface utilisateur Web utile pour trier les données.

Cependant, dans un environnement de production multi-nœuds et multi-conteneurs, des piles de métriques plus complexes telles quePrometheus etGrafana peuvent aider à organiser et à surveiller les données de performances de vos conteneurs.

Sommaire

Dans ces sections, nous avons brièvement présenté certaines des meilleures pratiques en matière de construction de conteneurs, de configuration d’un pipeline CI / CD et d’un registre d’images, ainsi que de considérations relatives à l’augmentation de l’observabilité dans vos conteneurs.

Dans la section suivante, nous explorerons les fonctionnalités de Kubernetes qui vous permettent d’exécuter et de mettre à l’échelle votre application conteneurisée dans un cluster.

Déploiement sur Kubernetes

À ce stade, vous avez conteneurisé votre application et mis en œuvre une logique pour optimiser sa portabilité et son observabilité dans les environnements Cloud Native. Nous allons maintenant explorer les fonctionnalités de Kubernetes qui fournissent des interfaces simples pour gérer et faire évoluer vos applications dans un cluster Kubernetes.

Écrire les fichiers de déploiement et de configuration du pod

Une fois que votre application a été conteneurisée et publiée dans un registre, vous pouvez maintenant la déployer dans un cluster Kubernetes à l'aide du workload Pod. La plus petite unité déployable dans un cluster Kubernetes n'est pas un conteneur mais un pod. Les pods consistent généralement en un conteneur d'applications (comme une application Web Flask conteneurisée), ou un conteneur d'applications et tous les conteneurs "sidecar" qui remplissent des fonctions d'assistance telles que la surveillance ou la journalisation. Les conteneurs d'un pod partagent des ressources de stockage, un espace de noms réseau et un espace de port. Ils peuvent communiquer entre eux en utilisantlocalhost et peuvent partager des données en utilisant des volumes montés. De plus, la charge de travail du pod vous permet de définir desInit Containers qui exécutent des scripts ou des utilitaires de configuration avant que le conteneur d'application principal ne commence à s'exécuter.

Les pods sont généralement déployés à l'aide de Deployments, qui sont des contrôleurs définis par des fichiers YAML qui déclarent un état souhaité particulier. Par exemple, un état d'application peut exécuter trois réplicas du conteneur d'applications Web Flask et exposer le port 8080. Une fois créé, le plan de contrôle adapte progressivement l'état réel du cluster à l'état souhaité déclaré dans le déploiement en planifiant les conteneurs sur des nœuds, le cas échéant. Pour mettre à l'échelle le nombre de réplicas d'application exécutés dans le cluster, par exemple de 3 à 5, vous mettez à jour le champreplicas du fichier de configuration de déploiement, puiskubectl apply le nouveau fichier de configuration. À l'aide de ces fichiers de configuration, les opérations de dimensionnement et de déploiement peuvent toutes être suivies et versionnées à l'aide de vos services de contrôle de source existants et de vos intégrations.

Voici un exemple de fichier de configuration de Kubernetes Deployment pour une application Flask:

flask_deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask
        image: sammy/flask_app:1.0
        ports:
        - containerPort: 8080

Ce déploiement lance 3 pods qui exécutent un conteneur appeléflask en utilisant l'imagesammy/flask_app (version1.0) avec le port8080 ouvert. Le déploiement est appeléflask-app.

Pour en savoir plus sur les pods et les déploiements Kubernetes, consultez les sectionsPods etDeployments de la documentation officielle de Kubernetes.

Configurer le stockage de pod

Kubernetes gère le stockage de pods à l'aide de volumes, de volumes persistants (PV) et de revendications de volume persistant (PVC). Les volumes sont l'abstraction de Kubernetes utilisée pour gérer le stockage des pods et prennent en charge la plupart des offres de stockage en bloc des fournisseurs de cloud ainsi que le stockage local sur les nœuds hébergeant les pods en cours d'exécution. Pour voir une liste complète des types de volumes pris en charge, consultez le Kubernetesdocumentation.

Par exemple, si votre pod contient deux conteneurs NGINX qui doivent partager des données entre eux (disons que le premier, appelénginx sert des pages Web, et le second, appelénginx-sync récupère les pages à partir d'un emplacement externe et met à jour les pages servies par le conteneurnginx), la spécification de votre Pod ressemblerait à ceci (ici nous utilisons le type de volumeemptyDir):

pod_volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nginx-web
      mountPath: /usr/share/nginx/html

  - name: nginx-sync
    image: nginx-sync
    volumeMounts:
    - name: nginx-web
      mountPath: /web-data

  volumes:
  - name: nginx-web
    emptyDir: {}

Nous utilisons unvolumeMount pour chaque conteneur, indiquant que nous aimerions monter le volumenginx-web contenant les fichiers de page Web à/usr/share/nginx/html dans le conteneurnginx et à/web-data dans le conteneurnginx-sync. Nous définissons également unvolume appelénginx-web de typeemptyDir.

De la même manière, vous pouvez configurer le stockage de pod à l'aide de produits de stockage de blocs cloud en modifiant le typevolume deemptyDir au type de volume de stockage cloud approprié.

Le cycle de vie d'un volume est lié au cycle de vie du pod, maisnot à celui d'un conteneur. Si un conteneur d'un pod meurt, le volume persiste et le conteneur nouvellement lancé pourra monter le même volume et accéder à ses données. Lorsqu'un volume est redémarré ou meurt, ses volumes le sont également. Toutefois, si les volumes sont constitués d'un stockage en mode cloud, ils seront simplement démontés avec les données toujours accessibles par les futurs pods.

Pour conserver les données lors des redémarrages et des mises à jour du pod, vous devez utiliser les objets PersistentVolume (PV) et PersistentVolumeClaim (PVC).

PersistentVolumes sont des abstractions représentant des éléments de stockage persistant, tels que des volumes de stockage en bloc cloud ou un stockage NFS. Ils sont créés séparément de PersistentVolumeClaims, qui constituent des demandes de stockage pour les développeurs. Dans leur configuration Pod, les développeurs demandent un stockage persistant à l'aide de PVC, que Kubernetes met en correspondance avec les volumes PV disponibles (si vous utilisez le stockage en mode cloud, Kubernetes peut créer de manière dynamique PersistentVolumes lors de la création de PersistentVolumeClaims).

Si votre application nécessite un volume persistant par réplica, comme dans de nombreuses bases de données, vous ne devez pas utiliser Deployments mais utiliser le contrôleur StatefulSet, conçu pour les applications nécessitant des identificateurs de réseau stables, un stockage persistant stable et des garanties de commande. Les déploiements doivent être utilisés pour les applications sans état. Si vous définissez un PersistentVolumeClaim à utiliser dans une configuration de déploiement, ce PVC sera partagé par toutes les répliques du déploiement.

Pour en savoir plus sur le contrôleur StatefulSet, consultez le Kubernetesdocumentation. Pour en savoir plus sur les revendications PersistentVolumes et PersistentVolume, consultez lesdocumentation de stockage Kubernetes.

Injection de données de configuration avec Kubernetes

Semblable à Docker, Kubernetes fournit les champsenv etenvFrom pour définir les variables d'environnement dans les fichiers de configuration du pod. Voici un exemple d'extrait de code d'un fichier de configuration de pod qui définit la variable d'environnementHOSTNAME dans le pod en cours d'exécution surmy_hostname:

sample_pod.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          value: my_hostname
...

Cela vous permet de déplacer la configuration hors de Dockerfiles et dans les fichiers de configuration Pod et Deployment. Un avantage clé de l'externalisation de la configuration de vos fichiers Dockerfiles est que vous pouvez désormais modifier ces configurations de charge de travail Kubernetes (par exemple, en modifiant la valeurHOSTNAME enmy_hostname_2) séparément de vos définitions de conteneur d'application. Une fois que vous avez modifié le fichier de configuration du pod, vous pouvez le redéployer à l'aide de son nouvel environnement, tandis que l'image du conteneur sous-jacent (défini via son fichier Dockerfile) n'a pas besoin d'être recréée, testée et transférée dans un référentiel. Vous pouvez également mettre à jour ces configurations de pod et de déploiement séparément de vos fichiers Dockerfiles, ce qui vous permet de détecter rapidement les modifications les plus récentes et de séparer davantage les problèmes de configuration des bogues d'application.

Kubernetes fournit une autre construction pour externaliser et gérer davantage les données de configuration: ConfigMaps et Secrets.

ConfigMaps et Secrets

Les ConfigMaps vous permettent d’enregistrer les données de configuration en tant qu’objets auxquels vous faites ensuite référence dans vos fichiers de configuration de pods et de déploiements, afin d’éviter le codage en dur des données de configuration et de les réutiliser dans des pods et des déploiements.

Voici un exemple, en utilisant la configuration de pod ci-dessus. Nous allons d'abord enregistrer la variable d'environnementHOSTNAME en tant que ConfigMap, puis la référencer dans la configuration du pod:

kubectl create configmap hostname --from-literal=HOSTNAME=my_host_name

Pour le référencer à partir du fichier de configuration du pod, nous utilisons les constructionsvalueFrom etconfigMapKeyRef:

sample_pod_configmap.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          valueFrom:
            configMapKeyRef:
              name: hostname
              key: HOSTNAME
...

La valeur de la variable d'environnementHOSTNAME a donc été complètement externalisée des fichiers de configuration. Nous pouvons ensuite mettre à jour ces variables pour tous les déploiements et pods les référençant, et redémarrer les pods pour que les modifications prennent effet.

Si vos applications utilisent des fichiers de configuration, ConfigMaps vous permet en outre de stocker ces fichiers en tant qu'objets ConfigMap (en utilisant l'indicateur--from-file), que vous pouvez ensuite monter dans des conteneurs en tant que fichiers de configuration.

Les secrets fournissent les mêmes fonctionnalités essentielles que ConfigMaps, mais doivent être utilisés pour des données sensibles telles que les informations d'identification de base de données, car les valeurs sont codées en base64.

Pour en savoir plus sur ConfigMaps et Secrets, consultez les Kubernetesdocumentation.

Créer des services

Une fois que votre application est opérationnelle dans Kubernetes, chaque pod se voit attribuer une adresse IP (interne), partagée par ses conteneurs. Si l'un de ces pods est supprimé ou meurt, des adresses IP différentes seront attribuées aux pods nouvellement démarrés.

Pour les services de longue durée qui exposent des fonctionnalités à des clients internes et / ou externes, vous pouvez accorder à un ensemble de pods effectuant la même fonction (ou déploiement) une adresse IP stable qui charge les demandes dans ses conteneurs. Vous pouvez le faire en utilisant un service Kubernetes.

Les services Kubernetes ont 4 types, spécifiés par le champtype dans le fichier de configuration du service:

  • ClusterIP: il s'agit du type par défaut, qui accorde au service une adresse IP interne stable accessible de n'importe où à l'intérieur du cluster.

  • NodePort: Cela exposera votre service sur chaque nœud sur un port statique, entre 30000 et 32767 par défaut. Lorsqu'une demande atteint un nœud à son adresse IP de nœud et lesNodePort pour votre service, la demande sera équilibrée en charge et acheminée vers les conteneurs d'applications de votre service.

  • LoadBalancer: cela créera un équilibreur de charge à l'aide du produit d'équilibrage de charge de votre fournisseur de cloud et configurera unNodePort et unClusterIP pour votre service vers lesquels les demandes externes seront acheminées.

  • ExternalName: ce type de service vous permet de mapper un service Kubernetes à un enregistrement DNS. Il peut être utilisé pour accéder à des services externes à partir de vos pods à l'aide du DNS Kubernetes.

Notez que la création d'un service de typeLoadBalancer pour chaque déploiement exécuté dans votre cluster créera un nouvel équilibreur de charge cloud pour chaque service, ce qui peut devenir coûteux. Pour gérer le routage des demandes externes vers plusieurs services à l'aide d'un seul équilibreur de charge, vous pouvez utiliser un contrôleur de pénétration. Les contrôleurs d'entrée sortent du cadre de cet article, mais pour en savoir plus à leur sujet, vous pouvez consulter les Kubernetesdocumentation. Un contrôleur d'entrée simple populaire est leNGINX Ingress Controller.

Voici un fichier de configuration de service simple pour l'exemple Flask utilisé dans les pods et déploiementssection de ce guide:

flask_app_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: flask-app
  type: LoadBalancer

Ici, nous choisissons d'exposer le déploiement deflask-app à l'aide de ce serviceflask-svc. Nous créons un équilibreur de charge cloud pour acheminer le trafic du port80 de l'équilibreur de charge vers le port conteneur exposé8080.

Pour en savoir plus sur les services Kubernetes, consultez la sectionServices de la documentation Kubernetes.

Enregistrement et surveillance

L'analyse des journaux de conteneurs et de pods individuels à l'aide dekubectl logs etdocker logs peut devenir fastidieuse à mesure que le nombre d'applications en cours d'exécution augmente. Pour vous aider à déboguer des problèmes d’application ou de cluster, vous devez implémenter une journalisation centralisée. À un niveau élevé, il s'agit d'agents s'exécutant sur tous les nœuds de travail qui traitent les fichiers journaux et les flux de pod, les enrichissent avec des métadonnées et transmettent les déconnexions à un backend tel queElasticsearch. À partir de là, les données du journal peuvent être visualisées, filtrées et organisées à l'aide d'un outil de visualisation tel queKibana.

Dans la section relative à la journalisation au niveau du conteneur, nous avons présenté l'approche recommandée par Kubernetes consistant à enregistrer les applications dans les conteneurs dans les flux stdout / stderr. Nous avons également brièvement abordé la journalisation des conteneurs Sidecar, qui peuvent vous donner plus de flexibilité lors de la journalisation à partir de votre application. Vous pouvez également exécuter des agents de journalisation directement dans vos pods pour capturer les données de journal locales et les transférer directement vers votre back-end de journalisation. Chaque approche présente des avantages et des inconvénients, ainsi que des compromis sur l'utilisation des ressources (par exemple, l'exécution d'un conteneur d'agent de journalisation à l'intérieur de chaque pod peut nécessiter beaucoup de ressources et submerger rapidement votre système de journalisation). Pour en savoir plus sur les différentes architectures de journalisation et leurs compromis, consultez lesdocumentation de Kubernetes.

Dans une configuration standard, chaque nœud exécute un agent de journalisation commeFilebeat ouFluentd qui récupère les journaux de conteneur créés par Kubernetes. Rappelez-vous que Kubernetes crée des fichiers journaux JSON pour les conteneurs sur le nœud (dans la plupart des installations, ils peuvent être trouvés à/var/lib/docker/containers/). Celles-ci doivent être tournées à l'aide d'un outil tel que logrotate. L'agent de journalisation du nœud doit être exécuté en tant queDaemonSet Controller, un type de charge de travail Kubernetes qui garantit que chaque nœud exécute une copie du pod DaemonSet. Dans ce cas, le pod contient l'agent de journalisation et sa configuration, qui traite les journaux des fichiers et des répertoires montés dans le pod de journalisation DaemonSet.

Semblable au goulot d'étranglement lié à l'utilisation dekubectl logs pour déboguer les problèmes de conteneur, vous devrez éventuellement envisager une option plus robuste que simplement utiliserkubectl top et le tableau de bord Kubernetes pour surveiller l'utilisation des ressources du pod sur votre cluster. La surveillance au niveau du cluster et de l'application peut être configurée à l'aide du système de surveillance et de la base de données chronologiques dePrometheus et du tableau de bord des métriques deGrafana. Prometheus utilise un modèle «pull», qui gratte périodiquement les points de terminaison HTTP (comme/metrics/cadvisor sur les nœuds, ou les points de terminaison de l'API REST de l'application/metrics) pour les données métriques, qu'il traite et stocke ensuite. Ces données peuvent ensuite être analysées et visualisées à l'aide du tableau de bord Grafana. Prometheus et Grafana peuvent être lancés dans un cluster Kubernetes comme n'importe quel autre service de déploiement et de maintenance.

Pour une résilience accrue, vous souhaiterez peut-être exécuter votre infrastructure de journalisation et de surveillance sur un cluster Kubernetes distinct ou à l'aide de services de journalisation et de métriques externes.

Conclusion

La migration et la modernisation d'une application afin qu'elle puisse s'exécuter efficacement dans un cluster Kubernetes impliquent souvent des quantités non négligeables de planification et d'architecture de modifications logicielles et d'infrastructure. Une fois mises en œuvre, ces modifications permettent aux propriétaires de services de déployer en permanence de nouvelles versions de leurs applications et de les adapter facilement en fonction des besoins, avec un minimum d’interventions manuelles. Des étapes telles que l'externalisation de la configuration de votre application, la configuration correcte de la journalisation et de la publication des mesures et la configuration des vérifications de l'intégrité vous permettent de tirer pleinement parti du paradigme Cloud Native sur lequel Kubernetes a été conçu. En construisant des conteneurs portables et en les gérant à l'aide d'objets Kubernetes tels que Deployments and Services, vous pouvez pleinement utiliser votre infrastructure de calcul et vos ressources de développement disponibles.