
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ă: „” și „».
Î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 exemplu, cel mai răspândit dintre aceste pluginuri — — 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ă . 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: 5978Acest exemplu nu este cel mai simplu dintre 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).

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:
- IngressSimilar pentru ieșire:
podSelector: {}
policyTypes:
- Egress— pentru a-l dezactiva. Și iată ce este necesar pentru a-l activa:
podSelector: {}
egress:
- {}
policyTypes:
- EgressRevenind 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 în depozitul oficial. Acolo este menționată și o alternativă — proiectul Open Source , care extinde semnificativ setul standard de API-uri Kubernetes în ceea ce privește politicile de rețea.

Să ne familiarizăm cu Calico: teorie
Pluginul Calico poate fi utilizat în integrare cu Flannel (subproiectul ) 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, :

Multe soluții mari gestionate cu K8s, cum ar fi , , ș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 î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. :

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 , se întâlnesc menționări despre construirea de rețele Service Mesh ( 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, , 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:

Î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: 6379Practic, 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 . 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:

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: 9100Iar î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 (aceeași abordare este adoptată și de alții — în special, în ).
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:

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ă:
- Pod-urile cu VPN sunt planificate pe nod în modul
hostNetwork, adică pe IP-ul efectiv. - 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). - 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:
- «»;
- „Ghidul ilustrat pentru configurarea rețelei în Kubernetes”: , ;
- «».
Sursa: habr.com
