Calico para redes en Kubernetes: conocer y un poco de 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 un complemento Calico de terceros que amplía las características estándar. En el camino, se demostrará la conveniencia de la configuración y algunas características con ejemplos reales de la experiencia de nuestra operación.

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 la creación de redes en Kubernetes " e " Introducción a las políticas de red de Kubernetes para profesionales de la seguridad ".

En el contexto de este artículo, es importante tener en cuenta que K8s no es responsable de la conectividad de red entre contenedores y nodos: para ello se utilizan todo tipo de complementos CNI (Interfaz de red de contenedores). También hablamos más sobre este concepto.

Por ejemplo, el más común de estos complementos, Flannel , proporciona conectividad de red completa entre todos los nodos del clúster al levantar puentes en cada nodo, asegurando una subred para él. Sin embargo, la disponibilidad total y no regulada no siempre es útil. Para proporcionar un aislamiento mínimo en el clúster, es necesario intervenir en la configuración del firewall. En general, se da a la gestión de la propia CNI, por lo que cualquier intervención de terceros en iptables puede interpretarse de forma incorrecta o ignorarse por completo.

Y listo para usar, la API NetworkPolicy se proporciona para organizar la administración de políticas de red en el clúster de Kubernetes. Este recurso, que se extiende a espacios de nombres seleccionados, puede contener reglas para restringir el acceso de una aplicación a otra. También le permite configurar la accesibilidad entre pods específicos, entornos (espacios de nombres) o bloques de direcciones 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: 5978 

Este no es el ejemplo más primitivo de la documentación oficial que puede desalentar de una vez por todas el deseo de comprender la lógica de las políticas de red. Sin embargo, todavía tratamos de comprender los principios y métodos básicos de procesamiento de flujos de tráfico utilizando políticas de red ...

Es lógico que haya 2 tipos de tráfico: entrante a pod (Ingress) y saliente (Egress).



En realidad, la política se divide en estas 2 categorías en 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 ( etiqueta en terminología de Kubernetes): estas son las políticas que operan.

Además de un número finito de selectores unidos por alguna etiqueta, existe la posibilidad de escribir reglas como "Permitir / Denegar todo / Todo" en diferentes variaciones. Para esto, se utilizan construcciones de la forma:

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

- En este ejemplo, todos los pods del entorno cierran el tráfico entrante. El comportamiento opuesto se puede lograr con tal construcción:

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

Del mismo modo para salientes:

  podSelector: {} policyTypes: - Egress 

- para deshabilitarlo. 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 admiten trabajar con NetworkPolicy . Por ejemplo, la Franela ya mencionada no sabe cómo configurar las políticas de red, como se indica explícitamente en el repositorio oficial. También se menciona una alternativa: el proyecto Calico Open Source, que extiende significativamente la API estándar de Kubernetes en términos de políticas de red.



Conoce a Calico: teoría


El complemento Calico se puede usar en integración con Flannel (un subproyecto de Canal ) o solo, cubriendo tanto la conectividad de red como las funciones de administración de disponibilidad.

¿Qué características ofrece la solución en caja K8s y el conjunto de API 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 pods etiquetados con etiquetas.
  • las reglas se pueden aplicar a pods, entornos o subredes;
  • Las reglas pueden contener protocolos, instrucciones de puerto con nombre o simbólicas.

Y así es como Calico extiende estas características:

  • 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 objetivo o la fuente de las reglas puede ser un puerto, rango de puertos, protocolos, atributos HTTP o ICMP, IP o subred (4 o 6 generaciones), cualquier selector (nodos, hosts, entornos);
  • Además, el flujo de tráfico se puede controlar mediante la configuración de DNAT y las políticas de reenvío de tráfico.

El primer compromiso de GitHub en el repositorio de Calico data de 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 red de Kubernetes; esto se indica, por ejemplo, en los resultados de una encuesta realizada por The New Stack :



Muchas soluciones administradas grandes con K8, como Amazon EKS , Azure AKS , Google GKE y otras, comenzaron a recomendar su uso.

En cuanto al rendimiento, todo es genial aquí. Al probar su producto, el equipo de desarrollo de Calico demostró un rendimiento astronómico al lanzar más de 50,000 contenedores en 500 nodos físicos con una velocidad de creación de 20 contenedores por segundo. No hubo problemas con el escalado. Dichos resultados ya se anunciaron en el anuncio de la primera versión. La investigación independiente sobre el ancho de banda y el consumo de recursos también confirma el rendimiento de Calico, que es casi el mismo que el de Flannel. Por ejemplo :



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

Practica con Calico


En el caso general de usar Kubernetes de vainilla, la instalación de CNI se reduce a usar el archivo calico.yaml descargado del sitio oficial usando kubectl apply -f .

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

Aislamiento en el medio ambiente.


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



Hay 2 aplicaciones web implementadas en el clúster: Node.js y PHP, una de las cuales usa Redis. Para bloquear el acceso a Redis desde PHP, mientras se deja la conectividad con Node.js, es suficiente aplicar 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 

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

El ejemplo utiliza la apiVersion de apiVersion “fuera de la caja”, pero nada impide usar el recurso del mismo nombre de la entrega Calico . La sintaxis es más extensa allí, por lo que debe volver a escribir la regla para el caso anterior en el siguiente formulario:

 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 prohibir todo el tráfico a través de la API NetworkPolicy habitual contienen estructuras con corchetes que son difíciles de entender y recordar. En el caso de Calico, para cambiar la lógica de la regla del firewall al contrario, simplemente cambie la action: Allow a la action: Deny .

Aislamiento del ambiente


Ahora imagine una situación en la que una aplicación genera métricas comerciales para recopilarlas en Prometheus y un análisis más detallado a través de Grafana. La descarga puede contener datos confidenciales que, de forma predeterminada, vuelven a estar disponibles públicamente. Cerremos estos datos de miradas indiscretas:



Prometheus, como regla, se coloca en un entorno de servicio separado; en el ejemplo, será un espacio de nombres de la siguiente forma:

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

El campo metadata.labels aquí no fue accidental. Como se mencionó anteriormente, namespaceSelector (como podSelector ) opera en etiquetas. Por lo tanto, para permitir tomar métricas de todos los pods en un puerto específico, deberá agregar alguna 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 usa las políticas de Calico, la sintaxis sería:

 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ítica a 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 "Prohibir todo y descubrir explícitamente lo necesario", como se registra en la documentación oficial (otros siguen un enfoque similar, en particular, en el artículo ya mencionado ).

Usando objetos opcionales Calico


Permítame recordarle que a través del conjunto extendido de API de Calico, puede controlar la disponibilidad de nodos, no limitado a pods. En el siguiente ejemplo, el uso de GlobalNetworkPolicy cierra la posibilidad de pasar solicitudes ICMP en el clúster (por ejemplo, pings de pod a un nodo, entre pods o de un nodo a IP 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 

En el caso anterior, los nodos del clúster todavía pueden "comunicarse" entre sí a través de ICMP. Y esta pregunta se resuelve mediante GlobalNetworkPolicy aplicada a la 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"] 

Caso de VPN


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



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. Las subredes de inserción están completamente 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 for 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 en todos los nodos del clúster y es imposible escalar el número de instancias del servidor para la tolerancia a fallas. Además, debe cambiar el rango de puertos predeterminado ...

Como resultado de una búsqueda de posibles soluciones, se eligió lo siguiente:

  1. Las vainas VPN se programan por host en modo hostNetwork , es decir, en la IP real.
  2. El servicio se publica a través de ClusterIP . El puerto se eleva físicamente en el host, al que se puede acceder desde el exterior con algunas advertencias (disponibilidad condicional de una dirección IP real).
  3. La definición del nodo en el que se levantó la vaina está más allá del alcance de nuestra historia. Solo puedo decir que puede "clavar" firmemente el servicio al host o escribir un pequeño servicio de sidecar que supervisará la dirección IP actual del servicio VPN y editará los registros DNS registrados con los clientes, que tiene suficiente imaginación.

Desde el punto de vista del enrutamiento, podemos identificar de forma exclusiva al cliente para la VPN por su dirección IP emitida por el servidor VPN. A continuación se muestra un ejemplo primitivo de restringir el acceso a dicho cliente a los servicios, una ilustración en el mencionado Redis:

 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 preserva el servicio DNS, cuyo funcionamiento a menudo sufre cuando se elaboran las reglas. Porque, como se mencionó anteriormente, cuando aparece un selector, se le aplica una política predeterminada prohibitiva, a menos que se especifique lo contrario.

Resumen


Por lo tanto, utilizando 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 disparos en gorriones, y la introducción de una red L3 con túneles BGP e IP-IP parece monstruosa en una instalación simple de Kubernetes en una red plana ... Sin embargo, el resto de la herramienta parece bastante viable y útil.

El aislamiento del clúster por requisitos de seguridad no siempre es factible, y es en tales casos que Calico (o una solución similar) viene al rescate. Los ejemplos en este artículo (con un poco de refinamiento) se utilizan en varias instalaciones de nuestros clientes en AWS.

PS


Lea también en nuestro blog:

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


All Articles