Servicio Dart: marco de aplicación del servidor

Tabla de contenido




Formación



La última vez que terminamos con eso colocó una página web ficticia estática, desarrollada con Flutter para la web. La página muestra el progreso del desarrollo de nuestro servicio, pero los datos sobre las fechas de inicio del desarrollo y lanzamiento tenían que estar codificados en la aplicación. Por lo tanto, perdimos la capacidad de cambiar la información de la página. Es hora de desarrollar una aplicación de servidor de datos. Un diagrama de todas las aplicaciones de servicio se encuentra en el artículo "Servicio en lenguaje Dart: Introducción, infraestructura de backend" .



En este artículo, escribiremos una aplicación utilizando el marco Aqueduct ., evaluar su rendimiento y consumo de recursos en diferentes modos, escribir un kit de herramientas para compilarlo en una aplicación nativa para Windows y Linux, ocuparse de las migraciones del esquema de la base de datos para las clases de aplicación de dominio e incluso publicar la imagen de la ventana acoplable de nuestra herramienta en el registro público de DockerHub.







Utilidad




Instalación de acueducto



Comencemos instalando dart-sdk, un kit de desarrollo de Dart. Puede instalarlo usando el administrador de paquetes de su sistema operativo como se sugiere aquí . Sin embargo, en el caso de Windows, no hay ningún administrador de paquetes instalado en su sistema de forma predeterminada. Por lo que sólo:



  • Descargue el archivo y descomprímalo en la unidad C:
  • , , , . . « »



  • Path . dart , , C:\dart-sdk\bin
  • , dart pub ( dart)



    dart --version






    pub -v




  • , ,
  • aqueduct CLI (command line interface)



    pub global activate aqueduct






    aqueduct




En teoría, también es posible instalar localmente un servidor de base de datos PostgreSQL . Sin embargo, Docker nos permitirá evitar esta necesidad y hacer que el entorno de desarrollo sea similar al tiempo de ejecución en el servidor.



Generación de aplicaciones



Entonces, abramos la carpeta de nuestro servidor en VsCode



code c:/docs/dart_server


Para aquellos que no han visto el primer y segundo artículo, el código fuente se puede clonar desde el repositorio de guthub :



git clone https://github.com/AndX2/dart_server.git
Creemos una plantilla de aplicación:



aqueduct create data_app






Familiaricémonos con el contenido de la plantilla del proyecto:



  • README.md : una nota que describe cómo trabajar con un proyecto de acueducto, ejecutar pruebas, generar documentación API, etc. Archivo de soporte.
  • pubspec.yaml — pub. , , , .





  • config.yaml config.src.yaml — . .
  • analysis_options.yaml — ( ). .
  • .travis.yml — (continuous Integration). .
  • pubspec.lock .packages — pub. — , , — ().
  • .dart_tool/package_config.json — , aqueduct CLI. .
  • bin/main.dart — (, ). ( ).





  • lib/channel.dartApplicationChannel — . Aqueduct CPU RAM. ( Dart isolate) () .



  • lib/data_app.dart — . (library) dart_app





  • test/ — . -, . Postman.


Configuración



La primera tarea a resolver es configurar la aplicación al inicio. Aqueduct tiene un mecanismo incorporado para extraer parámetros de los archivos de configuración, pero este mecanismo no es muy conveniente cuando se ejecuta en un contenedor Docker. Actuaremos de manera diferente:



  • Pasemos la lista de variables al sistema operativo del contenedor.
  • Al iniciar la aplicación dentro del contenedor, leemos las variables de entorno del sistema operativo y las usamos para la configuración inicial.
  • Creemos una ruta para ver todas las variables de entorno de la aplicación en ejecución a través de la red (esto será útil al ver el estado de la aplicación desde el panel de administración).


En la carpeta / lib , cree varias carpetas y el primer repositorio para acceder a las variables de entorno:







EnvironmentRepository en el constructor lee las variables de entorno del sistema operativo en forma de diccionario Map <String, String> y las guarda en la variable privada _env . Agreguemos un método para obtener todos los parámetros en forma de diccionario: lib / service / EnvironmentService - el componente lógico del acceso a los datos del EnvironmentRepository:















Inyección de dependencia



Aquí debe detenerse y lidiar con las dependencias de los componentes:



  • el controlador de red necesitará una instancia del servicio variable,
  • el servicio debe ser único para toda la aplicación,
  • para crear un servicio, primero debe crear una instancia del repositorio de variables.


Resolveremos estos problemas utilizando la biblioteca GetIt . Conecte el paquete requerido a pubspec.yaml : cree







una instancia del contenedor del inyector lib / di / di_container.dart y escriba un método con el registro del repositorio y el servicio: llame al método de inicialización del contenedor DI en el método de preparación de la aplicación:















Capa de red



lib / controller / ActuatorController : componente de red http. Contiene métodos para acceder a los datos de servicio de la aplicación: Declaremos manejadores de ruta para controladores en lib / controller / Routes :















Primer comienzo



Para correr necesitas:



  • empaquetar la aplicación en una imagen de Docker,
  • agregar contenedor al script docker-compose,
  • configurar NGINX para solicitudes de proxy.


Cree un Dockerfile en la carpeta de la aplicación . Este es el script para construir y ejecutar la imagen para Docker: Agregue el contenedor de la aplicación al script docker-compose.yaml : Cree el archivo data_app.env con variables de configuración para la aplicación: Agregue una nueva ubicación a la configuración de depuración de NGINX conf.dev.d / default.conf : Ejecute la depuración script con la bandera de pre-construcción:



































docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml up --build






El script se ejecutó correctamente, pero hay varios puntos alarmantes:



  • La imagen oficial de dardos de Google está archivada en 290 MB . Cuando se desempaqueta, ocupará mucho más espacio: 754 MB. Vea una lista de imágenes y sus tamaños:



    docker images
  • El tiempo de compilación y compilación JIT fue de más de 100 segundos. Demasiado para ejecutar una aplicación en oferta
  • Consumo de memoria en el panel de Docker 300 MB inmediatamente después del lanzamiento



  • En una prueba de carga (red GET / api / actuador / solicitudes solamente) el consumo de memoria está en el rango de 350-390 MB para una aplicación que se ejecuta en un solo aislamiento





Presumiblemente, los recursos de nuestro VPS económico no son suficientes para ejecutar una aplicación que consume tantos recursos. Vamos a revisar:



  • Cree una carpeta en el servidor para la nueva versión de la aplicación y copie el contenido del proyecto



    ssh root@dartservice.ru "mkdir -p /opt/srv_2" && scp -r ./* root@91.230.60.120:/opt/srv_2/
  • Ahora necesita transferir el proyecto de la página web de / opt / srv_1 / public / y todo el contenido de la carpeta / opt / srv_1 / sertbot / a esta carpeta (contiene certificados SSL para NGINX y Let's encrypt bot logs) y copiar la clave de / opt / srv_1 / dhparam /
  • Inicie el monitor de recursos del servidor en una consola separada



    htop


  • Ejecutemos el script docker-compose en la carpeta / opt / srv_2 /



    docker-compose up --build -d
  • Así es como se ve la compilación de la aplicación antes del lanzamiento:





  • Y así, en el trabajo: de







    1 GB de RAM disponible, nuestra aplicación consume 1,5 GB "ocupando" el archivo de paginación que falta. Sí, la aplicación ha comenzado, pero no estamos hablando de capacidad de carga.
  • Detengamos el guión:



    docker-compose down


AOT



Tenemos tres tareas por resolver:



  • reducir el consumo de RAM mediante la aplicación de dardos,
  • reducir el tiempo de inicio,
  • reducir el tamaño del contenedor de la ventana acoplable de la aplicación.


La solución es abandonar el dardo en tiempo de ejecución. Desde la versión 2.6, las aplicaciones de dardos admiten la compilación en código ejecutable nativo . Aqueduct admite la compilación a partir de la versión 4.0.0-b1.



Comencemos por eliminar la CLI del acueducto localmente:



pub global deactivate aqueduct


Instale la nueva versión:



pub global activate aqueduct 4.0.0-b1


Aumentemos las dependencias en pubspec.yaml: Construyamos la







aplicación nativa:



aqueduct build


El resultado será un ensamblaje de un solo archivo data_app.aot de aproximadamente 6 MB de tamaño. Puede iniciar inmediatamente esta aplicación con parámetros, por ejemplo:



data_app.aot --port 8080 --isolates 2


El consumo de memoria inmediatamente después del lanzamiento es inferior a 10 MB.



Veamos bajo carga. Parámetros de prueba: solicitudes GET / actuador de la red, 100 subprocesos con la velocidad máxima disponible, 10 minutos. Resultado:







Total: velocidad promedio - 13k solicitudes por segundo para un cuerpo de respuesta JSON de 1.4kV, tiempo de respuesta promedio - 7 ms, consumo de memoria (para dos instancias) 42 MB. No hay errores.



Cuando repetimos la prueba con seis instancias de la aplicación, la velocidad media, por supuesto, sube a 19k / seg, pero la utilización del procesador llega al 45% cuando el consumo de memoria es de 64 MB.

Este es un resultado excelente.



Embalaje de contenedores



Aquí enfrentaremos una dificultad más: solo podemos compilar una aplicación de dardos en un nativo para el sistema operativo actual. En mi caso, este es Windows10 x64. En un contenedor docker, por supuesto, preferiría una de las distribuciones de Linux, por ejemplo, Ubuntu 20.10.



La solución aquí será un docker-stand intermedio, utilizado solo para construir aplicaciones nativas para Ubuntu. Vamos a escribir es / dart2native / Dockerfile : Ahora vamos a construir una imagen ventana acoplable nombrado en aqueduct_builder: 4.0.0-b1 , sobrescribir las versiones antiguas, en su caso:











docker build --pull --rm -f "dart2native\Dockerfile" -t aqueduct_builder:4.0.0-b1 "dart2native"


Vamos a revisar:



docker images






Escribamos un script de compilación para la aplicación nativa docker-compose.dev.build.yaml : Ejecute el script de compilación:











docker-compose -f docker-compose.dev.build.yaml up






El archivo data_app.aot compilado para Ubuntu ya ocupa 9 MB. Al inicio, utiliza 19 MB de RAM (para dos instancias). Realicemos pruebas de carga local en las mismas condiciones, pero en un contenedor con proxy NGINX (GET, 100 subprocesos):







En promedio, 5.3k solicitudes por segundo. Al mismo tiempo, el consumo de RAM no superó los 55 MB. El tamaño de la imagen ha disminuido en comparación con el dardo y el acueducto instalados de 840 MB a 74 MB en el disco.



Escribamos un nuevo script docker-compose.aot.yaml para lanzar la aplicación. Para hacer esto, reemplazaremos el bloque de descripción data_app instalando la imagen base “vacía” de Ubuntu: 20.10. Montemos el archivo de ensamblaje y cambiemos el comando de inicio:







Resolvamos un problema de servicio más: de hecho, la imagen de construcción de la ventana acoplable con dardo y acueducto instalados es una herramienta bastante reutilizable. Tiene sentido cargarlo en un registro público y conectarlo como una imagen descargable lista para usar. Esto requiere:



  • registrarse en un registro público como DockerHub ,
  • iniciar sesión localmente con el mismo inicio de sesión



    docker login 
  • cambiar el nombre de la imagen cargada de acuerdo con el inicio de sesión / título: esquema de etiquetas



    docker image tag a365ac7f5bbb andx2/aqueduct:4.0.0-b1
  • descargar imagen en el registro



    docker push andx2/aqueduct:4.0.0-b1


    https://hub.docker.com/repository/docker/andx2/aqueduct/general



Ahora podemos modificar nuestro script de compilación para usar la imagen pública







Conexión a la base de datos



Aqueduct ya tiene un ORM incorporado para trabajar con una base de datos PostgreSQL. Para usarlo necesitas:



  • Cree objetos de dominio que describan registros en la base de datos.
  • . : , , . Aqueduct , , ManagedObject ( ), , . .
  • . , , .
  • , aqueduct, , seed() — - .
  • aqueduct CLI.


Comencemos conectando un nuevo contenedor docker a la base de datos PostgreSQL en el script docker-compose.aot.yaml. Imagen lista basada en Linux Alpine (versión "compacta" de Linux para aplicaciones integradas): aquí debe prestar atención al archivo de variable de entorno data_db.env . El hecho es que la imagen está preconfigurada para usar estas variables como nombre de usuario, host, puerto y contraseña de acceso. Agreguemos estas variables al archivo: Los valores se dan de forma condicional. También montaremos la carpeta de host ./data_db/ en un contenedor para almacenar datos de la base de datos. Luego, en la aplicación data_app, agregue la clase / service / DbHelper para conectarse a la base de datos usando variables de entorno:





























Creemos un objeto de dominio administrado por ORM para obtener la configuración de la aplicación cliente: Agregue un repositorio y un servicio para agregar configuraciones y obtenga la versión actual: Controlador de red: Registre nuevos componentes en el contenedor DI: Agregue un nuevo controlador y punto final al enrutador: Ahora generaremos un archivo de migración de base de datos. Ejecutemos:















































aqueduct db generate
El resultado será la creación de archivos de migración en la carpeta del proyecto: Ahora necesita resolver el problema del servicio: las migraciones deben aplicarse desde un sistema con acueducto (y dardo) instalado a una base de datos que se ejecuta en un contenedor, y esto debe hacerse tanto durante el desarrollo local como en el servidor. Para este caso, usaremos la imagen previamente construida y publicada para el ensamblaje AOT. Escribamos el correspondiente script de migración de base de datos docker-compose: Un detalle interesante es la cadena de conexión de la base de datos. Al ejecutar el script, puede pasar un archivo con variables de entorno como argumento y luego utilizar estas variables para la sustitución en el script:























docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env --compatibility up --abort-on-container-exit


Prestemos también atención a las banderas de lanzamiento:



  • --compatibility : compatibilidad con versiones de docker-compose de scripts 2.x. Esto permitirá que las opciones de implementación se utilicen para restringir el uso de recursos por parte del contenedor, que se ignoran en las versiones 3.x. Hemos limitado el consumo de RAM a 200 MB y el uso de CPU al 50%
  • --abort-on-container-exit : esta marca establece el modo de ejecución del script para que cuando uno de los contenedores del script se detenga, todos los demás terminen. Por lo tanto, cuando se ejecuta el comando para migrar el esquema de la base de datos y el contenedor con acueducto se detiene, docker-compose también terminará el contenedor de la base de datos.


Publicación



Para prepararse para la publicación de la aplicación, debe:



  • data_app.env data_db.env. , POSTGRES_PASSWORD=postgres_password
  • docker-compose.aot.yaml docker-compose.yaml.
  • /api/actuator. .


Copie la carpeta de la aplicación ./data_app/ al servidor . Un punto importante aquí será el interruptor -p (copiar conservando los atributos del archivo) en el comando de copia. Permítame recordarle que al construir la aplicación nativa, establecemos los derechos de ejecución en el archivo data_app.aot :



scp -rp ./data_app root@dartservice.ru:/opt/srv_1


Copiemos también:



  • Se modificó la configuración de NGINX ./conf.d/default.conf
  • Scripts de inicio y migración docker-compose.yaml , docker-compose.migrations.yaml
  • Archivos con variables de entorno data_app.env y data_db.env


Agregue la carpeta / opt / srv_1 / data_db en el servidor . Este es el volumen del sistema de archivos del host que se montará en el contenedor de la base de datos. Todos los datos de PostgreSQL se guardarán aquí.



mkdir /opt/srv_2/data_db


Ejecutemos el script para migrar el esquema de la base de datos:



docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env up --abort-on-container-exit


Ejecutemos el script de la aplicación:



docker-compose up -d


-> Código fuente github



En lugar de una conclusión



El marco para las aplicaciones backend está listo. En el próximo artículo, basándonos en él, escribiremos una nueva aplicación para autorizar a los usuarios del servicio. Para hacer esto, usaremos la especificación oAuth2 y la integraremos con VK y Github.



All Articles