¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Daniel Guala 29/02/2024 Cargando comentarios…
Algunos clientes requieren al círculo de QSO una mayor flexibilidad y capacidad de personalización en la conectividad de red de los pods en sus clusters de Kubernetes. Al permitir la asignación de múltiples interfaces de red a un solo Pod, Multus CNI facilita la implementación de escenarios de red complejos en Kubernetes.
En este post haremos un repaso genérico de varios conceptos de redes en Kubernetes y utilizaremos Multus CNI y el NM State Operator para añadir redes adicionales en pods y VMs en OpenShift Virtualization.
El modelo de red de Kubernetes es el conjunto de conceptos, reglas y abstracciones que definen cómo se comunican los contenedores, pods y nodos dentro de un clúster de Kubernetes.
El modelo intenta solucionar los siguientes desafíos de comunicación en K8s a través de los siguientes métodos:
El modelo de red de Kubernetes se implementa a través de los Container Runtimes en cada nodo usando Container Network Interface (CNI) Plugins.
Similar a los CRI y CSI, CNI es un proyecto de la CNCF que consta de una especificación y bibliotecas para escribir Plugins para configurar interfaces de red en contenedores de Linux, junto con una serie de plugins compatibles.
CNI se ocupa únicamente de la conectividad de red de los contenedores y de la creación, modificación, lectura o eliminación de los recursos asignados cuando se crea o elimina el contenedor. Debido a este enfoque, CNI tiene una amplia gama de soporte y la especificación es fácil de implementar.
El plugin CNI Primario o por defecto es el plugin principal que gestiona las redes en nuestros clusters de K8s.
Hay múltiples Plugins CNI con diferentes implementaciones, pero todos los plugins principales o primarios deben resolver la comunicación pod a pod cumpliendo los siguientes requisitos de Kubernetes:
Una forma de agruparlos es en base a la integración nativa con Clouds, obteniendo así 3 grupos:
A continuación podemos ver ejemplos de esta agrupación:
Una vez tenemos los principales requisitos cubiertos, observamos que no todos los plugins CNI cubren todas las necesidades y por ello tenemos una gran variedad de soluciones.
Se puede tener una combinación de diferentes CNI para diferentes funciones, como Calico para network policies y Flannel para la gestión de redes de los pods.
No se pueden tener dos CNI diferentes que realicen exactamente la misma funcionalidad en el mismo clúster simultáneamente.
Una funcionalidad muy interesante es, por ejemplo, que un pod pueda comunicarse directamente con una red externa. Aquí es donde Multus CNI entra en escena 😀.
Con Multus CNI, es posible configurar redes adicionales junto con la red de pods predeterminada, añadiendo interfaces de red a los pods en otras redes distintas a la red principal de pods.
El plugin Multus CNI actúa como un meta plugin al llamar a otros plugins CNI para funcionalidades de red avanzadas.
En OpenShift se proporcionan los siguientes plugins CNI para que Multus CNI los utilice cuando necesitemos añadir más redes a pods o máquinas virtuales en OpenShift Virtualization:
El plugin de Multus CNI se configura a través de NetworkAttachmentDefinition (net-attach-def o NAD) CRD. Se puede configurar en:
En la demo de este post utilizaremos además de Multus CNI los siguientes Operadores.
El NMState Operator gestiona configuración de red de nodos declarativamente a través de la API de Kubernetes vía CRD.
Los 3 principales son:
El kubernetes-nmstate-operator se ha incluido como componente en OCP Virtualization hasta la versión 4.10 de OCP. A partir de esta versión hay que instalar el Operador por separado.
En otros posts del blog de Paradigma ya os hemos comentado sobre OpenShift Virtualization y la necesidad de escenarios intermedios de migración a cloud native en la que convivan cargas de trabajo virtualizadas y en contenedores. Este Operador permite alojar y gestionar workloads virtualizados en la misma plataforma que los workloads basados en contenedores.
La tecnología detrás de OpenShift Virtualization se desarrolla en la comunidad OpenSource de KubeVirt.
Llegados a este punto tendrás varias preguntas como por qué te he contado todo esto 😛.
Todo está relacionado: Con el NMState Operator podemos gestionar declarativamente interfaces de red físicas en los nodos y crear recursos (bridges) para que a través de Multus CNI añadamos redes adicionales a pods o VM de OpenShift Virtualization.
Otra pregunta que te habrá surgido es por qué los clientes pueden querer todo este lío de CRDs.
Hemos visto, entre otros, los siguientes casos de uso:
En esta demo:
Nos quedaría algo así:
Vamos a ver el estado actual de la configuración de red del nodo y vemos que no hay ninguna referencia al bridge br1 que queremos crear. En el siguiente Yaml vemos la información relevante del NNS (hay información omitida sobre routes, dns, etc.):
apiVersion: nmstate.io/v1beta1
kind: NodeNetworkState
metadata:
name: ip-10-0-179-230.eu-west-1.compute.internal
status:
currentState:
interfaces:
- ipv4:
address:
- ip: 10.0.179.230
prefix-length: 17
auto-dns: true
auto-gateway: true
auto-route-table-id: 0
auto-routes: true
dhcp: true
enabled: true
lldp:
enabled: false
mac-address: 0A:FA:D4:3D:B0:A7
mtu: 9001
name: ens6
state: up
type: ethernet
- ipv4:
address: []
enabled: false
ipv6:
address: []
enabled: false
lldp:
enabled: false
mac-address: 0A:DB:EC:A1:B5:DF
mtu: 1500
name: eth1
state: down
type: ethernet
- ipv4:
address:
- ip: 192.168.0.60
prefix-length: 24
auto-dns: true
auto-gateway: true
auto-route-table-id: 0
auto-routes: true
dhcp: true
enabled: true
lldp:
enabled: false
mac-address: 0A:AE:69:A1:16:FF
mtu: 9001
name: eth2
state: up
type: ethernet
- ipv4:
address:
- ip: 127.0.0.1
prefix-length: 8
enabled: true
ipv6:
address:
- ip: ::1
prefix-length: 128
enabled: true
mac-address: "00:00:00:00:00:00"
mtu: 65536
name: lo
state: up
type: unknown
Ahora vamos a utilizar una NodeNetworkConfigurationPolicy para levantar un bridge llamado br1 sobre la eth2.
# br1-eth2-policy.yaml
apiVersion: nmstate.io/v1
kind: NodeNetworkConfigurationPolicy
metadata:
name: br1-eth2-policy
spec:
nodeSelector:
external-network: "true"
desiredState:
interfaces:
- name: br1
description: Linux bridge with eth2 as a port
type: linux-bridge
state: up
ipv4:
dhcp: true
enabled: true
bridge:
options:
stp:
enabled: false
Port:
- name: eth2
Pero antes de crear el manifiesto anterior, como se puede observar, la política se asocia a nodos a través de un nodeSelector, por lo que vamos a etiquetar nuestro nodo antes de crear la política.
Para ello, ejecutamos:
oc label node external-network='true' -l node-role.kubernetes.io/worker=''
Y ahora creamos el objeto con el manifiesto anterior a través de un fichero o por la interfaz web.
oc create -f br1-eth2-policy.yaml
Una vez creada podemos ver el estado de la política con en siguiente comando:
$ oc get nncp
NAME STATUS
br1-eth2-policy progressing
Y ver el estado de la NodeNetworkConfigurationEnactment creada automáticamente por la política con el comando:
$ oc get nnce
NAME STATUS
ip-10-0-179-230.eu-west-1.compute.internal.br1-eth2-policy Available
Deberemos esperar hasta que ambos objetos estén en status available.
Una vez los objetos anteriores están disponibles podemos mirar el NNS y ver si se ha creado el nuevo bridge:
apiVersion: nmstate.io/v1beta1
kind: NodeNetworkState
metadata:
name: ip-10-0-179-230.eu-west-1.compute.internal
status:
currentState:
interfaces:
- bridge:
options:
group-addr: 01:80:C2:00:00:00
group-forward-mask: 0
hash-max: 4096
mac-ageing-time: 300
multicast-last-member-count: 2
multicast-last-member-interval: 100
multicast-querier: false
multicast-querier-interval: 25500
multicast-query-interval: 12500
multicast-query-response-interval: 1000
multicast-query-use-ifaddr: false
multicast-router: 1
multicast-snooping: true
multicast-startup-query-count: 2
multicast-startup-query-interval: 3125
stp:
enabled: false
forward-delay: 15
hello-time: 2
max-age: 20
priority: 32768
port:
- name: eth2
stp-hairpin-mode: false
stp-path-cost: 100
stp-priority: 32
vlan:
enable-native: false
mode: trunk
trunk-tags:
- id-range:
max: 4094
min: 2
description: Linux bridge with eth2 as a port
ipv4:
address:
- ip: 192.168.0.60
prefix-length: 24
auto-dns: true
auto-gateway: true
auto-route-table-id: 0
auto-routes: true
dhcp: true
enabled: true
lldp:
enabled: false
mac-address: 0A:AE:69:A1:16:FF
mtu: 9001
name: br1
state: up
type: linux-bridge
- ipv4:
address:
- ip: 10.0.179.230
prefix-length: 17
auto-dns: true
auto-gateway: true
auto-route-table-id: 0
auto-routes: true
dhcp: true
enabled: true
lldp:
enabled: false
mac-address: 0A:FA:D4:3D:B0:A7
mtu: 9001
name: ens6
state: up
type: ethernet
- ipv4:
address: []
dhcp: false
enabled: false
ipv6:
address: []
autoconf: false
dhcp: false
enabled: false
lldp:
enabled: false
mac-address: 0A:AE:69:A1:16:FF
mtu: 1500
name: eth2
state: up
type: ethernet
- ipv4:
address:
- ip: 127.0.0.1
prefix-length: 8
enabled: true
ipv6:
address:
- ip: ::1
prefix-length: 128
enabled: true
mac-address: "00:00:00:00:00:00"
mtu: 65536
name: lo
state: up
type: unknown
Una vez confirmado el bridge, debemos ver si el nodo de Kubernetes detecta este bridge como disponible para ser utilizado. Para ello ejecutamos el siguiente comando:
$ oc get no -o json | jq '.items[].status.allocatable'
{
"attachable-volumes-aws-ebs": "25",
"bridge.network.kubevirt.io/br1": "1k",
"cpu": "71500m",
"devices.kubevirt.io/kvm": "1k",
"devices.kubevirt.io/sev": "0",
"devices.kubevirt.io/tun": "1k",
"devices.kubevirt.io/vhost-net": "1k",
"ephemeral-storage": "96143180846",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "196537932Ki",
"ovs-cni.network.kubevirt.io/br0": "1k",
"pods": "250"
}
Vemos como el bridge que acabamos de crear está disponible en la línea "bridge.network.kubevirt.io/br1": "1k". Deberemos guardar esta referencia para utilizar posteriormente con la networkAttachmentDefinition.
Utilizando la referencia anterior podríamos crear una network attachment definition de la siguiente manera:
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
annotations:
k8s.v1.cni.cncf.io/resourceName: bridge.network.kubevirt.io/br1
name: br1-eth2-net
namespace: default
spec:
config: >-
{"name":"br1-eth2-net","type":"cnv-bridge","cniVersion":"0.3.1","bridge":"br1","macspoofchk":false,"ipam":{}}
Si la creamos así, deberemos gestionar la configuración de IPs separadamente.
Con las network attachment definitions, es posible configurar que las interfaces añadidas soliciten IP vía DHCP si tuviésemos configurado un servidor DHCP en la red adicional usando:
{"name":"br1-eth2-net","type":"cnv-bridge","cniVersion":"0.3.1","bridge":"br1","macspoofchk":false,"ipam":{"type" : "dhcp"}}
O incluso añadir IPs estáticas, gateways, routes, etc.:
{"name":"br1-eth2-net","type":"cnv-bridge","cniVersion":"0.3.1","bridge":"br1","macspoofchk":false,"ipam":{"type": "static", "addresses": [ { "address": "192.168.0.61/26" }] }}
Deberemos tener esto en cuenta a la hora de configurar las IPs en VMs y Pods.
Como no tenemos disponible un DHCP en la red adicional y para demostrar las capacidades de las net-attach-def, crearemos 2 objetos.
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
annotations:
k8s.v1.cni.cncf.io/resourceName: bridge.network.kubevirt.io/br1
name: net-br1-pod
spec:
config: >-
{"name":"net-br1-pod","type":"cnv-bridge","cniVersion":"0.3.1","bridge":"br1","macspoofchk":true,"ipam":{"type":"static","addresses":
[ { "address": "192.168.0.61/24" }] }}
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
annotations:
k8s.v1.cni.cncf.io/resourceName: bridge.network.kubevirt.io/br1
name: net-br1-vm
spec:
config: >-
{"name":"net-br1-pod","type":"cnv-bridge","cniVersion":"0.3.1","bridge":"br1","macspoofchk":true,"ipam":{"type":"static","addresses":
[ { "address": "192.168.0.62/24" }] }}
Recordemos: si tuviésemos un DHCP, bastaría con adjuntar pod y vm a la misma net-attach-def indicando que el IPAM es de tipo DHCP.
Es la hora de añadir la red que hemos configurado a VMs y Pods.
Para añadir una red adicional en una VM, debemos asociar la net-attach-def que creamos antes con una nueva interfaz de red en una Máquina Virtual apagada de OpenShift Virtualization.
Podemos hacerlo a través del manifiesto de la VM o de la consola web, en este caso usaremos la consola.
Una vez añadida, deberíamos configurar qué IP queremos asignar a la VM, si no se ha hecho a través de la net-attach-def.
Como la hemos hecho a través de la NAD no hay más que hacer, pero podríamos configurar la IP de la VM usando cloud-init modificando el manifiesto de la VM.
kind: VirtualMachine
spec:
# ...
template:
# ...
spec:
volumes:
- cloudInitNoCloud:
networkData: |
version: 2
ethernets:
eth1:
addresses:
- 10.10.10.14/24
O gestionando, por ejemplo, a través de ConfigMaps o cloud-init que exista un fichero de este estilo en la ruta /etc/sysconfig/network-scripts/ifcfg-eth1.
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=192.168.0.62
PREFIX=26
DEFROUTE=no
IPV4_FAILURE_FATAL=no
IPV6_DISABLED=yes
IPV6INIT=no
NAME=eth1
UUID=bcc1d1f7-93c1-3029-9183-260631483bdd
DEVICE=eth1
ONBOOT=yes
AUTOCONNECT_PRIORITY=-999
HWADDR=00:00:00:00:00:02
Confirmamos que la VM tiene la IP que deseamos:
Para crear un pod con una IP en una red adicional, en este caso al haber usado la NAD para gestionar la IP es tan simple como crear el siguiente manifiesto:
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: '[{
"name": "net-br1-pod",
"default-route": ["192.168.0.2"]
}]'
spec:
containers:
- name: samplepod
command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
image: alpine
Lo confirmamos entrando en el pod y viendo las interfaces de red y viendo que además de la red de pods existe otra interfaz con la ip que definimos en la NAD:
Comprobaremos que podemos alcanzar recursos en la nueva red usando simplemente ping.
Ping desde el Pod a la VM:
Ping desde la VM al pod:
Añadiendo redes adicionales a la red de Pods en Kubernetes, se abre un abanico de posibilidades para solventar requerimientos y desafíos complejos en nuestros clusters. En estos casos, Multus CNI es nuestro mejor aliado.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.