Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Formålet med artikkelen er å introdusere leseren til det grunnleggende om nettverk og administrasjon av nettverkspolicyer i Kubernetes, samt tredjeparts Calico-plugin som utvider standardfunksjoner. Underveis vil den enkle konfigurasjonen og noen funksjoner bli demonstrert ved hjelp av ekte eksempler fra vår driftserfaring.

En rask introduksjon til Kubernetes nettverksenhet

En Kubernetes-klynge kan ikke tenkes uten et nettverk. Vi har allerede publisert materiale om det grunnleggende: "En illustrert guide til nettverksbygging i Kubernetes"Og"En introduksjon til Kubernetes nettverkspolicyer for sikkerhetseksperter'.

I forbindelse med denne artikkelen er det viktig å merke seg at K8s selv ikke er ansvarlig for nettverkstilkobling mellom containere og noder: for dette, div. CNI-plugins (Container Networking Interface). Mer om dette konseptet vi de fortalte meg også.

For eksempel er den vanligste av disse pluginene Flanell — gir full nettverkstilkobling mellom alle klyngenoder ved å heve broer på hver node, og tilordne et subnett til den. Fullstendig og uregulert tilgjengelighet er imidlertid ikke alltid fordelaktig. For å gi en slags minimal isolasjon i klyngen, er det nødvendig å gripe inn i konfigurasjonen av brannmuren. I det generelle tilfellet er det plassert under kontroll av den samme CNI, som er grunnen til at eventuelle tredjepartsintervensjoner i iptables kan tolkes feil eller ignoreres helt.

Og "ut av boksen" for organisering av nettverkspolicyadministrasjon i en Kubernetes-klynge er gitt NetworkPolicy API. Denne ressursen, fordelt over utvalgte navneområder, kan inneholde regler for å skille tilgang fra en applikasjon til en annen. Den lar deg også konfigurere tilgjengelighet mellom spesifikke poder, miljøer (navneområder) eller blokker med IP-adresser:

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

Dette er ikke det mest primitive eksemplet på offisiell dokumentasjon kan en gang for alle motvirke ønsket om å forstå logikken i hvordan nettverkspolitikk fungerer. Imidlertid vil vi fortsatt prøve å forstå de grunnleggende prinsippene og metodene for å behandle trafikkstrømmer ved å bruke nettverkspolicyer...

Det er logisk at det er 2 typer trafikk: inn i poden (Ingress) og utgående fra den (Egress).

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Egentlig er politikk delt inn i disse 2 kategoriene basert på bevegelsesretningen.

Det neste nødvendige attributtet er en velger; den som regelen gjelder for. Dette kan være en pod (eller en gruppe med pods) eller et miljø (dvs. et navneområde). En viktig detalj: begge typer av disse objektene må inneholde en etikett (etikett i Kubernetes terminologi) - det er disse politikerne opererer med.

I tillegg til et begrenset antall velgere forent av en slags etikett, er det mulig å skrive regler som "Tillat/nekt alt/alle" i forskjellige varianter. For dette formål brukes konstruksjoner av skjemaet:

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

— i dette eksemplet er alle pods i miljøet blokkert fra innkommende trafikk. Den motsatte oppførselen kan oppnås med følgende konstruksjon:

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

Tilsvarende for utgående:

  podSelector: {}
  policyTypes:
  - Egress

- for å slå den av. Og her er hva du skal inkludere:

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

Tilbake til valget av en CNI-plugin for en klynge, er det verdt å merke seg det ikke alle nettverksplugin støtter NetworkPolicy. For eksempel, den allerede nevnte Flannel vet ikke hvordan man konfigurerer nettverkspolicyer, som det sies direkte i det offisielle depotet. Et alternativ er også nevnt der - et Open Source-prosjekt Calico, som betydelig utvider standardsettet med Kubernetes APIer når det gjelder nettverkspolicyer.

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Bli kjent med Calico: teori

Calico-pluginen kan brukes i integrasjon med Flannel (delprosjekt kanal) eller uavhengig, som dekker både nettverkstilkobling og tilgjengelighetsadministrasjonsfunksjoner.

Hvilke muligheter gir bruk av K8s "boksede" løsning og API-settet fra Calico?

Her er hva som er innebygd i NetworkPolicy:

  • politikere begrenses av miljøet;
  • retningslinjer brukes på pods merket med etiketter;
  • regler kan brukes på pods, miljøer eller undernett;
  • regler kan inneholde protokoller, navngitte eller symbolske portspesifikasjoner.

Slik utvider Calico disse funksjonene:

  • policyer kan brukes på alle objekter: pod, container, virtuell maskin eller grensesnitt;
  • regler kan inneholde en spesifikk handling (forbud, tillatelse, logging);
  • målet eller kilden til regler kan være en port, en rekke porter, protokoller, HTTP- eller ICMP-attributter, IP eller subnett (4. eller 6. generasjon), alle velgere (noder, verter, miljøer);
  • I tillegg kan du regulere passasje av trafikk ved hjelp av DNAT-innstillinger og retningslinjer for videresending av trafikk.

De første forpliktelsene på GitHub i Calico-depotet dateres tilbake til juli 2016, og et år senere tok prosjektet en ledende posisjon i organisering av Kubernetes-nettverkstilkobling - dette er for eksempel bevist av undersøkelsesresultatene, dirigert av The New Stack:

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Mange store administrerte løsninger med K8s, som f.eks Amazon EX, Azure AKS, Google GKE og andre begynte å anbefale den til bruk.

Når det gjelder ytelse, er alt flott her. Ved å teste produktet deres demonstrerte utviklingsteamet fra Calico astronomisk ytelse, og kjørte mer enn 50000 500 beholdere på 20 fysiske noder med en produksjonshastighet på XNUMX beholdere per sekund. Ingen problemer ble identifisert med skalering. Slike resultater ble annonsert allerede ved kunngjøringen av den første versjonen. Uavhengige studier som fokuserer på gjennomstrømning og ressursforbruk bekrefter også at Calicos ytelse er nesten like god som Flannels. For eksempel:

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Prosjektet utvikler seg veldig raskt, det støtter arbeid i populære løsninger administrert K8s, OpenShift, OpenStack, det er mulig å bruke Calico når du distribuerer en klynge ved hjelp av sparke, det er referanser til konstruksjonen av Service Mesh-nettverk (her er et eksempel brukes sammen med Istio).

Øv med Calico

I det generelle tilfellet med å bruke vanilje Kubernetes, kommer installasjon av CNI ned til å bruke filen calico.yaml, lastet ned fra den offisielle nettsiden, ved bruk av kubectl apply -f.

Som regel er den nåværende versjonen av plugin-modulen kompatibel med de siste 2-3 versjonene av Kubernetes: drift i eldre versjoner er ikke testet og er ikke garantert. I følge utviklerne kjører Calico på Linux-kjerner over 3.10 som kjører CentOS 7, Ubuntu 16 eller Debian 8, på toppen av iptables eller IPVS.

Isolasjon i miljøet

For en generell forståelse, la oss se på en enkel sak for å forstå hvordan nettverkspolicyer i Calico-notasjonen skiller seg fra standard og hvordan tilnærmingen til å lage regler forenkler lesbarheten og konfigurasjonsfleksibiliteten:

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Det er 2 nettapplikasjoner distribuert i klyngen: i Node.js og PHP, hvorav den ene bruker Redis. For å blokkere tilgang til Redis fra PHP, mens du opprettholder tilkobling med Node.js, bruk bare følgende policy:

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

I hovedsak tillot vi innkommende trafikk til Redis-porten fra Node.js. Og de forbød tydeligvis ikke noe annet. Så snart NetworkPolicy vises, begynner alle velgerne nevnt i den å bli isolert, med mindre annet er spesifisert. Isolasjonsreglene gjelder imidlertid ikke for andre objekter som ikke dekkes av velgeren.

Eksemplet bruker apiVersion Kubernetes ut av esken, men ingenting hindrer deg i å bruke den ressurs med samme navn fra Calico-leveransen. Syntaksen der er mer detaljert, så du må omskrive regelen for tilfellet ovenfor i følgende form:

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 ovennevnte konstruksjonene for å tillate eller nekte all trafikk gjennom den vanlige NetworkPolicy API inneholder konstruksjoner med parenteser som er vanskelige å forstå og huske. Når det gjelder Calico, for å endre logikken til en brannmurregel til det motsatte, bare endre action: Allowaction: Deny.

Isolasjon etter miljø

Se for deg en situasjon der en applikasjon genererer forretningsberegninger for innsamling i Prometheus og videre analyse ved hjelp av Grafana. Opplastingen kan inneholde sensitive data, som igjen er offentlig synlig som standard. La oss skjule disse dataene fra nysgjerrige øyne:

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Prometheus er som regel plassert i et eget tjenestemiljø - i eksemplet vil det være et navneområde som dette:

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

Feltet metadata.labels dette viste seg ikke å være noen tilfeldighet. Som nevnt ovenfor, namespaceSelector (i tillegg til podSelector) opererer med etiketter. Derfor, for å tillate at beregninger tas fra alle pods på en bestemt port, må du legge til en slags etikett (eller ta fra eksisterende), og deretter bruke en konfigurasjon som:

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

Og hvis du bruker Calico-policyer, vil syntaksen være slik:

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

Generelt, ved å legge til denne typen policyer for spesifikke behov, kan du beskytte mot ondsinnet eller utilsiktet forstyrrelse i driften av applikasjoner i klyngen.

Den beste praksisen, ifølge skaperne av Calico, er "Blokker alt og åpne eksplisitt det du trenger", dokumentert i offisiell dokumentasjon (andre følger en lignende tilnærming - spesielt i allerede nevnt artikkel).

Bruke flere Calico-objekter

La meg minne deg på at gjennom det utvidede settet med Calico APIer kan du regulere tilgjengeligheten av noder, ikke begrenset til pods. I følgende eksempel bruker GlobalNetworkPolicy muligheten til å sende ICMP-forespørsler i klyngen er stengt (for eksempel pings fra en pod til en node, mellom pods eller fra en node til en IP-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

I tilfellet ovenfor er det fortsatt mulig for klyngenoder å "nå ut" til hverandre via ICMP. Og dette problemet er løst med hjelp GlobalNetworkPolicy, brukt på en enhet 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"]

VPN-saken

Til slutt vil jeg gi et veldig reelt eksempel på bruk av Calico-funksjoner for nær-klyngeinteraksjon, når et standardsett med policyer ikke er nok. For å få tilgang til nettapplikasjonen bruker klienter en VPN-tunnel, og denne tilgangen er tett kontrollert og begrenset til en spesifikk liste over tjenester som er tillatt for bruk:

Calico for nettverksbygging i Kubernetes: introduksjon og litt erfaring

Klienter kobler til VPN via standard UDP-port 1194 og, når de er tilkoblet, mottar de ruter til klyngeundernettene til pods og tjenester. Hele undernett blir presset for ikke å miste tjenester under omstart og adresseendringer.

Porten i konfigurasjonen er standard, noe som legger noen nyanser på prosessen med å konfigurere applikasjonen og overføre den til Kubernetes-klyngen. For eksempel, i samme AWS dukket LoadBalancer for UDP bokstavelig talt opp på slutten av fjoråret i en begrenset liste over regioner, og NodePort kan ikke brukes på grunn av videresending på alle klyngenoder og det er umulig å skalere antall serverforekomster for feiltoleranseformål. I tillegg må du endre standardområdet for porter...

Som et resultat av å søke gjennom mulige løsninger, ble følgende valgt:

  1. Poder med VPN er planlagt per node inn hostNetwork, det vil si til den faktiske IP-en.
  2. Tjenesten er lagt ut utenfor gjennom ClusterIP. En port er fysisk installert på noden, som er tilgjengelig fra utsiden med mindre forbehold (betinget tilstedeværelse av en ekte IP-adresse).
  3. Å bestemme noden som pod-rosen på er utenfor omfanget av historien vår. Jeg vil bare si at du kan koble tjenesten til en node eller skrive en liten sidevognstjeneste som vil overvåke den gjeldende IP-adressen til VPN-tjenesten og redigere DNS-postene som er registrert hos klienter - den som har nok fantasi.

Fra et rutingperspektiv kan vi identifisere en VPN-klient unikt ved dens IP-adresse utstedt av VPN-serveren. Nedenfor er et primitivt eksempel på å begrense en slik klients tilgang til tjenester, illustrert på ovennevnte 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]

Her er det strengt forbudt å koble til port 6379, men samtidig opprettholdes driften av DNS-tjenesten, hvis funksjon ganske ofte lider under utarbeidelse av regler. Fordi, som tidligere nevnt, når en velger vises, brukes standard avvisningspolicy på den med mindre annet er spesifisert.

Resultater av

Ved å bruke Calicos avanserte API kan du dermed fleksibelt konfigurere og dynamisk endre ruting i og rundt klyngen. Generelt kan bruken se ut som å skyte spurver med en kanon, og å implementere et L3-nettverk med BGP og IP-IP-tunneler ser monstrøs ut i en enkel Kubernetes-installasjon på et flatt nettverk... Men ellers ser verktøyet ganske levedyktig og nyttig ut. .

Det er ikke alltid mulig å isolere en klynge for å oppfylle sikkerhetskravene, og det er her Calico (eller en lignende løsning) kommer til unnsetning. Eksemplene gitt i denne artikkelen (med mindre modifikasjoner) brukes i flere installasjoner av våre klienter i AWS.

PS

Les også på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar