
O objetivo deste artigo é apresentar ao leitor os fundamentos da interação de rede e da gestão de políticas de rede no Kubernetes, assim como o plugin Calico, que expande as funcionalidades padrão. Serão demonstradas a facilidade de configuração e algumas funcionalidades com exemplos práticos da nossa experiência operacional.
Introdução rápida ao dispositivo de rede Kubernetes
É impossível imaginar um cluster Kubernetes sem rede. Já publicamos materiais sobre seus fundamentos: “” e “».
No contexto deste artigo, é importante observar que a conectividade de rede entre contêineres e nós não é gerenciada pelo próprio K8s: para isso, são utilizados diversos plugins CNI (Interface de Rede de Contêiner). Falamos mais sobre este conceito também .
Por exemplo, o plugin mais comum é — que fornece conectividade total entre todos os nós do cluster, através da criação de pontes em cada nó, atribuindo uma sub-rede a ele. No entanto, a disponibilidade total e não regulamentada nem sempre é benéfica. Para garantir alguma mínima isolamento no cluster, é necessário intervir na configuração do firewall. De maneira geral, isso é gerido pelo próprio CNI, o que significa que qualquer intervenção externa no iptables pode ser interpretada de maneira incorreta ou até ignorada.
E "pronto para uso" para gerenciar políticas de rede em um cluster Kubernetes é oferecido o . Este recurso, que se aplica aos namespaces selecionados, pode conter regras para delimitar o acesso de um aplicativo a outro. Ele também permite configurar a acessibilidade entre pods específicos, ambientes (namespaces) ou blocos de endereços 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: 5978Este não é o exemplo mais simples entre Pode, de uma vez por todas, desmotivar a compreensão da lógica do funcionamento das políticas de rede. No entanto, ainda assim tentaremos entender os princípios básicos e métodos de processamento do tráfego utilizando políticas de rede...
É lógico que existem 2 tipos de tráfego: o que entra no pod (Ingress) e o que sai dele (Egress).

Essas 2 categorias, quanto à direção do movimento, são como se divide a política.
O próximo atributo obrigatório é o seletor; aquele a quem a regra se aplica. Pode ser um pod (ou grupo de pods) ou um ambiente (ou seja, um namespace). Um detalhe importante: ambos os tipos desses objetos devem conter um rótulo (label na terminologia do Kubernetes) — são exatamente eles que as políticas manipulam.
Além do número fixo de seletores, agrupados por algum rótulo, existe a possibilidade de redigir regras como "Permitir/proibir tudo/todos" em várias variações. Para isso, são utilizadas construções do tipo:
podSelector: {}
ingress: []
policyTypes:
- Ingress— neste exemplo, a todos os pods do ambiente é negado o tráfego de entrada. O comportamento oposto pode ser alcançado com a seguinte construção:
podSelector: {}
ingress:
- {}
policyTypes:
- IngressDa mesma forma para o tráfego de saída:
podSelector: {}
policyTypes:
- Egress— para desativá-lo. E aqui está como ativá-lo:
podSelector: {}
egress:
- {}
policyTypes:
- EgressVoltando à escolha do plugin CNI para o cluster, vale destacar que nem todo plugin de rede suporta o trabalho com NetworkPolicy. Por exemplo, o já mencionado Flannel não consegue configurar políticas de rede, como está no repositório oficial. Também mencionada lá está a alternativa — um projeto Open Source , que expande consideravelmente o conjunto padrão de APIs do Kubernetes em termos de políticas de rede.

Vamos conhecer o Calico: teoria
O plugin Calico pode ser utilizado em integração com Flannel (subprojeto ) ou de forma independente, cobrindo tanto as funções de conectividade de rede quanto as possibilidades de gerenciamento de acessibilidade.
Quais possibilidades são oferecidas pelo uso da solução "pronta" do K8s e do conjunto de APIs do Calico?
Aqui está o que está embutido no NetworkPolicy:
- as políticas são restritas ao ambiente;
- as políticas se aplicam a pods marcados com rótulos;
- as regras podem ser aplicadas a pods, ambientes ou sub-redes;
- as regras podem conter protocolos, referências de porta nomeadas ou simbólicas.
E aqui está como o Calico expande 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);
- como destino ou origem das regras pode haver uma porta, 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, é possível regular o tráfego por meio de configurações de DNAT e políticas de encaminhamento de tráfego.
Os primeiros commits no GitHub do repositório Calico datam de julho de 2016, e já um ano depois o projeto ocupou posições de liderança na organização de conectividade de rede do Kubernetes — isso é corroborado, por exemplo, pelos resultados de uma pesquisa, :

Muitas grandes soluções gerenciadas com K8s, como , , e outras, começaram a recomendá-lo para uso.
No que diz respeito ao desempenho, tudo está ótimo. Durante os testes de seu produto, a equipe de desenvolvimento do Calico demonstrou índices astronômicos, rodando mais de 50.000 contêineres em 500 nós físicos com uma taxa de criação de 20 contêineres por segundo. Não foram identificados problemas de escalabilidade. Esses resultados já no anúncio da primeira versão. Pesquisas independentes, focadas na largura de banda e no consumo de recursos, também confirmam o desempenho do Calico, que é praticamente equivalente ao do Flannel. :

O projeto está se desenvolvendo muito rapidamente, suportando a operação em soluções populares de K8s gerenciadas, OpenShift, OpenStack, há a possibilidade de utilizar o Calico ao implantar um cluster com , há menções sobre a construção de redes Service Mesh ( de utilização em conjunto com o Istio).
Prática com o Calico
No caso geral de uso do Kubernetes vanilla, a instalação do CNI consiste na aplicação do arquivo calico.yaml, , usando kubectl apply -f.
Em geral, a versão atual do plug-in é compatível com as 2-3 últimas versões do Kubernetes: o funcionamento em versões mais antigas não é testado nem garantido. Segundo os desenvolvedores, o Calico funciona com o kernel Linux acima da versão 3.10 sob o CentOS 7, Ubuntu 16 ou Debian 8, sobre iptables ou IPVS.
Isolamento dentro do ambiente
Para uma compreensão geral, consideremos um caso simples para entender como as políticas de rede na notação Calico diferem das padrão e como a abordagem na formulação das regras facilita a legibilidade e a flexibilidade na configuração:

No cluster, estão implantações 2 aplicativos web: um em Node.js e outro em PHP, sendo que um deles utiliza Redis. Para restringir o acesso ao Redis a partir 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: 6379Basicamente, permitimos o tráfego de entrada na porta do Redis a partir do Node.js. E explicitamente não proibimos nada mais. Assim que uma NetworkPolicy entra em vigor, todos os seletores mencionados nela começam a se isolar, a menos que algo diferente seja especificado. As regras de isolamento, no entanto, não se aplicam a outros objetos que não estejam cobertos pelo seletor.
O exemplo utiliza apiVersion do Kubernetes 'pronto para uso', mas nada impede de utilizar . A sintaxe lá é mais abrangente, então será necessário reescrever a regra para o caso mencionado anteriormente 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 estruturas mencionadas acima para permitir ou negar todo o tráfego por meio da API padrão de NetworkPolicy contêm construções complexas que são difíceis de entender e memorizar. No caso do Calico, para inverter a lógica da regra do firewall, basta mudar action: Allow para action: Deny.
Isolamento por ambientes
Agora, vamos imaginar uma situação onde o aplicativo gera métricas de negócios para coleta no Prometheus e posterior análise por meio do Grafana. A extração pode conter dados sensíveis que, por padrão, novamente estão disponíveis para visualização pública. Vamos proteger esses dados de olhares curiosos:

O Prometheus geralmente é colocado em um ambiente de serviço separado — no exemplo, isso será um namespace do seguinte tipo:
apiVersion: v1
kind: Namespace
metadata:
labels:
module: prometheus
name: kube-prometheus Campo metadata.labels não foi acidental. Como mencionado anteriormente, namespaceSelector (assim como podSelector) opera com rótulos. Portanto, para permitir a coleta de métricas de todos os pods em uma determinada porta, será necessário adicionar algum rótulo (ou usar um dos existentes) e, em seguida, 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: 9100E no caso de usar políticas 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:
- 9100No geral, ao adicionar esse tipo de políticas para necessidades específicas, é possível proteger contra intervenções maliciosas ou acidentais no funcionamento dos aplicativos no cluster.
A melhor prática, segundo os criadores do Calico, é a abordagem "Proíba tudo e abra o que for necessário de forma explícita", registrada em (uma abordagem semelhante é seguida por outros — em particular, em ).
A aplicação de objetos adicionais do Calico
Lembro que, através de um conjunto avançado de API, o Calico pode regular a acessibilidade dos nós, sem se limitar a pods. No próximo exemplo, usando GlobalNetworkPolicy é bloqueada a passagem de requisições ICMP no cluster (por exemplo, pings de um pod para um nó, entre pods ou de um nó para o IP de um 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 No caso acima, os nós do cluster ainda têm a possibilidade de se "comunicar" entre si via ICMP. E essa questão é resolvida por meio de GlobalNetworkPolicy, aplicada à 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 com VPN
Por fim, apresento um exemplo bastante real do uso das funções do Calico para interação em torno do cluster, quando o conjunto padrão de políticas não é suficiente. Para acesso a um aplicativo web por clientes, é usado um túnel VPN, e esse acesso é rigidamente controlado e limitado a uma lista específica de serviços permitidos:

Os clientes conectam-se ao VPN através da porta UDP padrão 1194 e, ao conectar, recebem rotas para as sub-redes dos pods e serviços do cluster. As sub-redes são enviadas integralmente para não perder serviços durante reinicializações e alterações de endereços.
A porta na configuração é padrão, o que impõe algumas nuances no processo de configuração do aplicativo e sua migração para o cluster Kubernetes. Por exemplo, no AWS LoadBalancer, a opção para UDP só surgiu no final do ano passado em uma lista restrita de regiões, e o NodePort não pode ser utilizado devido ao seu encaminhamento em todos os nós do cluster, tornando inviável aumentar o número de instâncias do servidor visando alta disponibilidade. Além disso, será necessário alterar o intervalo de portas padrão selecionado...
Como resultado da exploração de soluções possíveis, foi escolhida a seguinte:
- Os pods com VPN estão previstos para o nó em modo
hostNetwork, ou seja, no IP real. - O serviço é exposto externamente através de
ClusterIP. No nó, uma porta é fisicamente levantada, que está acessível externamente com algumas ressalvas (a condição de ter um endereço IP real). - A determinação do nó em que o pod está ativo está além do escopo desta narrativa. Apenas mencionarei que é possível "fixar" rigidamente o serviço a um nó ou escrever um pequeno serviço sidecar que monitorará o IP atual do serviço VPN e corrigirá os registros DNS registrados pelos clientes — conforme a criatividade de cada um.
Do ponto de vista de roteamento, podemos identificar inequivocamente um cliente no VPN pelo seu endereço IP, fornecido pelo servidor VPN. Abaixo, um exemplo simples de limitação de acesso a tal cliente aos serviços, ilustrado no Redis mencionado anteriormente:
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, a conexão na porta 6379 é rigidamente proibida, mas ao mesmo tempo a operação do serviço DNS é mantida, cuja funcionalidade frequentemente sofre ao compor regras. Porque, como mencionado anteriormente, ao surgirem seletores, aplica-se uma política restritiva por padrão, a menos que especificado de outra forma.
Resultados
Assim, com a API avançada do Calico, é possível configurar e alterar dinamicamente a roteação no cluster e ao seu redor. Em geral, seu uso pode parecer como usar um canhão para matar pardais, e a implementação de uma rede L3 com túneis BGP e IP-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.
A isolação do cluster para atender aos requisitos de segurança nem sempre pode ser realizada, e é exatamente nesses casos que o Calico (ou solução semelhante) entra em cena. Os exemplos citados no artigo (com um pequeno ajuste) são usados em várias instalações de nossos clientes na AWS.
P.S.
Confira também em nosso blog:
- «»;
- «Guia ilustrado para a estrutura da rede no Kubernetes»: , ;
- «».
Fonte: habr.com
