proyecto fin de carrera - universidad de...
TRANSCRIPT
Equation Chapter 1 Section 1
Proyecto Fin de Carrera
Ingeniería de Telecomunicación
Diseño y desarrollo de un servicio de información
demográfica en entorno Cloud basado en
contenedores
Autor: Jesús Álvarez Fernández-Nespral
Tutor: Isabel Román Martínez
Departamento de Ingeniería Telemática
Escuela Técnica Superior de Ingeniería
Universidad de Sevilla
Sevilla, 2018
iii
Proyecto Fin de Carrera
Ingeniería de Telecomunicación
Diseño y desarrollo de un servicio de información
demográfica en entorno Cloud basado en
contenedores
Autor:
Jesús Álvarez Fernández-Nespral
Tutor:
Isabel Román Martínez
Departamento de Ingeniería Telemática
Escuela Técnica Superior de Ingeniería
Universidad de Sevilla
Sevilla, 2018
v
Proyecto Fin de Carrera: Diseño y desarrollo de un servicio de información demográfica en entorno Cloud
basado en contenedores
Autor: Jesús Álvarez Fernández-Nespral
Tutor: Isabel Román Martínez
El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros:
Presidente:
Vocales:
Secretario:
Acuerdan otorgarle la calificación de:
Sevilla, 2018
El Secretario del Tribunal
vii
A mi familia
A mis maestros
ix
Agradecimientos
A todos los que con su infinita paciencia me han apoyado y animado en todo momento durante la realización
del proyecto.
xi
Resumen
En el presente proyecto se muestra la transición de un servicio de información demográfica, utilizado en un
contexto de integración con el sistema sanitario, pero desarrollado en un entorno local y con un modelo
monolítico tradicional, para dotarlo de las capacidades necesarias para su consumo como servicio Web, y con
un alto grado de escalabilidad y facilidad de despliegue al estar basado en tecnología de contenedores.
xiii
Abstract
This project shows the transition of a demographic information service, used in a context of health systems
integration, but developed in a local environment and with a traditional monolithic model, to provide it with the
necessary capacities for its consumption as a Web service, and with a high degree of scalability and ease of use,
being based on containers' technology.
xv
Índice
Agradecimientos ix
Resumen xi
Abstract xiii
Índice xv
Índice de Tablas xviii
Índice de Figuras xx
1 Introducción 1 1.1 Contexto 1 1.2 Motivación y objetivos 6 1.3 Material empleado 7 1.4 Estructura del documento 8
2 Estado de la técnica 9 2.1 Servicio de Identificación Demográfico 9
2.1.1 PIDS 10 2.2 Servicios Web 14
2.2.1 Arquitectura de software distribuido 14 2.2.2 Componentes y estándares de los Servicios Web SOAP 16
2.3 Contenedores 23 2.3.1 Arquitectura de contenedores: similitudes y diferencias con máquinas virtuales 25 2.3.2 Escenarios de uso habituales 27 2.3.3 Componentes del kernel 27 2.3.4 Orquestación 28
2.4 Cloud Computing 32 2.4.1 Modelos de servicio 33 2.4.2 Cloud computing y contenedores - CaaS 33
3 Herramientas 35 3.1 XPath 35 3.2 PostgreSQL 35
3.2.1 Arquitectura básica 35 3.2.2 Por qué PostgreSQL 36
3.3 MyBatis 37 3.3.1 Utilización de MyBatis 37 3.3.2 Características adicionales 38
3.3.3 Ventajas frente a herramientas ORM 38 3.4 API JAX-WS – proyecto Metro 39
3.4.1 Parte servidora JAX-WS 41 3.4.2 Parte cliente JAX-WS 42
3.5 AWS 43 3.5.1 Productos y servicios 43
3.6 Docker 45 3.6.1 Introducción 45 3.6.2 Arquitectura, componente y comandos básicos 45 3.6.3 Otras herramientas del ecosistema Docker 52 3.6.4 Docker en modo Swarm 53
4 Trabajo desarrollado 61 4.1 Fase 1: implementación del servicio PIDS 61
4.1.1 Situación de partida 61 4.1.2 Objetivos 63 4.1.3 Migración del sistema de gestión de la base de datos 63 4.1.4 Transformación de la gestión de sentencias 64 4.1.5 Estructura de paquetes, clases y métodos 73
4.2 Fase 2: PIDS como servicio Web 75 4.2.1 Enfoque descendente / WSDL 75 4.2.2 Implementación del servicio Web 80 4.2.3 Estructura de carpetas para el proyecto y ficheros de despliegue 83 4.2.4 Ant (secuencia de tareas) 85 4.2.5 Cliente del servicio Web 86
4.3 Fase 3: Despliegue en contenedores 89 4.3.1 Descripción de las imágenes Docker 89 4.3.2 Despliegue local 92 4.3.3 Despliegue en un clúster de nodos distribuidos 101
5 Conclusiones 127 5.1 Líneas de mejora 128
Referencias 131
Glosario 134
xvii
ÍNDICE DE TABLAS
Tabla 1–1. Norma UNE-EN 13606 5
Tabla 2–1. Clases de conformidad PIDS 12
Tabla 2–2. Especificaciones servicios Web 18
Tabla 2–3. Diferencia mensaje SOAP RCP style y Document style. 21
Tabla 2–4. Tecnologías de virtualización basadas en contenedores 31
Tabla 3–1. Comparativa MyBatis - Hibernate 39
Tabla 3–2. wsimport: equivalencia WSDL / Java 42
Tabla 4–1. Componentes MyBatis 65
Tabla 4–2. Ejemplos descriptivos de traitnames 72
xix
ÍNDICE DE FIGURAS
Figura 1-1. Estándares de interoperatividad en el dominio sanitario. 5
Figura 2-1. Modelo de referencia PIDS 10
Figura 2-2. Diagrama de herencia PIDS 11
Figura 2-3. Interacción de un conjunto de servicios Web 16
Figura 2-4. Interacción de un conjunto de servicios Web 19
Figura 2-5. Elementos WSDL 22
Figura 2-6. Diferencias WSDL 1.1 y 2.0 22
Figura 2-7. Monolíticos vs microservicios 24
Figura 2-8. Arquitectura de máquinas virtual frente a contenedores 25
Figura 2-9. PID namespace 27
Figura 2-10. MOUNT namespace 28
Figura 2-11. Responsabilidad entre cliente y proveedor según modalidad de servicio 33
Figura 3-1. Proyecto Metro 40
Figura 3-2. wsimport: equivalencia namespace WSDL / estructura de carpetas 42
Figura 3-3. Arquitectura Docker 46
Figura 3-4. Capas de una imagen 47
Figura 3-5. Tipos de volumenes 49
Figura 3-6. Pila de red 50
Figura 3-7. Redes bridge 51
Figura 3-8. Diferencia redes bridge y overlay 51
Figura 3-9. Comandos Docker 52
Figura 3-10. Descubrimiento de servicios 56
Figura 3-11. Secuencia de balanceo de carga 56
Figura 3-12. Propiedad routing mesh 57
Figura 3-13. Token de anexión al Swarm 57
Figura 4-1. ISO 13606-1:2013: Modelo de referencia 62
xxi
Figura 4-2. Interfaz pgAdmin III 64
Figura 4-3. Flujo de actividad de los componentes MyBatis 66
Figura 4-4. Estructura del proyecto de servicio Web 83
Figura 4-5. publicación servicio Web 85
Figura 4-6. Cliente NetBeans (1) 87
Figura 4-7. Cliente NetBeans (2) 87
Figura 4-8. Cliente NetBeans (3) 88
Figura 4-9. Cliente NetBeans (4) 88
Figura 4-10. Cliente GUI 88
Figura 4-11. Estructura proyecto Docker 90
Figura 4-12. Estructura proyecto Docker - Compose 92
Figura 4-13. Esquema de contenedores en despliegue local 95
Figura 4-14. Repositorio Docker Hub 100
Figura 4-15. Imágenes en Docker Hub 101
Figura 4-16. Grupo de seguridad 104
Figura 4-17. Grupo de seguridad – servicios publicados 111
Figura 4-18. Visualizer - estado del swarm 112
Figura 4-19. Visualizer - escalado 113
Figura 4-20. Propiedades del volumen dbdata – prueba de resiliencia 114
Figura 4-21. Visualizer - cambio de disponibilidad (drain) de un nodo 115
Figura 4-22. Agentes de escucha del balanceador 116
Figura 4-23. Edición de grupos de seguridad 116
Figura 4-24. Creación de grupo de destino 117
Figura 4-25. Parámetros de la creación del balanceador de carga 118
Figura 4-26. Reglas de balanceo 118
Figura 4-27. Grupos de destino 119
Figura 4-28. Grupos de destino para comunicación segura 122
Figura 4-29. Configuración de agente de escucha seguro 123
Figura 4-30. Esquema del stack deplegado en cloud 123
Figura 5-1. Escenario de partida y final 127
1
1 INTRODUCCIÓN
1.1 Contexto
El presente proyecto está contextualizado a nivel macro en la necesidad manifiesta de la transformación digital
del sector de la salud, apoyado en la aplicación fundamental de las tecnologías de la información y la
comunicación (en adelante, TIC).
La incorporación de las TIC a la asistencia sanitaria es un hecho consolidado desde hace mucho tiempo, y se ha
llevado a cabo con el objetivo de mejorar la eficacia, eficiencia y efectividad del sistema de salud, tanto los
relativos a los procesos clínicos como administrativos, en equilibrio con la calidad ofrecida. El uso de sistemas
TIC es un elemento clave para disminuir la fragmentación de los sistemas de salud e incrementar la calidad
asistencial y el grado de cobertura. La actividad propia de los servicios de salud se caracteriza por un
procesamiento masivo de información, motivo por el que las TIC se han convertido en un instrumento clínico.
E incluso, esta sinergia alcanzada con los avances tecnológicos está impulsando una evolución de los servicios
sanitarios que apuntan hacia formas de trabajo cada vez más flexibles y móviles.
A lo largo del documento se hará mención a los aspectos relacionados con la interoperatividad, uso de estándares,
representación de la información clínica, usabilidad, seguridad, privacidad, y confidencialidad que son
características a tener en cuenta para poder abordar la implementación de sistemas sanitarios. Y muy
particularmente se abordará la identificación de personas como prerrequisito clave en el proceso de
implementación.
1.1.1 Transformación digital sanitaria - Historia Clínica Electrónica
En el sector sanitario existen numerosas fuentes de datos heterogéneas que arrojan una gran cantidad de
información relacionada con los pacientes, las enfermedades y los centros sanitarios. Esta información, bien
analizada, resulta de gran utilidad para los profesionales sanitarios. En la práctica, la cantidad de datos e
información que manejan las organizaciones de salud ha obligado a rediseñar los procesos sanitarios basándose
en el uso de las TIC en todos los niveles y para todas las funciones. Contar con un sistema informatizado permite
que los gestores y los profesionales de la salud accedan oportunamente a datos confiables y estructurados,
posibilitando su procesamiento. Sin embargo, para ser efectivos, eficaces y sustentables, los sistemas de salud
deben ser gestionados de forma adecuada y coordinada. Esta gestión es el mecanismo que controla finalmente
el impacto potencial en la calidad asistencial. En un contexto tan complejo como el sanitario incorporar las TIC
a la actividad clínica se convierte en una ardua tarea. La complejidad del ámbito de la salud se debe, entre otros
motivos, a la diversidad de actores involucrados y su heterogeneidad, desde los pacientes hasta las diferentes
especializaciones profesionales e instituciones gubernamentales, cada uno con sus propios intereses, prioridades
y cultura. Todo ello provocó inicialmente que las instituciones sanitarias desarrollaran múltiples sistemas de
información especializados en la resolución de problemas concretos, quedando la información fragmentada en
diversos sistemas independientes. Dado que el acceso a la información útil era parcial, la toma de decisiones se
realizaba con información incompleta, lo que se traduce en potenciales riesgos para los pacientes.
Para garantizar el acceso a una asistencia sanitaria de calidad y segura, es preciso definir una planificación
estratégica que esté coordinada con los diversos actores del sistema de salud. Uno de sus componentes básicos
debería ser centrar en el paciente la transformación digital de los procesos mediante la introducción de las TIC.
Siguiendo este principio, la transformación digital y la implantación de una Historia Clínica Electrónica (HCE)
van de la mano: la historia clínica puede definirse como el repositorio que contiene toda la información relativa
a la salud de un paciente. Por lo tanto, es un instrumento imprescindible para que el profesional de la salud pueda
llevar a cabo su actividad y prestar al paciente la mejor atención posible en cada momento, ya que constituye el
núcleo principal de las fuentes de información de todo el sistema. Abordar la transformación digital entonces,
empezando por tener una Historia Clínica Electrónica, parece ser un paso lógico y necesario, al tiempo que se
convierte en el habilitador necesario para la transformación general de los sistemas sanitarios.
Introducción
2
Entendidos los beneficios y retos de la implantación de un sistema de Historia Clínica Electrónica, abordar este
tipo de proyectos exige cumplir con algunos requisitos indispensables, siendo los más relevantes la identificación
unívoca de las personas, la interoperatividad con otros sistemas mediante la utilización de estándares, la
adecuada representación de la información clínica, el cumplimiento de la normativa legal vigente en materia de
salud, la seguridad y confidencialidad de la información, entre otros.
En los próximos subapartados se hará una breve introducción de al par interoperatividad-estandarización para
reforzar su relevancia en el ámbito de la sanidad.
1.1.1.1 Interoperatividad
La historia de las organizaciones y de los sistemas de información en salud está marcada por la segregación y la
diversidad. Los sistemas y servicios de salud usan tecnologías, formatos y terminologías diferentes, lo que tiende
a aumentar la fragmentación de la información, y por consiguiente repercute en la disgregación de la atención
sanitaria. No obstante, si se quiere definir un proyecto global y consistente, la aproximación de una solución o
aplicación única para todo tampoco funciona. Por tanto, se reconoce la interoperatividad entre sistemas como
uno de los requisitos imprescindibles, concebido como la capacidad para que los sistemas de salud operen de
forma armónica, intercambiando y utilizando al máximo la información disponible. De este modo, la
información es compartida y está accesible desde cualquier punto de la red sanitaria en la que se requiera su
consulta y se garantiza la coherencia y calidad de los datos en todo el sistema, con el consiguiente beneficio para
la continuidad asistencial y la seguridad del paciente.
Inicialmente la interoperatividad se centraba en la comunicación, intercambio y uso de información entre
proveedores de salud y pacientes. Ante el nuevo paradigma de gestión de salud, la interoperatividad debe ampliar
su alcance y garantizar el intercambio de información entre todos los actores que participan en la gestión del
sistema de salud como son las instituciones sanitarias, aseguradoras, prestadores de servicio, agencias
gubernamentales, etc. Trasladando el concepto de interoperatividad a la HCE, el sistema no debe ser entendido
como un silo independiente, sino que requiere información de otros sistemas, tanto de la propia institución como
fuera de ella, por lo que es necesario desarrollarla teniendo en cuenta la posibilidad de intercambio de datos.
Para una interoperatividad completa no es suficiente con usar un estándar para mensajería entre sistemas, se
debe ir más allá del mero transporte de datos y tener estándares que definan la semántica de la información que
luego será transportada. Es por ello que la interoperatividad consta de diferentes niveles:
• Interoperatividad técnica: es la base de la conexión entre sistemas, incluyendo la definición las
interfaces, tanto físicos como lógicos, que permiten que las cargas funcionales puedan intercambiar
información, los mecanismos de interconexión, la presentación de los datos, la accesibilidad u otros
aspectos de naturaleza similar. Se apoya principalmente en formatos estandarizados de intercambio de
información como XML, HTML, JSON o EDI.
• Interoperatividad sintáctica u operativa: centrada en la definición de la sintaxis para la construcción
de los mensajes. Habilita el intercambio, asegurando que la información se transfiera en el formato
correcto, pero sin valorarla. Intercambio de datos, mensajes, documentos mediante el acuerdo de una
estructura sintáctica.
• Interoperatividad semántica: relativa a la interpretación homogénea de los datos intercambiados.
Implica que los sistemas entiendan la información que está procesando mediante el uso de terminologías
y ontologías médicas. Es decir, dos sistemas serán semánticamente interoperables si otorgan el mismo
valor y significado original a la información intercambiada, de forma que cada uno de ellos entiende
por sí mismo lo que el otro extremo le envía. Está dividida en dos clases, la de procesamiento distribuido
y la global. El procesamiento distribuido requiere que los sistemas acuerden previamente qué
información se intercambia, con qué formato, qué protocolo, etc. La interoperatividad semántica global
se basa en conceptos y reglas lógicas para que la información intercambiada pueda ser interpretable de
forma automática por aplicaciones que no intervinieron en su creación.
• Interoperatividad organizativa o de negocio: se sustenta en las reglas de negocio y flujos de trabajo
que regulan la participación de los distintos actores en los procesos colaborativos a nivel de las
organizaciones, con el fin de alcanzar objetivos mutuamente acordados relativos a los servicios que
prestan. En este nivel toma relevancia el conocimiento de los procesos de negocio de la organización.
3
3
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Está implícito que para lograr los máximos beneficios de la interoperatividad se necesitan mecanismos de
gobierno que permitan establecer un conjunto de normas, procedimiento, políticas, estándares, etc. En definitiva,
la interoperatividad debe estar normalizada. Hablar de interoperatividad no tiene sentido si no se hace dentro de
un marco estandarizado que defina los métodos para llevar a cabo los intercambios de información, y garantice
que la información será correctamente interpretada en diversos sistemas, implementados por distintos
proveedores y en distintas tecnologías.
La prestación integrada de servicios de salud requiere la adopción de estándares sanitarios, de tal forma que
permitan la interoperatividad y el perfeccionamiento de los sistemas de información en salud.
1.1.1.2 Estandarización
Hoy es reconocido por todos los expertos que existe la necesidad de disponer de sistemas distribuidos e
interconectados, y es por esto que es necesario la adopción de estándares en los niveles organizativo, semánticos
y tecnológicos como un elemento estratégico para la interacción y comunicación de los sistemas de Historia
Clínica Electrónica, considerados como el núcleo de la actividad clínica.
Conviene aclarar previamente que los estándares pueden ser abiertos o privados [1]. Los estándares abiertos son
aquellos que pueden ser usados por toda la industria y los desarrolladores sin pago de licencias o derechos de
uso. Por ejemplo, Digital Imaging and Communication in Medicine (DICOM), openEHR y Object Management
Group (OMG). Los estándares privados requieren un registro y/o un pago de derechos de uso y el cumplimiento
de ciertas normas para su implementación.
Por otra parte, los estándares pueden ser reconocidos a nivel internacional, regional o nacional. Siguiendo esta
clasificación se distinguen, por ejemplo, las normas internacionales ISO (International Standard
Organization), las normas del Comité Europeo de Normalización (CEN), y las normas UNE elaboradas por el
organismo de normalización nacional AENOR, a través de diferentes Comités Técnicos de Normalización
(CTN) [21]. Hay procesos que, al tratarse de aspectos técnicos de carácter general reconocidos y no depender
de normativas legales o regulaciones de uso locales, se resuelven convenientemente mediante estándares
internacionales. Sin embargo, existen otros aspectos de fuerte dependencia nacional o local, como la
identificación de personas o de profesionales, que requieren definiciones específicas en cada punto de
implementación.
Tanto para la interoperatividad sintáctica, referida a la estructura de la comunicación entre sistemas, como para
la interoperatividad semántica, referida al entendimiento de la información comunicada, el sector salud ha
desarrollado estándares para varios propósitos. Los esfuerzos de estandarización se concentran en cuatro grandes
áreas:
• Modelos para representa la información en salud, que deben ser capaces de representar la información
en diferentes niveles específicos para poder atender las necesidades tanto clínicas como de gestión, sin
dejar de garantizar que la semántica de los datos capturados sea única.
• Terminologías y modelos de conceptos. Esta es una de las áreas más tradicionales de los estándares en
salud, que incluye nomenclaturas, clasificaciones y ontologías en todas las áreas del conocimiento en
salud, como enfermedades, listas de problemas, diagnósticos, fármacos, técnicas y procedimientos,
determinaciones analíticas y laboratorios, entre otros.
• Comunicación entre sistemas y mensajería, que tiene como objetivo definir los protocolos de
comunicación, y el formato y estructura de elementos de datos utilizados para un eficaz intercambio de
información.
• Seguridad de los datos, que incluye los mecanismos físicos y lógicos que garantizan la confidencialidad,
disponibilidad e integridad de la información, como, por ejemplo, los procesos de autenticación de
usuarios. Los conceptos de firma y certificación digital también están incluidos en esta área.
Introducción
4
Así, por ejemplo, entre los estándares de comunicaciones y mensajería (interoperatividad sintáctica) se
encuentran:
• HL7 V2.X, HL7 V3. Permite el intercambio electrónico de datos demográficos, clínicos y
administrativos, y las comunicaciones entre sistemas de información sanitarios. HL7 v3 está basado
en el modelo de referencia de HL7 (Reference Informative Model o RIM), a partir del cual se
construyen las especificaciones de mensajes relativos a diferentes dominios del entorno de salud,
como contabilidad y facturación, asistencia sanitaria, soporte a las decisiones clínicas, arquitectura
de documento clínico, afirmaciones clínicas, laboratorio, entre otros.
• DICOM. Define la normalización de los registros de imágenes diagnósticas y la forma de comunicarlas.
• X12. Protocolo de comunicación estadounidense aplicado para el intercambio electrónico
multisectorial, entre ellas la salud, de transacciones comerciales, utilizando mensajería EDI y XML.
• CEN/ISO 13606. Para la comunicación de documentos clínicos digitales entre sistemas de Historia
Clínica Electrónica o entre ellos y repositorios de información clínica centralizados. El estándar está
dividido en cinco partes: modelo de referencia (Reference Model o RM), intercambio de arquetipos,
vocabularios y terminología, seguridad y especificación de las interfaces.
Con respecto a estándares de terminología o datos en salud (interoperatividad semántica), algunos de ellos son:
• CIE-10 o Clasificación Internacional de Enfermedades. Es una clasificación estadística de términos
clínicos como diagnósticos, síntomas y procedimientos.
• LOINC. Facilita el intercambio de resultados de pruebas de laboratorio, métricas y otras observaciones
clínicas.
• SNOMED CT. Es un vocabulario clínico normalizado, multilingüe y codificado de gran amplitud,
constituida por conceptos, descripciones y relaciones, que permite la representación del contenido de
los documentos clínicos para su interpretación automática e inequívoca entre sistemas distintos de forma
precisa.
Existen distintas organizaciones desarrolladoras de estándares o SDO (Standards Developing Organizations) en
el campo de la salud para todos sus niveles, entre los que destacan HL7 International, ISO/TC 215, CEN/TC
251, la Sociedad de Sistemas de Información y Gestión en Sanidad (HIMSS), la Asociación Nacional de
Fabricantes Eléctricos (NEMA), la Organización para el Desarrollo de Estándares Internacionales en
Terminología de la Salud (IHTSDO), entre muchas otros.
Esta diversidad de organismos especializados es también un claro reflejo de la fragmentación del sector de la
salud, y la necesidad de unificar criterios en pro de la interoperatividad. Hay en marcha una iniciativa de
armonización, el Joint Initiative Council (JIC), liderada por el Comité ISO (ISO/TC 215) y apoyada por Health
Level Seven International (HL7) y la Comisión Europea (CEN/TC 251), y a la que se van adhiriendo otras
organizaciones, que tiene como objetivo coordinar los esfuerzos en el desarrollo de estándares promoviendo una
línea única de actuación en las áreas donde tenga sentido. Este trabajo debe dar como resultado estándares de
mejor calidad y definidos de manera más rápida.
1.1.1.3 Estándares de la HCE
Los esfuerzos para lograr la interoperatividad semántica en la comunicación (acceder, transferir, modificar o
añadir) de la Historia Clínica Electrónica se concentran en el estándar CEN/ISO EN13606, desarrollado por el
Comité Técnico 251 del Comité Europeo de Normalización (CEN). La aportación más reconocida de la norma
reside en la definición de una estructura de información estable y consistente basado en un modelo dual, que
separa claramente la información (modelo de referencia que contiene las entidades básicas para representar
cualquier información de la HCE) y el conocimiento (modelo de arquetipos que describen semánticamente los
conceptos clínicos como combinaciones estructuradas de las entidades del modelo de referencia).
Por tanto, la adopción del estándar permite que la definición de modelos clínicos o arquetipos puedan ser
5
5
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
compartidos y reutilizados para alcanzar un proceso de normalización a nivel internacional en el que sea posible
mantener la coordinación clínica.
A nivel nacional está el estándar UNE-EN ISO 13606, asociado al comité CTN 139 que se encarga de la
normalización de las tecnologías de la información y las comunicaciones que sean o puedan ser de aplicación
en el ámbito de la salud y el bienestar en general.
La norma se compone de cinco partes, algunas de las cuales también han sido aprobadas como norma ISO:
Norma Título Comité Técnico / Subcomité
UNE-EN ISO
13606-1:2013
Informática sanitaria. Comunicación de la historia clínica
electrónica. Parte 1: Modelo de referencia. (ISO 13606-
1:2008).
CTN 139 - Tecnologías de la
información y las comunicaciones
para la salud
UNE-EN
13606-2:2007
Informática sanitaria. Comunicación de la historia clínica
electrónica. Parte 2: Arquetipos (Ratificada por AENOR
en octubre de 2007.)
CTN 139 - Tecnologías de la
información y las comunicaciones
para la salud
UNE-EN
13606-3:2008
Informática sanitaria. Comunicación de la historia clínica
electrónica. Parte 3: Arquetipos de referencia y listas de
términos
CTN 139 - Tecnologías de la
información y las comunicaciones
para la salud
UNE-EN
13606-4:2007
Informática sanitaria. Comunicación de la historia clínica
electrónica. Parte 4: Seguridad.
CTN 139/SC 1 - Sistemas de
información e historias clínicas
UNE-EN ISO
13606-5:2010
Informática sanitaria. Comunicación de la historia clínica
electrónica. Parte 5: Especificación de interfaces. (ISO
13606-5:2010)
CTN 139 - Tecnologías de la
información y las comunicaciones
para la salud
Tabla 1–1. Norma UNE-EN 13606
Otros estándares relacionados con aspectos normalizados de la historia clínica electrónica:
• UNE-EN ISO 10781:2015. Informática sanitaria. Modelo funcional de un sistema de historia clínica
electrónica. Publicación 2 (MF HCE) (ISO 10781:2015) (Ratificada por AENOR en octubre de 2015.)
• UNE-CEN ISO/TS 14441:2013. Informática sanitaria. Requisitos de seguridad y privacidad del
sistema de HCE para la evaluación de la conformidad (ISO/TS 14441:2013) (Ratificada por AENOR
en marzo de 2014.)
• UNE-EN ISO 27789:2013. Informática sanitaria. Auditorías de seguimiento de la historia clínica
electrónica (ISO 27789:2013) (Ratificada por AENOR en abril de 2013.)
Figura 1-1. Estándares de interoperatividad en el dominio sanitario.
Introducción
6
1.1.1.4 Identidad unívoca
La identificación inequívoca del paciente es una necesidad esencial de toda actividad en el área de la salud.
Teniendo en cuenta que el objetivo de registrar la información clínica es garantizar que esté accesible durante
todo el proceso asistencial y se pueda consultar en etapas posteriores, un primer riesgo evidente es la posibilidad
de cometer errores en la identificación de las personas que intervengan en el proceso. Por tanto, una primera
condición indispensable al iniciar proyectos de HCE se refiere a normalizar la identificación única de todas las
personas, profesionales, prestadores y proveedores que forman parte del sistema sanitario.
Se da la circunstancia que, teniendo en cuenta la dimensión de los servicios de salud, la complejidad de su
actividad y el elevado número de pacientes y profesionales que se encuentran bajo su cargo, un sistema completo
de salud está compuesto de diversos subsistemas o centros auxiliares, la mayoría de los cuales tratarán datos
personales de los pacientes, que pueden mantener su propio dominio local de identificación de pacientes. Es
precisa una adecuada gestión de los procedimientos de identificación que permita intercambiar información
entre sistemas y evitar que la información se duplique o se encuentre desagregada, sin perjuicio sobre la calidad
de los procesos asistenciales, clínicos y administrativos ni riesgo para la propia seguridad y privacidad de
los pacientes.
1.2 Motivación y objetivos
La evolución de las tecnologías de la e-salud, y por ende de cualquier sistema de información que lo integre,
está marcada por los requisitos de interoperatividad y estandarización. La necesidad de aplicar estos dos
conceptos sobre un sistema de información demográfico en el contexto de un sistema sanitario constituye el eje
central de la motivación que ha conducido al planteamiento del presente proyecto de fin de carrera.
El proyecto arranca desde un servicio de identificación de personas desarrollado en un proyecto previo donde
también se había llegado a definir e implementar sobre una base de datos la estructura de datos basados en
estándares europeos de información sanitaria. No obstante, todo el desarrollo se haría sobre una arquitectura
monolítica y local. Salvado el diseño del modelo de referencia, que no será modificado, la aportación de este
proyecto está enfocada en abordar mejoras sobre el desarrollo inicial y cubrir los requisitos de interoperatividad
técnica conforme a los siguientes objetivos generales:
• Ampliar las capacidades funcionales del sistema de información demográfico de partida para que,
además de identificar de forma unívoca a una persona en base a ciertas características distintivas de la
misma y recabar la información demográfica de una persona a partir de su identificador, también
actualice o borre los atributos registrados de las personas. Adicionalmente, todas estas tareas consultivas
y de actualización deben poder realizarse sobre cualquier tipo de atributo.
• Remodelar la aplicación bajo los principios de bajo acoplamiento y alta cohesión, segregando la lógica
del sistema de la capa de persistencia para dotarla de mayor flexibilidad.
• Permitir una fácil integración operativa con otros sistemas para el intercambio estandarizado de la
información. Teniendo en cuenta la heterogeneidad tecnológica de los sistemas en el ámbito sanitario,
y siendo el sistema de identificación de personas un componente común para todos ellos, es fundamental
envolver el desarrollo objeto del proyecto como un servicio Web, a fin de transformarlo en un sistema
abierto e independiente de la plataforma o lenguaje de los clientes que lo consuman.
• Desplegar el sistema en un entorno que garantice la accesibilidad requerida por la habitual dispersión
geográfica de la red asistencial. En este sentido, también debido a la creciente demanda de servicios de
salud, la tendencia en el sector es aprovechar las ventajas operativas y económicas que ofrece la
tecnología de cómputo en el cloud.
Adicionalmente, el aprovisionamiento del sistema mediante una arquitectura de virtualización basado en
contenedores orquestados dará cobertura a otros objetivos relacionados con proveer características propias de
sistemas productivos y tener un acercamiento a los modelos de desarrollo actuales, tales como:
7
7
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
• Conseguir un mayor rendimiento y expansión mediante el balanceo de carga y escalabilidad horizontal.
• Establecer una arquitectura que incremente el nivel de tolerancia a fallos.
• Tener una primera aproximación al enfoque basado en microservicios, alejada de la arquitectura
monolítica desde la que parte el proyecto.
• Posibilitar la automatización del despliegue, independientemente de la plataforma o entorno destino, e
integración de manera sencilla en flujos de integración continua.
1.3 Material empleado
Se han utilizado diferentes componentes, librerías software y herramientas para conformar el entorno de trabajo
empleado en la realización del proyecto. Se enumeran a continuación:
Plataforma base: Máquina virtual VirtualBox
Sistema operativo: Ubuntu Linux 16.04 LTS (64-bit)
Prestaciones hardware:
CPU: Intel(R) Core(TM) i7-4510U CPU @ 2.00GHz
RAM: 3GB
Disco: 30GB
Herramientas de desarrollo:
Oracle(R) Java(TM) SE Development Kit 1.8.0_131
Ant 1.9.6
NetBeans IDE 8.2
Librerías adicionales:
Metro (JAX-WS) 2.3.1
Controlador JDBC - PostgreSQL 42.1.4
MyBatis 3.4.5
JAXB2 Basics Plugins 0.11.0
Entorno de trabajo:
Tomcat 8.5.30
PostgreSQL 8.5 + PGAdmin III
Docker 17.09.0-ce
Docker Machine 0.13.0
Docker Compose 1.17.1
Plugin de almacenamiento Docker REX-Ray 0.11.2
Cuenta de Amazon Web Services (AWS)
A lo largo de la memoria se irán aportando detalles de cada elemento y su implicación en el desarrollo del
proyecto.
Introducción
8
1.4 Estructura del documento
Tras la presentación de la motivación y objetivos generales perseguidos en el proyecto, el resto de los capítulos
del proyecto se organizan de la siguiente manera:
Capítulo 2: Estado del arte. En este capítulo se revisa el contexto tecnológico teórico en el que está circunscrito
el proyecto. Se abordan los conceptos tecnológicos generales sobre los que se fundamenta la propuesta de
servicio desarrollada, haciendo un repaso a la evolución que han seguido, los beneficios aportados, algunas
soluciones que los implementan y su posicionamiento en el ámbito del desarrollo software actual.
Capítulo 3: Herramientas. Dedicado a la descripción de las herramientas, servicios y componentes particulares
que han sido relevantes en el desarrollo del proyecto. Se analizan en detalle las características, funcionamiento
y aportación general al proyecto con la idea de ser lo más específico posible en la explicación de las actividades
desempeñadas durante el diseño y construcción del servicio de información demográfico. Para algunas de ellas
se aporta una comparación con otras alternativas directas y la justificación de su selección.
Capítulo 4: Trabajo desarrollado. Capítulo que concentra todo el proceso de desarrollo y configuración de la
solución planteada, a partir de las herramientas tratadas en los capítulos anteriores. Incluye el detalle de las
actividades llevadas a cabo en cada fase de la transformación del servicio de información demográfico, desde la
ampliación funcional de la aplicación hasta su despliegue en un entorno Cloud basado en contenedores, pasando
por el diseño de la misma como servicio Web.
Capítulo 5: Conclusiones y líneas de mejora. Finalmente se concluye con una valoración de los resultados
logrados frente a los objetivos establecidos, y la propuesta de varias opciones de mejora en la línea de
continuidad del proyecto.
9
2 ESTADO DE LA TÉCNICA
2.1 Servicio de Identificación Demográfico
Una persona a lo largo de su vida será atendida en múltiples centros asistenciales de diferentes organizaciones,
que utilicen diferentes esquemas de identificación, lo que obliga a extender el requisito de la identificación única
de un paciente a dominios de escala regional, nacional e incluso internacional, para habilitar la continuidad
asistencial en un marco de interoperatividad entre sistemas de salud de múltiples instituciones.
No en todos los casos la solución al problema será contar un identificador universal, sujeto a numerosos
problemas de registro reconocidos, y será necesario complementar con criterios alternativos, a partir de
conjuntos de datos demográficos dados, mediante los que resolver las referencias cruzadas de un mismo paciente
y verificar la identidad de una persona. La información más apropiada para identificar a una persona es aquella
cuyos atributos perduren o cambien poco en el tiempo. Se producen situaciones, por ejemplo, en las que el
paciente carece de un documento que acredite su identificador o no lo tiene disponible. Son precisos servicios
de identificación que contemplen tanto el proceso de acreditación de la identidad como la correlación de datos
demográficos.
Los servicios de identificación demográficos nacen con la finalidad de unificar el proceso de asignación e
identificación de las personas en el entorno sanitario, de forma que garantice el acceso de los profesionales
sanitarios a la información sobre los pacientes en términos de calidad y continuidad asistencial. Algunas
especificaciones relacionadas con los servicios de identificación son [2]:
• Person Indentification Service (PIDS) es la especificación del servicio incluido en CORBAmed, grupo
de trabajo perteneciente a la organización internacional OMG (Object Management Group), para la
gestión de identidades basado en un modelo de federación de dominios. Provee de una interfaz con la
que sería posible conectar diferentes modelos de personas pre-existentes, con diferentes formatos de
registro, de manera federada para la interoperatividad entre ellos. De esta forma la especificación
consigue que la mayoría de los sistemas de identificación de personas existentes participen como
componentes de un entorno de integración más amplio.
PIDS es la especificación en la que se basa este proyecto para la implementación de servicio de
identificación demográfico.
• Entity Indentification Service (EIS) es uno de los servicios que componen el proyecto HSSP
(HealthCare Services Specification Project), fruto de un acuerdo conjunto entre HL7 y OMG. Va un
paso más allá del PIDS, proporcionando un conjunto de interfaces de servicios para identificar de
manera unívoca diferentes tipos de entidades, no sólo pacientes individuales, en el ámbito de una
organización de salud o en un conjunto de ellas.
• Perfiles de integración del Integrating the Healthcare Enterprise (IHE), describen casos de uso de
gestión de la información clínica y especifican cómo usar los estándares existentes (HL7, IETF,
DICOM, ISO, etc.) para abordarlos. Es decir, los sistemas interdepartamentales que implementan
perfiles de integración resuelven problemas de interoperatividad. Concretamente, el perfil Patient
Identifier Cross Referencing (PIX) soporta la referencia cruzada de identificadores de pacientes de
varios dominios a partir de un registro maestro de identificadores de pacientes que puede ser consultada
por otras aplicaciones, y el perfil Patient Demographics Query (PDQ) que permite que aplicaciones
consulten un servidor central para recuperar la información demográfica y de visita de un paciente.
• Servicios nacionales basados en identificadores únicos como el Personal Demographics Service (PDS)
del Sistema Nacional de Salud del Reino Unido (NHS).
En el sistema sanitario español, con objeto de disponer de datos normalizados de cada persona, se
emiten dentro de cada Comunidad Autónoma tarjetas sanitarias individuales (TSI). Las tarjetas
sanitarias incorporan una serie de datos básicos comunes, vinculadas a un código de identificación
Estado de la técnica
10
personal único para cada ciudadano en el Sistema Nacional de Salud. Cada Comunidad Autónoma
dispone de una base de datos de TSI (BDTSI-CA) que se vuelca su información sobre una base de datos
de TSI común (BDTSI-SNS). El Ministerio de Sanidad, Servicios Sociales e Igualdad (MSSSI) es el
responsable de asignar un Código de Identificación Personal para cada persona y vincularlo a otros
códigos personales pueda tener en los distintos territorios del Estado.
2.1.1 PIDS
El servicio de identificación de personas (PIDS) de CORBAmed [3] surge como especificación para abordar la
interoperatividad entre múltiples instituciones, cubriendo la necesidad de identificar pacientes de forma
consistente.
PIDS está diseñado para:
• Soportar de manera simultánea la asignación de identificadores de persona (ID) dentro de un dominio
particular y correlacionar identificadores entre múltiples dominios.
• Agilizar las búsquedas y obtención de coincidencias, independientemente del algoritmo de
coincidencia.
• Apoyar la federación de servicios PIDS independientemente de la topología.
• Proteger la confidencialidad de las personas mediante diversas políticas de confidencialidad y
mecanismos de seguridad.
• Habilitar la interoperatividad entre PIDS que implementen las funcionalidades centrales de la
especificación, y admita la personalización y extensión específicas del servicio.
• Definir diferentes niveles de cumplimiento, según el grado de sofisticación, que van desde consultas
dentro de un único dominio hasta el funcionamiento correlacionando ID en dominios federados grandes.
2.1.1.1 Modelo de Referencia de dominio
El esquema propuesto por CORBAmed PIDS proporciona una solución para soportar la identificación de
pacientes a través de varios organismos de salud. Cada organismo de salud asigna ID que identifican a pacientes
dentro de su dominio local. Fuera de ese sistema u organización dichos ID carecen de significado. PIDS sirve
para federar distintos dominios, generando un lugar en donde los datos de los diferentes dominios se
homogenicen en un único conjunto de datos mínimos. Así, la información sensible de los pacientes permanece
almacenada localmente, con identificadores locales, de forma que solo se relacionan con el identificador del
nivel superior cuando sea necesario transmitirlos.
Figura 2-1. Modelo de referencia PIDS
11
11
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Con este modelo los pacientes pueden ser identificados en cada punto de atención, sea el caso de centros
auxiliares, laboratorios o emergencia, entre otros. Pero siempre se mantiene la relación con los dominios
superiores que cuentan con datos básicos y son el puente de interconexión entre los dominios inferiores. En la
figura se representa el modelo de referencia para una infraestructura sanitaria compuesto de diversos dominios
de identificación, donde la organización de salud es la encargada de establecer la relación entre ellos.
2.1.1.2 Modelo conceptual
Para caracterizar cualquier atributo demográfico tratado en el servicio, PIDS define una estructura de tipos que
se emplea en todas sus interfaces. Entre los más destacados, el tipo Trait se utiliza para contener cualquier
dato demográfico. Se compone de un campo de nombre, de tipo TraitName, y un campo de valor, que puede
ser de cualquier tipo. Un conjunto de traits se declara con el tipo Profile.
Los restantes tipos de datos se explicarán junto con la descripción de las interfaces que implementa el servicio
de identificación. No obstante, la especificación no restringe ni concreta ningún modelo de datos específico para
representar los atributos demográficos; simplemente se limita a definir la entidad genérica Trait que los
contiene, dejando la posibilidad de usar el modelo de referencia que mejor convenga. Como se ha visto
anteriormente, hay estándares que introducen en sus especificaciones modelos de información con los que
facilitar la integración semántica del sistema. Algunos ejemplos son el RIM de HL7, el RM de openEHR o la
primera parte del CEN/ISO 13606.
2.1.1.3 Interfaces
La especificación provee de interfaces tanto para la gestión de los ID como para el acceso a los datos
demográficos, todas heredadas de un componente, IdentificationComponent, en la que se concentran
las características de las personas emparejadas con un ID.
Figura 2-2. Diagrama de herencia PIDS
Estado de la técnica
12
Cada interfaz representa una funcionalidad concreta. En función del número de interfaces completas que
implemente el servicio PIDS, lo cual exige cumplir estrictamente con todas las operaciones, atributos,
excepciones y comportamiento esperado para esas interfaces, obtendrá una clase de conformidad diferente con
respecto a la especificación. En la siguiente tabla se resumen los distintos tipos de conformidad junto con las
interfaces que los componen:
Clase Conformidad IdentifyPerson ProfileAccess SequentialAccess IDMgr IdentityAccess CorrelationMgr
Simple PIDS * *
Sequencial Access PIDS * * *
ID Domain Mgr PIDS * * *
Identity Access PIDS * *
Correlation PIDS *
Tabla 2–1. Clases de conformidad PIDS
• Simple PIDS: provee las operaciones básicas para acceder al perfil de un ID y recuperar posibles ID
potenciales que coincidan con algunas características especificadas. Es el tipo de servicio que
implementaría normalmente un sistema auxiliar.
• Sequencial Access PIDS: añade la capacidad de acceder secuencialmente al conjunto de ID.
• ID Domain Mgr PIDS: agrega la capacidad de crear nuevos ID y modificar los estados de los ID
existentes.
• Identity Access PIDS: alternativa al Simple PIDS, proporcionando la misma funcionalidad, pero usa la
interfaz IdentityAccess en lugar de ProfileAccess. De esta forma puede tener diferentes políticas de
acceso para cada ID.
• Correlation PIDS: contiene una única interfaz, que proporciona toda la funcionalidad para cargar y
acceder a los perfiles e ID correlacionados entre los dominios participantes. Se puede combinar con los
otros tipos de conformidad.
Uno de los objetivos de este proyecto será ampliar la implementación de un servicio PIDS de clase "Simple
PIDS", que incluye las interfaces básicas de consulta a la información demográfica. A continuación, se describen
las interfaces que componen el servicio implementado.
Interfaz IdentifyPerson
Esta interfaz proporciona una forma de encontrar los identificadores de personas que concuerdan con un
conjunto de atributos, considerando su relevancia. Tiene un único método, find_candidates, que devuelve
el listado de candidatos que se ajusten con un conjunto de atributos o perfil especificado, por encima de un
umbral de confianza también dado. En la llamada al método se indica también los atributos de los candidatos
potenciales que se quieren conocer. Los parámetros de entrada y salida del método se describen a continuación:
Parámetros de entrada
o profile_selector: conjunto de atributos conocidos de la persona que se busca. Es una
secuencia de elementos de tipo TraitSelector. El TraitSelector es un parámetro
que identifica un atributo de la persona a buscar. Se compone de un Trait y un campo de
peso relativo, a modo de valor de ponderación que tiene la coincidencia del Trait en la
búsqueda de los candidatos. El uso del campo peso no está estandarizando, por lo que
13
13
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
dependerá de la implementación particular.
o states_of_interest: conjunto de estados (IdState) en los que se deben encontrar los
identificadores de las personas que se buscan.
o confidence_threshold: representa el umbral de confianza. Los candidatos que no
tengan un nivel de coincidencia por encima de este umbral se descartan.
o sequence_max: número máximo de candidatos que se quiere recibir en la llamada. Si el
servicio encuentra más coincidencias, se devuelven en un iterador.
o iterator_max: número máximo de candidatos que se debe devolver en el iterador.
o traits_requested: Atributos que se devolverán para cada candidato. Es una secuencia
de elementos de tipo TraitSpec, que son nombres de atributos para los que el servicio debe
encontrar su valor, junto con algunas características del propio atributo como si es de lectura,
obligatorio o si se puede usar en la búsqueda de personas.
Parámetros de salida
o returned_sequence: secuencia de candidatos devueltos que coinciden parcialmente con la
búsqueda de personas realizada. Contiene el ID de la persona, el campo de confianza de determina
el nivel de coincidencia con el perfil de búsqueda y el conjunto de atributos solicitados en la
llamada.
o returned_iterator: contiene los candidatos que exceden del número máximo requerido en
la consulta.
Interfaz ProfileAccess
Esta es la interfaz principal para acceder a los atributos asociados con un ID. Proporciona los métodos
elementales de un servicio de identificación de personas. Estos métodos son:
• get_traits_known: A partir de un identificador de persona (PersonId) el servicio devuelve un
elemento TraitNameSeq, que contiene todos los nombres de todos los atributos conocidos para dicha
persona.
• get_profile: A partir de un identificador de persona (PersonId) devuelve el conjunto de
atributos, mediante un elemento de tipo Profile, que han sido solicitados en llamada a través de un
elemento de tipo SpecifiedTraits.
• get_profile_list: Es un mecanismo abreviado para obtener perfiles para más de una
identificación a la vez. A partir de una secuencia de ID (PersonIdSeq) devuelve el conjunto de
atributos solicitados los perfiles para cada uno de ellos, mediante una secuencia de elementos de tipo
TaggedProfile. Cada elemento TaggedProfile que incluye un perfil y la identificación
asociada a él.
• get_deactivated_profile_list: Similar al método get_profile_list salvo que
permite obtener perfiles de IDs cuyo estado sea DEACTIVATED (en desuso).
• update_and_clear_traits: Modifica el perfil de un ID. Se pasa como parámetro de entrada
una secuencia de elementos de tipo ProfileUpdate. Este tipo contiene un identificador, el nombre
de los atributos que deben borrarse del perfil y los nuevos valores de los atributos que deben
modificarse.
• get_id_info: Devuelve el estado de cada ID introducido.
Estado de la técnica
14
2.2 Servicios Web
Se inicia el apartado con una breve revisión de las arquitecturas de software distribuido y su evolución. Será
necesario para comprender los motivos que condicionaron a reformular el servicio de información demográfico
desde el que parte el proyecto, planteado como sistema monolítico ejecutado en un entorno local, para
desarrollarlo como un servicio Web y así satisfacer los requisitos de disponibilidad, interoperatividad y
explotabilidad exigidos por el contexto global del proyecto.
2.2.1 Arquitectura de software distribuido
A lo largo del tiempo la forma en que se han desarrollado las aplicaciones informáticas ha ido cambiando,
tendiendo siempre hacia un diseño del software cada vez más modular, simplificado y distribuido.
El software distribuido es aquel en que sus elementos pueden comunicarse entre sí, aunque estén en máquinas
diferentes a través de una red de comunicaciones. Tradicionalmente se parte del protocolo RPC (Remote
Procedure Call) para la comunicación en entornos distribuidos, con el inconveniente de su alto grado de
acoplamiento con la tecnología empleada. Ejemplo de lenguajes que pueden operar en entornos distribuidos
encontramos Java con su RMI (Remote Method Invocation), la tecnología DCOM de Microsoft y CORBA
(Common Object Request Object Architecture) que es un estándar definido por la organización OMG (Object
Management Group). Pero a medida que van creciendo los requisitos de integración entre sistemas de diferentes
tecnologías para el intercambio de información y la cooperación funcional, se va consolidando un enfoque de
diseño orientado en servicios. Las etapas generales en el proceso de diseño de un servicio son:
• Identificación del servicio: consiste en identificar la funcionalidad concreta que va a ser expuesta como
un servicio. Idealmente, la funcionalidad seleccionada debe tener una alta granularidad, sin
dependencias y con potencial para su reutilización, de forma que pueda ser aprovechado de forma
trasversal por varios procesos.
• Diseño: referido a la definición de la interfaz del servicio y así como sus parámetros de entrada y salida.
• Implementación: comprende la selección de la tecnología con la que ejecutar la funcionalidad
seleccionada como servicio.
Otro escenario habitual para el diseño software orientado a servicios se produce bajo la necesidad de
interoperatividad e integración con otros componentes software de sistemas heredados que no pueden ser
reconstruidos.
Así, los principios que caracterizan los servicios frente a los modelos tradicionales de arquitectura distribuida
son, entre otros:
• Contrato de descripción del servicio: los contratos definen la especificación de las interfaces del
servicio ofrecido, incluyendo las operaciones permitidas, los tipos y modelo de datos, y los mensajes
necesarios que se intercambiarán para garantizar la estandarización y granularidad de los
servicios. Constituye la base de la arquitectura de software orientada a servicio.
• Bajo acoplamiento: el acoplamiento se refiere al nivel de dependencia entre los sistemas implicados.
Cuanto menor sea el acoplamiento, mayor independencia tendrá el servicio respecto de su
implementación y tecnología empleada. Esta circunstancia hará más sencillo el desarrollo y evolución
de la lógica de negocio del servicio, al no tener impacto sobre los consumidores del servicio por no
haberse alterado el contrato que lo describe.
• Abstracción: está estrechamente relacionada con el concepto de contrato y bajo acoplamiento. Se refiere
a limitar la definición del servicio con la información estrictamente esencial para entender su propósito,
operaciones y mensajes, evitando contener cualquier interpretación técnica de la lógica que la
implementa. Así, el bajo acoplamiento se consigue con niveles adecuados de abstracción en el diseño
del contrato.
• Reusabilidad o multipropósito: la segmentación de la funcionalidad en servicios abstractos permite que
éstos sean reaprovechados por otros servicios y/o sistemas. Cuanto más genérica sea la funcionalidad
15
15
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
del servicio, mayor será la reusabilidad potencial del mismo. El diseño óptimo de los contratos en la
definición de los servicios abstractos influirá en gran medida sobre su reusabilidad.
Nota: La evolución del modelo orientado a servicios, enfocada en lograr una mayor modularidad de la lógica
de negocio, ha derivado en la aparición del concepto de microservicios. El beneficio directo perseguido por
una arquitectura de microservicios es la agilidad y velocidad para hacer cambios.
Atendiendo a los principios enunciados, se puede entender que las arquitecturas orientadas a servicios estén
estrechamente asociadas a los denominados servicios Web.
Los servicios Web, según la definición de W3C (siglas de World Wide Web Consortium), "[…] es un sistema
de software diseñado para permitir la interoperatividad de máquina a máquina a través de una red". Aquí la
clave está en el concepto de interoperatividad, que permite utilizar los servicios Web para integrar sistemas de
diferentes lenguajes de programación y que se ejecutan en plataformas diferentes, habilitando un mecanismo de
comunicación entre componentes software totalmente independientes a través de la red con plena compatibilidad
y lograr crear sistemas más complejos. Todo esto lo consigue basándose en estándares abiertos, que han sido
adoptados por la industria del software, para la descripción de la interfaz de las operaciones ofrecidas por el
servicio, la creación y codificación de los mensajes de comunicación, y el transporte de los mensajes mediante
protocolos de Internet.
A nivel conceptual, un servicio es una funcionalidad software proporcionada a través de un endpoint accesible
a través de la red. Existen principalmente dos estilos diferentes para el diseño y desarrollo de servicios Web, los
basados en el protocolo SOAP (Simple Object Access Protocol) y los que siguen el estilo de arquitectura
software REST (Representational State Transfer).
Servicios Web SOAP
Los servicios SOAP, o directamente conocidos como Web Services, se basan en el intercambio de
información estructurada para intercomunicarse sobre la red conforme al protocolo SOAP. SOAP es un
protocolo de comunicación basado en XML que describe la arquitectura y el formato de los mensajes a
intercambiar cuando se invoca un servicio Web. Los mensajes SOAP se transmiten usando protocolos
de comunicación como HTTP, SMTP o FTP, aunque el caso más habitual es mediante HTTP.
El diseño de un servicio Web requiere establecer un contrato formal en el que se describa la interfaz que
ofrece el servicio Web. Un documento WSDL (Web Service Description Language) servirá para
construir en XML estos contratos estandarizados, incluyendo información sobre qué funciones están
disponibles en el servicio, cómo invocarlo, dónde encontrarlo, y qué tipos de datos se usan en las
operaciones. Se puede ampliar para especificar otros requerimientos no funcionales como los
relacionados con las transacciones, la seguridad o la coordinación.
Servicios Web RESTful
Los servicios Web RESTful son básicamente servicios Web que implementan la arquitectura software
REST. Sin entrar en detalle, REST constituye un conjunto de principios arquitectónicos que define la
interacción entre distintos componentes. Sustituye el concepto de servicio por el de recurso como el
elemento sobre el que se definen operaciones y que es accesible mediante un identificador único global.
Las restricciones establecidas por la arquitectura REST ya vienen implementadas en el protocolo HTTP,
de ahí a que los servicios Web RESTful estén perfectamente integrados con HTTP, y utilicen sus
métodos y códigos de respuesta para implementar los principios de la arquitectura REST. De esta forma,
se simplifica la comunicación entre aplicaciones ya que los datos se envían directamente sobre el
protocolo HTTP, sin necesidad de emplear un protocolo de comunicaciones como SOAP, ni
definiciones de servicio en forma de fichero WSDL.
Estado de la técnica
16
Los servicios Web, y particularmente los basados en SOAP, satisfacen de forma intrínseca muchos de los
principios básicos comentados de la arquitectura orientada a servicios. Se logra en gran medida a partir de la
definición abstracta del servicio mediante un lenguaje de descripción de servicios (WSDL) que, a su vez, actúa
como contrato estandarizado de la interfaz de comunicación entre los servicios. Además, mediante WSDL se
consigue un desacoplamiento de la lógica del servicio, de forma que al invocar un servicio Web simplemente
hay que conocer las operaciones disponibles, sin tener detalles sobre la implementación del mismo ni que ello
tenga algún impacto sobre los clientes que lo consume. Atendiendo a la reusabilidad de los servicios Web,
conceptualmente es posible ver los servicios Web como unidades de trabajo que pueden combinarse e interactuar
con otros servicios para reaprovechar las funcionalidades ofrecidas y realizar operaciones complejas.
Todo lo anterior contribuye a potenciar la interoperatividad entre sistemas, promoviendo nuevos escenarios de
colaboración empresarial o integración entre sistemas corporativos, con independencia de la tecnología o
plataforma que los soporten. Ya se comentó en un apartado anterior que uno de los principales retos en la
evolución de la innovación en el campo de la sanidad era la interoperatividad entre organismos, mediante la
estandarización del intercambio de mensajes en un escenario compuesto por sistemas heterogéneos, entre
otros requisitos. Los sistemas sanitarios han encontrado en los servicios Web un importante aliado para
salvar sus requisitos de integración.
Figura 2-3. Interacción de un conjunto de servicios Web
Los siguientes apartados se centrarán en describir las características y funcionalidades de los servicios Web
SOAP, al ser el método seleccionado para exponer la lógica del servicio de información demográfica objeto del
proyecto.
2.2.2 Componentes y estándares de los Servicios Web SOAP
Los Servicios Web involucran una familia de estándares abiertos para describir y suministrar servicios web e
interactuar con ellos, y cuyas especificaciones han sido promovidas por organizaciones o consorcios,
constituidos tanto por proveedores de productos tecnológicos como por organismo de estandarización. Entre
estas organizaciones implicadas en el desarrollo de estándares relacionados con los servicios Web, los más
destacados son W3C, OASIS y WS-I.
W3C (World Wide Web Consortium)
Fundada en 1994 por Tim Berners-Lee, W3C es una comunidad internacional donde diferentes
organizaciones, procedentes de diversos puntos del mundo y de campos muy diferentes, forman parte
del W3C para el desarrollo de estándares Web de alta calidad. La actividad que desempeñan respecto a
los servicios Web es el diseño de la infraestructura, definición de la arquitectura y creación de la
tecnología central de los servicios Web. Entre los estándares que han desarrollado para los servicios
Web se encuentran SOAP y WSDL.
17
17
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
OASIS (Organization for the Advancement of Structured Information Standards)
OASIS es un consorcio internacional, sin ánimo de lucro, centrado en especificaciones relacionadas con
el comercio electrónico y la seguridad. Sus contribuciones más importantes para los servicios Web son
el servicio de registros UDDI, el lenguaje BPEL para la composición de servicios Web y la
especificación WS-Security sobre seguridad.
WS-I (Web Services Interoperability Organization)
WS-I es el organismo creado para fomentar la interoperatividad entre diferentes implementaciones de
Servicios Web. Publicó el WS-I Basic Profile, una guía con las mejores prácticas sobre cómo deben
usarse correctamente los estándares, como XML, SOAP, WSDL o UDDI, para ayudar a la comunidad
a desarrollar e implementar servicios web interoperables. Pasó a formar parte de OASIS y finaliza su
actividad en diciembre de 2017.
Más adelante se entrará en detalle a describir los estándares y protocolos que, como tales, conforman los
fundamentos para el desarrollo de los servicios Web, independientemente de la plataforma donde se construyan
dichos servicios. Estos estándares se han ido complementando posteriormente con la aparición de
especificaciones que extienden a SOAP con la intención de cubrir las carencias iniciales de la especificación de
los servicios web, y dotarles de flexibilidad y modularidad en el ámbito de áreas como la calidad del servicio, la
seguridad, el procesamiento distribuido de transacciones, etc. Algunas de las extensiones, denominadas WS-*,
son las siguientes:
• Attachments – Permite incluir documentos no XML, como archivos binarios o imágenes.
• Routing/Intermediaries – Relacionadas al proceso de enviar mensajes SOAP a través de intermediarios.
Permite agregar varios servicios Web y ofrecerlos como parte del paquete, incrementando la
escalabilidad de los servicios.
• WS-Security – Proporciona un marco de seguridad para la comunicación. Así, el envelope SOAP
soporta integridad del mensaje, confidencialidad y autenticación. Describe el uso de certificados X.509,
firmas digitales, tockets Kerberos, etc.
• WS-Policy – Es una ampliación del WS-Security. Permite configurar más detalle cómo y quién puede
usar un servicio Web.
• WS-Addressing – Define cómo se debe representar la dirección e instancia concreta del servicio en un
mensaje SOAP. Necesario para declarar servicio stateful.
• WS-ReliableMessaging – Establece las normas para el intercambio fiable de mensajes.
• Quality of Services – QoS es una medida de la calidad del servicio; con esta extensión es posible
caracterizar el funcionamiento del servicio.
• Context/Privacy – Relacionada con la WS-Security, hace referencia a guardar el contexto y la
privacidad del entorno de los usuarios.
No obstante, estas extensiones no serán objeto del estudio en el proyecto y serán propuestos como puntos de
mejora futuros, especialmente la especificación WS-Security relativa a la implementación de la seguridad en el
intercambio de mensajes para garantizar su integridad y confidencialidad, y la autenticación de los extremos.
De forma resumida, XML es el metalenguaje con el que se construyen los demás estándares, SOAP es la
implementación de los mensajes en los servicios web, WSDL servirá para construir contratos estandarizados
con soporte de XML Schema, y UDDI registrará el WSDL del servicio para que sea publicado y localizable.
Estado de la técnica
18
Protocolo
Capa Descripción
HTTP Hypertext
Transfer
Protocol
Transporte del
servicio
Responsable de transportar los mensajes entre las aplicaciones o
servicios. En realidad, puede usarse cualquiera de los protocolos
estándar de Internet (SMTP, FTP, POP), siendo el más común el
protocolo HTTP.
SOAP Simple Object Access Protocol
X
M
L
Mensajería XML
Detalla la forma en que se construyen los mensajes en XML que
van a ser intercambiados durante el proceso de comunicación
entre servicios. Las principales características del protocolo son la
extensibilidad (se pueden ampliar fácilmente con nuevas
especificaciones), neutralidad (puede usarse con cualquier
protocolo de transporte) y la independencia (puede usar
independientemente del lenguaje de programación o arquitectura
usada)
WSDL Web Service
Description Language
Descripción del
servicio
Define de forma estándar, utilizando XML Schema, el modo de
describir la interfaz pública de un determinado servicio,
incluyendo desde la forma que deben tener de los mensajes hasta
la localización a la que deben ser enviados.
UDDI Universal
Description
Discovery and Integration
Publicación y
localización
Especifica la forma de publicar servicios Web en un registro y
cómo puede consultarse para descubrir servicios que estuvieran
disponibles.
Inicialmente estaba pensado para ser un registro global, pero este
objetivo no llegó a consolidarse. Se emplea como registro interno
de servicios y descripción de servicios.
Como alternativa o complemento a UDDI, la especificación WSIL
(Web Service Inspection Language) propone un enfoque
alternativo para el descubrimiento de servicios distribuidos, sin
necesidad de consultar a un registro centralizado, preguntando
directamente al proveedor de servicios sobre los servicios que
proporciona.
Tabla 2–2. Especificaciones servicios Web
2.2.2.1 XML
XML (Extensible Markup Language) es un metalenguaje simple de marcas (etiquetas), desarrollado por W3C,
cuya finalidad es la de establecer la estructura de modelos de información en un ámbito general. En el contexto
de los servicios Web, con XML se define el formato estándar para estructurar los datos intercambiados, de
acuerdo al protocolo SOAP, y establece el formato y la estructura de los documentos WSDL que describen los
servicios.
2.2.2.2 Mensaje SOAP
Un mensaje SOAP es un documento XML ordinario que contienen los siguientes elementos:
• Envoltorio ("envelope"): es el elemento raíz de un mensaje SOAP y es obligatorio. Este elemento define
el documento XML como un mensaje SOAP, marcando su comienzo y final. Debe estar vinculado al
namespace declarado con valor http://www.w3.org/2003/05/soap-envelope/. Si se
usase otro namespace diferente, se generaría un error y el mensaje sería descartado. Incluye dos sub-
elementos, la cabecera ("header") y el cuerpo ("body") del mensaje.
• Cabecera ("header"): es un elemento opcional que tiene la finalidad de proporcionar información
específica de la aplicación acerca del mensaje en sí mismo. Se puede componer de varios sub-elementos
o bloques de cabecera que aportan la información de control adicional procesada por nodos intermedios
SOAP a lo largo del flujo de mensajes. Se entiende por nodo SOAP cualquier aplicación que procese
los mensajes SOAP. Los bloques de cabecera son el medio para ampliar el mensaje con nuevas
funcionalidades (seguridad, enrutamiento, coordinación, etc.).
19
19
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
• Cuerpo ("body"): es el elemento obligatorio que contiene los datos propios de la aplicación ("carga útil")
que se intercambian en el mensaje SOAP y dirigida al destinatario final del mensaje. Normalmente
serán datos en formato XML. SOAP define un elemento hijo para el cuerpo, el elemento fault, que se
utiliza para notificar errores en el procesamiento del mensaje, e incluye información específica del error,
como un código predefinido, una descripción, y la dirección del nodo que ha generado el error. Otros
elementos del cuerpo los define el servicio web que los utiliza.
Cuando sea necesario enviar en el mensaje datos que no son XML, será necesario recurrir a la especificación de
mensajes SwA (SOAP with Attachments) o MTOM (Message Transmission Optimization Mechanism). De esta
forma será posible enviar cualquier tipo de contenido junto al mensaje SOAP.
<?xml version = "1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Header>
…
…
</soap:Header>
<soap:Body>
…
…
<soap:Fault>
…
…
</soap:Fault>
…
</soap:Body>
</soap:Envelope>
La especificación SOAP define la estructura de los mensajes, pero no la forma en que se estos se intercambian.
Para ello se usan los llamados "enlaces SOAP" ("SOAP bindings"), que permite especificar cómo debe
transmitirse un mensaje SOAP utilizando un protocolo determinado para después enviar el mensaje con las
cabeceras propias del protocolo. La mayoría de las implementaciones de SOAP proporcionan enlaces para los
protocolos de comunicación más comunes, como HTTP o SMTP; sin embargo, HTTP es el único protocolo
estandarizado para SOAP, lo que garantiza que esté soportado prácticamente por todas las implementaciones de
SOAP.
Una solicitud HTTP SOAP especifica al menos dos encabezados HTTP:
• Content-Type: define el tipo MIME del mensaje y la codificación (opcional) usado para el cuerpo
del mensaje.
• Content-Length: especifica el número de bytes en el cuerpo del mensaje.
Figura 2-4. Interacción de un conjunto de servicios Web
Estado de la técnica
20
2.2.2.3 XML Schema
Como se introdujo antes, un esquema XML (XML schema) describe la estructura de un documento XML,
semántica, y restricciones sobre su contenido, permitiendo su validación. A este lenguaje también se le conoce
como XSD (XML Schema Definition). Establece los requisitos estructurales como qué elementos, qué tipo de
datos, qué atributos, cuántas veces se repiten, etc. Muchos de los formatos XML estandarizados están definidos
por un esquema XML.
Es una alternativa a los esquemas DTD (Document Type Definition) pero con mayores ventajas al estar formados
en XML:
• Es posible confirmar que está bien formado conforme a las reglas sintácticas de XML.
• Soporta tipos de datos predeterminados para elementos y atributos que pueden combinar o restringir
para crear nuevos patrones de datos.
• Son extensibles y se pueden reutilizar en otros esquemas gracias a los espacios de nombres
("namespaces"), que evitan los conflictos de nombres entre elementos y atributos que tienen el mismo
nombre, pero diferentes funciones u orígenes.
Una vez explicado el uso de los esquemas XML, será más sencillo entender la estructura de los ficheros WSDL.
2.2.2.4 WSDL
WSDL es un leguaje basado en XML para la descripción de la interfaz funcional de un servicio Web. En
términos generales, un documento WSDL especifica los detalles de las operaciones, los mecanismos de
interacción, incluyendo los tipos y formatos de los datos de envío y devolución, y la localización del servicio.
El hecho de que esté estandarizado y escrito en XML permite que pueda ser procesado por máquinas (machine
readable) hasta tal punto que, a partir de la definición WSDL del servicio Web, se generen automáticamente los
componentes de código adicionales para la implementación del servicio y de los propios clientes.
La estructura del documento WSDL se puede separar en dos secciones diferenciadas, relacionadas con la
descripción abstracta del servicio y la descripción concreta. La parte abstracta describe la interfaz y mensajes
del servicio Web con detalle, es decir, los elementos del servicio que no guardan relación con ninguna tecnología
en particular y que no serían susceptibles de modificación si se produjera un cambio en la tecnología del servicio;
mientras que la parte concreta sí especifica elementos asociados a un tipo de tecnología en concreto, como el
protocolo de comunicación empleado o la localización del servicio.
A continuación, se verán con más detalle cada uno de los elementos WSDL implicados en cada parte del
documento:
Descripción abstracta:
Elemento <definitions>: es el elemento raíz del documento WSDL. Por lo general, contiene un
conjunto de declaraciones de espacio de nombres que se utilizan en todo el archivo WSDL.
Elemento <types>: definen los tipos de datos que se utilizan en los mensajes que se van a intercambiar.
Es posible definir los tipos de datos directamente dentro del elemento o importando el fichero XSD
donde se hizo.
Elemento <message>: define de forma abstracta los datos intercambiados en las solicitudes y
respuestas realizadas entre el proveedor y consumidor del servicio Web. Los mensajes contendrán partes
(<part>) relacionado con los tipos de datos definidos en el elemento <types>, o se puede indicar
directamente un tipo de dato si es de tipo simple predefinido.
Elemento <portType>: contiene el conjunto de operaciones abstractas permitidas por el servicio
Web, semejante a la definición de una interfaz (término empleado en WSDL 2.0). Cada operación
define los mensajes de entrada (input) y salida (output) que están implicados. En función del orden y
la combinación de mensajes de entrada y salida en la operación, se pueden identificar cuatro patrones
de intercambio (MEP): one-way (la operación recibe un mensaje input pero no devuelve respuesta
21
21
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
output), request-response (la operación recibe una petición input y devuelve una respuesta output),
solicit-response (la operación puede enviar una petición output y esperar un respuesta input), y
notification (la operación manda un mensaje outpu y no espera por una respuesta output).
Descripción concreta:
Elemento <binding>: especifica los protocolos de comunicación que deben usarse y el formato de los
datos para las operaciones de cada <portType>. Es decir, indica cómo acceder al servicio Web. En el
caso de un binding de SOAP/HTTP, el binding incluirá toda la información necesaria sobre cómo se va
a estructurar la carga útil de los mensajes SOAP que se intercambien, mediante los atributos estilo (style)
y uso (use), de forma que no haya error de interpretación y se puedan procesar los mensajes
automáticamente.
En función de la combinación de los atributos style/use, existen tres mecanismos para la codificación
de la carga útil de los mensajes SOAP: RPC/encoded (no recomendado por WS-I), RPC/literal y
Document/literal, siendo ésta última la recomendada para la mayoría de los casos, principalmente
porque permite validar el documento enviado en el mensaje SOAP contra un esquema.
• RCP style: especifica que el <soap:body> contiene un elemento con el nombre de la operación
del servicio Web invocado. Este elemento contiene una entrada por cada parámetro de la
operación, o con el valor devuelto para los mensajes de respuesta. Es decir, el contenido de
<soap:body> es descrito por el WSDL (de la operación y el nombre del componente) y, a
continuación, por esquema XML.
• Document style: las partes del mensaje se colocan bajo <soap:body>. El contenido de <soap:
body> está descrito por el esquema XML de la sección <types> del documento WSDL. La
aplicación del servidor es responsable de mapear los objetos del servidor (parámetros, llamadas
a métodos, etc.) y los valores de los documentos XML.
public void myMethod(int x, float y);
<types>
<schema>
<element name="xElement" type="xsd:int"/>
<element name="yElement" type="xsd:float"/> </schema> </types>
<message name="myMethodRequest">
<part name="x" element="xElement"/>
<part name="y" element="yElement"/> </message>
<message name="empty"/>
<portType name="PT">
<operation name="myMethod">
<input message="myMethodRequest"/>
<output message="empty"/> </operation> </portType>
<binding .../>
Mensaje SOAP RPC/literal
<soap:envelope>
<soap:body>
<myMethod>
<x>5</x> <y>5.0</y>
</myMethod>
</soap:body> </soap:envelope>
Mensaje SOAP Document/literal
<soap:envelope>
<soap:body>
<xElement>5</xElement> <yElement>5.0</yElement>
</soap:body> </soap:envelope>
Tabla 2–3. Diferencia mensaje SOAP RCP style y Document style.
Elemento <service>: contiene una colección de puertos o ports. Cada puerto especifica el punto de
acceso o dirección de red (endpoint) vinculado a un binding desde el que se puede acceder al servicio
Web.
Estado de la técnica
22
Figura 2-5. Elementos WSDL
De forma resumida, un servicio (service) se accede mediante un port asociado a un binding particular. Este
binding es una implementación de un portType, que describe las operaciones (operation) del servicio. Estas
operaciones del portType se componen de mensajes (message) que intercambian datos XML definidos por los
types.
La estructura documentada es la referida a la versión WSDL 1.1, ya que es la que se utiliza en el desarrollo del
proyecto. Existe una versión más actual, WSDL 2.0, que plantea algunos cambios menores semánticos y otros
de nomenclatura y estructurales, con el objetivo de conseguir un mayor entendimiento. Las principales
diferencias de estructura de WSDL 2.0 con respecto a WSDL 1.1 son:
• Reemplaza el elemento definitions por description
• Integra el elemento message en el elemento types.
• Reemplaza el elemento portType por interface
• Reemplaza el elemento port por endpoint
Figura 2-6. Diferencias WSDL 1.1 y 2.0
23
23
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Sin embargo, la versión WSDL 2.0 no ha tenido una adopción formal y hasta la fecha no se ha convertido en
estándar. Este es el principal motivo por el que no está soportada por muchas de las herramientas o
implementaciones de los servicios Web como puede ser SOAPUI, CXF o APIs de JAVA.
Se ha mencionado que el formato estándar de WSDL basado en XML para la descripción de un servicio Web
lo convierte en un componente que puede ser procesado automáticamente. Esto ha propiciado que la mayoría
de los entornos de desarrollo, framework de aplicaciones o APIs de programación relacionados con los servicios
Web introduzcan herramientas que facilitan la programación de dichos servicios. El desarrollo de Servicios Web
sería una tarea altamente tediosa si hubiera que desarrollar el código para interpretar los mensajes SOAP de
entrada, y generar manualmente el mensaje de respuesta. Atendiendo al modelo de programación que se
mantenga, se hablan de dos enfoques en el diseño de servicios Web:
• Enfoque ascendente (bottom-up) o code-first, con el que se crea el correspondiente documento WSDL
a partir del código fuente del servicio ya existente.
• Enfoque descendente (top-down) o contract-first, para la generación de las interfaces de código
necesarias para invocar un servicio Web a partir del documento WSDL que lo describe.
El método empleado dependerá de varios criterios como la complejidad o la permutabilidad del servicio, el
entorno donde se vaya a consumir, etc. Por norma general, el enfoque contract-first es el que ofrece una mayor
eficacia dado que el diseño estándar definido en el contrato es incorporado a la lógica de negocio del servicio,
es decir, exige un planteamiento previo y definición iniciales, tanto de los servicios, como de los datos que van
a formar parte del mismo. Además, el desacoplamiento con la lógica interna de la aplicación le otorga robustez
frente a modificaciones, y asegura la reusabilidad, un mejor rendimiento y un sencillo control del versionado.
2.3 Contenedores
En los últimos años se están introduciendo dos nuevas innovaciones tecnológicas en dos ámbitos tecnológicos
muy distintos, como son la arquitectura software y la infraestructura virtualizada, pero cuya tendencia y
evolución parecen ir de la mano: los microservicios y contenedores.
Sin entrar en un detalle técnico, la arquitectura basada en microservicios pretende solucionar problemáticas
específicas de las aplicaciones monolíticas, en las que toda la funcionalidad está contenida en una gran y única
pieza de software, dificultando la escalabilidad y el mantenimiento de la aplicación. Esto implica que todo el
bloque debe realizarse sobre la misma tecnología, se ejecuta sobre un mismo proceso y, muy especialmente, que
cualquier cambio requerido supone un redespliegue global de la aplicación en su conjunto ya que podría
potencialmente afectar otras partes. En estos casos, la complejidad de la situación es en la mayoría de las
ocasiones proporcional al tamaño del equipo de desarrollo.
El enfoque que subyace tras los microservicios es la división de la aplicación en pequeños módulos
autocontenidos y que ofrecen una determinada funcionalidad, siguiendo la regla de mínimo acoplamiento y
máxima cohesión [4]. Esta división permite la evolución de cada funcionalidad por separado, habilitando a la
aplicación crecer y decrecer ágilmente en función de la demanda o necesidades de negocio, tanto en escalado
horizontal como en funcionalidad de dichos bloques/servicios, y reduciendo la complejidad del sistema, ya que
cada microservicio se ocupa de resolver un problema concreto.
La siguiente figura representa la comparación entre los enfoques de desarrollo que se vienen discutiendo:
Estado de la técnica
24
Figura 2-7. Monolíticos vs microservicios
En cierto modo, los microservicios son la evolución natural de las arquitecturas orientadas a servicios (SOA, de
sus siglas en inglés Service Oriented Architecture).
No obstante, este cambio de paradigma introducido por los microservicios abre la necesidad de resolver nuevos
desafíos relativos a la administración de los servicios y gobernanza, transaccionalidad en las operaciones,
consistencia de los datos, control de versiones, estrategia de pruebas, gestión de la seguridad, entre otras. Es por
ello que, para solucionar los retos de arquitectura planteados, aparte de la funcionalidad principal del
microservicio, necesitaremos incluir en nuestro sistema componentes operacionales específicos que posibiliten
una correcta implementación de la arquitectura, como mecanismos de descubrimiento de servicios,
balanceadores de carga, centralización de logs, monitorización del estado de salud de los nodos y servicios, etc.
Con todo el esquema arquitectónico de referencia aclarado, el siguiente paso es concretar su modelo de
despliegue, referido como el modo en que vamos a organizar y gestionar los despliegues de los microservicios,
así como a las tecnologías disponibles para tal fin. En este punto es donde la virtualización de contenedores ha
tomado especial relevancia frente a las virtualizaciones convencionales de máquinas, basado en la emulación de
componentes de hardware para poder utilizar varios servidores virtuales con su propio sistema operativo.
La virtualización basada en contenedores es una aproximación a la virtualización en la cual la capa de
virtualización se ejecuta como una aplicación en el sistema operativo del equipo anfitrión. En este enfoque, sólo
se ejecuta un único núcleo o kernel del sistema operativo, el correspondiente al host anfitrión, que se comparte
de forma aislada con los entornos virtuales invitados o "guest" ejecutados sobre el mismo, denominados
contenedores.
El contenedor, en su concepción más básica, opera como un proceso para el sistema operativo y empaqueta una
aplicación junto con todas las dependencias que necesita para ejecutarse. Esta aplicación tiene una vista aislada
del sistema de archivos y de otros recursos que ha provisionado el host del contenedor para él, y utiliza
indirectamente el kernel del sistema operativo principal para ejecutarse [5]. El aislamiento lógico entre
contenedores se consigue mediante diferentes medidas técnicas que básicamente consisten en asignar espacios
de nombres o namespaces distintos a cada sistema invitado. Inicialmente los espacios de nombres eran un sector
de tecnología reservado a Linux, sin embargo, en los últimos dos años, Microsoft ha hecho un importante
esfuerzo, inversión y alianzas para el desarrollo de la tecnología de contenedores de Windows [6].
1) Las aplicaciones monolíticas se dividen en
capas funcionales, como Web, negocios y datos.
2) Para escalar aplicaciones monolíticas, es
preciso clonarlas en varios servidores.
3) Las aplicaciones de microservicios separan las
funciones en servicios más pequeños
independientes.
4) Este enfoque de microservicios se escala
horizontalmente mediante la implementación de
cada servicio de manera independiente, con la
creación de instancias de estos servicios en
servidores.
25
25
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
La tecnología de contenedores se alinea perfectamente con el desarrollo ágil, independiente y escalable que
demanda la arquitectura de microservicios. No obstante, del mismo modo que sería completamente posible
construir una aplicación siguiendo un enfoque de microservicios sin usar contenedores, es posible construir una
aplicación mucho más tradicional dentro de un contenedor, lo que puede tener sentido cuando se quiere
aprovechar las capacidades de orquestación de contenedores que se cerán a continuación.
2.3.1 Arquitectura de contenedores: similitudes y diferencias con máquinas virtuales
Aunque a priori el concepto de contenedores pueda confundirse con el de máquina virtual por las similitudes
que comparten, revisando las diferencias más destacadas entre ambas tecnologías de virtualización será posible
entender las principales ventajas e inconvenientes de la virtualización de contenedores, así como los escenarios
en los que encaja mejor un modelo de virtualización u otro.
Para una mayor comprensión, en el siguiente diagrama se compara gráficamente el modelo de capas de las
arquitecturas de máquina virtual y de contenedor.
Figura 2-8. Arquitectura de máquinas virtual frente a contenedores
Como se deduce del diagrama anterior, la principal diferencia entre máquina virtual y contenedor radica en la
forma en la que cada modelo de virtualización utiliza los recursos de la máquina [7]. Mientras que la máquina
virtual requiere de un hipervisor, también conocido como monitor de máquina virtual, sobre el que montar su
propio sistema operativo completo, el motor de contenedores usa el sistema operativo subyacente del host.
Así, las máquinas virtuales comparten los recursos hardware y el hipervisor se ocupa de la asignación de dichos
recursos a cada máquina, mientras que los contenedores comparten sistema operativo, ejecutándose
directamente sobre el kernel como un proceso más, sin necesidad de duplicar librerías ni aplicaciones en cada
contenedor.
Desde este punto de partida, surgen el resto de diferencias entre ambas tecnologías. Se compararán en términos
de rapidez, portabilidad, y seguridad.
Rapidez:
Cada máquina virtual arranca y ejecuta su propia instancia completa del sistema operativo que utilice,
labor supervisada por la capa de hipervisor, con el consecuente aumento en el consumo de recursos
hardware, y de la dedicación en la puesta en marcha y mantenimiento del sistema operativo. Esta
sobrecarga no se produce en el caso de los contenedores, que van a consumir muchos menos recursos
del host anfitrión. En términos prácticos, esto puede significar que se pueden poner dos o tres veces más
aplicaciones en un único servidor con los contenedores que con las máquinas virtuales.
También derivado del punto anterior, los tiempos de inicio, apagado y ejecución son menores en
contenedores, quedando reducido a la ejecución de un nuevo proceso para el sistema operativo, mientras
que una máquina virtual requiere del arranque normal de un sistema operativo con todos sus
componentes y servicios habituales.
Estado de la técnica
26
Portabilidad:
Los contenedores "empaquetan" sus propias dependencias, que incluyen tanto los recursos software
como hardware. Así pues, la virtualización de contenedores no exige de una máquina física para poder
ejecutarse. El software necesario para la virtualización de contenedores puede ejecutarse sin problemas,
tanto sobre un servidor virtual como físico, mientras que un sistema de virtualización clásico necesita
de un equipo físico anfitrión y un hipervisor para realizar el control.
Por ejemplo, se puede aprovechar la alta capacidad de portabilidad de los contenedores en el ciclo de
vida para el desarrollo de software, ya que propicia el despliegue ágil de aplicaciones tanto en entornos
de desarrollo como de producción, simplificando a desarrolladores y administradores de sistemas las
pruebas y adaptaciones a cambios de hardware desde el entorno de prueba al de producción. Al contrario
que con máquinas virtuales con las que no sería tan fácilmente posible conseguir una estandarización
del entorno donde funcionará nuestra aplicación.
Por otro lado, si bien usar el mismo sistema operativo hace que los contenedores no deban preocuparse
por las dependencias, una vez las aplicaciones se estén ejecutando adecuadamente, también los limita
en cierta medida. Con las máquinas virtuales no importa que hipervisor esté utilizando (KVM, Hyper-
V, Xen o cualquier otro), pueden utilizar cualquier sistema operativo.
Seguridad:
Una de las ventajas de la utilización de máquinas virtuales es la abstracción a nivel de hardware que se
traduce en kernels individuales para cada máquina. Todo ello redunda en un mayor nivel de aislamiento
y seguridad, al quedar reducida la superficie de ataque al hipervisor.
Los contenedores proporcionan mecanismos que posibilitan el aislamiento. Sin embargo, en caso de
producirse una brecha de seguridad que ponga en riesgo el kernel, un atacante podría comprometer uno
de contenedores y dejar expuestos al resto de contenedores o al propio host anfitrión en que se ejecutan
y con los que comparte el kernel. Además, el riesgo será proporcional a la densidad de contenedores
alojados en el host anfitrión.
Así pues, los contenedores no son tan herméticos como las máquinas virtuales con sistema operativo
propio. Y, aunque en el primer caso un ataque al hipervisor pudiera ocasionar graves daños, al ser menos
complejos, no lograrían tanto alcance como en el caso de un kernel.
Nota: Un ejemplo de vulnerabilidades que pueden comprometer el kernel del sistema son Spectre y Meltdown
(CVE-2017-5753, CVE-2017-5715 y CVE-2017-5754). Concretamente, la vulnerabilidad Meltdown es una
vulnerabilidad de seguridad en el hardware que afecta a microprocesadores de Intel, ARM y AMD, y que
permitiría que un proceso maligno pueda leer de la memoria que pertenece al kernel, aún sin contar con
autorización para hacerlo. Esto significa que Meltdown puede eludir el aislamiento entre los contenedores que
comparten el kernel comprometido y, por lo tanto, exponer los datos de todos los demás contenedores en el
mismo host físico.
Nota: Microsoft ha conseguido proporcionar dos tipos de contenedores que difieren en el nivel de aislamiento
que ofrecen. En este sentido, los contenedores de Hyper-V ofrecerán mayor aislamiento mediante una
virtualización optimizada y el sistema operativo Windows Server que separa los contenedores entre sí y del
sistema operativo host, en perjuicio del rendimiento y eficiencia en tiempo de ejecución. Están especialmente
diseñados para entornos multiinquilinos (con requisitos para el hospedaje de código no seguro), entre los que
se incluyen las aplicaciones de SaaS y el hospedaje de procesos.
27
27
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
2.3.2 Escenarios de uso habituales
Las virtudes que se han puesto de manifiesto de los contenedores frente a las máquinas virtuales no significan
que vayan a reemplazarlas o apartarlas por completo del panorama tecnológico. Los expertos coinciden en que
ambos sistemas de virtualización aportan valores distintivos, y la decisión de usar una u otra tecnología vendrá
fuertemente determinada por el alcance y requisitos que tenga nuestro servicio. De hecho, no es extraño hablar
de soluciones híbridas donde aparezcan ambos conceptos, como el uso de contenedores dentro de máquinas
virtuales, evitando por ejemplo algunos de los problemas de seguridad implícitos en los contenedores que se
comentaron anteriormente.
Se observa que las máquinas virtuales son preferibles en entornos estables, consolidados y con aplicaciones
heterogéneas. Sin embargo, será más factible encontrar contenedores en alguno de los siguientes casos:
• Entornos de integración continua, cuando el paso de desarrollo a producción en un proyecto sea lo más
a menudo posible, para así poder detectar fallos cuanto antes.
• En el despliegue de aplicaciones dónde sea necesaria una arquitectura de microservicios, de alta carga,
que requieran un alto grado de replicación y con capacidad de dimensionar los servicios de forma rápida
y automatizada.
• Para la creación de un entorno operativo consistente, en el que una aplicación pueda ser trasladada de
un ambiente a otro (desarrollo, prueba, producción) con mínimo o nulo impacto.
2.3.3 Componentes del kernel
Los espacios de nombre o namespaces, junto con los cgroups, constituyen la base de los contenedores Linux:
• Los grupos de control o cgroups son un excelente mecanismo para controlar la asignación de recursos
hardware, limitando el impacto en el sistema. Para ello se definen jerarquías en árbol en las que se
agrupan los procesos del sistema apoyándose en una ruta del sistema de ficheros, normalmente ubicado
en /sys/fs/cgroup o /cgroup.
• Los namespaces son una funcionalidad del kernel, soportada a partir de la versión 2.6.23, que permite
crear una abstracción de recursos del sistema (identificadores de procesos, nombres de máquina,
identificadores de usuario, recursos de red, etc.) y los vincula solo a los procesos que están contenidos
dentro del mismo espacio de nombres, garantizando un espacio independiente y aislado entre los
mismos [8].
Existen 7 tipos diferentes de namespaces, relacionados con distintos aspectos del sistema:
o NETWORK namespace. Aísla los entornos a nivel de red. Así, cada namespace de red tendrá sus
propios interfaces de red, direcciones de red, tablas de enrutamiento, puertos de red, etc.
o PID namespace. Aísla el espacio de identificadores de proceso. Un contenedor tendrá su propia
jerarquía de procesos y su proceso padre o systemd (PID 1).
Figura 2-9. PID namespace
Estado de la técnica
28
o UTS namespace. Aísla los identificadores de sistema relacionados con el nombre de la máquina y
el dominio de la misma.
o MOUNT namespace. Aísla los puntos de montaje de los sistemas de ficheros que puede ver un
grupo de procesos, similar al funcionamiento original de chroot [9].
Figura 2-10. MOUNT namespace
o USER namespace. Aísla identificadores de usuarios y grupos. Así dentro un contenedor es posible
tener un usuario con ID 0 (root) que se corresponda con un ID de usuario sin privilegios en el
host.
o IPC namespace. Aísla los recursos de intercomunicación entre procesos dentro del espacio de
nombres.
o Cgroup namespace (a partir del kernel 4.6). Aísla la vista de los cgroups entre procesos dentro del
espacio de nombres.
Ambas tecnologías se han ido asentando paulatinamente hasta una completa integración en el kernel. Del mismo
modo, otros componentes del kernel han ido complementando el soporte al funcionamiento de los contenedores,
especialmente en términos de seguridad:
• Las Capabilities dota a los sistemas de virtualización de contenedores de una capa de seguridad
adicional ya que permite controlar y restringir las operaciones privilegiadas que pueden lanzar los
procesos que se ejecutan dentro de los contenedores, tales como el montaje de sistemas de ficheros,
cambio de fecha o la manipulación de interfaces de red, entre otras, aun siendo root. Las capabilities
necesarias pueden concederse o denegarse al arrancar el contenedor según se necesite.
• La integración de los contenedores con SELinux o AppArmor hace posible asociarles directamente
políticas de seguridad de control de acceso obligatorio, conocido también como MAC, que autorizan o
deniegan las operaciones que pueden hacer. Funciona de una manera similar a Capabilities, pero aporta
nuevas políticas de seguridad y además refuerza el control del host anfitrión frente a determinadas
acciones privilegiadas ejecutadas desde los contenedores.
2.3.4 Orquestación
Volviendo al paralelismo inicial de contendores y la arquitectura de microservicios, ya se ha demostrado como
los contenedores permiten desacoplar la capa de aplicación de la infraestructura subyacente, sin que ello penalice
en rendimiento y facilitando la construcción de los servicios. Además, la ligereza que los caracterizan
posibilitaría una fácil escalabilidad que los convierten en el perfecto candidato para el despliegue de
microservicios, por ejemplo, que estén basados en diferentes frameworks o lenguajes de programación.
Sin embargo, el enfoque de microservicios introduce otros requisitos, como los relacionados con la gestión de
los recursos, a nivel de despliegue y monitorización, cuando es necesario repartir las múltiples instancias que
conforman una aplicación o servicio entre diferentes máquinas y contenedores, garantizar la interconexión entre
ellos, o mantenerlos en un entorno dinámico y distribuido.
Frente a estos retos es esencial disponer de mecanismos de orquestación que abstraigan la complejidad de la
plataforma subyacente y automaticen la gestión de los despliegues de la propia infraestructura y los servicios
que alojan. En el contexto de los servicios multi-contenedor distribuidos, la orquestación se refiere al proceso
29
29
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
de automatizar y gestionar un pool de máquinas, conocido comúnmente como clúster, en las que se ejecutan
contenedores, y configurar su comportamiento e interacción conjunta [10]. Engloba, en su concepto más amplio,
los siguientes procesos:
• Planificación ("scheduling"): decide la máquina donde correr la imagen de un contendor, para
equilibrar la carga o establecer las restricciones impuestas por el usuario en la configuración del
servicio.
• Afinidad ("affinity"): especifica que los contenedores se ejecuten en el clúster próximos o separados,
según se quiera satisfacer requisitos de rendimiento o disponibilidad.
• Monitorización ("health monitoring"): vigila el estado de los contenedores del clúster.
• Conmutación por error ("failover"): replanifica el despliegue de un contenedor para proporcionar
tolerancia a fallos.
• Escalado ("scaling"): agrega o retira máquinas del clúster en función de la demanda de carga.
• Enrutamiento de red ("networking"): proporciona una red superpuesta para la interconexión entre
contenedores del clúster.
• Descubrimiento de servicio ("service discovery"): permite localizar los contenedores en el clúster,
incluso cuando cambian de IP o se mueven entre máquinas.
• Actualizaciones contínuas ("rolling updates"): administra las actualizaciones de los contenedores para
evitar interrupciones de servicio o revertirlas en caso de fallo.
La gestión de clústeres de máquinas y la planificación ("scheduling") de contenedores son los dos componentes
clave en la implementación de servicios multi-contenedor sobre varias máquinas. Están estrechamente
relacionados, por lo que es normal que las soluciones de orquestación actuales integren ambas funciones. No
obstante, algunas soluciones de orquestación van más allá e incluyen balanceadores de carga, sistemas de
resolución de nombres de contenedores y mucho más. Algunos son extensibles y se integran con otros
frameworks que los complementan para agregar capacidades adicionales.
A continuación, se resumen a alto nivel algunas de las tecnologías de clustering y orquestación de código abierto
más extendidas:
2.3.4.1 Docker en modo Swarm
Desde su versión 1.12, el motor de Docker ("Docker Engine") integra Swarm como su solución nativa para la
orquestación y gestión de clúster de máquinas, denominados enjambres ("swarms"), provistas de Docker Engine
para ejecutar contenedores. Al estar completamente embebido en el Docker Engine, no es necesario la
instalación de ningún software de orquestación adicional, pudiendo crear el enjambre, desplegar los servicios de
aplicación al enjambre y administrar su comportamiento de la misma forma que se hacía con Docker Engine y
Docker Compose.
Swarmkit es el proyecto independiente que implementa la capa de orquestación de Docker.
Docker se activa en modo Swarm para crear un clúster nuevo o añadir a una máquina nueva al enjambre. En
este modo, cada una de las máquinas del clúster pasan a denominarse “nodos”. El escenario más habitual para
la creación de un swarm incluye nodos distribuidos entre múltiples máquinas físicas y servidores Cloud.
Otras características relevantes son:
• Servicio de tolerancia a fallos y replicación, que planifica ("scheduling") los contenedores en el clúster
en caso de problema en algún nodo.
• Redes superpuestas ("overlay") para la comunicación privada de los contenedores, con servicio DNS
embebido para el descubrimiento de servicios
• Alta disponibilidad mediante el uso de varios maestros
• Balanceo de carga interno
Estado de la técnica
30
• Monitorización del estado de salud de los servicios, recreando contendores si alguno deja de funcionar.
• Seguridad de red, forzando la autenticación y cifrado TLS de las comunicaciones entre nodos, usando
una Autoridad de Certificación.
A la hora de implementar un Docker en modo Swarm, los usuarios suelen recurrir a otra herramienta del
ecosistema Docker, Docker Machine.
2.3.4.2 Kubernetes
Kubernetes (K8s) es una solución de código abierto diseñado para para la automatización de despliegues, el
escalado y la gestión de aplicaciones en contenedores. Con Kubernetes, es posible decidir cuándo deben
ejecutarse los contenedores, aumentar o disminuir el tamaño de los contenedores de la aplicación o verificar el
consumo de recursos de las implementaciones de la aplicación. El proyecto de Kubernetes se basa en la
experiencia de Google en el trabajo con contenedores a escala masiva, y a día de hoy se encuentra bajo el
patrocinio de la Cloud Native Computing Foundation (CNCF).
Se implementa inicialmente para el propio uso interno de Google como orquestador de su sistema de recursos
en los datacenters.
Las características clave incluyen escalado automático incorporado, balanceo de carga, orquestación de
volúmenes y administración de secretos. Además, hay una interfaz de usuario web para ayudar a administrar y
solucionar problemas del clúster. Con estas características incluidas, Kubernetes a menudo requiere menos
software de terceros que Swarm o Mesos.
Las opciones para utilizar Kubernetes apenas tienen restricción, casi cualquier opción de uso es posible.
Kubernetes está tomando ventaja frente a otros orquestadores, al contar con un catálogo más amplio de
funcionalidades, si bien lo hace más complejo. Tiene una curva de aprendizaje más pronunciada y su
configuración puede requerir más esfuerzo que Swarm.
No obstante, gracias a todas las posibilidades de instalación que ofrece y porque muchas soluciones lo están
integrando en sus arquitecturas (se encuentra en todos los PaaS y en todos los servicios Cloud) se está
consolidando como la principal solución de orquestación. También prueba de ello fue el anuncio en octubre de
2017 de la integración de Kubernetes de forma nativa en Docker.
2.3.4.3 Apache Mesos + Marathon
La combinación de Apache Mesos con Marathon Mesosphere conforman una solución completa para la
orquestación de contendores.
Apache Mesos es un administrador de clústeres de código abierto que simplifica la ejecución de aplicaciones en
un clúster escalable de servidores. Mesos se describe como un kernel de sistemas distribuidos o, dicho de otra
forma, una plataforma de clúster que proporciona recursos computacionales a aplicaciones. Fue diseñado para
plataformas muy grandes y para ser extremadamente confiable. Es posible escalar Mesos a miles de nodos.
Mesos es el corazón del sistema Mesosphere, una solución de software que amplía las capacidades de
administración de clúster de Apache Mesos con componentes adicionales para proporcionar una forma nueva y
novedosa de administrar las infraestructuras de servidor. Al combinar varios componentes con Mesosphere,
como Marathon, permite escalar fácilmente las aplicaciones abstrayendo muchas de las complejidades asociadas
con este proceso. Adicionalmente, Mesosphere destaca por funciones tales como la planificación eficiente de
servicios (según consumo de CPU y RAM), escalado, tolerancia a fallas, alta disponibilidad (a través de
Zookeeper) y descubrimiento de servicios.
Apache Mesos es anterior a Docker, pero que agregó soporte para Docker integrando el framework Marathon.
Es, sin embargo, esta diversidad de componentes y posibilidades la que complica su configuración y
mantenimiento, en comparación con otras soluciones de orquestación.
31
31
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
2.3.4.4 Comparativa
La siguiente tabla compara las diferentes soluciones de orquestación tratadas conforme a unos puntos
básicos que apoyen la decisión de la tecnología seleccionada para los casos de uso y requerimientos del
proyecto.
Docker Swarm Kubernetes Mesos+Marathon
Instalación del
clúster Fácil de instalar y configurar
Ligeramente complejo de
configurar
Complejidad variable,
dependiendo del escenario de
configuración
Despliegue de
contenedores Completamente nativo de
Docker
Basado en fichero YAML
para definir los servicios
del clúster
Basado en ficheros de
definición JSON
Tamaño
mínimo del
clúster
Un único nodo.
En entornos de producción, al
menos dos nodos gestores
para garantizar la alta
disponibilidad. También se
necesitan múltiples nodos
trabajadores para la
distribución y replicación de
servicios.
Un servidor maestro y un
servidor minion.
En entornos de
producción serán
necesarios al menos tres
nodos maestros, y tantos
minions como se
requiera.
Un maestro y un esclavo.
En entornos de producción
serán necesarios al menos tres
maestros, y tantos minions
como se requiera.
Escalabilidad
Adecuado para requisitos de
pequeña o media escala.
Despliegue rápido de
contenedores.
Adecuado para medios o
grandes clústeres.
Mayores garantías sobre
el estado del clúster, en
perjuicio de la agilidad de
escalado.
Adecuado para casos de escala
masiva.
Buena opción si se precisa
combinar contenedores y
aplicaciones normales en el
mismo clúster.
Principal
característica
Facilidad de uso y
aprendizaje. Margen de
usabilidad significativo.
Las mejores funciones de
planificación de pods [11]
cuando se requieren
implementaciones de
aplicaciones complejas.
Escala en miles, y
características de restricciones
basadas en rack /
máquinas disponibles para
ajustar con precisión dónde
desplegar las aplicaciones.
Tabla 2–4. Tecnologías de virtualización basadas en contenedores
En este proyecto se hará uso de Docker en modo Swarm, ya que es preferible en entornos donde se favorece la
simplicidad y el desarrollo rápido. Es la forma más compatible y natural de organizar las infraestructuras de
contenedores de Docker al estar nativamente integrado en el motor de Docker. En este sentido, es más flexible
y permitirá transicionar la aplicación en contenedores más fácilmente. Por otro lado, la aplicación del proyecto
no es compleja ni tiene requisitos de escala masiva.
Viendo el creciente interés y uso de contenedores de la comunidad tecnológica, los grandes fabricantes y
proveedores de servicios Cloud están cada vez más volcados en ofrecer soluciones de orquestación populares
en sus entornos, dando origen al modelo de computación Cloud CaaS, acrónimo de Contenedor como Servicio
("Container as a Service"). Este tema se tratará con mayor detalle en un apartado posterior.
Estado de la técnica
32
2.4 Cloud Computing
La transformación digital que sigue experimentando la sociedad está originando un incremento recurrente de las
necesidades de procesamiento, almacenamiento y conectividad de los sistemas de información para la provisión
de servicios que den cobertura a las demandas sociales y empresariales actuales. Estos requisitos unidos a ciertas
circunstancias tecnológicas como la reducción de coste de los componentes informáticos, o la mayor
conectividad vinculada al abaratamiento del acceso a la red e incremento de las velocidades de transferencia
alcanzados, han convergido en la concepción tecnológica del "cloud computing" o computación en la nube. El
cloud computing se refiere a la forma de proveer como servicio bajo demanda recursos informáticos, tales como
servidores, sistemas de almacenamiento, puestos de trabajo, servicios de correo, aplicaciones, bases de datos,
redes, entrega de contenido, etc., a través de Internet.
La evolución tecnológica en el campo de la virtualización ha sido un elemento clave en el desarrollo del modelo
cloud. La flexibilidad, elasticidad y escalabilidad que aporta la virtualización habilita a los proveedores cloud a
provisionar de forma ágil, con un alto grado de automatización, optimización de recursos y administración
simplificada, los recursos precisos requeridos por los clientes. Cuando se habla de elasticidad y escalabilidad se
refiere tanto al aumento de los recursos como a la disminución de los mismos, para evitar un sobre-
aprovisionamiento cuando la carga de trabajo se reduce o penalizar en rendimiento.
El concepto de cloud computing responde también a un modelo de negocio impulsado por grandes proveedores
cloud, como Amazon, Microsoft o Google, que cuentan con numerosos clientes y han alcanzado enormes
economías de escala con las que pueden ofrecer una gran variedad de servicios tecnológicos a precios
competitivos, contratados en la modalidad de pago por uso, y ajustados a las necesidades concretas de sus
clientes. Frente al modelo tradicional de datacenter interno, abstrae del coste de mantenimiento hardware de sus
sistemas o de inversiones adicionales por haber dimensionado incorrectamente la capacidad requerida por los
sistemas de información. Con los servicios cloud se paga por lo que se usa, con la posibilidad de aumentar o
reducir los recursos en cuestión de minutos según sea necesario, eliminando la necesidad de planificar en cada
momento la capacidad de la infraestructura y reduciendo los tiempos de aprovisionamiento. De esta forma, los
gastos iniciales y recurrentes son mucho más bajos, permitiendo centrar el esfuerzo en proyectos propios del
negocio.
Por otro lado, la globalidad alcanzada por los proveedores cloud asegura la disponibilidad de los servicios
redundados en múltiples regiones del mundo, con la consiguiente reducción de la latencia en el consumo de los
servicios para los clientes.
Conforme a la definición del cloud computing formulada por el NIST (National Institute of Standards and
Technology) en su publicación especial SP 800-145, un servicio cloud debe cumplir cinco características
esenciales [12]:
• Autoservicio bajo demanda: Un consumidor debe poder autoabastecerse de recursos informáticos como
la capacidad de almacenamiento o computación, en función de sus necesidades, sin que sea necesaria
interacción humana del proveedor del servicio.
• Accesibilidad desde la red: Los servicios ofrecidos deben ser accesibles independiente de localización,
a través de mecanismos estándares y desde diferentes plataformas con conexión a la red (ordenadores,
smartphones, tablets, etc.).
• Recursos compartidos: Los recursos deben estar puestos a disposición de todos los clientes, y son
asignados o reutilizados atendiendo a la demanda de dichos clientes. De esta forma, se consiguen
economías de escala que repercutirán en menores costes para los clientes.
• Elasticidad: Un cliente debe poder dimensionar a demanda los recursos de manera rápida, elástica, e
incluso automática, en prácticamente cualquier momento. Esto se traduce en un incremento sustancial
de la agilidad de los clientes para adaptarse los requisitos de su entorno. Los recursos ofrecidos deben
ser percibidos como ilimitados para el cliente.
• Servicio supervisado: Los sistemas cloud deben controlar y optimizar el uso de los recursos
automáticamente dotándose de mecanismos de medición que reporten de forma transparente tanto para
el proveedor de servicios cloud como para el cliente.
33
33
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
2.4.1 Modelos de servicio
Los servicios ofertados por los proveedores cloud se pueden agrupar en tres tipologías fundamentales:
Infraestructura como Servicio (IaaS):
El modelo de servicio IaaS ofrece acceso bajo demanda a la infraestructura de TI (servidores, máquinas virtuales,
almacenamiento, redes, sistemas operativos) que se aprovisiona y administra a través de Internet, liberando a los
clientes de las actividades derivadas de la gestión de la capa física. Proporciona el más alto nivel de versatilidad
y control sobre los recursos de TI, similar la gestión de recursos de TI que se puede hacer en un entorno on-
premise, ya que el mantenimiento de todo el software está delegado en el cliente.
Plataforma como Servicio (PaaS):
En los servicios de tipo PaaS se provee de un entorno de desarrollo, con herramientas de programación,
middleware, sistemas de administración de bases de datos, servidores Web, etc., diseñado para liberar la
necesidad de administrar la capa de infraestructura, y centrarse en la implementación, despliegue y
administración de las aplicaciones. Así, abstrae de la preocupación de adquirir recursos y licencias, planificar la
capacidad necesaria o mantener el software en óptimas condiciones.
Software como Servicio (SaaS):
Los servicios SaaS proporcionan aplicaciones basadas en la nube completas que los clientes pueden usar a través
de Internet a demanda, pagando solo por el uso que se hace a través de una suscripción o del nivel de uso.
El proveedor de servicios cloud administra la infraestructura y toda la capa de software que soporta la aplicación.
También se encarga del mantenimiento, como la gestión de las actualizaciones software y revisiones de
seguridad. La infraestructura subyacente y tecnologías que dan soporte a la aplicación son transparentes a los
clientes.
Figura 2-11. Responsabilidad entre cliente y proveedor según modalidad de servicio
2.4.2 Cloud computing y contenedores - CaaS
Contenedores como Servicio (CaaS) es el nombre que ha recibido un nuevo modelo de servicio que entrega a
los usuarios un entorno provisto de todos los recursos necesarios para la gestión completa del ciclo de vida de
las aplicaciones, desde su desarrollo hasta su puesta en producción, mediante soluciones de virtualización basada
Estado de la técnica
34
en contenedores, herramientas de orquestación e infraestructura distribuida.
Al basarse en la tecnología de virtualización a nivel de sistema operativo, CaaS se situaría como capa intermedia
entre la infraestructura como servicio y la plataforma como servicio. De esta forma, se ofrece un servicio que
hereda los beneficios de los contenedores en cuanto a rendimiento, ocupación, escalabilidad y portabilidad, y
sin estar limitado a las herramientas o entornos de ejecución que proporcionan los proveedores en el modelo
PaaS.
Aunque es sólo un componente más, en el corazón de una plataforma CaaS se encuentran los orquestadores de
contenedores que, como ya se ha comentado en un apartado anterior, están diseñados para gestionar operaciones
tales como el despliegue de contenedores y la administración de clústeres de máquinas, entre otras cosas. Sin
embargo, los orquestadores por sí solos no resuelven la gestión del ciclo de vida completo de las aplicaciones.
Es por ello que los servicios CaaS integran otros componentes con los que completar el marco de trabajo, como:
• Paneles de administración para la gestión completa de todos los componentes y diversas API de
gestión.
• Servicio de registro para el almacenamiento y gestión segura de imágenes.
• Almacenamiento persistente de los datos.
• Balanceadores de carga que administren y distribuyan el tráfico externo.
• Controles de acceso basado en roles a todos los elementos, incluidos repositorios, nodos, secretos, redes,
volúmenes, etc. para los diferentes equipos de desarrollo participantes.
• Complementos de seguridad que permitan establecer comunicaciones seguras, autoridades de
certificación, gestión de secretos, escaneos de vulnerabilidades, firma de imágenes, etc.
• Monitorización, gestión de logs, recolección de métricas, análisis y programación de alertas.
• Mecanismos de integración continua, que agrega los cambios de código en un repositorio compartido
y son validados automáticamente, y de entrega continua, que prepara los cambios que hayan superado
pruebas automáticas para entregarlos en producción si es aprobado.
• Entornos de trabajo colaborativos donde compartir comentarios, reporting de errores, etc.
• Integración con otros servicios de proveedores cloud.
Estas características son generales y pueden variar en función del proveedor de servicios seleccionado.
Si bien trabajar con CaaS añaden una capa de abstracción que simplifica la gestión de los contenedores, durante
todo su ciclo de vida, la decisión de usar Docker en modo Swarm directamente, sin el apoyo de alguna de las
soluciones tipo CaaS mencionadas. Esta decisión responde a la motivación de trabajar como mayor versatilidad
en la gestión de los contenedores y servicios desplegados en el swarm. De cara a la ejecución del proyecto es
necesario trabajar en un nivel más bajo para entender con mayor profundidad de detalle el funcionamiento y
opciones/alternativas que ofrece la tecnología.
35
3 HERRAMIENTAS
3.1 XPath
A lo largo del proyecto se han establecido condiciones específicas que obligan a recorrer y tratar documentos
XML, utilizados principalmente como ficheros de propiedades, con objeto de recuperar la información
contenida.
XPath (XML Path Language) [13] es un lenguaje de consulta que permite navegar a través de los elementos y
atributos de un documento XML para recuperar información. Es un estándar recomendado por W3C que fue
creado originalmente para su uso con el estándar de W3C XSLT (eXtensible Stylesheet Language for
Transformations) y contituye la base de otras especificaciones relacionadas.
El funcionamiento de XPath se basa en el modelado conceptual del documento XML como un árbol de nodos,
llamado modelo de datos.
El bloque de construcción básico de XPath es la expresión, siendo la más habitual la expresión de ruta, que se
utiliza para seleccionar un nodo o conjunto de ellos en un documento XML. En una expresión de ruta, los tramos
de la expresión ("steps"), es decir, cada parte separada por "/" o "//", son evaluados secuencialmente.
La sintaxis de las expresiones es similar a la estructura de un sistema de ficheros tradicional. Sin embargo, al ser
una especificación de coincidencia de patrones, su semántica es totalmente diferente. En un ejemplo sencillo, la
expresión /level1/level2 selecciona todos los elementos level2 que se encuentran debajo de un elemento level1.
Para seleccionar un elemento level2 específico, habría que indexar usando corchetes. Así, la ruta
/level1[3]/level2[2] seleccionaría el segundo elemento level2 debajo del tercer elemento level1.
XPath está soportado en Java SE (javax.xml.xpath).
3.2 PostgreSQL
PostgreSQL es un sistema avanzado de gestión de bases de datos relacionales (ORDBMS, Object-Relational
Database Management System) de código abierto. Respaldado por más de 15 años de desarrollo activo, tiene
una arquitectura comprobada, de clase empresarial avanzada, con la que ha logrado una sólida reputación debido
a sus características innovadoras, integridad, seguridad y fiabilidad.
Al igual que muchos otros proyectos de código abierto, el mantenimiento y mejora de PostgreSQL no está
controlado por ninguna compañía. Es un proyecto de comunidad y está respaldado por desarrolladores
voluntarios y entidades comerciales que contribuyen en su desarrollo. Dicha comunidad es conocida como
PGDG (PostgreSQL Global Development Group).
Uno de los claros beneficios de PostgreSQL es que se trata de un proyecto de código abierto, distribuido bajo
una licencia libre, la licencia PostgreSQL, que otorga la libertad a cualquier persona de usar, modificar, distribuir
PostgreSQL de manera gratuita, independientemente del propósito que se le vaya a dar, sea privado, comercial
o académico. Todo esto se traduce, además del indudable ahorro de licencias y reducción de costes, en un
impulso a la innovación conseguido con el trabajo colaborativo de la comunidad, en una mayor confiabilidad
del software por estar sometido a la inspección de un elevado número de personas, y un mayor control sobre el
código que permite adaptarlo a las necesidades particulares de cada uno.
3.2.1 Arquitectura básica
La estructura física de PostgreSQL es sencilla: incluye una memoria compartida reservada para cacheo de la
base de datos y del registro de transacciones, algunos procesos que operan en segundo plano y ficheros de datos.
A alto nivel, PostgreSQL está basado en una arquitectura cliente/servidor que sigue el modelo de "proceso por
usuario". En este modelo, un proceso servidor (backend) puede atender exclusivamente a un solo cliente. De
esta manera se permiten las conexiones concurrentes de diferentes clientes. En su funcionamiento, un proceso
Herramientas
36
maestro postgres se queda escuchando esperando conexiones entrantes y se encarga de generar un nuevo proceso
para cada conexión. Desde ese momento, el cliente y el nuevo proceso del servidor se comunican sin la
intervención del proceso original de postgres.
Algunas de las características generales y funciones diferenciadoras de PostgreSQL, según su documentación
oficial, son:
• ACID compliant: asegura que las transacciones se ejecutan completamente o se anulan ("atomicity"),
no van a romper las reglas de integridad de la base datos cuando se acoplen los datos ("consistency"),
son independientes unas de otras ("isolation"), y persistirán una vez finalicen ("durability").
• Multiplataforma: se ejecuta en todos los principales sistemas operativos, incluidos Linux, UNIX (AIX,
BSD, HP-UX, macOS, Solaris) y Windows.
• Cumplimiento de estándares: compatible con la mayoría de los tipos de datos del estándar SQL:2016.
Admite caracteres de internacionalización, codificación multibyte, unicode, y es compatible con la
configuración regional para la clasificación, la distinción entre mayúsculas y minúsculas y el formateo.
• Escalabilidad: posee una gran escalabilidad tanto en la cantidad de datos que puede administrar como
en el número de usuarios concurrentes que puede manejar.
• Altamente configurable y extensible: dispone de interfaces nativas para la mayoría de los leguajes de
programación como C/C++, Java, .Net, Perl, Go, Python, Ruby, Tcl, ODBC, entre otros.
• Soporte: cuenta con el apoyo y contribución de una amplia comunidad dedicada de desarrolladores
profesionales, junto con una oferta de soporte comercial, disponible a empresas y usuarios.
• Funcionalidades avanzadas: controles de acceso granular, tablespaces, replicación asíncrona,
transacciones anidadas (savepoints), copias de respaldo en línea o en caliente, optimización de
consultas, herencia de tabla (es un concepto de bases de datos orientadas a objetos), etc.
PostgreSQL gestiona el acceso concurrente de manera eficiente mediante la implementación del control de
concurrencia multi-versión (MVCC - Multi-Version Concurrency Control). En contraposición del bloqueo a
nivel de tabla, se trata de un sistema que previene a los lectores de bloquear a los escritores y a los escritores de
bloquear a los lectores.
Para proveer de atomicidad y durabilidad, PostgreSQL utiliza la característica denominada registro de escritura
anticipada o WAL (Write Ahead Logging), que de forma resumida consiste en una técnica para retener a nivel
de disco (archivado de los ficheros WAL o REDO log) la descripción de los cambios hechos a la base de datos,
antes de sean escritos en la base de datos. El uso de WAL da como resultado un número significativamente
menor de escrituras en disco.
3.2.2 Por qué PostgreSQL
El presente proyecto se realiza bajo la premisa de no incurrir en costes directos, lo que limita el desarrollo a la
selección y empleo de herramientas de código abierto. En el ámbito de bases de datos relacionales, las principales
alternativas son MySQL y PostgreSQL, aunque la colección de características y prestaciones ofrecidas con
PostgreSQL es comparable a las de otras soluciones comerciales de bases de datos como Oracle o SQL Server.
La única similitud entre MySQL y PostgreSQL es que estos dos proyectos son de código abierto; aparte de eso,
ambos proyectos se fundamentan en concepciones tecnológicas muy diferentes. PostgreSQL ha centrado sus
esfuerzos en garantizar altos índices de fiabilidad, integridad de datos, y escalabilidad, aunque ello penalice en
el consumo de recursos y el rendimiento frente a otros sistemas de bases datos. Por otro lado, MySQL se ha
enfocado en la facilidad de uso y la optimización las operaciones, ofreciendo un mejor rendimiento y rapidez de
respuesta.
37
37
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
3.3 MyBatis
MyBatis es un framework de persistencia de código abierto [14], que simplifica la persistencia de objetos en
bases de datos relacionales, separando la capa de negocio y la capa de base de datos. Soporta SQL,
procedimientos almacenados y mapeos avanzados.
Almacenar y recuperar datos hacia y desde bases de datos relacionales es un requisito muy común, y una parte
indispensable de muchas aplicaciones. La capa de persistencia implica la población de objetos Java con datos
cargados desde la base de datos mediante consultas SQL y la persistencia de los datos en objetos Java en la base
de datos mediante SQL. Aunque Java proporciona una API JDBC (Java DataBase Connectivity) para el acceso
a la base de datos, es una API de bajo nivel, y requiere una gran cantidad de codificación repetitiva para realizar
operaciones de base de datos, como la creación de una conexión, establecer los parámetros de entrada, liberar
los recursos, etc. Utilizando JDBC, la obtención de datos de una base de datos, la incorporación en objetos Java
y la persistencia de datos de objetos Java en una base de datos se convierte en un procesos poco eficiente y
tedioso. MyBatis abstrae y automatiza todas esas tareas comunes para que el programador pueda enfocarse en
los aspectos verdaderamente de valor a la aplicación, como preparar las sentencias SQL a ejecutar y pasar los
datos de entrada como objetos Java. Es decir, MyBatis adopta un enfoque simple que aprovecha los beneficios
de SQL, pero evita la complejidad de JDBC, reduciendo considerablemente el código de la aplicación y
facilitando la implementación de la persistencia de objetos.
MyBatis no es estrictamente un ORM (Object Relational Mapper), con el que se mapean objetos Java a tablas
de base de datos de la forma más directa y transparente posible. Con MyBatis se mapean las clases a sentencias
SQL o procedimientos almacenados que programa el desarrollador. En resumidas cuentas, las herramientas
ORM completas generan SQL, mientras que MyBatis usa SQL directamente. Al estar centrado en SQL, MyBatis
permite utilizar todas las funcionalidades de la base de datos como procedimientos almacenados, vistas,
consultas de cualquier complejidad o funcionalidades específicas del proveedor, permitiendo al programador
manipular el SQL para optimizar las sentencias para la consecución de los datos perseguidos.
La simplicidad es su mayor ventaja sobre las herramientas de mapeo relacional de objetos: asocia objetos, como
un JavaBean o un Map, con procedimientos almacenados o sentencias de SQL utilizando unos ficheros XML
descriptores, desde código Java mediante anotaciones o combinando ambas opciones. Para el proyecto se ha
preferido mantener externalizadas las sentencias SQL en ficheros XML, a fin de mantener claridad del código e
independencia entre las capas de datos y la lógica de negocio. Esta separación entre SQL y el lenguaje de
programación de la aplicación es un requisito del proyecto para poder aprovechar las capacidades de
automatización alcanzados en el proceso de generación de las clases del modelo de datos y posterior despliegue
del proyecto en el entorno de desarrollo.
Se utiliza habitualmente MyBatis para aplicaciones con bases de datos legadas, o poco normalizadas o cuando
es preciso tener el control total del SQL ejecutado, por ejemplo, porque el modelo de datos esté previamente
creado.
3.3.1 Utilización de MyBatis
De forma general y asumiendo que se cuenta con el modelo de datos de la base de datos y los objetos asociados,
los pasos para utilizar MyBatis son los siguientes:
• Configuración de los parámetros que definen el comportamiento global del framework en ejecución.
Cada proyecto de MyBatis tiene un fichero de configuración XML primario, altamente parametrizable,
en el que se definen a alto nivel los parámetros de conexión JDBC a la base de datos (dataSource), la
configuración en el manejo de las transacciones, la definición de alias para los tipos Java (typeAliases),
los manejadores externos o personalizados de tipos Java no soportados o no estándares (typeHandler)
para convertir los valores de los PreparedStatement o los ResultSets devueltos de las sentencias SQL, y
la localización de los ficheros XML de mapeo (mappers).
• Definición de los Mapped Statements o ficheros XML de mapeo.
Los ficheros XML denominados mappers contienen las sentencias SQL y las definiciones de mapeo
(SQL mapped statements). La potencia de MyBatis radica en la gestión de los Mapped Statements. En
estos ficheros se establece la configuración de cómo guardar y recuperar los objetos usando sentencias
Herramientas
38
SQL y el tratamiento de los ResultSets devueltos. Según las sentencias que se deban lanzar, los mappers
contendrán elementos XML de tipo insert, delete, update o select en los que se definan las sentencias
SQL correspondientes.
MyBatis es capaz de automatizar el proceso de establecer los parámetros de consulta a partir de las
propiedades de los objetos de entrada de Java, y mapear las columnas devueltas en una consulta con las
propiedades del JavaBean relacionado en base a las coincidencias de sus nombres. Para el caso de
sentencias más complejas, se puede definir en el fichero XML un elemento ResultMap que describa la
relación entre columnas y propiedades de la clase Java en cuestión. El elemento ResultMap es el
elemento más importante y potente de MyBatis, cuyo uso equivaldría al 90% del código que JDBC
necesitaría para obtener datos de ResultSets.
A través de los ResultMaps, MyBatis también soporta el mapeo de resultados del tipo one-to-many o
many-to-many. Estas relaciones se pueden cargar de diferentes formas: mediante Selects anidadas que
se ejecutan desde el propio ResultMap para devolver los tipos complejos perseguidos, o mediante
ResultMaps anidados que tratan los datos repetidos de ResultSets resultante de consultas con joins entre
tablas. Se recomienda la opción de los ResultMaps anidados para evitar el "problema de las N+1 Selects"
que puede originar un impacto considerable de rendimiento cuando ser trabaja con volúmenes elevados
de datos.
• Creación del código Java para la invocación de los Mapped Statements y recuperación de los objetos
mapeados con la base de datos.
MyBatis simplifica significativamente el código, en comparación con JDBC. Por lo general, las líneas
de código necesarias para ejecutar una mapped statement se reducen en la mayoría de los casos a una.
3.3.2 Características adicionales
A continuación, se enumeran otras características o funcionalidades de interés del framework MyBatis que
colaboran en facilitar la implementación de la lógica de persistencia:
• Integración con otros frameworks de desarrollo: proporciona soporte de integración con los populares
frameworks de inyección de dependencias Spring y Guice, simplificando aún más el trabajo con
MyBatis.
• Lazy loading: puede cargar las consultas de forma diferida, hasta el momento en que se requieren sus
datos relacionados, evitando el coste de lanzar todas las consultas de las relaciones a la vez.
• Caché: Por defecto la única caché activa es la caché local de sesión que se utiliza para evitar dependencias
circulares y acelerar ejecuciones repetidas de consultas anidadas, únicamente durante la duración de una
sesión. MyBatis incorpora una funcionalidad de caché transaccional de segundo nivel muy potente, y
también se integra con varias bibliotecas de caché de terceros, como EHCache, Memcached, Ignite y
Hazelcast.
• SQL dinámico: en cualquier Mapped Statement, permite la creación de consultas SQL dinámicas basadas
en los parámetros de entrada. Utiliza expresiones con elementos del tipo if, foreach, choose (when -
otherwise), trim (where - set) o bind.
• Anotaciones: para implementar los Mapped Statements pueden usarse anotaciones en las clases mapper,
sin necesidad de utilizar ningún fichero XML. Sin embargo, cuando las sentencias son más complejas
presentan ciertas limitaciones y no alcanzan la misma flexibilidad y potencia que la configuración en los
ficheros XML.
3.3.3 Ventajas frente a herramientas ORM
Claramente MyBatis es una alternativa directa a la programación de acceso a bases de datos con una API JDBC.
No obstante, proporciona ciertas ventajas que, en determinados escenarios, lo convierte en una mejor opción
que las soluciones ORM. MyBatis combina lo mejor de trabajar a nivel SQL y del mapeo ORM. Consigue una
gran flexibilidad y ser reutilizable, pero evita la complejidad de JDBC, al mismo tiempo que otorga la facilidad
de mapeo de las herramientas ORM, sin tener su rigidez; sin embargo, no debe ser utilizado cuando se requiere
de una automatización total y transparente de la persistencia [15].
39
39
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Las soluciones ORM completas, como Hibernate, son más avanzadas y proveen de una mayor transparencia
antes cambios del modelo de objetos, dado que generan las sentencias SQL de forma dinámica. Puede crear un
esquema de base de datos de acuerdo con un modelo de objetos Java. Lo anterior confirma que Hibernate
funciona mejor cuando la aplicación está enfocada en el objeto más que en la base de datos, aunque esto exige
tener un control total del esquema. Por ello, no es recomendable el uso de Hibernate en escenarios donde hay
una importante discrepancia entre el modelo de objetos y el modelo de datos.
A modo de resumen, en la siguiente tabla se recogen las principales propiedades de Hibernate y MyBatis que lo
diferencian.
MyBatis Hibernate
Requiere conocimientos de SQL. El
desarrollador es el encargado de programar las
sentencias.
Fomenta el trabajo con objetos de entidades y
genera las sentencias SQL necesarias, lo que implica
un ahorro de tiempo.
Se puede aprovechar las características
específicas de la base de datos y preparar
consultas SQL optimizadas. Mejor rendimiento.
Se pierde el control de las sentencias que realmente
se están ejecutando.
Más simple y flexible, con una curva de
aprendizaje más rápida. Fuerte curva de aprendizaje. Tiene múltiples
opciones para formar consultas: SQL, HQL, Criteria
API.
Centrado en SQL, dependiente de la base de
datos. Mantenimiento más costoso. Orientado a objetos. Lo hace relativamente
independiente de la base de datos.
Mapea ResultSets de la API JDBC a objetos Java.
No es relevante la estructura de tablas de la base
de datos. Mejor uso con base de datos pre-
existentes.
Mapea objetos Java a tablas de la base de datos.
Eficaz cuando se tiene el control completo sobre el
esquema de la base de datos.
Tabla 3–1. Comparativa MyBatis - Hibernate
3.4 API JAX-WS – proyecto Metro
En la especificación JSR 224 - JAX-WS 2.0 (Java API for XML Web Services) se establecen los fundamentos
para la creación de servicios Web basados en SOAP en Java. La implementación de referencia de JAX-WS
forma parte de las plataformas Java SE y Java EE.
Al utilizar JAX-WS, el desarrollo de servicios web y clientes se simplifica con una mayor independencia de la
plataforma para aplicaciones Java mediante el uso de proxies dinámicos y anotaciones Java. Aunque los
mensajes SOAP son complejos, la API JAX-WS oculta esta complejidad del desarrollador de la aplicación. En
el lado del servidor, simplemente hay que especificar las operaciones del servicio Web definiendo métodos en
una interfaz escrita en el lenguaje de programación Java, y desarrollar una o más clases que implementan esos
métodos. En el lado cliente, se crea un proxy (un objeto local que representa el servicio) y luego simplemente se
invocan métodos en el proxy. El runtime de JAX-WS se encarga de leer o generar los mensajes SOAP,
permitiendo al desarrollador centrarse en la tarea de programar la funcionalidad que implementan los servicios,
y abstrayéndole de los mecanismos de invocación de éstos.
Existen varias implementaciones de la especificación como CXF o Axis2. El proyecto Metro, perteneciente a la
comunidad GlassFish, es el que desarrolla el código base para la implementación de referencia (JAX-WS RI)
de la especificación, como un componente dentro de su pila de servicios Web (Web service stack). Los
componentes que constituyen la distribución de Metro son:
• WSIT (Web Services Interopartibility Technologies): subsistema que incluye diferentes APIs de Java
que implementan varias especificaciones WS-* con las que se puede extender el protocolo SOAP y
ampliarlo con funcionalidades avanzadas empresariales. Así, proporciona funciones de servicios Web
como mensajería confiable, transacciones atómicas, o seguridad y confianza.
Herramientas
40
• JAX-WS RI (Java API for XML-Based Web Services Reference Implementation): framework de
desarrollo de servicios Web. Proporciona todas las herramientas y características básicas para la
interoperatividad de los servicios Web (mensajería SOAP, procesamiento WSDL, transmisión de datos
binarios con MTOM, WS-Addressing, etc.). JAX-WS forma parte de la plataforma Java SE desde la
edición 6 (Java SE 6).
• JAXB (Java Architecture for XML Binding Reference Implementation): implementación de referencia
de la especificación JSR-222, es una tecnología de Java que proporciona una capa de correlación
bidireccional entre las clases de Java y documentos XML; es decir, JAX-WS se apoya en JAXB para
mapear de un modo sencillo clases Java con los tipos de datos de un esquema XML, y así simplificar
el desarrollo de los servicios Web.
Figura 3-1. Proyecto Metro
Las operaciones que realiza JAXB de convertir una clase Java en un esquema XML y, al contrario, de
un esquema XML obtener su correspondiente clase Java, se conoce como serialización ("marshalling")
y deserialización ("unmarshalling") respectivamente. El paquete de enlaces de JAXB, javax.xml.bind,
define las interfaces y las clases abstractas que se utilizan directamente con las clases de contenido.
Además, el paquete contiene las API de serialización y deserialización.
Java proporciona cuatro herramientas de línea de comando para facilitar el desarrollo de servicios Web, dos de
la cuales se usan en el tratamiento de documentos WSDL y las otras en la conversión entre esquemas XML y
clases de Java:
• xjc: es un compilador de esquemas ("binding compiler") que genera clases JavaBeans con las
propiedades descritas en el esquema XML origen y las anotaciones JAXB necesarias por la API para
procesar los documentos XML. Es posible personalizar y extender XJC con el fin de alterar la
configuración predeterminada y controlar las correlaciones con las clases Java generadas.
• schemagen: genera esquemas XML (fichero con extensión .xsd) desde clases Java. Una vez creado el
esquema, JAXB puede convertir documentos XML asociados al esquema en y desde objetos Java, en
los procesos de marshalling y unmarshalling.
• wsgen: genera artefactos portátiles JAX-WS a partir de la implementación del endpoint del servicio
Web y la invocación del servicio web. Adicionalmente, puede generar un archivo WSDL y el
correspondiente documento de esquema XML.
• wsimport: genera artefactos portátiles JAX-WS a partir de un documento WSDL dado. Estas clases
facilitan el desarrollo de clientes, y también de servicios en un modelo de diseño descendente (contract-
first).
Además de usar las herramientas de línea de comandos también es posible invocarlas como una tarea Ant [16].
En la implementación del servicio web se ha usado la versión 2.3.1 de Metro que implementa la versión 2.2.11.
La versión de Java SE empleada ya incorpora una implementación de la última API de JAX-WS 2.2; sin
embargo, no contiene todas las dependencias de JAX-WS necesarias que pertenecen a la especificación Java
EE, como el servlet com.sun.xml.ws.transport.http.servlet.WSServlet o algunas clases del paquete
javax.transaction, para que el servicio Web pueda desplegarse en un contenedor servlet sobre Tomcat, que no
es un servidor Web completo de Java EE.
41
41
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
3.4.1 Parte servidora JAX-WS
JAX-WS impone la existencia de un SEI identificada con la anotación @WebService (javax.jws.WebService).
Se conoce como Service Endpoint Interface (SEI) a la interfaz Java que expone las operaciones de una interfaz
de servicio web en términos de métodos abstractos de Java. Es decir, los clientes se comunican con servicios
web basados en SOAP a través de sus interfaces SEI. La clase Java que implementa los métodos definidos en el
SEI se le denomina Service Implementation Bean (SIB).
Es posible desarrollar un servicio usando JAX-WS mediante dos enfoques de partida: a partir de un documento
WSDL dado (enfoque descendente o contract-first) o desde una clase Java que implementa el servicio (enfoque
ascendente o code-first).
De forma general, el enfoque de desarrollo de un servicio Web recomendado es el descendente, partir de un
fichero WSDL para luego generar el código que implemente los servicios. De esta forma, se refuerza el concepto
de que un servicio es una entidad abstracta, neutral a su implementación. También exige una mayor dedicación
en planificar y diseñar la interfaz exacta requerida por el servicio antes de comenzar a codificar. El enfoque
ascendente se emplea habitualmente en los que se necesite habilitar una aplicación existente, apoyado en las
anotaciones del código que habría que añadir.
En cualquier caso, una implementación válida de un servicio web es una clase java que cumple los siguientes
requisitos [17]:
• La clase que implementa el endpoint debe estar anotada con javax.jws.WebService o
javax.jws.webServiceprovider (si se desea trabajar directamente a nivel de mensajes XML).
• La clase que implementa el endpoint podría referenciar de forma explícita un SEI con el atributo
endpointInterface de la anotación @WebService, sin ser obligatorio. Si no se especifica el atributo, se
define un SEI implícito para la clase anotada.
• Los métodos del SEI deben ser public y no declarados como static o final.
• Los métodos del SEI expuestos en el servicio Web deben estar anotados con javax.jws.WebMethod.
• Los métodos del SEI expuestos en el servicio Web deben tener tipos de parámetros de entrada y
devueltos compatibles con JAXB [18].
• SEI no puede ser declarada final ni ser una clase abstracta.
• SEI debe tener un constructor público por defecto.
• SEI no debe implementar el método finalize().
Tanto si se sigue un enfoque de desarrollo ascendente como descendente, las herramientas que provee Java se
pueden usar en el proceso de implementación del servicio Web. En el caso de wsimport, la herramienta da
soporte al enfoque en sentido descendente y genera todos los artefactos Java portables del servicio Web, tanto
para la parte del servidor como del cliente, para poder invocar a las operaciones del servicio web de forma
correcta. wsimport procesa el fichero WSDL y los esquemas XML, y genera artefactos tales como:
• Interfaz Java anotada correspondiente al Service Endpoint Interface (SEI), equivalente al elemento
portType del documento WSDL. Esta interfaz se utiliza para implementar el endpoint del servicio Web
o para crear instancias de cliente proxy dinámico.
• Clase que extiende la clase javax.xml.ws.Service. Representa al elemento service del documento
WSDL, y es utilizada para instanciar al SEI en un cliente proxy dinámico. Proporciona la vista del
cliente de un servicio Web.
• Clases de excepción mapeados desde los elementos fault del WSDL que hubiera.
• Clases correspondientes a los elementos input y output/response de cada operación del servicio Web.
• Beans de datos generados por JAXB a partir de los tipos de datos de los esquemas XML.
• Clase Objectfactory para facilitar la instanciación interna de las clases input y output/response.
Herramientas
42
• Clase package-info, que anota el paquete java donde están ubicados las clases Java generadas a partir
de los esquemas XML del WSDL. Toma como referencia el targetNamespace para determinar la
estructura de carpetas.
Figura 3-2. wsimport: equivalencia namespace WSDL / estructura de carpetas
En la siguiente tabla se representa la correlación de los elementos del fichero WSDL con las clases Java que son
generadas al ejecutar wsimport:
WSDL Java
TargetNamespace Nombre del paquete
PortType Nombre del SEI
Operation Nombre del método
Part Parámetro de entrada y valor devuelto
Type Parámetro de entrada y valor devuelto
Fault Clase de Excepción
Binding Anotación javax.jws.soap.SOAPBinding
Service Atributo serviceName de la anotación javax.jws.WebService
Tabla 3–2. wsimport: equivalencia WSDL / Java
Según la especificación de servicios Web para Java EE (JSR-109), los servicios Web para Java pueden
implementarse de varias formas: como una clase Java que se ejecuta en un contenedor Web (modelo de
despliegue basado en servlet), o como un EJB (Enterprise JavaBean) de sesión stateless o singleton en un
contenedor EJB. Como se verá en el próximo capítulo, se ha optado por desplegar en un contenedor de servlet
Tomcat el archivo WAR que empaqueta los artefactos generados y el archivo WSDL junto con la
implementación del SEI correspondiente al servicio Web del PIDS desarrollado.
3.4.2 Parte cliente JAX-WS
De acuerdo con la especificación de servicios Web para Java EE, JAX-WS proporciona soporte para el modelo
de cliente de servicio Web dinámico (API Dispatch) y estático (API Dynamic Proxy). Ambos casos permiten la
invocación síncrona y asíncrona de servicios Web de JAX-WS.
El modelo de programación del cliente estático para JAX-WS es el llamado cliente de proxy dinámico. El cliente
de proxy dinámico invoca un servicio web dinámicamente, en tiempo de ejecución, basado en una interfaz del
endpoint de servicio (SEI) generado. Tras crear el proxy (interfaz/clase que extiende javax.xml.ws.Service), el
cliente puede utilizar los métodos del proxy y acceder a la implementación del servicio Web utilizando los
métodos especificados en el SEI. Los clientes de servicio web JAX-WS que utilizan el modelo de programación
de proxy dinámico pueden utilizar la herramienta wsimport, que procesa el archivo WSDL del servicio Web y
generar artefactos Java portátiles con los que crear el cliente del servicio Web. En general, el transporte,
codificación y dirección del endpoint son transparentes para el cliente. El cliente solamente necesita realizar
43
43
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
llamadas sobre el SEI, siendo incapaz de distinguir si los métodos a los que llama están ejecutándose en local o
en remoto, ni tampoco puede saber cómo está implementado el servicio (servlet, EJB, etc.).
Sin embargo, si se prefiere trabajar directamente con XML, con la estructura del mensaje o con la estructura de
la carga de datos del mensaje, en lugar de con una abstracción de Java, la API Dispatch (javax.xml.ws.Dispatch)
permite desarrollar un cliente de servicio web dinámico conocido como cliente de asignación.
Para las demostraciones sobre el funcionamiento del servicio Web desarrollado en el proyecto se hará uso de un
cliente de proxy dinámico, basado en el modelo de programación de cliente estático.
3.5 AWS
Amazon Web Services (AWS) es el conjunto de servicios Web con lo que Amazon proporciona servicios de
infraestructura de TI, que constituye su plataforma de cloud computing pública. Amazon fue el pionero en
ofrecer el exceso de recursos de TI como modelo de negocio, y hoy en día es uno de los proveedores de servicios
más importantes en el panorama del cloud computing. Desde 2006 ha ido evolucionando, ofreciendo en la
actualidad un amplio catálogo de servicios de infraestructura, como capacidad de procesamiento, opciones de
almacenamiento, herramientas de desarrollo, redes y bases de datos que se ofrecen a demanda, con
disponibilidad inmediata y en una modalidad de pago por uso.
AWS ofrece sus servicios a cientos de miles de clientes activos en más de 190 países alrededor del mundo.
Dispone de centros de datos repartidos por el globo, estando su infraestructura global agrupada por regiones y
zonas de disponibilidad. Una región es un área geográfica del mundo independiente que tiene varias ubicaciones
aisladas llamadas zonas de disponibilidad. Cada zona de disponibilidad consta de uno o más centros de datos,
cada uno con energía redundante, redes y conectividad, alojados en instalaciones separadas. Las zonas de
disponibilidad de una misma región están conectadas mediante enlaces de baja latencia. Con esta segmentación
AWS puede proporcionar redundancia, tolerancia a fallos y alta disponibilidad con total flexibilidad para
recursos, como instancias o datos, en varias ubicaciones.
3.5.1 Productos y servicios
Amazon Web Services ofrece más de 90 productos basados en cloud agrupados en diferentes categorías, entre
las que destacan los servicios de computación, almacenamiento, bases de datos, redes y entrega de contenido,
productividad empresarial, análisis, herramientas para desarrolladores, herramientas de administración,
seguridad, IoT, y móviles.
AWS ofrece una suscripción gratuita durante 12 meses para tener una experiencia práctica con la plataforma, y
una amplia selección de productos y servicios de AWS. Las capacidades ofrecidas por la suscripción gratuita
son suficientes para montar la plataforma de simulación y testeo en el cloud sobre la que se evaluará el servicio
de información demográfica objeto del presente proyecto.
Herramientas
44
Algunos de los productos empleados en el proyecto, cuya configuración y uso específico se desarrollará
en el capítulo sobre el trabajo realizado, son los siguientes:
Amazon EC2
Amazon Elastic Compute Cloud (Amazon EC2) es el servicio Web a través del cual se puede solicitar y
aprovisionar capacidad de computación escalable. Ofrece la posibilidad de crear entornos informáticos virtuales
en el cloud, aumentando o disminuyendo su capacidad a demanda, arrancando nuevas instancias (escalado
horizontal) o incrementado las prestaciones de las instancias desplegadas (escalado vertical) en cuestión de
minutos. Una instancia EC2 no es más que una máquina virtual en AWS sobre la que el usuario tiene el control
completo a través de las API del servicio Web. AWS proporciona varios tipos de instancias, que varían
principalmente en prestaciones de CPU y memoria, según las necesidades de rendimiento respectivas del
usuario. También permite elegir el sistema operativo de la instancia a partir de Imágenes de Máquina de Amazon
(AMI o Amazon Machine Image), entre cuyas opciones se incluyen varias distribuciones de Linux y Microsoft
Windows Server.
Amazon EC2 está diseñado para integrarse con la mayoría de los servicios de AWS, como Amazon Virtual
Private Cloud (Amazon VPC) o Amazon Elastic Block Store (Amazon EBS), el primero para proporcionar una
funcionalidad de red sólida a los recursos de TI, y el segundo como almacenamiento de soporte. De esta forma
AWS es capaz de proveer de una plataforma virtual completa ajustada a las necesidades de procesamiento y
almacenamiento particulares de cada cliente.
Un apartado en el que AWS hace especial hincapié es la capa de seguridad que envuelven todos sus servicios.
En este sentido, Amazon EC2 ofrece diferentes funcionalidades y características que la convierte en una solución
segura. Por ejemplo, los grupos de seguridad ("security groups") que se vinculan a las instancias EC2 funcionan
como un firewall virtual en el que se especifican reglas con los protocolos, puertos y rango de direcciones IP
que pueden alcanzarlas, así como el tráfico de salida permitido. Otros ejemplos serían la criptografía de clave
pública para el inicio de sesión y obtener el acceso de forma segura a la instancia, políticas de control de acceso
a los recursos de Amazon EC2, o la configuración de redes virtuales aisladas de forma lógica del resto del cloud.
Amazon VPC
Amazon Virtual Private Cloud (Amazon VPC) permite aprovisionar un entorno virtual, denominado nube virtual
privada (VPC), aislado lógicamente del resto de redes virtuales. Se pueden utilizar los recursos de AWS dentro
de la red de Amazon VPC y ahí crear instancias de Amazon EC2. El usuario tiene el control completo de la red,
pudiendo configurarla con los elementos propios de una red tradicional como el rango de IP, las subredes, las
tablas de enrutamiento o las puertas de enlace a Internet.
Al igual que con las instancias EC2, también se pueden establecer grupos de seguridad a nivel de VPC y
subredes, así como listas de control de acceso a la red.
AWS IAM
La herramienta de seguridad de AWS Identity and Access Management (IAM) complementa al resto de servicios
dotándolos de mecanismos avanzados de control de acceso de los usuarios y recursos de AWS. IAM controla
las medidas de identificación y autenticación, locales o federadas, claves de acceso, autenticación multifactor
(MFA), dispositivos permitidos, etc. Con IAM se pueden crear usuarios o grupos de usuarios, concederles
credenciales de seguridad, granularizar los privilegios de acceso mediante roles hasta el nivel de las operaciones
que pueden realizar sobre los recursos o servicios de AWS, e incluso restringir aún más los permisos en base a
condiciones específicas. Además, se pueden delegar permisos para crear y administrar recursos de AWS a otros
servicios de AWS utilizando roles definidas en IAM.
Amazon ELB
Amazon Elastic Load Balancer (ELB) es el servicio de balanceo de carga que distribuye el tráfico entrante entre
varios destinos, como instancias de EC2, contenedores y direcciones IP. Propicia la tolerancia a fallos y
45
45
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
consistencia en rendimiento de las aplicaciones al equilibrar las cargas de tráfico, y aún más al poder repartirlas
entre varias zonas de disponibilidad.
ELB ofrece tres tipos de balanceadores, en función de si opera a nivel de aplicación (capa 7) para tráfico HTTP
y HTTPS (ALB o Application Load Balancer), a nivel de transporte (capa 4) para carga de tráfico TCP (NLB o
Network Load Balancer), o con aplicaciones que se construyeron dentro de la red EC2- Classic (CLB o Classic
Load Balancer). Todos ellos garantizan la escalabilidad, rendimiento y seguridad para obtener un alto nivel de
tolerancia a fallo para cualquier aplicación.
Amazon EBS
Amazon ofrece varias opciones de almacenamiento de datos, con diferentes prestaciones de rendimiento y
durabilidad, que pueden combinarse para ajustarse a los requerimientos particulares de los usuarios y cargas de
trabajo.
Amazon Elastic Block Store (EBS) proporciona volúmenes de almacenamiento de nivel de bloque fácilmente
escalables y diseñados para utilizarlos con instancias EC2 en ejecución que se encuentren en la misma zona de
disponibilidad. Son persistentes al ciclo de vida de las instancias, aunque sólo pueden estar vinculadas a una sola
en cada momento. Ofrecen un rendimiento de I/O de disco constante y de baja latencia, lo que lo convierte en
una opción adecuada para bases de datos relacionales. Amazon EBS asegura la protección de los datos en reposo
y tránsito (datos que se mueven entre el volumen y la instancia) con características de cifrado y la configuración
de políticas de control de acceso en IAM.
3.6 Docker
Docker [19] se han impuesto prácticamente como el estándar de facto para la administración de contenedores.
Cuenta con el apoyo de las grandes compañías del sector tecnológico, asegurando su continuidad y evolución
continua, y proporciona una suite de herramientas que facilitan su empleo sin prácticamente tener que conocer
su funcionamiento interno. Por todo ello, será la solución empleada en el desarrollo del presente proyecto.
Tras una breve introducción, este apartado está dedicado a describir los conceptos y componentes de su
arquitectura interna, los modelos habituales de despliegue, así como algunos comandos básicos empleados en la
gestión de los contenedores, con el objeto de facilitar la comprensión y seguimiento del trabajo desarrollado.
3.6.1 Introducción
Docker empaqueta aplicaciones o servicios en contenedores, unidades estandarizadas que incluyen todo lo
necesario para su ejecución, incluidas bibliotecas, herramientas de sistema, código y entorno de ejecución. Su
eslogan “Build, Ship, and Run Any App, Anywhere” resume perfectamente las principales funciones de uso:
Docker dota de un sistema para implementar y ajustar la escala de aplicaciones rápidamente en cualquier
entorno, desde el desarrollo hasta la operación, con independencia del sistema operativo de las máquinas que
alojen los contenedores, consiguiendo así un proceso de integración ágil en las pruebas y despliegues.
Docker está disponible es dos ediciones: Community Edition (CE) y Enterprise Edition (EE).
Docker Community Edition (CE) es la plataforma pensada para desarrolladores y pequeños equipos
que están comenzando a experimentar con Docker y las aplicaciones basadas en contenedores
Docker Enterprise Edition (EE) está diseñado para el desarrollo empresarial y equipos de IT que
despliegan aplicaciones productivas en escala. Es una solución del tipo Container-as-a-Service (CaaS)
que proporciona a las empresas una solución para implementar cualquier aplicación distribuida en
cualquier infraestructura, tanto on-premise como Cloud [20].
3.6.2 Arquitectura, componente y comandos básicos
Es conveniente introducir brevemente el concepto de imagen, muy recurrente en la terminología de Docker, y
que más adelante se tratará con más profundidad. Las imágenes se podrían entender como un componente
estático, de sólo lectura, que contienen todo lo necesario para ejecutar un ejecutar un software, incluyendo el
Herramientas
46
código, el entorno de ejecución, bibliotecas, variables de entorno y archivos de configuración. En este sentido,
un contenedor en Docker es la instanciación o ejecución de una imagen, pudiendo ejecutar varios contenedores
a partir de una misma imagen.
En Docker, su arquitectura está integrada por cuatro componentes: el motor de Docker (Docker Engine),
containerd, containerd-shm y runC.
Figura 3-3. Arquitectura Docker
Docker Engine es el principal punto de entrada de Docker. Es responsable de construir la imagen Docker,
orquestación, administración de volúmenes, redes, escalado, etc.
Docker Engine es una aplicación cliente-servidor: un cliente Docker lanza instrucciones a un proceso demonio
Docker (dockerd) que escucha las peticiones del cliente y se encarga de las tareas de construir, ejecutar, distribuir
y mantener los contenedores, imágenes, volúmenes y redes. Aunque incluye un cliente en línea de comandos
(CLI), el comando docker, Docker Engine permite la interactuación con cualquier cliente, local o remoto, que
consuma las interfaces de su API REST (Docker Engine API).
Una vez que Docker Engine reciba la solicitud para ejecutar una imagen, delega la responsabilidad al demomio
de containerd (docker-containerd), que administra el ciclo de vida completo del contenedor. containerd permitió
desprender la supervisión de los contenedores del Docker Engine y en un demonio separado.
El proceso runC es el responsable de la creación e inicio del contenedor, es decir, es el entorno de ejecución de
un contenedor. Se ocupa de la interfaz de bajo nivel (libcontainer) con las funcionalidades del kernel de Linux
como cgroups o namespaces, pero de acuerdo con las especificaciones de la Iniciativa Open Containers (OCI)
[21].
De forma general, la secuencia de ejecución interna de un contenedor en Docker se resume de la siguiente
forma:
• Docker Engine recibe la instrucción de ejecutar una imagen Docker.
• Docker Engine delega la responsabilidad de administrar el ciclo de vida completo del contenedor a
containerd.
• containerd descarga o transfiere la imagen desde el repositorio y almacena la imagen. Crea el sistema
de archivos raíz antes de llamar al contenedor-shim con la configuración del contenedor.
• containerd-shim usa runC para crear e inicializar el contenedor.
• Cuando el contenedor arranca, runC finaliza para permitir contenedores sin procesos demonios.
• El ciclo de vida restante del contenedor es administrado por containerd a través de container-shim (hay
un proceso containerd-shim por cada contenedor).
Este esquema da la capacidad de reiniciar o actualizar el motor sin impacto sobre los contenedores en
ejecución.
47
47
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Muchos de estos componentes han sido donados por Docker a proyectos independientes en su empeño por
dotar mayor modularidad a su arquitectura, al mismo tiempo asegurar la neutralidad con otros actores de la
industria (proveedores de servicios Cloud y otros servicios de orquestación).
3.6.2.1 Imágenes Docker
Ejecutar una aplicación en Docker comienza con la construcción u obtención de una imagen Docker. Una
imagen Docker es un archivo tar que contiene todos los archivos, bibliotecas, binarios e instrucciones necesarias
para ejecutar una aplicación. Es básicamente el sistema de archivos que usarán los contenedores que se creen a
partir de ella.
Existen múltiples imágenes, públicas o privadas, de aplicaciones y servicios proporcionados por sus propios
fabricantes, preparadas para ser usadas en cualquier sistema de Docker. Así, es fácil encontrar imágenes con
aplicaciones preinstaladas como por ejemplo servidores web o motores de bases de datos, que se pueden usar
directamente o crear una nueva imagen a partir de ellas para añadirles alguna personalización adicional, según
sea el propósito deseado.
Es posible construir imágenes propias, adaptadas a las necesidades particulares de la aplicación que se desplegará
en contenedores. Para ello, es necesario crear un archivo de texto Dockerfile que, a modo de receta y con una
sintaxis sencilla, contenga la descripción e instrucciones para automatizar la creación de la imagen. La ventaja
del uso de los ficheros Dockerfile es que permite automatizar el proceso de creación de imágenes e ir
modificándolo según se precise. Por tanto, es posible adaptar el ciclo de vida de los contenedores para que
evolucione al mismo ritmo que el versionado de los servicios o aplicaciones que contienen.
El comando docker build ejecuta las instrucciones de un fichero Dockerfile línea a línea y va mostrando los
resultados en pantalla. Posteriormente se pueden consultar las imágenes creadas y almacenadas con el comando
docker images.
Las imágenes en Docker están compuestas por capas apiladas. Cada capa representa un cambio en el sistema de
archivos del contenedor y estas a su vez pueden ser compartidas entre distintas imágenes. La ventaja de las capas
es que una nueva imagen reutilizará aquellas capas que ya estuvieran almacenadas, haciendo más rápido el
proceso de construcción de imágenes y reduciendo el uso de los recursos de la máquina. Por ejemplo, cada
instrucción en un Dockerfile creará una capa en la imagen. Si se modifica el archivo Dockerfile, al volver a crear
la imagen, solo se reconstruyen las capas que hayan cambiado. Esto es parte de lo que hace que las imágenes
sean tan livianas, pequeñas y rápidas.
Con el comando docker history se puede consultar el histórico de una imagen Docker en el que se muestra todos
los comandos que se han ejecutado para crear la imagen.
Las capas de una imagen se apilan unas encima de otras. Todas, a excepción de la última, son de sólo lectura.
Cuando se crea un nuevo contenedor, se añade una capa de escritura por encima de las capas de lectura
subyacentes. Esta capa conserva cualquier cambio realizado en el contenedor en ejecución, como crear,
modificar o borrar ficheros.
Figura 3-4. Capas de una imagen
Herramientas
48
Docker utiliza controladores ("drivers") de almacenamiento para administrar cómo las capas interactúan unas
con otras (sistemas de fichero de unión o UnionFS). Tiene disponibles varios drivers que implementan de forma
diferente la forma de almacenar y gestionar los contenedores de la máquina Docker, pero prácticamente todos
usan capas de imágenes apilables y la estrategia de copiar al escribir (CoW, Copy on Write): si una capa, incluida
la de escritura, necesita acceder a un archivo o directorio de una capa inferior dentro de la imagen, simplemente
recorrerá las capas y utilizará el archivo existente. Pero la primera vez que otra capa necesita modificar el archivo
(al construir la imagen o ejecutar el contenedor), el archivo se copia en la capa de escritura y se modifica. Desde
entonces, el fichero de la capa subyacente de lectura seguirá existiendo, pero oculto por la copia para el
contenedor. La consecuencia inmediata de esta estrategia es que los contenedores que realicen muchas
operaciones de escritura consumirán un mayor espacio. Es recomendable en estos casos, usar volúmenes Docker
(apartado 3.6.2.5), que son independientes del contenedor en ejecución.
Las capas se identifican por el digest resultante de aplicar un algoritmo de cifrado al contenido de la capa. El
formato del identificador se compone de algoritmo:digest.
Cada capa se almacena en un directorio de la máquina Docker. Para sistemas Linux, en la ruta
/var/lib/docker/<controlado de almacenamiento>/layers/ se pueden consultar las capas existentes. Los nombres
de los directorios no se corresponden con los identificadores de las capas.
3.6.2.2 Docker Hub / Store
Docker Hub es una plataforma Cloud en modalidad Software como servicio (SaaS, Software-as-a-Service) que
provee de un servicio de registro utilizado como repositorio de imágenes Docker. Aloja repositorios de imágenes
públicas aportados por la comunidad y repositorios oficiales, de software mantenido como proyectos de código
abierto, disponibles para su descarga, facilitando la compartición y el lanzamiento de aplicaciones.
En los repositorios no se alojan los Dockerfile, si no directamente las imágenes Docker. Es posible configurar
Docker Hub para que obtener los archivos Dockerfile de un repositorio de código, como GitHub o BitBucket, y
construir las imágenes de los Dockerfile de forma automática cuando se produzcan cambios en los archivos.
Docker Store, a diferencia de Docker Hub, aloja contenido publicado y mantenido por una entidad comercial,
previa validación de requisitos que otorguen garantías de calidad, confianza y soporte. En general, todo el
contenido de la comunidad públicamente disponible puede localizarse a través de Docker Hub y Docker Store,
y esto incluye imágenes oficiales.
Para poder encontrar repositorios e imágenes en Docker Hub, se puede buscar desde el portal web de Docker
Hub o usar el cliente de línea de comandos de Docker para ejecutar el comando docker search. Docker está
configurado para buscar imágenes en Docker Hub por defecto. No es necesario iniciar sesión para buscar y
descargar (comando docker pull) imágenes de Docker Hub; sin embargo, si es necesario identificarse en Docker
(comando docker login) para subir (comando docker push) una nueva imagen al repositorio.
En el desarrollo del proyecto ha sido necesario suscribirse a Docker Hub con una cuenta gratuita para crear un
repositorio privado en el que mantener las imágenes personalizadas de los servidores Web y base de datos que
apoyan el despliegue del servicio de información demográfica.
3.6.2.3 Docker Registry
Docker Registry es un repositorio de imágenes Docker privado y seguro. Se distribuye como una imagen de un
contenedor por lo que sería el equivalente a tener un Docker Hub que forma parte de la arquitectura de
contenedores desplegada, como un componente más. Será especialmente útil en situaciones que necesiten de un
control estricto del dónde se almacenan las imágenes y un sistema de distribución interno y rápido de imágenes.
3.6.2.4 Contendores Docker
Docker ejecuta procesos en contenedores aislados, que tienen su propio sistema de archivos, su propia red y su
propio árbol de procesos. Un contenedor se puede considerar en Docker como la instancia en ejecución de una
imagen Docker. Es necesario, por tanto, conseguir primero la imagen del contenedor.
El comando docker run lanza un contenedor a partir de una imagen y ejecuta el comando que se le indique.
49
49
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Docker busca el nombre de la imagen especificada localmente. En el caso de que no existiera, la descarga de
Docker Hub y crea el nuevo contenedor: se asigna una capa de lectura y escritura, se crea una interfaz de red y
se asigna una dirección IP (si no se especificó ninguna opción de red en el comando de ejecución), se inicia el
contenedor y ejecuta el proceso indicado.
Un contenedor de Docker solo permanece en ejecución mientras el proceso lanzado esté en ejecución. Así pues,
al finalizar el comando /bin/bash con exit, el contenedor se detiene, pero no se elimina. Es posible ejecutarlo de
nuevo o eliminarlo (docker rm <contendor>).
El comando docker ps imprime la lista de los contenedores activos, y añadiendo la opción -a, incluye los
contenedores no iniciadas.
Con el comando docker rmi <imagen> se elimina localmente la imagen indicada, siempre y cuando no haya
ningún contenedor que la use.
3.6.2.5 Volumen de almacenamiento
Se ha explicado que cuando se crea un nuevo contenedor, Docker añade una capa de escritura vinculada al
contenedor por encima de las capas de lectura que conforman la imagen, y en el que el contenedor podría
almacenar datos; sin embargo, cuando se borra un contenedor, también lo hace su sistema de ficheros, perdiendo
cualquier información que contuviera.
Un contenedor está pensado para ser efímero, por lo que la persistencia de la información se plantea como un
reto a considerar. Para atajarlo, Docker proporciona tres opciones para externalizar la persistencia del ciclo de
vida de los contenedores: volúmenes, volúmenes conectados (bind mounts) y volúmenes temporales (tmpfs
mounts).
Figura 3-5. Tipos de volumenes
Además de mantener los datos fuera del ciclo de vida de un contenedor, estas opciones de persistencia presentan
las siguientes ventajas frente a la persistencia de datos en la capa de escritura del contenedor:
• No aumentan el tamaño de los contenedores que los usan.
• Permiten compartir información entre contenedores.
• Proporcionan un mejor rendimiento al escribir directamente sobre el sistema de fichero de la máquina
y no requerir controladores de almacenamientos para UnionFS.
Siempre que sea posible, los volúmenes deben ser la primera opción predefinida para la persistencia de los datos
generados con contenedores Docker. Internamente, un volumen de datos es un directorio especial de cuya
creación y administración se encarga Docker. En sistemas Linux, suele estar albergado en la ruta
/var/lib/docker/volumes/<volume-name> de la máquina anfitriona.
Existen multitud de drivers de volúmenes que se pueden usar, por ejemplo, para almacenar los datos en máquinas
remotas o proveedores de servicios Cloud, entre otras posibilidades.
Herramientas
50
Con los volúmenes conectados o bind mount se usa un directorio local del sistema de archivos de la máquina
dentro de uno o más contenedores de Docker. Una de las desventajas que existen es que la administración de los
permisos en los directorios se vuelve manual y propenso a errores. Además, al no haber ninguna restricción en
cuanto al directorio que puede mapearse, la posibilidad de escribir sobre el sistema de ficheros de la máquina
anfitriona se rompe en mayor medida el aislamiento con el contenedor, pudiendo crear brechas de seguridad.
Los volúmenes temporales o tmpfs mount no ofrecen realmente una solución de persistencia para los datos ya
que son almacenados en la memoria de la máquina anfitriona y se borran igualmente al detener el contenedor.
Son una manera de montar carpetas temporales en un contenedor, útil para almacenar información sensible, de
sesiones web o contenido temporal.
3.6.2.6 Red
Docker permite crear y configurar diferentes redes para la comunicación entre los contenedores y con máquina
anfitriona, mediante línea de comandos con el comando principal docker network. Incluye varios controladores
("drivers") de red nativos, sin requerir ningún módulo extra.
Al instalar Docker, se crean automáticamente 3 redes preconfiguradas, denominadas none, host y bridge.
• none se usa cuando no se quiere que el contenedor tenga funciones de red.
• bridge es la red por defecto a la que se conectarán todos los contenedores si no se especifica otra red en
la ejecución del contenedor. Usa el direccionamiento 172.17.0.0/16.
En la instalación de Docker, automáticamente se configura en la máquina anfitriona una nueva interfaz
virtual de red, docker0, será usada para hacer la conexión puente (Linux bridge) con cada uno de
nuestros contenedores. La IP por defecto de docker0 es 172.17.0.1.
Figura 3-6. Pila de red
• host representa la red mapeada de la máquina anfitriona. Esto significa que no habrá aislamiento a nivel
de red entre la máquina anfitriona y los contenedores que corran en él. Por ejemplo, si iniciamos un
contenedor que ejecute un servidor web, será accesible desde la IP de nuestro host, sin necesidad de
exponer ningún puerto.
Un punto importante en relación a la red bridge por defecto es que Docker no soporta el descubrimiento
veth (virtual ethernet devices):
interfaz de red que actúa como un cable de conexión entre dos espacios de nombres de red. Un extremo del veth se coloca dentro del contenedor con el nombre de ethX y el otro se conecta a la red Docker.
Docker0 (Linux bridge):
Implementación virtual de un switch físico dentro del kernel de Linux.
Network namespace:
Representa una pila de red aislada con su propia colección de interfaces, rutas y reglas firewall.
51
51
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
automático de servicio en ella, lo que significa que los contenedores conectados podrán comunicarse por IP y
no por el nombre del contenedor.
La configuración de las redes puede ser totalmente personalizada, y crear una topología virtual de redes
segregadas para la comunicación aislada entre contenedores que estén dentro de las mismas. Así, por ejemplo,
se podría generar una nueva red bridge, con un direccionamiento IP específico, si fuese necesario, a la que se
conecten ciertos contenedores para crear entornos aislados
Figura 3-7. Redes bridge
A diferencia de la red puente predeterminada, las redes definidas por el usuario admiten la dirección IP manual
y la asignación de subred. Además, una nueva red bridge definida incorpora un servicio de DNS interno que
resuelve nombres de contenedores en direcciones IP. Si no se especificase un direccionamiento IP a la red, el
driver IPAM nativo de Docker se lo asigna automáticamente tomando la siguiente subred disponible en el
espacio de IP privado.
No obstante, muchas aplicaciones requieren entornos de conexión específicos para efectos de seguridad o
funcionalidad que las redes nativas de Docker no cubren. Es por ello que el soporte de red es ampliable, mediante
drivers de red adicionales que soporten topologías específicas. Hay muchos proyectos enfocados en expandir el
ecosistema de redes de Docker.
Figura 3-8. Diferencia redes bridge y overlay
Herramientas
52
Un tipo de red cuya configuración será necesaria durante el proyecto es la red overlay o superpuesta: red virtual
construida por encima de las conexiones de red existentes que conecta los contenedores distribuidos en un clúster
de máquinas. Se abordará en el apartado específico de los enjambres de nodos o swarm.
3.6.2.7 Puertos
Por defecto, los contenedores son accesibles por otros contenedores ejecutándose en la misma máquina
anfitriona y por la propia máquina sin ninguna configuración adicional. La interfaz docker0 de la máquina
simplemente enrutará las solicitudes recibidas al contenedor adecuado.
Cuando se crean imágenes o se ejecutan contenedores, existe la opción de exponer los puertos o publicarlos:
• Exponer un puerto se utiliza con fines informativos para indicar los puertos en cuestión que usa el
contenedor.
• Publicar un puerto, lo mapeará a la interfaz de la máquina anfitriona y hará que sea accesible desde el
exterior.
3.6.2.8 Comandos
La siguente figura recoge un resumen de los comandos básicos del CLI descritos en los puntos anteriores
Figura 3-9. Comandos Docker
3.6.3 Otras herramientas del ecosistema Docker
Hoy en día Docker es mucho más que un entorno de ejecución de contenedores. Al mismo tiempo que ha ido
ampliando su enfoque empresarial, se han ido asentado numerosos proyectos de código abierto alrededor del
proyecto central, dotándolo de un ecosistema vivo de herramientas, interfaces y servicios adicionales. En su
última etapa, los esfuerzos parecen estar dirigidos al despliegue sencillo, rápido y flexible de aplicaciones
distribuidas en infraestructuras y entornos Cloud. Esta evolución ha sido posible con el respaldo de gigantes de
la industria como Microsoft, Amazon u Oracle.
Otras herramientas de interés que complementan el ecosistema Docker son:
3.6.3.1 Docker Compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones multicontenedor en Docker. A partir
de un fichero central, basado en el lenguaje de marcado YAML, que describe y configura los servicios que
componen la aplicación, y un único comando se pueden iniciar los múltiples contenedores que conjuntamente
soportan una aplicación.
Docker-compose tiene la consideración de herramienta de orquestado de contenedores con la limitación de estar
53
53
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
acotado su funcionamiento a una única máquina. Si se ejecutan diferentes proyectos compose en la misma
máquina, docker-compose los aísla diferenciándolos por un nombre de proyecto, que por defecto se corresponde
con el nombre del directorio base del proyecto.
Se ha ido modificando la sintaxis de los ficheros compose para soportar, con cada versión de formato, nuevas
funcionalidades y compatibilidad con las versiones actualizadas de Docker (enlace). La versión más reciente y
recomendada en la 3.X, diseñada especialmente para habilitar el uso de Compose con Docker en modo Swarm.
3.6.3.2 Docker Machine
Es un proyecto open source para automatizar la creación y administración de máquinas preparadas con Docker
en casi cualquier infraestructura, simplificando el proceso de instalación de Docker Engine de forma sustancial.
Docker Machine permite utilizar nuestro cliente CLI Docker para gestionar de forma centralizada otros Docker
Engine remotos que hayan sido configurados con esta herramienta. Se encarga también de generar los
certificados TLS para la comunicación segura con cada máquina provisionada.
Existen multitud de drivers o conectores para interactuar con máquinas de las principales plataformas de IaaS
como Amazon Web Services, Microsoft Azure, Google Compute Engine, Digital Ocean o plataformas de
virtualización de escritorio como VMware Fusion, VMware vSphere u Oracle VirtualBox (enlace). También
tiene un driver genérico para poder gestionar una máquina que ya estuviera funcionando y se quisiera gestionar
con Docker Machine.
Se suele recurrir a esta herramienta cuando es necesario gestionar un elevado número de nodos de Docker. Es
por ello que se usa habitualmente en combinación con Docker en modo Swarm para la configuración y
administración de un clúster de nodos Docker.
3.6.3.3 Docker en modo Swarm
Es la solución nativa para la gestión y orquestación de clúster de contenedores.
Ha tenido un papel destacado en el proyecto para conseguir alcanzar los objetivos planteados. Es por ello que se
le dedicará un apartado específico en el que describir más ampliamente su arquitectura y aplicación.
3.6.3.4 Docker Cloud
Docker Cloud es un servicio Web mediante el cual se pueden crear, probar, supervisar y distribuir contenedores
Docker en diversas infraestructuras. Docker Cloud no ofrece servicios de alojamiento, por lo que todos los
recursos para las máquinas que se crean han de ser suministrados por proveedores externos o por el propio
datacenter. Destaca por proveer de integración con las APIs de proveedores de Infraestructura como servicio
(IaaS, Infrastructure-as-a-Service) consolidados, facilitando la gestión de las máquinas Docker distribuidas en
diferentes infraestructuras y posibilitando la implementación de arquitecturas híbridas en combinación con
máquinas locales.
La plataforma provee de otras funcionalidades adicionales como su propio servicio de registro, funciones
automatizadas de construcción y prueba de imágenes (por ejemplo, vinculados a cambios en repositorios de
código fuente como GitHub o BitBucket) o realizae escaneos a las imágenes para verificar que están libres de
vulnerabilidades.
3.6.4 Docker en modo Swarm
Docker en modo Swarm es la forma nativa de Docker Engine para la gestión y orquestación de clústeres de
máquinas Docker distribuidas, funcionando como un enjambre ("swarms") de nodos preparados para la
ejecución de contenedores Docker, sin requerir ningún componente externo adicional.
Se basa en el proyecto de código abierto Swarmkit, que incluye un conjunto amplio de funcionalidades para la
orquestación de sistemas distribuidos (no necesariamente contenedores Docker) como el descubrimiento de
nodos, gestión del clúster, planificación de tareas y seguridad.
Herramientas
54
3.6.4.1 Conceptos básicos
Es conveniente aclarar previamente algunos conceptos a las que se hacen referencia habitualmente cuando
Docker se activa en modo Swarm:
• Nodo (“node”)
Un nodo es una instancia de Docker Engine que participa en un Swarm. En términos prácticos, se refiere
a cada una de las máquinas que forman parte del clúster. Pueden tratarse de nodos físicos o virtualizados,
como servidores cloud o plataformas de virtualización de escritorio como VirtualBox.
• Servicio (“service”)
Un servicio es una abstracción que define las tareas, una o varias réplicas, que serán ejecutadas dentro
del clúster. En la definición del servicio se especifica la imagen en la que se basará y qué
parametrización utilizar para crear los contenedores que soportarán el servicio.
• Tarea (“task”)
Las tareas son las cargas de trabajo básicas dentro de Swarm, y son implementadas como contenedores
individuales y los comandos que ejecutan. En este sentido, al hablar de servicios, la forma correcta para
referirse a cada réplica no es contenedor sino tarea.
• Pila (“stack”)
Una pila es un conjunto de servicios que se ejecutan a la vez. Los servicios que componen el stack se
definen en un fichero en formato YAML (con extensión .yml), donde se recogen las especificaciones y
características de cada servicio.
Del mismo modo que Docker Compose define e inicia contenedores, se pueden definir y ejecutar stacks
de servicios Swarm.
En los sucesivos apartados se usará indistintamente el término "tarea de servicio" y "contendor" para referirse a
lo mismo.
3.6.4.2 Arquitectura
Docker en modo Swarm tiene una arquitectura tradicional de clúster, en la que existen nodos encargados de
gestionar el clúster, conocidos como nodos "Manager", y que también planifican las tareas entre los nodos que
las ejecutan, denominados nodos "Worker". Por defecto, los nodos Manager también desempeñan funciones de
nodo Worker, a no ser que se restringa para que sólo asuman las tareas de gestión.
Sólo los nodos Manager pueden gestionar el clúster. Si bien en caso de fallo de todos los managers del clúster
los contenedores que estuvieran en ejecución no se verán afectados y continuarán operando con normalidad, no
será posible administrar el clúster, lo que incluye la planificación de nuevas tareas. Es indispensable disponer de
varios nodos Manager para asegurar el estado global del clúster. Docker en modo Swarm proporciona alta
disponibilidad entre los nodos Manager utilizado el algoritmo de consenso Raft, con el que consigue replicar de
forma consistente el estado del clúster entre todos los nodos Manager. De esta forma si el nodo Manager que
fue elegido líder cae inesperadamente, el resto de nodos Manager podrán seleccionar cualquier otro como líder
para retomar la planificación de tareas.
De acuerdo al algoritmo, si desplegamos n nodos Manager, se requiere un quórum de n/2+1 nodos Manager
para elegir al líder, con una tolerancia a fallos de (n-1)/2 nodos Manager. Por lo general, se recomienda
disponer de un número impar de nodos Manager, pero no demasiados a fin de no saturar el clúster con
actividades de control y penalizar en rendimiento. Las buenas prácticas consideran como idóneos entre tres,
cinco o siete nodos Manager.
3.6.4.3 Características
Docker en modo Swarm integra funcionalidades propias de la orquestación y planificación de servicios en los
nodos de un clúster de contenedores, que ayudan a maximizar la disponibilidad de las tareas, al mismo tiempo
55
55
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
que optimiza los recursos disponibles. La automatización conseguida se hace especialmente relevante en los
casos de servicios con altos requerimientos de escalado, difícilmente administrables de forma manual.
A continuación, se hace describen las principales características incorporadas en Docker en relación a la gestión
de clúster de nodos:
Integración en Docker Engine:
Toda la gestión del swarm de nodos Docker y despliegue de los servicios se puede realizar desde el cliente CLI
del propio Docker Engine.
Diseño dinámico:
Dependiendo de las circunstancias del swarm en un momento determinado, los nodos del clúster pueden
promocionar o degradar su rol en tiempo de ejecución desde un nodo Manager. Así, se adapta el clúster para
hacer más tolerante a fallos durante una demanda puntual, pudiendo volver a restablecer el clúster pasado la
ventana crítica.
Escalado:
Para cada servicio se puede declarar el número de tareas a ejecutar. La decisión sobre qué nodo del clúster
ejecutar una nueva tarea están regidas según la estrategia de planificación elegida (actualmente, sólo soporta la
estrategia denominada spread, basada en la disponibilidad de recursos). También provee mecanismos de control
con los restringir o reservar recursos a las tareas, e influir sobre el planificador conforme a atributos o etiquetas
asignados a los nodos. En caso de conflicto, las restricciones de recursos siempre prevalecerán sobre las
preferencias establecidas.
Tolerancia a fallos:
El nodo Manager está continuamente monitorizando el estado del clúster con la intención de conciliar el estado
vigente con el deseado (propiedad “desired state reconciliation”), según el número de réplicas que se haya
indicado en la definición de los servicios. De esta forma, si algún componente del clúster falla, sea una tarea
individual o un nodo completo, el modo Swarm intentará restaurar el estado deseado, recreando todas las
tareas afectadas en los nodos restantes.
Redes multi-nodos:
Enfocado a servicios, Docker proporciona funcionalidades de red avanzadas para la creación de redes virtuales
superpuestas (“overlay”), basadas en la tecnología VXLAN [22], que abstraen las topologías subyacentes y
comunican los contenedores de un servicio ejecutados en nodos distribuidos, desacoplándolo de la red física.
Básicamente, VXLAN usa una técnica de encapsulamiento que envuelve tramas de nivel 2 en datagramas UDP
de capa 4, y las envía por redes IP.
Según los requisitos de la aplicación, se pueden crear tantas redes como sean necesarios. Los contenedores que
se unan a la red podrán comunicarse, independientemente del nodo en que estén desplegados. El alcance para
estas redes siempre será de todo el clúster, aunque un nodo no tendrá acceso a la red hasta que se ejecute en el
nodo al menos una de las tareas de un servicio vinculado a la red en cuestión.
Por defecto, cuando se inicia un clúster de Swarm, se crean automáticamente dos nuevas redes en todos los
nodos del clúster. Una de ella es la red overlay por defecto ingress de Docker Swarm y la otra es una red de tipo
puente docker_gwbridge que conecta las redes overlay con una interfaz física local para puentear la
comunicación con el exterior, y funcionando a modo de gateway interno dentro del clúster. Los flujos de tráfico
de contenedor a contenedor no pasan por este puente.
Gracias al proyecto libNetwork, Docker puede extenderse mediante plugins de drivers de red para soportar un
amplio rango de tecnologías de red, como VXLAN, IPVLAN o MACVLAN, y adaptarlo a los requisitos de las
aplicaciones y a diferentes entornos de red. Actualmente Docker en modo Swarm solo es compatible con el
driver integrado de redes overlay.
Herramientas
56
Descubrimiento de servicios:
Docker distribuye por balanceo de carga las solicitudes internas de un servicio a otro servicio asignándoles una
dirección IP virtual (VIP). Por defecto, cuando se vincula un servicio a una red, los nodos Managers asignan al
servicio una IP virtual. Adicionalmente, aprovechando los servidores DNS embebidos de cada Docker Engine,
es posible resolver internamente los nombres de servicios y contenedores desplegados en el clúster, siempre y
cuando la consulta provenga de un contenedor conectado a la misma red del servicio o contenedor solicitado.
La secuencia de resolución sería la siguiente:
• Cada tarea tiene un "DNS resolver" que envía consultas DNS a Docker Engine, que actúa como servidor
DNS.
• Docker Engine comprueba si la petición DNS se corresponde con una tarea o servicio en cada red a la
que está conectado el contenedor solicitante.
• Si la encuentra, Docker Engine busca la dirección IP que coincida con el nombre de la tarea o servicio
en su almacén de clave-valor y devuelve la IP o IP virtual (VIP) del servicio al solicitante.
• Si no lo encuentra, Docker Engine envía la petición DNS al servidor DNS por defecto (lo interpreta
como una consulta DNS externa).
Figura 3-10. Descubrimiento de servicios
Balanceo de carga:
Para apoyar al descubrimiento interno de servicios, Docker usa una función de balanceo de carga de tráfico
integrada en el kernel de Linux, IP Virtual Server (IPVS). Esta función se activa automáticamente cuando se
crea un servicio, al que, como se ha comentado, se le asigna una IP virtual (VIP) [23]. Por defecto, cuando
Docker Engine resuelve la VIP de un servicio consultado, se encarga de balancear el tráfico enviado a esa IP
virtual entre todos los contenedores sanos de ese servicio en la red.
Existe la opción de configurar el servidor DNS de Docker para que resuelva las solicitudes en modo round-robin
[24], sin que sea necesario crear una VIP para cada servicio; el servidor resuelve el nombre del servicio de forma
rotativa a las IPs de las tareas individuales que lo componen.
Figura 3-11. Secuencia de balanceo de carga
57
57
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Por otro lado, el modo Swarm utiliza el sistema de balanceo de capa 4 (capa de transporte) para exponer servicios
hacia el exterior del clúster cuando se crea o actualiza el servicio. Si se publica un puerto de un servicio, se
publica en todos los nodos del clúster. Docker utiliza su propiedad "routing mesh", que combina las tecnologías
IPVS e iptables del kernel, para dirigir las solicitudes externas a las tareas que constituyen el servicio a través de
la red overlay ingress. Así, los clientes externos, como por ejemplo un balanceador de carga cloud, pueden
acceder al servicio por el puerto publicado desde cualquier nodo del clúster, incluso si la petición llegase a un
nodo que no está ejecutando ninguna tarea del servicio. Routing mesh permite abstraerse del número y
distribución de los contenedores que soportan el servicio; Docker se encarga automáticamente de la compleja
capa de gestión de la arquitectura de red subyacente y el balanceo de carga, sin que se convierta más en una
preocupación para poder escalar los servicios.
Figura 3-12. Propiedad routing mesh
Seguridad por defecto:
Docker en modo Swarm implementa mecanismos para garantizar la seguridad del tráfico de control y gestión
en todas las fases del ciclo de vida de la orquestación: inicio de confianza del clúster, adhesión segura y
autorizada de nuevos nodos, gestión de identidad de los nodos, almacenamiento e intercambio de información
autenticada y cifrada.
Teniendo en mente los mecanismos de seguridad mencionados, la secuencia de creación del swarm sería:
• El nodo Manager designado genera una nueva Autoridad de Certificación raíz (CA) para distribuir
certificados entre los nodos que se vayan añadiendo al swarm y así disponer de un canal de
comunicación TLS cifrado y con autenticación mutua de nodos. Si se prefiere, Docker da la posibilidad
de usar una CA raíz externa.
• También se generan dos tokens para el ingreso de nuevos nodos al swarm, uno para nodos Managers y
otro para Workers, que incluyen un hash del certificado de la CA raíz junto con una clave generada
aleatoriamente. Los nuevos nodos usarán el hash para validar el certificado de la CA raíz contra el
Manager, y el Manager usará la clave para confirmar que es el nodo que solicita incorporarse al swarm
es válido.
Figura 3-13. Token de anexión al Swarm
Herramientas
58
• Una vez añadidos, el Manager emite un certificado para el nuevo nodo que lo identifica.
• Los nodos del swarm usan sus certificados para establecer comunicaciones mutuamente autenticadas.
Para los datos en tránsito entre contenedores de distintos nodos o con clientes externos, se puede cifrar el tráfico
utilizando túneles IPSec. Se habilita el cifrado indicando el parámetro --opt encrypted al crear una red overlay.
Entonces, Docker crea túneles IPSEC entre todos los nodos donde haya planificadas tareas de servicios
conectados a la red. El nodo Manager también regenera periódicamente claves simétricas para IPSec y las
distribuye a todos los nodos del clúster.
Actualizaciones continuas ("rolling updates"):
Con el modo Swarm se puede gestionar y especificar la estrategia de actualización de los servicios, tanto en el
momento de la creación del servicio como posteriormente. Por ejemplo, si la imagen de un servicio se ha
actualizado o cambia, el nodo Manager del swarm puede actualizar las tareas de un servicio, fijando la
secuencialidad o el paralelismo de la actualización y el retraso entre actualizaciones que eviten interrupciones
en el servicio. De esta forma, Docker actualizará la configuración del servicio, deteniendo las tareas
desactualizadas y creando las nuevas que coincidan con la configuración deseada, sin la necesidad de reiniciar
el servicio.
3.6.4.4 Componentes adicionales
En el anterior apartado se han enunciado las características principales inherentes al uso de Docker en modo
Swarm. Existen otros componentes o propiedades relacionados con esta modalidad de funcionamiento que se
han utilizado en el proyecto. Se introducen a continuación para facilitar la comprensión de su uso cuando se
describa en el capítulo de trabajo realizado.
Secrets:
Cuando, a efectos de configuración, es necesario compartir con los contenedores de Docker datos sensibles
desde el punto de vista de la seguridad como contraseñas, certificados, claves privadas o cualquier otra
información de acceso restringido se solía recurrir a variables de entorno que se declaraban en el momento
de la ejecución de los contenedores e incluso incrustados en un fichero Dockerfile. Sin embargo, esta
práctica no es segura. Esa información estará expuesta al examinar las propiedades del contenedor, con el
consecuente riesgo de seguridad que ello supone.
Docker Secrets es una funcionalidad que permite la gestión centralizada datos sensibles, secrets, para cifrarlos
y distribuirlos entre las tareas de los servicios a los que explícitamente se les ha otorgado acceso.
Al añadir un nuevo secreto al swarm, con el comando docker secret create, Docker envía el secreto al nodo
Manager a través de una conexión TLS mutuamente autenticada, haciendo uso de la CA integrada que se creó
automáticamente en el inicio del swarm. Los secretos se almacenan cifrados en el log Raft intercambiado entre
todos los nodos Manager; por tanto, solo los nodos Manager tienen acceso a todos los secretos almacenados.
Si un servicio, nuevo o en ejecución, recibe el permiso de acceder a un secreto, el nodo Manager se encarga de
enviar el secreto a la tarea de servicio a través de la conexión TLS mutuamente autenticada ya establecida
exclusivamente a los nodos que ejecutarán el servicio en particular. Es decir, las tareas y los nodos no pueden
solicitar un secreto, sino que son emitidos por un nodo Manager como parte de la creación o actualización de
un servicio.
El secreto se monta no encriptado en un sistema de archivos en memoria dentro de las tareas del servicio
autorizado, bajo la ruta /run/secrets/<nombre_secreto>. Tan pronto como un nodo deje de ejecutar tareas de un
servicio autorizado, eliminará el secreto de la memoria.
Volumen - persistencia multi-nodo con RexRay:
Por naturaleza los contenedores Docker son volátiles, es decir, los datos que se escriban tenderán a desaparecer
a la vez que lo hace el propio contenedor. Un contenedor, por tanto, no tiene almacenamiento persistente de
forma predeterminada; la información añadida a su sistema de ficheros está sujeta al ciclo de vida del contenedor.
Esta circunstancia no supone ningún problema para servicios sin estado o "stateless". Sin embargo, para los
59
59
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
servicios "stateful" , como por ejemplo las bases de datos, habría que almacenar los datos en algún lugar externo
al propio contenedor, y así evitar que la información no se vea afectada cuando un contenedor necesite ser
sustituido.
Ya se vió en el apartado 3.6.2.5 que Docker ofrece la solución de montar volúmenes con el objetivo de conservar
los datos, incluso cuando se elimina el contenedor donde está montado. Sin embargo, siempre estarán vinculados
al sistema de ficheros de la máquina donde está ejecutándose el contenedor. En un entorno de clúster de nodos
Docker distribuidos que ejecuta un servicio stateful, se presenta la dificultad que los datos han de estar accesibles
independientemente del nodo en el que estén ejecutándose las tareas del servicio.
Una posible solución para paliar este problema es establecer restricciones en el servicio para que sus tareas se
desplieguen siempre en el mismo nodo sobre cuyo sistema de ficheros se presenta el volumen de persistencia.
Esta opción tiene la limitación de perder la capacidad de escalabilidad horizontal con la que asegurar la alta
disponibilidad de la persistencia de los datos ante un fallo del nodo; se restringe la tolerancia a fallos del servicio,
y por tanto de la aplicación.
Docker puede usar diferentes drivers para manejar los volúmenes [25], algunos vienen integrados y otros se
pueden instalar como plugins con los que extender el soporte de volúmenes de Docker. Para el propósito
perseguido, es necesario un driver de volumen que permita la integración con sistemas de almacenamiento
externos y sea compatible con múltiples nodos. De esta forma, será posible ejecutar bases de datos y otros
servicios stateful sin preocuparse del nodo del swarm en el que vayan a ser desplegados.
REX-Ray
REX-Ray [26] es motor de orquestación de almacenamiento para contenedores de código abierto que cubre
todas las necesidades planteadas. Con una configuración sencilla, se encarga de orquestar las tareas de
almacenamiento entre los nodos de un clúster y proporciona interoperatividad de Docker en modo Swarm con
múltiples proveedores de almacenamiento como Amazon EC2, Digital Ocean, Google Compute Engine, Azure,
e incluso VirtualBox. En el proyecto se emplearán servicios EBS de AWS para alojar la información del servicio
de base de datos relacional que utiliza la aplicación de información demográfica.
REX-Ray basa su funcionamiento en libStorage, una API de almacenamiento que se utiliza de interfaz para
garantizar la integración con plataformas de almacenamiento externas. Implementa una arquitectura
cliente/servidor en la que un servicio centralizado (controller), recibe peticiones de clientes (agents) para realizar
conjuntamente la orquestación del almacenamiento. Esta arquitectura admite varias opciones de
implementación: REX-Ray pueden desplegarse como un único proceso independiente que agrupa todos los
componentes cliente/servidor, o de forma centralizada en la que el servidor los agentes están segregados. En
instalaciones de pocos nodos es habitual emplear el modelo descentralizado, donde REX-Ray en modo
independiente se instala en cada nodo, agrupando cliente y servidor en el mismo servicio. La arquitectura
centralizada es más común y beneficiosa en clústeres de nodos de mayor escala, donde un controlador central
se encarga de coordinar todas las operaciones.
La instalación en los nodos Docker se puede realizar como un simple binario o como un plugin de volumen
Docker, que empaqueta el cliente/servidor en un contenedor. De forma predeterminada, REX-Ray trata las
solicitudes de Docker a través de un socket UNIX.
Además de integrarse con Docker y Kubernetes, REX-Ray permite la interoperación con los orquestadores de
contenedores que sean compatibles con la especificación CSI (del inglés, "Container Storage Interface"), un
estándar para el ciclo de vida de un volumen, que aspira a que las herramientas de orquestación puedan compartir
sus drivers de almacenamiento.
3.6.4.5 Otras soluciones
A lo largo del apartado se ha puesto foco en el funcionamiento de Docker en modo Swarm para desplegar clúster
de nodos Docker de manera manual e interactiva. Otras soluciones, como Docker for AWS, aprovechan los
servicios maduros que implementan los proveedores cloud para ofrecer mecanismos que automatizan la
configuración de una infraestructura de orquestación de contenedores distribuidos. Así, por ejemplo, Docker
para AWS usa Amazon CloudFormation para automatizar el despliegue y la configuración de EC2, configurar
auto-escalado, definir los roles y reglas de control de acceso IAM necesarios, provisionar otros recursos de
infraestructura relacionados con el clúster como balanceadores de cargar, y mucho más.
Herramientas
60
61
4 TRABAJO DESARROLLADO
El desarrollo y despliegue del servicio de identificación objeto de este proyecto se ha estructurado en tres etapas
claramente diferenciadas que pueden corresponderse con la evolución de un sistema monolítico, en nuestro caso
un servicio de información demográfica, hacia una aplicación de consumo distribuido y con una arquitectura
que garantice los requisitos de intercomunicación con otros componentes, así como simplificar su despliegue y
mantenimiento:
1. Implementación funcional del servicio: mejora y ampliación de la implementación del servicio,
tomando como partida el servidor PIDS de un proyecto anterior.
2. Publicación del servicio: adaptación como servicio Web del servidor PIDS a fin de satisfacer los
requisitos de interoperatividad e integración demandados por el servicio en un entorno productivo
heterogéneo, de coexistencia con otras aplicaciones software.
3. Aprovisionamiento del entorno de ejecución: despliegue del servidor en un entorno de virtualización
basado en contenedores, aprovechando la flexibilidad, accesibilidad y capacidades computaciones
ofrecidas por servicios cloud.
En los sucesivos apartados se describen, para cada una de las fases enunciadas, los objetivos particulares a alto
nivel que se han fijado, las decisiones adoptadas para la consecución de tales objetivos, y la exposición de
aquellas actividades y partes de la programación relevantes con las que han materializado dichas decisiones.
4.1 Fase 1: implementación del servicio PIDS
4.1.1 Situación de partida
El presente proyecto toma como referencia el proyecto "Servidor demográfico basado en normas del CEN y
CORBAmed" realizado por María Ángeles Repullo López, cuyo propósito era normalizar un modelo de
información para datos demográficos, basado en estándares europeos de información sanitaria, que pudiera
usarse en el desarrollo de un servicio de información demográfica conforme a la especificación PIDS del grupo
CORBAmed.
Los datos demográficos de referencia para el proyecto de partida están recogidos en normas del Comité técnico
251 del CEN (CEN/TC 251). Concretamente, los estándares que se utilizaron en la constitución del modelo de
información demográfica son los siguientes:
• UNE-CEN/TS 14796:2005. Informática sanitaria. Tipos de datos. La norma define los tipos básicos de
los elementos que representan los valores de la información.
• UNE-EN 14822-2:2005. Informática sanitaria. Componentes de información con fines generales
(GPICS). Esta norma está compuesta de 3 partes. La segunda parte establece los componentes para los
datos no clínicos, incluyendo la información demográfica. La estructura semántica de estos datos se
apoya en los tipos básicos definidos en la norma anterior.
No se seleccionaron todos los tipos a los que se hacen referencia en los estándares, tanto de los básicos como de
los GPICS, sino un conjunto suficiente para representar a una persona. La enumeración que sigue proporciona
el detalle de los tipos que se utilizaron:
• Tipos básicos (primitivos y compuestos): DV (tipo abstracto del que heredan el resto de tipos básicos
del estándar), BL (valor booleano), CS (representa un concepto codificado), CV (concepto codificado,
indicando el esquema de codificación usado), II (identificador unívoco de una entidad), INT (valor
entero), IVL_TS (intervalo de tiempo), ST (cadena de caracteres), TS (estampa de tiempo), URL
(dirección telemática).
Trabajo desarrollado
62
• Tipos CAG (Common Attribute Groups), atributos comúnmente vinculados a información
demográfica: EntityName (nombre de una entidad), EntityNamePart (cada una de las partes de un
nombre), PostalAddress (dirección postal), PostalAddressPart (cada una de las partes de una dirección
postal), Telecom (localizador telemático).
• Tipos GPICS (General Purpose Information Components): HealthcareProfessionalRole (descripción
del trabajo de personal sanitario), LanguageCommunication (idioma de comunicación),
PatientExtendedInformation (información extendida de un paciente), PatientStandardInformation
(información demográfica general de un paciente), Person (información demográfica general de una
persona), SubjectOfCarePerson (relaciona información de una persona que reciba atención sanitaria)
Cada tipo se implementó como una clase Java, de forma que las clases que representan a tipos complejos tienen
como variables a tipos básicos u otros tipos complejos. A partir de estas clases se maneja la información
demográfica sobre las personas.
Se parte también del modelo de base de datos diseñado para almacenar la información demográfica, junto con
el conjunto de clases y métodos Java que proporcionaban desde la creación de la base de datos hasta la gestión
de acceso y consulta para la recuperación de información. Sin embargo, ha sido necesario realizar algunos
cambios en el modelo, manteniendo la normalización de las tablas, por motivos que se indicarán más adelante.
En el proyecto anterior quedaron implementados algunos métodos de dos de las interfaces definidas en el
servicio de identificación de personas de CORBAmed, IdentifyPerson y ProfileAccess.
Nota: es preciso señalar que la norma relativa a los tipos de datos básicos fue anulada en 2012, en favor de un
conjunto de tipos de datos básicos desarrollado por ISO / TC 215, incluido en el modelo de referencia del
estándar UNE-EN ISO 13606. Hasta la fecha, sin embargo, los tipos básicos siguen estando basados en un
subconjunto de la norma obsoleta CEN/TS 14796, aunque en una futura revisión de la norma el enfoque será
alinearse con los tipos de datos HL7.
Si bien una evolución lógica hubiera sido adoptar el modelo de datos al modelo de información recogido en el
estándar 13606-1:2013, se ha mantenido el mismo modelo de datos que en el proyecto inicial al no estar
contenido en el alcance del proyecto. Los objetivos actuales están orientados a mejorar y ampliar la
funcionalidad del servicio original, extender la interfaz de acceso y consumo como servicio Web, quedando
como requisito secundario, aunque deseable, la actualización del modelo de datos.
Figura 4-1. ISO 13606-1:2013: Modelo de referencia
63
63
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Por otro lado, ya se había comentado que la especificación del PIDS de CORBAmed no establecía ninguna
restricción en cuanto al modelo de datos empleado. Con la decisión tomada de mantener el mismo modelo de
datos y teniendo en mente el esquema conceptual de federación de dominios de los servicios PIDS, el presente
proyecto se correspondería con la implementación de un servicio de identificación dentro de un dominio de
identificación local. De esta forma, el actual desarrollo podría seguir utilizándose como referencia en otros
proyectos que profundicen en la federación de dominios o la integración entre sistemas de información clínicos.
En cualquier caso, aprovechando que las variaciones de los tipos primitivos de 13606-1:2013 con respecto a su
predecesora no son significativas, esta consideración quedará recogida como posible línea de continuación o
mejora para la aplicación.
4.1.2 Objetivos
Los objetivos particulares que se marcan en esta fase están dirigidos a:
Migración del sistema de gestión de base de datos, con capacidades de operar en un entorno productivo.
• Optimización del desarrollo para rebajar el nivel de acoplamiento entre el código y el modelo de datos
demográfico, a fin de permitir la reutilización de los componentes funcionales del servidor con
independencia del modelo de datos seleccionado.
• Incremento de la profundidad de acceso a la información demográfica contenida en la base de datos, de
forma que las búsquedas de candidatos o las consultas y actualizaciones de perfiles se puedan realizar
a partir de atributos de más bajo nivel.
• Ampliación de la funcionalidad del servidor PIDS, con la implementación del método
update_and_clear_traits() de la interfaz ProfileAccess.
Alineados con los anteriores objetivos planteados, se han realizado varias modificaciones en la implementación
del servicio original, así como la adición de nuevas funcionalidades. A continuación, se detallan cada una de las
acciones y cambios llevados a cabo para la consecución de dichos objetivos:
4.1.3 Migración del sistema de gestión de la base de datos
El proyecto inicial había usado Microsoft Access como gestor de base de datos. Fue un primer requisito del
proyecto reemplazarla por otro sistema de mayor potencial, que dispusiera de capacidades avanzadas que
permitieran al servicio operar en un escenario con altas cargas de datos o de acceso concurrente, similar a los
demandados en un entorno profesional, y que no limitara su funcionamiento a sistemas operativos de Microsoft.
La justificación de la selección de PostgreSQL como gestor de base de datos fue tratado en el capítulo 3.
En una primera iteración del proyecto, el entorno de desarrollo se conforma localmente sobre una máquina
virtual Linux. En ella se instala y configura PostgreSQL para ejecutarlo como un proceso más de la máquina.
Inicialmente, la importación de la estructura y datos a PostgreSQL no constituye ningún problema, dado que el
proyecto anterior incluye clases, DB_Manager y DB_Factory, con todos los métodos necesarios para la
interacción con la base de datos, creación de las tablas e inicialización con datos de prueba desde sentencias
SQL estándar. Una vez instalado PostgreSQL, tan sólo es necesario modificar los parámetros de acceso que
estaban configurados, como credenciales, nombre de la base de datos, ruta de conexión, e invocar los métodos
de creación de la estructura de tablas de la base de datos y registro de datos iniciales. Para posteriores
restauraciones y pruebas, se parte de respaldos de seguridad que se hacen periódicamente a la base de datos,
pudiendo prescindir del código anterior.
La migración de la base de datos no tuvo ningún impacto sobre el código funcional del servicio. Como se verá
más adelante, todo el código correspondiente a la interacción con la base de datos se reemplaza completamente,
aunque por motivos ajenos a la migración. No obstante, ha habido que aplicar modificaciones menores en la
estructura de la base de datos. Entre los cambios más destacados se encuentran:
• Cambio las propiedades de algunos atributos de las tablas entitynamepart, postaladdresspart y
telecomuse para que admitan un valor por defecto. En la implementación del método
update_and_clear_traits(), la actualización de los tipos a los que pertenecen esos atributos solo ha sido
posible mediante la combinación de varias sentencias en una misma transacción. Al tratarse de atributos
Trabajo desarrollado
64
también declarados como NOT NULL, deben tener un valor registrado durante el proceso de
actualización.
• Adición de un nuevo índice en la tabla EntityName. Se pueden dar casos en los que deban registrarse
diferentes atributos del tipo EntityName para una misma persona. Conforme a la anterior estructura de
la tabla, no había forma de indexar la búsqueda por un registro particular, obligando a devolver en una
consulta todos los registros EntityName que existieran para una persona. Este nuevo índice pretende
representar la preferencia de una persona en cuanto a los nombres que tiene asociados a su identidad.
• Actualización de las restricciones ("constraits") de las tablas para mantener la integridad de la base de
datos en las acciones de actualización y borrado.
• Normalización de tabla postaladdress, dividiendo la tabla en postaladdress y postaluse.
La conexión a la base de datos desde la aplicación Java requiere de controlador JDBC (Java Database
Connectivity). El controlador proporciona una implementación razonablemente completa de la especificación
JDBC además de algunas extensiones específicas de PostgreSQL. Para su funcionamiento, basta con incluirlo
en al classpath para la aplicación.
Como dato adicional, muchas de las tareas de gestión de la base de datos, como copias de seguridad, gestión de
usuarios y permisos de acceso, pruebas de consultas SQL, edición de las propiedades de tablas o la revisión de
los datos registrados, se realizaron desde la interfaz gráfica pgAdmin III, que es compatible con todas las
características de PostgreSQL.
Figura 4-2. Interfaz pgAdmin III
4.1.4 Transformación de la gestión de sentencias
No hay que olvidar que el proyecto estaba inicialmente enmarcado en el propósito global de establecer un
modelo de integración de dominios de identificación federados, en el que cada dominio podría emplear distintos
modelos de información. Es por ello que se considera un requisito clave diseñar el servicio PIDS reduciendo el
acoplamiento general de los componentes del servicio y, muy particularmente, entre la lógica funcional y el
modelo de datos demográficos seleccionado. Mantener el acoplamiento lo más bajo posible entre ambas capas
redundará en una simplificación del mantenimiento, una mejora considerable en la detección y corrección de
errores, y especialmente en la reutilización de la lógica de negocio, dotando modularidad y portabilidad a la
aplicación.
Originalmente, todo el tratamiento de la base de datos se había implementado trabajando con la API JDBC
directamente, haciendo que las operaciones estuvieran integradas en clases Java con el código de la aplicación.
65
65
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Además, se generaron métodos de consulta estrechamente vinculados a cada tipo de dato que se necesitaba
recuperar. Al final, todo deriva en la generación de una gran cantidad de código, con duplicidades y
perfectamente acoplado a la lógica de negocio de la aplicación. Cualquier cambio en el modelo de datos o en las
consultas obligaba a localizar en el código todas las partes implicadas en el cambio, y editarlo, con el consecuente
riesgo de modificar accidentalmente alguna parte no afectada o no tener en consideración todas las dependencias
existentes con el cambio. Posteriormente, era necesario recompilar el proyecto por completo para asegurarse
que no hay errores no contemplados. Una práctica recomendada de la programación por capas consiste
precisamente en separar la lógica de negocio de la capa de datos, apuntando a mejorar la mantenibilidad del
código y compartir totalmente el modelo de datos entre todas las versiones de la aplicación.
Todo lo anterior motivó la decisión de volver a reformular todo el código relacionado con la gestión de las
operaciones de base de datos, insertando una capa de persistencia implementada con el framework MyBatis,
tratado en el apartado 3.3. Esta capa mantiene la aplicación Java independiente de la lógica de persistencia y la
tecnología de la base de datos subyacente. MyBatis se encarga de mapear los objetos, en el ámbito de la
aplicación, a los datos que hay que persistir en la base de datos utilizando ficheros XML denominados mappers.
4.1.4.1 Uso del framework de persistencia MyBatis
MyBatis representa el cambio más significativo realizado en el proyecto sobre la implementación del servicio.
Su utilización conlleva dos aportaciones fundamentales sobre el proyecto: la simplificación del código de acceso
a la base de datos y persistencia de los objetos Java, y el mayor desacople entre el código de la base de datos y
el código de la aplicación.
Para usar MyBatis sólo es necesario incluir el fichero mybatis-x.x.x.jar correspondiente a la versión a utilizar en
el classpath.
La ejecución de SQL y el mapeo objeto-relacional se realiza integrando entre sí los componentes principales de
MyBatis que se describen en la siguiente tabla:
Componente / fichero de configuración Descripción
Fichero de configuración MyBatis SqlMapConfig.xml
Fichero XML que configura el comportamiento de MyBatis.
Es un archivo que explica detalles como los parámetros de
conexión a la base de datos, la ruta de los ficheros de mapeo, cómo
deben controlarse las transacciones, la configuración global de
MyBatis, etc.
org.apache.ibatis.session.
SqlSessionFactoryBuilder
Lee el fichero de configuración y crea una instancia de
SqlSessionFactory.
Esta clase puede desecharse una vez creada la SqlSessionFactory.
No tiene sentido reutilizarlo, por lo que ámbito y ciclo de vida es el
método.
org.apache.ibatis.session. SqlSessionFactory
Crea una instancia de SqlSession.
Es recomendable tener sólo una instancia de esta clase que perdure
durante toda la ejecución de la aplicación, por lo que su ámbito es
la aplicación.
org.apache.ibatis.session. SqlSession
Contiene todos los métodos necesarios para ejecutar sentencias
SQL contra la base de datos y controlar las transacciones.
Las instancias de esta clase no son thread safe, por lo que no deben
compartirse. Cada hilo de ejecución debería tener su propia
instancia de SqlSession. El ámbito adecuado es el de petición
(request).
Ficheros mappers Ficheros XML que contienen las definiciones de mapeo (SQL
mapped statements)
Tabla 4–1. Componentes MyBatis
Trabajo desarrollado
66
Flujo de ejecución
Con una solicitud de ejecución de cualquier sentencia, se desencadena un flujo de trabajo en el que participan
los componentes principales de MyBatis para acceder a la base de datos. Según el momento en que intervienen
los componentes, el flujo de actividad puede dividirse en dos tipos: las actividades que se realizan una única vez
al inicio de la aplicación y las que se ejecutan con cada sentencia. El siguiente diagrama reproduce todo este
proceso:
Figura 4-3. Flujo de actividad de los componentes MyBatis
En el arranque del servicio PIDS (1), se carga el fichero de propiedades que contiene información relevante para
la configuración de la aplicación como las rutas a los ficheros de configuración de MyBatis, otros ficheros de
propiedades o los valores de ciertas propiedades que se usan para la configuración del DataSource. Las
propiedades de este fichero estarán disponibles a lo largo del ciclo de vida de la aplicación. Inmediatamente, a
partir de la clase de utilidad Resources que proporciona MyBatis con métodos que simplifican la carga de
recursos desde el classpath u otras ubicaciones, se recupera la ruta del fichero de configuración de MyBatis (2)
que se utiliza en la instanciación del SqlSessionFactory válido durante la ejecución de la aplicación (3).
MapLoader.java
// …
private static SqlSessionFactory sqlSessionFactory=null; static{
try{ Reader reader=Resources.getResourceAsReader(getMapConfigPath());
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); }catch(IOException e){} }
// …
Posteriormente, con cada operación que la aplicación requiera realizar sobre la base de datos (4), se crea una
instancia SqlSession a partir del SqlSessionFactory construido en el inicio de la aplicación (5)(6). Se usan los
métodos de SqlSession (7) para invocar la sentencia SQL declarada en los ficheros XML de mapeo a partir del
67
67
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
identificador dentro del namespace que se utiliza para el mapped statement indicado (8). El commit y rollback
de la transacción son métodos de SqlSession que deben llamarse desde la aplicación. Igualmente, es importante
cerrar la SqlSession una vez se haya obtenido la respuesta de la sentencia para asegurarse que los recursos de la
base de datos se liberan correctamente.
Con MyBatis se simplifica la programación frente a JDBC, reduciendo el código para las sentencias de tipo
SELECT, DELETE y UPDATE de tipos básicos a prácticamente una línea de código. Así mismo, favorece que
la lógica del servicio se constituya como un bloque software modular, que hace posible su reutilización y
portabilidad, aún en caso de sustitución completa del modelo de datos de la capa subyacente.
Queries.java
// … public List SelectTrait(String consulta, DV ind){ List select=session.selectList(consulta, ind); return select; } public int DeleteTrait(String consulta, DV ind){ int cont=0; cont=session.delete(consulta, ind); if(cont==0) System.out.println("** No se han producido cambios **"); session.commit(); return cont; } // …
Como se puede comprobar en el extracto de código anterior, dado que no se puede saber con antelación el
número de objetos que se espera recibir tras una consulta, los resultados de las sentencias SELECT se recogen
siempre en un objeto java.util.List.
Nota: no ha sido posible confeccionar esa envoltura de abstracción a las sentencias UPDATE de algunos tipos
complejos como EntityName, EntityNamePart, PostalAdress, PostalAddressPart, Language, Telecom,
etc., para los cuales hay que implementar cierta lógica en la aplicación y la creación de métodos específicos.
No se ha estudiado la posibilidad de resolver este inconveniente mediante procedimientos almacenados,
también soportados por MyBatis. No obstante, con el objetivo de homogeneizar el modo de invocación de todos
los métodos de actualización en tiempo de ejecución, requiriendo para ello únicamente el identificador de la
sentencia, se utiliza el patrón Reflection [27] sobre estos métodos.
Configuración
El primer paso sería la configuración de MyBatis mediante el fichero SqlMapConfig.xml, aunque igualmente se
hubiera podido configurar desde la propia aplicación Java.
MyBatis proporciona una multitud de parámetros [28] que pueden personalizarse en función de las necesidades
particulares de la aplicación. Existe una configuración predeterminada para cada parámetro de configuración
que suele ser adecuada en la mayoría de los casos.
Trabajo desarrollado
68
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="<Path_to>/ResourceData.properties"/>
<!-- … -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property value="${driver}" name="driver"/>
<property value="${url}" name="url"/>
<property value="${username}" name="username"/>
<property value="${password}" name="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="pid/pfc/service/maps/CS.xml"/>
<mapper resource="pid/pfc/service/maps/CV.xml"/>
<mapper resource="pid/pfc/service/maps/ST.xml"/>
<mapper resource="pid/pfc/service/maps/URL.xml"/>
<mapper resource="pid/pfc/service/maps/EntityNamePart.xml"/>
<mapper resource="pid/pfc/service/maps/EntityName.xml"/>
<mapper resource="pid/pfc/service/maps/PostalAddress.xml"/>
<!-- … -->
</mappers>
</configuration>
Las secciones más importantes son:
• El elemento environments específica la identificación del entorno por defecto. Pueden configurarse
varios entornos que usen las mismas sentencias mapped statements.
• El elemento enviroment se identifica con un nombre. En este caso, "development".
• La configuración del transactionManager se establece a JDBC, lo que significa que se delega en la
aplicación la gestión de las operaciones transaccionales (commit, rollback, etc.).
• El dataSource se configura como POOLED, es decir, se utilizará un pool de conexiones para que no
sea necesario crear una conexión cada vez que se requiera interactuar con la base de datos. Los valores
de los parámetros de conexión (driver, ruta de conexión, usuario y password) del DataSource se
configuran dinámicamente a partir del fichero de propiedades ResourceData.properties indicado en el
elemento properties.
• Dentro de la sección de mappers se especifican los ficheros XML de mapeo. Se ha considera crear un
fichero por cada tipo de datos para mayor claridad, aunque todos se encuentran en el mismo
namespace.
Aunque podría seleccionarse una ubicación arbitraria, para la locación de los ficheros de mapeo se han
seguido las recomendaciones de MyBatis, en las que los namespaces de los ficheros se deben
corresponder con los nombres de los paquetes Java. De esta forma se asegura la usabilidad del código.
69
69
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Mapped Statements
En el proyecto se toma la decisión de externalizar las sentencias a ficheros a ficheros XML de mapeo (XML
mappers). En estos ficheros es donde se vinculan los objetos Java con sentencias SQL mediante la definición de
mapped statements. Los mapped statements se corresponden con los elementos XML que contienen las
sentencias de tipo SELECT, INSERT, DELETE y UPDATE. Todos los statements deben tener un identificador
único dentro del namespace del mapper. Para ejecutar cualquiera de estas sentencias es necesario pasar la
identificación apropiada desde los métodos en la aplicación Java, además del objeto de parámetro de entrada.
En la mayoría de los casos y dependiendo del tipo de statement, el parámetro de entrada será el JavaBean que
representa un tipo de dato demográfico, o un objeto con los índices de búsqueda particulares.
MyBatis es capaz de determinar automáticamente el TypeHandler con el que mapear los parámetros de consulta
a partir de las variables de los objetos de entrada pasados en el método de invocación del statement.
Para cada tipo de statement, MyBatis provee de varios atributos, relacionados con el funcionamiento de la caché,
el tiempo máximo de respuesta, los tipos de los parámetros de entrada y devueltos, etc., con los que configurar
su comportamiento. Los valores por defecto para los atributos de los insert, delete y update statements son
suficientes; mientras que los más habituales en los select statements son los atributos resultType, con el nombre
completamente cualificado (si no se ha especificado un TypeAlias) del tipo devuelto, o resultMap, para resolver
mapeos de mayor complejidad.
Los mapped statements declarados en el proyecto se organizan en base a los siguientes criterios:
• Cada tipo de dato del modelo de información demográfica, sea simple o complejo, tiene su propio
fichero XML mapper con el mismo nombre del tipo que representa.
• Todos los mappers pertenecen al mismo namespace
(pid.pfc.service.mappers.BeanMapper), así los ficheros quedan ubicados en la misma
ruta.
• Cada fichero agrupa las sentencias en varios bloques, no siendo estrictamente necesario que existan en
todos los ficheros:
o SELECT: conjunto de las sentencias para la recuperación de la información a partir de índices
de identificación.
o DELETE: conjunto de las sentencias para el borrado de información a partir de índices de
identificación.
o UPDATE: conjunto de sentencias para la actualización de información a partir índices de
identificación. Empleados especialmente para los tipos simples.
o INSERT: conjunto de sentencias para la inserción de lo valores de los índices de identificación.
Usados como apoyo en determinadas operaciones de actualización.
o IDENTIFYPERSON: conjunto de las sentencias para la recuperación del identificador de
persona a partir de datos demográficos.
o CHECK: conjunto de las sentencias opcionales para la comprobación de la integridad de
algunas tablas relacionales.
o HELP: conjunto de las sentencias opcionales de apoyo a determinadas operaciones o para la
recuperación de información adicional sobre las tablas.
• Cada fichero contiene los statements referentes a los atributos de una persona que se correspondan con
el tipo de datos que representa el fichero.
• La nomenclatura usada para la identificación de cada statement es: get/del/up/in/get_personid[upper
traitname]<atribute trait>
El fichero Auxiliary.xml contiene algunas sentencias no directamente relacionadas con los datos demográficos,
pero necesarias en los métodos de las interfaces del servicio PIDS, como por ejemplo para el cálculo del grado
de coincidencia en la "búsqueda de candidatos". También aloja los fragmentos de SQL que se reutilizan en varias
consultas, definidos en los elementos sql.
Trabajo desarrollado
70
Los tipos complejos de alto nivel, como EntityName o Postaladdress, se componen de propiedades de tipos
simples y otros tipos complejos. A diferencia del proyecto anterior y como se describe más adelante, el actual
servicio PIDS se diseña con el propósito de poder lanzar consultas desde atributos de más bajo nivel, como por
ejemplo actualizar el segundo apellido de una persona o consultar uno de los números de teléfonos de una
determinada finalidad. Estas relaciones entre los atributos complejos y sus elementos relacionados se
implementan mediante Selects anidadas definidas en ResultMaps. Así, por ejemplo, en el ResultMap de un trait
de tipo EntityName, la asociación con su propiedad entityNamePart, otro tipo complejo, se realiza indicando el
identificador de la select statement que se encarga de mapear los objetos EntityNamePart. Estas asociaciones
encadenadas serán recursivas para los tipos complejos, hasta llegar a tipos simples. Si bien este método es
sencillo, deriva en la ocurrencia del problema conocido como "N+1 consultas" que puede generar un impacto
considerable en el rendimiento cuando el volumen de datos es grande.
EntityName.xml
<!-- … --> <resultMap id="get-entityname-result"
type="pid.pfc.service.beans.EntityName"> <association property="entityNamePart" select="getEntityNamePart" column="{person=personid, rol=personrole, primer=priority}"/> <association property="validTime" select="getValidTimeEntityName" column="{person=personid, rol=personrole, primer=priority}"/> </resultMap> <!-- … --> <select id="getEntityName" resultMap="get-entityname-result"
resultOrdered="true"> select * from entityname where personrole=#{rol} and personid=#{person} <if test="primer != null and primer != ''">AND priority=cast(#{primer} as integer) </if> ; </select> <!-- … -->
MyBatis dispone de alternativas que resuelven el problema del N+1, como las asociaciones mediante
ResultMaps anidados en sentencias que hacen JOIN de las tablas relacionadas.
Nota: a modo de ejemplo, en el fichero Telecom.xml del proyecto está preparada una consulta basada en
ResultMaps anidados. Con este método se evita el problema de "N+1 consultas", aunque igualmente se pierde la
característica de reutilizar para las sentencias de atributos complejos aquellas que ya estuvieran diseñadas
para los sub-atributos que los componen.
Tal y como se aprecia en el ejemplo anterior, las capacidades de SQL dinámico de MyBatis se aprovecharán
principalmente para, sobre una misma consulta, recuperar un atributo de la persona o una colección de ellos, en
función de la disponibilidad o no de los índices que se hubieran aportado en la consulta.
4.1.4.2 Diseño del "árbol de traits"
Relacionado con el modelo de datos, se debe construir un documento XML con la estructura jerarquizada de los
atributos demográficos. Para el ámbito del proyecto, este documento XML se denominará "árbol de traits". El
árbol de traits cumple dos finalidades clave: establecer la sintaxis de nombrado de los atributos (la propiedad
71
71
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
traitname para el objeto Trait definido en la especificación PIDS) y almacenar los identificadores de los mapped
statements asociados a cada atributo.
El nombre del trait (traitname) estará formado por subcadenas o tramos, separados por barras inclinadas, que se
corresponden con los nombres de los elementos por los que se pasa a medida que se va recorriendo la jerarquía
del árbol, de manera similar a las rutas de directorios en un sistema de ficheros. Los sub-elementos que se
correspondan con colecciones de traits, tienen un atributo atrib con los nombres separados por coma de los
índices (separados por coma) que se pueden usar para indexar esos traits.
Traits.xml
<?xml version="1.0" encoding="UTF-8"?> <PID> <!-- … --> <addr atrib="primer"> <!-- … --> <postalAddress atrib="segundo, use"> <SELECT>getPostalAddressPart</SELECT> <DELETE>delPostalAddressPart</DELETE> <UPDATE>upPostalAddressPart</UPDATE> <INSERT/> <addressLine> <!-- … --> </addressLine> <!-- … --> </postalAddress> <!-- … --> </PID>
Las reglas sintácticas que se han establecido para los traitnames son:
• El primer tramo nunca lleva índices.
• El primer tramo determina el "rol" de la persona. Los valores posibles son Patient y
HealthCareProfessional.
• Los índices están inmediatamente después del nombre del tramo.
• Los índices tienen la nomenclatura [@<nombre del índice>].
Si un traitname no contiene ningún índice para un trait cuyo elemento en el árbol tiene el atributo atrib, se
interpreta que el valor esperado es la colección completa de traits registrados.
Obviamente el orden de los tramos en el nombre del trait debe coincidir con la estructura jerárquica definida en
el árbol. Del mismo modo, si se especifica un índice en un tramo del traitname que no lo requiere o no coincide
con ninguno de los declarados en el árbol para ese tramo, la evaluación del nombre provocará una excepción en
la aplicación. Esta evaluación del traitname no tiene efecto en la búsqueda de candidatos partiendo de atributos
dados.
Trabajo desarrollado
72
En la siguiente tabla se indican algunos ejemplos que se pueden extraer del fragmento del árbol de traits anterior
para terminar de entender la construcción del traitname:
TraitName Descripción
/Patient/addr Conjunto de todos los
PostalAddress existentes para una
persona
/Patient/addr[@primer=1]/postalAddress[@segundo=3] Tercera parte (p.ej, código postal)
de la dirección preferente registrada
para una persona
/Patient/addr[@primer=1]/postalAddress Todas las partes de la dirección
preferente registrada para una
persona
/Patient/addr/postalAddress[@segundo=3] Terceras partes de todas las
direcciones registradas para una
persona
Tabla 4–2. Ejemplos descriptivos de traitnames
Esta indexación ha permitido diferenciarse del servicio PIDS desarrollado en el proyecto de partida, cuyas
consultas sólo se podían hacer para los elementos de primer nivel. En el proyecto actual se ha ampliado el
espectro consultivo y de tratamiento, especialmente útil en las actualizaciones y borrados de registros muy
concretos de la base de datos. Sin embargo, el árbol de traits constituye el punto de mayor acoplamiento entre
el código y el modelo de datos en el proyecto. Además, la sintaxis que se utiliza en el árbol debe ser conocida
en la parte cliente de la aplicación, de lo contrario no podrá formar correctamente el nombre de los traits.
Conocido el nombre del trait y el tipo de sentencia que se pretende invocar, es relativamente sencillo conseguir
el identificador del mapped statement específico. Cada elemento trait en el árbol XML está formado por
subelementos SELECT, DELETE, UPDATE, INSERT con los identificadores que reciben como parámetro los
métodos de ejecución de sentencias. No necesariamente todos los elementos tendrán identificadores para todos
los tipos de sentencias. Un subelemento adicional, IdentifyPerson, estará únicamente disponible en los elementos
que pueden ser utilizados como atributos para la búsqueda de candidatos.
En cuanto al código de la aplicación, todo lo relativo al tratamiento de los nombres de los traits, obtención de
los índices e identificadores de las sentencias, y validaciones de las expresiones se desarrolla utilizando los
métodos proporcionados en la librería XPath.
4.1.4.3 Clase Index
Los métodos para ejecutar sentencias de la clase SqlSession, en su forma más sobrecargada, sólo admiten un
objeto como parámetro de entrada para el mapeo con las sentencias SQL, además del identificador de la
sentencia. La mayoría de las consultas SELECT se han implementado para que sólo necesiten recibir los valores
de los índices usados en el mapeo de cualquier tipo de atributo. En este sentido, se ha creado la clase Index, de
tipo JavaBean, que consta de tantas propiedades como índices se utilicen en el árbol de traits. Por lo general, la
invocación de métodos de consulta solo recibe como parámetro una instancia de esta clase.
En el caso de las sentencias UPDATE el inconveniente es que, junto con los valores de los índices, el objeto
recibido como parámetro también debe contener las propiedades del atributo a actualizar, independientemente
del tipo que se trate. Se opta por hacer que la clase DV extienda a su vez la clase Index, y así conseguir "propagar"
las propiedades de los índices y sus métodos setter y getter a cada tipo de atributo.
73
73
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
4.1.5 Estructura de paquetes, clases y métodos
La parte funcional del servicio PIDS se ha desarrollado con la estructura de paquetes Java que se describe en
este apartado.
• pid.pfc.service.common: contiene las clases para el manejo de los ficheros de propiedades que
almacenan pares clave-valor correspondientes a las rutas de ficheros de configuración u otras variables
relevantes para el servicio , y los carga en tiempo de ejecución para su disponibilidad a lo largo de todo
el ciclo de vida de la aplicación.
• pid.pfc.service.handler: incluye las clases encargadas del tratamiento de los traitnames, y
la construcción y evaluación de las expresiones XPath para recuperar del árbol de traits el identificador
de la sentencia relativo al trait que se hubiera indicado.
• pid.pfc.service.handler.exceptions: incluye excepciones relacionadas principalmente
con los incumplimientos de las reglas sintácticas establecidas para los traitnames.
• pid.pfc.service.loader: contiene la clase para la instanciación del SqlSessionFactory único
durante toda la ejecución de la aplicación.
Trabajo desarrollado
74
• pid.pfc.service.query: comprende la clase con los métodos para ejecutar las sentencias contra
la base de datos usando la librería MyBatis.
• pid.pfc.service.impl: se corresponden con las clases del SIB del servicio Web, que
implementan los métodos definidos en el SEI para el servicio Web generado a partir del documento
WSDL (más detalles en la próxima sección sobre la transformación del PIDS en servicio Web). En ellos
es donde se desarrolla toda la capa funcional del servicio. Estas clases e interfaces se describieron en el
apartado 3.4.1.
• pid.pfc.service.maps: ruta en la que se encuentran los fichero XML de mapeo con los mapped
statements, organizados por los tipos simples y GPICS vinculados a una persona.
75
75
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
• pid.pfc.service.tree: ruta al árbol al fichero XML que representa al árbol de traits.
4.2 Fase 2: PIDS como servicio Web
No cabe duda que la innovación en el campo de la e-sanidad está ligada a la comunicación e interacción entre
los sistemas sanitarios, de diferentes instituciones, diferentes tecnologías y dispersos geográficamente, que
deben intercambiar información de forma estandarizada para proveer servicios integrados.
En el contexto de este proyecto se convierte en un requisito la implementación del PIDS como servicio Web
para dotarle de las capacidades de interoperatividad reclamadas y distribuir el servicio a través de Internet. Se
va a construir como un servicio Web basado en SOAP, usando JAX-WS como API de programación para leer
o generar los mensajes SOAP implicados en la invocación de los métodos remotos.
A pesar de contar con una implementación avanzada del PIDS, se empleará un enfoque pseudo-descendente
(contract-first) para la programación del servicio Web; es decir, el punto de partida será la definición del fichero
WSDL que, junto con los esquemas XML de los tipos de datos usados en el servicio, describe las interfaces de
operación del servicio. La complejidad de los datos intercambiados en las operaciones incentiva la necesidad de
tener un mayor control sobre las estructuras de datos más adecuadas para la invocación del servicio. Sin
embargo, se han realizado algunas personalizaciones para evitar tener que modificar el código disponible. Por
ejemplo, se han impuesto restricciones directas sobre el esquema XML para preservar la compatibilidad del tipo
timestamp con el formato ya usado en la base datos ("dd-MM-yyyy HH:mm:ss"). Este tipo de dato no está
soportado de forma predeterminada por JAXB (apartado 3.4), aunque tampoco era deseable recurrir a la
definición de una clase personalizada XMLAdapter con el que controlar el formato del dato en los procesos de
marshalling y unmarshalling. Estas condiciones en el esquema XML, derivadas de un formato existente en la
implementación del servicio, son por las que se habla de un diseño pseudo-descendente.
4.2.1 Enfoque descendente / WSDL
En este apartado se explicará en detalle el contenido del fichero WSDL definido para el servicio PIDS. En
términos generales, en el "contrato" WSDL se especifica el formato de los datos, define los mensajes
intercambiados en las operaciones del servicio y establece los requerimientos de transporte a través de la red. Lo
detalles de la estructura del documento WSDL se desarrolló en el apartado 2.2.2.4.
El elemento raíz de un documento WSDL contiene varios atributos xmlns para declarar los elementos de
namespaces, tanto los de estándares como los definidos para los tipos de datos, y asignarles un prefijo
identificativo. Con el atributo targetNamespace se establece el namespace de todos los elementos que se definan
en el documento. Sabiendo que la estructura de carpetas se genera tomando como referencia el
targetNamespace, se asigna el valor http://pfc.pid/wsdl para mantener la nomenclatura de los
paquetes de la implementación del PIDS.
Dentro del elemento <types> se añaden dos esquemas XML:
• targetNamespace http://pfc.pid/service/beans: contiene la definición en XML de los
tipos de datos usados en el modelo de información demográfica, incluyendo los tipos básicos, CAG y
GPICS.
Todos los tipos son declarados con la etiqueta <complexType> con el mismo nombre del tipo de datos
al que representan. Mantienen la jerarquía y estructura de herencia comentada en apartados anteriores:
los tipos complejos son contenedores de elementos de tipos simples u otros tipos complejos. Algunas
consideraciones para la interpretación del esquema son:
o La definición de un elemento element debe contener una propiedad name, que representa su
nombre, y una propiedad type para indicar el tipo de elemento. Podemos utilizar alguno de los
tipos predefinidos (built-in types), por ejemplo xs:string, xs:integer, o bien podemos definir
Trabajo desarrollado
76
nuevos tipos utilizando etiquetas simpleType o complexType.
o Los elementos que componen un complexType deben contener un atributo name y type. En
función de la propiedad del tipo al que representen, el atributo type puede ser alguno de los tipo
predefinidos en XML schema, por ejemplo xsd:string, o bien otro de los nuevos tipos definidos
en el propio esquema XML.
o Los componentes de un tipo que no tengan el atributo minOccurs a "0" serán interpretados
como elementos obligatorios (las propiedades de la clase Java que JAXB genere a partir del
esquema estarán anotadas como @XmlElement(required = true)).
o El atributo maxOccurs se utiliza en aquellas propiedades que, según el estándar del tipo de
datos, se correspondan con una colección de elementos de un tipo determinado.
PID.wsdl -- Types: beans
<!-- … -->
<xsd:complexType name="ST"> <xsd:complexContent> <xsd:extension base="bn:DV"> <xsd:sequence> <xsd:element name="characterString" type="xsd:string"/> <xsd:element name="charset" type="bn:CS"/> <xsd:element name="language" type="bn:CS"/> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType>
<xsd:complexType name="EntityName"> <xsd:sequence> <xsd:element maxOccurs="unbounded" minOccurs="0" name="entityNamePart"
type="bn:EntityNamePart"/> <xsd:element name="validTime" type="bn:IVLTS"/> </xsd:sequence> </xsd:complexType>
<xsd:complexType name="EntityNamePart"> <xsd:sequence> <xsd:element name="entityNamePart" type="bn:ST"/> <xsd:element name="namePartType" type="bn:CS"/> <xsd:element name="namePartQualifier" type="bn:CS"/> </xsd:sequence> </xsd:complexType>
<!-- … -->
• targetNamespace http://pfc.pid/xsd: se corresponde con el modelo de información
demográfica de la especificación PIDS. En este esquema XML también se definen los elementos que
"envuelven" los parámetros de entrada, excepciones y valores de retorno utilizados por las operaciones
del servicio Web.
A diferencia del esquema XML de los beans, se han añadido anotaciones en línea para personalizar la
forma en la que JAXB debe representar las colecciones de elementos en los procesos de serialización y
deserialización [29].
77
77
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
PID.wsdl -- Types: xsd
<!-- … --> <xsd:complexType name="Profile"> <xsd:sequence> <xsd:element maxOccurs="unbounded" minOccurs="0" name="seq"
type="axis2:Trait"> <xsd:annotation> <xsd:appinfo> <jxb:property collectionType="indexed"/> </xsd:appinfo> </xsd:annotation> </xsd:element> </xsd:sequence> </xsd:complexType>
<!-- … -->
<!-- Tipos "wrappers" de los parámetros y valores devueltos de la operación
GetProfile -->
<xsd:element name="getProfileReq"> <xsd:complexType> <xsd:sequence> <xsd:element name="PersonId" type="xsd:string"/> <xsd:element name="traitsRequested" type="axis2:SpecifiedTraits"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="getProfileRes"> <xsd:complexType> <xsd:sequence> <xsd:element name="profile" type="axis2:Profile"/> </xsd:sequence> </xsd:complexType> </xsd:element> <!-- … --> <!-- Ejemplo de elemento Fault --> <xsd:element name="duplicateTraitsFault"> <xsd:complexType> <xsd:sequence> <xsd:element name="duplicate" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> <!-- … -->
La etiqueta <annotation> especifica que esa parte del esquema está destinada al software de
procesamiento de esquemas. La etiqueta <appinfo> introduce las instrucciones para la herramienta de
generación de código xjc de JAXB. En este caso, simplemente se indica que el valor resultante no deber
ser una clase java.util.List sino una propiedad array (Trait []).
Aunque esta personalización resta flexibilidad al servicio Web, ya que lo liga en cierto modo a la
tecnología que la desarrolla, se realiza con fines expositivos de las posibilidades de personalización de
JAXB. Por otro lado, aclarar que hubiese sido posible usar un fichero externo con las personalizaciones
declaradas.
Trabajo desarrollado
78
Nota: cada uno de los esquemas XML se podrían haber definido en un documento externo para luego importarlo
al WSDL indicando simplemente la localización del fichero como atributo para el elemento <schema>.
Igualmente, las personalizaciones en las vinculaciones de JAXB podrían haberse declarado en un fichero
externo, sin necesidad de modificar el esquema original, aunque se han mantenido sobre el esquema para
visualizarlas más fácilmente en el contexto del esquema al que se aplican.
Basados en los tipos de datos definidos, ahora se pueden crear la estructura de los mensajes (<message>) que
se intercambiarán en las operaciones, relativas a solicitudes, respuestas o notificación de excepciones. Cada
mensaje se compone de una o más partes, conceptualmente equivalentes a los parámetros de entrada o valores
devueltos en la invocación de una función. El atributo element de cada parte del mensaje especifica el tipo de
dato de dichos parámetros.
PID.wsdl -- Messages
<!-- … -->
<wsdl:message name="getProfileRequest"> <wsdl:part name="param" element="ns:getProfileReq"/> </wsdl:message> <wsdl:message name="getProfileResponse"> <wsdl:part name="result" element="ns:getProfileRes"/> </wsdl:message> <wsdl:message name="InvalidIdFaultMessage"> <wsdl:part name="part1" element="ns:invalidIdFault"/> </wsdl:message>
<!-- … -->
El siguiente paso es describir las interfaces del servicio Web mediante las operaciones contenidas en los
elementos <portType>. Se declaran dos elementos portType, IdentifyPersonPortType y
ProfileAccessPortType, uno por cada interfaz del servicio PIDS dentro del alcance del proyecto, en los que
se incluyen tantas operaciones como métodos se vayan a implementar de la interfaz. Los elementos
<operation> se identifican con el mismo nombre del método que representan, y según el tipo de operación,
tendrá elementos <input> y/o <output> con los mensajes de entrada y salida que se intercambiarán. En el
proyecto todas las operaciones son del tipo request-response. Los elementos <fault> se corresponden con
los mensajes para la comunicación de situaciones excepcionales.
PID.wsdl -- PortType: IdentifyPerson
<!-- … --> <wsdl:portType name="IdentifyPersonPortType"> <wsdl:operation name="findCandidates"> <wsdl:input message="tns:findCandidatesRequest"/> <wsdl:output message="tns:findCandidatesResponse"/> <wsdl:fault name="duplicateTraitsException"
message="tns:DuplicateTraitsFaultMessage"/> <wsdl:fault name="unknownTraitsException"
message="tns:UnknownTraitsFaultMessage"/> <wsdl:fault name="invalidWeightException"
message="tns:InvalidWeightFaultMessage"/> </wsdl:operation> </wsdl:portType> <!-- … -->
79
79
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Finalizada la parte abstracta del documento WSDL, comienza la parte concreta con los elementos <binding>,
que describen cómo deben invocarse las operaciones han sido definidas en los elementos <portType>. El
atributo type del binding hace referencia al portType. El elemento <soap:binding> indica que se usará un
binding de SOAP 1.1, la URI asignada al atributo transport identifica a HTTP como el protocolo de transporte
(SOAP sobre HTTP) a usar, y el atributo style establece document como el estilo para los mensajes SOAP. Por
cada operación se especifica el tipo de codificación (use) que se usará en el cuerpo de los mensajes de entrada,
salida y fault. La operación será para todos los casos Document/literal. No se ha especificado ninguna URI para
el atributo soapAction, que representa la cabecera SOAPAction HTTP enviada con cada petición para poder
filtrar los mensajes SOAP.
PID.wsdl -- Binding: IdentifyPerson
<!-- … --> <wsdl:binding name="IdentifyPersonBinding"
type="tns:IdentifyPersonPortType"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/> <wsdl:operation name="findCandidates"> <soap:operation style="document" soapAction=""/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> <wsdl:fault name="duplicateTraitsException"> <soap:fault name="duplicateTraitsException" use="literal"/> </wsdl:fault> <wsdl:fault name="unknownTraitsException"> <soap:fault name="unknownTraitsException" use="literal"/> </wsdl:fault> <wsdl:fault name="invalidWeightException"> <soap:fault name="invalidWeightException" use="literal"/> </wsdl:fault> </wsdl:operation> </wsdl:binding> <!-- … -->
Por último, los elementos <service> indican dónde se pueden invocar las operaciones del servicio Web.
Contienen elementos <port> que asocian el servicio (endpoint) a una dirección física particular. Dado que se
trata de un servicio Web SOAP, el elemento <port> alberga un elemento <soap:address> cuyo atributo
location especifica la dirección URL del servicio.
PID.wsdl -- Service: IdentifyPerson
<!-- … --> <wsdl:service name="IdentifyPersonService"> <wsdl:port name="IdentifyPersonPort" binding="tns:IdentifyPersonBinding"> <soap:address
location="http://localhost:8080/pid/services/identifyperson"/> </wsdl:port> </wsdl:service> <!-- … -->
Trabajo desarrollado
80
4.2.2 Implementación del servicio Web
La API de JAX-WS abstrae al desarrollador del procesamiento de los mensajes SOAP, la invocación de los
métodos que implementan el servicio, y la construcción del mensaje SOAP de respuesta que se devuelve al
cliente. Para facilitar aún más la tarea, JAX-WS proporciona la herramienta wsimport que, tomando como
entrada el documento WSDL que describe el servicio PIDS, genera las clases JavaBeans que mapean con los
tipos de datos definidos en los esquemas XML, así como la interfaz Java correspondiente al SEI del servicio
Web, entre otros.
En el proyecto la herramienta se utiliza invocando la tarea Ant com.sun.tools.ws.ant.WsImport. Es requisito, por
tanto, tener instalado en el entorno de desarrollo una distribución binaria de Ant. En la lista que sigue se enumeran
otros requisitos y dependencias que se han tenido en cuenta para la definición de la tarea Ant adecuada:
• La versión JDK usada no integra la clase de la tarea Ant, únicamente la herramienta de línea de
comandos. Es necesario descargar las librerías de Metro (apartado 3.4) e incluirlas en el classpath.
Concretamente la tarea Ant está disponible en el paquete webservices-tools.jar.
• MyBatis se apoya en los métodos getters y setters de las propiedades de los JavaBeans que se pasan en
los métodos de las sentencias para mapearlos con los parámetros de consulta (usando Reflection). Sin
embargo, JAXB genera las clases de acuerdo con la especificación oficial de los JavaBeans que
establece que el método getter para las propiedades boolean sean del tipo boolean
is<PropertyName>(). wsimport usa el compilador xjc para la generación de las clases Java, y una
de las características más importantes de xjc es que se puede extender mediante plugins que modifiquen
o personalicen el código generado. En este sentido, se ha utilizado el plugin booleangetter.jar que
genera el método getter necesario para las propiedades de tipo primitivo boolean.
• Por defecto, xjc no crea métodos toString, útiles especialmente para pruebas con clientes simples, ni
métodos setters para las clases de tipo Collection. La extensión JAXB2 Basics Plugins [30] (jaxb2-
basics-plugins-*.jar) cubren estas características que se echan en falta.
Nota: para usar las extensiones de JAXB hay que incluir las librerías del plugin en el classpath y activarlo
añadiendo los argumentos de xjc correspondientes.
build.xml -- wsimport
<!-- … --> <taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport"> <classpath refid="jaxws.classpath"/> <classpath> <pathelement location="${service.src.dir}/java"/> <pathelement path="${service.src.dir}/webapp/WEB-INF/lib/booleangetter.jar"/> <pathelement path="${service.src.dir}/webapp/WEB-INF/lib/jaxb2bas0.11.0.jar"/>
</classpath> </taskdef> <wsimport sourcedestdir="${target.gen.dir}" wsdl="${wsdl.location}"
xnocompile="true"> <xjcarg value="-Xboolean-getter"/> <xjcarg value="-XtoString"/> <xjcarg value="-Xsetters"/> <xjcarg value="-Xsetters-mode=direct"/> </wsimport> <!-- … -->
Los artefactos generados se organizan en paquetes tomando como referencia los targetNamespace declarados
81
81
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
en el documento WSDL y los esquemas XML. Los paquetes y clases resultantes tras la invocación de wsimport
con el fichero WSDL del servicio PIDS se resumen a continuación:
• pid.pfc.service.beans: contiene los JavaBeans de los datos demográficos de referencia, tanto
tipos básicos como CAG y GPICS, correlacionados desde los tipos de uno de los esquemas XML.
• pid.pfc.xsd: paquete con las clases mapeadas del esquema XML referente a los tipos de datos de
la especificación del PIDS, y las clases correspondientes a los parámetros de entrada, excepciones y
valores devueltos utilizados en los métodos de las interfaces del PIDS.
Los paquetes generados a partir de los esquemas XML incluyen una clase ObjectFactory, con los métodos de
factoría de cada clase o interfaz generada, y otra package-info, que proporciona la declaración del paquete
asociado a partir del targetNamespace del esquema XML.
• pid.pfc.wsdl: junto con las clases para el tratamiento de los mensajes de excepción, incluye las
clases del SEI, IdentifyPersonPortType y ProfileAccessPortType, con la definición de los métodos de
las interfaces del PIDS, y las clases IndentifyPersonService y ProfileAccessService, utilizadas por el
cliente JAX-WS para comunicarse con el servicio Web a través del SEI.
Trabajo desarrollado
82
Centrándose en la interfaz IdentifyPerson, la clase IdentifyPersonPortType representa el SEI, anotada con
@WebService (javax.jws.WebService). La clase también tiene la anotación @SOAPBinding para especificar
el estilo y la codificación de los mensajes SOAP utilizados al invocar el servicio. El atributo parameterStyle con
el valor SOAPBinding.ParameterStyle.BARE determina que los parámetros del método representan
todo el cuerpo del mensaje. En este caso, la interfaz declara únicamente el método findCandidates. La anotación
@WebMethod indica que debe ser publicado como operación del servicio, mientras que las anotaciones
@WebParam y @WebResult asociadas al método determinan los nombres de los parámetros y resultados
del método en el fichero WSDL.
IdentifyPersonPortType.java
// … @WebService(name = "IdentifyPersonPortType",
targetNamespace = "http://pfc.pid/wsdl") @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) public interface IdentifyPersonPortType { @WebMethod @WebResult(name = "findCandidatesRes",
targetNamespace = "http://pfc.pid/xsd",
partName = "result") public FindCandidatesRes findCandidates( @WebParam(name = "findCandidatesReq",
targetNamespace = "http://pfc.pid/xsd",
partName = "parameters") FindCandidatesReq parameters) throws DuplicateTraitsFaultMessage,
InvalidWeightFaultMessage,
UnknownTraitsFaultMessage ; }
La clase IdentifyPersonPortTypeImpl representa el SIB, que proporciona una implementación del SEI.
También debe estar anotada con @WebService añadiendo, como mínimo, el atributo endpointInterface para
vincular el SIB a su SEI correspondiente. Sin embargo, los métodos del SIB no tienen que estar anotados con
@WebMethod; esta anotación sólo se usa en el contexto del SEI.
83
83
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Es una buena práctica activar la validación de los esquemas de los mensajes enviados al servicio Web,
especialmente para detectar cualquier problema en el formato de los mensajes entrantes. Basta con añadir la
anotación @SchemaValidation (com.sun.xml.ws.developer.SchemaValidation) a nivel de servicio Web. No
obstante, hay que tener en consideración que esta validación está deshabilitada por defecto porque tiene un
impacto significativo sobre el rendimiento.
IdentifyPersonPortTypeImpl.java
// … @SchemaValidation() @WebService(serviceName = "IdentifyPersonService", portName = "IdentifyPersonPort", endpointInterface = "pid.pfc.wsdl.IdentifyPersonPortType", targetNamespace = "http://pfc.pid/wsdl")
public class IdentifyPersonPortTypeImpl implements IdentifyPersonPortType {
// … }
4.2.3 Estructura de carpetas para el proyecto y ficheros de despliegue
Para construir el proyecto del servicio Web primero es necesario crear una estructura de carpetas adecuada que
facilite el desarrollo iterativo de versiones y mantenga el código fuente independiente de los ficheros compilados.
Está basado en las convenciones estructurales recomendadas para proyectos Maven [31]. La estructura se diseña
con el objetivo último de implementar el servicio Web utilizando el modelo de programación de servlets y, por
tanto, empaquetar el servicio en un WAR que se desplegará sobre un contenedor de servlets. Concretamente
para el proyecto, el contenedor donde se despliegue será un servidor Tomcat.
Figura 4-4. Estructura del proyecto de servicio Web
La carpeta raíz será el directorio de trabajo desde el que ejecutar los objetivos ("targets") Ant que automatizan las
tareas habituales de generación (wsimport), compilación, construcción y despliegue. El fichero build.xml alojado
Trabajo desarrollado
84
en el directorio es el que describe los objetivos con cada una de las tareas a ejecutar. Bajo la carpeta raíz del
proyecto se crean dos carpetas en los que se guardan por separado todos los recursos y código relacionado con
la parte del cliente y del servidor PIDS. Haciendo foco en la carpeta para el servicio, el contenido y la finalidad
de cada subcarpeta se describe como sigue:
src/main/java Código fuente del servicio
src/main/resources Ficheros XML mappers de MyBatis y árbol de traits, ficheros de propiedades
src/main/webapp Recursos para una aplicación Web. Contiene el directorio WEB-INF
target/classes Clases compiladas
target/generated Artefactos JAX-WS generados con wsimport a partir del WSDL y esquemas
XML
El directorio WEB-INF contiene toda la información de configuración necesaria para un servicio Web
empaquetado en un WAR. De acuerdo a este modelo de despliegue, el fichero WSDL se almacena en el
subdirectorio WEB-INF/wsdl para su publicación durante el despliegue, y la descripción del despliegue en el
servidor Tomcat (contenedor no Java EE) se define en los ficheros web.xml y sun-jaxws.xml.
• En el fichero web.xml se declara el agente de escucha ("listener") WSServletContextListener, que
inicializa y configura el port del WSDL del servicio Web, y el registro del servlet WSServlet de JAX-
WS, que entrega las peticiones al servicio, utilizando la clase que implementa dicho servicio. El fichero
web.xml no es obligatorio a partir de la API Servlet 3.0, y Metro puede crear automáticamente el registro
del servlet JAX-WS predeterminado cuando se utilizan contenedores no-Java EE.
• El fichero sun-jaxws.xml contiene la definición de la implementación de los endpoints del servicio. Cada
endpoint representa un port del WSDL, y contiene toda la información sobre la clase que implementa
el servicio, el patrón URL del servlet (debe encajar con el patrón indicado en el fichero web.xml) para
el acceso HTTP, la ubicación del fichero WSDL, y los nombres del port y service del WSDL.
Por ejemplo, en el siguiente extracto del fichero sun-jaxws.xml se indica que la implementación del
servicio viene dada por la clase pid.pfc.service.impl.IdentifyPersonPortTypeImpl (dicha clase debe
estar anotada con @WebService).
sun-jaxws.xml
<!-- … -->
<endpoint name="Endpoint_for_IdentifyPerson" implementation="pid.pfc.service.impl.IdentifyPersonPortTypeImpl" wsdl="WEB-INF/wsdl/PID.wsdl" service="{http://pfc.pid/wsdl}IdentifyPersonService" port="{http://pfc.pid/wsdl}IdentifyPersonPort" url-pattern="/services/identifyperson"/>
<!-- … -->
Nota: No es necesario especificar en el fichero de sun-jaxws.xml información del binding ni usar la anotación
@BindingType, ya que el binding por defecto es SOAP1.1/HTTP
Las librerías o ficheros JAR de los que dependa el servicio Web se ubican en el directorio WEB-INF/lib y serán
agregados automáticamente al classpath del servicio en tiempo de ejecución. El directorio contendrá, por
ejemplo, las librerías de MyBatis, el controlador JDBC de la base de datos, y los plugins de xjc. Además,
teniendo en cuenta que Tomcat no es un contenedor que cumpla las especificaciones Java EE completamente,
es preciso incluir también las librerías concretas de JAX-WS para que pueda trabajar con servicios Web.
85
85
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
4.2.4 Ant (secuencia de tareas)
Con el código fuente, los ficheros de recursos y configuración, las librerías externas dependientes, y el
documento WSDL en la carpeta adecuada, se puede automatizar el proceso de empaquetamiento y distribución
del proyecto conforme a la invocación secuencial de tareas Ant sencillas contenidas en el fichero build.xml.
La secuencia de despliegue consta de un primer objetivo, generate-jaxws-classes, que procesa el fichero WSDL
para generar los artefactos JAX-WS. Se utiliza la tarea Ant de wsimport descrita anteriormente, a la que se le
especifica la ruta donde guardar el código fuente generado, la ubicación del fichero WSDL, la opción xnocompile
para que no compile el código generado, y las propiedades de personalización para el compilador xjc a partir de
los plugins JAXB. El siguiente objetivo se encarga de compilar todo el código fuente del proyecto, tanto de la
parte cliente como la parte del servidor. Una vez compilado, el objetivo create-jaxws-war empaqueta la
aplicación mediante la tarea Ant predefinida war. A partir de los elementos anidados en la tarea se agregan los
recursos necesarios (clases, ficheros XML, propiedades, librerías) al fichero WAR resultante.
<lib> Todos los ficheros incluidos en la ruta del atributo dir, a excepción de los
explícitamente excluidos, se guardarán en el directorio WEB-INF/lib del WAR
<classes> Todos los ficheros incluidos en la ruta del atributo dir, a excepción de los
explícitamente excluidos, se guardarán en el directorio WEB-INF/clases del
WAR
<webinf> Todos los ficheros incluidos en la ruta del atributo dir, se guardarán en el
directorio WEB-INF del WAR Se utiliza para añadir el fichero sun-jaxws.xml. Nota: el descriptor web.xml a utilizar se indica mediante el atributo obligatorio
webxml de la tarea war.
<zipfileset> Clases compiladas
Por lo general, el WAR del servicio Web contiene el fichero WSDL, la clase SEI, la clase que implementa el
servicio y sus clases dependientes, los artefactos JAX-WS generados, las librerías dependientes del servicio, y
los descriptores de despliegue.
Finalmente, se puede desplegar el WAR en el contenedor Tomcat. El objetivo Ant deploy-war espera que le
ingresen una ruta local válida donde realizar la copia del WAR.
Con el Tomcat iniciado, para verificar que el servicio Web está ejecutándose correctamente simplemente habría
que introducir, por ejemplo, la dirección http://localhost:8080/pid/services/identifyperson en cualquier
navegador Web y se obtiene una página con la descripción de los metadatos de los endpoints junto con la URI
del WSDL (coincide con la URI del servicio Web con el sufijo ?wsdl) y el nombre cualificado de las clases
que implementa los SEI.
Figura 4-5. publicación servicio Web
Trabajo desarrollado
86
Nota: si se accede a cualquiera de los dos endpoints, JAX-WS enumera todos los endpoints definidos en la
aplicación. Igualmente, si se consulta el fichero WSDL de cualquier de los endpoints, se devuelve el mismo
archivo XML (los endpoints se han definido a partir del mismo archivo).
Esta información es la que usa un cliente para acceder y consumir el servicio.
4.2.5 Cliente del servicio Web
La herramienta wsimport, a partir del WSDL del servicio Web, facilita la creación de clientes JAX-WS sencillos
no gestionados (no ejecutados en contenedor Java EE) siguiendo el modelo de programación de cliente proxy
dinámico. Genera la clase que extiende la clase javax.xml.ws.Service cuyos métodos instancian al SEI para
habilitar un proxy de comunicación con el servicio Web. Desde esta forma, los clientes acceden a la
implementación del servicio Web mediante llamadas directas sobre los métodos del SEI, ajenos a si el servicio
está ejecutando en local o remoto, y siendo transparente para ellos todo lo relacionado con el transporte o la
codificación de los mensajes.
Suponiendo el caso práctico de un cliente que quiera ejecutar la operación findCandidates de la interfaz
identifyPerson, primero hay que importar al cliente los artefactos JAX-WS generados por wsimport. Si se desea
aprovechar las características de extensión del compilador de JAXB xjc, la manera más sencilla de invocar
wsimport es ejecutar una tarea Ant com.sun.tools.ws.ant.WsImport, similar a lo visto en un punto anterior de este
capítulo. En este caso, la opción relativa a la localización del WSDL apuntará a la URI de algunos de los
endpoints del servicio Web. Por ejemplo, http://localhost:8080/pid/services/identifyperson?wsdl.
Luego, tanto solo habrá que instanciar la clase identifyPersonService e invocar el método getIdentifyPersonPort
que devuelve una instancia del SEI del servicio Web (identifyPersonPortType). El cliente ejecuta las
operaciones utilizando esta instancia; en este ejemplo, findCandidates.
Principal.java
// … pid.pfc.wsdl.IdentifyPersonService service = new pid.pfc.wsdl.IdentifyPersonService(); pid.pfc.wsdl.IdentifyPersonPortType port = service.getIdentifyPersonPort(); pid.pfc.xsd.FindCandidatesReq parameters = new pid.pfc.xsd.FindCandidatesReq(); // … pid.pfc.xsd.FindCandidatesRes result = port.findCandidates(parameters);
// …
El IDE Netbeans también proporciona un asistente que recrea los pasos realizados para crear un cliente de un
servicio Web de forma gráfica e interactiva. Sobre un proyecto nuevo o existente creamos un nuevo archivo de
tipo Web Service Client.
87
87
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Figura 4-6. Cliente NetBeans (1)
El asistente de creación solicita la localización del fichero WSDL del servicio Web y el nombre del paquete
donde se generaran los artefactos Java para el cliente. Por ejemplo, es posible indicar la URL en la que está
publicado el WSDL. Si no se especifica ningún nombre para el paquete de destino, por defecto la estructura de
paquetes se basará en el namespace del propio WSDL. También da la opción de generar el código de un cliente
Dispatch si se desea trabajar a más bajo nivel en la construcción de los mensajes o cargas de trabajo XML.
Figura 4-7. Cliente NetBeans (2)
Finalizados los pasos de creación, NetBeans invocará a wsimport para generar los artefactos del servicio, que se
podrán listar en la carpeta Generate Sources (jaxws-wsimport) del proyecto. En otra carpeta del proyecto, Web
Service References, se obtiene una referencia a los endpoints y operaciones disponibles en el servicio Web.
Trabajo desarrollado
88
Figura 4-8. Cliente NetBeans (3)
NetBeans también proporciona un atajo para crear el código que invoca al servicio. Basta con arrastrar la
operación deseada desde la carpeta Web Service References a la parte en la que se quiera insertar el código de la
clase que se estuviera editando. Otra opción sería desde el menú de NetBeans Source > Insert Code … > Call
Web Service Operation.
Figura 4-9. Cliente NetBeans (4)
Sólo estaría pendiente la edición el código para especificar los parámetros de entrada necesarios para la
operación.
Este mismo proceso se aplica para insertar el código de invocación en los métodos de acción de la interfaz
gráfica del cliente diseñado con la librería Swing.
Figura 4-10. Cliente GUI
89
89
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
4.3 Fase 3: Despliegue en contenedores
En el arranque de esta fase del proyecto, el servidor PIDS parte de una arquitectura en la que todos sus
componentes técnicos están ejecutándose localmente sobre la misma máquina, sin posibilidad de proporcionar
una plataforma escalable y que garantice la alta disponibilidad del servicio. De cara a facilitar la gestión del ciclo
de vida de la aplicación hasta su puesta en producción, es necesario adecuar un entorno de desarrollo que permita
agilizar la puesta en marcha de diferentes escenarios para la realización de pruebas, que reduzca la dependencia
con la configuración del propio entorno, y que posibilite mantener un control de versiones de cada componente
para su posible restauración.
Se ha visto que los contenedores están cosechando una rápida aceptación como mecanismo de entrega para
aplicaciones, en gran parte debido a que permite la provisión de entornos uniformes para implementar
aplicaciones, con independencia del sistema operativo, la distribución o infraestructura en la que se ejecute. Así
se resuelven muchos de los problemas habituales en la puesta en producción relacionados con dependencias o
inconsistencias entre los entornos de desarrollo y producción.
A lo largo del apartado se va a describir el proceso de despliegue del servicio PIDS en un entorno basado en
contenedores Docker. Al tratarse de una aplicación sin estado ("stateless"), al menos a nivel de servicio Web,
encaja muy bien con la filosofía de contenedores. Además, gracias a la versatilidad que proporciona el
ecosistema de herramientas de Docker, es posible optar por diferentes modalidades de despliegue para simular,
por ejemplo, diferentes contextos de demanda del servicio. Se plantearán dos escenarios, uno de baja demanda
o con fines de desarrollo en el que los contendores que dan soporte al servicio se despliegan sobre una única
máquina, y otro de alta demanda que exige, entre otras características, dotar al servicio de prestaciones
productivas relacionadas con la tolerancia a fallos o la capacidad de escalado "al vuelo". En este segundo caso
el servicio PIDS estará apoyado en una arquitectura de clúster multinodo, implementada sobre un entorno de
instancias Cloud.
Para todos los escenarios establecidos en el proyecto, cada uno de los servicios técnicos que componen
el stack tecnológico del PIDS se empaquetará en contenedores de servicios específicos y autónomos que
interactuarán entre sí. Rápidamente se puede diferenciar una capa de servicio Web y otra de base de datos para
dar soporte funcional al PIDS, que se pueden separar en contenedores para gestionarlos (desplegarlos,
actualizarlos, escalarlos, etc.) individualmente. Así, la arquitectura de base estará formada por contenedores que
ejecutan, por una parte, un servidor Tomcat donde alojar el WAR con el servicio Web del PIDS, y, por otro lado,
una base de datos PostgreSQL inicializada mediante un fichero .sql que contiene las sentencias de creación de
la estructura del PIDS y valores iniciales de cada tupla.
El apartado se va a organizar empezando con una breve descripción de las imágenes Docker que se usarán en la
ejecución de los contenedores necesarios, y continuar con la exposición detallada del proceso de despliegue de
los contenedores en los escenarios antes comentados. Dado que con Docker prácticamente todo puede ser
definido en ficheros de configuración, el objetivo último será la conseguir el máximo nivel de automatización
del despliegue y administración de los contenedores de servicios.
Es un prerrequisito para el seguimiento del apartado tener instalado Docker, Docker Compose y Docker
Machine.
4.3.1 Descripción de las imágenes Docker
Con carácter práctico, la imagen de un contenedor se trata fundamentalmente del sistema de archivos preparado
que usará el proceso que se ejecuta en el contenedor, tal y como se explicó en el apartado 3.6.2.1. En Docker
Hub (apartado 3.6.2.2) se pueden encontrar las imágenes oficiales de los dos componentes requeridos: Tomcat
y PostgreSQL. No obstante, se crearán ficheros Dockerfile por cada componente para extender mínimamente
las imágenes oficiales y adaptarlas a las necesidades particulares del proyecto. Se recuerda que un fichero
Dockerfile es una especie de manifiesto donde especificar las características de la imagen, el software adicional
que debe tener instalado, librerías, servicios y configuraciones.
Para tener un mejor control de versiones, se crea una estructura de carpetas en la que mantener los ficheros de
Dockerfile de cada componente:
Trabajo desarrollado
90
Figura 4-11. Estructura proyecto Docker
Dentro la carpeta data de cada componente se añaden todos los recursos y ficheros que serán necesarios adjuntar
a la imagen para la inicialización de los contenedores correspondientes.
4.3.1.1 Imagen db
Se corresponde con la base de datos del proyecto. En su carpeta data se guarda un fichero .sql equivalente a un
dump de la base de datos que incluye las sentencias SQL de creación de las tablas y la inserción de datos iniciales
de prueba, y un script de inicialización para la creación la base de datos del proyecto y un nuevo rol. El script
también se encarga de ejecutar el dump anterior para crear la estructura de la base de datos y cargar los datos de
prueba.
El contenido del Dockerfile es el siguiente:
Dockerfile -- db
FROM postgres:9.5 COPY ./data/init.sh /docker-entrypoint-initdb.d/1-init-dbpid.sh COPY ./data/backup-PID.sql /sql/init.sql
La nueva imagen toma como base la imagen estándar de PostgreSQL [32]. La etiqueta ("tag") 9.5 especifica la
versión del software que se va a usar.
Según la documentación de la imagen de PostgreSQL, en caso de tener que realizar tareas de inicialización
adicionales, la imagen está preparada para ejecutar cualquier fichero .sql o script .sh que se localice en el
directorio /docker-entrypoint-initdb.d del contenedor en el momento de su creación. La primera instrucción
COPY hace la copia del del script de inicialización en el directorio indicado del contenedor. El propio script va
a ejecutar el fichero .sql, por lo que no hay necesidad expresa de añadirlo en el mismo directorio. Si hubiera
varios ficheros de inicialización, se ejecutarían en orden alfabético, por eso es una buena práctica prefijarlos con
una numeración que establezca el orden en que se deben ejecutar. De forma predeterminada, la imagen de
PostgreSQL tiene configurada autenticación trust desde el acceso local, lo que significa que no será necesario
proporcionar una contraseña en estos scripts que se ejecutan desde el propio contenedor.
Hay varias variables de entorno opcionales definidas en la imagen que pueden ayudar en la inicialización del
contenedor. Algunas de las variables que se usarán habitualmente son:
• POSTGRES_PASSWORD: establece la contraseña del superusuario de PostgreSQL.
• POSTGRES_USER: creará el rol especificado con permisos de superusuario y una base de datos con el
mismo nombre. Si no se especifica, se usará el usuario predeterminado postgres.
• POSTGRES_DB: define un nombre diferente para la base de datos predeterminada creada al iniciar por
primera vez la imagen. Si no se especifica, se usará el valor de la variable POSTGRES_USER.
Adicionalmente, el script de inicialización se ha desarrollado para utilizar otras variables de entorno que deben
proporcionarse en el despliegue del contenedor:
• PID_PASS: variable requerida. Establece la contraseña para el rol PID_USER.
91
91
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
• PID_DB: nombre de la base de datos usada para el proyecto. Si no se especifica, se le asigna el valor
"PID" por defecto.
• PID_USER: nombre del rol propietario de la base de datos PID_DB. Si no se especifica, el valor por
defecto es "ibatis".
Además, los ficheros .sql que se encuentren en el directorio /docker-entrypoint-initdb.d se ejecutarán con el
usuario especificado en la variable POSTGRES_USER.
Situándose en la carpeta del proyecto (docker_project), para construir la imagen se usa el comando docker build
de la siguiente forma:
$ docker build -t dockg6/pid:db.10 db/.
Sending build context to Docker daemon 98.3kB
Step 1/3 : FROM postgres:9.5
---> 988f714a7307
Step 2/3 : COPY ./data/init.sh /docker-entrypoint-initdb.d/1-init-dbpid.sh
---> b56aba186fa5
Step 3/3 : COPY ./data/backup-PID.sql /sql/init.sql
---> 083b131f471e
Successfully built 083b131f471e
Successfully tagged dockg6/pid:db.10
Nota: ya estaba descargada la imagen de postgres:9.5. Por eso en la salida del comando anterior no aparece
la descarga de las 12 capas que forman la imagen.
La opción -t permite nombrar y etiquetar la imagen con el formato "nombre:etiqueta". El comando
buscará un fichero de nombre Dockerfile en la ruta especificada.
4.3.1.2 Imagen webApp
Para desplegar el WAR del servicio Web generado en la fase anterior del proyecto es necesario disponer de un
contenedor que ejecute el servidor Apache Tomcat. Su carpeta data debe coincidir con la ruta especificada en el
objetivo Ant deploy-war del proceso de distribución del servicio Web.
El contenido del Dockerfile es el siguiente:
Dockerfile -- webApp
FROM tomcat RUN rm -rf /usr/local/tomcat/webapps/* COPY ./data/pid.war /usr/local/tomcat/webapps/pid.war
La nueva imagen toma como base la imagen estándar de Tomcat [33]. Al no haber especificado tag de la imagen,
automáticamente usará la versión latest si existiese.
Nota: no se recomienda usar las versiones latest para el despliegue de contenedores en entornos productivos
ya que podría estar aplicando modificaciones en la arquitectura que afecten a la estabilidad del servicio sin
haber hecho pruebas previamente. Por ejemplo, si se volviera a construir la imagen pasado un tiempo, la capa
principal podría haberse actualizado y ser incompatible con versiones anteriores.
Trabajo desarrollado
92
A continuación, se ejecuta el comando rm para borrar las aplicaciones Web que vienen por defecto con la imagen
oficial, y finalmente copia el fichero WAR del servicio Web en el directorio webapps, desde donde Tomcat se
encargará de desplegarlo automáticamente en el inicio del contenedor.
Ya solo falta construir la imagen con el comando docker build:
$ docker build -t dockg6/pid:webApp.10 webApp/.
Sending build context to Docker daemon 18.64MB
Step 1/3 : FROM tomcat
---> 11df4b40749f
Step 2/3 : RUN rm -rf /usr/local/tomcat/webapps/*
---> Running in ebd088c59e71
---> 2bda9ecd0c6d
Removing intermediate container ebd088c59e71
Step 3/3 : COPY ./data/pid.war /usr/local/tomcat/webapps/pid.war
---> 36ebb39a905f
Successfully built 36ebb39a905f
Successfully tagged dockg6/pid:webApp.10
4.3.2 Despliegue local
Una vez construidas las imágenes para el proyecto, ya se podrían iniciar contenedores relativos a las imágenes
con el comando docker run. Sin embargo, el despliegue standalone, por separado, de contenedores que
pertenecen a una misma aplicación dificulta la administración y orquestación de los contenedores para iniciarlos,
detenerlos e interconectarlos de forma ordenada. Siempre existe la posibilidad de crear una capa de networking
entre ellos, pero de forma descentralizada. Dentro del ecosistema de Docker hay herramientas disponibles para
facilitar la administración de contenedores que están relacionados entre sí. En este apartado se utilizará Docker
Compose (apartado 3.6.3.1), una herramienta para orquestar contenedores Docker iniciados sobre una misma
máquina, cuyo archivo YAML, donde se describe la configuración del stack de la aplicación multi-contenedor,
se utilizará como base para el despliegue de los contenedores en un clúster de nodos distribuidos.
Hay que añadir el fichero .yml descriptor de los contenedores en la raíz de la estructura de carpetas del proyecto
para usar Docker Compose.
Figura 4-12. Estructura proyecto Docker - Compose
El fichero pid-compose.yml se puede descomponer en tres bloques: services, donde se declaran los dos
contenedores, db y webApp, que componen la aplicación y cual va a ser la configuración de los contenedores,
networks, con la configuración de las redes que se van a usar, y volumes, en la que se especifican los volúmenes
de datos persistentes provistos para la aplicación.
93
93
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
pid-compose.yml
version: '3.4' services: db: container_name: pid-db build: ./db image: dockg6/pid:db.10 ports: - "5432:5432" volumes: - dbdata:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=changeit - PID_DB=PID - PID_USER=ibatis - PID_PASS=changeitPID networks: - pid-network webApp: container_name: pid-webApp build: ./webApp image: dockg6/pid:webapp.10 depends_on: - db ports: - "8080:8080" networks: - pid-network networks: pid-network: volumes: dbdata:
Al generar los contenedores, este fichero le especifica a Docker que haga lo siguiente:
• Crea la red llamada <project-name>_pid-network con los parámetros de red por defecto. Al no haber
especificado ningún driver concreto, será de tipo bridge. Si tampoco se hubiera especificado ninguna
red, por defecto se crea una a partir del nombre del proyecto; en este caso, docker_project.
• Crea el volumen de datos local, en el sistema de ficheros del anfitrión, nombrado <project-
name>_dbdata.
• Los contenedores que se generen deben tener los nombres indicados en la propiedad container_name.
• Construye las imágenes correspondientes a cada contenedor en base a la ubicación de los ficheros
Dockerfile indicada con la opción build. Al haber especificado también la instrucción image, se
nombrarán de acuerdo al nombre y etiqueta indicada.
• Mapea mediante ports el puerto 8080 del contenedor Tomcat al puerto 8080 del sistema anfitrión, y
aunque no es estrictamente necesario para el funcionamiento de la aplicación, también se publica el
puerto 5432 de PostgreSQL por comodidad, para poder desempeñar tareas de administración sobre la
base de datos desde la máquina anfitriona, en lugar de tener que realizarlas desde otros contenedores
temporales conectados a la misma red.
Trabajo desarrollado
94
• Vincula el volumen anterior a la ruta donde PostgreSQL almacena toda configuración y datos de la base
de datos para ganar persistencia más allá del ciclo de vida del contenedor db.
• Proporciona los valores de las variables de entorno declaradas en la opción environment, que se utilizan
para inicializar el contenedor de base de datos.
• Establece una dependencia entre los contenedores. Con la opción depends_on el contenedor db se inicia
siempre antes del webApp, y la ejecución del contenedor webApp conlleva implícitamente el inicio del
contenedor db.
• Agrega ambos contenedores a la red pid-network para que puedan intercomunicarse.
La primera línea indica la versión del formato del fichero compose que se está usando e indica las opciones
soportadas en el fichero. La sintaxis de los ficheros compose se ha ido modificando para mantener la
compatibilidad con nuevas funcionalidades introducidas en las revisiones de Docker (enlace). La versión más
reciente y recomendada en la 3.X, diseñada especialmente para asegurar la compatibilidad cruzada entre Docker
Compose y Docker en modo Swarm.
Una característica interesante, de entre otras opciones que no se han configurado, es la posibilidad de establecer
restricciones para controlar la asignación de recursos, en términos de CPU y memoria, que pueden consumir
cada contenedor. De esta forma, se evita que un contenedor pueda agotar todos los recursos, limitando el impacto
sobre otros contenedores e incluso sobre el propio sistema host. Del mismo modo, se podrían reservar unos
recursos mínimos para los contenedores.
Para ejecutar la aplicación se utiliza el siguiente comando desde el directorio raíz del proyecto, en que también
está el fichero compose.
$ docker-compose -f pid-compose.yml -p pid up -d/.
Creating network "pid_pid-network" with the default driver
Creating volume "pid_dbdata" with default driver
Building db
Step 1/3 : FROM postgres:9.5
---> 988f714a7307
Step 2/3 : COPY ./data/init.sh /docker-entrypoint-initdb.d/1-init-dbpid.sh
---> 9dd1a62bc8ea
Step 3/3 : COPY ./data/backup-PID.sql /sql/init.sql
---> 8f52b031aeb8
Successfully built 8f52b031aeb8
Successfully tagged dockg6/pid:db.10
WARNING: Image for service db was built because it did not already exist. To
rebuild this image you must use
`docker-compose build` or `docker-compose up --build`.
Building webApp
Step 1/3 : FROM tomcat
---> 11df4b40749f
Step 2/3 : RUN rm -rf /usr/local/tomcat/webapps/*
---> Running in d44f9c64c37b
---> c2fd45a27680
Removing intermediate container d44f9c64c37b
Step 3/3 : COPY ./data/pid.war /usr/local/tomcat/webapps/pid.war
---> b9e55db67ca0
Successfully built b9e55db67ca0
Successfully tagged dockg6/pid:webapp.10
WARNING: Image for service webApp was built because it did not already exist. To
rebuild this image you must use
`docker-compose build` or `docker-compose up --build`.
Creating pid-db ...
Creating pid-db ... done
Creating pid-webApp ...
Creating pid-webApp ... done
95
95
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Al no haber usado el nombre por defecto para el fichero compose (docker-compose.yml), con la opción -f se
especifica el nombre del fichero alternativo. La opción -d configura la ejecución de los contenedores en segundo
plano.
Nota: es importante haber cambiado la ruta de conexión a la base de datos configurada para MyBatis para que
contenga el nombre del contendor de PostgreSQL. En este caso la ruta sería jdbc:postgresql://pid-db:5432/PID
Figura 4-13. Esquema de contenedores en despliegue local
Parar y eliminar los contenedores, redes y volúmenes que componen la aplicación se hace mediante el siguiente
comando:
$ docker-compose -f pid-compose.yml -p pid down -v
Stopping pid-webApp ... done
Stopping pid-db ... done
Removing pid-webApp ... done
Removing pid-db ... done
Removing network pid_pid-network
Removing volume pid_dbdata
Volviendo al fichero compose, ha sido necesario especificar "en claro" las contraseñas de los usuarios de
PostgreSQL, no siendo esta circunstancia deseable a efectos de seguridad. Además, estos datos se proporcionan
mediante variables de entorno, por lo que al hacer un listar los procesos junto con sus parámetros de invocación
quedarán expuestos. Docker Compose también soporta la declaración de múltiples variables de entorno en un
fichero .env externo sobre el que se podría establecer un control de acceso más estricto.
pid-compose.yml -- environment file
… db: … env-file: - ./db/data/postgres-var.env … …
Trabajo desarrollado
96
postgres-var.env
POSTGRES_PASSWORD=changeit PID_DB=PID PID_USER=ibatis PID_PASS=changeitPID
No obstante, los datos sensibles seguirán expuestos a cualquiera mal uso de las variables de entorno.
$ docker inspect -f "{{json .Config.Env}}" pid-db | \
sed 's/"//g;s/\[//g;s/\]//g' | \
tr ',' '\n'
POSTGRES_PASSWORD=changeit
PID_USER=ibatis
PID_DB=PID
PID_PASS=changeitPID
LANG=en_US.utf8
PG_MAJOR=9.5
PG_VERSION=9.5.10-1.pgdg80+1
PGDATA=/var/lib/postgresql/data
…
La mejor forma de tratar estos datos sensibles es mediante la administración centralizada de secretos que
implementa Docker (apartado 3.6.4.4). Se recuerda que, utilizando una conexión TLS, un secreto se transmite
de manera segura sobre la red, y cuando se vincula a un servicio, sólo los contenedores de ese servicio tienen
acceso a él. Además, la información sensible nunca se escribe en disco, sino que el secreto es almacenado en un
volumen temporal (tmpfs) que se monta en memoria bajo la ruta /run/secrets/<nombre_secreto>.
Sin embargo, Docker Secrets es una función nativa de Docker en modo Swarm (apartado 3.6.4), por lo que
habría que adaptar los contenedores actuales para que se ejecuten como "servicios". Los nodos managers del
swarm se encargan de coordinar la administración de secretos, cifrados en el log Raft que comparten.
A fin de mantener el mismo esquema que se venía describiendo con Docker Compose y utilizando la
terminología de Docker en modo Swarm, el stack de servicios se ejecutará en un único nodo, a escala de una
tarea por servicio. Aunque se puede tomar como referencia el mismo fichero compose que se estaba utilizando,
hay que aplicar algunos cambios menores sobre el fichero para describir el stack y la forma de desplegarlo, así
como incluir la definición de los secretos.
pid-compose_secrets.yml
version: '3.4' services: db: image: dockg6/pid:db.20 volumes: - dbdata:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD_FILE=/run/secrets/postgres-pass - PID_DB=PID - PID_USER=ibatis - PID_PASS_FILE=/run/secrets/pid-pass networks:
97
97
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
- pid-network secrets: - pid-pass - postgres-pass webApp: image: dockg6/pid:webapp.20 ports: - "8080:8080" depends_on: - db networks: - pid-network secrets: - source: pid-file
target: pidfile.properties
networks: pid-network:
volumes: dbdata:
secrets: pid-pass: external: true pid-file:
file: ./webApp/data/secrets/pidfile.properties
postgres-pass: external: true
Las modificaciones realizadas con respecto al fichero compose anterior son:
• Se omite la opción container_name en la definición de los servicios.
• La opción build es ignorada cuando se despliega un stack en modo Swarm. Sólo se admiten imágenes
pre-construidas.
• Se declaran los secretos postgres-pass, pid-pass y pid-file utilizados por los servicios del stack en el
nuevo bloque secrets. Los secretos pid-pass y postgres-pass son declarados como external, lo que
significa que se tratan de referencias a secretos ya creados en el swarm, mientras que pid-file se crea en
el momento del despliegue del stack a partir del contenido del fichero referenciado en el compose. El
servicio db usa los secretos pid-pass y postgres-pass, y webApp sólo pid-file.
• Las variables de entorno asociadas a las contraseñas obtienen sus valores de los respectivos secretos
localizados en la carpeta /run/secrets del contenedor. La imagen de PostgreSQL tiene algunas variables
de entorno con variantes compatibles con secretos. Concretamente, la variable
POSTGRES_PASSWORD_FILE de la imagen está preparada para leer de un fichero.
Inicialmente el script de inicialización utilizado en la imagen de las tareas del servicio db sólo podía obtener los
valores de las variables de entorno si se le pasaban directamente. Al ampliar la funcionalidad con los secretos de
Docker, el script se modifica de forma que las variables PID_XXX también cuenten con una variante que pueda
leer sus valores a partir del contenido de los ficheros especificados en la variable PID_XXX_FILE. Una vez
actualizado, esta adaptación obliga a tener que construir una nueva versión de la imagen.
El secreto pid-file hace referencia al fichero de propiedades pidfile.properties en el que se han volcado los valores
de los parámetros de conexión a la base de datos de MyBatis que antes estaban en fichero de inicialización
ResourceData.properties. Para el correcto funcionamiento de la aplicación, los parámetros registrados en el
documento, como el usuario, contraseña o nombre de la base de datos, deben coincidir con los valores de las
variables del servicio db.
Trabajo desarrollado
98
Nota: el fichero pidfile.properties se copiará en la ruta /run/secrets del contenedor. Ha sido modificar el fichero
de configuración de MyBatis (SqlMapConfig.xml) para que pueda localizar este nuevo recurso de propiedades
fuera del classpath de la aplicación con una ruta absoluta (/run/secrets/pidfile.properties).
Para iniciar el modo Swarm se ejecuta el comando docker swarm init. Esto convertirá automáticamente la
máquina en el nodo maestro o Manager del swarm, al que se podrían ir agregando nuevos nodos. Se comprueba
también la creación por defecto de la red overlay o superpuesta ingress y la red para puentear redes overlay con
la interfaz física local docker_gwbridge (de ámbito local). Ahora, el driver de red que se utiliza por defecto es
de tipo overlay (apartado 3.6.4.3).
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
103916a… bridge bridge local
ba5c4c8… docker_gwbridge bridge local
b9df448… host host local
p001q9a… ingress overlay swarm
3d0080c… none null local
Con el swarm arrancado, ya es posible ejecutar los comandos para la gestión de los secretos. Así, aquellos que
fueron declarados como recursos externos, deben estar creados previo al despliegue del stack, o de lo contrario,
el despliegue finalizará con un error de "secret not found". En el caso de postgres-pass, relativo a la contraseña
del superusuario de PostgreSQL, no necesario para el proyecto y que sólo se inicializa por motivos de seguridad,
el secreto guarda una contraseña alfanumérica aleatoria creada con el siguiente comando:
$ openssl rand -base64 30 | docker secret create postgress-pass -
Ejecutando el comando docker secret ls en el único nodo se obtiene el listado de secretos declarados en el swarm,
junto con un identificador unívoco y otras propiedades como la fecha de creación o actualización. Para obtener
más información de cada secreto se puede usar el comando docker secret inspect <secret_name>.
$ docker secret ls
ID NAME CREATED UPDATED
4tuvtfte6g41x6y… pid_postgres-pass 3 hours ago 3 hours ago
o1b2473mt6bb766… pid_pid-file 3 hours ago 3 hours ago
w1cgzda6rcn88rw… pid_pid-pass 3 hours ago 3 hours ago
Hay que tener en cuenta que es posible asignar el acceso de un servicio sobre un secreto en cualquier momento,
pero no se puede eliminar un secreto que este usando por algún servicio. Además, es importante reseñar que los
secretos son inmutables y tampoco se pueden actualizar directamente ni cambiar de nombre. Por ejemplo, en el
caso de que un secreto hubiese sido comprometido, el proceso para actualizarlo con la mínima disrupción sería
actualizar el servicio que lo use para revocar su acceso a dicho secreto y otorgarle de nuevo acceso usando el
mismo nombre del fichero destino.
99
99
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
$ docker service update --secret-rm pid-pass --secret-add \
source=new-pass-pid, target=pid-pass <servicio>
Finalmente, para desplegar el stack usaremos el siguiente comando:
$ docker stack deploy -c pid-compose_secrets.yml pid
Creating network pid_pid-network
Creating service pid_db
Creating service pid_webApp
El último argumento del comando es un nombre para el stack. Cada red, volumen y nombre de servicio se
prefijan con dicho nombre.
Nota: al igual que ocurría con docker-compose, habría que volver a actualizar la URL de conexión a la base
de datos en el fichero ResourceData.properties. siguiendo el patrón
jdbc:postgresql://<stack_name>_<service_name>:5432/<dataBase_name>.
En este caso, sería jdbc:postgresql://pid_db:5432/PID
Examinando el stack se puede comprobar el estado de los dos servicios que lo componen, las tareas que soportan
los servicios, los nodos que componen el swarm, etc.
$ docker stack ls
NAME SERVICES
pid 2
$ docker stack services pid
ID NAME MODE REPLICAS IMAGE PORTS
ktr4yv4… pid_db replicated 1/1 dockg6/pid:db.20
p8vnp2u… pid_webApp replicated 1/1 dockg6/pid:webApp.20 *:8080->8080
$ docker stack ps pid
ID NAME IMAGE NODE DESIRED STATE
Icapmwds… pid_webApp.1 dockg6/pid:webApp.20 PFC Running
o2b0brr5… pid_db.1 dockg6/pid:db.20 PFC Running
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
i0qkttr6iy61da… * PFC Ready Active Leader
En el siguiente apartado se aprovechará todo el trabajo realizado hasta el momento para desplegar el servidor
PIDS en un clúster multi-nodo Docker orquestado. Igualmente se podrá comprobar cómo al estar todo
prácticamente gestionado mediante ficheros de configuración se facilita el trabajo de cara a la programación y
automatización de todo el proceso, y por tanto al mantenimiento del proyecto bajo un sistema de control de
versiones.
Trabajo desarrollado
100
Pero antes de entrar a describir el siguiente escenario se explicará cual ha sido la medida adoptada para resolver
una de las consideraciones al usar Docker en modo Swarm: la distribución de imágenes. En el aparatado anterior
ya se comentó que un stack de Docker sólo acepta imágenes "pre-compiladas", y considerando que se va a
configurar un swarm compuesto de nodos distribuidos, es indispensable disponer un Registro como repositorio
de imágenes accesible desde los nodos swarm. De otra forma, habría que distribuir las imágenes manualmente
en cada nodo del clúster, lo cual sería impracticable. En este caso se ha usado Docker Hub, aunque igualmente
se podría haber configurado un registro privado como otro servicio más del stack.
El registro en Docker Hub da acceso a la creación de un único repositorio privado gratuito. En él se van a guardar
las imágenes personalizadas creadas para el proyecto. Con el propósito de mantener ordenados los repositorios,
el nombre de la cuenta empleada en el registro determinará el namespace bajo el que se organizan todos los
repositorios pertenecientes a una misma suscripción. Los datos de la cuenta de Docker Hub para el proyecto
serán dockg6 como namespace y pid como nombre del repositorio.
Figura 4-14. Repositorio Docker Hub
Lo habitual es que el nombre del repositorio coincida con el de la imagen que se va a contener, sin embargo, son
dos imágenes personalizadas las que se usan en el proyecto. Aunque no es lo recomendable, teniendo en cuenta
que las imágenes que se suban no van a tener una difusión fuera de este proyecto, se etiquetarán las imágenes
con un formato que permitan diferenciarlas para poder llevar un control de versiones de cada imagen con un
único repositorio.
Nota: no se muestran las etiquetas de las imágenes descargadas (docker pull) desde Docker Hub al usar el
comando docker images. Se trata de un comportamiento conocido pero que no influye en el comportamiento
del stack (Docker usa el digest de las imágenes para identificarlas. Es posible tener dos imágenes diferentes
con el mismo nombre y etiqueta, sin embargo, el digest de cada una será diferente).
Antes de subir (docker push) una imagen al repositorio, es necesario que el nombre de la imagen tenga un patrón
específico compuesto por el nombre de la cuenta, el del repositorio y, de manera opcional, un tag o etiqueta.
Habría que renombrar las imágenes (docker tag) con el formato <cuenta>/<repositorio>:<tag>. Esta es
precisamente la nomenclatura que se ha estado usando durante todo el proyecto a la hora de construir las
imágenes (docker build -t), por lo que se podría hacer la subida sin necesidad de re-etiquetar. No obstante,
primero hay que logarse en Docker Hub mediante el comando docker login.
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you
don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: dockg6
Password:
Login Succeeded
101
101
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
$ docker push dockg6/pid:db.20
The push refers to a repository [docker.io/dockg6/pid]
5b4995fac356: Pushed
eeed011d30f4: Pushed
1dcd23cba987: Layer already exists
3f5111fd7752: Layer already exists
…
Gracias a la estructura en capas de las imágenes (apartado 3.6.2.1) se optimiza el ancho de banda, no siendo
necesario subir o descargar las capas de las imagenes que no hubieran cambiado.
Figura 4-15. Imágenes en Docker Hub
4.3.3 Despliegue en un clúster de nodos distribuidos
Todo el trabajo realizado hasta ahora con contenedores se ha desarrollado en una única máquina Docker. El
funcionamiento del PIDS en un entorno de producción exige dotarlo de resiliencia, con capacidades de escalado
ágil para atender picos de demanda que no impacten en el rendimiento de los servicios, y actualizaciones
continuas que no impliquen interrupciones innecesarias. Todo ello sin menoscabo de una administración
simplificada. En el contexto de los contenedores, estos requisitos se satisfacen mediante una plataforma de
orquestación de servicios multi-contenedor.
El objetivo último de este apartado será utilizar Docker en modo Swarm para desplegar el stack de servicios
definido en el apartado anterior con tareas replicadas y distribuidas en un clúster de nodos. Para el
aprovisionamiento de los nodos se utilizarán instancias EC2 de Amazon Web Services (AWS), creadas y
administradas mediante la herramienta Docker Machine (apartado 3.6.3.2). Son, por tanto, prerrequisitos para
seguir avanzando estar inscrito en una cuenta de AWS, tener instalado Docker Machine y, como se vio en el
apartado anterior, disponer de las imágenes del proyecto en un repositorio de Docker Hub.
Según lo explicado en el apartado 3.6.4.2, los nodos de un clúster Swarm tendrán uno de los dos roles posibles,
Manager o Worker. Los nodos Workers contienen las tareas de los servicios mientras que los Managers se
encargan de la gestión del clúster, monitorizando el estado y planificando la ejecución de las tareas. Si no se
especifica lo contrario, un Manager puede actuar al mismo tiempo como Worker dentro del swarm.
En el escenario de partida que se propone, el clúster estará constituido por cuatro nodos, un Manager y tres
Trabajo desarrollado
102
Workers. Es importante reseñar que con un solo Manager se corre el riesgo de perder la capacidad de gestión
del clúster en caso de que el nodo se viese comprometido, aunque las tareas que estuvieran activas continuarían
ejecutándose. No obstante, esta arquitectura se plantea con fines expositivos, para posteriormente demostrar
cómo se puede promover o degradar los nodos en el swarm.
Nota: la mayoría de las herramientas de orquestación, como Docker en modo Swarm, usan el algoritmo de
consenso Raft entre los nodos Managers para poder mantener el estado del clúster con cierta tolerancia a fallos
y poder seleccionar un nuevo Manager líder en caso del fallo. De manera general, el algoritmo viene a
recomendar un número impar no elevado de nodos Managers.
4.3.3.1 Aprovisionamiento
Antes de desplegar las instancias EC2 que representen los nodos del clúster, hay que acceder a la cuenta de AWS
para crear un usuario IAM administrador y generar la clave de acceso del usuario, ya que para crear máquinas
en AWS con Docker Machine hay que proporcionar los datos Access Key ID y Secret Access Key de la clave.
Ambos parámetros se pueden almacenar en el fichero ~/.aws/credentials, donde Docker Machine busca por
defecto las claves de acceso para que no será necesario especificarlas cada vez que se ejecute el comando de
creación de nuevas instancias.
A través de Docker Machine se crearán los cuatro nodos del clúster, con Docker Engine instalados en ellos.
Todos los nodos tendrán las mismas características, especificadas en el momento de la instanciación con el
siguiente comando:
$ docker-machine create -d amazonec2 \
--amazonec2-instance-type "t2.micro" \
--amazonec2-security-group "docker-machine" \
--amazonec2-zone a \
--amazonec2-region eu-west-1 \
--amazonec2-iam-instance-profile "rexrayrole" \
--amazonec2-root-size 8 nodo1
Running pre-create checks...
Creating machine...
(nodo1) Launching instance...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this
virtual machine, run: docker-machine env nodo1
Para aprovisionar los nodos en AWS, hay que especificar al menos el driver amazonec2, una región AWS
apropiada y un nombre para el nodo. El driver incluye una amplia variedad de opciones [34] para configurar
diferentes características de procesamiento, red, seguridad, etc. Aunque se ha indicado explícitamente en el
comando, las instancias EC2 que se crean por defecto, y están incluidas en la capa gratuita de AWS, son del tipo
t2.micro, provistas de 1 CPU y 1 GB de memoria. El tamaño del disco para las instancias se ha fijado a 8GB.
Son prestaciones suficientes para probar el funcionamiento del servidor PIDS en circunstancias de carga de
trabajo moderadas.
103
103
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Mediante la opción --amazonec2-iam-instance-profile se especifica el perfil de instancia asociado a la instancia
EC2. El perfil de instancia contiene un rol definido que otorga permisos temporales a las aplicaciones que se
ejecutan en la instancia EC2, sin necesidad de tener que indicar credenciales. Sobre este punto se hablará más
adelante, en el momento que se vaya a crear el volumen de persistencia para la base de datos.
Por limitaciones que también se verán en los sucesivos puntos, todos los nodos deben crearse en la misma región
y zona de disponibilidad, si bien no es un escenario óptimo en términos de continuidad y tolerancia a fallos. La
región seleccionada ha sido Irlanda (eu-west-1). Esta decisión viene influenciada, en parte, por la obligación de
garantizar el cumplimiento de ciertas demandas legales en materia de protección de datos referidas a la ubicación
de los datos.
Como no se ha especificado lo contrario, el nodo se despliega en la VPC por defecto con una AMI AWS de
Ubuntu 16.04 TLS.
En la salida del comando se pueden comprobar las acciones realizadas, como la creación de la máquina virtual,
la generación de los certificados TLS para comunicación segura con las máquinas, o la configuración del
demonio de Docker. Asumiendo que se tiene un cliente Docker instalado, al final de la salida se dan las
indicaciones de cómo conectarlo con la máquina Docker remota: el comando eval $(docker-machine env nodo1)
establece las variables de entorno necesarias para apuntar la CLI de Docker local al Docker Engine remoto.
Los cuatro nodos creados en el proyecto se nombrarán como manager-01, nodo-02, nodo-03 y nodo-04. La
salida de docker-machine ls proporciona información general del estado de los nodos, como el nombre del nodo,
el driver con el que fue generado, la URL de administración, la versión de Docker instalada o los errores en la
máquina.
Nota: la columna ACTIVE marca con un * el nodo que está "activo", es decir, aquel al que la CLI de Docker
local está apuntando, mediante la variable de entorno DOCKER_HOST.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER
manager-01 * amazonec2 Running tcp://34.2.2.5:2376 v18.03.0-ce
nodo-02 - amazonec2 Running tcp://34.4.7.8:2376 v18.03.0-ce
nodo-03 - amazonec2 Running tcp://34.44.9.19:2376 v18.03.0-ce
nodo-04 - amazonec2 Running tcp://34.2.60.20:2376 v18.03.0-ce
Nota: al no tener una IP pública persistente en AWS puede ocurrir que, por ejemplo, al parar (docker-machine
stop <nodo>) y arrancar de nuevo los nodos, las direcciones IP de los nodos cambien. En esta situación, cuando
se intentasen establecer las variables de entorno del cliente Docker para conectar con el Docker Engine de una
de las instancias EC2, se produciría un error indicando que los certificados de conexión no son válidos. Para
regenerar los certificados hay que ejecutar el comando docker-machine regenerate-certs <nodo>
4.3.3.2 Inicio en modo Swarm
Una vez creados los cuatro nodos AWS del clúster, hay que conectar la CLI de Docker con el nodo de asumirá
el rol de Manager para iniciar el Swarm con el comando:
$ docker swarm init --advertise-addr <Manager_Private_IP/network_interface>
Trabajo desarrollado
104
El comando es el mismo que el usado en el punto anterior previo a la creación de secretos, pero añadiendo la
opción --advertise-addr que especifica la dirección IP o interfaz de red que utilizará internamente, en las
comunicaciones entre Managers y en la red overlay. Esta opción sólo es obligatoria si la máquina tiene varias
interfaces de red, aunque siempre es recomendable indicarla para mayor aclaración.
La salida del comando declara el nodo como primer Manager del swarm y proporciona instrucciones de cómo
añadir nuevos nodos al swarm, tanto Workers como Managers, a partir de la validación de los respectivos tokens
de ingreso (apartado 3.6.4.3).
Como paso previo a la ampliación del clúster es preciso habilitar el tráfico de gestión y comunicación entre los
nodos que van a formar parte del swarm. Al crear las instancias EC2 con Docker Machine, se crea un grupo de
seguridad denominado "docker-machine", que únicamente tiene permitido el tráfico entrante por los puertos
22/tcp, para el acceso por SSH, y 2376/tcp, para la comunicación remota entre cliente y servidor Docker. No
obstante, es necesario editar el grupo de seguridad para igualmente permitir las comunicaciones internas del
clúster en los siguientes puertos:
2377/tcp Tráfico de administración del clúster
7946/tcp-udp Tráfico de comunicación entre nodos, para el servicio de
descubrimiento de contendores
4789/udp Tráfico de administración del clúster
Figura 4-16. Grupo de seguridad
Con el grupo de seguridad configurado, se añadirán los restantes nodos como Workers del swarm. El token de
adhesión, como se trató en el apartado 3.6.4.3, se componen de dos partes separadas por un guion: un hash del
certificado de la CA que se utiliza en el clúster para la conexión TLS entre nodos, y una clave única del clúster
para cada rol. De esta forma, permite al Manager validar al nodo que solicita el registro en el clúster, y confirmar
al nuevo nodo el ingreso en el swarm correcto a partir del certificado de la CA. En caso de no haber anotado el
token específico para los nodos Workers al iniciar el swarm, se puede recuperar desde manager-01 con el
comando docker swarm join-token -q worker. Este token junto con la IP del Manager, anunciada en la opción -
-advertise-addr del arranque del swarm, son los parámetros de entrada para el comando de unión al clúster.
Dicho comando debe ejecutarse desde cada futuro nodo miembro del swarm.
105
105
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
$ eval $(docker-machine env nodo-0X)
$ docker swarm join –token <Worker-token> \
--advertise-addr <Worker_Private_IP> \
<Manager_Private_IP>:2377
Los pasos anteriores se tienen que repetir en nodo-02, nodo03 y nodo-04.
Habiendo apuntado de nuevo el cliente Docker al nodo Manager, el comando docker node ls lista los nodos que
forman el clúster, indicando su nombre, estado (ready | unknown | down | disconnected), disponibilidad para
contener tareas (active | pause | drain), y estado de los nodos Manager en el consenso Raft (leader | reachable
| unavailable).
$ eval $(docker-machine env manager-01)
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
kulpaj8oqkbdt2kd… * manager-01 Ready Active Leader
ma8bsssognf3nnnf… nodo-02 Ready Active
rom4i4utaszvvk3o… nodo-03 Ready Active
6g0x07xv6ijru71w… nodo-04 Ready Active
En este instante, el clúster está formado por un nodo Manager y tres nodos Workers. Suponiendo el requisito de
hacer el clúster más tolerante a fallo, se podrían promover como Managers los nodos nodo-02 y nodo-03 a
través del comando docker node promote < nodo>. La gestión de los roles debe hacerse desde el nodo Manager.
Volviendo a consultar el estado del clúster, se comprueba que nodo-02 y nodo-03 se han actualizado a Managers
"alcanzables" (reachable). Aun sin ser el Manager líder del swarm, desde cualquiera de estos nodos se podría
actuar sobre cualquier otro Manager, e incluso llegar a degradar al nodo manager-01 a Worker con el comando
docker node demote manager-01.
$ eval $(docker-machine env manager-01)
$ docker node promote nodo-02
Node nodo-02 promoted to a manager in the swarm.
$ docker node promote nodo-03
Node nodo-03 promoted to a manager in the swarm.
$docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
kulpaj8oqkbdt2kdtf… * manager-01 Ready Active Leader
ma8bsssognf3nnnfxd… nodo-02 Ready Active Reachable
rom4i4utaszvvk3ohl… nodo-03 Ready Active Reachable
6g0x07xv6ijru71wur… nodo-04 Ready Active
$ eval $(docker-machine env nodo-02)
$ docker node demote manager-01
Manager manager-01 demoted in the swarm.
Trabajo desarrollado
106
Estas actualizaciones se realizan a efectos de representar la capacidad de adaptación del clúster a cada
circunstancia o necesidad. Para continuar con el apartado, se vuelve a restablecer la configuración del swarm al
estado anterior de este planteamiento.
4.3.3.3 Persistencia multi-nodo
El servicio de base de datos que compone el stack del PIDS es un servicio stateful que requiere del
almacenamiento persistente de la información, por lo que será conveniente montar un volumen externo con
nombre. Sin embargo, en la arquitectura clúster actual, el reto es que el volumen debe estar accesible a todos los
nodos, ya que podría planificarse la ejecución de tareas del servicio de base de datos en cualquiera de ellos. Este
problema ya fue discutido en el apartado 3.6.4.4, donde se planteaban varias estrategias para persistir la
información de la base de datos, como definir restricciones en el stack que fuercen la planificación de las tareas
de base de datos siempre en el mismo nodo que presenta el volumen, con el inconveniente de condicionar la
continuidad del servicio a la disponibilidad del nodo en cuestión. Finalmente, la solución adoptada ha sido definir
un volumen EBS (apartado 3.5.1) en la misma VPC AWS, gestionado con el driver de REX-Ray (apartado
3.6.4.4), de modo que cualquier nodo del clúster pudiera acceder a los datos.
El driver de almacenamiento de REX-Ray no viene integrado con Docker, por lo que tiene que instalarse y
configurarse en cada uno de los nodos del clúster. La instalación agrupará los componentes cliente y servidor de
REX-Ray en todos los nodos, siguiendo un modelo descentralizado de gestión de los volúmenes descrito en el
apartado 3.6.4.4. La forma más sencilla de distribuirlo es mediante el plugin REX-Ray para Docker [35], que se
configura mediante variables de entorno en el mismo momento de la instalación:
$ docker plugin install rexray/ebs<:version> \
EBS_ACCESSKEY="" \
EBS_SECRETKEY="" \
EBS_REGION=eu-west-1a
El nombre del driver del volumen incluye también el nombre de la plataforma de almacenamiento en el formato
rexray/<storage_platform>. Para el proyecto, REX-Ray se encargará de la gestión de un volumen montado
sobre el servicio EBS de AWS. Omitir la versión del plugin en el nombre es equivalente a especificar la etiqueta
latest (los desarrolladores de REX-Ray aseguran que la versión latest siempre se corresponde con la versión más
reciente y estable del plugin).
Los parámetros de configuración indican el tipo de sistema de ficheros a usar, la ruta de datos dentro del volumen
y permisos asignados, valores de reintentos y tiempos de espera antes de determinar un fallo, etc. Todos son
opcionales a excepción de las variables EBS_ACCESSKEY y EBS_SECRETKEY que contienen las credenciales
de AWS con permisos suficientes para que REX-Ray pueda realizar llamadas a la API AWS. El driver EBS de
REX-Ray utiliza la librería AWS SDK oficial, por lo que soporta varias formas de proporcionar credenciales de
acceso. En este caso las variables anteriores están vacías porque, si se vuelve al momento de la creación de las
instancias EC2, se había declarado un perfil de instancia con un rol IAM personalizado, denominado rexrayrole,
asociado a los nodos del clúster. Así, se facilitan a las instancias EC2 credenciales temporales que REX-Ray
podrá utilizar para realizar las acciones sobre recursos AWS definidas en el rol, y sin necesidad de distribuir
ningún tipo de clave de acceso de largo plazo.
La creación del rol rexrayrole es un paso previo al aprovisionamiento de instancias EC2. Desde la consola de
administración de AWS, el rol se crea en la consola de IAM, seleccionando el tipo de rol AWS Service y el
servicio EC2. A continuación se le vincula una política personalizada (administrada por el cliente) que define
los permisos mínimos requeridos por REX-Ray para crear, montar, desmontar y gestionar los puntos de montaje
EBS. El siguiente fichero JSON describe la política que otorga acceso a las APIs de AWS necesarias:
107
107
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
RexRay_policy.json
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ec2:DetachVolume", "ec2:AttachVolume", "ec2:CopySnapshot", "ec2:DeleteSnapshot", "ec2:ModifyVolumeAttribute", "ec2:DescribeInstances", "ec2:DescribeTags", "ec2:DescribeSnapshotAttribute", "ec2:CreateTags", "ec2:DescribeSnapshots", "ec2:DescribeVolumeAttribute", "ec2:CreateVolume", "ec2:DeleteVolume", "ec2:DescribeVolumeStatus", "ec2:ModifySnapshotAttribute", "ec2:DescribeAvailabilityZones", "ec2:DescribeVolumes", "ec2:CreateSnapshot" ], "Resource": "*" } ] }
Nota: al crear el rol con la Consola de administración de AWS, automáticamente se crea el perfil de instancia
con el mismo nombre. Si se hubiera usado AWS CLI, habría que crear explícitamente el perfil de instancia y
añadirle el rol.
Otra variable que es importante especificar en la instalación del plugin de REX-Ray es la referida al parámetro
region, que representa la región y zona de disponibilidad AWS donde va a aprovisionarse el volumen EBS. Hay
que recordar que el ámbito del almacenamiento EBS es la zona de disponibilidad, es decir, sólo será visible por
aquellas instancias desplegadas en la misma zona de disponibilidad. Igualmente, en caso de un fallo inesperado
el volumen sólo puede replicarse dentro de dicha zona de disponibilidad. Esta limitación de EBS es la que ha
obligado a distribuir los nodos en una única zona de disponibilidad. Un caso idóneo para la redundancia de los
servicios hubiese sido distribuir los nodos Manager en un mínimo de tres zonas de disponibilidad para mitigar
cualquier fallo que afectase a una zona completa.
Los valores por defecto del resto de variables para la configuración del driver EBS de REX-Ray son suficientes
y adecuados.
Con REX-Ray instalado en todos los nodos, ya se puede crear el volumen externo para la persistencia de los
datos desde un nodo Manager:
$ eval $(docker-machine env manager-01)
$ docker volume create --driver=rexray/ebs \
--name=dbdata --opt=size=5
Trabajo desarrollado
108
El parámetro opt=size=5 indica que el volumen de datos tiene un tamaño de 5GB.
En el fichero de descripción del stack el volumen de datos dbdata estará declarado como external.
4.3.3.4 Despliegue del stack
Son pocas las modificaciones a aplicar sobre el último fichero de descripción del stack para adaptarlo al nuevo
entorno de nodos distribuidos. Lo más relevante es la adición de la opción deploy en cada uno de los servicios
para especificar parámetros de configuración relacionados con la implementación y la ejecución de los
servicios.
pid-compose_stack.yml
version: '3.4' services: db: image: dockg6/pid:db.20 volumes: - dbdata:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD_FILE=/run/secrets/postgres-pass - PID_DB=PID - PID_USER=ibatis - PID_PASS_FILE=/run/secrets/pid-pass deploy: placement: constraints: - node.labels.db == true networks: - pid-network secrets: - postgres-pass - pid-pass webApp: image: dockg6/pid:webApp.20 ports: - "8080:8080" depends_on: - db deploy: replicas: 4 update_config: parallelism: 2 delay: 10s failure_action: rollback networks: - pid-network secrets: - source: pid-file target: pidfile.properties visualizer: image: dockersamples/visualizer:stable ports: - "9000:8080" stop_grace_period: 1m30s volumes:
109
109
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
- "/var/run/docker.sock:/var/run/docker.sock" deploy: placement: constraints: [node.role == manager] resources: limits: cpus: '0.20' memory: 100M reservations: cpus: '0.10' memory: 50M
networks: pid-network: external: true
volumes: dbdata: external: true
secrets: pid-file: file: ./webApp/data/secrets/pidfile.properties postgres-pass: external: true pid-pass:
file: ./db/data/secrets/pid-pass
En el caso del servicio webApp, al ser un servicio "replicado" (modo por defecto, por lo que no necesita ninguna
opción de configuración -- otro modo, por ejemplo, sería global que implicar desplegar una tarea por cada nodo
del swarm --), con la sub-opción replicas se especifica el número de tareas del servicio que se ejecutarán en los
nodos el swarm. La estrategia de planificación de tareas en modo Swarm, denominada "spread", se basa en la
evaluación de los recursos disponibles en los nodos del clúster. Los criterios que sigue el planificador del clúster
a la hora de desplegar una nueva tarea serían: la nueva tarea será planificada primeramente en un nodo que no
estuviera ejecutando ninguna tarea del mismo servicio, independientemente del número de tareas de otros
servicios en ejecución. Si todos los nodos tienen al menos una tarea del servicio, el planificador selecciona el
nodo con menos tareas del servicio. Por último, en caso de coincidencia, ya tendría en cuenta la valoración
general de las tareas que estuvieran ejecutándose en cada nodo. Es la forma que tiene de garantizar la alta
disponibilidad de los servicios. La estrategia de planificación no sólo se aplica en caso de escalado de los
servicios sino también para preservar el "estado deseado" declarado para los servicios cuando se produce algún
fallo inesperado en las tareas, nodos u otros elementos de infraestructura. Si algún componente del clúster falla,
Swarm intenta restaurar las tareas de servicio afectadas al estado predefinido mediante la planificación de nuevas
tareas que serán desplegadas conforme a la estrategia descrita.
La sub-opción udpate_config determina el comportamiento del servicio replicado durante la aplicación de una
actualización del servicio, como por ejemplo la modificación de la imagen en la que se basa un servicio. El
funcionamiento predeterminado es actualizar una tarea del servicio, parándola y volviéndola a iniciar con la
configuración actualizada. Hasta que no se encuentre en estado "running", el proceso no prosigue con las
sucesivas tareas del servicio. En el stack se ha cambiado este comportamiento en el servicio webApp para
actualizar simultáneamente las tareas en grupos de dos (parallelism: 2), con un tiempo de espera de 10 segundos
entre cada grupo de tareas (delay: 10s), y que fuerce la marcha atrás en caso de fallo (failure_action: rollback).
El servicio db sólo tendrá una tarea en el swarm; sin embargo, se impone una restricción (constraints) que limita
la planificación de la tarea únicamente en los nodos del clúster que fueron etiquetados con el atributo
personalizado node.labels.db. Por tanto, es necesario que, antes de desplegar el stack, se hayan etiquetado
convenientemente aquellos nodos donde se pueden desplegar las tareas del servicio. Una etiqueta es un par de
clave-valor que se utiliza para aplicar metadatos a objetos Docker, en este caso a nodos. En el proyecto, la tarea
del servicio de base de datos sólo se podrá planificar en los nodos manager-01 y nodo-02, los cuales habrá que
actualizar para añadirles la etiqueta db correspondiente:
Trabajo desarrollado
110
$ eval $(docker-machine env manager-01)
$ docker node update --label-add db=true manager-01
$ docker node update --label-add db=true nodo-02
$ docker node inspect --format '{{ .Spec.Labels }}' self
map[db:true]
Nota: el volumen vinculado al servicio db está soportado por el sistema de almacenamiento Amazon EBS. Un
volumen EBS sólo se puede montar sobre una única instancia EC2, siendo éste el principal motivo por el que
el servicio sólo tiene una réplica. No obstante, se considera satisfecho el objetivo de ofrecer unas garantías
básicas de continuidad, que se ha logrado mediante la persistencia de la información en un volumen externo y
la posibilidad de adjuntarlo sin intervención manual en el nodo del clúster donde se hubiera restablecido el
servicio ante una contingencia en la infraestructura.
Se podrían estudiar alternativas para escalar horizontalmente el servicio de base de datos como montar un
volumen compartido EFS y complementarlo con un sistema de control de concurrencia en PostgreSQL, pero
queda fuera del alcance del proyecto.
Si por cualquier circunstancia (por ejemplo, el conflicto con otras restricciones o por falta de recursos) la tarea
no pudiera planificarse en ninguno de los nodos sobre los que tiene impuesta la restricción, permanecerá en un
estado "pending" hasta el momento en que sea posible desplegarla.
Docker en modo Swarm también incluye algunos atributos predefinidos basados en metadatos implícitos de los
nodos o Docker Engine que se pueden usar para establecer restricciones sobre la planificación de las tareas de
los servicios. Así, el servicio visualizer del stack tiene impuesta la restricción de que sus tareas asociadas sólo
puedan desplegarse en nodos Manager, a partir del atributo node.role. Visualizer [36] es un proyecto de código
abierto que se despliega como servicio para poder monitorizar el estado del swarm. Con él que se puede
visualizar vía Web un diagrama de los nodos que componen el clúster, las tareas de los servicios que se ejecutan
en cada nodo y la representación gráfica del estado de todos los componentes. No tiene ninguna influencia sobre
el servidor PIDS, pero ayudará a comprobar visualmente lo que sucede en el swarm durante algunas pruebas de
simulación.
Dado que el servicio visualizer solo puede desplegarse en nodos Manager, es conveniente establecer
restricciones de recursos para la CPU y memoria que puede consumir, y evitar cualquier riesgo de inanición de
recursos que impida al Manager desempeñar las operaciones de gestión del swarm. Al especificar un límite de
CPU y memoria (limits) se garantiza que las tareas del servicio no van a consumir más de los recursos definidos
(en este caso, las tareas no pueden usar más de un 20% de CPU y 100MB de memoria). Por el contrario, también
se han reservan recursos dedicados a cada tarea del servicio dentro de los nodos donde se ejecuten (un 10% de
la CPU y 50MB de memoria). Al igual que ocurre con el resto de restricciones, si no es posible planificar una
tarea porque se hubiesen reservado más recursos de los disponibles en los nodos del clúster, la tarea pasará a un
estado "pending" mientras no se liberen los recursos mínimos.
El comando para el despliegue del stack es el mismo que en el caso anterior, donde el swarm estaba compuesto
por un único nodo. En esta ocasión se le ha añadido la opción --with-registry-auth para enviar detalles de
autenticación de Docker Hub y que los nodos de AWS puedan descargar las imágenes de los servicios desde el
repositorio privado, a excepción de la imagen del servicio visualizer que la descargará de su repositorio oficial
público.
$ eval $(docker-machine env manager-01)
$ docker stack deploy --with-registry-auth -c pid-compose_stack.yml pidreg
111
111
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Para poder consumir los servicios desde el exterior se había declarado la publicación de los puertos 8080/tcp
para el servicio webApp y el puerto 9000/tcp para visualizer (mapeado al 8080/tcp). Según se explicó en el
apartado 3.6.4.3, cuando en modo Swarm se publica el puerto de un servicio, se está publicando para cada nodo
del clúster. Si una petición externa llegase a un nodo en el que no se estuviera ejecutando ninguna tarea del
servicio solicitado, Docker utilizará la propiedad "routing mesh" (apartado 3.6.4.3) para enrutar el tráfico hasta
las tareas que constituyen el servicio. Es necesario modificar el grupo de seguridad "docker-machine" para
permitir el tráfico entrante por esos puertos desde las direcciones IP origen de los clientes del servidor PIDS.
Figura 4-17. Grupo de seguridad – servicios publicados
4.3.3.5 Pruebas funcionales
Con el stack desplegado, se realizarán unas sencillas pruebas de simulación con las que evidenciar el
comportamiento del swarm ante la necesidad de escalado horizontal de los servicios los casos de escalado, o en
caso de fallo de un contenedor o nodo del clúster. Estas características ya se trataron a nivel teórico en el apartado
3.6.4.3.
Partimos de la situación representada en el siguiente esquema que proporciona el servicio visualizer:
Trabajo desarrollado
112
Figura 4-18. Visualizer - estado del swarm
Tal y como se puede comprobar en la imagen anterior, la planificación de las tareas en el swarm se ha realizado
conforme a las restricciones configuradas en el stack.
Prueba de escalado
Suponiendo un escenario donde existe la necesidad de atender una demanda creciente de peticiones de servicio
(por ejemplo, por un incremento del número de usuarios) y, por tanto, hay que escalar el número de tareas del
servicio sobre las que repartir la carga para mejorar el rendimiento y la alta disponibilidad del servicio.
Ejecutando el siguiente comando desde el nodo Manager se incrementan las tareas del servicio webApp a 10
réplicas. El planificador del swarm repartirá las tareas en base a la estrategia comentada en el apartado anterior.
$ docker service scale pidreg_webApp=10 --detach=false
pidreg_webApp scaled to 10
overall progress: 10 out of 10 tasks
1/10: running [==================================================>]
2/10: running [==================================================>]
…
10/10: running [==================================================>] verify: Service converged
113
113
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Figura 4-19. Visualizer - escalado
Una vez finalizada la necesidad, se puede volver a restablecer el escenario a la situación de partida.
$ docker service scale pidreg_webApp=4 --detach=false
Prueba de replicación / resiliencia
En caso de fallo de algún componente del clúster, como una tarea individual o un nodo completo, el modo
Swarm intentará restablecerlo para mantener el estado deseado declarado del stack. El modo más sencillo de
demostrar esta característica es simular que uno de los nodos deja de estar disponible en el clúster actualizando
su parámetro de disponibilidad en el swarm. La prueba se centra en los cambios sobre la única tarea del servicio
db.
Previamente se confirma que la tarea del servicio db está ejecutándose en el nodo-02.
$ docker service ps \
--format 'table{{.ID}}\t{{.Name}}\t{{.Image}}\t{{.Node}}\t{{.DesiredState}}' \
pidreg_db
ID NAME IMAGE NODE DESIRED STATE
a9wc9 … pidreg_db.1 dockg6/pid:db.20 nodo-02 Running
Se actualizará el parámetro de disponibilidad del nodo-02 de active a drain para que deje de ejecutar cualquier
tarea.
Trabajo desarrollado
114
$ docker node update --availability drain nodo-02
$ docker node ls \ --format 'table{{.ID}}\t{{.Hostname}}\t{{.Availability}}\t{{.ManagerStatus}}'
ID HOSTNAME AVAILABILITY MANAGER STATUS
qw9y7em … * manager-01 Active Leader
qn4t442 … nodo-02 Drain
kuhkvk6 … nodo-03 Active
n7hjbbd … nodo-04 Active
Por las restricciones establecidas en el stack, la tarea del servicio de base de datos sólo podrá replicarse al nodo
manager-01, sobre el que también se montará el volumen dbdata gracias al driver de almacenamiento de REX-
Ray.
$ docker service ps \
--format 'table {{.ID}}\t{{.Name}}\t{{.Image}}\t{{.Node}}\t{{.DesiredState}}' \
pidreg_db
ID NAME IMAGE NODE DESIRED STATE
16s5n … pidreg_db.1 dockg6/pid:db.20 manager-01 Running
8oi89 … \_ pidreg_db.1 dockg6/pid:db.20 nodo-02 Shutdown
Figura 4-20. Propiedades del volumen dbdata – prueba de resiliencia
Aunque se vuelva a cambiar la disponibilidad del nodo-02, los contenedores no se rebalancean al nuevo nodo
automáticamente.
115
115
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
$ docker node update --availability active nodo-02
$ docker stack ps pidreg
ID NAME NODE DESIRED STATE 16s5 … pidreg_db.1 manager-01 Running 8oi8 … \_pidreg_db.1 nodo-02 Shutdown ufmn … pidreg_webApp.1 nodo-03 Running a8bm … pidreg_visualizer.1 manager-01 Running up5y … pidreg_webApp.2 nodo-04 Running 6qxv … pidreg_webApp.3 manager-01 Running 7i2r … pidreg_webApp.4 nodo-03 Running 7o1n … \_ pidreg_webApp.4 nodo-02 Shutdown
Figura 4-21. Visualizer - cambio de disponibilidad (drain) de un nodo
4.3.3.6 Balanceador de carga
La forma más común de acceder a un servicio del que hay varias tareas replicadas entre múltiples nodos es
utilizando un balanceado de carga externo. AWS provee de un servicio de balanceo de carga, Elastic Load
Balancing (ELB), para actuar como único punto de entrada a los servicios y distribuir el tráfico externo entrante
entre todas las instancias del clúster. Aunque el balanceador direccione el tráfico a un nodo donde no se esté
ejecutando ninguna tarea del servicio en cuestión, el modo Swarm lo entregará a un nodo con una tarea activa
mediante la característica de routing mesh.
Para el proyecto, sólo los servicios webApp y visualizer tienen puertos publicados para tráfico HTTP. Entre las
tres opciones de balanceadores que ofrece ELB, Application Load Balancer (ALB) es el más adecuado para la
arquitectura del stack. Funciona a nivel de aplicación (capa 7) y únicamente para el equilibrio de carga de tráfico
HTTP y HTTPS. ALB requiere que se especifiquen subredes de más de una zona de disponibilidad para dotar
de alta disponibilidad a las aplicaciones, aunque para el caso del proyecto será irrelevante dado que todos los
nodos del clúster se sitúan en la misma zona de disponibilidad.
Trabajo desarrollado
116
La creación del balanceador se realizará desde la consola Web de AWS. Durante el asistente de creación se
especificarán los datos que siguen:
• Hay que proporcionar un nombre para el balanceador.
• Estará expuesto a Internet.
• Inicialmente se crearán dos agentes de escucha (listener) para el tráfico HTTP en los puertos 8080 y
9000, del servicio webApp y visualizer respectivamente. Al no haber creado ningún agente de escucha
seguro (HTTPS) se puede omitir el paso de importación de certificados.
Figura 4-22. Agentes de escucha del balanceador
• Se creará un nuevo grupo de seguridad llamado "ELB" asociado al balanceador para permitir el tráfico
entrante por los puertos 8080 y 9000 limitadas a las IP origen que interesen. Posteriormente será
necesario editar el grupo de seguridad "docker-machine" vinculado a los nodos para conceder la entrada
del tráfico que provenga del balanceador.
Figura 4-23. Edición de grupos de seguridad
117
117
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Nota: ya que de cara a los nodos todo el tráfico entrante tiene como origen el balanceador, también se eliminan
las reglas del grupo de seguridad "docker-machine" que permitían el acceso directo desde un cliente externo.
• Los grupos de destino (target groups) al que el balanceador enrutará las solicitudes entrantes estarán
compuesto por todos los nodos del clúster. El asistente de creación del balanceador sólo da opción a
registrar un grupo de destino para un puerto, aunque al finalizar es posible crear nuevos grupos de
destinos y añadirlos en las reglas de balanceo. En el caso concreto del grupo de destino al que se reenvía
el tráfico del puerto 8080, se configura la comprobación de estado basado en la accesibilidad a la ruta
/pid/services/identifyperson para que el balanceador pueda monitorizar el estado de cada uno de los
servicios situado detrás de él.
Figura 4-24. Creación de grupo de destino
Antes de la completar todos los pasos, hay una página para la confirmación de todos los parámetros de
configuración.
Trabajo desarrollado
118
Figura 4-25. Parámetros de la creación del balanceador de carga
Una vez completado el alta del balanceador, se puede crear otro grupo de destino relacionado con el tráfico del
servicio visualizer, en el puerto 9000, y editar las reglas de balanceo para el listener correspondiente de forma
que reenvíe el tráfico a este nuevo grupo de destino. Aunque el servicio está restringido a ejecutarse en los nodos
Manager, se añadirán todos los nodos del clúster al grupo por si a futuro se decidiera promocionar alguno.
Dado que el servicio webApp sólo es consumible mediante una URL específica y con el objetivo de restringir la
superficie de exposición del servicio, se definen reglas de reenvío basado en rutas: el listener para el puerto 8080
direccionará el tráfico en función de la ruta o contexto de la URL. Previamente habrá que crear un grupo de
destino "vacío", es decir, sin ninguna instancia. Las reglas de balanceado se editan de la siguiente manera:
• Una primera regla para reenviar todo el tráfico entrante por el puerto 8080 que coincida con la ruta
/pid/services/* al grupo de destino seleccionado.
• La regla por defecto entregará cualquier tráfico del puerto 8080 al grupo de destino vacío.
Figura 4-26. Reglas de balanceo
Pese a que el balanceador es flexible en cuanto al mapeo de los puertos de escucha y reenvío, por claridad y
comodidad, se han configurado los grupos de destino para que los puertos publicados en el balanceador sean los
mismos a los que reenvía el tráfico.
119
119
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Figura 4-27. Grupos de destino
4.3.3.7 Servicio Web a través de un canal seguro
La sensibilidad de la información tratada por el servidor PIDS exige aplicar una capa de cifrado que garantice
la integridad y confidencialidad en el tránsito de los datos intercambiados. Para securizar un servicio Web existen
dos opciones:
• La seguridad en el mensaje (SOAP), por ejemplo, con el estándar WS-Security que proporciona
diferentes posibilidades de securización, mediante el uso de Kerberos, SAML, tokens, certificados
X.509, etc. La seguridad se implementa en la capa de aplicación.
• La seguridad en la capa de transporte, por ejemplo, enviando los mensajes sobre el protocolo HTTPS.
El objetivo perseguido en este apartado será cifrar todo el tráfico entre cliente y servidor a través de una canal
seguro HTTPS. El balanceador de carga no se configurará como terminador SSL/TLS, sino que se garantiza la
seguridad punto a punto, entre cliente-balanceador y balanceador-servidor.
La secuencia de cambios realizados en el proyecto para alcanzar este objetivo ha sido:
1.- Obtener la clave privada y certificado de servidor
En el contexto académico y de desarrollo del proyecto, se generará un certificado autofirmado para el servidor
que, aunque no esté acreditado por ninguna autoridad de certificación (CA) reconocida y sirva para garantizar
la identidad del servidor, puede utilizarse para proveedor de una canal de comunicación seguro. El único
requisito será tener instalado el paquete OpenSSL.
Con el siguiente comando, se generan dos ficheros de formato .PEM relativos a la clave privada y el certificado
autofirmado del servidor:
$ openssl req -x509 -newkey rsa:4096 -keyout server-rsa-key.pem \
-out server-rsa-cert.pem -days 365
Generating a 4096 bit RSA private key .................................................................++ ...........................................++ writing new private key to 'server-rsa-key.pem' Enter PEM pass phrase: Verifying - Enter PEM pass phrase: ******** ----- You are about to be asked to enter information that will be incorporated into your certificate request.
Trabajo desarrollado
120
What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:ES State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:PIDS Email Address []:
El parámetro -keyout especifica el nombre de fichero de la nueva clave privada, de tipo RSA de 4096 bits
(- newkey). También, el parámetro -out define el nombre de fichero para el certificado autofirmado de salida
(- x509), que tendrá una vigencia de 365 días (-days).
De toda la información solicitada, únicamente se especifica una contraseña para proteger la clave privada y el
Common Name (CN) que, en caso de tener un dominio propio, se correspondería con el nombre de host en la
URL que valida el certificado.
Los ficheros de salida se guardan en el directorio data de webApp, dentro de la estructura de carpetas del
proyecto, para su posterior uso en el despliegue del servicio.
2.- Actualización del endpoint del servicio Web
Tan sólo hay que modificar la URL de localización de los ports en el documento WSDL del servicio Web.
PID.wsdl -- Service: IdentifyPerson
<!-- … --> <wsdl:service name="IdentifyPersonService"> <wsdl:port name="IdentifyPersonPort" binding="tns:IdentifyPersonBinding"> <soap:address location="https://localhost:8443/pid/services/identifyperson"/> </wsdl:port> </wsdl:service>
<!-- … -->
Nota: este cambio en el fichero WSDL empaquetado dentro del WAR obliga a tener que crear una nueva imagen
para el servicio webApp y alojarla en el repositorio privado de Docker Hub para poder usarla en el escenario
de nodos Docker distribuidos.
3.- Actualización del stack
Son varios los cambios aplicados al fichero compose del stack, todos relacionados con el servicio webApp:
• Definición de dos nuevos secretos Docker, server-rsa-key.pem y server-rsa-cert.pem, correspondientes
a los ficheros de la clave y el certificado generados anteriormente.
• Definición de una configuración Docker (Docker Configs) que sobrescribe el fichero de configuración
de Tomcat server.xml [37]en las tareas del servicio webApp. El nuevo server.xml habilita un elemento
Connector para aceptar conexiones HTTPS por el puerto 8443, con soporte del protocolo HTTP/2. Se
configura para usar la clave privada y el certificado proporcionados a través de los secretos del punto
anterior, sin crear un almacén de claves en el servidor donde mantenerlos.
121
121
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Nota: las configuraciones Docker permiten almacenar información no sensible, como ficheros de
configuración, sin necesidad de tener que copiarla en la construcción de la imagen (dockerfile), usar variables
de entorno o montar un volumen. De esta forma es posible mantener la imagen lo más genérica posible.
Las configuraciones funcionan de forma similar a los secretos, excepto que no se almacenan cifradas ni en
memoria, sino directamente en el sistema de archivos del contenedor.
server.xml
<!-- … --> <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig>
<Certificate certificateKeyFile="/run/secrets/server-rsa-key.pem" certificateFile="/run/secrets/server-rsa-cert.pem" certificateKeyPassword="********" type="RSA" /> </SSLHostConfig> </Connector> <!-- … -->
• Publicación del puerto 8443, en lugar del 8080.
• Uso de la nueva imagen base para el servicio.
pid-compose_stack-cert.yml
version: '3.4' services: #... webApp: image: dockg6/pid:webApp.30 ports: - "8443:8443" depends_on: - db deploy: replicas: 4 update_config: parallelism: 2 delay: 10s failure_action: rollback networks: - pid-network configs: - source: server.xml target: /usr/local/tomcat/conf/server.xml secrets: - source: pid-file target: pidfile.properties - server-rsa-key.pem - server-rsa-cert.pem
Trabajo desarrollado
122
configs: server.xml: file: ./webApp/data/server.xml secrets: pid-file: file: ./webApp/data/secrets/pidfile.properties postgres-pass: external: true pid-pass: file: ./db/data/secrets/pid-pass server-rsa-key.pem: file: ./webApp/data/secrets/certs/server-rsa-key.pem server-rsa-cert.pem: file: ./webApp/data/secrets/certs/server-rsa-cert.pem #...
4.- Nuevo listener del balanceador
Desde la consola de AWS, es necesario crear un nuevo agente de escucha seguro en el balanceador ALB para
que pueda reenviar las solicitudes HTTPS a las instancias del clúster. Por otro lado, el balanceador utilizará una
conexión cifrada para comunicarse con las instancias, por lo que también es preciso crear un nuevo grupo de
destino al que el balanceador distribuirá el tráfico por el puerto 8443.
Figura 4-28. Grupos de destino para comunicación segura
Durante el proceso se solicita seleccionar el certificado que se utilizará en las conexiones frontend. En este caso,
se importará en el servicio IAM la clave privada y el certificado generados anteriormente, copiando el contenido
con codificación PEM de los ficheros en las casillas correspondientes, que se utilizará para establecer la
configuración de seguridad del listener.
123
123
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
Figura 4-29. Configuración de agente de escucha seguro
Si no se hubiera configurado previamente, al finalizar la creación del listener aparecerá una advertencia
informando que el grupo de seguridad "ELB" asociado al balanceador no tiene una regla que deje pasar el tráfico
en el puerto de escucha. Así mismo, hay que editar el grupo de seguridad "docker-machine" para permitir el
tráfico entrante desde el balanceador a las instancias.
Figura 4-30. Esquema del stack deplegado en cloud
5.- Actualización del cliente
El cliente debe añadir el certificado autofirmado en su almacén de certificados de confianza o truststore, de
forma que el cliente pueda autenticar al servidor como host de confianza. Este almacén suele localizarse en la
ruta $JAVA_HOME/jre/lib/security con el nombre de fichero cacerts. Esta acción puede realizarse con la
Trabajo desarrollado
124
herramienta keytool incluida en el JDK/JRE de Java. El siguiente comando debe ejecutarse con permisos de
root para agregar el certificado en el almacén de claves:
$ keytool -keystore $JAVA_HOME/jre/lib/security/cacerts -importcert \
-alias pids -file server-rsa-cert.pem
Adicionalmente, Java por defecto verifica que el CN del certificado sea el mismo que el nombre de host de la
URL del servicio Web. Si no coinciden, el cliente del servicio Web fallará con una excepción. Para evitarlo
durante el desarrollo, en el código del cliente se puede establecer como verificador predeterminado un
verificador javax.net.ssl.HostnameVerifier personalizado que anule la comprobación cuando la
dirección IP o nombre de host sea la del endpoint del servicio Web. En esta fase del proyecto, se corresponderá
con la IP externa asignada al balanceador de carga. Para ello, habría que insertar un bloque estático en la clase
principal del cliente Java de la siguiente forma:
Principal.java
public class Principal { static { javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier( new javax.net.ssl.HostnameVerifier(){ public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) { if (hostname.equals("<direccion_IP / hostname>")) { return true; } return false; } }); } // …
}
Nota: esta solución solo se recomienda para fines de prueba y desarrollo. En entornos de producción, debe
usarse un certificado firmado por una CA de confianza, en cuyo caso no hubiera sido necesario plantear
ninguna de estas modificaciones para el cliente.
4.3.3.8 Automatización del despliegue
Todo el proceso descrito para el despliegue del PIDS en el entorno cloud se ha conseguido automatizar mediante
scripts interactivos desarrollados en bash.
Son prerrequisitos para la ejecución de los scripts:
• Tenerlos ubicados en una carpeta que se encuentre al mismo nivel que el directorio del proyecto
Docker.
• Disponer de las imágenes base para los servicios del stack cargadas en el repositorio de Docker Hub.
Para ello, se ha debido completar la generación del WAR que empaqueta el servicio Web del servidor
PIDS y haber etiquetado las imágenes construidas con la nomenclatura correspondiente.
125
125
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
• Haber iniciado sesión en Docker Hub con el comando docker login.
• Disponer de las credenciales de AWS (aws_access_key_id y aws_secret_access_key) en el fichero
estándar para Amazon AWS, ubicado en ~/.aws/credentials.
• Haber creado un rol (rexrayrole) específico para otorgar a las instancias declaradas los permisos
requeridos por RexRay.
• Configurar el grupo de seguridad asociado las instancias EC2 de AWS para permitir el tráfico entrante
desde el balanceador de carga, entre instancias y de gestión del clúster por los puertos de comunicación
correspondientes.
• Configurar el balanceador ELB, con los agentes de escucha y grupos de destino necesarios, aunque no
será posible añadir los targets en los grupos de destino hasta que se hayan dado de alta las instancias
del clúster. Así mismo, el grupo de seguridad del balanceador debe permitir el tráfico entrante de los
servicios expuestos, y el certificado auto-firmado del servicio Web tiene que importarse al servicio
IAM.
• Mantener la estructura de carpetas para el proyecto que se ha ido documentando, de forma que los datos
de los servicios, como secretos o configuraciones Docker que hacen referencia a ficheros, estén
disponibles en tiempo de despliegue del stack.
El proceso se ha divido en cuatro scripts, numerados e identificados por su correspondencia con cada una de las
acciones realizadas durante el despliegue en un clúster de nodos distribuidos:
• 01-swarm-build.sh: realiza el aprovisionamiento de los nodos del clúster en AWS, inicia el swarm e
ingresa los nodos Workers y Manager, y añade metadato a los nodos donde estará restringido el
despliegue del servicio db.
• 02-RexRay-install.sh: instala el plugin de Docker de REX-Ray/EBS en cada una de las instancias para
garantizar la persistencia multi-nodo de los datos.
• 03-volume-network-secret-create.sh: crea todos los componentes externos del stack, como el volumen
de persistencia, la red de comunicación a la que se conectan las tareas de los servicios, y el secreto
relativo a la contraseña del superusuario postgres de la base de datos (se crea de forma aleatoria).
• 04-stack-deploy.sh: despliega el stack a partir del fichero pid-compose_stack.yml. Antes del mostrar
información del estado de los servicios, espera 30 segundos, tiempo suficiente para la descarga de las
imágenes de los servicios y despliegue de las réplicas en las instancias del clúster.
La variante 04_2-stack-deploy.sh se diferencia del anterior en que el fichero compose utilizado es el
pid-compose_stack-cert.yml.
127
5 CONCLUSIONES
Con carácter general, a lo largo del proyecto se ha realizado la actualización de un servicio de identificación
de personas. El sistema original, que utilizaba una arquitectura tradicional con un servidor de aplicaciones y un
gestor de base de datos desplegados en un único equipo, se ha migrado a una arquitectura distribuida y con
posibilidad de replicación, que no solo le confiere beneficios directos de estabilidad, escalabilidad y seguridad,
sino que permite acelerar el desarrollo facilitando el control de versiones, el mantenimiento y la reutilización
de componentes. Todo ello propicia el uso e integración de metodologías ágiles en el ciclo de vida de la
aplicación para obtener unos resultados más eficientes y de mayor calidad, al reducir los tiempos de entrega y
la anticipación a errores.
Figura 5-1. Escenario de partida y final
Haciendo un repaso a los objetivos planteados en el inicio del proyecto, se pueden considerar plenamente
satisfechos, si bien las decisiones de diseño tomadas para la consecución de alguno de ellos no han estado exentas
de problemas.
La decisión de usar el framework de persistencia MyBatis simplificó significativamente la programación de la
lógica de persistencia, al tiempo que permitió centrarse en el desarrollo de nuevos métodos del servicio PIDS y
ejecutar operaciones sobre atributos a un nivel de detalle más profundo. Uno de los propósitos que se pretendían
conseguir con el uso del framework de persistencia era segregar la lógica propia del servicio y la de acceso y
tratamiento de la base de datos, de forma que la aplicación se pudiera convertir en un bloque software reutilizable
y completamente independiente del modelo de datos que se emplease. Esta pretensión se ha logrado en su mayor
parte, a excepción de algunas operaciones UPDATE de tipos complejos para los que se definieron métodos
específicos, en lugar de implementarlos como procedimientos almacenados.
Conclusiones
128
128
El desarrollo de la aplicación como servicio Web es lo que garantiza el cumplimiento del requisito fundamental
de integración con otros sistemas sanitarios mediante mecanismos estandarizados de comunicación, tanto a nivel
de canal como de formato. Se toma la determinación de implementarlo como servicio Web SOAP (en el
arranque del proyecto, la mayor parte de los trabajos y estudios relacionados a nivel de interoperatividad técnica
en el ámbito sanitario mostraban una mayor tendencia en el uso del protocolo SOAP), teniendo siempre en
perspectiva el contexto general en el que va a funcionar la aplicación: los sistemas sanitarios presentan un
complejo entramado de relaciones entre distintos agentes, gobernados por una descentralización administrativa.
Este entorno de integración exige funcionar bajo un estándar consolidado y en base a un "contrato" normalizado,
representado por el documento WSDL, donde se especifiquen claramente las operaciones y el tipado de los datos
que se van a intercambiar. Por otro lado, el empleo de un enfoque descendente (contract-first) ha supuesto un
punto adicional de desacoplamiento con el modelo de datos empleado, generándose éste automáticamente a
partir de las herramientas de JAX-WS incluidas en el JDK que toman como parámetro el documento WSDL del
servicio.
Cabe destacar que en un primer planteamiento se llegó a desarrollar el servicio tanto con Apache Axis2 como
con CXF. Finalmente se optó por usar la implementación de referencia de la API JAX-WS, suficiente para
cumplir con las necesidades iniciales de integración que no demandaban extender el servicio con
especificaciones WS-* adicionales. Tampoco se requería compatibilidad con frameworks de desarrollo como
Spring, donde sí podría destacar CXF frente al resto de opciones.
Durante la fase de estudio del proyecto quedaron en evidencia las altas expectativas que se han generado entorno
a la virtualización basada en contenedores. Viendo la cuota de aceptación progresiva que está recibiendo, no
cabe duda de que acabará convirtiéndose en una pieza integral en los entornos de producción de compañías de
todos los tamaños. Son continuas las innovaciones y apuestas que están haciendo los grandes proveedores de
servicios en relación a esta tecnología, que cuenta ya con unas bases sólidas asentadas y un constante crecimiento
de uso entre la comunidad de desarrolladores. Aun a riesgo de quedar desactualizado en poco tiempo por el alto
ritmo de evolución que mantiene, no se ha querido dejar pasar la oportunidad de evaluar su aplicación en el
proyecto y aprovecharse de la sencillez de su modelo declarativo para construir las arquitecturas de servicio. La
mejor baza del uso de contenedores reside en su ligereza y portabilidad, que lo convierte prácticamente en una
herramienta de automatización de despliegue, ideal para integrarse con las filosofías actuales de tipo DevOps.
La naturaleza efímera de los contendores, respaldada por características como la ligereza y rapidez de arranque,
permiten que puedan ser sustituidos fácilmente sin que el funcionamiento del sistema se vea alterado. Sin
embargo, esta tecnología alcanza su mayor potencial combinando los servicios de computación cloud con
herramientas de orquestación de clúster de contenedores, tal y como se ha demostrado en el proyecto, cubriendo
sin esfuerzo los requisitos que se habían fijado respecto al aprovisionamiento de un entorno con prestaciones de
escalabilidad y alta disponibilidad. Una circunstancia que también ha propiciado la simplicidad en el despliegue
es el carácter stateless de la aplicación, a excepción de la necesidad de persistir la información almacenada en la
base de datos, pero que igualmente se ha resuelto cómodamente mediante el uso de un plugin de almacenamiento
para la gestión de un volumen creado sobre servicios de almacenamiento en AWS.
5.1 Líneas de mejora
El ámbito del proyecto contempla un importante número de áreas donde se podrían realizar mejoras o
extensiones del proyecto. Centrándose en el alcance concreto del proyecto, a continuación se plantean diferentes
líneas de continuidad a diferentes niveles:
• A nivel de desarrollo, podría actualizarse el modelo de los tipos de datos utilizado en el servicio al
modelo de referencia de la normativa UNE-EN ISO 13606, estando este requisito fuera del alcance
actual.
En un plano más técnico, el modelo de datos está estructurado de acuerdo a relaciones de tipos
complejos compuestos por colecciones de tipos simples u otros tipos complejos. Este es el mismo
planteamiento que se ha seguido en la composición anidada de las sentencias SQL, al establecerse como
objetivo ampliar las especificaciones de búsqueda y hacer más granular las condiciones de recuperación
de los atributos. Con este enfoque ha sido posible generar una estructura modular para reaprovechar en
las sentencias de los atributos de mayor nivel los bloques de SQL empleados en las sentencias de los
129
129
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
atributos de más bajo nivel que los componen. No obstante, puede derivar en la ocurrencia del problema
de "N+1 consultas", comentado en el proyecto. La versatilidad y dinamismo que proporciona MyBatis
da la posibilidad de atajar el problema desde diferentes opciones que pueden revisarse. Como ejemplo,
se ha dejado preparada en el paquete del proyecto una consulta basada en ResultMaps anidados que
resuelve el problema y que podría usarse como referencia para hacerla extensible al resto de sentencias.
Otro punto de mejora está relacionado con eliminar los pocos puntos de acoplamiento que han quedado
en las operaciones de actualización de datos, de forma que la lógica del servicio de información
demográfica pudiera entregarse como un paquete totalmente independiente. No se llegó a estudiar a
fondo ni se pudo probar la integración en el proyecto de algunas funcionalidades de MyBatis, como su
capacidad de ejecutar operaciones por lotes (batch updates) con los que es muy probable que se
obtuviera mejores resultados de desacople.
Por otro lado, siguiendo las buenas prácticas de desarrollo software, se podría perfeccionar el
tratamiento de la jerarquía de excepciones o la gestión de los logs para llevar un registro de los sucesos
importantes o de depuración de una aplicación Java.
• A nivel de arquitectura, una vez entendidos los fundamentos de la tecnología de contenedores y las
herramientas de orquestación que los gobiernan, las propuestas de mejora van enmarcadas al despliegue
de los servicios aprovechando las innovaciones continuas que van surgiendo en esta materia. Así, las
mencionadas soluciones de contenedores como servicios (CaaS) que los grandes proveedores de
servicios van agregando a su catálogo permiten unificar y agilizar aún más todo el proceso de creación,
despliegue y entrega.
No ha sido necesario por las características del servicio desarrollado, pero se podría extender la
infraestructura proyectada y dejarla preparada para soportar aplicaciones stateful que requieran
persistencia de sesión. En una arquitectura monolítica compuesta por un par servidores esta necesidad
se puede salvar sin complicaciones usando algún tipo de mecanismo de caché que distribuya la sesión
entre los servidores o mediante balanceadores de carga que redirijan todas las peticiones al mismo
servidor (sticky session). Sin embargo, la idea detrás de los clústeres de contenedores es proporcionar
unas escalas mucho más elevadas, con varios contenedores replicados en el mismo nodo. Una de las
posibles opciones sería implementar dentro del clúster un servicio de balanceo de carga que soporte
sticky sessions con soluciones como Traefik o Docker Flow Proxy.
• A nivel funcional, es imprescindible abordar una estrategia de seguridad desde todas las perspectivas,
dado el ámbito de operación previsto para el sistema y la naturaleza de datos de carácter personal que
se tratan. Los sistemas de información en el sector de la salud están sometidos al desafío de preservar
la privacidad y confidencialidad de los datos personales, circunstancia que además se encuentra
respaldada por importante componente normativo y legal, y que convierte a la protección de los datos
en una prioridad [38].
Al margen de las medidas de carácter organizativo, el marco regulatorio de la protección de datos de
carácter personal europeo demanda la aplicación de medidas oportunas y eficaces en todos los dominios
de seguridad: protección de las comunicaciones, mecanismos de control de identificación y acceso,
mantenimiento de registros de auditoría, análisis de vulnerabilidades (a nivel de aplicación,
configuración y sistemas), bastionado de los sistemas, copias de seguridad, cifrado de los datos en
reposo, etc. Trasladando estas obligaciones al stack tecnológico del servicio de identificación
desarrollado, muchos de estos controles han sido tenidos en cuenta durante la configuración de la
infraestructura en el cloud y en el despliegue del servicio con contenedores como una primera
aproximación para obtener unos niveles de seguridad aceptables, aunque insuficientes en un entorno
productivo real. Sería conveniente reforzarlas y enmarcarlas en una política de seguridad común que las
gobiernen.
Siguiendo en la línea de la seguridad y particularizando en la tecnología de contenedores, aunque
utilizan una serie de técnicas de aislamiento para proteger los contenedores unos de otros y de la
infraestructura subyacente con funciones del kernel como cgroups o namespaces, no están exentos de
problemas de seguridad, siendo uno de los principales motivos que pudieran estar frenando su
Conclusiones
130
130
expansión. Sin embargo, han conseguido importantes avances complementándose con proyectos como
SELinux, AppArmor o Seccomp que bien configurados funcionarían a modo de cortafuegos.
Igualmente, el Centro para la Seguridad en Internet (CIS) tiene publicado un documento de buenas
prácticas sobre la correcta configuración de Docker [39]. La aplicación de estos puntos podría plantearse
como posible proyecto de seguridad.
A nivel de los servicios Web SOAP, una forma óptima de de asegurar la confidencialidad e integridad
durante el intercambio de mensajes sería utilizando un modelo de seguridad de la especificación WS-
Security (WSS) para establecer una capa de autenticación y cifrado de extremo a extermo en el servicio.
131
131
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
REFERENCIAS
[1] S. Indarte y P. Pazos Gutiérrez, «Estándares e interoperabilidad en salud electrónica: Requisitos para una
gestión sanitaria efectiva y eficiente,» Comisión Económica para América Latina y el Caribe (CEPAL),
Santiago, 2011.
[2] J. Vander Sloten, P. Verdonck, M. Nyssen y J. Haueisen, 4th European Conference of the International
Federation for Medical and Biological Engineering 23-27 November 2008, Antwerp, Belgium (IFMBE
Proceedings), Antwerp: Springer, 2009.
[3] OMG, «Person Identification Service (PIDS) Specification,» 2001.
[4] J. Lewis y M. Fowler, «Microservices, a definition of this new architectural term,» 2014. [En línea].
Available: https://martinfowler.com/articles/microservices.html.
[5] «Contenedores de Windows,» Microsoft, 2 05 2016. [En línea]. Available: https://docs.microsoft.com/es-
es/virtualization/windowscontainers/about/.
[6] T. Brown, «MSDN Magazine,» Microsoft, abril 2017. [En línea]. Available:
https://msdn.microsoft.com/es-es/magazine/mt797649.aspx.
[7] W. Wong, «What’s the Difference Between Containers and Virtual Machines?,» ElectronicDesign, 2016.
[En línea]. Available: http://www.electronicdesign.com/dev-tools/what-s-difference-between-containers-
and-virtual-machines.
[8] «Debian Manpages,» septiembre 2017. [En línea]. Available:
https://manpages.debian.org/testing/manpages/cgroup_namespaces.7.en.html.
[9] Wikipedia, «Chroot,» [En línea]. Available: https://es.wikipedia.org/wiki/Chroot .
[10] M. Russinovich, «Microservices: An application revolution powered by the cloud,» Blog Microsoft
Azure, 2016. [En línea]. Available: https://azure.microsoft.com/es-es/blog/microservices-an-application-
revolution-powered-by-the-cloud/.
[11] «Kubernetes,» [En línea]. Available: https://kubernetes.io/docs/concepts/workloads/pods/pod/.
[12] P. Mell y T. Grance, «SP 800-145. The NIST Definition of Cloud Computing,» National Institute of
Standards & Technology, 2011. [En línea]. Available:
https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-145.pdf.
[13] W3C, «XML Path Language (XPATH) 3.1,» 2017. [En línea]. Available: http://www.w3.org/TR/xpath-
3/ .
[14] «Mybatis reference dcoumentation,» [En línea]. Available: http://www.mybatis.org/mybatis-3/index.html
Referencias
132
132
.
[15] K. L. Nitin, S. Ananya y S. Sangeetha, «iBATIS, Hibernate, and JPA: Which is right for you?,»
JavaWorld, 2008. [En línea]. Available: https://www.javaworld.com/article/2077875/open-source-
tools/ibatis--hibernate--and-jpa--which-is-right-for-you-.html.
[16] «JAX-WS Release Documentation. Tools,» Oracle Java Enterprise Edition, [En línea]. Available:
https://javaee.github.io/metro-jax-ws/doc/user-guide/ch04.html#tools-wsimport-ant-task .
[17] «Creating a Simple Web Service and Clients with JAX-WS,» Oracle Java Documentation, [En línea].
Available: https://docs.oracle.com/javaee/7/tutorial/jaxws001.htm#BNAYN.
[18] «Types Supported by JAX-WS,» Oracle Java Documentation, [En línea]. Available:
https://docs.oracle.com/javaee/7/tutorial/jaxws002.htm#BNAZC.
[19] «Docker Community Edition,» [En línea]. Available: https://www.docker.com/community-edition.
[20] «Docker Enterprise Edition,» [En línea]. Available: https://www.docker.com/enterprise-edition.
[21] «OCI, Open Containers Initiative,» [En línea]. Available: https://www.opencontainers.org/.
[22] «Virtual Extensible LAN,» Wikipedia, [En línea]. Available:
https://es.wikipedia.org/wiki/Virtual_Extensible_LAN.
[23] «IP Virtual Server,» Wikipedia, [En línea]. Available: https://en.wikipedia.org/wiki/IP_Virtual_Server.
[24] «Use overlay networks,» Docker Docs, [En línea]. Available:
https://docs.docker.com/network/overlay/#bypass-the-routing-mesh-for-a-swarm-service.
[25] «Use Docker Engine plugins,» Docker Docs, [En línea]. Available:
https://docs.docker.com/engine/extend/legacy_plugins/#volume-plugins.
[26] «REX-Ray,» [En línea]. Available: https://rexray.readthedocs.io/en/stable/ .
[27] G. McCluskey, «Using Java Reflection,» Oracle, 1998. [En línea]. Available:
http://www.oracle.com/technetwork/articles/java/javareflection-1536171.html.
[28] «MyBatis configuration. Settings,» [En línea]. Available: http://www.mybatis.org/mybatis-
3/configuration.html#settings.
[29] «The Java EE 5 Tutorial: Chapter 17 Binding between XML Schema and Java Classes,» Oracle, [En
línea]. Available: https://docs.oracle.com/cd/E19575-01/819-3669/6n5sg7bj5/index.html.
[30] «JAXB2 Basics,» [En línea]. Available: https://github.com/highsource/jaxb2-basics.
[31] «Introduction to the Standard Directory Layout,» Apache Maven Project, [En línea]. Available:
https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html.
[32] «PostgreSQL official docker container,» [En línea]. Available: https://hub.docker.com/_/postgres/ .
[33] «Tomcat official docker container,» [En línea]. Available: https://hub.docker.com/_/tomcat.
133
133
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
[34] «Machine drivers. Amazon Web Services,» Docker Docs, [En línea]. Available:
https://docs.docker.com/machine/drivers/aws/.
[35] «REX-Ray EBS plugin for Docker,» [En línea]. Available: https://rexray.readthedocs.io/en/stable/user-
guide/schedulers/docker/plug-ins/aws/ .
[36] «Docker Swarm Visualizer,» [En línea]. Available: https://github.com/dockersamples/docker-swarm-
visualizer .
[37] «Apache Tomcat 8 - SSL/TLS Configuration HOW-TO,» The Apache Software Foundation , 2018. [En
línea]. Available: https://tomcat.apache.org/tomcat-8.5-doc/ssl-howto.html.
[38] R. García, «El Reglamento General de Protección de Datos y su aplicación en el ámbito sanitario,» Revista
de la Sociedad Española de Informática y Salud, nº 127, 02 2018.
[39] «CIS Docker Community Edition Benchmark version 1.1.0,» CIS, [En línea]. Available:
https://www.cisecurity.org/benchmark/docker/.
Glosario
134
134
GLOSARIO
AENOR: Asociación Española de Normalización y Certificación 3
AMI: Amazon Machine Image 46
API: Application Programming Interface 35
AWS: Amazon Web Services 8
CEN: Comité Europeo de Normalización 3
CIS: Center for Internet Security 132
CLI: Command Line Interface 48
CORBA: Common Object Request Object Architecture 14
CTN: Comité Técnico de Normalización 3
DCOM: Distributed Component Object Model 14
DICOM: Digital Imaging and Communication in Medicine 3
DNS: Domain Name System 30
DTD: Document Type Definition 21
EBS: Elastic Block Store 46
EJB: Enterprise JavaBeans 44
ELB: Elastic Load Balancer 47
FTP: File Transfer Protocol 16
HCE: Historia Clínica Electrónica 1
HTTP: Hypertext Transfer Protocol 16
IAM: Identity and Access Management 46
JAXB: Java Architecture for XML Binding 42
JAX-WS: Java API for XML based Web Services 41
JDBC: Java DataBase Connectivity 39
JDK: Java SE Development Kit 82
JIC: Joint Initiative Council 4
OASIS: Organizarion for the Advancement of Structured Information Standards 17
OCI: Open Container Initiative 48
OMG: Object Management Group 3
ORM: Object Relational Mapper 39
PIDS: Person Identification Service 9
REST: Representational State Transfer 16
RMI: Remote Method Invocation 14
135
135
Diseño y desarrollo de un servicio de información demográfica en entorno Cloud basado en
contenedores
RPC: Remote Procedure Call 14
SDO: Standards Developing Organizations 4
SEI: Service Endpoint Interface 43
SIB: Service Implementation Bean 43
SMTP: Simple Mail Transfer Protocol 16
SOA: Service Oriented Architecture 25
SQL: Structure Query Language 38
TIC: Tecnología de la Información y la Comunicación 1
TSI: Tarjeta Sanitaria Individual 9
UDDI: Universal Description Discovery and Integration 17
VIP: Virtual IP 58
VPC: Virtual Private Cloud 46
W3C: World Wide Web Consortium 15
WS: Web Service 17
WSDL: Web Service Description Language 17
XML:eXtensible Markup Language 2