Calico para networking en Kubernetes: introducción y una pequeña experiencia

Calico para networking en Kubernetes: introducción y una pequeña experiencia

El propósito del artículo es presentar al lector los conceptos básicos de las redes y la administración de políticas de red en Kubernetes, así como el complemento Calico de terceros que amplía las capacidades estándar. A lo largo del camino, se demostrará la facilidad de configuración y algunas características utilizando ejemplos reales de nuestra experiencia operativa.

Una introducción rápida al dispositivo de red Kubernetes

No se puede imaginar un clúster de Kubernetes sin una red. Ya hemos publicado materiales sobre sus conceptos básicos: “Una guía ilustrada para establecer redes en Kubernetes"Y"Introducción a las políticas de red de Kubernetes para profesionales de la seguridad".

En el contexto de este artículo, es importante señalar que K8s en sí no es responsable de la conectividad de red entre contenedores y nodos: para ello, varios Complementos CNI (Interfaz de red de contenedores). Más sobre este concepto nosotros también me dijeron.

Por ejemplo, el más común de estos complementos es Franela — proporciona conectividad de red completa entre todos los nodos del clúster levantando puentes en cada nodo y asignándole una subred. Sin embargo, una accesibilidad completa y no regulada no siempre es beneficiosa. Para proporcionar algún tipo de aislamiento mínimo en el cluster, es necesario intervenir en la configuración del firewall. En general, está bajo el control del mismo CNI, por lo que cualquier intervención de terceros en iptables puede interpretarse incorrectamente o ignorarse por completo.

Y se proporciona "listo para usar" para organizar la gestión de políticas de red en un clúster de Kubernetes. API de política de red. Este recurso, distribuido en espacios de nombres seleccionados, puede contener reglas para diferenciar el acceso de una aplicación a otra. También le permite configurar la accesibilidad entre pods, entornos (espacios de nombres) o bloques de direcciones 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 no es el ejemplo más primitivo de documentación oficial puede desalentar de una vez por todas el deseo de comprender la lógica de cómo funcionan las políticas de red. Sin embargo, todavía intentaremos comprender los principios y métodos básicos de procesamiento de flujos de tráfico utilizando políticas de red...

Es lógico que existan 2 tipos de tráfico: el que entra al pod (Ingress) y el que sale del mismo (Egress).

Calico para networking en Kubernetes: introducción y una pequeña experiencia

En realidad, la política se divide en estas 2 categorías según la dirección del movimiento.

El siguiente atributo requerido es un selector; aquel a quien se aplica la regla. Puede ser un pod (o un grupo de pods) o un entorno (es decir, un espacio de nombres). Un detalle importante: ambos tipos de estos objetos deben contener una etiqueta (Label en terminología de Kubernetes): estos son con los que operan los políticos.

Además de un número finito de selectores, unidos por algún tipo de etiqueta, es posible escribir reglas como “Permitir/denegar todo/todos” en diferentes variaciones. Para ello se utilizan construcciones de la forma:

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

— en este ejemplo, todos los pods del entorno están bloqueados para el tráfico entrante. El comportamiento opuesto se puede lograr con la siguiente construcción:

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

Lo mismo para salientes:

  podSelector: {}
  policyTypes:
  - Egress

- para apagarlo. Y esto es lo que debe incluir:

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

Volviendo a la elección de un complemento CNI para un clúster, vale la pena señalar que no todos los complementos de red son compatibles con NetworkPolicy. Por ejemplo, la ya mencionada franela no sabe configurar políticas de red, lo que se dice directamente en el repositorio oficial. Allí también se menciona una alternativa: un proyecto de código abierto. Calicó, que amplía significativamente el conjunto estándar de API de Kubernetes en términos de políticas de red.

Calico para networking en Kubernetes: introducción y una pequeña experiencia

Conociendo a Calico: teoría

El complemento Calico se puede utilizar en integración con franela (subproyecto Canal) o de forma independiente, cubriendo tanto la conectividad de red como las capacidades de gestión de disponibilidad.

¿Qué oportunidades ofrece el uso de la solución "en caja" de K8 y el conjunto de API de Calico?

Esto es lo que está integrado en NetworkPolicy:

  • los políticos están limitados por el medio ambiente;
  • las políticas se aplican a los grupos marcados con etiquetas;
  • las reglas se pueden aplicar a pods, entornos o subredes;
  • Las reglas pueden contener protocolos, especificaciones de puertos con nombre o simbólicos.

Así es como Calico amplía estas funciones:

  • las políticas se pueden aplicar a cualquier objeto: pod, contenedor, máquina virtual o interfaz;
  • las reglas pueden contener una acción específica (prohibición, permiso, registro);
  • el destino o la fuente de las reglas puede ser un puerto, una variedad de puertos, protocolos, atributos HTTP o ICMP, IP o subred (cuarta o sexta generación), cualquier selector (nodos, hosts, entornos);
  • Además, puede regular el paso del tráfico mediante la configuración de DNAT y las políticas de reenvío de tráfico.

Los primeros compromisos en GitHub en el repositorio de Calico se remontan a julio de 2016, y un año después, el proyecto tomó una posición de liderazgo en la organización de la conectividad de la red de Kubernetes; esto se evidencia, por ejemplo, en los resultados de la encuesta. realizado por The New Stack:

Calico para networking en Kubernetes: introducción y una pequeña experiencia

Muchas soluciones administradas de gran tamaño con K8, como Amazon EKS, AKS azul, GKE de Google y otros comenzaron a recomendar su uso.

En cuanto al rendimiento, aquí todo es genial. Al probar su producto, el equipo de desarrollo de Calico demostró un rendimiento astronómico, ejecutando más de 50000 contenedores en 500 nodos físicos con una tasa de creación de 20 contenedores por segundo. No se identificaron problemas con el escalado. Tales resultados fueron anunciados Ya en el anuncio de la primera versión. Estudios independientes centrados en el rendimiento y el consumo de recursos también confirman que el rendimiento de Calico es casi tan bueno como el de franela. Por ejemplo:

Calico para networking en Kubernetes: introducción y una pequeña experiencia

El proyecto se está desarrollando muy rápidamente, admite el trabajo en soluciones populares administradas K8, OpenShift, OpenStack, es posible usar Calico al implementar un clúster usando policías, hay referencias a la construcción de redes Service Mesh (aquí hay un ejemplo utilizado junto con Istio).

Practica con Calico

En el caso general de usar Kubernetes básico, la instalación de CNI se reduce a usar el archivo calico.yaml, descargado del sitio web oficial, vía kubectl apply -f.

Como regla general, la versión actual del complemento es compatible con las últimas 2 o 3 versiones de Kubernetes: el funcionamiento en versiones anteriores no se prueba ni se garantiza. Según los desarrolladores, Calico se ejecuta en kernels de Linux superiores a 3.10 con CentOS 7, Ubuntu 16 o Debian 8, además de iptables o IPVS.

Aislamiento dentro del medio ambiente.

Para una comprensión general, veamos un caso simple para comprender cómo las políticas de red en la notación Calico difieren de las estándar y cómo el enfoque para crear reglas simplifica su legibilidad y flexibilidad de configuración:

Calico para networking en Kubernetes: introducción y una pequeña experiencia

Hay 2 aplicaciones web implementadas en el clúster: en Node.js y PHP, una de las cuales usa Redis. Para bloquear el acceso a Redis desde PHP, manteniendo la conectividad con Node.js, simplemente aplique la siguiente 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

Básicamente, permitimos el tráfico entrante al puerto de Redis desde Node.js. Y claramente no prohibieron nada más. Tan pronto como aparece NetworkPolicy, todos los selectores mencionados en él comienzan a aislarse, a menos que se especifique lo contrario. Sin embargo, las reglas de aislamiento no se aplican a otros objetos no cubiertos por el selector.

El ejemplo utiliza apiVersion Kubernetes listo para usar, pero nada te impide usarlo recurso del mismo nombre de la entrega Calico. La sintaxis allí es más detallada, por lo que deberá reescribir la regla para el caso anterior de la siguiente 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

Las construcciones mencionadas anteriormente para permitir o denegar todo el tráfico a través de la API NetworkPolicy normal contienen construcciones entre paréntesis que son difíciles de entender y recordar. En el caso de Calico, para cambiar la lógica de una regla de firewall a lo contrario, simplemente cambie action: Allow en action: Deny.

Aislamiento por ambiente

Ahora imagine una situación en la que una aplicación genera métricas comerciales para su recopilación en Prometheus y su posterior análisis utilizando Grafana. La carga puede contener datos confidenciales, que nuevamente son visibles públicamente de forma predeterminada. Ocultemos estos datos de miradas indiscretas:

Calico para networking en Kubernetes: introducción y una pequeña experiencia

Prometheus, por regla general, se coloca en un entorno de servicio separado; en el ejemplo, será un espacio de nombres como este:

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

Campo metadata.labels Esto resultó no ser un accidente. Como se ha mencionado más arriba, namespaceSelector (como podSelector) opera con etiquetas. Por lo tanto, para permitir que se tomen métricas de todos los pods en un puerto específico, deberá agregar algún tipo de etiqueta (o tomar de las existentes) y luego aplicar una 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

Y si usas políticas de Calico, la sintaxis será así:

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 general, al agregar este tipo de políticas para necesidades específicas, puede protegerse contra interferencias maliciosas o accidentales en el funcionamiento de las aplicaciones en el clúster.

La mejor práctica, según los creadores de Calico, es el enfoque "Bloquear todo y abrir explícitamente lo que necesites", documentado en documentación oficial (otros siguen un enfoque similar - en particular, en artículo ya mencionado).

Usar objetos Calico adicionales

Permítanme recordarles que a través del conjunto ampliado de API de Calico se puede regular la disponibilidad de los nodos, sin limitarse a los pods. En el siguiente ejemplo usando GlobalNetworkPolicy la capacidad de pasar solicitudes ICMP en el clúster está cerrada (por ejemplo, pings de un pod a un nodo, entre pods o de un nodo a un pod de 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

En el caso anterior, todavía es posible que los nodos del clúster se "contacten" entre sí a través de ICMP. Y este problema se resuelve por medio GlobalNetworkPolicy, aplicado a una entidad 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"]

El caso de las VPN

Finalmente, daré un ejemplo muy real del uso de funciones de Calico para el caso de interacción cercana a un clúster, cuando un conjunto estándar de políticas no es suficiente. Para acceder a la aplicación web, los clientes utilizan un túnel VPN, y este acceso está estrictamente controlado y limitado a una lista específica de servicios permitidos para su uso:

Calico para networking en Kubernetes: introducción y una pequeña experiencia

Los clientes se conectan a la VPN a través del puerto UDP estándar 1194 y, cuando están conectados, reciben rutas a las subredes del clúster de pods y servicios. Se envían subredes enteras para no perder servicios durante los reinicios y los cambios de dirección.

El puerto en la configuración es estándar, lo que impone algunos matices en el proceso de configuración de la aplicación y su transferencia al clúster de Kubernetes. Por ejemplo, en el mismo AWS LoadBalancer para UDP apareció literalmente a fines del año pasado en una lista limitada de regiones, y NodePort no se puede usar debido a su reenvío a todos los nodos del clúster y es imposible escalar el número de instancias de servidor para fines de tolerancia a fallos. Además, tendrás que cambiar el rango predeterminado de puertos...

Como resultado de la búsqueda de posibles soluciones se optó por la siguiente:

  1. Los pods con VPN se programan por nodo en hostNetwork, es decir, a la IP real.
  2. El servicio se publica en el exterior a través de ClusterIP. En el nodo se instala físicamente un puerto al que se puede acceder desde el exterior con pequeñas reservas (presencia condicional de una dirección IP real).
  3. Determinar el nodo sobre el que surgió la vaina está más allá del alcance de nuestra historia. Solo diré que puede "clavar" firmemente el servicio a un nodo o escribir un pequeño servicio complementario que monitoreará la dirección IP actual del servicio VPN y editará los registros DNS registrados con los clientes, quien tenga suficiente imaginación.

Desde una perspectiva de enrutamiento, podemos identificar de forma única a un cliente VPN por su dirección IP emitida por el servidor VPN. A continuación se muestra un ejemplo primitivo de cómo restringir el acceso de dicho cliente a los servicios, ilustrado en el 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]

Aquí está estrictamente prohibido conectarse al puerto 6379, pero al mismo tiempo se conserva el funcionamiento del servicio DNS, cuyo funcionamiento a menudo se ve afectado al redactar las reglas. Porque, como se mencionó anteriormente, cuando aparece un selector, se le aplica la política de denegación predeterminada a menos que se especifique lo contrario.

resultados

Por lo tanto, al utilizar la API avanzada de Calico, puede configurar de manera flexible y cambiar dinámicamente el enrutamiento dentro y alrededor del clúster. En general, su uso puede parecer como disparar gorriones con un cañón, e implementar una red L3 con BGP y túneles IP-IP parece monstruoso en una instalación simple de Kubernetes en una red plana... Sin embargo, por lo demás, la herramienta parece bastante viable y útil. .

Es posible que no siempre sea factible aislar un clúster para cumplir con los requisitos de seguridad, y aquí es donde Calico (o una solución similar) viene al rescate. Los ejemplos dados en este artículo (con modificaciones menores) se utilizan en varias instalaciones de nuestros clientes en AWS.

PS

Lea también en nuestro blog:

Fuente: habr.com

Añadir un comentario