Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Celem artykułu jest zapoznanie czytelnika z podstawami sieciowania i zarządzania politykami sieciowymi w Kubernetesie, a także z wtyczką Calico firmy trzeciej rozszerzającą standardowe możliwości. Po drodze łatwość konfiguracji i niektóre funkcje zostaną zademonstrowane na rzeczywistych przykładach z naszego doświadczenia operacyjnego.

Szybkie wprowadzenie do urządzenia sieciowego Kubernetes

Nie można sobie wyobrazić klastra Kubernetes bez sieci. O ich podstawach publikowaliśmy już materiały: „Ilustrowany przewodnik po sieci w Kubernetesie"A"Wprowadzenie do zasad sieci Kubernetes dla specjalistów ds. bezpieczeństwa".

W kontekście tego artykułu należy zauważyć, że sam K8s nie jest odpowiedzialny za łączność sieciową między kontenerami i węzłami: w tym celu różne wtyczki CNI (Interfejs sieciowy kontenera). Więcej o tej koncepcji my mi też powiedzieli.

Na przykład najpopularniejszą z tych wtyczek jest Flanela — zapewnia pełną łączność sieciową pomiędzy wszystkimi węzłami klastra poprzez podnoszenie mostów na każdym węźle, przypisując do niego podsieć. Jednak pełna i nieuregulowana dostępność nie zawsze jest korzystna. Aby zapewnić jakąś minimalną izolację w klastrze, konieczna jest ingerencja w konfigurację firewalla. W ogólnym przypadku znajduje się on pod kontrolą tego samego CNI, dlatego wszelkie interwencje stron trzecich w iptables mogą zostać błędnie zinterpretowane lub całkowicie zignorowane.

Dostępna jest także gotowa do użycia konfiguracja zarządzania polityką sieciową w klastrze Kubernetes Interfejs API zasad sieciowych. Zasób ten, rozproszony w wybranych przestrzeniach nazw, może zawierać reguły różnicujące dostęp z jednej aplikacji do drugiej. Umożliwia także skonfigurowanie dostępności pomiędzy określonymi podami, środowiskami (przestrzeniami nazw) lub blokami adresów 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

To nie jest najbardziej prymitywny przykład oficjalna dokumentacja może raz na zawsze zniechęcić do chęci zrozumienia logiki działania polityk sieciowych. Jednak nadal będziemy próbować zrozumieć podstawowe zasady i metody przetwarzania przepływów ruchu za pomocą polityk sieciowych...

Logiczne jest, że istnieją 2 rodzaje ruchu: wchodzący do kapsuły (Ingress) i wychodzący z niego (Egress).

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Właściwie politykę dzieli się na te 2 kategorie w zależności od kierunku ruchu.

Następnym wymaganym atrybutem jest selektor; ten, którego dotyczy reguła. Może to być pod (lub grupa podów) lub środowisko (tj. przestrzeń nazw). Ważny szczegół: oba typy tych obiektów muszą zawierać etykietę (etykieta w terminologii Kubernetesa) – to właśnie nimi operują politycy.

Oprócz skończonej liczby selektorów połączonych jakąś etykietą, możliwe jest napisanie reguł takich jak „Zezwól/zabroń wszystkim/wszystkim” w różnych odmianach. W tym celu stosuje się konstrukcje postaci:

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

— w tym przykładzie wszystkie pody w środowisku są blokowane przed ruchem przychodzącym. Odwrotne zachowanie można osiągnąć za pomocą następującej konstrukcji:

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

Podobnie dla wychodzących:

  podSelector: {}
  policyTypes:
  - Egress

- aby to wyłączyć. A oto, co należy uwzględnić:

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

Wracając do wyboru wtyczki CNI dla klastra warto o tym wspomnieć nie każda wtyczka sieciowa obsługuje NetworkPolicy. Na przykład wspomniany już Flannel nie wie, jak skonfigurować zasady sieciowe, które jest to powiedziane bezpośrednio w oficjalnym repozytorium. Wspomniana jest tam również alternatywa - projekt Open Source Perkal, co znacząco rozszerza standardowy zestaw API Kubernetesa pod kątem polityk sieciowych.

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Poznanie Calico: teoria

Wtyczkę Calico można wykorzystać w integracji z Flannel (podprojekt Kanał) lub niezależnie, obejmujące zarówno łączność sieciową, jak i możliwości zarządzania dostępnością.

Jakie możliwości daje zastosowanie „pudełkowego” rozwiązania K8 i zestawu API firmy Calico?

Oto, co jest wbudowane w NetworkPolicy:

  • politycy są ograniczeni przez środowisko;
  • zasady stosowane są do podów oznaczonych etykietami;
  • reguły można zastosować do podów, środowisk lub podsieci;
  • reguły mogą zawierać protokoły, nazwane lub symboliczne specyfikacje portów.

Oto jak Calico rozszerza te funkcje:

  • polityki można zastosować do dowolnego obiektu: poda, kontenera, maszyny wirtualnej lub interfejsu;
  • reguły mogą zawierać konkretną akcję (zakaz, zezwolenie, logowanie);
  • celem lub źródłem reguł może być port, zakres portów, protokoły, atrybuty HTTP lub ICMP, adres IP lub podsieć (4. lub 6. generacji), dowolne selektory (węzły, hosty, środowiska);
  • Dodatkowo możesz regulować przepływ ruchu za pomocą ustawień DNAT i zasad przekazywania ruchu.

Pierwsze commity na GitHubie w repozytorium Calico datowane są na lipiec 2016, a rok później projekt objął wiodącą pozycję w organizacji łączności sieciowej Kubernetes – świadczą o tym chociażby wyniki ankiety, prowadzone przez The New Stack:

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Wiele dużych rozwiązań zarządzanych z K8, takich jak Amazon EX, Azure AKS, Google GKE a inni zaczęli go polecać do użytku.

Jeśli chodzi o wydajność, tutaj wszystko jest świetne. Testując swój produkt, zespół programistów Calico wykazał astronomiczną wydajność, uruchamiając ponad 50000 500 kontenerów na 20 węzłach fizycznych z szybkością tworzenia XNUMX kontenerów na sekundę. Nie stwierdzono żadnych problemów ze skalowaniem. Takie wyniki zostały ogłoszone już w momencie ogłoszenia pierwszej wersji. Niezależne badania skupiające się na przepustowości i zużyciu zasobów również potwierdzają, że wydajność Calico jest prawie tak dobra jak Flannela. na przykład:

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Projekt rozwija się bardzo szybko, wspiera pracę w popularnych rozwiązaniach zarządzanych K8s, OpenShift, OpenStack, możliwe jest wykorzystanie Calico przy wdrażaniu klastra przy użyciu kopspojawiają się odniesienia do budowy sieci Service Mesh (Oto przykład używany w połączeniu z Istio).

Ćwicz z Calico

W ogólnym przypadku korzystania z waniliowego Kubernetesa instalacja CNI sprowadza się do użycia pliku calico.yaml, pobrany z oficjalnej strony internetowej, używając kubectl apply -f.

Z reguły aktualna wersja wtyczki jest kompatybilna z najnowszymi 2-3 wersjami Kubernetesa: działanie w starszych wersjach nie jest testowane i nie jest gwarantowane. Według twórców Calico działa na jądrach Linuksa w wersji powyżej 3.10 z systemem CentOS 7, Ubuntu 16 lub Debian 8, a także iptables lub IPVS.

Izolacja w środowisku

Dla ogólnego zrozumienia spójrzmy na prosty przypadek, aby zrozumieć, czym polityki sieciowe w notacji Calico różnią się od standardowych i jak podejście do tworzenia reguł upraszcza ich czytelność i elastyczność konfiguracji:

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

W klastrze wdrożone są 2 aplikacje internetowe: w Node.js i PHP, z czego jedna korzysta z Redis. Aby zablokować dostęp do Redis z poziomu PHP, zachowując jednocześnie łączność z Node.js, wystarczy zastosować następującą politykę:

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

Zasadniczo zezwoliliśmy na ruch przychodzący do portu Redis z Node.js. I najwyraźniej niczego więcej nie zakazali. Gdy tylko pojawi się NetworkPolicy, wszystkie wymienione w nim selektory zaczynają być izolowane, chyba że określono inaczej. Jednakże zasady izolacji nie dotyczą innych obiektów nieobjętych selektorem.

Przykład wykorzystuje apiVersion Kubernetes od razu po wyjęciu z pudełka, ale nic nie stoi na przeszkodzie, aby z niego skorzystać zasób o tej samej nazwie z dostawy Calico. Składnia jest tam bardziej szczegółowa, dlatego będziesz musiał przepisać regułę dla powyższego przypadku w następującej formie:

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

Wyżej wymienione konstrukcje zezwalające lub blokujące cały ruch poprzez zwykły interfejs API NetworkPolicy zawierają konstrukcje z nawiasami, które są trudne do zrozumienia i zapamiętania. W przypadku Calico, aby zmienić logikę reguły zapory ogniowej na odwrotną, wystarczy ją zmienić action: Allow na action: Deny.

Izolacja ze względu na środowisko

Teraz wyobraźmy sobie sytuację, w której aplikacja generuje metryki biznesowe do gromadzenia w Prometheusie i dalszej analizy za pomocą Grafany. Przesyłany plik może zawierać wrażliwe dane, które domyślnie są ponownie widoczne publicznie. Ukryjmy te dane przed wścibskimi oczami:

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Prometheus z reguły umieszczany jest w osobnym środowisku serwisowym – w przykładzie będzie to taka przestrzeń nazw:

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

Pole metadata.labels okazało się, że to nie przypadek. Jak wspomniano powyżej, namespaceSelector (lubić podSelector) działa z etykietami. Dlatego, aby umożliwić pobieranie metryk ze wszystkich podów na określonym porcie, będziesz musiał dodać jakiś rodzaj etykiety (lub pobrać z istniejących), a następnie zastosować konfigurację taką jak:

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

A jeśli użyjesz zasad Calico, składnia będzie następująca:

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

Generalnie dodając tego typu polityki pod konkretne potrzeby można zabezpieczyć się przed złośliwą lub przypadkową ingerencją w działanie aplikacji w klastrze.

Najlepszą praktyką, zdaniem twórców Calico, jest podejście „Zablokuj wszystko i wyraźnie otwórz to, czego potrzebujesz”, udokumentowane w oficjalna dokumentacja (inni stosują podobne podejście - w szczególności w już wspomniany artykuł).

Korzystanie z dodatkowych obiektów perkalowych

Przypomnę, że poprzez rozbudowany zestaw API Calico możesz regulować dostępność węzłów, nie ograniczając się do podów. W poniższym przykładzie za pomocą GlobalNetworkPolicy możliwość przekazywania żądań ICMP w klastrze jest zamknięta (na przykład pingi z podu do węzła, między podami lub z węzła do poda 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

W powyższym przypadku węzły klastra nadal mogą „skontaktować się” ze sobą za pośrednictwem protokołu ICMP. I ten problem został rozwiązany za pomocą środków GlobalNetworkPolicy, zastosowany do jednostki 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"]

Sprawa VPN

Na koniec podam bardzo realny przykład wykorzystania funkcji Calico w przypadku interakcji bliskiej klastra, gdy standardowy zestaw polityk nie wystarczy. Aby uzyskać dostęp do aplikacji internetowej, klienci korzystają z tunelu VPN, a dostęp ten jest ściśle kontrolowany i ograniczony do określonej listy usług, z których można korzystać:

Calico dla sieci w Kubernetesie: wprowadzenie i trochę doświadczenia

Klienci łączą się z siecią VPN za pośrednictwem standardowego portu UDP 1194 i po nawiązaniu połączenia otrzymują trasy do podsieci klastra podów i usług. Wypychane są całe podsieci, aby nie utracić usług podczas ponownego uruchamiania i zmiany adresu.

Port w konfiguracji jest standardowy, co narzuca pewne niuanse na proces konfigurowania aplikacji i przenoszenia jej do klastra Kubernetes. Przykładowo w tym samym AWS LoadBalancer dla UDP pojawił się dosłownie pod koniec ubiegłego roku w ograniczonej liście regionów, a NodePort nie może być używany ze względu na jego forwardowanie na wszystkich węzłach klastra i nie da się skalować liczby instancji serwerów dla celów odporności na błędy. Dodatkowo będziesz musiał zmienić domyślny zakres portów...

W wyniku przeszukania możliwych rozwiązań wybrano:

  1. Pody z VPN są zaplanowane na każdy węzeł hostNetwork, czyli do rzeczywistego adresu IP.
  2. Usługa jest wysyłana na zewnątrz przez ClusterIP. Na węźle fizycznie zainstalowany jest port, który jest dostępny z zewnątrz z niewielkimi zastrzeżeniami (warunek obecności prawdziwego adresu IP).
  3. Określenie węzła, na którym wyrósł strąk, wykracza poza zakres naszej historii. Powiem tylko, że można mocno „przybić” usługę do węzła lub napisać małą usługę poboczną, która będzie monitorować aktualny adres IP usługi VPN i edytować rekordy DNS zarejestrowane u klientów - kto ma dość wyobraźni.

Z punktu widzenia routingu możemy jednoznacznie zidentyfikować klienta VPN na podstawie jego adresu IP wydanego przez serwer VPN. Poniżej prymitywny przykład ograniczania takiemu klientowi dostępu do usług, zilustrowany na ww. Redisie:

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]

Tutaj łączenie się z portem 6379 jest surowo zabronione, ale jednocześnie zachowane jest działanie usługi DNS, której funkcjonowanie często cierpi przy ustalaniu reguł. Ponieważ, jak wspomniano wcześniej, gdy pojawia się selektor, stosowana jest do niego domyślna zasada odmowy, chyba że określono inaczej.

Wyniki

Dzięki temu, korzystając z zaawansowanego API Calico, możesz elastycznie konfigurować i dynamicznie zmieniać routing w klastrze i wokół niego. Generalnie jego użycie może wyglądać jak strzelanie z armaty do wróbli, a implementacja sieci L3 z tunelami BGP i IP-IP wygląda monstrualnie w prostej instalacji Kubernetesa na płaskiej sieci... Jednak poza tym narzędzie wygląda całkiem realnie i użyteczno .

Wyizolowanie klastra w celu spełnienia wymagań bezpieczeństwa może nie zawsze być wykonalne i w tym przypadku na ratunek przychodzi Calico (lub podobne rozwiązanie). Przykłady podane w tym artykule (z niewielkimi modyfikacjami) zostały wykorzystane w kilku instalacjach naszych klientów w AWS.

PS

Przeczytaj także na naszym blogu:

Źródło: www.habr.com

Dodaj komentarz