Calico para networking en Kubernetes: introdución e un pouco de experiencia

Calico para networking en Kubernetes: introdución e un pouco de experiencia

O propósito do artigo é presentar ao lector os conceptos básicos de redes e xestión de políticas de rede en Kubernetes, así como o complemento Calico de terceiros que amplía as capacidades estándar. Ao longo do camiño, demostrarase a facilidade de configuración e algunhas características mediante exemplos reais da nosa experiencia operativa.

Unha introdución rápida á aplicación de rede de Kubernetes

Non se pode imaxinar un clúster de Kubernetes sen unha rede. Xa publicamos materiais sobre as súas bases: "Guía ilustrada de redes en Kubernetes"E"Unha introdución ás políticas de rede de Kubernetes para profesionais da seguridade».

No contexto deste artigo, é importante ter en conta que o propio K8s non é responsable da conectividade de rede entre contedores e nodos: para iso, varios Complementos CNI (Interface de rede de contedores). Máis sobre este concepto nós tamén mo dixeron.

Por exemplo, o máis común destes complementos é Flanela — proporciona conectividade de rede completa entre todos os nodos do clúster levantando pontes en cada nodo, asignándolle unha subrede. Non obstante, a accesibilidade completa e non regulada non sempre é beneficiosa. Para garantir algún tipo de illamento mínimo no clúster, é necesario intervir na configuración do firewall. No caso xeral, colócase baixo o control do mesmo CNI, polo que calquera intervención de terceiros en iptables pode interpretarse incorrectamente ou ignorarse por completo.

E "fóra da caixa" para organizar a xestión de políticas de rede nun clúster de Kubernetes NetworkPolicy API. Este recurso, distribuído en espazos de nomes seleccionados, pode conter regras para diferenciar o acceso dunha aplicación a outra. Tamén permite configurar a accesibilidade entre pods, contornos (espazos de nomes) ou bloques de enderezos 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 non é o exemplo máis primitivo documentación oficial pode desalentar dunha vez por todas o desexo de comprender a lóxica de como funcionan as políticas de rede. Non obstante, aínda intentaremos comprender os principios e métodos básicos de procesamento de fluxos de tráfico mediante políticas de rede...

É lóxico que haxa 2 tipos de tráfico: de entrada ao pod (Ingress) e de saída (Egress).

Calico para networking en Kubernetes: introdución e un pouco de experiencia

En realidade, a política divídese nestas dúas categorías en función da dirección do movemento.

O seguinte atributo necesario é un selector; aquel a quen se aplica a regra. Pode ser un pod (ou un grupo de pods) ou un entorno (é dicir, un espazo de nomes). Un detalle importante: os dous tipos destes obxectos deben conter unha etiqueta (etiqueta en terminoloxía de Kubernetes) - estes son cos que operan os políticos.

Ademais dun número finito de selectores unidos por algún tipo de etiqueta, é posible escribir regras como "Permitir/negar todo/todos" en diferentes variacións. Para este fin, utilízanse construcións da forma:

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

— Neste exemplo, todos os pods do contorno están bloqueados para o tráfico entrante. O comportamento contrario pódese conseguir coa seguinte construción:

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

Do mesmo xeito para as saídas:

  podSelector: {}
  policyTypes:
  - Egress

- para apagalo. E aquí tes o que hai que incluír:

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

Volvendo á elección dun complemento CNI para un clúster, paga a pena sinalalo non todos os complementos de rede admiten NetworkPolicy. Por exemplo, o xa mencionado Flannel non sabe como configurar as políticas de rede, que dise directamente no repositorio oficial. Alí tamén se menciona unha alternativa: un proxecto de código aberto chita, que amplía significativamente o conxunto estándar de API de Kubernetes en termos de políticas de rede.

Calico para networking en Kubernetes: introdución e un pouco de experiencia

Coñecendo Calico: teoría

O complemento Calico pódese usar en integración con Flannel (subproxecto Canle) ou de forma independente, cubrindo tanto a conectividade de rede como as capacidades de xestión da dispoñibilidade.

Que oportunidades ofrece usar a solución "en caixa" K8s e o conxunto de API de Calico?

Aquí está o que está integrado en NetworkPolicy:

  • os políticos están limitados polo medio ambiente;
  • as políticas aplícanse aos pods marcados con etiquetas;
  • as regras pódense aplicar a pods, ambientes ou subredes;
  • as regras poden conter protocolos, especificacións de portos con nome ou simbólicos.

Así é como Calico estende estas funcións:

  • as políticas pódense aplicar a calquera obxecto: pod, contedor, máquina virtual ou interface;
  • as regras poden conter unha acción específica (prohibición, permiso, rexistro);
  • o destino ou fonte das regras pode ser un porto, unha serie de portos, protocolos, atributos HTTP ou ICMP, IP ou subrede (4ª ou 6ª xeración), calquera selector (nodos, hosts, ambientes);
  • Ademais, pode regular o paso do tráfico mediante a configuración de DNAT e as políticas de reenvío de tráfico.

Os primeiros compromisos en GitHub no repositorio de Calico remóntanse a xullo de 2016, e un ano despois o proxecto asumiu unha posición de liderado na organización da conectividade de rede de Kubernetes; así o demostran, por exemplo, os resultados da enquisa. dirixida por The New Stack:

Calico para networking en Kubernetes: introdución e un pouco de experiencia

Moitas grandes solucións xestionadas con K8, como Amazon EKS, Azure AKS, Google GKE e outros comezaron a recomendalo para o seu uso.

En canto ao rendemento, aquí todo é xenial. Ao probar o seu produto, o equipo de desenvolvemento de Calico demostrou un rendemento astronómico, executando máis de 50000 contedores en 500 nodos físicos cunha taxa de creación de 20 contedores por segundo. Non se identificaron problemas coa escala. Tales resultados foron anunciados xa no anuncio da primeira versión. Estudos independentes centrados no rendemento e no consumo de recursos tamén confirman que o rendemento de Calico é case tan bo como o de Flannel. Por exemplo:

Calico para networking en Kubernetes: introdución e un pouco de experiencia

O proxecto desenvólvese moi rápido, admite o traballo en solucións populares xestionadas K8s, OpenShift, OpenStack, é posible usar Calico ao despregar un clúster usando cops, hai referencias á construción de redes Service Mesh (aquí tes un exemplo usado en conxunto con Istio).

Practica con Calico

No caso xeral de usar Vanilla Kubernetes, instalar CNI redúcese a usar o ficheiro calico.yaml, descargado do sitio web oficial, mediante o uso kubectl apply -f.

Como regra xeral, a versión actual do complemento é compatible coas últimas versións 2-3 de Kubernetes: o funcionamento en versións antigas non está probado e non está garantido. Segundo os desenvolvedores, Calico funciona en núcleos Linux superiores a 3.10 con CentOS 7, Ubuntu 16 ou Debian 8, ademais de iptables ou IPVS.

Illamento no medio

Para unha comprensión xeral, vexamos un caso sinxelo para entender como as políticas de rede na notación Calico difieren das estándar e como o enfoque para crear regras simplifica a súa lexibilidade e flexibilidade de configuración:

Calico para networking en Kubernetes: introdución e un pouco de experiencia

Hai 2 aplicacións web despregadas no clúster: en Node.js e PHP, unha das cales usa Redis. Para bloquear o acceso a Redis desde PHP, mantendo a conectividade con Node.js, só tes que 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

Esencialmente, permitimos o tráfico entrante ao porto de Redis desde Node.js. E claramente non prohibiron nada máis. En canto aparece NetworkPolicy, todos os selectores mencionados nela comezan a estar illados, a non ser que se especifique o contrario. Non obstante, as regras de illamento non se aplican a outros obxectos non contemplados polo selector.

O exemplo usa apiVersion Kubernetes listo para usar, pero nada che impide usalo recurso do mesmo nome da entrega Calico. A sintaxe alí é máis detallada, polo que terás que reescribir a regra para o caso anterior 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 construcións mencionadas anteriormente para permitir ou denegar todo o tráfico a través da API de NetworkPolicy regular conteñen construcións con parénteses que son difíciles de entender e lembrar. No caso de Calico, para cambiar a lóxica dunha regra de firewall pola contraria, basta con cambiar action: Allow en action: Deny.

Illamento por medio

Agora imaxina unha situación na que unha aplicación xera métricas comerciais para a súa recollida en Prometheus e análise posterior mediante Grafana. A carga pode conter datos confidenciais, que de novo se poden ver publicamente por defecto. Ocultemos estes datos de miradas indiscretas:

Calico para networking en Kubernetes: introdución e un pouco de experiencia

Prometheus, por regra xeral, colócase nun ambiente de servizo separado; no exemplo, será un espazo de nomes como este:

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

Campo metadata.labels isto non resultou ser un accidente. Como se mencionou anteriormente, namespaceSelector (así como podSelector) funciona con etiquetas. Polo tanto, para permitir que se tomen métricas de todos os pods nun porto específico, terás que engadir algún tipo de etiqueta (ou tomar das existentes) e despois aplicar unha configuración 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 usa as políticas de Calico, a sintaxe será a seguinte:

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

En xeral, ao engadir este tipo de políticas para necesidades específicas, pode protexerse contra interferencias maliciosas ou accidentais no funcionamento das aplicacións do clúster.

A mellor práctica, segundo os creadores de Calico, é o enfoque "Bloquear todo e abrir explícitamente o que necesites", documentado en documentación oficial (outros seguen un enfoque similar, en particular, en artigo xa mencionado).

Usando obxectos Calico adicionais

Permíteme lembrarche que a través do conxunto estendido de API de Calico podes regular a dispoñibilidade de nós, sen limitarse aos pods. No seguinte exemplo usando GlobalNetworkPolicy a capacidade de pasar solicitudes ICMP no clúster está pechada (por exemplo, pings dun pod a un nodo, entre pods ou dun nodo a un 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 anterior, aínda é posible que os nodos do clúster "se comuniquen" entre si a través de ICMP. E este problema resólvese por medios GlobalNetworkPolicy, aplicado a unha 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 VPN

Finalmente, vou dar un exemplo moi real de uso de funcións Calico para o caso da interacción preto do clúster, cando un conxunto estándar de políticas non é suficiente. Para acceder á aplicación web, os clientes usan un túnel VPN, e este acceso está estrictamente controlado e limitado a unha lista específica de servizos permitidos:

Calico para networking en Kubernetes: introdución e un pouco de experiencia

Os clientes conéctanse á VPN a través do porto UDP estándar 1194 e, cando están conectados, reciben rutas ás subredes do clúster de pods e servizos. Subredes enteiras son empurradas para non perder servizos ao reiniciar e cambiar os enderezos.

O porto na configuración é estándar, o que impón algúns matices ao proceso de configuración da aplicación e a súa transferencia ao clúster de Kubernetes. Por exemplo, no mesmo AWS LoadBalancer para UDP apareceu literalmente a finais do ano pasado nunha lista limitada de rexións, e NodePort non se pode usar debido ao seu reenvío en todos os nodos do clúster e é imposible escalar o número de instancias do servidor para fins de tolerancia a fallos. Ademais, terás que cambiar o intervalo predeterminado de portos...

Como resultado da procura de posibles solucións, optouse por:

  1. Os pods con VPN están programados por nodo hostNetwork, é dicir, á IP real.
  2. O servizo publícase fóra a través ClusterIP. Un porto está instalado fisicamente no nodo, que é accesible desde o exterior con pequenas reservas (presenza condicional dun enderezo IP real).
  3. Determinar o nodo no que se levantou a vaina está fóra do alcance da nosa historia. Só direi que podes "clavar" o servizo nun nodo ou escribir un pequeno servizo de sidecar que supervisará o enderezo IP actual do servizo VPN e editará os rexistros DNS rexistrados cos clientes, quen teña suficiente imaxinación.

Desde a perspectiva do enrutamento, podemos identificar un cliente VPN polo seu enderezo IP emitido polo servidor VPN. A continuación móstrase un exemplo primitivo de restrinxir o acceso de tal cliente aos servizos, ilustrado no Redis anteriormente mencionado:

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]

Aquí, a conexión ao porto 6379 está estrictamente prohibida, pero ao mesmo tempo consérvase o funcionamento do servizo DNS, cuxo funcionamento adoita sufrir ao elaborar regras. Porque, como se mencionou anteriormente, cando aparece un selector, aplícase a política de denegación predeterminada a menos que se especifique o contrario.

Resultados de

Así, usando a API avanzada de Calico, pode configurar e cambiar dinámicamente o enrutamento dentro e arredor do clúster. En xeral, o seu uso pode parecer disparar a gorrións cun canón, e implementar unha rede L3 con túneles BGP e IP-IP parece monstruoso nunha simple instalación de Kubernetes nunha rede plana... Porén, se non, a ferramenta parece bastante viable e útil. .

Illar un clúster para cumprir os requisitos de seguridade pode non ser sempre viable, e aquí é onde Calico (ou unha solución similar) acude ao rescate. Os exemplos que se dan neste artigo (con pequenas modificacións) utilízanse en varias instalacións dos nosos clientes en AWS.

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario