¿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
David Morales 07/06/2023 Cargando comentarios…
Toda definición canónica de un roadmap de transformación digital incluye estrategias específicas para escenarios de transición, denominados habitualmente como “lift and shift” o “improve and move”. Todos ellos están encaminados a resolver diferentes escenarios intermedios en la migración de una aplicación hacia una arquitectura objetivo, minimizando los riesgos asociados a la misma.
Existen multitud de sectores que en este camino de transformación se enfrentan a desafíos importantes como la presencia de soluciones propietarias, aplicaciones complejas o, incluso, imposiciones regulatorias, que exigen una estrategia de modernización menos agresiva que sitúe esas soluciones intermedias como ciudadanos de primer nivel y permita afianzar una transición segura hacia el objetivo final.
Por ejemplo, la migración hacia plataformas puramente basadas en contenedores no siempre es factible, viable o incluso recomendable. En un proceso tan complejo como la adopción de Kubernetes el pragmatismo adquiere una importancia decisiva.
Uno de los principales retos en la adopción de Kubernetes es abordar la transición desde virtualización o bare-metal a contenedores.
Una estrategia en la que los objetivos sean tecnológicos y finalistas forzará la migración a contenedores, como una condición sine qua non para que una aplicación pueda definirse como modernizada a cloud native. Es decir, en este modelo la virtualización no tiene cabida en cloud native y, por tanto, el camino crítico de todas las aplicaciones en este roadmap de transformación digital es una migración forzosa a contenedores.
El resultado más que probable de una estrategia de este tipo son máquinas virtuales disfrazadas de contenedores.
Dado que el foco está más en el destino y el formato que en interiorizar la transformación, es probable que el proceso termine sin adoptar las prácticas, patrones, arquitecturas y beneficios propios de una plataforma cloud native basada en Kubernetes, más allá de una cuestión de formato (contenedores).
Una estrategia más sensata es dividir el dominio de la aplicación. Algunos componentes se mantienen como servicios virtualizados, mientras que otros se migran a contenedores.
Este enfoque permite que cada aplicación pueda tener diferentes velocidades en la transición a cloud native y, por tanto, que haya una oportunidad de partida para una adopción de cloud native segura.
En este escenario podríamos definir aplicaciones cloud native híbridas como aquellas cuyo dominio está compuesto de servicios virtualizados y contenedores.
El reto tecnológico de esta estrategia es la convivencia entre servicios virtualizados y otros basados en contenedores. Y para lograrlo pueden adoptarse diferentes implementaciones.
Aunque puede parecer atractivo desplegar una plataforma de virtualización junto con Kubernetes, bien sea independiente (como oVirt, VMWare…) o integrada en el stack de un proveedor cloud (Amazon, Azure, Google…), esta arquitectura mixta supone una gestión duplicada de todos los aspectos de una plataforma.
Toda funcionalidad transversal (como HA, networking, backups, observabilidad, seguridad, disaster recovery) debe resolverse en dos plataformas que no convergen. El desarrollo, integración, despliegue y operación quedan totalmente afectados por la decisión, impactando de manera importante la velocidad de entrega y la complejidad del entorno.
En el caso de una aplicación cloud native híbrida (componentes virtualizados y en contenedores), el despliegue queda dividido en dos plataformas estancas.
KubeVirt es un operador de Kubernetes que permite desplegar y administrar máquinas virtuales en Kubernetes, extendiendo sus capacidades para gestionar cargas virtualizadas, permitiendo así combinar máquinas virtuales y contenedores en un clúster de Kubernetes.
Gracias a esta tecnología, es posible simplificar la gestión y eliminar la necesidad de plataformas separadas para los servicios virtualizados y los contenedores. El dominio de una aplicación cloud native híbrida se circunscribe a la Kubernetes, permitiendo una gestión unificada de la aplicación.
Kubevirt es un proyecto open source, auspiciado por la Cloud Native Computing Foundation e impulsado también por Red Hat, como base de su offering comercial OpenShift Virtualization.
Las ventajas de KubeVirt son numerosas, pero destacan las siguientes:
Para poner en contexto todas sus ventajas, vamos a definir una aplicación cloud native híbrida y a resolver el despliegue completo de la misma, donde convivirán contenedores con máquinas virtuales.
Una aplicación cloud native híbrida es aquella cuyo dominio está compuesto de servicios virtualizados y contenedores.
Imaginemos un escenario sencillo:
En este escenario, la aplicación python ya está migrada a contenedores, pero la base de datos aún está virtualizada.
El despliegue de esta aplicación requiere una solución para la comunicación entre la base de datos y el driver de MongoDB que ejecuta la aplicación python para recuperar el dato.
Nota: Esta es una aplicación de ejemplo desarrollada para la demostración de este caso de uso. No se han aplicado prácticas de seguridad o gestión de la configuración, entre otros asuntos.
Aplicación Python
La aplicación Python es una aplicación web simple creada con Flask:
from flask import Flask, jsonify
from pymongo import MongoClient
app = Flask(__name__)
@app.route('/')
def welcome():
client = MongoClient('mongodb', 27017)
db = client['demo']
collection = db['names']
name = collection.find_one()['name']
client.close()
rc = '\n'
return f"Welcome to a cloud native hybrid application, {name}! {rc}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
Expone un endpoint que devuelve un mensaje en el que un nombre se extrae de una base de datos MongoDB.
Deployment de la aplicación
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubevirt-networking-poc-helm
namespace: kubevirt-networking-poc
spec:
replicas: 1
selector:
matchLabels:
app: kubevirt-networking-poc
template:
metadata:
labels:
app: kubevirt-networking-poc
spec:
containers:
- name: kubevirt-networking-poc
image: "kubevirt-networking-poc:latest"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: kubevirt-networking-poc-service
namespace: kubevirt-networking-poc
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: 8080
selector:
app: kubevirt-networking-poc
El recurso Deployment define cómo se implementa y se ejecuta la aplicación Python en el clúster de Kubernetes.
El Deployment especifica la cantidad de réplicas, en este caso 1, las etiquetas y la configuración de los contenedores. El contenedor se crea a partir de la imagen de la aplicación y se expone en el puerto 8080.
El recurso Service se utiliza para exponer la aplicación Python en la red y permitir que otros componentes del clúster, así como los usuarios finales, se comuniquen con ella.
El recurso Service define el tipo de servicio (LoadBalancer), el puerto en el que se expone la aplicación (8080) y las etiquetas que se utilizan para seleccionar los Pods que implementan la aplicación.
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: mongodb-vm
namespace: kubevirt-networking-poc
spec:
running: true
template:
metadata:
labels:
kubevirt.io/domain: mongodb-vm
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
- name: mongodb-data
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
model: virtio
resources:
requests:
memory: 2Gi
cpu: 1
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: "quay.io/containerdisks/centos-stream
:9"
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#cloud-config
users:
- name: superuser
plain_text_passwd: 'superuser'
lock_passwd: false
groups: users, admin
sudo: ALL=(ALL) NOPASSWD:ALL
hostname: mongodb-vm
package_upgrade: true
write_files:
- path: /etc/yum.repos.d/mongodb-org-6.0.repo
content: |
[mongodb-org-6.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/6.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-6.0.asc
- path: /root/init-mongo.js
content: |
db = new Mongo().getDB("demo");
db.names.insertOne({name: "John Doe"});
- path: /root/adjust_config_mongo.sh
content: |
sed -i 's/^\( *bindIp *: *\).*/\10.0.0.0/' /etc/mongod.conf
runcmd:
- dnf install -y epel-release
- yum install -y mongodb-org
- systemctl daemon-reload
- systemctl enable --now mongod.service
- sleep 10
- chmod +x /root/adjust_config_mongo.sh
- sh /root/adjust_config_mongo.sh
- systemctl restart mongod.service
- sleep 10
- mongosh < /root/init-mongo.js
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
Uno de los Custom Resources que aporta KubeVirt es VirtualMachine, que permite definir instancias de servicios virtuales de forma declarativa.
La especificación de VirtualMachine incluye información sobre la configuración y los recursos de la máquina virtual:
Cloud-Init: Bootstrap de la máquina virtual
Cloud-init es una herramienta open source que se utiliza para inicializar y configurar instancias de máquinas virtuales y servidores en la nube al momento de su creación. Cloud-init se ejecuta en la fase de arranque de la instancia, permitiendo la personalización de la configuración del sistema, la instalación de paquetes, la creación de usuarios, la configuración de la red, la asignación de claves SSH y muchas otras tareas de administración.
La mayoría de las imágenes de sistemas operativos en la nube (por ejemplo, Ubuntu, CentOS, Fedora, Debian, etc.) ya tienen cloud-init preinstalado. Los proveedores de servicios en la nube como AWS, Google Cloud o Azure admiten el uso de cloud-init para personalizar las instancias en el momento del lanzamiento.
Cloud-init utiliza un archivo de configuración en formato YAML para definir las acciones y configuraciones específicas que se aplicarán a la instancia. Este archivo de configuración puede ser proporcionado por el usuario al momento de crear la instancia en el proveedor de servicios en la nube, o puede ser almacenado en un repositorio centralizado y accesible para su uso en múltiples instancias.
Cloud-init se puede también combinar con herramientas de gestión de la configuración como Chef o Puppet, para ampliar las opciones de bootstrapping de servicios virtuales.
En el caso concreto del ejemplo que nos ocupa, la instalación y configuración de MongoDB en la máquina virtual Fedora se lleva a cabo mediante un archivo de configuración de Cloud-init, que se especifica en el archivo vm.yaml dentro del volumen llamado cloudinitdisk.
El archivo de configuración de Cloud-init se compone de varias secciones, que incluyen la instalación de paquetes, la configuración de archivos y la ejecución de comandos para una puesta en marcha básica de MongoDB.
También se cargan los datos en la colección de mongo de la aplicación.
Volúmenes
La máquina virtual utiliza principalmente dos volúmenes:
apiVersion: v1
kind: Service
metadata:
name: mongodb
namespace: kubevirt-networking-poc
spec:
ports:
- name: mongo
port: 27017
protocol: TCP
targetPort: 27017
selector:
kubevirt.io/domain: mongodb-vm
Con este recurso “Service” podemos exponer la base de datos MongoDB que se ejecuta en la máquina virtual en el entorno de OpenShift, permitiendo que otros componentes del namespace, como la aplicación Python, se comuniquen con ella.
La especificación del recurso Service incluye la siguiente información:
Cuando un usuario invoque el endpoint que expone el servicio asociado a la aplicación Python, recibirá esta respuesta:
Donde el nombre John Doe se ha recogido de una base de datos MongoDB virtualizada sobre una plataforma Kubernetes. Yikes!
Todo el código está disponible en este repositorio.
KubeVirt permite unificar el despliegue completo de la aplicación sobre Kubernetes. Si utilizáramos una solución como Helm, podríamos desplegar y versionar nuestra aplicación cloud native híbrida en conjunto, facilitando no solo la instalación, sino también el rollout de nuevas versiones o el rollback.
Dado que se puede definir de forma declarativa el servicio virtualizado, tal y como se definen otras necesidades en Kubernetes, podemos emplear ya todas las técnicas disponibles para gestionar el despliegue, como gestión de la configuración, templating, parametrización, placement rules, etc.
También es interesante destacar cómo es posible trabajar con Persistent Volumes a este nivel, permitiendo la integración de las soluciones de storage de Kubernetes con las máquinas virtuales.
Dado que virtualización y contenedores se despliegan en Kubernetes, se pueden utilizar todas las técnicas de networking a nuestro alcance para garantizar la comunicación entre contenedores y servicios virtualizados.
En este caso hemos empleado un Service para permitir la comunicación entre un contenedor, donde tenemos el cliente de Mongo en la aplicación de Python, y una máquina virtual, donde tenemos la base de datos.
Además, podemos segmentar el tráfico de red respecto a otras aplicaciones gracias al uso de namespaces.
¿Es posible profundizar aún más en el uso de virtualización en Kubernetes? Sí, en este post nos centramos en el uso de las NetworkPolicies y, próximamente veremos, certificados, observabilidad o políticas.
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.