Calico para networking em Kubernetes: introdução e um pouco de experiência

Calico para networking em Kubernetes: introdução e um pouco de experiência

O objetivo do artigo é apresentar ao leitor os fundamentos da rede e do gerenciamento de políticas de rede no Kubernetes, bem como o plug-in Calico de terceiros que estende os recursos padrão. Ao longo do caminho, a facilidade de configuração e alguns recursos serão demonstrados através de exemplos reais de nossa experiência operacional.

Uma rápida introdução ao dispositivo de rede Kubernetes

Um cluster Kubernetes não pode ser imaginado sem uma rede. Já publicamos materiais sobre seus fundamentos: “Um guia ilustrado para networking no Kubernetes"E"Uma introdução às políticas de rede Kubernetes para profissionais de segurança".

No contexto deste artigo, é importante notar que o próprio K8s não é responsável pela conectividade de rede entre contêineres e nós: para isso, vários Plug-ins CNI (Interface de rede de contêiner). Mais sobre esse conceito nós eles também me disseram.

Por exemplo, o mais comum desses plug-ins é Flanela — fornece conectividade de rede completa entre todos os nós do cluster, levantando pontes em cada nó e atribuindo uma sub-rede a ele. No entanto, a acessibilidade completa e não regulamentada nem sempre é benéfica. Para proporcionar algum tipo de isolamento mínimo no cluster, é necessário intervir na configuração do firewall. No caso geral, é colocado sob o controle do mesmo CNI, razão pela qual quaisquer intervenções de terceiros no iptables podem ser interpretadas incorretamente ou totalmente ignoradas.

E é fornecido "pronto para uso" para organizar o gerenciamento de políticas de rede em um cluster Kubernetes API de política de rede. Este recurso, distribuído em namespaces selecionados, pode conter regras para diferenciar o acesso de uma aplicação para outra. Também permite configurar a acessibilidade entre pods, ambientes (namespaces) ou blocos de endereços IP específicos:

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

Este não é o exemplo mais primitivo de documentação oficial pode desencorajar de uma vez por todas o desejo de compreender a lógica de como funcionam as políticas de rede. No entanto, ainda tentaremos compreender os princípios e métodos básicos de processamento de fluxos de tráfego usando políticas de rede...

É lógico que existem 2 tipos de tráfego: entrando no pod (Ingress) e saindo dele (Egress).

Calico para networking em Kubernetes: introdução e um pouco de experiência

Na verdade, a política está dividida nestas 2 categorias com base na direção do movimento.

O próximo atributo obrigatório é um seletor; aquele a quem a regra se aplica. Pode ser um pod (ou um grupo de pods) ou um ambiente (ou seja, um namespace). Um detalhe importante: ambos os tipos desses objetos devem conter uma etiqueta (rótulo na terminologia do Kubernetes) - são com eles que os políticos operam.

Além de um número finito de seletores unidos por algum tipo de rótulo, é possível escrever regras como “Permitir/negar tudo/todos” em diferentes variações. Para tanto, são utilizadas construções do formulário:

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

— neste exemplo, todos os pods no ambiente estão bloqueados para tráfego de entrada. O comportamento oposto pode ser alcançado com a seguinte construção:

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

Da mesma forma para saída:

  podSelector: {}
  policyTypes:
  - Egress

- para desligá-lo. E aqui está o que incluir:

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

Voltando à escolha de um plugin CNI para um cluster, vale ressaltar que nem todo plug-in de rede oferece suporte a NetworkPolicy. Por exemplo, o já citado Flannel não sabe configurar políticas de rede, o que é dito diretamente no repositório oficial. Uma alternativa também é mencionada lá - um projeto Open Source Chita, o que expande significativamente o conjunto padrão de APIs Kubernetes em termos de políticas de rede.

Calico para networking em Kubernetes: introdução e um pouco de experiência

Conhecendo Calico: teoria

O plugin Calico pode ser usado em integração com Flannel (subprojeto Canal) ou de forma independente, abrangendo recursos de conectividade de rede e gerenciamento de disponibilidade.

Que oportunidades o uso da solução “in a box” K8s e do conjunto de API da Calico oferece?

Aqui está o que está integrado ao NetworkPolicy:

  • os políticos são limitados pelo ambiente;
  • as políticas são aplicadas aos pods marcados com rótulos;
  • as regras podem ser aplicadas a pods, ambientes ou sub-redes;
  • as regras podem conter protocolos, especificações de portas nomeadas ou simbólicas.

Veja como o Calico estende essas funções:

  • as políticas podem ser aplicadas a qualquer objeto: pod, contêiner, máquina virtual ou interface;
  • as regras podem conter uma ação específica (proibição, permissão, registro);
  • o alvo ou origem das regras pode ser uma porta, um intervalo de portas, protocolos, atributos HTTP ou ICMP, IP ou sub-rede (4ª ou 6ª geração), quaisquer seletores (nós, hosts, ambientes);
  • Além disso, você pode regular a passagem do tráfego usando configurações de DNAT e políticas de encaminhamento de tráfego.

Os primeiros commits no GitHub no repositório Calico datam de julho de 2016 e, um ano depois, o projeto assumiu uma posição de liderança na organização da conectividade da rede Kubernetes - isso é evidenciado, por exemplo, pelos resultados da pesquisa, conduzido por The New Stack:

Calico para networking em Kubernetes: introdução e um pouco de experiência

Muitas soluções gerenciadas de grande porte com K8s, como Amazon EX, Azure AKS, Google GKE e outros começaram a recomendá-lo para uso.

Quanto ao desempenho, tudo é ótimo aqui. Ao testar seu produto, a equipe de desenvolvimento do Calico demonstrou um desempenho astronômico, executando mais de 50000 contêineres em 500 nós físicos com uma taxa de criação de 20 contêineres por segundo. Nenhum problema foi identificado com o dimensionamento. Tais resultados foram anunciados já no anúncio da primeira versão. Estudos independentes com foco no rendimento e no consumo de recursos também confirmam que o desempenho do Calico é quase tão bom quanto o do Flannel. Por exemplo:

Calico para networking em Kubernetes: introdução e um pouco de experiência

O projeto está se desenvolvendo muito rapidamente, suporta trabalho em soluções populares gerenciadas K8s, OpenShift, OpenStack, é possível usar Calico ao implantar um cluster usando chute, há referências à construção de redes Service Mesh (aqui está um exemplo usado em conjunto com o Istio).

Pratique com Calico

No caso geral de uso do Kubernetes vanilla, a instalação do CNI se resume a usar o arquivo calico.yaml, baixado do site oficial, usando kubectl apply -f.

Como regra, a versão atual do plugin é compatível com as 2-3 versões mais recentes do Kubernetes: a operação em versões mais antigas não é testada e não é garantida. Segundo os desenvolvedores, o Calico roda em kernels Linux acima de 3.10 rodando CentOS 7, Ubuntu 16 ou Debian 8, além de iptables ou IPVS.

Isolamento dentro do ambiente

Para uma compreensão geral, vejamos um caso simples para entender como as políticas de rede na notação Calico diferem das políticas padrão e como a abordagem para criar regras simplifica sua legibilidade e flexibilidade de configuração:

Calico para networking em Kubernetes: introdução e um pouco de experiência

Existem 2 aplicações web implantadas no cluster: em Node.js e PHP, uma das quais usa Redis. Para bloquear o acesso ao Redis do PHP, mantendo a conectividade com o Node.js, basta aplicar a seguinte política:

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

Essencialmente, permitimos o tráfego de entrada para a porta Redis do Node.js. E eles claramente não proibiram mais nada. Assim que NetworkPolicy aparecer, todos os seletores mencionados nele começarão a ser isolados, a menos que especificado de outra forma. Contudo, as regras de isolamento não se aplicam a outros objetos não abrangidos pelo seletor.

O exemplo usa apiVersion Kubernetes pronto para uso, mas nada impede que você o use recurso de mesmo nome da entrega Calico. A sintaxe é mais detalhada, então você precisará reescrever a regra para o caso acima na seguinte forma:

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

As construções mencionadas acima para permitir ou negar todo o tráfego por meio da API NetworkPolicy regular contêm construções com parênteses que são difíceis de entender e lembrar. No caso do Calico, para alterar a lógica de uma regra de firewall para o oposto, basta alterar action: Allow em action: Deny.

Isolamento por ambiente

Agora imagine uma situação em que uma aplicação gera métricas de negócios para coleta no Prometheus e posterior análise utilizando o Grafana. O upload pode conter dados confidenciais, que novamente podem ser visualizados publicamente por padrão. Vamos esconder esses dados de olhares indiscretos:

Calico para networking em Kubernetes: introdução e um pouco de experiência

O Prometheus, via de regra, é colocado em um ambiente de serviço separado - no exemplo será um namespace como este:

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

Campo metadata.labels isso acabou não sendo um acidente. Como acima mencionado, namespaceSelector (Como podSelector) opera com rótulos. Portanto, para permitir que as métricas sejam obtidas de todos os pods em uma porta específica, você terá que adicionar algum tipo de rótulo (ou retirar dos existentes) e então aplicar uma configuração como:

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

E se você usar políticas do Calico, a sintaxe será assim:

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

Em geral, ao adicionar esses tipos de políticas para necessidades específicas, você pode proteger contra interferências maliciosas ou acidentais na operação de aplicativos no cluster.

A melhor prática, segundo os criadores do Calico, é a abordagem “Bloqueie tudo e abra explicitamente o que você precisa”, documentada em documentação oficial (outros seguem uma abordagem semelhante - em particular, em artigo já mencionado).

Usando objetos Calico adicionais

Deixe-me lembrá-lo de que, por meio do conjunto estendido de APIs Calico, você pode regular a disponibilidade de nós, não se limitando a pods. No exemplo a seguir usando GlobalNetworkPolicy a capacidade de passar solicitações ICMP no cluster é fechada (por exemplo, pings de um pod para um nó, entre pods ou de um nó para um pod 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

No caso acima, ainda é possível que os nós do cluster “alcançem” uns aos outros via ICMP. E esse problema é resolvido por meio GlobalNetworkPolicy, aplicado a uma entidade 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"]

O caso da VPN

Por fim, darei um exemplo muito real do uso de funções Calico para o caso de interação quase cluster, quando um conjunto padrão de políticas não é suficiente. Para acessar a aplicação web, os clientes utilizam um túnel VPN, e esse acesso é rigidamente controlado e limitado a uma lista específica de serviços permitidos para uso:

Calico para networking em Kubernetes: introdução e um pouco de experiência

Os clientes se conectam à VPN por meio da porta UDP padrão 1194 e, quando conectados, recebem rotas para as sub-redes do cluster de pods e serviços. Sub-redes inteiras são enviadas para não perder serviços durante reinicializações e alterações de endereço.

A porta na configuração é padrão, o que impõe algumas nuances no processo de configuração da aplicação e transferência para o cluster Kubernetes. Por exemplo, no mesmo AWS LoadBalancer para UDP apareceu literalmente no final do ano passado em uma lista limitada de regiões, e o NodePort não pode ser usado devido ao seu encaminhamento em todos os nós do cluster e é impossível escalar o número de instâncias do servidor para propósitos de tolerância a falhas. Além disso, você terá que alterar o intervalo padrão de portas...

Como resultado da pesquisa de possíveis soluções, foi escolhido o seguinte:

  1. Os pods com VPN são programados por nó em hostNetwork, isto é, para o IP real.
  2. O serviço é divulgado externamente através ClusterIP. No nó está fisicamente instalada uma porta que pode ser acessada externamente com pequenas reservas (presença condicional de um endereço IP real).
  3. Determinar o nó no qual o pod surgiu está além do escopo da nossa história. Direi apenas que você pode conectar o serviço a um nó ou escrever um pequeno serviço secundário que monitorará o endereço IP atual do serviço VPN e editará os registros DNS registrados nos clientes - quem tiver imaginação suficiente.

Do ponto de vista do roteamento, podemos identificar exclusivamente um cliente VPN pelo seu endereço IP emitido pelo servidor VPN. Abaixo está um exemplo primitivo de restrição do acesso de tal cliente aos serviços, ilustrado no Redis mencionado acima:

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]

Aqui é estritamente proibida a ligação à porta 6379, mas ao mesmo tempo é preservado o funcionamento do serviço DNS, cujo funcionamento muitas vezes é prejudicado na elaboração das regras. Porque, como mencionado anteriormente, quando um seletor aparece, a política de negação padrão é aplicada a ele, a menos que seja especificado de outra forma.

Resultados de

Assim, usando a API avançada do Calico, você pode configurar com flexibilidade e alterar dinamicamente o roteamento dentro e ao redor do cluster. Em geral, seu uso pode parecer atirar em pardais com um canhão, e implementar uma rede L3 com túneis BGP e IP-IP parece monstruoso em uma instalação simples do Kubernetes em uma rede plana... No entanto, caso contrário, a ferramenta parece bastante viável e útil .

Isolar um cluster para atender aos requisitos de segurança nem sempre é viável, e é aí que o Calico (ou uma solução semelhante) vem em socorro. Os exemplos dados neste artigo (com pequenas modificações) são utilizados em diversas instalações de nossos clientes na AWS.

PS

Leia também em nosso blog:

Fonte: habr.com

Adicionar um comentário