The Twelve-Factors Kubernetes

“Kubernetes is the Linux of the cloud”This quote by Kelsey Hightower during the Kubecon 2017 in Austin emphasize the rise of Kubernetes among modern cloud infrastructures.

This rise is partly driven by the developers community, but also by the web giants such as Google, Amazon, Alibaba or Red Hat who have invested a lot on this technology, and keep contributing to its improvement and smoothening its integration to their respective ecosystems. EKS for AWS, GKE for Google and AKS for Azure are good illustrations of that.

This article lists the 12 basic rules and good practices to know about when starting using Kubernetes optimally. This list interests anyone, developer or sysadmin, who uses K8s daily.

I. 1 Pod = 1 or n containers

The naming is important to make sure everyone is on the same page about what’s what. In the Kubernetes world, a Pod is the smallest computing unit deployable on a cluster. It’s made of one or more containers. The containers within a Pod share the same IP, the same storage and are co-located on the same node of the cluster.

To go further: https://kubernetes.io/docs/concepts/workloads/pods/pod/

II. Labels everywhere

Most Kubernetes resources can be labelled: the Pods, the Nodes, the Namespaces etc. This labelling is done injecting a key-value pair into the metadata. Labelling components allows for 2 things:

  • A technical use, as many inter-dependant resources use labels to identify one another. For instance, I can label part of my Nodes “zone: front” because these nodes are likely to host web applications. I then assign a affinity to my frontend Pods so that they get hosted by the nodes labelled “zone: front”.
  • An organisational use: assign labels allow to easily identify resources and request them efficiently. For instance, to retrieve all nodes in the front zone, i can run:

$> kubectl get nodes -l zone=front

To go further: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

III. Infrastructure as Code and versioning

All Kubernetes resources can be written as a YAML or JSON files. The resource creation from a file is done with the command line:

$> kubectl apply -f MYFILE.{yaml,json}

The apply command does a smart diff, so it only creates the resource if it wasn’t already there, updates it if the file was changed, and does nothing otherwise.

The use of files allows to track, version and reproduce the complete system at any time. It is therefore a commonly adopted practice to to version the K8sresource description files with the same rigor as for the code.

To go further: https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#kubectl-apply

IV. A Service to expose

Pods never communicate directly with one another, they got through a Service, because Pods are volatile and short-lived across the cluster. During some maintenance operations, some Pods may migrate from one node to another. These same Pods may also reboot, scale out, or even be destroyed, when upgrading for instance. In each of these cases, the Pod IP changes, as well as its name. The Service is a Kubernetes resource located in front of the Pods and allowing for some ports of the Pods on the network. Services have a fixed name and a fixed dedicated IP. Thus you can access your Pods whatever their IPs or names. The matching between Services and Pods relies on labels. When the Service matches several Pods, it load-balances the traffic with a round-robin algorithm.

To go further: https://kubernetes.io/docs/tutorials/kubernetes-basics/expose-intro/

V. ConfigMap and secret to configure

ConfigMaps and Secrets are Kubernetes resources allowing to manage the Pods configuration. The configuration is described as a set of key-values. These configurations are then injected into the Pods as environment variables or as configuration files mounted on the the containers.
The use of these resources allows to decouple the Pods description from any configuration. Whether they are written in YAML or JSON, the configurations are versionable (except for Secrets which hold sensitive information).

To go further : https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

VI. Limit and Request to control the resources utilization

In the many Pods configuration options, it is possible to define the resources used and usable by the Pod (CPU and memory):

  • requests, this configuration is applicable to CPU or memory. It defines the minimum resources the Pod will need to run. These values are used by the scheduler at the time of the node allocation. It also enables auto-scaling. The target CPU utilisation is based on the requested CPU and the Pods autoscaler – which is also a Kubernetes resource – will automatically scale up or down the number of Pods to reach it.
  • limits, just like request, this configuration is applicable to CPU and memory. It defines the maximum amount of resources usable by the Pod. Defining these parameters prevents a failing Pod to compromise the whole cluster by consuming all the resources.

If the Kubernetes cluster administrator defined resource quotas for the Namespaces, defining requests and limits becomes mandatory, or the Pod won’t be scheduled. When these values aren’t defined, the administrator may also define default values in a K8s resource named LimitRange.

To go further: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

VII. Think of the Pods lifecycle

It is possible to deploy a Pod by describing its configuration in a YAML/JSON file and injecting it into K8s with the kubectl client. Be careful, this method does not benefit from the Pods resilience offered by K8s by design. If the Pod crashes, it won’t be automatically replaced. It is recommended to use a Deployment. This K8s object allows to write a Pod along with its configuration but it also hides the resilience complexity. Indeed, the Deployment will generate a ReplicaSet. The only goal of the ReplicaSet is to make sure that the number of running Pods matches the desired number of Pods. It also provides the abilityto scale Pods at will. The Deployment allows to configure the deployment strategies. It is for instance possible to define a rolling-update strategy in case of a new version of a Pod’s container.

The following command allows to start Pods (for instance, a Nginx):

$> kubectl run nginx image=nginx --replicas=2

This command will generate a Deployment with a Pod running the Nginx container. The same Deployment will also generate the ReplicaSet which will ensure 2 Pods are running at any time.

To go further: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

VIII. LivenessProbe to monitor

As we saw, the ReplicaSet allows to ensure the number of running Pods matches the number of desired Pods. It restarts any failing Pods. It is also possible to configure the Pods resiliency on the functional level. For that there is the LivenessProbe option. It provides the abilityto automatically restart the Pod if the condition is not verified.

Just like LivenessProbe monitors the status of an application, the ReadinessProbe monitors when an application is available after reboot. This is useful for an application that runs tasks before it actually starts (eg. data injection).

To go further: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/

IX. Latest is not a version

K8s is a container orchestrator and the name of the container to deploy is specified in the Pod configuration. The naming of an image is composed as such:

<Registry name>/<Container name>:<tag or version>

It is a common practice to increment the image version just like you increment the version of a code base, but also to assign the tag “latest” to the last build image.
Configuring a Pod to deploy an image with the tag “latest” is not a good practice for several reasons:

  • No control over the deployed version, and some possible side effects related to the new versions of the image components.
  • Latest may be buggy.
  • It is possible to configure the Pods to select an images “pull” strategy. It’s the “ImagePullPolicy” option which can have 3 values:
    • IfNotPresent: Pull image only if it isn’t locally available on the node
    • Always: Always pull image
    • Never : Never pull image

IfnotPresent is the default option, so if we use an image with the tag “latest”, Kubernetes will fetch the “latest” version image from at the first deployment. Then as it will be locally present on the node for subsequent deployments, it won’t download the image again from the registry, even if a new “latest” version image was pushed.

To go further: https://kubernetes.io/docs/concepts/configuration/overview/#container-images

X. Pods are stateless

Pods are short lived and volatile, they can be moved to other nodes in case of maintenance, deployments or reboot. They can also – and that’s a big perk of K8s like systems – scale on demand. The inbound flow to Pods is load-balanced by the Service in front of the Pods. That’s why applications hosted on K8s must use a third party provider to store data. For instance an e-commerce website storing session information as files within a container (let’s say a purchase cart) will lose data when the Pod scales or restarts.

The solution to address this issue vary depending use cases. For instance, a key-value storage service (redis, memcache) can be considered to store session data. For a file hosting application, an object storage solution such as AWS S3 will be favored.

To go further: https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/

XI. Distributed Storage Volumes

As we have seen, your applications should be stateless. You may need however to deploy stateful components requiring a storage layer such as a database. Kubernetes provides the abilityto mount volumes within the Pods. It then becomes possible to mount a volume provided by AWS, Azure of Google’s storage services. The storage is then external of your cluster and remains attached to the Pod in case of a redeployment to a different node. It is also possible to mount a volume between the node where the Pod is deployed and the Pod itself. But this solution should not be considered. Indeed, your Pod and its volume attached to the host may be migrated and lose all its data.

To go further: https://kubernetes.io/docs/concepts/storage/volumes/

XII. My applications are 12 factors apps

The application code that will eventually be deployed to a Kubernetes cluster has to respect a set of rules. The 12 factors apps are a set of advice/good practices created by Heroku. Heroku is a PaaS provider hosting applications as containers, and these principles are a way to best operate code meant to be containerized.

The main recommendations are:

  • Code versioning (GIT)
  • Providing a health check URL
  • Stateless application
  • Environment variable based configuration
  • Log output to standard or error output
  • Degraded mode management
  • Graceful start/stop

To go further: https://blog.octo.com/applications-node-js-a-12-facteurs-partie-1-une-base-de-code-saine/

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *


Ce formulaire est protégé par Google Recaptcha

The Twelve-Factors Kubernetes

“Kubernetes est le Linux du cloud” cette phrase prononcée par Kelsey Hightower lors de la Kubecon 2017 à Austin montre bien l’émergence de Kubernetes dans les infrastructures cloud modernes. Cette émergence est tirée d’une part par l’ensemble de la communauté de développeurs mais aussi par des géants du web comme : Google, Amazon, Alibaba ou encore Red Hat qui misent beaucoup sur cette technologie, contribuent à son amélioration et à son intégration avec leurs écosystèmes respectifs. EKS pour AWS, GKE pour Google et AKS pour Azure en sont les premiers exemples.

Cet article liste les 12 règles de base ainsi que les bonnes pratiques à avoir en tête lorsque l’on souhaite utiliser Kubernetes au mieux. Cette liste s’adresse à toutes les personnes, développeurs ou bien administrateurs système qui utilisent K8s au quotidien.

I. 1 Pod = 1 ou n conteneurs

La terminologie est importante pour bien aligner tout le monde sur le même langage. Dans le monde Kubernetes, un Pod est la plus petite unité de calcul déployable sur un cluster. Il se compose de 1 ou plusieurs conteneurs. Les conteneurs au sein d’un Pod partagent la même IP, le même stockage et sont colocalisés sur le même noeud du cluster.

Pour aller plus loin : https://kubernetes.io/docs/concepts/workloads/pods/pod/

II. Labels everywhere

La plupart des ressources Kubernetes peuvent être labellisées : les Pods, les Nodes, les Namespaces etc … Cette labellisation se présente sous la forme d’un champ clé valeur injecté dans les métadonnées. Labelliser ses composants a 2 vertues :

  • Un usage technique, en effet de nombreuses ressources inter-dépendantes se basent sur les labels pour s’identifier. Exemple : je labellise une partie de mes noeuds (Nodes) “zone: front” car ces noeuds sont susceptibles d’accueillir des applications de type web. Je donne une affinité à mes Pods de type frontend de sorte à ce qu’ils se placent sur les noeuds portant le label “zone: front”.
  • Un usage organisationnel : poser des labels permet de mieux identifier les ressources et surtout permet de les requêter efficacement.

Exemple : Je souhaite avoir la liste de mes noeuds qui sont en zone front.

$> kubectl get nodes -l zone=front

Pour aller plus loin : https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

III. Infrastructure as Code et versionnement

L’ensemble des ressources Kubernetes peut être décrit sous forme de fichier YAML ou JSON. La création de ressources à partir d’un fichier se fait via la commande :

$> kubectl apply -f MONFICHIER.{yaml,json}

La commande apply réalise un diff intelligent, en effet : elle ajoute la ressource si celle-ci n’est pas présente, la modifie si le fichier a été changé et ne fait rien si elle existe déjà.

L’utilisation des fichiers permet de tracer, de versionner et de reproduire le système complet à chaque instant. Il est donc d’usage de versionner les fichiers de description des ressources K8s au même titre que du code.

Pour aller plus loin : https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#kubectl-apply

IV. Un Service pour exposer

Les Pods ne communiquent jamais entre eux directement, ils passent par un Service car les Pods sont volatiles et éphémères au travers du cluster. Il peut arriver que pour des opérations de maintenance certains Pods migrent d’un noeud à un autre. Il peut également arriver que ces Pods redémarrent, passent à l’échelle (scaling) ou même soient détruits en cas de montée de version notamment. Dans chacun des cas, l’IP du Pod change ainsi que son nom. Le Service est une ressource Kubernetes à part entière se plaçant devant les Pods et permettant d’exposer certains ports des Pods sur le réseau. Les Services ont un nom fixe et une IP propre qui est fixe elle aussi. Ainsi nous pouvons accéder à nos Pods quelles que soient leurs IPs ou noms. Le matching entre les Services et les Pods se fait au travers des labels. Lorsque le Service match plusieurs Pods celui-ci loadbalance le trafic en round-robin.

Pour aller plus loin : https://kubernetes.io/docs/tutorials/kubernetes-basics/expose-intro/

V. ConfigMap et secret pour configurer

Les ConfigMaps et les Secrets sont des ressources Kubernetes permettant de gérer la configuration des Pods. La configuration y est décrite sous forme de paires clé-valeur. Ces configurations sont ensuite injectées dans les Pods soit sous forme de variables d’environnement soit sous forme de fichiers de configuration directement montés dans les conteneurs.

L’utilisation de ces ressources permet de décorréler la description des Pods de toute configuration. Pouvant être décrit en YAML ou JSON, les configurations sont versionables (sauf les Secrets qui contiennent des données sensibles).

Pour aller plus loin : https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

VI. Limit et Request pour maitriser l’utilisation des ressources

Dans les nombreuses options de configurations des Pods, il est possible de définir les ressources utilisées et utilisables par le Pod (cpu, et mémoire) :

  • requests, cette configuration est applicable au cpu ou à la mémoire, elle définit les ressources minimum dont le Pod a besoin pour fonctionner. Ces valeurs seront utilisées par le scheduler au moment du choix du placement.
  • limits, comme pour la request cette configuration s’applique sur le cpu et la mémoire. Elle définit la quantité maximale de ressources utilisables par le Pod. Définir ces paramètres permet d’éviter qu’un Pod défaillant mette en péril tout le cluster en consommant l’intégralité des ressources. Cela permet également de mettre en place de l’autoscaling. L’autoscaler de Pods – qui est également une ressource Kubernetes – se base sur la limit de cpu et peut scaler automatiquement si par exemple 80% de la limit est atteinte.

Si l’administrateur du cluster Kubernetes a défini des quotas de ressources pour les Namespaces, il devient alors obligatoire de définir les valeurs de requests et de limits, sinon le Pod ne sera pas schedulé. Il se peut également qu’en l’absence de ces valeurs l’administrateur en définisse par défaut dans une ressource K8s appelée LimitRange.

Pour aller plus loin : https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

VII. Penser au cycle de vie des Pods

Il est possible de déployer un Pod en décrivant directement sa configuration en YAML/JSON et de l’injecter dans K8s avec le client kubectl. Attention, passer par cette méthode nous prive de la résilience des Pods proposée par design par K8s. Si le Pod tombe, il ne sera pas relancé automatiquement. Il est recommandé de passer par un Deployment. Cet objet K8s permet de décrire un Pod avec toute sa configuration mais il cache également la complexité de la résilience. En effet le Deployment va générer un ReplicaSet. Le seul rôle du ReplicaSet est d’assurer que le nombre de Pods présents correspond au nombre de Pods souhaités, il permet également de faire scaler les Pods à la demande. Le Deployment permet de configurer les stratégies de déploiement. Il est par exemple possible de définir une stratégie de rolling-update en cas de montée de version d’un conteneur d’un Pod.

La commande suivante permet de démarrer des Pods (ex: pour un nginx) :

$> kubectl run nginx image=nginx --replicas=2

Cette commande va générer un Deployment avec la configuration d’un Pod exécutant le conteneur nginx, ce même Deployment va également générer le ReplicaSet qui va s’assurer qu’il y a bien 2 Pods qui tournent à chaque instant.

Pour aller plus loin : https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

VIII. LivenessProbe pour surveiller

Nous l’avons vu, le rôle du ReplicaSet est de garantir que le nombre de Pods qui tournent est conforme au nombre de Pods souhaité. Il relance donc les Pods qui tombent en erreur. Il est possible de configurer les Pods pour assurer la résilience mais au niveau fonctionnel cette fois. Pour cela il existe l’option : LivenessProbe. Elle permet de vérifier grâce à une URL (/healthz) ou un script si l’application est fonctionnelle. Cela permet de relancer automatiquement le Pod si la condition n’est pas vérifiée.

De la même façon que l’option LivenessProbe vérifie le statut d’une application, il existe l’option ReadinessProbe qui teste le moment où une application est disponible suite à son démarrage. Cela est utile pour une application qui fait des opérations avant de démarrer (Injection de données).

Pour aller plus loin : https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/

IX. Latest n’est pas une version

K8s est un orchestrateur de conteneurs et le nom du conteneur à déployer est spécifié dans la configuration du Pod. La nomenclature d’un nom d’une image est la suivante :
<Nom de la registry>/<Nom du conteneur>:<tag ou version>


Il est d’usage d’incrémenter la version d’une image au même titre que nous incrémentons les versions du code, mais aussi de poser le tag “latest” sur la dernière image construite.

Configurer un Pod pour déployer une image avec le tag “latest” n’est pas une bonne pratique pour plusieurs raisons :

  • Pas de maîtrise de la version déployée, potentiellement des effets de bords liés aux nouvelles versions des composants de l’image.
  • Latest peut contenir des bugs.
  • Il est possible de configurer les Pods pour choisir une stratégie de “pull” des images. C’est l’option “ImagePullPolicy” cette option peut prendre 3 valeurs:
    • IfNotPresent : Pull l’image seulement si elle n’est pas disponible localement sur le noeud
    • Always : Pull l’image systématiquement
    • Never : Ne pull jamais l’image

IfnotPresent est l’option par défaut, donc si nous utilisons une image avec le tag latest, au premier déploiement Kubernetes ira pull l’image en version latest, puis comme elle est déjà présente sur le noeud il ne re-téléchargera pas l’image en cas de nouveau déploiement, même si une nouvelle image latest à été créée et est présente sur la registry.

Pour aller plus loin : https://kubernetes.io/docs/concepts/configuration/overview/#container-images

X. Les Pods sont stateless

Les Pods sont éphémères et volatiles, ils peuvent être amenés à changer de noeud en cas de maintenance, de nouveau déploiement ou s’ils sont redémarrés. Ils peuvent également – et c’est un des gros intérêts d’un système comme K8s – scaler à la demande. Le flux entrant dans les Pods est alors load-balancé par le Service devant les Pods. C’est la raison pour laquelle les applications hébergées sur K8s doivent utiliser une solution tierce pour réaliser du stockage. Par exemple un site de e-commerce qui persiste des données de session par fichier au sein du conteneur (au hasard un panier d’achat). En cas de scaling ou de redémarrage du Pod les données seront perdues.

Les solutions pour palier ce problème diffèrent suivant les usecases. Un service de stockage clé/valeur (redis, memcache) peut être envisagé pour stocker les données de sessions par exemple. Pour une application hébergeant des fichiers et autres pièces jointes on privilégiera une solution de stockage objet comme AWS S3.

Pour aller plus loin : https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/

XI. Les Volumes sur stockage distribué

Nous l’avons vu, nos applications doivent être stateless. Il arrive cependant de déployer des composants stateful nécessitant une couche de stockage, les bases de données par exemple. Kubernetes permet de monter des volumes au sein des Pods. Il est alors possible de monter un volume issu du block storage AWS, Azure ou Google. Le stockage est alors externe à notre cluster et suit le Pod en cas de redéploiement sur un noeud différent. Il est également possible de monter un volume entre le noeud sur lequel est déployé le Pod et le Pod. Ce mode de montage ne doit à aucun moment être envisagé. Notre Pod avec son volume rattaché sur l’hôte est susceptible de migrer et de perdre ainsi toutes ses données.

Pour aller plus loin : https://kubernetes.io/docs/concepts/storage/volumes/

XII. Mes applications sont 12 factors app

Le code applicatif qui à terme sera déployé sur un cluster Kubernetes se doit de respecter un certain nombre de règles. Les 12 factors apps sont un ensemble de conseils/bonnes pratiques créé par Heroku. Heroku est un fournisseur de plateforme PaaS hébergeant des applications sous forme de conteneurs et ces principes sont un moyen d’opérer au mieux du code à vocation d’être conteneurisé.

Les principales recommandations sont:

  • Versionnement du code (GIT)
  • Exposition d’une URL de healthcheck
  • Application stateless
  • Configuration par variables d’environnement
  • La sortie des logs sur la sortie standard ou la sortie d’erreur
  • Gestion du mode dégradé.
  • Gestion des arrêts/relances de manière propre.

Pour aller plus loin : https://blog.octo.com/applications-node-js-a-12-facteurs-partie-1-une-base-de-code-saine/

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *


Ce formulaire est protégé par Google Recaptcha