Comment résoudre l’épuisement des adresses IPs avec EKS et VPC CNI

Amazon VPC CNI est l’addon réseau d’Amazon pour les clusters EKS. Il implémente la gestion réseau sous forme d’un DaemonSet appelé aws-node, déployé dans le cluster. Ce DaemonSet est constitué de deux composants principaux : L-IPAM (Local IP Address Management) et le plugin CNI.

L-IPAM :

  • Gère la création, l’association et la suppression des Elastic Network Interfaces (ENI) sur chaque nœud.
  • Alloue des adresses ou des préfixes IP aux ENI dès qu’un nœud est provisionné.
  • Maintient un ensemble pré-assigné d’adresses IP et d’ENI pour accélérer le démarrage des Pods planifiés.

Plugin CNI :

  • Configure les interfaces réseau et les paires ethernet virtuelles (veth) pour connecter les Pods.
  • Ajoute la bonne interface réseau au namespace du Pod concerné.
  • Assure la communication Pod-à-Pod et entre les Pods et les services externes.
  • Dialogue avec IPAMD (IP Adress Management Daemon) via le protocole RPC (Remote Procedure Call) pour coordonner les assignations IP.

Les adresses IP sont prises dynamiquement dans le sous-réseau primaire de l’interface réseau principale du nœud. Cette configuration garantit que les Pods et leurs nœuds utilisent le même sous-réseau, simplifiant ainsi la gestion réseau et la connectivité.

Optimiser la configuration de aws-node

Le mode par défaut du CNI Amazon VPC est le Secondary IP mode, dans lequel le binaire CNI (/opt/cni/bin/aws-cni) utilise IPAMD pour gérer les adresses IP et les ENIs (Elastic Network Interfaces) attachées à chaque instance.

Fonctionnement :

  • IPAMD vérifie périodiquement que le nombre correct d’adresses IP et d’ENIs est attaché à l’instance.
  • Cette vérification repose sur trois variables d’environnement configurables :
    • WARM_ENI_TARGET : Nombre minimum d’ENIs préchauffées (avec des adresses IP) à maintenir.
    • WARM_IP_TARGET : Nombre minimum d’adresses IP non utilisées prêtes à être assignées aux Pods.
    • MINIMUM_IP_TARGET : Nombre minimal d’adresses IP à attacher, quelles que soient les ENIs disponibles.

Problèmes du mode par défaut

  1. Configuration par défaut inefficace (WARM_ENI_TARGET=1) :
    • Cette valeur signifie qu’IPAMD conserve une ENI pleine d’adresses IP attachée à chaque instance, prête à être assignée à des Pods.
    • Exemple d’impact :
      • Une instance t3.small (max. 3 ENIs, 9 IPs par ENI) avec 5 Pods actives :
        • Résultat : 3 ENIs attachées, 9 IPs assignées (5 utilisées, 4 inutilisées).
      • Une instance p3dn.24xlarge (max. 15 ENIs, 50 IPs par ENI) avec 3 Pods actives :
        • Résultat : 2 ENIs attachées, 100 IPs assignées (3 utilisées, 97 inutilisées).
  2. Combinaisons inadaptées de variables :
    • Par exemple, avec WARM_IP_TARGET=5, MINIMUM_IP_TARGET=10 et 7 Pods actifs, une mauvaise configuration peut entraîner un surplus d’ENIs/IPs inutilisées.

Comment ajuster les variables d’environnement

Si le plugin Amazon VPC CNI est déjà activé sur un cluster EKS, vous pouvez modifier les variables d’environnement dans le DaemonSet aws-node dans le namespace kube-system :

# Configure the WARM_ENI_TARGET, WARM_IP_TARGET,MINIMUM_IP_TARGET
kubectl set env ds aws-node -n kube-system WARM_ENI_TARGET=1
kubectl set env ds aws-node -n kube-system WARM_IP_TARGET=1
kubectl set env ds aws-node -n kube-system MINIMUM_IP_TARGET=1
# Confirm if environment variables are set 
kubectl describe daemonset -n kube-system aws-node | grep WARM_ENI_TARGET
kubectl describe daemonset -n kube-system aws-node | grep WARM_IP_TARGET
kubectl describe daemonset -n kube-system aws-node | grep MINIMUM_IP_TARGET

Optimisation recommandée

  • Réduire les adresses IP inutilisées : Ajustez WARM_ENI_TARGET et WARM_IP_TARGET en fonction de vos charges de travail pour limiter le gaspillage de ressources.
  • Évitez une limite trop basse : Un WARM_IP_TARGET trop faible peut augmenter le nombre d’appels API AWS, entraînant des latences dans l’attribution des IPs.

Avec une configuration adaptée à vos besoins, vous pouvez optimiser l’utilisation des ressources réseau tout en maintenant des performances élevées.

Configurer un réseau personnalisé pour éviter l’épuisement d’IP

Un réseau personnalisé permet de gérer efficacement les ressources IP en allouant des sous-réseaux dédiés aux Pods. Voici les étapes nécessaires pour configurer un tel réseau :

Étape 1 : Configurer votre VPC

1. Ajouter des sous-réseaux privés :

Créez de nouveaux sous-réseaux dans le VPC souhaité :

SUBNET_ID=$(aws ec2 create-subnet \
    --vpc-id vpc-1234567890abcdef0 \
    --cidr-block 10.0.1.0/24 \
    --availability-zone us-east-1a \
    --query 'Subnet.SubnetId' \
    --output text)

2. Désactiver l’attribution d’IP publiques :

aws ec2 modify-subnet-attribute \
    --subnet-id $SUBNET_ID \
    --map-public-ip-on-launch

3. Récupérer les zones de disponibilité :

Pour chaque sous-réseau, obtenez sa zone de disponibilité afin de garantir leur répartition correcte dans votre cluster :

az_1=$(aws ec2 describe-subnets --subnet-ids $SUBNET_ID --query 'Subnets[*].AvailabilityZone' --output text)

### 4. Associer un CIDR additionnel au VPC :

Si vous manquez de plage IP dans votre VPC, vous pouvez associer un nouveau CIDR bloc :

vpc_id=$(aws eks describe-cluster --name my-custom-networking-cluster --query "cluster.resourcesVpcConfig.vpcId" --output text)
aws ec2 associate-vpc-cidr-block --vpc-id $vpc_id --cidr-block 192.168.1.0/24

5. Vérifier l’association du CIDR :

Assurez-vous que le CIDR est bien associé :

aws ec2 describe-vpcs --vpc-ids $vpc_id --query 'Vpcs[*].CidrBlockAssociationSet[*].{CIDRBlock: CidrBlock, State: CidrBlockState.State}' --out table

Exemple de sortie

----------------------------------
|          DescribeVpcs          |
+-----------------+--------------+
|    CIDRBlock    |    State     |
+-----------------+--------------+
|  192.168.0.0/24 |  associated  |
|  192.168.1.0/24 |  associated  |
+-----------------+--------------+

6. Créer des sous-réseaux dans le nouveau CIDR :

Ces sous-réseaux doivent être dans les mêmes zones de disponibilité que les sous-réseaux des nœuds, pour une distribution cohérente :

new_subnet_id_1=$(aws ec2 create-subnet --vpc-id $vpc_id --availability-zone $az_1 --cidr-block 192.168.1.0/27 \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-custom-pod-networking-vpc-PrivateSubnet01},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
    --query Subnet.SubnetId --output text)
new_subnet_id_2=$(aws ec2 create-subnet --vpc-id $vpc_id --availability-zone $az_2 --cidr-block 192.168.1.32/27 \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-custom-pod-networking-vpc-PrivateSubnet02},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
    --query Subnet.SubnetId --output text)

7. Vérifier les sous-réseaux présents dans votre VPC :

aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" \
    --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \
    --output table

## Étape 2 : Activer le réseau personnalisé sur EKS

La configuration du vpc faite, nous pouvons configurer le cluster eks. Par défaut le daemonset du plugin amazon vpc cni assigne aux pods les IPs et mes groupes de sécurité de l’interface primaire des noeuds. La variable d’environnement AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG permet de modifier ce comportement en la définissant à true afin que l’ipamd utilise des groupes de sécurité et des sous-réseaux provenant de l’allocation d’interface réseau élastique d’un ENIConfig. Cette dernière est une ressource custom qui est déployé par sous-réseau spécifique pour les pods.

1. Activer le réseau personnalisé

Activez la variable AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG pour permettre l’utilisation des sous-réseaux personnalisés :

kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

2. Récupérer les groupes de sécurité du cluster

Obtenez l’ID du groupe de sécurité de votre cluster ou celui que vous souhaitez appliquer aux pods :

cluster_security_group_id=$(aws eks describe-cluster --name $cluster_name --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)

3. Créer des ENIConfig pour chaque sous-réseau

Créez un fichier ENIConfig pour chaque sous-réseau utilisé par les pods. Utilisez les sous-réseaux précédemment configurés :

cat >$az_1.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: $az_1
spec:
  securityGroups:
    - $cluster_security_group_id
  subnet: $new_subnet_id_1
EOF
cat >$az_2.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: $az_2
spec:
  securityGroups:
    - $cluster_security_group_id
  subnet: $new_subnet_id_2
EOF

Appliquez ces configurations dans le cluster :

kubectl apply -f $az_1.yaml
kubectl apply -f $az_2.yaml
kubectl get ENIConfigs

4. Vérifier la configuration de l’annotation ENI

Vérifiez si la variable ENI_CONFIG_ANNOTATION_DEF est déjà définie :

kubectl describe daemonset aws-node -n kube-system | grep ENI_CONFIG_ANNOTATION_DEF
  • Si elle n’est pas définie, configurez-la :
kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

Note : Les annotations k8s.amazonaws.com/eniConfig ont priorité sur les labels. Vérifiez qu’aucune annotation contradictoire n’est présente sur vos nœuds.

Si aucune valeur n’est définie, cela signifie que l’annotation ENI_CONFIG_ANNOTATION_DEF n’existe pas encore. Cette variable est essentielle, car elle détermine la clé du label des nœuds utilisée pour sélectionner l’ENIConfig. Elle doit être configurée lorsque la variable AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true.

La valeur de cette variable spécifie le nom de l’ENIConfig à appliquer sur un nœud donné. Avant de la définir, assurez-vous que l’annotation k8s.amazonaws.com/eniConfig n’est pas déjà présente sur vos nœuds. Note : Les annotations ont toujours priorité sur les labels dans ce contexte.

Pour garantir une sélection automatique de la ressource ENIConfig en fonction des zones de disponibilité, configurez la variable avec la commande suivante :

kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

6. Recréer les groupes de nœuds (si nécessaire)

Pour que les nouveaux nœuds utilisent le réseau personnalisé, recréez les groupes de nœuds avec le label k8s.amazonaws.com/eniConfig. Si vos ENIConfig ont des noms personnalisés, vous pouvez annoter les nœuds directement :

kubectl annotate node ip-192-168-0-126.us-west-2.compute.internal k8s.amazonaws.com/eniConfig=EniConfigName1

7. Vérifier les IPs des pods

Assurez-vous que les pods utilisent les IPs des sous-réseaux secondaires configurés :

kubectl get pods -A -o wide

Exemple de sortie :

NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE     IP              NODE                                          NOMINATED NODE   READINESS GATES
kube-system   aws-node-twpz2             2/2     Running   0          5m19s   192.168.0.113   ip-192-168-0-113.eu-west-1.compute.internal   <none>           <none>
kube-system   aws-node-z8cg4             2/2     Running   0          5m21s   192.168.0.91    ip-192-168-0-91.eu-west-1.compute.internal    <none>           <none>
kube-system   coredns-844dbb9f6f-r6b4p   1/1     Running   0          72m     192.168.1.29    ip-192-168-0-91.eu-west-1.compute.internal    <none>           <none>
kube-system   coredns-844dbb9f6f-r7vj8   1/1     Running   0          72m     192.168.1.30    ip-192-168-0-91.eu-west-1.compute.internal    <none>           <none>
kube-system   kube-proxy-ggxlb           1/1     Running   0          5m21s   192.168.0.91    ip-192-168-0-91.eu-west-1.compute.internal    <none>           <none>
kube-system   kube-proxy-ps4vc           1/1     Running   0          5m19s   192.168.0.113   ip-192-168-0-113.eu-west-1.compute.internal   <none>           <none>

Dans cet exemple, les pods utilisent des IPs du bloc CIDR 192.168.1.0.

Bien joué ! Votre cluster est maintenant configuré pour utiliser un réseau personnalisé. 🚀