Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Het doel van dit artikel is om de lezer kennis te laten maken met de basisprincipes van netwerksamenwerking en het beheer van netwerkpolicies in Kubernetes, evenals met de externe plugin Calico die de standaardmogelijkheden uitbreidt. Ondertussen worden de gebruiksvriendelijkheid van de configuratie en enkele functies geïllustreerd aan de hand van echte voorbeelden uit onze ervaring.

Snelle inleiding tot het netwerkapparaat van Kubernetes

Een Kubernetes-cluster is niet voor te stellen zonder netwerk. We hebben al eerder materiaal gepubliceerd over de basisprincipes daarvan: “Geïllustreerde handleiding voor het netwerk in Kubernetes» en «Inleiding tot netwerkkaders van Kubernetes voor beveiligingsspecialisten.».

In de context van dit artikel is het belangrijk om op te merken dat niet Kubernetes zelf verantwoordelijk is voor de netwerkverbinding tussen containers en nodes: hiervoor worden verschillende CNI-plugins (Container Networking Interface) gebruikt. Meer over dit concept hebben we ook verteld.

Bijvoorbeeld, de meest voorkomende van dergelijke plugins — Flannel — zorgt voor volledige netwerkconnectiviteit tussen alle nodes van het cluster door bruggen op elke node op te zetten en een subnet aan elke node toe te wijzen. Echter, volledige en ongereguleerde beschikbaarheid is niet altijd nuttig. Om enige minimale isolatie in het cluster te waarborgen, is het noodzakelijk om in de firewall-configuratie te interveniëren. In het algemeen is dit beheer toegewezen aan de betreffende CNI, waardoor elke externe inmenging in iptables verkeerd geïnterpreteerd of volledig genegeerd kan worden.

En ‘uit de doos’ wordt voor het beheer van netwerkpolicies in het Kubernetes-cluster de NetworkPolicy APIgeleverd. Deze bron, die zich uitstrekt over geselecteerde naamruimtes, kan regels bevatten voor het scheiden van toegang tussen de ene applicatie en de andere. Het maakt ook het configureren van de beschikbaarheid tussen specifieke pods, omgevingen (naamruimtes) of IP-adresblokken mogelijk:

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

Dit is een niet zo'n primitief voorbeeld van de officiële documentatie Het kan een keer en voor altijd de wens om te begrijpen hoe netwerkinstellingen werken uitschakelen. Toch zullen we proberen de basisprincipes en methoden van verkeersverwerking met behulp van netwerkinstellingen te begrijpen...

Het is logisch dat er 2 soorten verkeer zijn: binnenkomend in de pod (Ingress) en uitgaand daarvan (Egress).

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Eigenlijk zijn deze 2 categorieën op basis van bewegingsrichting waarop de beleidsregels zijn verdeeld.

Een andere verplichte parameter is de selector; degene aan wie de regel wordt toegepast. Dit kan een pod (of een groep pods) of een omgeving zijn (dat wil zeggen, een naamruimte). Een belangrijk detail: beide soorten objecten moeten een label bevatten (label in Kubernetes-terminologie) — dat zijn de labels waarmee de beleidsregels werken.

Naast een eindig aantal selectors die door een label zijn verenigd, bestaat de mogelijkheid om regels te schrijven zoals "Sta alles toe / verbied alles aan iedereen" in verschillende variaties. Hiervoor worden constructies zoals gebruikt:

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

— in dit voorbeeld wordt alle binnenkomend verkeer voor pods in de omgeving geblokkeerd. Het tegenovergestelde gedrag kan worden bereikt met de volgende constructie:

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

Hetzelfde geldt voor uitgaand verkeer:

  podSelector: {}
  policyTypes:
  - Egress

— om het uitgaande verkeer uit te schakelen. En dit is wat je nodig hebt om het in te schakelen:

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

Terugkomend op de keuze van de CNI-plug-in voor de cluster, is het belangrijk op te merken dat niet elke netwerkinplug ondersteunt het werken met NetworkPolicy. Bijvoorbeeld, de eerder genoemde Flannel kan geen netwerkinstellingen configureren, wat duidelijk is aangegeven in de officiële repository. Daar wordt ook een alternatief genoemd — het Open Source-project Calico , dat de standaard API-set van Kubernetes aanzienlijk uitbreidt op het gebied van netwerkinstellingen.Laten we Calico leren kennen: theorie

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

De Calico-plug-in kan worden gebruikt in integratie met Flannel (een subproject

Canal ) of zelfstandig, en dekt zowel netwerkconnectiviteitsfuncties als mogelijkheden voor toegangbeheer.Welke mogelijkheden biedt het gebruik van de "out-of-the-box" K8s-oplossing en de API-set van Calico?

Dit is wat ingebouwd is in NetworkPolicy:

beleidsregels zijn beperkt tot de omgeving;

  • beleidsregels worden toegepast op pods gelabeld met labels;
  • regels kunnen worden toegepast op pods, omgevingen of subnetten;
  • regels kunnen protocollen, naam- of symbolische poortaanduidingen bevatten.
  • En zo breidt Calico deze functies uit:

Zo breidt Calico deze functies uit:

  • Beleid kan van toepassing zijn op elk object: pod, container, virtuele machine of interface;
  • Regels kunnen een specifieke actie bevatten (verbod, toestemming, logging);
  • Als doel of bron van regels kan een poort, een poortbereik, protocollen, HTTP- of ICMP-attributen, IP of subnet (versie 4 of 6), en elke selector (knoop, host, omgevingen) dienen;
  • Daarnaast kan het verkeer worden beheerd door middel van DNAT-instellingen en verkeersdoorstuurbeleid.

De eerste commits op GitHub in de Calico-repository dateren van juli 2016, en al een jaar later had het project een leidende positie in de netwerkinfrastructuur van Kubernetes – dit blijkt onder andere uit de resultaten van een enquête, uitgevoerd door The New Stack:

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Veel grote beheerde oplossingen met K8s, zoals Amazon EKS, Azure AKS, Google GKE en anderen, zijn begonnen het gebruik ervan aan te bevelen.

Wat de prestaties betreft, is alles uitstekend. Bij het testen van hun product heeft het ontwikkelteam van Calico astronomische resultaten getoond door meer dan 50.000 containers te laten draaien op 500 fysieke nodes met een snelheid van 20 containers per seconde. Er zijn geen problemen bij het schalen vastgesteld. Dergelijke resultaten werden aangekondigd al bij de lancering van de eerste versie. Onafhankelijk onderzoek gericht op doorvoer en hulpbronnenverbruik bevestigt ook de prestaties van Calico, die praktisch niet onderdoen voor Flannel. Bijvoorbeeld:

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Het project ontwikkelt zich zeer snel, ondersteunt het werken in populaire beheerde K8s-oplossingen, OpenShift, OpenStack, en biedt de mogelijkheid om Calico te gebruiken bij het opzetten van een cluster met behulp van kops, er zijn vermeldingen over het opzetten van Service Mesh-netwerken (hier is een voorbeeld van gebruik samen met Istio).

Praktijk met Calico

In het algemeen komt de installatie van CNI voor standaard Kubernetes neer op het gebruik van het bestand calico.yaml, gedownload van de officiële website, met behulp van kubectl apply -f.

Over het algemeen is de actuele versie van de plugin compatibel met de laatste 2-3 versies van Kubernetes: er wordt geen garantie gegeven voor werk in oudere versies. Volgens de ontwikkelaars werkt Calico op een Linux-kernel hoger dan 3.10 onder CentOS 7, Ubuntu 16 of Debian 8, bovenop iptables of IPVS.

Isolatie binnen de omgeving

Voor een algemeen begrip nemen we een eenvoudig geval om te begrijpen hoe netwerkbeleid in Calico-notatie verschilt van de standaard en hoe de aanpak voor het opstellen van regels de leesbaarheid en flexibiliteit van de configuratie vergemakkelijkt:

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

In het cluster zijn er 2 webapplicaties uitgerold: een op Node.js en een op PHP, waarvan er één Redis gebruikt. Om de toegang tot Redis vanuit PHP te blokkeren, terwijl de connectiviteit met Node.js behouden blijft, is het voldoende om het volgende beleid toe te passen:

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

In wezen hebben we binnenkomend verkeer op de Redis-poort vanuit Node.js toegestaan. We hebben niets anders expliciet verboden. Zodra er een NetworkPolicy is, worden alle selectors die daarin worden genoemd geïsoleerd, tenzij anders aangegeven. Deze isolatieregels zijn echter niet van toepassing op andere objecten die niet door de selector worden gedekt.

In het voorbeeld wordt gebruik gemaakt van apiVersion van Kubernetes ‘out of the box’, maar er staat niets in de weg om de naamgeving uit de levering van Calico. De syntaxis daar is uitgebreider, zodat het nodig zal zijn om de regel voor de hierboven beschreven situatie als volgt te herschrijven:

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

De hierboven genoemde constructies voor het toestaan of blokkeren van al het verkeer via de standaard NetworkPolicy API bevatten complexe constructies met haakjes die moeilijk te begrijpen en te onthouden zijn. In het geval van Calico is het voldoende om de logica van het firewall-regel om te keren door simpelweg action: Allow en een werkende opdracht krijgen. action: Deny.

Isolatie op omgevingen

Stel je nu een situatie voor waarin een applicatie zakelijke metrics genereert voor verzameling in Prometheus en verdere analyse via Grafana. De export kan gevoelige gegevens bevatten die standaard opnieuw toegankelijk zijn voor het grote publiek. Laten we deze gegevens voor nieuwsgierige blikken afsluiten:

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Prometheus wordt doorgaans in een afzonderlijke dienstomgeving geplaatst — in dit voorbeeld zal dat de volgende namespace zijn:

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

Veld metadata.labels was hier niet toevallig. Zoals eerder vermeld, namespaceSelector (net als podSelector) werkt met labels. Daarom, om statistieken van alle pods op een bepaalde poort te kunnen ophalen, moet je een of ander label toevoegen (of er een van de bestaande nemen), en dan een configuratie toepassen zoals:

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

En in het geval van het gebruik van Calico-beleidsregels zou de syntaxis als volgt zijn:

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

Over het algemeen kan het toevoegen van dergelijke beleidsregels voor specifieke behoeften voorkomen dat kwaadwillende of toevallige inmenging in de werking van applicaties in de cluster plaatsvindt.

Volgens de makers van Calico is de beste praktijk de benadering "Verbied alles en open expliciet wat nodig is", vastgelegd in de officiële documentatie (een soortgelijke benadering wordt ook gevolgd door anderen — in het bijzonder in de reeds genoemde artikel).

Het gebruik van aanvullende Calico-objecten

Ik herinner eraan dat met de uitgebreide API-set van Calico de beschikbaarheid van knooppunten kan worden gereguleerd, zonder beperkt te zijn tot pods. In het volgende voorbeeld met behulp van GlobalNetworkPolicy wordt de mogelijkheid tot het versturen van ICMP-verzoeken in de cluster afgesloten (bijvoorbeeld, pings van een pod naar een knooppunt, tussen pods of van een knooppunt naar het IP van een pod):

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

In het bovenstaande geval blijft de mogelijkheid voor cluster-knooppunten om met elkaar te communiceren via ICMP bestaan. En dit probleem wordt opgelost door middel van GlobalNetworkPolicy, toegepast op de entiteit 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"]

Geval met VPN

Ten slotte geef ik een vrij reëel voorbeeld van het gebruik van Calico-functies voor de situatie van interactie in de buurt van de cluster, wanneer de standaard set van beleidsregels niet voldoende is. Voor toegang tot de webapplicatie door klanten wordt een VPN-tunnel gebruikt, en deze toegang wordt strikt gecontroleerd en beperkt tot een specifieke lijst van toegestane diensten:

Calico voor netwerken in Kubernetes: introductie en enkele ervaringen

Klanten maken verbinding met de VPN via de standaard UDP-poort 1194 en ontvangen bij verbinding routes naar de cluster-subnetten van de pods en services. De subnetten worden in hun geheel gepusht om services niet te verliezen tijdens herstarts en adreswijzigingen.

De poort in de configuratie is standaard, wat enkele nuances toevoegt aan het configureren van de applicatie en het verplaatsen ervan naar het Kubernetes-cluster. In AWS LoadBalancer kwam de ondersteuning voor UDP bijvoorbeeld pas aan het einde van vorig jaar beschikbaar, in een beperkt aantal regio's, en NodePort kan niet worden gebruikt vanwege het doorsturen naar alle knooppunten van het cluster, waardoor het aantal serverinstantie's voor hoge beschikbaarheid niet kan worden opgeschaald. Bovendien moet het standaard poortbereik worden gewijzigd...

Na een beoordeling van mogelijke oplossingen is het volgende gekozen:

  1. Pods met VPN worden gepland op de node in de modus hostNetwork, dat wil zeggen, op het feitelijke IP.
  2. De service wordt naar buiten gepusht via ClusterIP. Op de node wordt fysiek een poort geopend die extern beschikbaar is met enkele kanttekeningen (voorwaardelijk aanwezig van een werkelijk IP-adres).
  3. De identificatie van de node waarop de pod is opgestart ligt buiten het bereik van ons verhaal. Ik zeg alleen dat je de service stevig aan een node kunt 'vastplakken' of een kleine sidecar-service kunt schrijven die het huidige IP-adres van de VPN-service bijhoudt en de DNS-records aanpast die bij klanten zijn ingesteld — wat je verbeelding ook maar kan opbrengen.

Vanuit het perspectief van routering kunnen we de klant achter de VPN zonder twijfel identificeren aan de hand van het IP-adres dat door de VPN-server wordt uitgegeven. Hieronder staat een eenvoudig voorbeeld van hoe de toegang voor zo'n klant tot services kan worden beperkt, geïllustreerd aan de hand van de eerder genoemde Redis:

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]

Hier wordt de verbinding op poort 6379 strikt verboden, maar blijft de werking van de DNS-service behouden, waarvan de functionaliteit vaak wordt aangetast bij het opstellen van regels. Want, zoals eerder vermeld, wordt bij het verschijnen van een selector het standaard verbod van toepassing als er geen andere voorwaarden zijn aangegeven.

Conclusies

Met de uitgebreide API van Calico kan de routering in het cluster flexibel worden geconfigureerd en dynamisch worden gewijzigd. Over het algemeen kan het gebruik ervan voelen als een kanon op een spreeuw, terwijl het implementeren van een L3-netwerk met BGP- en IP-IP-tunnels enorm lijkt in een eenvoudige Kubernetes-installatie in een vlak netwerk... Maar verder lijkt de tool heel levensvatbaar en nuttig.

Het isoleren van een cluster om aan beveiligingseisen te voldoen is niet altijd haalbaar, en in dergelijke gevallen komt Calico (of een vergelijkbare oplossing) van pas. De in het artikel gegeven voorbeelden (met een kleine aanpassing) worden in verschillende installaties van onze klanten in AWS gebruikt.

P.S.

Lees ook op onze blog:

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster