7.4. Configuration de la mémoire des clusters pour répondre aux exigences en matière de mémoire des conteneurs et de risque
En tant qu'administrateur de cluster, vous pouvez aider vos clusters à fonctionner efficacement en gérant la mémoire des applications :
- Déterminer les besoins en mémoire et en risques d'un composant d'application conteneurisé et configurer les paramètres de mémoire du conteneur en fonction de ces besoins.
- Configurer les moteurs d'exécution des applications conteneurisées (par exemple, OpenJDK) pour qu'ils adhèrent de manière optimale aux paramètres de mémoire configurés du conteneur.
- Diagnostiquer et résoudre les erreurs de mémoire liées à l'utilisation d'un conteneur.
7.4.1. Comprendre la gestion de la mémoire des applications
Il est recommandé de lire entièrement l'aperçu de la façon dont OpenShift Container Platform gère les ressources informatiques avant de poursuivre.
Pour chaque type de ressource (mémoire, CPU, stockage), OpenShift Container Platform permet de placer des valeurs optionnelles request et limit sur chaque conteneur d'un pod.
Notez les points suivants concernant les demandes de mémoire et les limites de mémoire :
Memory request
- La valeur de la demande de mémoire, si elle est spécifiée, influence le planificateur de OpenShift Container Platform. Le planificateur prend en compte la demande de mémoire lorsqu'il planifie un conteneur sur un nœud, puis délimite la mémoire demandée sur le nœud choisi pour l'utilisation du conteneur.
- Si la mémoire d'un nœud est épuisée, OpenShift Container Platform donne la priorité à l'éviction des conteneurs dont l'utilisation de la mémoire dépasse le plus leur demande de mémoire. Dans les cas graves d'épuisement de la mémoire, le tueur OOM du nœud peut sélectionner et tuer un processus dans un conteneur sur la base d'une métrique similaire.
- L'administrateur du cluster peut attribuer un quota ou des valeurs par défaut pour la valeur de la demande de mémoire.
- L'administrateur du cluster peut remplacer les valeurs de demande de mémoire spécifiées par un développeur, afin de gérer le surengagement du cluster.
Memory limit
- La valeur de la limite de mémoire, si elle est spécifiée, fournit une limite stricte à la mémoire qui peut être allouée à tous les processus d'un conteneur.
- Si la mémoire allouée par tous les processus dans un conteneur dépasse la limite de mémoire, le tueur de nœuds hors mémoire (OOM) sélectionnera et tuera immédiatement un processus dans le conteneur.
- Si une demande de mémoire et une limite sont spécifiées, la valeur de la limite de mémoire doit être supérieure ou égale à la demande de mémoire.
- L'administrateur du cluster peut attribuer des quotas ou des valeurs par défaut pour la limite de mémoire.
-
La limite minimale de mémoire est de 12 Mo. Si un conteneur ne démarre pas en raison d'un événement pod
Cannot allocate memory
, la limite de mémoire est trop basse. Augmentez ou supprimez la limite de mémoire. La suppression de la limite permet aux pods de consommer des ressources de nœuds illimitées.
7.4.1.1. Gestion de la stratégie de mémoire des applications
Les étapes pour dimensionner la mémoire des applications sur OpenShift Container Platform sont les suivantes :
Determine expected container memory usage
Déterminez l'utilisation moyenne et maximale prévue de la mémoire du conteneur, de manière empirique si nécessaire (par exemple, en effectuant des tests de charge distincts). N'oubliez pas de tenir compte de tous les processus susceptibles de s'exécuter en parallèle dans le conteneur : par exemple, l'application principale génère-t-elle des scripts auxiliaires ?
Determine risk appetite
Déterminer le risque d'éviction. Si l'appétit pour le risque est faible, le conteneur doit demander de la mémoire en fonction de l'utilisation maximale prévue plus un pourcentage de marge de sécurité. Si l'appétit pour le risque est plus élevé, il peut être plus approprié de demander de la mémoire en fonction de l'utilisation moyenne prévue.
Set container memory request
Définir la demande de mémoire du conteneur en fonction de ce qui précède. Plus la demande représente précisément l'utilisation de la mémoire de l'application, mieux c'est. Si la demande est trop élevée, l'utilisation du cluster et du quota sera inefficace. Si la demande est trop faible, les risques d'éviction de l'application augmentent.
Set container memory limit, if required
Définir la limite de mémoire du conteneur, si nécessaire. Définir une limite a pour effet de tuer immédiatement un processus du conteneur si l'utilisation combinée de la mémoire de tous les processus du conteneur dépasse la limite, et c'est donc une bénédiction mitigée. D'une part, cela peut rendre évident un excès d'utilisation de la mémoire non anticipé ("fail fast") ; d'autre part, cela met fin aux processus de manière abrupte.
Notez que certains clusters OpenShift Container Platform peuvent exiger qu'une valeur limite soit définie ; certains peuvent surcharger la demande en fonction de la limite ; et certaines images d'application s'appuient sur une valeur limite définie car elle est plus facile à détecter qu'une valeur de demande.
Si une limite de mémoire est fixée, elle ne doit pas être inférieure au pic prévu d'utilisation de la mémoire du conteneur, plus un pourcentage de marge de sécurité.
Ensure application is tuned
Assurez-vous que l'application est adaptée aux demandes configurées et aux valeurs limites, le cas échéant. Cette étape est particulièrement importante pour les applications qui mettent en commun de la mémoire, comme la JVM. Le reste de cette page en traite.
Ressources supplémentaires
7.4.2. Comprendre les paramètres OpenJDK pour OpenShift Container Platform
Les paramètres par défaut de l'OpenJDK ne fonctionnent pas bien avec les environnements conteneurisés. Par conséquent, certains paramètres supplémentaires de la mémoire Java doivent toujours être fournis lorsque l'OpenJDK est exécuté dans un conteneur.
L'agencement de la mémoire de la JVM est complexe, dépend de la version et sa description détaillée dépasse le cadre de cette documentation. Cependant, comme point de départ pour l'exécution d'OpenJDK dans un conteneur, au moins les trois tâches suivantes liées à la mémoire sont essentielles :
- Surcharge de la taille maximale du tas de la JVM.
- Encourager la JVM à libérer la mémoire inutilisée au profit du système d'exploitation, le cas échéant.
- S'assurer que tous les processus JVM au sein d'un conteneur sont correctement configurés.
L'optimisation des charges de travail JVM pour l'exécution dans un conteneur dépasse le cadre de cette documentation et peut nécessiter la définition de plusieurs options JVM supplémentaires.
7.4.2.1. Comprendre comment passer outre la taille maximale du tas de la JVM
Pour de nombreuses charges de travail Java, le tas de la JVM est le plus gros consommateur de mémoire. Actuellement, l'OpenJDK autorise par défaut jusqu'à 1/4 (1/-XX:MaxRAMFraction
) de la mémoire du nœud de calcul à être utilisée pour le tas, que l'OpenJDK soit exécuté dans un conteneur ou non. Il est donc possible d'outrepasser ce comportement à l'adresse essential, en particulier si une limite de mémoire est également fixée pour le conteneur.
Il y a au moins deux façons d'y parvenir :
Si la limite de mémoire du conteneur est définie et que les options expérimentales sont prises en charge par la JVM, définissez
-XX: UnlockExperimentalVMOptions -XX: UseCGroupMemoryLimitForHeap
.NoteL'option
UseCGroupMemoryLimitForHeap
a été supprimée dans le JDK 11. Utilisez-XX: UseContainerSupport
à la place.Cela fixe
-XX:MaxRAM
à la limite de mémoire du conteneur et la taille maximale du tas (-XX:MaxHeapSize
/-Xmx
) à 1/-XX:MaxRAMFraction
(1/4 par défaut).Remplacer directement l'un des éléments suivants :
-XX:MaxRAM
,-XX:MaxHeapSize
ou-Xmx
.Cette option implique le codage en dur d'une valeur, mais présente l'avantage de permettre le calcul d'une marge de sécurité.
7.4.2.2. Comprendre comment encourager la JVM à libérer la mémoire inutilisée au profit du système d'exploitation
Par défaut, l'OpenJDK ne renvoie pas agressivement la mémoire inutilisée au système d'exploitation. Cela peut convenir à de nombreuses charges de travail Java conteneurisées, mais les exceptions notables comprennent les charges de travail où des processus actifs supplémentaires coexistent avec une JVM dans un conteneur, que ces processus supplémentaires soient natifs, des JVM supplémentaires ou une combinaison des deux.
Les agents basés sur Java peuvent utiliser les arguments JVM suivants pour encourager la JVM à libérer la mémoire inutilisée au profit du système d'exploitation :
-XX:+UseParallelGC -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90.
Ces arguments sont destinés à restituer la mémoire du tas au système d'exploitation lorsque la mémoire allouée dépasse 110 % de la mémoire utilisée (-XX:MaxHeapFreeRatio
), en consacrant jusqu'à 20 % du temps de l'unité centrale au ramasse-miettes (-XX:GCTimeRatio
). À aucun moment, l'allocation du tas de l'application ne sera inférieure à l'allocation initiale du tas (surchargée par -XX:InitialHeapSize
/ -Xms
). Des informations complémentaires détaillées sont disponibles sur Tuning Java's footprint in OpenShift (Part 1), Tuning Java's footprint in OpenShift (Part 2), et sur OpenJDK and Containers.
7.4.2.3. Comprendre comment s'assurer que tous les processus JVM au sein d'un conteneur sont correctement configurés
Si plusieurs JVM sont exécutées dans le même conteneur, il est essentiel de s'assurer qu'elles sont toutes configurées de manière appropriée. Pour de nombreuses charges de travail, il sera nécessaire d'accorder à chaque JVM un pourcentage de budget mémoire, en laissant une marge de sécurité supplémentaire peut-être substantielle.
De nombreux outils Java utilisent différentes variables d'environnement (JAVA_OPTS
, GRADLE_OPTS
, etc.) pour configurer leurs JVM et il peut être difficile de s'assurer que les bons paramètres sont transmis à la bonne JVM.
La variable d'environnement JAVA_TOOL_OPTIONS
est toujours respectée par l'OpenJDK et les valeurs spécifiées dans JAVA_TOOL_OPTIONS
seront remplacées par d'autres options spécifiées sur la ligne de commande de la JVM. Par défaut, pour s'assurer que ces options sont utilisées par défaut pour toutes les charges de travail JVM exécutées dans l'image de l'agent basé sur Java, l'image de l'agent OpenShift Container Platform Jenkins Maven définit :
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dsun.zip.disableMemoryMapping=true"
L'option UseCGroupMemoryLimitForHeap
a été supprimée dans le JDK 11. Utilisez -XX: UseContainerSupport
à la place.
Cela ne garantit pas que des options supplémentaires ne soient pas nécessaires, mais constitue un point de départ utile.
7.4.3. Recherche de la demande et de la limite de mémoire à l'intérieur d'un pod
Une application souhaitant découvrir dynamiquement sa demande et sa limite de mémoire à l'intérieur d'un pod doit utiliser l'API descendante.
Procédure
Configurez le pod pour ajouter les strophes
MEMORY_REQUEST
etMEMORY_LIMIT
:apiVersion: v1 kind: Pod metadata: name: test spec: containers: - name: test image: fedora:latest command: - sleep - "3600" env: - name: MEMORY_REQUEST 1 valueFrom: resourceFieldRef: containerName: test resource: requests.memory - name: MEMORY_LIMIT 2 valueFrom: resourceFieldRef: containerName: test resource: limits.memory resources: requests: memory: 384Mi limits: memory: 512Mi
Créer la capsule :
oc create -f <nom-de-fichier>.yaml
Accéder au pod à l'aide d'un shell distant :
$ oc rsh test
Vérifier que les valeurs demandées ont été appliquées :
$ env | grep MEMORY | sort
Exemple de sortie
MEMORY_LIMIT=536870912 MEMORY_REQUEST=402653184
La valeur de la limite de mémoire peut également être lue à l'intérieur du conteneur par le fichier /sys/fs/cgroup/memory/memory.limit_in_bytes
.
7.4.4. Comprendre la politique de mise à mort des OOM
OpenShift Container Platform peut tuer un processus dans un conteneur si l'utilisation totale de la mémoire de tous les processus dans le conteneur dépasse la limite de mémoire, ou dans des cas graves d'épuisement de la mémoire du nœud.
Lorsqu'un processus est tué pour cause de mémoire insuffisante (OOM), il se peut que le conteneur se termine immédiatement. Si le processus PID 1 du conteneur reçoit le message SIGKILL, le conteneur se termine immédiatement. Sinon, le comportement du conteneur dépend du comportement des autres processus.
Par exemple, un processus de conteneur s'est terminé avec le code 137, indiquant qu'il avait reçu un signal SIGKILL.
Si le conteneur ne sort pas immédiatement, un OOM kill est détectable comme suit :
Accéder au pod à l'aide d'un shell distant :
# oc rsh test
Exécutez la commande suivante pour voir le nombre actuel de morts OOM dans
/sys/fs/cgroup/memory/memory.oom_control
:$ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control oom_kill 0
Exécutez la commande suivante pour provoquer une mise à mort OOM :
$ sed -e '' </dev/zero
Exemple de sortie
Killed
Exécutez la commande suivante pour afficher l'état de sortie de la commande
sed
:$ echo $?
Exemple de sortie
137
Le code
137
indique que le processus du conteneur s'est arrêté avec le code 137, indiquant qu'il a reçu un signal SIGKILL.Exécutez la commande suivante pour vérifier que le compteur de mises à mort OOM de
/sys/fs/cgroup/memory/memory.oom_control
s'est incrémenté :$ grep '^oom_kill ' /sys/fs/cgroup/memory/memory.oom_control oom_kill 1
Si un ou plusieurs processus d'un module sont tués par OOM, lorsque le module se termine par la suite, immédiatement ou non, il aura la phase Failed et la raison OOMKilled. Un module tué par OOM peut être redémarré en fonction de la valeur de
restartPolicy
. S'il n'est pas redémarré, les contrôleurs tels que le contrôleur de réplication remarqueront l'état d'échec du module et créeront un nouveau module pour remplacer l'ancien.Utilisez la commande suivante pour obtenir l'état du pod :
$ oc get pod test
Exemple de sortie
NAME READY STATUS RESTARTS AGE test 0/1 OOMKilled 0 1m
Si le module n'a pas redémarré, exécutez la commande suivante pour visualiser le module :
$ oc get pod test -o yaml
Exemple de sortie
... status: containerStatuses: - name: test ready: false restartCount: 0 state: terminated: exitCode: 137 reason: OOMKilled phase: Failed
En cas de redémarrage, exécutez la commande suivante pour afficher le pod :
$ oc get pod test -o yaml
Exemple de sortie
... status: containerStatuses: - name: test ready: true restartCount: 1 lastState: terminated: exitCode: 137 reason: OOMKilled state: running: phase: Running
7.4.5. Comprendre l'expulsion d'un pod
OpenShift Container Platform peut évincer un pod de son nœud lorsque la mémoire du nœud est épuisée. En fonction de l'ampleur de l'épuisement de la mémoire, l'expulsion peut être gracieuse ou non. L'expulsion gracieuse implique que le processus principal (PID 1) de chaque conteneur reçoive un signal SIGTERM, puis un peu plus tard un signal SIGKILL si le processus n'est pas déjà sorti. L'éviction non gracieuse implique que le processus principal de chaque conteneur reçoive immédiatement un signal SIGKILL.
Un pod évincé a la phase Failed et la raison Evicted. Il ne sera pas redémarré, quelle que soit la valeur de restartPolicy
. Cependant, les contrôleurs tels que le contrôleur de réplication remarqueront l'état d'échec du module et créeront un nouveau module pour remplacer l'ancien.
$ oc get pod test
Exemple de sortie
NAME READY STATUS RESTARTS AGE test 0/1 Evicted 0 1m
$ oc get pod test -o yaml
Exemple de sortie
... status: message: 'Pod The node was low on resource: [MemoryPressure].' phase: Failed reason: Evicted