Chita para redes em Kubernetes: conhecendo e um pouco de experiência



O objetivo do artigo é apresentar ao leitor os conceitos básicos de rede e gerenciamento de políticas de rede no Kubernetes, bem como um plug-in Calico de terceiros que amplia os recursos padrão. Ao longo do caminho, a conveniência da configuração e alguns recursos serão demonstrados com exemplos reais da experiência de nossa operação.

Introdução rápida 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 redes no Kubernetes ” e “ Introdução às políticas de rede Kubernetes para profissionais de segurança ”.

No contexto deste artigo, é importante observar que o K8s não é responsável pela conectividade de rede entre contêineres e nós: todos os tipos de plug-ins CNI (Container Networking Interface) são usados ​​para isso. Também conversamos mais sobre esse conceito.

Por exemplo, o mais comum desses plug-ins - Flannel - fornece conectividade de rede completa entre todos os nós do cluster levantando pontes em cada nó, protegendo uma sub-rede para ele. No entanto, a disponibilidade total e não regulamentada nem sempre é útil. Para fornecer um isolamento mínimo no cluster, é necessário intervir na configuração do firewall. No caso geral, é dado à administração da própria CNI, devido à qual qualquer intervenção de terceiros nas tabelas de ip pode ser interpretada incorretamente ou totalmente ignorada.

E pronto , a API NetworkPolicy é fornecida para organizar o gerenciamento de diretivas de rede no cluster Kubernetes. Esse recurso, que se estende aos namespaces selecionados, pode conter regras para restringir o acesso de um aplicativo para outro. 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 da documentação oficial que pode de uma vez por todas desencorajar o desejo de entender a lógica das políticas de rede. No entanto, ainda tentamos entender 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: de entrada para o pod (entrada) e de saída (saída).



Na verdade, a política é dividida nessas 2 categorias na direção do movimento.

O próximo atributo necessá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 espaço para nome). Um detalhe importante: ambos os tipos desses objetos devem conter um rótulo ( rótulo na terminologia do Kubernetes) - essas são as políticas que operam.

Além de um número finito de seletores unidos por algum rótulo, existe a possibilidade de escrever regras como "Permitir / Negar tudo / Tudo" em diferentes variações. Para isso, construções do formulário são usadas:

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

- neste exemplo, todos os pods do ambiente fecham o tráfego de entrada. O comportamento oposto pode ser alcançado por essa construção:

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

Da mesma forma para saída:

  podSelector: {} policyTypes: - Egress 

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

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

Voltando à escolha de um plug-in CNI para um cluster, é importante notar que nem todo plug-in de rede suporta o trabalho com o NetworkPolicy . Por exemplo, o já mencionado Flannel não sabe como configurar políticas de rede, conforme explicitamente declarado no repositório oficial. Uma alternativa também é mencionada lá - o projeto Calico Open Source, que estende significativamente a API Kubernetes padrão em termos de políticas de rede.



Conheça Calico: Theory


O plug-in Calico pode ser usado em integração com o Flannel (um subprojeto do Canal ) ou por conta própria, cobrindo os recursos de gerenciamento de disponibilidade e conectividade de rede.

Quais recursos a solução in a box K8s e o conjunto de API Calico fornecem?

Aqui está o que é incorporado ao NetworkPolicy:

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

E aqui está como o Calico estende esses recursos:

  • 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 destino ou a origem das regras pode ser uma porta, intervalo de portas, protocolos, atributos HTTP ou ICMP, IP ou sub-rede (4 ou 6 gerações), qualquer seletor (nós, hosts, ambientes);
  • Além disso, o fluxo de tráfego pode ser controlado usando as configurações DNAT e as políticas de encaminhamento de tráfego.

O primeiro commit do GitHub no repositório Calico data de julho de 2016 e, um ano depois, o projeto assumiu uma posição de liderança na organização da conectividade de rede Kubernetes - isso é indicado, por exemplo, pelos resultados de uma pesquisa realizada pelo The New Stack :



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

Quanto ao desempenho, tudo está ótimo aqui. Ao testar seu produto, a equipe de desenvolvimento do Calico demonstrou desempenho astronômico ao lançar mais de 50.000 contêineres em 500 nós físicos, com uma velocidade de criação de 20 contêineres por segundo. Não houve problemas com a escala. Tais resultados já foram anunciados no anúncio da primeira versão. Pesquisas independentes sobre largura de banda e consumo de recursos também confirmam o desempenho do Calico, que é quase o mesmo do Flannel. Por exemplo :



O projeto está se desenvolvendo muito rápido, ele suporta o trabalho nas soluções populares K8s gerenciadas, OpenShift, OpenStack, é possível usar o Calico ao implantar um cluster usando o kops , existem referências à criação de redes do Service Mesh (veja um exemplo de como usá-lo com o Istio).

Pratique com Calico


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

Como regra, a versão atual do plug-in é compatível com as 2-3 versões mais recentes do Kubernetes: o trabalho em versões mais antigas não é testado e não garante. Segundo os desenvolvedores, o Calico roda no kernel Linux acima de 3.10 no CentOS 7, Ubuntu 16 ou Debian 8, além de iptables ou IPVS.

Isolamento dentro do ambiente


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



Existem 2 aplicativos da web implantados no cluster: Node.js e PHP, um dos quais usa Redis. Para bloquear o acesso ao Redis a partir do PHP, deixando 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 tráfego de entrada para a porta Redis do Node.js. E, obviamente, eles não proibiram mais nada. Assim que o NetworkPolicy aparecer, todos os seletores mencionados nele começarão a ser isolados, a menos que seja indicado o contrário. Além disso, as regras de isolamento não se aplicam a outros objetos que não são cobertos pelo seletor.

O exemplo usa a apiVersion Kubernetes "pronta para uso", mas nada impede o uso do recurso com o mesmo nome na entrega do Calico . A sintaxe é mais extensa, portanto, é necessário reescrever a regra para o caso acima, da 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 proibir todo o tráfego através da API NetworkPolicy usual contêm estruturas com colchetes que são difíceis de entender e lembrar. No caso do Calico, para alterar a lógica da regra do firewall para o oposto, basta alterar a action: Allow para a action: Deny .

Isolamento do ambiente


Agora imagine uma situação em que um aplicativo gere métricas de negócios para coletá-las no Prometheus e análises adicionais através da Grafana. O descarregamento pode conter dados confidenciais, que, por padrão, estão novamente disponíveis ao público. Vamos fechar esses dados de olhares indiscretos:



O Prometheus, em regra, é colocado em um ambiente de serviço separado - no exemplo, será um espaço para nome do seguinte formato:

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

O campo metadata.labels aqui não foi acidental. Como mencionado acima, namespaceSelector (como podSelector ) opera em rótulos. Portanto, para permitir a obtenção de métricas de todos os pods em uma porta específica, você precisará adicionar algum rótulo (ou retirar dos existentes) e 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 Calico, a sintaxe seria:

 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, adicionando esse tipo de política a necessidades específicas, você pode se proteger contra interferências maliciosas ou acidentais na operação de aplicativos no cluster.

A melhor prática, de acordo com os criadores do Calico, é a abordagem "Proibir tudo e descobrir explicitamente o necessário", conforme registrada na documentação oficial (outros seguem uma abordagem semelhante, em particular no artigo já mencionado ).

Usando objetos opcionais do Calico


Deixe-me lembrá-lo de que, através do conjunto estendido de APIs do Calico, você pode controlar a disponibilidade de nós, não se limitando a pods. No exemplo a seguir, o uso de GlobalNetworkPolicy fecha a possibilidade de transmitir solicitações de ICMP no cluster (por exemplo, pings do pod para um nó, entre pods ou de um nó para o 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, os nós do cluster ainda podem "alcançar" um ao outro via ICMP. E essa pergunta é resolvida por meio de GlobalNetworkPolicy aplicado à 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"] 

Caso VPN


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



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

A porta na configuração é padrão, o que impõe algumas nuances ao processo de configuração do aplicativo e sua transferência para o cluster Kubernetes. Por exemplo, na mesma AWS, o LoadBalancer for 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 encaminhamento em todos os nós do cluster e é impossível dimensionar o número de instâncias do servidor para tolerância a falhas. Além disso, você precisa alterar o intervalo de portas padrão ...

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

  1. Os pods de VPN são agendados por host no modo hostNetwork , ou seja, no IP real.
  2. O serviço é lançado através do ClusterIP . A porta sobe fisicamente no host, acessível externamente com algumas advertências (disponibilidade condicional de um endereço IP real).
  3. A definição do nó no qual o pod subiu está além do escopo de nossa história. Só posso dizer que você pode "fixar" firmemente o serviço no host ou gravar um pequeno serviço de side-car que monitora o endereço IP atual do serviço VPN e edita os registros DNS registrados nos clientes - que têm imaginação suficiente.

Do ponto de vista do roteamento, podemos identificar exclusivamente o cliente para a VPN por seu endereço IP emitido pelo servidor VPN. Abaixo está um exemplo primitivo de restrição de acesso a esse cliente aos serviços, uma ilustração dos Redis mencionados 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 proibido conectar-se à porta 6379, mas, ao mesmo tempo, o serviço DNS é preservado, cujo funcionamento geralmente sofre ao elaborar as regras. Porque, como mencionado anteriormente, quando um seletor aparece, uma política padrão proibitiva é aplicada a ele, a menos que especificado de outra forma.

Sumário


Portanto, usando a API Calico Advanced, você pode configurar com flexibilidade e alterar dinamicamente o roteamento dentro e ao redor do cluster. Em geral, seu uso pode parecer disparar contra pardais, e a introdução de uma rede L3 com túneis IP BGP e IP parece monstruosa em uma instalação simples do Kubernetes em uma rede plana ... No entanto, o restante da ferramenta parece bastante viável e útil.

O isolamento de cluster para requisitos de segurança nem sempre é viável e é nesses casos que o Calico (ou uma solução semelhante) vem em socorro. Os exemplos neste artigo (com um pouco de refinamento) são usados ​​em várias instalações de nossos clientes na AWS.

PS


Leia também em nosso blog:

Source: https://habr.com/ru/post/pt485716/


All Articles