Introducción
En el mundo actual, las máquinas de la infraestructura de servidores se encuentran en centros de datos locales, centros de datos privados o centros de datos en nubes públicas. Estas máquinas pueden ser máquinas físicas de metal desnudo, máquinas virtuales (VM) con hipervisores o pequeños contenedores como contenedores Docker sobre máquinas físicas o virtuales. Estas máquinas pueden estar físicamente en el laboratorio local. En un escenario de centro de datos privado, donde sus propios hosts adquiridos se colocan en un espacio físico compartido en un centro de datos de terceros y se conectan de forma remota. Mientras que en los centros de datos públicos como AWS, GCP, Azure, OCI las máquinas se reservan o se crean bajo demanda para las necesidades altamente escalables que se conectan de forma remota. Cada uno de ellos tiene sus propias ventajas en cuanto a escalabilidad, seguridad, fiabilidad, gestión y costes asociados a estas infraestructuras.
Los equipos del entorno de desarrollo de productos pueden necesitar muchos servidores durante el proceso SDLC. Digamos que uno ha elegido el centro de datos privado con sus propias máquinas físicas junto con servidores Xen. Ahora, el reto es cómo se gestiona el ciclo de vida de las máquinas virtuales para el suministro o la terminación en entornos similares a la nube con procesos ágiles y sencillos.
El objetivo de este documento es proporcionar el modelo básico de infraestructura, la arquitectura, las API mínimas y fragmentos de código de ejemplo para que se puedan crear fácilmente entornos de infraestructura dinámicos.
Beneficios
Comprendamos primero la secuencia típica de los pasos seguidos en el proceso de infraestructura de este servidor. Puede recordarlo como se indica a continuación.
-
- Adquisición de las nuevas máquinas por IT
- Virtualización de host - Instalar servidor Xen y crear plantillas VM por TI
- Solicitud de máquinas virtuales estáticas por parte de los equipos de desarrollo y pruebas a través de tickets (por ejemplo, JIRA) a TI.
- Mantener las IPs de las máquinas virtuales recibidas en una base de datos o un archivo estático o codificadas en archivos de configuración o herramientas CI como en Jenkins config.xml.
- Supervisar las máquinas virtuales para comprobar que están en buen estado antes de utilizarlas para instalar los productos.
- Limpieza o desinstalación antes o después de la instalación de servidores
- Es posible que sea necesario limpiar el registro de Windows antes de instalar el producto.
- Se podría haber hecho una asignación fija de máquinas virtuales a un área o a un equipo o dedicarlas a un ingeniero
Ahora bien, ¿cómo puede hacer que este proceso sea más ágil? ¿Puedes eliminar la mayoría de los pasos anteriores con una simple automatización?
Sí. En nuestro entorno, tuvimos más de 1000 VMs y trató de lograr y sobre todo lo siguiente.
"VMs desechables bajo demanda según sea necesario durante la ejecución de las pruebas. Resuelva los problemas de limpieza de Windows con ciclos de pruebas regulares".
Como se puede ver a continuación, utilizando el servicio API de gestión dinámica de servidores VM, se pueden eliminar 6 de los 8 pasos y ofrece una visión ilimitada de la infraestructura para todo el equipo de producto. Sólo son necesarios los 2 primeros pasos: adquisición y virtualización del host. En efecto, esto ahorra tiempo y costes.

Flujo típico para obtener infraestructura
Modelo de infraestructura dinámica
La siguiente imagen muestra nuestra infraestructura propuesta para un entorno típico de productos de servidor donde 80% de contenedores Docker, 15% como VMs dinámicas y 5% como VMs estáticas agrupadas para casos especiales. Esta distribución se puede ajustar en función de lo que funcione mejor para su entorno.

Modelo de infraestructura
A partir de aquí, hablaremos más sobre la parte del gestor de servidores Dynamic VM.
Arquitectura del Gestor Dinámico de Servidores
En el gestor de servidores de máquinas virtuales dinámicas, un sencillo servicio de API en el que se pueden exponer las siguientes API REST y que se pueden utilizar en cualquier parte del proceso automatizado. Como muestra la pila de tecnología, python 3 y Python basado en Xen APIs se utilizan para la creación real de VMs con XenServer host. Flask se utiliza para la creación de la capa de servicio REST. El sistema operativo puede ser cualquiera de las plataformas soportadas por su producto como windows2016, centos7, centos8, debian10, ubuntu18, oel8, suse15.

Arquitectura del gestor de servidores de máquinas virtuales dinámicas
Guardar el historial de las máquinas virtuales para realizar un seguimiento del uso y el tiempo de aprovisionamiento o terminación puede ser analizado más a fondo. Para almacenar el documento json, se puede utilizar Couchbase enterprise server, que es una base de datos de documentos nosql.
API REST sencillas
Método | URI(s) | Propósito |
GET | /showall | Lista todas las máquinas virtuales en formato json |
GET | /getavailablecount/ | Obtiene la lista de máquinas virtuales disponibles para el dado |
GET | /getservers/?os=
/getservers/?os=&count= /getservers/?os=&count=&cpus=&mem= /getservers/?os=&expiresin= |
Disposiciones dadas VMs de .
También se puede admitir el recuento de cpus y el tamaño de mem. parámetro expiresin en minutos para obtener la expiración (auto terminación) de las VMs. |
GET | /releaseservers/?os=
/releaseservers/?os=&count= |
Termina dado VMs de |
Requisitos previos para hosts Xen dirigidos a máquinas virtuales dinámicas
- Identificar hosts Xen VM dinámicos específicos
- Copiar/crear las plantillas VM
- Mueva estos Hosts Xen a una VLAN/Subred separada (trabaje con IT) para el reciclaje de IPs.
Aplicación
A alto nivel -
- Crear funciones cada API REST
- Llame a un servicio común para realizar diferentes acciones REST.
- Comprender la creación de Sesión Xen, obtener los registros, clonar VM desde plantilla, adjuntar el disco correcto, esperar la creación de VM e IP recibida; borrado de VMs, borrado de discos.
- Iniciar automáticamente un hilo para la expiración de las máquinas virtuales
- Leer la configuración común como el formato .ini
- Comprender el trabajo con la base de datos Couchbase y guardar documentos
- Pruebe todas las API con los sistemas operativos y parámetros necesarios.
- Solucionar problemas, si los hay
- Realice un POC con pocos hosts Xen
Los siguientes fragmentos de código pueden ayudarle a comprenderlo mejor.
Creación de API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
@aplicación.ruta('/showall/') @aplicación.ruta("/showall") def showall_service(os=Ninguno): cuente, _ = get_all_xen_hosts_count(os) registro.información("--> count: {}".formato(cuente)) todos_vms = {} para xen_host_ref en gama(1, cuente + 1): registro.información("Getting xen_host_ref=" + str(xen_host_ref)) todos_vms[xen_host_ref] = realizar_servicio(xen_host_ref, nombre_servicio=listvms, os=os) devolver json.vuelca(todos_vms, sangría=2, ordenar_claves=Verdadero) @aplicación.ruta('/getavailablecount/') @aplicación.ruta('/getavailablecount') def getavailable_count_service(os=centos): """ Calcular el recuento disponible: Obtener CPUs totales, Memoria total Obtenga CPU y memoria libres Obtener todas las VMs - CPUs y Memoria asignadas Obtener cada plantilla de SO - CPUs y Memoria Recuento disponible1 = (CPUs libres - CPUs VMs)/OS_Template_CPUs Recuento disponible2 = (Memoria libre - Memoria VMs)/Memoria_OS_Template Devuelve min(cuenta1,cuenta2) """ cuente, recuentos_disponibles, xen_hosts = get_all_available_count(os) registro.información("{},{},{},{}".formato(cuente, recuentos_disponibles, xen_hosts, recuento_reservado)) si cuente > recuento_reservado: cuente -= recuento_reservado registro.información("Menos cuenta reservada: {},{},{},{}".formato(cuente, recuentos_disponibles, xen_hosts, recuento_reservado)) devolver str(cuente) # /getservers/username?count=number&os=centos&ver=6&expiresin=30 @aplicación.ruta('/getservers/') def servicio_servidores(nombre de usuario): global recuento_reservado si solicitar.args.consiga(Contar): vm_count = int(solicitar.args.consiga(Contar)) si no: vm_count = 1 os_name = solicitar.args.consiga("os) si solicitar.args.consiga(cpus): cpus_count = solicitar.args.consiga(cpus) si no: cpus_count = "por defecto" si solicitar.args.consiga(mem): mem = solicitar.args.consiga(mem) si no: mem = "por defecto" si solicitar.args.consiga('expiresin'): exp = int(solicitar.args.consiga('expiresin')) si no: exp = MAX_EXPIRY_MINUTES si solicitar.args.consiga(formato): formato_salida = solicitar.args.consiga(formato) si no: formato_salida = "servermanager" xhostref = Ninguno si solicitar.args.consiga(xhostref): xhostref = solicitar.args.consiga(xhostref) recuento_reservado += vm_count si xhostref: registro.información("--> VMs en xenhost dado" + xhostref) vms_ips_list = realizar_servicio(xhostref, crearvm, os_name, nombre de usuario, vm_count, cpus=cpus_count, maxmemory=mem, minutos_de_vencimiento=exp, formato_salida=formato_salida) devolver json.vuelca(vms_ips_list) ... # /releaseservers/{nombredeusuario} @aplicación.ruta('/releaseservers//') @aplicación.ruta('/releaseservers/') def servicio_servidores_sueltos(nombre de usuario): si solicitar.args.consiga(Contar): vm_count = int(solicitar.args.consiga(Contar)) si no: vm_count = 1 os_name = solicitar.args.consiga("os) eliminar_vms_res = [] para vm_index en gama(vm_count): si vm_count > 1: vm_nombre = nombre de usuario + str(vm_index + 1) si no: vm_nombre = nombre de usuario xen_host_ref = get_vm_existed_xenhost_ref(vm_nombre, 1, Ninguno) registro.información("VM a eliminar de xhost_ref=" + str(xen_host_ref)) si xen_host_ref != 0: delete_per_xen_res = realizar_servicio(xen_host_ref, deletevm, os_name, vm_nombre, 1) para borrado_vm_res en delete_per_xen_res: eliminar_vms_res.añadir(borrado_vm_res) si len(eliminar_vms_res) < 1: devolver "Error: VM " + nombre de usuario + "no existe" si no: devolver json.vuelca(eliminar_vms_res, sangría=2, ordenar_claves=Verdadero) def realizar_servicio(xen_host_ref=1, nombre_servicio='list_vms', os="centos", vm_prefix_names="", número_de_vms=1, cpus="por defecto", maxmemory="por defecto", minutos_de_vencimiento=MAX_EXPIRY_MINUTES, formato_salida="servermanager", sufijo_inicial=0): xen_host = get_xen_host(xen_host_ref, os) ... def principal(): # options = parse_arguments() set_log_level() aplicación.ejecute(host='0.0.0.0', depurar=Verdadero) |
Creación sesión Xen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def get_xen_session(xen_host_ref=1, os="centos"): xen_host = get_xen_host(xen_host_ref, os) si no xen_host: devolver Ninguno url = "http://" + xen_host[host.name] registro.información("\nXen Servidor host: " + xen_host[host.name] + "\n") pruebe: sesión = XenAPI.Sesión(url) sesión.xenapi.login_con_contraseña(xen_host['host.user'], xen_host['host.password']) excepto XenAPI.Fallo como f: error = "Fallo al adquirir una sesión: {}".formato(f.detalles) registro.error(error) devolver error devolver sesión |
Lista de máquinas virtuales
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
def lista_vms(sesión): vm_count = 0 vms = sesión.xenapi.VM.obtener_todos() registro.información("El servidor tiene {} objetos VM (esto incluye plantillas):".formato(len(vms))) registro.información("-----------------------------------------------------------") registro.información("S.No.,VMname,PowerState,Vcpus,MaxMemory,Networkinfo,Description") registro.información("-----------------------------------------------------------") vm_detalles = [] para vm en vms: información_de_red = N/A registro = sesión.xenapi.VM.obtener_registro(vm) si no (registro["is_a_template"]) y no (registro["is_control_domain"]): registro.depurar(registro) vm_count = vm_count + 1 nombre = registro["nombre_etiqueta"] nombre_descripción = registro["nombre_descripción"] estado_de_energía = registro["power_state"] vcpus = registro["VCPUs_max"] memoria_estatica_max = registro["memoria_estatica_max"] si registro["power_state"] != Detenido: ip_ref = sesión.xenapi.VM_guest_metrics.obtener_registro(registro['guest_metrics']) información_de_red = ','.únase a([str(elem) para elem en ip_ref[redes].valores()]) si no: continuar # Listado sólo de máquinas virtuales en ejecución vm_info = {nombre: nombre, 'power_state': estado_de_energía, vcpus: vcpus, memoria_estatica_max: memoria_estatica_max, información de red: información_de_red, descripción_nombre: nombre_descripción} vm_detalles.añadir(vm_info) registro.información(vm_info) registro.información("El servidor tiene {} objetos VM y {} plantillas"..formato(vm_count, len(vms) - vm_count)) registro.depurar(vm_detalles) devolver vm_detalles |
Crear VM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
def crear_vm(sesión, os_name, plantilla, nuevo_nombre_vm_, cpus="por defecto", maxmemory="por defecto", minutos_de_vencimiento=MAX_EXPIRY_MINUTES): error = '' vm_os_name = '' vm_ip_addr = '' prov_start_time = tiempo.tiempo() pruebe: registro.información("\n--- Creando VM: " + nuevo_nombre_vm_ + " utilizando " + plantilla) pifs = sesión.xenapi.FIP.obtener_todos_los_registros() más bajo = Ninguno para pifRef en pifs.llaves(): si (más bajo es Ninguno) o (pifs[pifRef][dispositivo] < pifs[más bajo][dispositivo]): más bajo = pifRef registro.depurar("Eligiendo PIF con dispositivo: {}".formato(pifs[más bajo][dispositivo])) ref = más bajo mac = pifs[ref][MAC] dispositivo = pifs[ref][dispositivo] modo = pifs[ref][ip_configuration_mode] dirección_ip = pifs[ref][IP] máscara_red = pifs[ref][IP] pasarela = pifs[ref][pasarela] servidor_dns = pifs[ref][DNS] registro.depurar("{},{},{},{},{},{},{}".formato(mac, dispositivo, modo, dirección_ip, máscara_red, pasarela, servidor_dns)) # Listar todos los objetos VM vms = sesión.xenapi.VM.obtener_todos_los_registros() registro.depurar("El servidor tiene {} objetos VM (esto incluye plantillas)".formato(len(vms))) plantillas = [] todas_las_plantillas = [] para vm en vms: registro = vms[vm] tipo_res = "VM" si registro["is_a_template"]: tipo_res = "Plantilla" todas_las_plantillas.añadir(vm) # Buscar una plantilla determinada si registro["nombre_etiqueta"].empiezapor(plantilla): plantillas.añadir(vm) registro.depurar(" Encontrados %8s con nombre_etiqueta = %s" % (tipo_res, registro["nombre_etiqueta"])) registro.depurar("El servidor tiene {} Plantillas y {} Objetos VM"..formato(len(todas_las_plantillas), len(vms) - len( todas_las_plantillas))) registro.depurar("Elegir una plantilla {} para clonar".formato(plantilla)) si no plantillas: registro.error("No se ha podido encontrar ninguna plantilla {}. Saliendo"..formato(plantilla)) sys.salida(1) plantilla_ref = plantillas[0] registro.depurar(" Plantilla seleccionada: {}".formato(sesión.xenapi.VM.get_name_label(plantilla_ref))) # Reintentos cuando se recibe la dirección 169.x ipaddr_max_retries = 3 cuenta_reintentos = 1 is_local_ip = Verdadero vm_ip_addr = "" mientras que is_local_ip y cuenta_reintentos != ipaddr_max_retries: registro.información("Instalando nueva VM desde la plantilla - intento #{}".formato(cuenta_reintentos)) vm = sesión.xenapi.VM.clonar(plantilla_ref, nuevo_nombre_vm_) red = sesión.xenapi.FIP.obtener_red(más bajo) registro.depurar("El PIF elegido está conectado a la red: {}".formato( sesión.xenapi.red.get_name_label(red))) vifs = sesión.xenapi.VIF.obtener_todos() registro.depurar(("Número de VIFs=" + str(len(vifs)))) para i en gama(len(vifs)): vmref = sesión.xenapi.VIF.obtener_VM(vifs[i]) a_vm_name = sesión.xenapi.VM.get_name_label(vmref) registro.depurar(str(i) + "." + sesión.xenapi.red.get_name_label( sesión.xenapi.VIF.obtener_red(vifs[i])) + " " + a_vm_name) si a_vm_name == nuevo_nombre_vm_: sesión.xenapi.VIF.mover(vifs[i], red) registro.depurar("Añadir no interactivo a la línea de comandos del kernel") sesión.xenapi.VM.set_PV_args(vm, "no interactivo") registro.depurar("Elegir un SR para instanciar los discos de la VM") piscina = sesión.xenapi.piscina.obtener_todos()[0] default_sr = sesión.xenapi.piscina.get_default_SR(piscina) default_sr = sesión.xenapi.SR.obtener_registro(default_sr) registro.depurar("Eligiendo SR: {} (uuid {})".formato(default_sr[etiqueta_nombre], default_sr[uuid])) registro.depurar("Solicitando al servidor que aprovisione almacenamiento a partir de la especificación de la plantilla") descripción = nuevo_nombre_vm_ + " de " + plantilla + " en " + str(datetime.datetime.utcnow()) sesión.xenapi.VM.set_name_description(vm, descripción) si cpus != "por defecto": registro.información("Configurar cpus a " + cpus) sesión.xenapi.VM.set_VCPUs_max(vm, int(cpus)) sesión.xenapi.VM.set_VCPUs_at_startup(vm, int(cpus)) si maxmemory != "por defecto": registro.información("Ajustando la memoria a " + maxmemory) sesión.xenapi.VM.set_memory(vm, maxmemory) # 8GB="8589934592" o 4GB="4294967296" sesión.xenapi.VM.provisión(vm) registro.información("Iniciando VM") sesión.xenapi.VM.iniciar(vm, Falso, Verdadero) registro.depurar(" La máquina virtual está arrancando") registro.depurar("Esperando a que se complete la instalación") # Obtener el nombre del SO y las IPs registro.información("Obteniendo el Nombre del Sistema Operativo y la IP...") config = leer_configuración() vm_network_timeout_secs = int(config.consiga("común", "vm.network.timeout.secs")) si vm_network_timeout_secs > 0: TIMEOUT_SECS = vm_network_timeout_secs registro.información("El tiempo máximo de espera en segundos para la dirección del SO de la VM es {0}".formato(str(TIMEOUT_SECS))) si "ganar" no en plantilla: maxtime = tiempo.tiempo() + TIMEOUT_SECS mientras que leer_nombre_os(sesión, vm) es Ninguno y tiempo.tiempo() < maxtime: tiempo.dormir(1) vm_os_name = leer_nombre_os(sesión, vm) registro.información("Nombre del sistema operativo de la máquina virtual: {}".formato(vm_os_name)) si no: # TBD: Esperar a que la red se actualice en Windows VM tiempo.dormir(60) registro.información("El tiempo máximo de espera en segundos para la dirección IP es " + str(TIMEOUT_SECS)) maxtime = tiempo.tiempo() + TIEMPO DE ESPERA_SECS # Esperar hasta que la IP no sea None o 169.xx (cuando no hay IPs disponibles, esto es por defecto) y timeout # no se alcanza. mientras que (leer_dirección_ip(sesión, vm) es Ninguno o leer_dirección_ip(sesión, vm).empiezapor( '169')) y \ tiempo.tiempo() < maxtime: tiempo.dormir(1) vm_ip_addr = leer_dirección_ip(sesión, vm) registro.información("VM IP: {}".formato(vm_ip_addr)) si vm_ip_addr.empiezapor('169'): registro.información("No hay IP de red disponible. Eliminando esta VM ... ") registro = sesión.xenapi.VM.obtener_registro(vm) estado_de_energía = registro["power_state"] si estado_de_energía != Detenido: sesión.xenapi.VM.hard_shutdown(vm) borrar_todos_los_discos(sesión, vm) sesión.xenapi.VM.destruir(vm) tiempo.dormir(5) is_local_ip = Verdadero cuenta_reintentos += 1 si no: is_local_ip = Falso registro.información("IP final de la máquina virtual: {}".formato(vm_ip_addr)) |
Borrar VM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
def eliminar_vm(sesión, vm_nombre): registro.información("Borrar VM: " + vm_nombre) eliminar_hora_inicio = tiempo.tiempo() vm = sesión.xenapi.VM.get_by_name_label(vm_nombre) registro.información("Número de máquinas virtuales encontradas con el nombre - " + vm_nombre + " : " + str(len(vm))) para j en gama(len(vm)): registro = sesión.xenapi.VM.obtener_registro(vm[j]) estado_de_energía = registro["power_state"] si estado_de_energía != Detenido: # session.xenapi.VM.shutdown(vm[j]) sesión.xenapi.VM.hard_shutdown(vm[j]) # print_all_disks(session, vm[j]) borrar_todos_los_discos(sesión, vm[j]) sesión.xenapi.VM.destruir(vm[j]) delete_end_time = tiempo.tiempo() suprimir_duración = redondo(delete_end_time - eliminar_hora_inicio) # suprimir de CB uuid = registro["uuid"] doc_key = uuid cbdoc = CBDoc() doc_resultado = cbdoc.obtener_documento(doc_key) si doc_resultado: valor_doc = doc_resultado.valor valor_doc["estado"] = "borrado hora_actual = tiempo.tiempo() valor_doc["deleted_time"] = hora_actual si valor_doc["hora_creada"]: valor_doc["live_duration_secs"] = redondo(hora_actual - valor_doc["hora_creada"]) valor_doc["delete_duration_secs"] = suprimir_duración cbdoc.save_dynvm_doc(doc_key, valor_doc) |
Uso histórico de máquinas virtuales
Es mejor mantener el historial de todas las VMs creadas y terminadas junto con otros datos útiles. Aquí está el ejemplo de documento json almacenado en el Couchbase, un base de datos Nosql gratuita servidor. Inserte un nuevo documento utilizando la clave como el uuid de referencia de xen opac cada vez que se aprovisione una nueva VM y actualice el mismo cada vez que se termine la VM. Rastrea el tiempo de uso de la VM y también cómo se ha realizado el aprovisionamiento/desaprovisionamiento por cada usuario.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
registro = sesión.xenapi.VM.obtener_registro(vm) uuid = registro["uuid"] # Guardar como documento en CB estado = "disponible" nombre de usuario = nuevo_nombre_vm_ piscina = "dynamicpool" valor_doc = {"ipaddr": vm_ip_addr, "origen": xen_host_description, "os": os_name, "estado": estado, "poolId": piscina, "prevUsuario": "", "nombre de usuario": nombre de usuario, "ver": "12", "memoria": memoria_estatica_max, "os_version": vm_os_name, "nombre": nuevo_nombre_vm_, "hora_creada": prov_end_time, "create_duration_secs": crear_duración, "cpu": vcpus, "disco": info_discos} # doc_value["dirección_mac"] = dirección_mac doc_key = uuid cb_doc = CBDoc() cb_doc.save_dynvm_doc(doc_key, valor_doc) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
clase CBDoc: def __init__(auto): config = leer_configuración() auto.cb_servidor = config.consiga("couchbase", "couchbase.server") auto.cb_bucket = config.consiga("couchbase", "couchbase.bucket") auto.cb_username = config.consiga("couchbase", "couchbase.username") auto.cb_contraseña_usuario = config.consiga("couchbase", "couchbase.userpassword") pruebe: auto.cb_cluster = Grupo(couchbase:// + auto.cb_servidor) auto.cb_auth = PasswordAuthenticator(auto.cb_username, auto.cb_contraseña_usuario) auto.cb_cluster.autentifique(auto.cb_auth) auto.cb = auto.cb_cluster.open_bucket(auto.cb_bucket) excepto Excepción como e: registro.error(Conexión fallida: %s ' % auto.cb_servidor) registro.error(e) def obtener_documento(auto, doc_key): pruebe: devolver auto.cb.consiga(doc_key) excepto Excepción como e: registro.error(Error al obtener el documento %s. % doc_key) registro.error(e) def save_dynvm_doc(auto, doc_key, valor_doc): pruebe: registro.información(valor_doc) auto.cb.upsert(doc_key, valor_doc) registro.información("%s añadido/actualizado correctamente" % doc_key) excepto Excepción como e: registro.error('Documento con clave: %s error de guardado' % doc_key) registro.error(e) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
"dynserver-pool": { "cpu": "6", "create_duration_secs": 65, "hora_creada": 1583518463.8943903, "delete_duration_secs": 5, "deleted_time": 1583520211.8498628, "disco": "75161927680", "ipaddr": "x.x.x.x", "live_duration_secs": 1748, "memoria": "6442450944", "nombre": "Win2019-Server-1node-DynVM", "origen": "s827", "os": "win16", "os_version": "", "poolId": "dynamicpool", "prevUsuario": "", "estado": "borrado", "nombre de usuario": "Win2019-Server-1node-DynVM", "ver": "12" } |
Configuración
La configuración del servicio Dynamic VM server manager, como el servidor couchbase, los servidores xenhost, los detalles de la plantilla, la caducidad por defecto y los valores de tiempo de espera de red se pueden mantener en un simple formato .ini. La configuración se carga dinámicamente sin necesidad de reiniciar el servicio Dynamic VM SM.
Archivo de configuración de ejemplo: .dynvmservice.ini
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
[couchbase] couchbase.servidor=<couchbase-hostIp> couchbase.cubo=<cubo-nombre> couchbase.nombre de usuario=<nombre de usuario> couchbase.contraseña de usuario=<contraseña> [común] vm.caducidad.minutos=720 vm.red.tiempo de espera.segs=400 [xenhost1] host.nombre=<xenhostip1> host.usuario=raíz host.contraseña=xxxx host.almacenamiento.nombre=Local Almacenamiento 01 centos.plantilla=tmpl-cnt7.7 centos7.plantilla=tmpl-cnt7.7 windows.plantilla=tmpl-win16dc - PATCHED (1) centos8.plantilla=tmpl-cnt8.0 oel8.plantilla=tmpl-oel8 deb10.plantilla=tmpl-deb10-demasiado debian10.plantilla=tmpl-deb10-demasiado ubuntu18.plantilla=tmpl-ubu18-4cgb suse15.plantilla=tmpl-suse15 [xenhost2] host.nombre=<xenhostip2> host.usuario=raíz host.contraseña=xxxx host.almacenamiento.nombre=ssd centos.plantilla=tmpl-cnt7.7 windows.plantilla=tmpl-win16dc - PATCHED (1) centos8.plantilla=tmpl-cnt8.0 oel8.plantilla=tmpl-oel8 deb10.plantilla=tmpl-deb10-demasiado debian10.plantilla=tmpl-deb10-demasiado ubuntu18.plantilla=tmpl-ubu18-4cgb suse15.plantilla=tmpl-suse15 [xenhost3] ... [xenhost4] ... |
Ejemplos
Ejemplo de llamadas a la API REST mediante curl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
$ rizo 'http://127.0.0.1:5000/showall' { "1": [ { "memoria_estatica_max": "6442450944", "nombre": "tunable-rebalance-out-Apr-29-13:43:59-7.0.0-19021", "nombre_descripción": "tunable-rebalance-out-Apr-29-13:43:59-7.0.0-19021 from tmpl-win16dc - PATCHED (1) on 2020-04-29 20:43:33.785778", "información de red": "172.23.137.20,172.23.137.20,fe80:0000:0000:0000:0585:c6e8:52f9:91d1", "power_state": "Corriendo", "vcpus": "6" }, { "memoria_estatica_max": "6442450944", "nombre": "nserv-nserv-rebalanceinout_P0_Set1_compression-Apr-29-06:36:12-7.0.0-19022", "nombre_descripción": "nserv-nserv-rebalanceinout_P0_Set1_compression-Apr-29-06:36:12-7.0.0-19022 from tmpl-win16dc - PATCHED (1) on 2020-04-29 13:36:53.717776", "información de red": "172.23.136.142,172.23.136.142,fe80:0000:0000:0000:744d:fd63:1a88:2fa8", "power_state": "Corriendo", "vcpus": "6" } ], "2": [ .. ] "3": [ .. ] "4": [ .. ] "5": [ .. ] "6": [ .. ] } $ rizo http://127.0.0.1:5000/getavailablecount/windows 2 $ rizo http://127.0.0.1:5000/getavailablecount/centos 10 $ rizo http://127.0.0.1:5000/getservers/demoserver?os=centos&count=3 ["172.23.137.73", "172.23.137.74", "172.23.137.75"] $ rizo http://127.0.0.1:5000/getavailablecount/centos 7 $ rizo http://127.0.0.1:5000/releaseservers/demoserver?os=centos&count=3 [ "demoserver1", "demoserver2", "demoserver3" ] $ rizo http://127.0.0.1:5000/getavailablecount/centos 10 $ |
Trabajos Jenkins con una única VM
1 2 3 4 5 6 7 8 |
rizo -s -o ${BUILD_TAG}_vm.json "http:///getservers/${BUILD_TAG}?os=windows&format=detailed" VM_IP_ADDRESS="`cat ${BUILD_TAG}_vm.json |egrep ${BUILD_TAG}|cut -f2 -d':'|xargs`" si [[ $VM_IP_ADDRESS =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; entonces echo Válido IP recibido si no echo NO a válido IP recibido salida 1 fi |
Se necesitan trabajos Jenkins con varias máquinas virtuales
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
rizo -s -o ${BUILD_TAG}.json "${SERVER_API_URL}/getservers/${BUILD_TAG}?os=${OS}&count=${COUNT}&format=detailed" cat ${BUILD_TAG}.json VM_IP_ADDRESS="`cat ${BUILD_TAG}.json |egrep ${BUILD_TAG}|cut -f2 -d':'|xargs|sed 's/,//g'`" echo $VM_IP_ADDRESS ADDR=() ÍNDICE=1 para IP en `echo $VM_IP_ADDRESS` do si [[ $IP =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; entonces echo Válido IP=$IP recibido ADDR[$ÍNDICE]=${IP} ÍNDICE=`expr ${ÍNDICE} + 1` si no echo NO a válido IP=$IP recibido salida 1 fi hecho echo ${ADDR[1]} echo ${ADDR[2]} echo ${ADDR[3]} echo ${ADDR[4]} |
Consideraciones clave
He aquí algunas de mis observaciones anotadas durante el proceso y que es mejor manejar para hacerlo más fiable.
- Manejar el nombre/ID de Almacenamiento diferente entre diferentes Hosts Xen
- Mantenga un registro del nombre del dispositivo de almacenamiento VM en el archivo de configuración de entrada de servicio.
- Manejar plantillas parciales sólo disponibles en algunos hosts Xen durante el aprovisionamiento.
- Cuando las IP de red no están disponibles y las API de Xen obtienen la dirección por defecto 169.254.xx.yy en Windows. Espere hasta obtener una dirección que no sea 169 o hasta que se agote el tiempo de espera.
- Los servidores de lanzamiento deben ignorar la plantilla os ya que algunas de las plantillas podrían no estar allí Xen Hosts
- Provisión en una referencia específica de host Xen determinada
- Manejar No hay IPs disponibles o not obtener IPs de red para algunas de las VMs creadas.
- Plan para tener una subred diferente para VMs dinámicas dirigidas a Hosts Xen. La red por defecto DHCP IP arrendamiento expiración podría ser en días (digamos 7 días) y no se proporcionan nuevas IPs.
- Maneje la comprobación de capacidad para contar las IPs en progreso como IPs reservadas y debería mostrar un recuento menor que lleno en este momento. De lo contrario, tanto las peticiones en curso como las entrantes podrían tener problemas. Una o dos VMs (cpus/memoria/tamaño de disco) pueden estar en el buffer mientras se crea y comprueba si hay peticiones paralelas.
Referencias
Algunas de las referencias clave que ayudan mientras se crea el servicio de gestor dinámico de servidores VM.
- https://www.couchbase.com/downloads
- https://wiki.xenproject.org/wiki/XAPI_Command_Line_Interface
- https://xapi-project.github.io/xen-api/
- https://docs.citrix.com/en-us/citrix-hypervisor/command-line-interface.html
- https://github.com/xapi-project/xen-api-sdk/tree/master/python/samples
- https://www.citrix.com/community/citrix-developer/citrix-hypervisor-developer/citrix-hypervisor-developing-products/citrix-hypervisor-staticip.html
- https://docs.ansible.com/ansible/latest/modules/xenserver_guest_module.html
- https://github.com/terra-farm/terraform-provider-xenserver
- https://github.com/xapi-project/xen-api/blob/master/scripts/examples/python/renameif.py
- https://xen-orchestra.com/forum/topic/191/single-device-not-reporting-ip-on-dashboard/14
- https://xen-orchestra.com/blog/xen-orchestra-from-the-cli/
- https://support.citrix.com/article/CTX235403
Espero que hayan pasado un buen rato de lectura.
Descargo de responsabilidad: Por favor, vea esto como una referencia si usted está tratando con Xen Hosts. Siéntase libre de compartir si ha aprendido algo nuevo que nos pueda ayudar. Agradecemos sus comentarios positivos.
Gracias a Raju Suravarjjala, Ritam Sharma, Wayne Siu, Tom Thrush y James Lee por su ayuda durante el proceso.