Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Le but de l'article est de présenter au lecteur les bases de la mise en réseau et de la gestion des politiques réseau dans Kubernetes, ainsi que le plugin tiers Calico qui étend les capacités standard. En cours de route, la facilité de configuration et certaines fonctionnalités seront démontrées à l'aide d'exemples réels tirés de notre expérience d'exploitation.

Une introduction rapide à l’appliance réseau Kubernetes

Un cluster Kubernetes ne peut être imaginé sans réseau. Nous avons déjà publié des documents sur leurs bases : «Un guide illustré de la mise en réseau dans Kubernetes"Et"Une introduction aux politiques réseau Kubernetes pour les professionnels de la sécurité».

Dans le cadre de cet article, il est important de noter que K8s lui-même n'est pas responsable de la connectivité réseau entre les conteneurs et les nœuds : pour cela, divers Plugins CNI (Interface réseau de conteneurs). Pour en savoir plus sur ce concept, nous ils m'ont aussi dit.

Par exemple, le plus courant de ces plugins est Flanelle — fournit une connectivité réseau complète entre tous les nœuds du cluster en élevant des ponts sur chaque nœud et en lui attribuant un sous-réseau. Toutefois, une accessibilité complète et non réglementée n’est pas toujours bénéfique. Pour assurer une sorte d'isolation minimale dans le cluster, il est nécessaire d'intervenir dans la configuration du pare-feu. Dans le cas général, il est placé sous le contrôle du même CNI, c'est pourquoi toute intervention de tiers dans iptables peut être mal interprétée ou totalement ignorée.

Et un « prêt à l'emploi » pour organiser la gestion des politiques de réseau dans un cluster Kubernetes est fourni API de politique de réseau. Cette ressource, répartie sur des espaces de noms sélectionnés, peut contenir des règles permettant de différencier les accès d'une application à l'autre. Il permet également de configurer l'accessibilité entre des pods, des environnements (espaces de noms) ou des blocs d'adresses IP spécifiques :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

Ce n’est pas l’exemple le plus primitif de documents officiels pourrait une fois pour toutes décourager le désir de comprendre la logique du fonctionnement des politiques de réseau. Cependant, nous essaierons toujours de comprendre les principes et méthodes de base de traitement des flux de trafic à l'aide des politiques de réseau...

Il est logique qu'il existe 2 types de trafic : entrant dans le pod (Ingress) et sortant de celui-ci (Egress).

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

En fait, la politique est divisée en ces 2 catégories en fonction de la direction du mouvement.

Le prochain attribut requis est un sélecteur ; celui à qui la règle s’applique. Il peut s'agir d'un pod (ou d'un groupe de pods) ou d'un environnement (c'est-à-dire un espace de noms). Un détail important : les deux types de ces objets doivent contenir une étiquette (étiquette dans la terminologie Kubernetes) - ce sont ceux avec lesquels les politiciens opèrent.

En plus d'un nombre fini de sélecteurs unis par une sorte d'étiquette, il est possible d'écrire des règles comme « Autoriser/refuser tout/tout le monde » dans différentes variantes. A cet effet, des constructions de la forme sont utilisées :

  podSelector: {}
  ingress: []
  policyTypes:
  - Ingress

— dans cet exemple, tous les pods de l'environnement sont bloqués du trafic entrant. Le comportement inverse peut être obtenu avec la construction suivante :

  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

De même pour les sortants :

  podSelector: {}
  policyTypes:
  - Egress

- pour l'éteindre. Et voici ce qu'il faut inclure :

  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

Revenant sur le choix d'un plugin CNI pour un cluster, il convient de noter que tous les plugins réseau ne prennent pas en charge NetworkPolicy. Par exemple, Flannel déjà mentionné ne sait pas comment configurer les politiques réseau, ce qui c'est dit directement dans le dépôt officiel. Une alternative y est également mentionnée - un projet Open Source Calicot, qui élargit considérablement l'ensemble standard des API Kubernetes en termes de politiques réseau.

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Apprendre à connaître Calico : théorie

Le plugin Calico peut être utilisé en intégration avec Flannel (sous-projet Canalaire) ou indépendamment, couvrant à la fois la connectivité réseau et les capacités de gestion de la disponibilité.

Quelles opportunités offre l’utilisation de la solution « en boîte » K8s et de l’ensemble d’API de Calico ?

Voici ce qui est intégré à NetworkPolicy :

  • les politiciens sont limités par l’environnement ;
  • les stratégies sont appliquées aux pods marqués d’étiquettes ;
  • les règles peuvent être appliquées aux pods, aux environnements ou aux sous-réseaux ;
  • les règles peuvent contenir des protocoles, des spécifications de port nommés ou symboliques.

Voici comment Calico étend ces fonctions :

  • les politiques peuvent être appliquées à n’importe quel objet : pod, conteneur, machine virtuelle ou interface ;
  • les règles peuvent contenir une action spécifique (interdiction, autorisation, journalisation) ;
  • la cible ou la source des règles peut être un port, une plage de ports, des protocoles, des attributs HTTP ou ICMP, une IP ou un sous-réseau (4e ou 6e génération), des sélecteurs quelconques (nœuds, hôtes, environnements) ;
  • De plus, vous pouvez réguler le passage du trafic à l'aide des paramètres DNAT et des politiques de transfert de trafic.

Les premiers commits sur GitHub dans le référentiel Calico remontent à juillet 2016, et un an plus tard, le projet a pris une position de leader dans l'organisation de la connectivité réseau Kubernetes - comme en témoignent, par exemple, les résultats de l'enquête, réalisé par The New Stack:

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

De nombreuses grandes solutions gérées avec K8, telles que Amazon EKS, Azure AKS, Google GKE et d'autres ont commencé à en recommander l'utilisation.

Côté performances, tout est super ici. En testant son produit, l'équipe de développement de Calico a démontré des performances astronomiques, exécutant plus de 50000 500 conteneurs sur 20 nœuds physiques avec un taux de création de XNUMX conteneurs par seconde. Aucun problème n’a été identifié concernant la mise à l’échelle. De tels résultats ont été annoncés déjà à l'annonce de la première version. Des études indépendantes axées sur le débit et la consommation des ressources confirment également que les performances de Calico sont presque aussi bonnes que celles de Flannel. Par exemple:

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Le projet se développe très rapidement, il prend en charge le travail dans les solutions populaires gérées K8s, OpenShift, OpenStack, il est possible d'utiliser Calico lors du déploiement d'un cluster utilisant puisque, il y a des références à la construction de réseaux Service Mesh (Voici un exemple utilisé conjointement avec Istio).

Entraînez-vous avec Calico

Dans le cas général d'utilisation de Kubernetes vanilla, l'installation de CNI revient à utiliser le fichier calico.yaml, téléchargé sur le site officiel, en utilisant kubectl apply -f.

En règle générale, la version actuelle du plugin est compatible avec les 2-3 dernières versions de Kubernetes : le fonctionnement dans les anciennes versions n'est pas testé et n'est pas garanti. Selon les développeurs, Calico fonctionne sur des noyaux Linux supérieurs à 3.10 exécutant CentOS 7, Ubuntu 16 ou Debian 8, en plus d'iptables ou d'IPVS.

Isolement dans l'environnement

Pour une compréhension générale, examinons un cas simple pour comprendre en quoi les politiques de réseau dans la notation Calico diffèrent des politiques standard et comment l'approche de création de règles simplifie leur lisibilité et leur flexibilité de configuration :

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Il y a 2 applications web déployées dans le cluster : en Node.js et PHP, dont une utilise Redis. Pour bloquer l'accès à Redis depuis PHP, tout en conservant la connectivité avec Node.js, appliquez simplement la stratégie suivante :

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-redis-nodejs
spec:
  podSelector:
    matchLabels:
      service: redis
  ingress:
  - from:
    - podSelector:
        matchLabels:
          service: nodejs
    ports:
    - protocol: TCP
      port: 6379

Essentiellement, nous avons autorisé le trafic entrant vers le port Redis depuis Node.js. Et ils n’ont clairement rien interdit d’autre. Dès que NetworkPolicy apparaît, tous les sélecteurs qui y sont mentionnés commencent à être isolés, sauf indication contraire. Toutefois, les règles d'isolement ne s'appliquent pas aux autres objets non couverts par le sélecteur.

L'exemple utilise apiVersion Kubernetes prêt à l'emploi, mais rien ne vous empêche de l'utiliser ressource du même nom de la livraison Calico. La syntaxe y est plus détaillée, vous devrez donc réécrire la règle pour le cas ci-dessus sous la forme suivante :

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-redis-nodejs
spec:
  selector: service == 'redis'
  ingress:
  - action: Allow
    protocol: TCP
    source:
      selector: service == 'nodejs'
    destination:
      ports:
      - 6379

Les constructions mentionnées ci-dessus pour autoriser ou refuser tout le trafic via l'API NetworkPolicy standard contiennent des constructions avec des parenthèses difficiles à comprendre et à mémoriser. Dans le cas de Calico, pour changer la logique d'une règle de pare-feu en sens inverse, il suffit de changer action: Allow sur action: Deny.

Isolement par environnement

Imaginez maintenant une situation dans laquelle une application génère des métriques commerciales à collecter dans Prometheus et à analyser plus en profondeur à l'aide de Grafana. Le téléchargement peut contenir des données sensibles, qui sont à nouveau visibles publiquement par défaut. Cachons ces données aux regards indiscrets :

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Prometheus, en règle générale, est placé dans un environnement de service distinct - dans l'exemple, il s'agira d'un espace de noms comme celui-ci :

apiVersion: v1
kind: Namespace
metadata:
  labels:
    module: prometheus
  name: kube-prometheus

Champ metadata.labels cela s’est avéré n’être pas un hasard. Comme mentionné ci-dessus, namespaceSelector (aimer podSelector) fonctionne avec des étiquettes. Par conséquent, pour permettre aux métriques d'être extraites de tous les pods sur un port spécifique, vous devrez ajouter une sorte d'étiquette (ou en extraire des étiquettes existantes), puis appliquer une configuration telle que :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-metrics-prom
spec:
  podSelector: {}
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          module: prometheus
    ports:
    - protocol: TCP
      port: 9100

Et si vous utilisez les politiques Calico, la syntaxe sera la suivante :

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-metrics-prom
spec:
  ingress:
  - action: Allow
    protocol: TCP
    source:
      namespaceSelector: module == 'prometheus'
    destination:
      ports:
      - 9100

En général, en ajoutant ce type de politiques pour des besoins spécifiques, vous pouvez vous protéger contre les interférences malveillantes ou accidentelles dans le fonctionnement des applications du cluster.

La meilleure pratique, selon les créateurs de Calico, est l'approche « Bloquez tout et ouvrez explicitement ce dont vous avez besoin », documentée dans documents officiels (d'autres suivent une approche similaire - notamment dans article déjà mentionné).

Utilisation d'objets Calico supplémentaires

Permettez-moi de vous rappeler que grâce à l'ensemble étendu d'API Calico, vous pouvez réguler la disponibilité des nœuds, sans se limiter aux pods. Dans l'exemple suivant, en utilisant GlobalNetworkPolicy la possibilité de transmettre des requêtes ICMP dans le cluster est fermée (par exemple, les pings d'un pod vers un nœud, entre pods ou d'un nœud vers un pod IP) :

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: block-icmp
spec:
  order: 200
  selector: all()
  types:
  - Ingress
  - Egress
  ingress:
  - action: Deny
    protocol: ICMP
  egress:
  - action: Deny
    protocol: ICMP

Dans le cas ci-dessus, il est toujours possible pour les nœuds du cluster de « se contacter » via ICMP. Et ce problème est résolu au moyen GlobalNetworkPolicy, appliqué à une entité HostEndpoint:

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: deny-icmp-kube-02
spec:
  selector: "role == 'k8s-node'"
  order: 0
  ingress:
  - action: Allow
    protocol: ICMP
  egress:
  - action: Allow
    protocol: ICMP
---
apiVersion: crd.projectcalico.org/v1
kind: HostEndpoint
metadata:
  name: kube-02-eth0
  labels:
    role: k8s-node
spec:
  interfaceName: eth0
  node: kube-02
  expectedIPs: ["192.168.2.2"]

Le cas du VPN

Enfin, je donnerai un exemple très réel d'utilisation des fonctions Calico dans le cas d'une interaction quasi-cluster, lorsqu'un ensemble standard de politiques ne suffit pas. Pour accéder à l'application Web, les clients utilisent un tunnel VPN, et cet accès est étroitement contrôlé et limité à une liste spécifique de services dont l'utilisation est autorisée :

Calico pour le réseautage dans Kubernetes : introduction et un peu d'expérience

Les clients se connectent au VPN via le port UDP standard 1194 et, une fois connectés, reçoivent des routes vers les sous-réseaux de cluster de pods et de services. Des sous-réseaux entiers sont poussés afin de ne pas perdre de services lors des redémarrages et des changements d'adresse.

Le port dans la configuration est standard, ce qui impose quelques nuances sur le processus de configuration de l'application et de son transfert vers le cluster Kubernetes. Par exemple, dans le même AWS LoadBalancer pour UDP est apparu littéralement à la fin de l'année dernière dans une liste limitée de régions, et NodePort ne peut pas être utilisé en raison de son transfert sur tous les nœuds du cluster et il est impossible de faire évoluer le nombre d'instances de serveur pour à des fins de tolérance aux pannes. De plus, vous devrez modifier la plage de ports par défaut...

Suite à la recherche de solutions possibles, les solutions suivantes ont été retenues :

  1. Les pods avec VPN sont planifiés par nœud dans hostNetwork, c'est-à-dire à l'adresse IP réelle.
  2. Le service est affiché à l'extérieur via ClusterIP. Un port est physiquement installé sur le nœud, accessible de l'extérieur avec de légères réserves (présence conditionnelle d'une véritable adresse IP).
  3. Déterminer le nœud sur lequel la gousse a poussé dépasse le cadre de notre histoire. Je dirai simplement que vous pouvez « clouer » étroitement le service sur un nœud ou écrire un petit service side-car qui surveillera l'adresse IP actuelle du service VPN et modifiera les enregistrements DNS enregistrés auprès des clients - celui qui a assez d'imagination.

Du point de vue du routage, nous pouvons identifier de manière unique un client VPN par son adresse IP émise par le serveur VPN. Vous trouverez ci-dessous un exemple primitif de restriction de l'accès d'un tel client aux services, illustré sur le Redis mentionné ci-dessus :

apiVersion: crd.projectcalico.org/v1
kind: HostEndpoint
metadata:
  name: vpnclient-eth0
  labels:
    role: vpnclient
    environment: production
spec:
  interfaceName: "*"
  node: kube-02
  expectedIPs: ["172.176.176.2"]
---
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: vpn-rules
spec:
  selector: "role == 'vpnclient'"
  order: 0
  applyOnForward: true
  preDNAT: true
  ingress:
  - action: Deny
    protocol: TCP
    destination:
      ports: [6379]
  - action: Allow
    protocol: UDP
    destination:
      ports: [53, 67]

Ici, la connexion au port 6379 est strictement interdite, mais en même temps le fonctionnement du service DNS est préservé, dont le fonctionnement souffre bien souvent lors de l'élaboration des règles. Parce que, comme mentionné précédemment, lorsqu'un sélecteur apparaît, la politique de refus par défaut lui est appliquée, sauf indication contraire.

Les résultats de

Ainsi, grâce à l'API avancée de Calico, vous pouvez configurer de manière flexible et modifier dynamiquement le routage dans et autour du cluster. En général, son utilisation peut ressembler à tirer des moineaux avec un canon, et implémenter un réseau L3 avec des tunnels BGP et IP-IP semble monstrueux dans une simple installation Kubernetes sur un réseau plat... Cependant, sinon l'outil semble tout à fait viable et utile .

Il n'est pas toujours possible d'isoler un cluster pour répondre aux exigences de sécurité, et c'est là que Calico (ou une solution similaire) vient à la rescousse. Les exemples donnés dans cet article (avec des modifications mineures) sont utilisés dans plusieurs installations de nos clients dans AWS.

PS

A lire aussi sur notre blog :

Source: habr.com

Ajouter un commentaire