Calico para redes en Kubernetes: introducción y algo de experiencia

Calico para redes en Kubernetes: introducción y algo de experiencia

El objetivo del artículo es presentar al lector los fundamentos de la interacción en red y la gestión de políticas de red en Kubernetes, así como el complemento externo Calico, que amplía las capacidades estándar. También se demostrará la facilidad de configuración y algunas características con ejemplos reales de nuestra experiencia.

Introducción rápida al dispositivo de red de Kubernetes

Un clúster de Kubernetes no se puede imaginar sin red. Ya hemos publicado materiales sobre sus fundamentos: «Guía ilustrada del funcionamiento de la red en Kubernetes» y «Introducción a las políticas de red de Kubernetes para especialistas en seguridad».

En el contexto de este artículo, es importante destacar que la conectividad de red entre contenedores y nodos no es responsabilidad del propio K8s: para ello se utilizan diversos complementos CNI (Interfaz de Red de Contenedores). Hablamos más sobre este concepto también aquí.

Por ejemplo, el complemento más común de este tipo es Flannel , que proporciona conectividad de red completa entre todos los nodos del clúster mediante la creación de puentes en cada nodo, asignando a cada uno de ellos una subred. Sin embargo, la disponibilidad total y no regulada no siempre es útil. Para garantizar cierta mínima aislamiento en el clúster, es necesario intervenir en la configuración del firewall. En general, esta tarea está bajo la gestión del propio CNI, lo que significa que cualquier intervención externa en iptables puede ser interpretada incorrectamente o incluso ignorada por completo.

Y "de inmediato", para organizar la gestión de políticas de red en el clúster de Kubernetes, se proporciona API de NetworkPolicy. Este recurso, que se aplica a los espacios de nombres seleccionados, puede contener reglas para delimitar el acceso entre algunas aplicaciones y otras. También permite ajustar la disponibilidad 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 sencillo de documentación oficial Puede acabar con el deseo de comprender la lógica del funcionamiento de las políticas de red de una vez por todas. Sin embargo, intentaremos entender los principios básicos y los métodos de procesamiento del tráfico mediante las políticas de red...

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

Calico para redes en Kubernetes: introducción y algo de experiencia

En realidad, estas 2 categorías se dividen según la dirección del movimiento.

El siguiente atributo obligatorio es el selector; el destinatario de la regla. Esto puede ser un pod (o un grupo de pods) o un entorno (es decir, un espacio de nombres). Un detalle importante: ambos tipos de objetos deben contener una etiqueta (label en la terminología de Kubernetes) — son con las que operan las políticas.

Además del número final de selectores, agrupados por una etiqueta, existe la posibilidad de escribir reglas como "Permitir/prohibir todo/a todos" en diferentes variaciones. Para ello se utilizan construcciones del tipo:

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

— en este ejemplo, se bloquea el tráfico de entrada a todos los pods del entorno. Se puede obtener un comportamiento contrario con la siguiente construcción:

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

De manera similar para el tráfico saliente:

  podSelector: {}
  policyTypes:
  - Egress

— para desactivarlo. Y esto es lo necesario para activarlo:

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

Volviendo a la elección del plugin CNI para el clúster, cabe destacar que no todos los plugins de red son compatibles con NetworkPolicy. Por ejemplo, el ya mencionado Flannel no sabe configurar políticas de red, lo cual se menciona directamente en el repositorio oficial. Allí también se menciona una alternativa: el proyecto de código abierto Calico, que amplía notablemente el conjunto estándar de API de Kubernetes en términos de políticas de red.

Calico para redes en Kubernetes: introducción y algo de experiencia

Conociendo Calico: teoría

El plugin Calico puede usarse en integración con Flannel (subproyecto Canal) o de forma independiente, cubriendo tanto funciones para garantizar la conectividad de red como capacidades de gestión de disponibilidad.

¿Qué posibilidades ofrece el uso de una solución "fuera de la caja" de K8s y el conjunto de API de Calico?

Esto es lo que está integrado en NetworkPolicy:

  • las políticas están limitadas al entorno;
  • las políticas se aplican a los pods etiquetados;
  • las reglas pueden aplicarse a pods, entornos o subredes;
  • las reglas pueden contener protocolos, especificaciones de puertos nombrados o simbólicos.

Y así es como Calico amplía estas funciones:

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

Los primeros commits en GitHub en el repositorio de Calico datan de julio de 2016, y ya un año después el proyecto ocupó posiciones de liderazgo en la organización de conectividad en red de Kubernetes — esto se refleja, por ejemplo, en los resultados de la encuesta, realizada por The New Stack:

Calico para redes en Kubernetes: introducción y algo de experiencia

Muchas grandes soluciones gestionadas con K8s, tales como Amazon EKS, Azure AKS, Google GKE y otros, han comenzado a recomendar su uso.

En cuanto al rendimiento, todo es excelente. En las pruebas de su producto, el equipo de desarrollo de Calico demostró cifras astronómicas, ejecutando más de 50000 contenedores en 500 nodos físicos a una velocidad de creación de 20 contenedores por segundo. No se encontraron problemas al escalar. Tales resultados fueron anunciados ya al presentar la primera versión. Los estudios independientes, orientados a la capacidad de procesamiento y el consumo de recursos, también confirman el rendimiento de Calico, que es prácticamente comparable al de Flannel. Por ejemplo,:

Calico para redes en Kubernetes: introducción y algo de experiencia

el proyecto se está desarrollando muy rápidamente, soporta el funcionamiento en soluciones populares de K8s gestionadas, OpenShift, OpenStack, y hay la posibilidad de utilizar Calico al desplegar un clúster mediante kops, se encuentran menciones sobre la construcción de redes Service Mesh (aquí hay un ejemplo de uso conjunto con Istio).

Práctica con Calico

En caso general de uso de Kubernetes vanilla, la instalación de CNI se reduce a utilizar el archivo calico.yaml, descargado desde el sitio oficial, mediante kubectl apply -f.

Por norma general, la versión actual del plugin es compatible con las 2-3 versiones más recientes de Kubernetes: no se prueban ni garantizan su funcionamiento en versiones más antiguas. Según los desarrolladores, Calico funciona en un núcleo de Linux superior a 3.10 bajo CentOS 7, Ubuntu 16 o Debian 8, sobre iptables o IPVS.

Aislamiento dentro del entorno

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

Calico para redes en Kubernetes: introducción y algo de experiencia

En el clúster se desplegaron 2 aplicaciones web: una en Node.js y otra en PHP, una de las cuales utiliza Redis. Para restringir el acceso a Redis desde PHP, manteniendo 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

En esencia, hemos permitido el tráfico entrante en el puerto de Redis desde Node.js. Y no hemos prohibido explícitamente nada más. Una vez que aparece la NetworkPolicy, todos los selectores mencionados en ella comienzan a aislarse, a menos que se indique lo contrario. Además, las reglas de aislamiento no se aplican a otros objetos no cubiertos por el selector.

En el ejemplo se utiliza apiVersion de Kubernetes "de serie", pero nada impide utilizar el recurso homónimo de la entrega de Calico. La sintaxis allí es más elaborada, por lo que será necesario reescribir la regla para el caso descrito anteriormente de la siguiente manera:

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 de NetworkPolicy tradicional contienen construcciones complicadas de recordar y comprender con paréntesis. En el caso de Calico, para invertir la lógica de la regla del firewall, basta con cambiar action: Allow en action: Deny.

Aislamiento por ambientes

Ahora imaginemos una situación en la que la aplicación genera métricas comerciales para su recopilación en Prometheus y análisis posterior a través de Grafana. La salida puede contener datos sensibles que, por defecto, nuevamente están disponibles para todos. Cerraremos estos datos a las miradas ajenas:

Calico para redes en Kubernetes: introducción y algo de experiencia

Prometheus, por lo general, se coloca en un entorno de servicio separado — en este ejemplo será un namespace de este tipo:

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

Campo metadata.labels no fue accidental aquí. Como se mencionó anteriormente, namespaceSelector (al igual que podSelector) opera con etiquetas. Por lo tanto, para permitir la recolección de métricas de todos los pod en un puerto determinado, será necesario agregar alguna etiqueta (o tomar de las existentes), y luego aplicar una configuración como la siguiente:

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 en caso de usar políticas de Calico, la sintaxis será la siguiente:

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, se puede proteger contra intervenciones 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 de "Denegar todo y abrir explícitamente lo necesario", como se establece en documentación oficial (este enfoque es sostenido también por otros - en particular, en el artículo ya mencionado).

Aplicación de objetos adicionales de Calico

Recuerdo que mediante un conjunto ampliado de API, Calico puede regular la accesibilidad de los nodos, sin limitarse a los pod. En el siguiente ejemplo, con GlobalNetworkPolicy se cierra la posibilidad de pasar solicitudes ICMP en el clúster (por ejemplo, pings desde un pod a un nodo, entre pod, o desde un nodo a la IP de un 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 aún pueden "contactarse" entre sí por ICMP. Y este asunto se resuelve con GlobalNetworkPolicy, aplicado 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"]

El caso de VPN

Finalmente, presentaré un ejemplo bastante real del uso de las funciones de Calico en el caso de interacción pericluster, cuando el conjunto estándar de políticas no es suficiente. Para el acceso de los clientes a la aplicación web se utiliza un túnel VPN, y este acceso está estrictamente controlado y limitado a una lista específica de servicios permitidos:

Calico para redes en Kubernetes: introducción y algo de experiencia

Los clientes se conectan a la VPN a través del puerto UDP estándar 1194 y al conectarse reciben rutas a las subredes de los pods y servicios en el clúster. Las subredes se envían en su totalidad para no perder servicios durante reinicios y cambios de direcciones.

El puerto en la configuración es estándar, lo que impone algunas peculiaridades en el proceso de configuración de la aplicación y su traslado al clúster de Kubernetes. Por ejemplo, el LoadBalancer de AWS para UDP apareció literalmente a finales del año pasado en una lista limitada de regiones, y no se puede usar NodePort debido a su redirección en todos los nodos del clúster, lo que hace imposible escalar el número de instancias del servidor para propósitos de alta disponibilidad. Además, será necesario cambiar el rango de puertos seleccionado por defecto…

Como resultado de la revisión de posibles soluciones, se eligió lo siguiente:

  1. Los pods con VPN están planeados para el nodo en modo hostNetwork, es decir, en la IP real.
  2. El servicio se expone externamente a través de ClusterIP. En el nodo se levanta físicamente un puerto que está disponible externamente con algunas salvedades (la condición de tener una dirección IP real).
  3. La definición del nodo en el que se levantó el pod queda fuera de nuestra narrativa. Solo diré que se puede 'fijar' estrictamente el servicio a un nodo o escribir un pequeño servicio sidecar que supervise la dirección IP actual del servicio VPN y ajuste los registros DNS establecidos en los clientes — según la creatividad de cada uno.

Desde el punto de vista del enrutamiento, podemos identificar claramente al cliente detrás de la VPN por su dirección IP, que es emitida por el servidor VPN. A continuación, un ejemplo simple de restricción de acceso para tal cliente a los servicios, ilustrado 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í se prohíbe estrictamente la conexión al puerto 6379, pero se mantiene el funcionamiento del servicio DNS, cuyo funcionamiento a menudo sufre al establecer reglas. Porque, como se mencionó anteriormente, cuando aparece un selector, se aplica una política de denegación por defecto a menos que se indique lo contrario.

Resultados

De esta manera, con la API extendida de Calico, se puede configurar de manera flexible y cambiar dinámicamente el enrutamiento en el clúster y a su alrededor. En términos generales, su uso podría parecer como disparar con un cañón a los gorriones, y la implementación de una red L3 con túneles BGP y IP-IP parece monstruosa en una instalación simple de Kubernetes en una red plana... Sin embargo, por lo demás, la herramienta parece bastante viable y útil.

La aislamiento del clúster para cumplir con los requisitos de seguridad no siempre puede ser implementado, y precisamente en tales casos, Calico (o una solución similar) viene al rescate. Los ejemplos proporcionados en el artículo (con algunas adaptaciones menores) se utilizan en varias instalaciones de nuestros clientes en AWS.

P.D.

También puedes leer en nuestro blog:

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster