Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Scopul acestui articol este de a introduce cititorul în conceptele de bază ale interacțiunii de rețea și gestionării politicilor de rețea în Kubernetes, precum și într-un plugin extern Calico, care extinde capacitățile standard. De asemenea, vor fi demonstrate comoditatea configurării și câteva caracteristici prin exemple reale din experiența noastră de exploatare.

O introducere rapidă în dispozitivul de rețea Kubernetes

Un cluster Kubernetes nu poate fi imaginat fără rețea. Am publicat deja materiale despre elementele de bază: „Un ghid ilustrat despre structura rețelei în Kubernetes” și „Introducere în politicile de rețea Kubernetes pentru specialiștii în securitate».

În contextul acestui articol, este important de menționat că conectivitatea de rețea între containere și noduri nu este asigurată de către K8s însăși: pentru aceasta sunt utilizate diverse pluginuri CNI (Container Networking Interface). Am discutat mai în detaliu despre acest concept de asemenea.

De exemplu, cel mai răspândit dintre aceste pluginuri — Flannel — asigură conectivitate completă de rețea între toate nodurile clusterului prin ridicarea unor poduri pe fiecare nod, alocându-i o subrețea. Totuși, accesibilitatea completă și nereglementată nu este întotdeauna benefică. Pentru a asigura o anumită izolare minimă în cluster, este necesară intervenția în configurarea firewall-ului. În general, aceasta este gestionată de CNI, motiv pentru care orice intervenții externe în iptables pot fi interpretate greșit sau ignorate complet.

Iar „din cutie” pentru organizarea gestionării politicilor de rețea în clusterul Kubernetes se oferă NetworkPolicy API. Acest resursă, care se extinde asupra spațiilor de nume alese, poate conține reguli pentru restricționarea accesului între diferite aplicații. De asemenea, permite configurarea accesibilității între anumite pod-uri, medii (spații de nume) sau blocuri de adrese IP:

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

Acest exemplu nu este cel mai simplu dintre documentația oficială poate că o dată pentru totdeauna va stârpi dorința de a înțelege logica funcționării politicilor de rețea. Totuși, vom încerca să înțelegem principiile de bază și metodele de procesare a fluxurilor de trafic prin intermediul politicilor de rețea…

Este logic că există 2 tipuri de trafic: cel care intră în pod (Ingress) și cel care iese din el (Egress).

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Așadar, aceste 2 categorii în funcție de direcția de mișcare sunt ceea ce delimitează politica.

Următoarea caracteristică obligatorie este selectorul; adică, la cine se aplică regula. Acesta poate fi un pod (sau un grup de poduri) sau un mediu (adică un spațiu de nume). Un detaliu important: ambele tipuri de aceste obiecte trebuie să conțină o etichetă (label în terminologia Kubernetes) — anume acestea sunt manipulate de politici.

Pe lângă numărul finit de selectoare, grupate printr-o etichetă, există posibilitatea de a scrie reguli precum „Permite/restricționează totul/tuturor” în diferite variații. Pentru aceasta se folosesc construcții de tip:

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

— în acest exemplu, tuturor podurilor din mediu li se va bloca traficul de intrare. O comportare opusă se poate obține prin următoarea construcție:

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

Similar pentru ieșire:

  podSelector: {}
  policyTypes:
  - Egress

— pentru a-l dezactiva. Și iată ce este necesar pentru a-l activa:

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

Revenind la alegerea pluginului CNI pentru cluster, merită menționat că nu fiecare plugin de rețea suportă funcționarea cu NetworkPolicy. De exemplu, Flannel, menționat anterior, nu poate configura politicile de rețea, despre care se spune clar în depozitul oficial. Acolo este menționată și o alternativă — proiectul Open Source Calico, care extinde semnificativ setul standard de API-uri Kubernetes în ceea ce privește politicile de rețea.

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Să ne familiarizăm cu Calico: teorie

Pluginul Calico poate fi utilizat în integrare cu Flannel (subproiectul Canal) sau singur, acoperind atât funcțiile de asigurare a conectivității de rețea, cât și capacitățile de gestionare a accesibilității.

Ce oportunități oferă utilizarea soluției „din cutie” K8s și setul de API-uri din Calico?

Iată ce este încorporat în NetworkPolicy:

  • politicile sunt limitate de mediu;
  • politicile se aplică podurilor etichetate;
  • regulile pot fi aplicate podurilor, mediilor sau subrețelelor;
  • regulile pot conține protocoale, indicații numite sau simbolice ale porturilor.

Iată cum Calico extinde aceste funcții:

  • Politicile pot fi aplicate oricărui obiect: pod, container, mașină virtuală sau interfață;
  • Regulile pot conține o acțiune specifică (interzicere, permisiune, logare);
  • Ca destinație sau sursă a regulilor poate fi un port, un interval de porturi, protocoale, atribute HTTP sau ICMP, IP sau subrețea (versiunea 4 sau 6), orice selecții (noduri, gazde, medii);
  • În plus, traficul poate fi reglementat prin setări DNAT și politici de tunelare a traficului.

Primele angajamente pe GitHub în depozitul Calico datează din iulie 2016, iar deja după un an proiectul a ocupat poziții de lider în organizația de conectivitate a rețelelor Kubernetes — acest lucru este confirmat, de exemplu, de rezultatele sondajelor, realizate de The New Stack:

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Multe soluții mari gestionate cu K8s, cum ar fi Amazon EKS, Azure AKS, Google GKE și altele, au început să-l recomande pentru utilizare.

În ceea ce privește performanța, aici totul este excelent. În timpul testării produsului său, echipa de dezvoltare Calico a demonstrat rezultate astronomice, lansând peste 50000 de containere pe 500 de noduri fizice cu o viteză de creare de 20 de containere pe secundă. Nu au fost identificate probleme la scalare. Toate aceste rezultate au fost raportate încă la anunțul primei versiuni. Cercetările independente, axate pe lățimea de bandă și consumul de resurse, confirmă de asemenea performanța Calico, care este aproape la fel de competitivă cu Flannel. De exemplu:

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Proiectul se dezvoltă foarte rapid, suportând funcționarea în soluții populare de K8s gestionate, OpenShift, OpenStack, având posibilitatea de a utiliza Calico la desfășurarea unui cluster cu ajutorul kops, se întâlnesc menționări despre construirea de rețele Service Mesh (iată un exemplu de utilizare împreună cu Istio).

Practică cu Calico

În cazul general de utilizare a Kubernetes-ului vanilat, instalarea CNI se reduce la aplicarea fișierului calico.yaml, descărcat de pe site-ul oficial, prin kubectl apply -f.

De obicei, versiunea actuală a pluginului este compatibilă cu ultimele 2-3 versiuni de Kubernetes: nu se testează și nu se garantează funcționarea în versiuni mai vechi. Potrivit declarațiilor dezvoltatorilor, Calico funcționează pe nucleul Linux mai mare de 3.10 sub CentOS 7, Ubuntu 16 sau Debian 8, pe deasupra iptables sau IPVS.

Izolarea în cadrul mediului

Pentru o înțelegere generală, să luăm un caz simplu pentru a înțelege cum diferă politicile de rețea în notația Calico de cele standard și cum abordarea în formularea regulilor le face mai ușor de citit și mai flexibile în configurare:

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

În cluster sunt desfășurate 2 aplicații web: una pe Node.js și alta pe PHP, una dintre ele folosind Redis. Pentru a restricționa accesul la Redis din PHP, păstrând în același timp conectivitatea cu Node.js, este suficient să aplicăm următoarea politică:

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

Practic, am permis traficul intrat pe portul Redis din Node.js. Și nu am interzis nimic altceva în mod explicit. Odată ce apare NetworkPolicy, toate selectorii menționați în acesta încep să se izoleze, dacă nu se specifică altceva. Totodată, regulile de izolare nu se aplică altor obiecte care nu sunt acoperite de selector.

În exemplu este folosit apiVersion Kubernetes-ului „din cutie”, dar nimic nu împiedică utilizarea resursei omonime din livrarea Calico. Sintaxa este mai extinsă, așa că va fi necesar să reformulăm regula pentru cazul descris mai sus după cum urmează:

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

Construcțiile menționate mai sus pentru a permite sau interzice tot traficul prin intermediul API-ului obișnuit NetworkPolicy conțin structuri complexe care sunt greu de perceput și memorat. În cazul Calico, pentru a schimba logica de funcționare a regulii firewall-ului în opusul său, este suficient să schimbăm action: Allow pe action: Deny.

Izolarea pe medii

Acum să ne imaginăm o situație în care aplicația generează metrici de afaceri pentru a fi colectate în Prometheus și analizate ulterior prin Grafana. Exportul poate conține date sensibile, care din nou sunt disponibile pentru toată lumea în mod implicit. Să protejăm aceste date de privirile curioase:

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Prometheus este, de regulă, plasați într-un mediu de servire separat — în exemplul de față acesta va fi un namespace de următoarea formă:

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

Câmp metadata.labels nu s-a întâmplat întâmplător. Așa cum s-a menționat mai sus, namespaceSelector (la fel ca și podSelector) operează cu etichete. Prin urmare, pentru a permite colectarea metricilor din toate pod-urile pe un anumit port, va trebui să adăugați o etichetă (sau să preluați din cele existente) și apoi să aplicați o configurație de genul:

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

Iar în cazul utilizării politicilor Calico, sintaxa va fi următoarea:

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

În general, adăugând astfel de politici pentru nevoi specifice, se poate proteja de intervenții malițioase sau accidentale în funcționarea aplicațiilor din cluster.

Cea mai bună practică, conform creatorilor Calico, este abordarea „Interzice totul și deschide în mod explicit ceea ce este necesar”, enunțată în documentația oficială (aceeași abordare este adoptată și de alții — în special, în articolul menționat anterior).

Aplicarea de obiecte suplimentare Calico

Amintesc că, printr-un set extins de API-uri, Calico poate reglementa accesibilitatea nodurilor, fără a se limita doar la pod-uri. În următorul exemplu, folosind GlobalNetworkPolicy se interzice trecerea de cereri ICMP în cluster (de exemplu, ping-uri dintr-un pod către un nod, între pod-uri sau dintr-un nod pe IP-ul pod-ului):

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

În cazul prezentat mai sus, rămâne posibilitatea nodurilor cluster-ului să comunice între ele pe ICMP. Această problemă se rezolvă prin GlobalNetworkPolicy, aplicată entității 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"]

Cazul cu VPN

În cele din urmă, voi prezenta un exemplu complet de utilizare a funcțiilor Calico pentru situații de interacțiune pericentrală, când setul standard de politici nu este suficient. Pentru accesul la aplicația web de către clienți se folosește un tunel VPN, iar acest acces este strict controlat și limitat la o listă specifică de servicii permise:

Calico pentru rețea în Kubernetes: introducere și câteva experiențe

Clienții se conectează la VPN prin portul standard UDP 1194 și, la conectare, primesc rute către subrețelele clusterului pod-urilor și serviciilor. Subrețelele sunt trimise complet pentru a nu pierde serviciile în cazul repornirilor și schimbării adreselor.

Portul în configurație este standard, ceea ce impune anumite nuanțe în procesul de configurare a aplicației și în migrarea acesteia în cluster-ul Kubernetes. De exemplu, în AWS LoadBalancer, suportul pentru UDP a apărut abia la sfârșitul anului trecut, în lista limitată a regiunilor, iar NodePort nu poate fi utilizat din cauza redirecționării pe toate nodurile cluster-ului, făcând imposibilă scalarea numărului de instanțe ale serverului pentru reziliența la defecțiuni. În plus, va fi necesar să schimbăm intervalul de porturi ales implicit...

În urma examinării soluțiilor posibile, următoarea variantă a fost aleasă:

  1. Pod-urile cu VPN sunt planificate pe nod în modul hostNetwork, adică pe IP-ul efectiv.
  2. Serviciul este expus extern prin ClusterIP. Pe nod, portul este activat fizic, fiind disponibil din exterior cu mici excepții (condiția existenței unui IP real).
  3. Determinarea nodului pe care s-a pornit pod-ul depășește cadrul acestei narațiuni. Voi spune doar că se poate „legătura” serviciul de nod sau se poate scrie un mic serviciu sidecar, care va urmări adresa IP curentă a serviciului VPN și va modifica înregistrările DNS scrise la clienți — cu ce și-a închipuit fiecare.

Din perspectiva rutării, putem identifica fără echivoc clientul prin VPN după adresa sa IP, emisă de serverul VPN. Mai jos se află un exemplu primitiv de restricționare a accesului unui astfel de client la servicii, bazat pe menționatul 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]

Aici este interzisă strict conectarea la portul 6379, dar funcționarea serviciului DNS este menținută, al cărei funcționare este adesea afectată în compilarea regulilor. Pentru că, după cum am menționat mai devreme, în cazul în care apare un selector, politica de respingere implicită se aplică, cu excepția cazului în care se specifică altceva.

Concluzii

Astfel, prin intermediul API-ului extins Calico, se poate configura flexibil și schimba dinamic rutarea în cluster și în jurul acestuia. În general, utilizarea sa poate părea ca o abordare excesivă, iar implementarea unei rețele L3 cu tuneluri BGP și IP-IP pare monstruoasă într-o instalare simplă de Kubernetes într-o rețea plată... Cu toate acestea, în rest, instrumentul se dovedește a fi destul de viabil și util.

Izolarea clusterului pentru a îndeplini cerințele de securitate nu întotdeauna poate fi realizată, iar în astfel de cazuri, Calico (sau o soluție similară) vine în ajutor. Exemplele prezentate în articol (cu câteva adaptări) sunt utilizate în mai multe instalări ale clienților noștri în AWS.

P.S.

Citiți și în blogul nostru:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster