Container Networks – CNI (Kubernetes)

En el articulo anterior se mencionó como Docker realizo cambios en su arquitectura para delegar responsabilidades del motor de ejecución. Este articulo abordará la segunda propuesta de Container Networks que actualmente es utilizada por Kubernetes para la administración de controladores de red.

Container Network Interface (CNI)

CNI

Es una especificación neutral propuesta por CoreOS y que ha sido adoptada por proyectos como Apache Mesos, Cloud Foundry, Kubernetes y rkt.

Los plugins son implementaciones de las acciones definidas por la especificación del CNI y que son consumidas por el motor de ejecución. Los binarios de los plugins necesitan al menos el permiso de CAP_NET_ADMIN para ser ejecutados correctamente. La acción a ejecutar por el binario es especificada mediante la variable de entorno CNI_COMMAND y esta acción solo puede ser una alguna de las siguientes opciones:

  • ADD: La cual permite agregar una interfaz de red a un contenedor especifico. Esta acción puede ser ejecutada múltiples veces para proporcionar varias interfaces de red a un solo contenedor.
  • DEL: Remueve una interfaz de red especifica a un contenedor.
  • CHECK: Se utiliza por el motor de ejecución para validar que la configuración de red se mantiene según lo solicitado.
  • VERSION: Imprime la versión de CNI soportada por el plugin.

Flujo de asignación de red en Kubernetes

Kubernetes utiliza el concepto de Pod para referirse a un conjunto de uno o mas contenedores que comparten una misma dirección IP. Esta dirección IP permite la comunicación de los contenedores hacia el exterior lo cual resulta importante comprender el proceso de su asignación.

Kubelet es el componente responsable del flujo en la creación del Pod, a continuación tratare de explicar la serie de pasos realizados por el componente de Dockershim en la creacion del Pod y asignación de dirección IP. Cabe señalar que Dockershim sera eliminado en la version 1.23 de Kubernetes, pero debido a la simplicidad de su método RunPodSandbox nos permite conocer mejor el flujo.

  1. Se asegura de tener la imagen del pause container. Este contenedor nos sirve como contenedor padre de los contenedores definidos en el pod y tiene dos funciones primordiales:
    1. Como la base para compartir Linux namespaces dentro del pod.
    2. Como PID 1 para cada pod.
  2. Crea la configuración requerida por el pause container, llamado la API de Docker.
  3. Crea un checkpoint del pause container en /var/lib/dockershim/sandbox a traves del administrador de Checkpoints.
  4. Inicia el pause container, nuevamente consumiendo la API de Docker.
  5. Configura las interfaces red (loopback y default) para el pause container. Esto lo realiza a traves de una serie de llamadas a los binarios de CNI utilizando su acción ADD.

Como puede observarse, la configuración de red del pause container no se realiza a traves del API de Docker sino a traves del CNI. Estos CNIs utilizan archivos de configuración en formato JSON, para definir entradas y salidas de datos esperados. Múltiples CNI plugins pueden ser ejecutados por el motor de ejecución.

Container Networks – CNM

La arquitectura de Docker separó (a partir de la versión 1.6) la creación de Contenedores de la configuración de la red para beneficiar la movilidad de las aplicaciones. En este articulo analizaremos a detalle uno de los estándares propuestos para esta separación.

Container Network Model (CNM)

Es la especificación propuesta por Docker. La biblioteca de libnetwork ofrece una implementación escrita en lenguaje Go. Actualmente proyectos como Cisco Contiv, Kuryr y Weave Net soportan dicha implementación.

Este modelo define tres conceptos principales:

  • Sandbox: El cual almacena la configuración de la red. Esta configuración incluye la administración de las interfaces de rede de los contenedores, la lista de rutas de tráfico y los valores de configuración para el DNS. Una implementación de un Sandbox puede ser Linux Network Namespace.
  • Endpoint: Permite la conexión de un Sandbox con la red. Una implementación de un Endpoint puede ser veth pair.
  • Network: Es un conjunto de Endpoints que se pueden comunicar entre si. Una implementación de un Network puede ser Linux bridge.

Libnetwork utiliza los siguientes objetos en su implementación.

  • NetworkController provee un punto de entrada a la biblioteca a traves de una API simple para asignar y manejar redes.
  • Driver realiza la implementación de la red. Los drivers pueden ser de dos tipos inbuilt (como Bridge, Host, None y Overlay) o remote.
  • Network es la implementación del componente de Network definido anteriormente. El Driver es notificado cada vez que un Network es creado o actualizado.
  • Endpoint ofrece conectividad para los servicios. Un Endpoint solo puede ser adjuntado a un Network.
  • Sandbox representa la configuración de la red como la dirección IP, la dirección MAC, las rutas de tráfico de red y las entradas del DNS. Un Sandbox puede tener varios Endpoints conectados a diferentes Networks.

El siguiente código en Go muestra la creacion de la red network1 utilizando el driver bridge para mas tarde conectar el contenedor container1 a traves del endpoint Endpoint1.

import (
	"fmt"
	"log"

	"github.com/docker/docker/pkg/reexec"
	"github.com/docker/libnetwork"
	"github.com/docker/libnetwork/config"
	"github.com/docker/libnetwork/netlabel"
	"github.com/docker/libnetwork/options"
)

func main() {
	if reexec.Init() {
		return
	}

	// Select and configure the network driver
	networkType := "bridge"

	// Create a new controller instance
	driverOptions := options.Generic{}
	genericOption := make(map[string]interface{})
	genericOption[netlabel.GenericData] = driverOptions
	controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
	if err != nil {
		log.Fatalf("libnetwork.New: %s", err)
	}

	// Create a network for containers to join.
	// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
	network, err := controller.NewNetwork(networkType, "network1", "")
	if err != nil {
		log.Fatalf("controller.NewNetwork: %s", err)
	}

	// For each new container: allocate IP and interfaces. The returned network
	// settings will be used for container infos (inspect and such), as well as
	// iptables rules for port publishing. This info is contained or accessible
	// from the returned endpoint.
	ep, err := network.CreateEndpoint("Endpoint1")
	if err != nil {
		log.Fatalf("network.CreateEndpoint: %s", err)
	}

	// Create the sandbox for the container.
	// NewSandbox accepts Variadic optional arguments which libnetwork can use.
	sbx, err := controller.NewSandbox("container1",
		libnetwork.OptionHostname("test"),
		libnetwork.OptionDomainname("docker.io"))
	if err != nil {
		log.Fatalf("controller.NewSandbox: %s", err)
	}

	// A sandbox can join the endpoint via the join api.
	err = ep.Join(sbx)
	if err != nil {
		log.Fatalf("ep.Join: %s", err)
	}

	// libnetwork client can check the endpoint's operational data via the Info() API
	epInfo, err := ep.DriverInfo()
	if err != nil {
		log.Fatalf("ep.DriverInfo: %s", err)
	}

	macAddress, ok := epInfo[netlabel.MacAddress]
	if !ok {
		log.Fatalf("failed to get mac address from endpoint info")
	}

	fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key())
}

Esta propuesta ofrece una gran versatilidad para la creación y administración de las redes de los contenedores, sin embargo el grupo de desarrolladores de Kubernetes no escogió este modelo de red. En su sitio oficial se explica las razones técnicas de dicha selección.

El driver MAC-VLAN

En el articulo anterior revisamos las redes que son creadas por defecto por el servicio de Docker y como el driver bridge agrega un linux bridge entre el contenedor y la interfaz de red del servidor, además de utilizar un veth pair para conectar el contenedor con dicho bridge.

El siguiente bloque de código muestra la relación existente entre contenedor y el servidor donde se ejecuta, se puede observar que la interfaz de red del contenedor eth0@if23 hace referencia a la interfaz numero 23 del servidor que es vethe752504.

$ docker run --detach --name container ubuntu:18.04 sleep infinity
c719a954130f4769afeff28654800e8d7593b14442cfb36bcbd0ed0eade28b83
$ docker exec -ti container ip addr show eth0
22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:50:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.80.0.2/24 brd 172.80.0.255 scope global eth0
       valid_lft forever preferred_lft forever
$ ip link show type veth
23: vethe752504@if22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether f6:5d:14:36:58:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242f6f9457f       no              vethe752504

Aunque utilizar un linux bridge resulta útil para la mayoría de los casos, existen aplicaciones las cuales requieren ser conectadas directamente a la red física del servidor donde son ejecutadas.

Existen dos tipos de drivers (macvlan y ipvlan) los cuales ofrecen una conexión más directa. Estos drivers reducen la latencia de red debido a que la ruta de tráfico de los paquetes de red es más corta. En este artículo revisamos a detalle el driver de MACVLAN o MAC-VLAN.

Definición

El driver MAC-VLAN opera en la capa de enlace de datos del modelo OSI y permite conectar múltiples subinterfaces de contenedores o máquinas virtuales a una sola interfaz física. Cada subinterfaz posee una dirección MAC (generada aleatoriamente) y por consiguiente una dirección IP.

Existen cuatro tipos de MAC-VLAN:

  • Privada: Donde todas las tramas de red son reenviadas por medio de la interfaz de red principal. No es posible una comunicación entre subinterfaces en el mismo servidor.
  • Virtual Ethernet Port Aggregator: Requiere un switch que tenga soporte IEEE 802.1Qbg el cual permite una comunicación entre subinterfaces en el mismo servidor. Las tramas de red son enviadas a través de la interfaz de red principal lo cual permite que puedan ser aplicadas políticas de red externas.
  • Bridge: Conecta todas las subinterfaces con un bridge lo cual permite que las tramas de red sean enviadas directamente sin salir del servidor.
  • Passthru: Permite conectar una sola subinterfaz a la interfaz de red principal.

Laboratorio

Para el caso de Docker, este servicio utiliza el tipo Bridge en el driver MAC-VLAN, lo que permite una comunicación externa e intercomunicación entre contenedores. El siguiente ejemplo muestra las instrucciones para crear una red de tipo MAC-VLAN en Docker y la creación de dos contenedores dentro de esa misma red.

$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:b0:b7:a2 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0
       valid_lft 85773sec preferred_lft 85773sec
$ docker network create --driver macvlan --subnet=10.0.2.0/24  --gateway=10.0.2.2 --opt parent=eth0 macvlan0
d92d3cf366628e962a0bbd4b922d6ca7b2c50e9dc69c5219a2dbd398ae32d923
$ docker run --detach --name container --network macvlan0 busybox sleep infinity
aa915e2015629361306c80b2e508f94b6855857b14cfe6ea397c151250f12cb2
$ docker run --detach --name container2 --network macvlan0 busybox sleep infinity
dff856cb2457b27bfd2f8e5bef44eada3f63bdc4aa8b4e3e8e3dd2ed52cd53d4

Las siguientes instrucciones muestran las direcciones IP asignadas a los contenedores que fueron creados recientemente.

$ docker exec -ti container ip addr show eth0
13: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:0a:00:02:01 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.1/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
$ docker exec -ti container2 ip addr show eth0
14: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:0a:00:02:03 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.3/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever

Una vez obtenidas las direcciones IP de los dos contenedores, es posible validar distintos tipos de comunicación.

Las siguientes instrucciones muestran la comunicación de este a oeste, donde se observa que los contenedores pueden comunicarse entre si.

$ docker exec -ti container ping -c 3 10.0.2.3
PING 10.0.2.3 (10.0.2.3): 56 data bytes
64 bytes from 10.0.2.3: seq=0 ttl=64 time=0.081 ms
64 bytes from 10.0.2.3: seq=1 ttl=64 time=0.083 ms
64 bytes from 10.0.2.3: seq=2 ttl=64 time=0.059 ms

--- 10.0.2.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.059/0.074/0.083 ms

La comunicación de sur a norte tiene algunas limitantes. Podemos observar que no es posible la comunicación directa con la dirección IP de la interfaz de red principal.

$ docker exec -ti container ping -c 3 10.0.2.15
PING 10.0.2.15 (10.0.2.15): 56 data bytes

--- 10.0.2.15 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

Sin embargo, es posible la comunicación directa con la dirección IP de la interfaz del Gateway.

$ docker exec -ti container ping -c 3 10.0.2.2
PING 10.0.2.2 (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=1 ttl=64 time=0.285 ms
64 bytes from 10.0.2.2: seq=2 ttl=64 time=0.233 ms

--- 10.0.2.2 ping statistics ---
3 packets transmitted, 2 packets received, 33% packet loss
round-trip min/avg/max = 0.233/0.259/0.285 ms

Tipos de red en Docker

Últimamente se habla mucho sobre contenedores, subrayando los beneficios de su uso para el desarrollo y despliegue de aplicaciones, y en ocasiones se hacen comparaciones erróneas con las Máquinas Virtuales. Este articulo pretende cubrir las opciones de red locales que ofrece Docker al crear contenedores.

Cuando el servicio de Docker inicia este crea localmente tres redes que ofrecen distintas capacidades.

$ docker network list
NETWORK ID          NAME                DRIVER              SCOPE
d1cd6bdefdb7        bridge              bridge              local
e14adf7f1918        host                host                local
9c7b12187b80        none                null                local

Se puede observar los distintos tipos de red local en la columna de DRIVER.

  • Bridge: Valor por defecto. Se utiliza cuando las aplicaciones son ejecutadas en contenedores independientes que necesitan comunicarse entre ellos.
  • Host: Valor usado en contenedores independientes donde se busca compartir los servicios de red ofrecidos por el servidor donde el contenedor fue creado.
  • Null: Deshabilita los servicios de red para dicho contenedor.

Host network

Este tipo de red comparte los servicios de red del equipo donde se ejecuta.  Además de ofrecer las siguientes capacidades:

  1. Las interfaces de red del contenedor son idénticas a las de la máquina donde se ejecuta.
  2. Solo existe una red de tipo host por máquina.
  3. Es necesario especificar el tipo de red como argumento «–net=host».
  4. Ni container linking ni port mapping son soportados.
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:92:e3:8c brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
$ docker run --net=host bash:5.0.17 ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:92:e3:8c brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever

None network

Es posible crear un contenedor sin algún tipo de red asignada a el. Este contenedor es solo accesible a traves del shell del servidor donde fue creado.

Estos contenedores pueden ser útiles para la ejecución de tareas por lotes que no requieren conectividad hacia o desde ellos.

$ docker run --net=none bash:5.0.17 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

Bridge network

Permite la creación de múltiples redes en el mismo servidor ofreciendo una separación lógica de la red, es decir, solo contenedores conectados a la misma red se pueden comunicar entre si.

$ docker run --net=bridge bash:5.0.17 ip addr show eth0
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:50:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.80.0.3/24 brd 172.80.0.255 scope global eth0
       valid_lft forever preferred_lft forever

La red predeterminada creada por el servicio Docker al momento de iniciar es llamada bridge y crea un bridge llamado docker0 con los valores de una subnet en el rango de 172.80.0.0/24.

$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242eaa3ec7c       no

Todos los contenedores creados en este servidor donde no se especifique su red serán conectados a esta red.

OPNFV y CNTT

Network Function Virtualization (NFV) representa el cambio mas significante en las redes de comunicación global en los últimos 30 años.  Es por ello que resulta esencial adoptar una estrategia donde se maximice la flexibilidad, agilidad e interoperabilidad de la red, aprovechando la rápida innovación del ecosistema.

OPNFV

La Plataforma Abierta para NFV o por sus siglas en ingles OPNFV,  es la comunidad de la Linux Foundation que provee herramientas para realizar una validación de proyectos upstream a traves de un proceso de integración continua. Cuenta con un programa de verificación de equipos que permite acelerar la transformación de las redes de proveedores de servicios y empresas.

OPNFV facilita la implementación y desarrollo de la tecnología de NFV permitiendo su amplia adopción.

Para entender el alcance de los proyectos upstream que OPNFV valida e integra, es necesario conocer las distintas partes que componen el modelo que el Instituto Europeo de Normas de Telecomunicaciones (ETSI) publicó en su especificación ETSI GS NFV-INF 001 V1.1.1 (2015-01) donde se representa la arquitectura de su infraestructura de la siguiente manera:


A continuación se definen algunos conceptos:

  • Network Function (NF): Es un bloque funcional dentro de una infraestructura de red en el cual tiene bien definido sus interfaces externas y su comportamiento funcional.
  • Physical Network Function (PNF): Refiere a un dispositivo de hardware que ofrece un propósito de red especifico o un Network Function.
  • Virtualised Network Function (VNF): Es una implementación de una Network Function en una o varias Máquinas Virtuales.
  • Cloud-Native Network Function (CNF): Es considerado como la siguiente generación de VNFs donde se utiliza paradigmas de desarrollo para la Nube en la implementación de Network Function.
  • Network Functions Virtualisation Infrastructure (NFVI): El conjunto de los componentes de hardware y software en el cual se crea un ambiente para desplegar VNFs y/o CNFs.
  • Virtualised Infrastructure Manager (VIM): Es responsable de controlar y administrar la infraestructura para NFV (NFVI), manteniendo un inventario de los recursos virtuales asignados a los recursos físicos.

CNTT

El desarrollo y despliegue de aplicaciones VNFs y/o CNFs requiere retos técnicos significantes que sumado a la proliferación de nuevas tecnologías afectan drásticamente las estrategias operacionales de cualquier negocio.

Es por eso que a principios del 2019 eso fue creado el comité de Cloud iNfrastructure Telco Taskforce (CNTT) el cual es responsable de la creación y documentación de un marco de trabajo común para la Infraestructura de la Nube.

El CNTT ofrece cuatro niveles de documentación necesarios para la descripción de componentes y su correcta aplicación:

  1. Modelos de Referencia: Enfocado en la abstracción de la Infraestructura y como los recursos y servicios son expuestos a las VNFs y/o CNFs.
  2. Arquitectura de Referencia: Define los componentes y propiedades de la Infraestructura en los cuales VNFs y/o CNFs son ejecutados, desplegados y diseñados.
  3. Implementación de Referencia: Es construido con base en los requerimientos y especificaciones de los Modelos y Arquitectura de Referencia agregando detalles sobre su implementacion.
  4. Consenso de Referencia: Verifica, prueba y certifica los requerimientos y especificaciones desarrolladas en otros documentos.

A continuación se muestra los diferentes tipos de elementos de una plataforma típica de la Nube.

CNTT Scope

 

Instalación de servicios OpenStack

OpenStack Kolla

A traves de los artículos anteriores realizamos la creación un repositorio para el almacenamiento de las imágenes Docker utilizadas por los servicios de OpenStack para así mas tarde configurar las herramientas y los nodos que almacenaran dichos servicios. Es por ello que solo resta realizar el último paso que es el proceso de Instalación de servicios de OpenStack. Este proceso consta de las siguientes etapas:

  1. bootstrap-servers: Inicializa los nodos destino con la instalación de dependencias necesarias por el proceso de Instalación de servicios de OpenStack. Entre las tareas que este proceso realiza se encuentra:
    • La configuración del archivo /etc/hosts.
    • Asegura que los grupos sudo y kolla existan en cada uno de los nodos.
    • La instalación de dependencias, certificados y claves GPG
    • Configuración del cortafuegos.
    • Instalación y configuración del servicio de Docker.
    • Sincronización los relojes.
  2. prechecks: Realiza una validación de requerimientos necesarios por el proceso de Instalación. Donde principalmente se valida que los puertos a ser usados por los servicios habilitados se encuentren disponibles.
  3. pull: Descarga de las imágenes de los servicios de OpenStack basándose en los roles que fueron definidos para cada nodo.
  4. deploy: Despliegue de servicios. Durante este proceso se realiza las siguientes tareas para cada servicio:
    • Creación de base de datos y tablas.
    • Creación y registro de usuarios.
    • Configuración de políticas de acceso.
  5. check: Realiza una validación a traves de tareas de sanity checks donde se determina el correcto funcionamiento de algunos servicios.
  6. post-deploy: Genera el archivo openrc para el usuario admin.

Estas etapas son traducidas en argumentos utilizados por la herramienta de kolla-ansible(la cual hemos instalado y configurado previamente). De manera que nuestro proceso puede ser realizado en una iteración de los distintos argumentos mediante ciclo.

for action in bootstrap-servers prechecks pull deploy check post-deploy; do
    sudo kolla-ansible -vvv -i $inventory_file $action -e 'ansible_user=kolla' -e 'ansible_become=true' -e 'ansible_become_method=sudo' | tee $action.log
done

Nota adicional: El repositorio de GitHub ha sido actualizado para incorporar las instrucciones necesarias para realizar un Instalación de servicios OpenStack para el release de Stein.

Configuración previa a la Instalación de OpenStack

En el articulo anterior creamos un servidor el cual administra las imágenes Docker que son utilizadas durante el proceso de instalación de OpenStack. A lo largo de este articulo sera explicado el proceso de configuración previa a la instalación de OpenStack.

Soluciones de Infraestructura como Servicio como lo es OpenStack, buscan maximizar el uso de los recursos del centro de datos, por lo cual se recomienda habilitar varios módulos del kernel que nos ayuden a mejorar el desempeño en cada uno de los equipos.

La virtualización anidada es la capacidad que permite a los usuarios la creación de máquinas virtuales dentro de una máquina virtual. El proceso de habilitación de esta capacidad depende de la arquitectura del procesador que se este utilizado, el comando lscpu nos proporciona la información acerca del CPU.

$ lscpu 
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                88
On-line CPU(s) list:   0-87
Thread(s) per core:    2
Core(s) per socket:    22
Socket(s):             2
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
Stepping:              1
CPU MHz:               1578.328
CPU max MHz:           3600.0000
CPU min MHz:           1200.0000
BogoMIPS:              4391.44
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              56320K
NUMA node0 CPU(s):     0-87
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb invpcid_single intel_pt ssbd ibrs ibpb stibp kaiser tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts flush_l1d

En este caso la arquitectura del procesador del servidor utilizado es x86, por lo que las instrucciones para habilitar son:

$ rmmod kvm-intel
$ echo 'options kvm-intel nested=y' >> /etc/modprobe.d/dist.conf
$ modprobe kvm-intel

Otro modulo del kernel que nos mejora el desempeño es vhost_net. De acuerdo a este articulo de RedHat, este modulo ayuda a reducir las operaciones de escritura, latencia y uso de CPU y se habilita a traves de la instrucción:

$ modprobe vhost_net

vhost_net

En este punto nuestros servidores o nodos destino han sido configurados para obtener un mejor desempeño. Ahora es momento de proceder con la instalación de la herramienta Kolla-Ansible y sus respectivas dependencias.

Todas instrucciones que se mostrarán a continuación pueden ser ejecutadas desde cualquier máquina que tenga conectividad a los nodos destino. En nuestro diagrama de ejemplo el servidor con nombre de undercloud mantiene conexión con el resto de nodos destino a traves de la red de administración con CIDR de «192.168.121.0/27».

Diagrama de ejemplo

Esta máquina utilizará kolla-ansible para realizar la Instalación de OpenStack. Este programa puede ser instalado de distintas formas, sin embargo en este caso sera instalado desde su código fuente.

$ wget http://tarballs.openstack.org/kolla-ansible/kolla-ansible-stable-rocky.tar.gz
$ tar -C /opt -xzf kolla-ansible-stable-rocky.tar.gz
$ mv /opt/kolla-*/ /opt/kolla-ansible
$ sudo cp /opt/kolla-ansible/etc/kolla/passwords.yml /etc/kolla/
$ pip install /opt/kolla-ansible

Como ultimo paso previo a la instalación de OpenStack, se necesita un archivo de configuración el cual especifique los detalles de la instalación (versión de OpenStack a instalar, dirección IP del VIP, dirección IP del repositorio de imágenes, habilitación de servicios, etc.). Estos son algunos de los valores utilizados en el archivo globals.yml:

openstack_release: "7.0.0"
kolla_internal_vip_address: "10.10.13.3"
docker_registry: "10.10.13.2:5000"
network_interface: "eth0"
kolla_external_vip_interface: "eth1"
api_interface: "{{ kolla_external_vip_interface }}"
storage_interface: "eth2"
cluster_interface: "eth2"
tunnel_interface: "eth1"
neutron_external_interface: "eth2"
neutron_plugin_agent: "opendaylight"

En este punto se cuenta con la configuración previa a la instalación de servicios de OpenStack. El siguiente articulo proporcionará los pasos necesarios para realizar el despliegue de los servicios de OpenStack en una configuración de Alta Disponibilidad.

Nota: El script undercloud.sh contiene las instrucciones mencionadas en este articulo

Creación de Imágenes de OpenStack usando Kolla

En el articulo anterior detallaba el proceso para la creación de imágenes de OpenStack en un repositorio Docker privado a traves del proyecto de Kolla. Dichas imágenes serán utilizadas mas parte por las tareas del proyecto de kolla-ansible durante el despliegue de servicios de OpenStack en una configuración que ofrezca Alta Disponibilidad.

Para esta serie de artículos fue necesario crear un repositorio el cual pudiera almacenar las instrucciones. La validación de este proyecto se realiza a traves de la tecnología de vagrant la cual nos permite provisionar automáticamente Máquinas Virtuales.

Con el fin de agregar mas nodos con fines mas especializados fue necesario modificar la arquitectura inicial, dando como resultado tenemos el siguiente diagrama:

Diagrama de la arquitectura de nodos

El servidor llamado «registry» realizara la creación de imágenes. Este cambio ofrece un mejor control y administración de las imágenes de OpenStack utilizadas durante el proceso de provisionamiento. Para la creación de este servidor es necesario instalar las dependencias del proyecto, para ello necesitaremos el manejador de paquetes de python(pip) y el código fuente del proyecto.

apt remove -y python-pip
apt-get install -y python-dev
curl -sL https://bootstrap.pypa.io/get-pip.py | sudo python

wget http://tarballs.openstack.org/kolla/kolla-stable-rocky.tar.gz
tar -C /opt -xzf kolla-stable-rocky.tar.gz
mv /opt/kolla-*/ /opt/kolla
pip install /opt/kolla

Una vez instalado los módulos de python, es necesario instalar e iniciar el servicio de Docker. La comunidad de Kolla utiliza el directorio de tools para los procesos Jenkins durante la validación de cambios propuestos en el código fuente, el script setup_Debian.sh nos permite instalar el servicio de Docker y el script start-registry crea un repositorio Docker privado a traves de un contenedor.

pushd /opt/kolla/tools/
./setup_Debian.sh
./start-registry
popd

Por ultimo procederemos a la creación de imágenes de OpenStack Kolla, el comando kolla-build utiliza el archivo de configuración kolla-build.ini, en este archivo es necesario especificar la IP del servidor de registry el cual almacenará las imágenes de OpenStack y también habilitaremos la publicación automática de las imágenes creadas con del valor de configuración «push».

push = true
registry = 10.10.13.2:5000

Una vez todo configurado podemos comenzar con el proceso de creación de imágenes con la ejecución del siguiente comando:

kolla-build --config-file /etc/kolla/kolla-build.ini

Instalación de OpenStack – Alta Disponibilidad (Creación de imágenes)

Últimamente he estado trabajando en la integración de Kolla como una alternativa adicional para la instalación de los servicios de OpenStack en el proyecto de XCI en OPNFV. Kolla ofrece el despliegue de servicios de OpenStack en contenedores Docker en lugar de contenedores Linux como lo hace OpenStack-Ansible. Este proyecto fue creado con el fin de simplificar el numero de variables utilizadas en el proceso de configuración.

Esta serie de artículos pretende recopilar los requisitos e instrucciones necesarias para la instalación de los servicios de OpenStack en una configuración de Alta Disponibilidad, es decir, tres nodos con roles de controlador y dos nodos con roles de computo, además un nodo adicional como medio para la ejecución de instrucciones.

Cabe mencionar que OpenStack Kolla puede ser visto como dos proyectos independientes, uno el cual controla el proceso de creación de imágenes de Docker y otro el que utiliza estas imágenes para realizar el despliegue de los servicios de OpenStack. Aunque es posible utilizar las imágenes de Docker provistas por la comunidad para el despliegue de los servicios de OpenStack, en esta ocasión las imágenes serán generadas a partir de repositorios remotos de los distintos proyectos de OpenStack.

Lo primero a realizar es la instalación de dependencias (Docker y modulos de Python) en el nodo llamado OPNFV, el cual mantendrá el catalogo de imágenes utilizadas por el resto de los nodos. La documentación oficial proporciona los siguientes pasos.

Instalación de Docker

$ sudo apt install -y software-properties-common linux-image-extra-$(uname -r) linux-image-extra-virtual
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt update
$ sudo apt install -y docker-ce

Instalación de OpenStack Kolla desde codigo fuente

$ sudo apt install -y python2.7 python-dev build-essential
$ curl -sL https://bootstrap.pypa.io/get-pip.py | sudo python
$ git clone https://github.com/openstack/kolla
$ cd kolla
$ sudo pip install .

Es posible generar una plantilla que facilite la configuración del proceso de creación de imágenes. Esta plantilla puede ser obtenida a traves de la ejecución de la herramienta tox utilizando el argumento de genconfig como valor de entorno.

Generacion de etc/kolla/kolla-build.conf

$ sudo pip install tox 
$ tox -e genconfig

En esta plantilla podemos especificar la ubicación del código fuente que sera utilizado por las imagenes. Por ejemplo, si se desea que la imagen para los servicios de Neutron sea generada a partir el código fuente del repositorio del día 28 de mayo del 2018. Solo es necesario localizar la sección [neutron-base] y modificar los siguientes valores de type, location y reference en el archivo kolla-build.conf.

[neutron-base]
type = git
location = https://git.openstack.org/openstack/neutron
reference = f723dfadeecc6f13cd3a1277dc13e1580a4ea5d0

Por ultimo solo queda iniciar el proceso de creación de imágenes, este se realiza por medio de el binario kolla-build que fue generado al momento de instalar Kolla.

kolla-build --config-file etc/kolla/kolla-build.conf

* Nota adicional: Las instrucciones previamente mencionadas han sido guardadas en el repositorio vagrant-kolla.

Revertir instalación de paquetes

No todas las actualizaciones en el sistema operativo pueden ser benéficas, en ocasiones es posible que la actualización de algún paquete cause conflictos en nuestras aplicaciones. Para estos casos es necesario revertir la actualización de paquetes.

A lo largo de este articulo explicaré los pasos necesarios para revertir instalación de paquetes de Docker para la distribución de Ubuntu.

Lo primero a realizar es obtener el nombre exacto del paquete a revertir su actualización. Es posible filtrar este nombre, a traves del comando find, del contenido de archivos del directorio /var/lib/dpkg/info/. Este directorio contiene archivos de la configuración de los paquetes instalados. El siguiente comando busca una coincidencia en las actualizaciones realizadas en las ultimas 24 horas.

$ find /var/lib/dpkg/info/ -name \*.list -mtime -1 | sed 's#.list$##;s#.*/##' | grep docker
docker-ce

Una vez que el nombre es encontrado es necesario recurrir a la tabla de versiones para conocer las versiones disponibles. Esta tabla puede ser mostrada a traves con el comando apt-cache policy.

$ sudo apt-cache policy docker-ce
docker-ce:
  Installed: 17.12.1~ce-0~ubuntu
  Candidate: 17.12.1~ce-0~ubuntu
  Version table:
 *** 17.12.1~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
        100 /var/lib/dpkg/status
     17.12.0~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.09.1~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.09.0~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.06.2~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.06.1~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.06.0~ce-0~ubuntu 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.03.2~ce-0~ubuntu-trusty 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.03.1~ce-0~ubuntu-trusty 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages
     17.03.0~ce-0~ubuntu-trusty 0
        500 https://download.docker.com/linux/ubuntu/ trusty/stable amd64 Packages

En este caso la versión instalada en el sistema operativo es la señalada con tres asteriscos al inicio del nombre, es decir, la versión 17.12.1~ce-0~ubuntu. Esta tabla también nos permite conocer las versiones disponibles de nuestro paquete.

Por ultimo y para por fin revertir instalación de paquetes solo queda forzar la instalación del paquete a la versión deseada.

$ sudo apt-get install docker-ce=17.09.1~ce-0~ubuntu --force-yes -y
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be DOWNGRADED:
  docker-ce
0 upgraded, 0 newly installed, 1 downgraded, 0 to remove and 0 not upgraded.
Need to get 21.0 MB of archives.
After this operation, 52.6 MB disk space will be freed.
Fetched 21.0 MB in 7s (2,693 kB/s)
dpkg: warning: downgrading docker-ce from 17.12.1~ce-0~ubuntu to 17.09.1~ce-0~ubuntu
(Reading database ... 69403 files and directories currently installed.)
Preparing to unpack .../docker-ce_17.09.1~ce-0~ubuntu_amd64.deb ...
docker stop/waiting
Unpacking docker-ce (17.09.1~ce-0~ubuntu) over (17.12.1~ce-0~ubuntu) ...
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Setting up docker-ce (17.09.1~ce-0~ubuntu) ...
Installing new version of config file /etc/bash_completion.d/docker ...
docker start/running, process 7219