{"id":85646,"date":"2020-06-18T01:42:15","date_gmt":"2020-06-17T23:42:15","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular"},"modified":"2020-06-18T01:42:15","modified_gmt":"2020-06-17T23:42:15","slug":"ci-cd-v-github-actions-dlya-proekta-na-flask-angular","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD en Github Actions para un proyecto en Flask+Angular","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn este art\u00edculo compartir\u00e9 mi experiencia configurando CI\/CD utilizando el panel de control Plesk y Github Actions. Hoy aprenderemos a desplegar un sencillo proyecto llamado \u00abHelloworld\u00bb. Est\u00e1 escrito en el framework de Python Flask, con trabajadores en Celery y un frontend en Angular 8.<\/p>\n<p><b>Enlaces a los repositorios:<\/b> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">para Talk, que es adecuado para realizar videoconferencias con entre 10 y 50 participantes en equipos convencionales.<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<\/p>\n<p>En la primera parte del art\u00edculo veremos nuestro proyecto y sus partes. En la segunda, entenderemos c\u00f3mo configurar Plesk e instalar las extensiones y componentes necesarios (BD, RabbitMQ, Redis, Docker, etc.).<\/p>\n<p>En la tercera parte, finalmente nos ocuparemos de c\u00f3mo configurar el pipeline para desplegar nuestro proyecto en el servidor en entornos dev y prod. Luego, lanzaremos el sitio en el servidor.<\/p>\n<p>Y s\u00ed, olvid\u00e9 presentarme. Me llamo Oleg Borzov, soy desarrollador fullstack en el equipo de CRM para gerentes de hipotecas en Domklik. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Visi\u00f3n general del proyecto<\/h1>\n<p>\nPara empezar, revisaremos dos repositorios del proyecto: el backend y el frontend, y echaremos un vistazo al c\u00f3digo.<\/p>\n<h2>Parte del backend: Flask+Celery<\/h2>\n<p>\nPara la parte posterior, utilic\u00e9 un stack bastante popular entre los desarrolladores de Python: el framework Flask (para APIs) y Celery (para la cola de tareas). Se utiliza SQLAlchemy como ORM. Para las migraciones, se aplica Alembic. Para la validaci\u00f3n de JSON en endpoints, se usa Marshmallow.<\/p>\n<p>En <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">el repositorio<\/a><\/noindex> hay un archivo Readme.md con una descripci\u00f3n detallada de la estructura e instrucciones para ejecutar el proyecto.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API de la parte web<\/a><\/noindex> es bastante sencilla, consta de 6 endpoints:<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 para verificar la disponibilidad;<\/li>\n<li>endpoints para registro, inicio de sesi\u00f3n, cierre de sesi\u00f3n y obtenci\u00f3n del usuario autenticado;<\/li>\n<li>un endpoint para enviar correos electr\u00f3nicos, que coloca la tarea en la cola de Celery.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Parte de Celery<\/a><\/noindex> es a\u00fan m\u00e1s simple, solo hay una tarea <code>send_mail_task<\/code>.<\/p>\n<p>En la carpeta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> hay dos subcarpetas: <\/p>\n<ul>\n<li><code>docker<\/code> con dos archivos Docker (<code>base.dockerfile<\/code> para construir una imagen base que cambia raramente y <code>Dockerfile<\/code> para las compilaciones principales);<\/li>\n<li><code>.env_files<\/code> \u2014 con archivos de variables de entorno para diferentes entornos. <\/li>\n<\/ul>\n<p>\nEn la ra\u00edz del proyecto hay cuatro archivos docker-compose:<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> para levantar una BD local para desarrollo;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> para levantar localmente un trabajador, BD, Redis y RabbitMQ;<\/li>\n<li><code>docker-compose.test.yml<\/code> para ejecutar pruebas durante el despliegue;<\/li>\n<li><code>docker-compose.yml<\/code> para el despliegue.<\/li>\n<\/ul>\n<p>\nY la \u00faltima carpeta que nos interesa es <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>. Contiene scripts shell para el despliegue:<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 ejecuci\u00f3n de migraciones y despliegue. Se ejecuta en el servidor despu\u00e9s de la compilaci\u00f3n y la ejecuci\u00f3n de pruebas en Github Actions;<\/li>\n<li><code>rollback.sh<\/code> \u2014 revertir contenedores a la versi\u00f3n anterior de la compilaci\u00f3n;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 enviar notificaciones sobre el despliegue a Telegram.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend en Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">Repositorio con el front-end<\/a><\/noindex> mucho m\u00e1s simple que el back-end. El front-end consta de tres p\u00e1ginas:<\/p>\n<ul>\n<li>P\u00e1gina principal con un formulario para enviar correos electr\u00f3nicos y un bot\u00f3n de salida.<\/li>\n<li>P\u00e1gina de acceso.<\/li>\n<li>P\u00e1gina de registro.<\/li>\n<\/ul>\n<p>\nLa p\u00e1gina principal tiene un dise\u00f1o minimalista:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn la ra\u00edz hay dos archivos <code>Dockerfile<\/code> y <code>docker-compose.yml<\/code>, as\u00ed como la carpeta familiar <code>.ci-cd<\/code> con un n\u00famero ligeramente menor de scripts que en el repositorio del back-end (se eliminaron los scripts para ejecutar pruebas).<\/p>\n<h1>Creamos el proyecto en Plesk<\/h1>\n<p>\nComencemos configurando Plesk y creando una suscripci\u00f3n para nuestro sitio.<\/p>\n<h2>Instalaci\u00f3n de extensiones<\/h2>\n<p>\nEn Plesk necesitaremos cuatro extensiones: <\/p>\n<ul>\n<li><code>Docker<\/code> para gestionar y visualizar el estado de los contenedores en el panel de administraci\u00f3n de Plesk;<\/li>\n<li><code>Git<\/code> para definir el paso de despliegue en el servidor;<\/li>\n<li><code>Let's Encrypt<\/code> para generar (y auto-renovar) certificados TLS gratuitos;<\/li>\n<li><code>Firewall<\/code> para configurar la filtraci\u00f3n del tr\u00e1fico entrante.<\/li>\n<\/ul>\n<p>\nSe pueden instalar a trav\u00e9s del panel de administraci\u00f3n de Plesk en la secci\u00f3n de Extensiones:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNo vamos a considerar la configuraci\u00f3n detallada de las extensiones, las configuraciones por defecto son suficientes para nuestros prop\u00f3sitos de demostraci\u00f3n.<\/p>\n<h2>Creaci\u00f3n de la suscripci\u00f3n y el sitio<\/h2>\n<p>\nA continuaci\u00f3n necesitamos crear una suscripci\u00f3n para nuestro sitio helloworld.ru y agregar el subdominio dev.helloworld.ru.<\/p>\n<ol>\n<li>Creamos la suscripci\u00f3n para el dominio helloworld.ru y especificamos el nombre de usuario y la contrase\u00f1a para el usuario del sistema:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3932dc7492aa523b6cbd540ad7a7fe6b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn la parte inferior de la p\u00e1gina marcamos la casilla <i>Asegure el dominio con Let's Encrypt<\/i>, si queremos configurar HTTPS para el sitio:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f4d23a34764fdeb411fe2ca9cc95cb45.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Luego, en esta suscripci\u00f3n, creamos el subdominio dev.helloworld.ru (para el cual tambi\u00e9n se puede emitir un certificado TLS gratuito):\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d57e6ba71454cb537917400de4275e92.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Instalaci\u00f3n de componentes del servidor<\/h1>\n<p>\nDisponemos de un servidor con <i>OS Debian Stretch 9.12<\/i> y el panel de control instalado <i>Plesk Obsidian 18.0.27<\/i>.<\/p>\n<p>Necesitamos instalar y configurar para nuestro proyecto:<\/p>\n<ul>\n<li>PostgreSQL (en nuestro caso ser\u00e1 un servidor con dos bases de datos para el entorno dev y prod).<\/li>\n<li>RabbitMQ (lo mismo, una instancia con diferentes vhosts para los entornos).<\/li>\n<li>Dos instancias de Redis (para los entornos dev y prod).<\/li>\n<li>Docker Registry (para el almacenamiento local de im\u00e1genes Docker compiladas).<\/li>\n<li>Interfaz de usuario para el Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nEl paquete de Plesk ya incluye la base de datos PostgreSQL, aunque no de la versi\u00f3n m\u00e1s reciente (en el momento de redactar este art\u00edculo, Plesk Obsidian <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.plesk.com\/release-notes\/obsidian\/software-requirements\/\">soportaba<\/a><\/noindex> Postgres versiones 8.4\u201310.8). Queremos la versi\u00f3n m\u00e1s reciente para nuestra aplicaci\u00f3n (12.3 en el momento de escribir este art\u00edculo), por lo que la instalaremos manualmente.<\/p>\n<p>Hay muchas instrucciones detalladas sobre c\u00f3mo instalar Postgres en Debian en la red (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">ejemplo<\/a><\/noindex>), as\u00ed que no las describir\u00e9 en detalle, solo proporcionar\u00e9 los comandos:<\/p>\n<pre><code class=\"bash\">wget -q https:\/\/www.postgresql.org\/media\/keys\/ACCC4CF8.asc -O - | sudo apt-key add -\nsudo sh -c 'echo \"deb http:\/\/apt.postgresql.org\/pub\/repos\/apt\/ stretch-pgdg main\" &gt;&gt; \/etc\/apt\/sources.list.d\/pgdg.list'\n\nsudo apt-get update\nsudo apt-get install postgresql postgresql-contrib\n<\/code><\/pre>\n<p>\nDado que PostgreSQL tiene configuraciones predeterminadas bastante mediocres, es esencial ajustar la configuraci\u00f3n. Para ello, nos ayudar\u00e1 <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">calculador<\/a><\/noindex>: debemos introducir los par\u00e1metros de nuestro servidor y reemplazar la configuraci\u00f3n en el archivo <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>por los sugeridos. Cabe aclarar que tales calculadoras no son una soluci\u00f3n m\u00e1gica, y la base de datos debe ser ajustada m\u00e1s cuidadosamente, en funci\u00f3n de su hardware, aplicaci\u00f3n y complejidad de las consultas. Pero esto es suficiente para empezar.<\/p>\n<p>Adem\u00e1s de la configuraci\u00f3n sugerida por la calculadora, tambi\u00e9n cambiamos en <code>postgresql.conf<\/code>el puerto predeterminado 5432 por otro (en nuestro ejemplo \u2014 <strong>53983<\/strong>).<\/p>\n<p>Despu\u00e9s de modificar el archivo de configuraci\u00f3n, reiniciamos el servidor de postgresql con el comando:<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nHemos instalado y configurado PostgreSQL. Ahora crearemos las bases de datos, usuarios para los entornos dev y prod, y otorgaremos a los usuarios los permisos para gestionar las bases de datos:<\/p>\n<pre><code class=\"bash\">$ su - postgres\npostgres:~$ create database hw_dev_db_name;\nCREATE DATABASE\npostgres:~$ create user hw_dev_db_user with password 'hw_dev_db_password';\nCREATE ROLE\npostgres:~$ grant ALL privileges ON database hw_dev_db_name to hw_dev_db_user;\nGRANT\npostgres:~$ create database hw_prod_db_name;\nCREATE DATABASE\npostgres:~$ create user hw_prod_db_user with password 'hw_prod_db_password';\nCREATE ROLE\npostgres:~$ grant ALL privileges ON database hw_prod_db_name to hw_prod_db_user;\nGRANT\n<\/code><\/pre>\n<p><\/p>\n<h2>RabbitMQ<\/h2>\n<p>\nPasemos a la instalaci\u00f3n de RabbitMQ, el intermediario de mensajes para Celery. Se instala de manera bastante sencilla en Debian:<\/p>\n<pre><code class=\"bash\">wget https:\/\/packages.erlang-solutions.com\/erlang-solutions_1.0_all.deb\nsudo dpkg -i erlang-solutions_1.0_all.deb\n\nsudo apt-get update\nsudo apt-get install erlang erlang-nox\n\nsudo add-apt-repository 'deb http:\/\/www.rabbitmq.com\/debian\/ testing main'\nwget -O- https:\/\/www.rabbitmq.com\/rabbitmq-release-signing-key.asc | sudo apt-key add -\n\nsudo apt-get update\nsudo apt-get install rabbitmq-server\n<\/code><\/pre>\n<p>\nDespu\u00e9s de la instalaci\u00f3n, tenemos que crear <i>vhosts<\/i>, usuarios y otorgar los permisos necesarios:<\/p>\n<pre><code class=\"bash\">sudo rabbitmqctl add_user hw_dev_amqp_user hw_dev_amqp_password \nsudo rabbitmqctl set_user_tags hw_dev_amqp_user administrator\nsudo rabbitmqctl add_vhost hw_dev_vhost\nsudo rabbitmqctl set_permissions -p hw_dev_vhost hw_dev_amqp_user \" .*\" \" .*\" \" .*\"\n\nsudo rabbitmqctl add_user hw_prod_amqp_user hw_prod_amqp_password \nsudo rabbitmqctl set_user_tags hw_prod_amqp_user administrator\nsudo rabbitmqctl add_vhost hw_prod_vhost\nsudo rabbitmqctl set_permissions -p hw_prod_vhost hw_prod_amqp_user \" .*\" \" .*\" \" .*\"\n<\/code><\/pre>\n<p><\/p>\n<h2>Redis<\/h2>\n<p>\nAhora vamos a instalar y configurar el \u00faltimo componente para nuestra aplicaci\u00f3n: Redis. Se utilizar\u00e1 como backend para almacenar los resultados de las tareas de Celery.<\/p>\n<p>Levantaremos dos contenedores de Docker con Redis para los entornos dev y prod utilizando la extensi\u00f3n <code>Docker<\/code> para Plesk.<\/p>\n<ol>\n<li>Entramos en Plesk, vamos a la secci\u00f3n de Extensiones, buscamos la extensi\u00f3n Docker y la instalamos (necesitamos la versi\u00f3n gratuita):\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Vamos a la extensi\u00f3n instalada, mediante la b\u00fasqueda encontramos la imagen <code>redis bitnami<\/code> y ponemos la \u00faltima versi\u00f3n:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Entramos en el contenedor descargado y ajustamos la configuraci\u00f3n: especificamos el puerto, el tama\u00f1o m\u00e1ximo de RAM asignada, la contrase\u00f1a en las variables de entorno, y montamos el volumen:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Realizamos los pasos 2-3 para el contenedor prod, en la configuraci\u00f3n solo cambiamos los par\u00e1metros: puerto, contrase\u00f1a, tama\u00f1o de RAM y la ruta a la carpeta de volumen en el servidor:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3af50181c8b3e1ace23a8de11826cb51.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Docker Registry<\/h2>\n<p>\nAdem\u00e1s de los servicios b\u00e1sicos, ser\u00eda conveniente instalar un repositorio Docker propio en el servidor. Afortunadamente, el espacio en los servidores ahora es bastante barato (definitivamente m\u00e1s barato que una suscripci\u00f3n a DockerHub) y el proceso de instalaci\u00f3n de un repositorio privado es muy simple. <\/p>\n<p>Queremos tener instalados:<\/p>\n<ul>\n<li>un repositorio Docker protegido por contrase\u00f1a, accesible a trav\u00e9s de un subdominio <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>Una interfaz de usuario para ver las im\u00e1genes en el repositorio, accesible en la direcci\u00f3n <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nPara ello:<\/p>\n<ol>\n<li>Crearemos en Plesk dos subdominios en nuestra suscripci\u00f3n: docker.helloworld.ru y docker-ui.helloworld.ru, y configuraremos certificados de Let\u2019s Encrypt para ellos.<\/li>\n<li>En la carpeta del subdominio docker.helloworld.ru, a\u00f1adiremos el archivo <code>docker-compose.yml<\/code> con este contenido:\n<pre><code class=\"plaintext\">version: \"3\"\n\nservices:\n  docker-registry:\n    image: \"registry:2\"\n    restart: always\n    ports:\n      - \"53985:5000\"\n    environment:\n      REGISTRY_AUTH: htpasswd\n      REGISTRY_AUTH_HTPASSWD_REALM: basic-realm\n      REGISTRY_AUTH_HTPASSWD_PATH: \/auth\/.htpasswd\n      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: \/data\n    volumes:\n      - .\/.docker-registry.htpasswd:\/auth\/.htpasswd\n      - .\/data:\/data\n\n  docker-registry-ui:\n    image: konradkleine\/docker-registry-frontend:v2\n    restart: always\n    ports:\n      - \"53986:80\"\n    environment:\n      VIRTUAL_HOST: '*, https:\/\/*'\n      ENV_DOCKER_REGISTRY_HOST: 'docker-registry'\n      ENV_DOCKER_REGISTRY_PORT: 5000\n    links:\n      - 'docker-registry'\n<\/code><\/pre>\n<p>\n<\/li>\n<li>Por SSH, generaremos el archivo .htpasswd para la autorizaci\u00f3n b\u00e1sica en el repositorio Docker:\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>Compilaremos y levantaremos los contenedores:\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>Y necesitamos redirigir Nginx a nuestros contenedores. Esto se puede hacer a trav\u00e9s de Plesk. <\/li>\n<\/ol>\n<p>\nLas siguientes acciones se deben realizar para los subdominios docker.helloworld.ru y docker-ui.helloworld.ru:<\/p>\n<p>En la secci\u00f3n <i>Dev Tools<\/i> en nuestro sitio, entramos en <i>Reglas de Proxy de Docker<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nY a\u00f1adimos una regla para el proxy del tr\u00e1fico entrante a nuestro contenedor:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>Verificamos que podemos autorizar en nuestro contenedor desde la m\u00e1quina local:\n<pre><code class=\"plaintext\">$ docker login docker.helloworld.ru -u hw_docker_admin -p hw_docker_password\nWARNING! Using --password via the CLI is insecure. Use --password-stdin.\nLogin Succeeded<\/code><\/pre>\n<\/li>\n<li>Tambi\u00e9n verificaremos el funcionamiento del subdominio docker-ui.helloworld.ru:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAl hacer clic en Browse repositories, el navegador mostrar\u00e1 una ventana de autorizaci\u00f3n donde deber\u00e1s ingresar el usuario y la contrase\u00f1a para el repositorio. Despu\u00e9s de eso, ser\u00e1s redirigido a una p\u00e1gina con la lista de repositorios (en este momento estar\u00e1 vac\u00eda):<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Abrir puertos en el Firewall de Plesk<\/h2>\n<p>\nDespu\u00e9s de instalar y configurar los componentes, necesitamos abrir puertos para que los componentes sean accesibles desde contenedores Docker y la red externa.<\/p>\n<p>Veamos c\u00f3mo hacer esto con el ejemplo de la extensi\u00f3n Firewall que instalamos previamente para Plesk.<\/p>\n<ol>\n<li>Vamos a <i>Herramientas y Configuraci\u00f3n &gt; Configuraci\u00f3n &gt; Firewall<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Vamos a <i>Modificar Reglas del Firewall de Plesk &gt; Agregar Regla Personalizada<\/i> y abrimos los siguientes puertos TCP para la subred de Docker (<strong>172.0.0.0\/8)<\/strong>:<br \/>\nRabbitMQ: 1883, 4369, 5671-5672, 25672, 61613-61614<br \/>\nRedis: 32785, 32786<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Tambi\u00e9n agregaremos una regla que abrir\u00e1 al mundo exterior los puertos de PostgreSQL y el panel de gesti\u00f3n de RabbitMQ:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Aplicamos las reglas utilizando el bot\u00f3n Aplicar Cambios:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Configuraci\u00f3n de CI\/CD en Github Actions<\/h1>\n<p>\nPasemos a la parte m\u00e1s interesante: configurar la pipeline de integraci\u00f3n y entrega continua de nuestro proyecto en el servidor.<\/p>\n<p>Esta pipeline constar\u00e1 de dos partes: <\/p>\n<ul>\n<li>construcci\u00f3n de la imagen y ejecuci\u00f3n de pruebas (para el backend) \u2014 del lado de Github;<\/li>\n<li>ejecuci\u00f3n de migraciones (para el backend) y despliegue de contenedores \u2014 en el servidor.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Despliegue en Plesk<\/h2>\n<p>\nPrimero, ocup\u00e9monos del segundo punto (ya que depende del primero).<\/p>\n<p>Configurar el proceso de despliegue lo haremos a trav\u00e9s de la extensi\u00f3n Git para Plesk.<\/p>\n<p>Veamos un ejemplo con el entorno de producci\u00f3n para el repositorio Backend.<\/p>\n<ol>\n<li>Entramos en la suscripci\u00f3n de nuestro sitio Helloworld y vamos a la subsecci\u00f3n Git:\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Pegamos en el campo \u2018Remote Git repository\u2019 el enlace a nuestro repositorio de Github y cambiamos la carpeta por defecto <code>httpdocs<\/code> a otra (por ejemplo, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Copiamos la clave p\u00fablica SSH de la etapa anterior y <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">agregamos<\/a><\/noindex> la pegamos en la configuraci\u00f3n de Github.<\/li>\n<li>Presionamos OK en la pantalla en el punto 2, despu\u00e9s de lo cual seremos redirigidos a la p\u00e1gina del repositorio en Plesk. Ahora necesitamos configurar la actualizaci\u00f3n del repositorio al hacer commits en la rama master. Para ello, vamos a <i>Configuraci\u00f3n del Repositorio<\/i> y guardamos el valor <code>Webhook URL<\/code> (lo necesitaremos m\u00e1s tarde al configurar Github Actions):\n<p><img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>En el campo Actions de la pantalla del punto anterior, ingresamos el script para iniciar el despliegue:\n<pre><code class=\"plaintext\">cd {REPOSITORY_ABSOLUTE_PATH}\n.ci-cd\/deploy.sh {ENV} {DOCKER_REGISTRY_HOST} {DOCKER_USER} {DOCKER_PASSWORD} {TG_BOT_TOKEN} {TG_CHAT_ID} <\/code><\/pre>\n<p>\ndonde: <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 ruta a la carpeta del repositorio backend de producci\u00f3n en el servidor;<br \/>\n<code>{ENV}<\/code> \u2014 entorno (dev\/prod), en nuestro caso <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 host de nuestro repositorio Docker<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 token del bot de Telegram;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 ID del chat\/canal para enviar notificaciones.<\/p>\n<p>Ejemplo de script:<\/p>\n<pre><code class=\"plaintext\">cd \/var\/www\/vhosts\/helloworld.ru\/httpdocs\/hw_back\/\n.ci-cd\/deploy.sh dev docker.helloworld.ru docker_user docker_password 12345678:AAbcdEfghCH1vGbCasdfSAs0K5PALDsaw -1001234567890<\/code><\/pre>\n<\/li>\n<li>A\u00f1adimos un usuario de nuestra suscripci\u00f3n al grupo Docker (para que pueda gestionar contenedores):\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\nEl entorno de desarrollo para el repositorio backend y frontend se configura de manera similar.<\/p>\n<h2>Pipeline de despliegue en Github Actions<\/h2>\n<p>\nPasamos a la configuraci\u00f3n de la primera parte de nuestro pipeline CI\/CD en Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nEl pipeline se describe en <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">el archivo deploy.yml<\/a><\/noindex>. <\/p>\n<p>Pero antes de analizarlo, llenaremos las variables Secret necesarias en Github. Para ello, nos dirigimos a <i>Configuraci\u00f3n -&gt; Secretos<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 el host de nuestro repositorio Docker (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 usuario del repositorio Docker;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 contrase\u00f1a para acceder;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 host donde est\u00e1 disponible el panel de Plesk (por ejemplo: <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 o <noindex><a rel=\"nofollow\" href=\"http:\/\/123.4.56.78\">123.4.56.78<\/a><\/noindex>:8443);<\/li>\n<li><code>DEPLOY_BACK_PROD_TOKEN<\/code> \u2014 token para el despliegue en el repositorio prod en el servidor (lo obtuvimos en Despliegue en Plesk punto 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 token para el despliegue en el repositorio dev en el servidor.<\/li>\n<\/ul>\n<p>\nEl proceso de despliegue es simple y consta de tres pasos principales: <\/p>\n<ul>\n<li>compilaci\u00f3n y publicaci\u00f3n de la imagen en nuestro repositorio;<\/li>\n<li>ejecuci\u00f3n de pruebas en el contenedor basado en la imagen reci\u00e9n creada;<\/li>\n<li>despliegue en el entorno adecuado seg\u00fan la rama (dev\/master).<\/li>\n<\/ul>\n<p><\/p>\n<h3>Frontend<\/h3>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\/blob\/master\/.github\/workflows\/deploy.yml\">El archivo deploy.yml para el repositorio front<\/a><\/noindex> no difiere mucho del de backend. Falta el paso de ejecuci\u00f3n de pruebas y cambian los nombres de los tokens para el despliegue. Los secretos para el repositorio front, por cierto, deben llenarse por separado.<\/p>\n<h1>Configuraci\u00f3n del sitio<\/h1>\n<p><\/p>\n<h2>Proxy de tr\u00e1fico a trav\u00e9s de Nginx<\/h2>\n<p>\nBueno, hemos llegado al final. Solo queda configurar el proxy del tr\u00e1fico entrante y saliente a nuestro contenedor a trav\u00e9s de Nginx. Este proceso ya lo hemos tratado en el punto 5 de la configuraci\u00f3n del Docker Registry. Lo mismo debe repetirse para las partes de backend y frontend en los entornos dev y prod.<\/p>\n<p>Voy a mostrar capturas de pantalla de la configuraci\u00f3n.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f3c38dc1c25b648db000c72ef2d30b2a.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h3>Frontend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD en Github Actions para un proyecto en Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>Aclaraci\u00f3n importante<\/strong>. En el contenedor frontend se proxyar\u00e1n todas las URL, excepto las que comienzan con <code>\/api\/<\/code> \u2014 se proxyar\u00e1n al contenedor backend (por lo que <strong>en el contenedor backend todos los manejadores deben comenzar con <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>Resultados<\/h2>\n<p>\nAhora nuestro sitio deber\u00eda estar disponible en las direcciones helloworld.ru y dev.helloworld.ru (entorno prod y dev respectivamente).<\/p>\n<p>En resumen, aprendimos c\u00f3mo preparar una aplicaci\u00f3n simple en Flask y Angular y configurar en Github Actions un pipeline para su despliegue en un servidor gestionado por Plesk.<\/p>\n<p>Reiterar\u00e9 los enlaces a los repositorios con el c\u00f3digo: <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">para Talk, que es adecuado para realizar videoconferencias con entre 10 y 50 participantes en equipos convencionales.<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<br \/>\n<br \/>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/domclick\/blog\/506802\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 CI\/CD \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f Plesk \u0438 Github Actions. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u0443\u0434\u0435\u043c \u0443\u0447\u0438\u0442\u044c\u0441\u044f \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043d\u0435\u0437\u0430\u043c\u044b\u0441\u043b\u043e\u0432\u0430\u0442\u044b\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u00abHelloworld\u00bb. \u041e\u043d \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Python-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 Flask, \u0441 \u0432\u043e\u0440\u043a\u0435\u0440\u0430\u043c\u0438 \u043d\u0430 Celery \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u043e\u043c \u043d\u0430 Angular 8. \u0421\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438: \u0431\u044d\u043a\u0435\u043d\u0434, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u043d\u0430\u0448 \u043f\u0440\u043e\u0435\u043a\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":85647,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-85646","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47CI\/CD \u0432 Github Actions \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 Flask+Angular | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-06-17T23:42:15+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-06-17T23:42:15+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47CI\/CD en Github Actions para un proyecto en Flask+Angular | ProHoster","description":"En este art\u00edculo compartir\u00e9 mi experiencia.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47CI\/CD \u0432 Github Actions \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 Flask+Angular | ProHoster","og:description":"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-06-17T23:42:15+00:00","article:modified_time":"2020-06-17T23:42:15+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"85646","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 13:12:50","updated":"2022-09-29 06:56:26","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}