{"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\/fr\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD dans Github Actions pour un projet Flask+Angular.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDans cet article, je vais partager mon exp\u00e9rience de configuration CI\/CD \u00e0 l'aide du panneau de contr\u00f4le Plesk et de Github Actions. Aujourd'hui, nous allons apprendre \u00e0 d\u00e9ployer un projet simple intitul\u00e9 \u00ab Helloworld \u00bb. Il est \u00e9crit sur le framework Python Flask, avec des workers sur Celery et un frontend sur Angular 8.<\/p>\n<p><b>Liens vers les d\u00e9p\u00f4ts :<\/b> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">backend<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<\/p>\n<p>Dans la premi\u00e8re partie de l'article, nous examinerons notre projet et ses composants. Dans la seconde, nous verrons comment configurer Plesk et installer les extensions et composants n\u00e9cessaires (BD, RabbitMQ, Redis, Docker, etc.).<\/p>\n<p>Dans la troisi\u00e8me partie, nous verrons enfin comment configurer le pipeline pour d\u00e9ployer notre projet sur le serveur dans les environnements dev et prod. Puis nous lancerons le site sur le serveur.<\/p>\n<p>Ah oui, j'ai oubli\u00e9 de me pr\u00e9senter. Je m'appelle Oleg Borzov, je suis d\u00e9veloppeur fullstack dans l'\u00e9quipe CRM pour les gestionnaires de pr\u00eats hypoth\u00e9caires chez Domclick. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Aper\u00e7u du projet<\/h1>\n<p>\nPour commencer, examinons les deux d\u00e9p\u00f4ts du projet \u2014 backend et frontend \u2014 et parcourons le code.<\/p>\n<h2>Partie backend : Flask+Celery<\/h2>\n<p>\nPour la partie backend, j'ai choisi une combinaison assez populaire parmi les d\u00e9veloppeurs Python : le framework Flask (pour l'API) et Celery (pour la file d'attente des t\u00e2ches). SQLAlchemy est utilis\u00e9 comme ORM. Alembic est utilis\u00e9 pour les migrations. Pour la validation JSON dans les handlers \u2014 Marshmallow.<\/p>\n<p>Dans <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">d\u00e9p\u00f4ts<\/a><\/noindex> il y a un fichier Readme.md avec une description d\u00e9taill\u00e9e de la structure et des instructions pour ex\u00e9cuter le projet.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API de la partie web<\/a><\/noindex> assez simple, compos\u00e9e de 6 handlers :<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 pour v\u00e9rifier la disponibilit\u00e9 ;<\/li>\n<li>handlers pour l'inscription, l'authentification, la d\u00e9sauthentification et l'obtention de l'utilisateur authentifi\u00e9 ;<\/li>\n<li>handler pour l'envoi d'email, qui place une t\u00e2che dans la file d'attente Celery.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Partie Celery<\/a><\/noindex> encore plus simple, il n'y a qu'une seule t\u00e2che <code>send_mail_task<\/code>.<\/p>\n<p>Dans le dossier <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> il y a deux sous-dossiers : <\/p>\n<ul>\n<li><code>docker<\/code> avec deux fichiers Docker (<code>base.dockerfile<\/code> pour construire une image de base peu modifi\u00e9e et <code>Dockerfile<\/code> pour les constructions principales) ;<\/li>\n<li><code>.env_files<\/code> \u2014 avec des fichiers de variables d'environnement pour diff\u00e9rents environnements. <\/li>\n<\/ul>\n<p>\n\u00c0 la racine du projet, il y a quatre fichiers docker-compose :<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> pour lever une BD locale pour le d\u00e9veloppement ;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> pour lever localement un worker, BD, Redis et RabbitMQ ;<\/li>\n<li><code>docker-compose.test.yml<\/code> pour ex\u00e9cuter des tests lors du d\u00e9ploiement ;<\/li>\n<li><code>docker-compose.yml<\/code> pour le d\u00e9ploiement.<\/li>\n<\/ul>\n<p>\nEt le dernier dossier int\u00e9ressant pour nous \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>. Il contient des scripts shell pour le d\u00e9ploiement :<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 lancement des migrations et du d\u00e9ploiement. Il est ex\u00e9cut\u00e9 sur le serveur apr\u00e8s la construction et l'ex\u00e9cution des tests dans Github Actions ;<\/li>\n<li><code>rollback.sh<\/code> \u2014 restauration des conteneurs \u00e0 la version pr\u00e9c\u00e9dente de la construction ;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 envoi de notifications de d\u00e9ploiement sur Telegram.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend sur Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">D\u00e9p\u00f4t avec le frontend<\/a><\/noindex> beaucoup plus simple que le backend. Le frontend se compose de trois pages :<\/p>\n<ul>\n<li>Page d'accueil avec un formulaire d'envoi d'email et un bouton de d\u00e9connexion.<\/li>\n<li>Page de connexion.<\/li>\n<li>Page d'inscription.<\/li>\n<\/ul>\n<p>\nLa page d'accueil a un design minimaliste :<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00c0 la racine se trouvent deux fichiers <code>Dockerfile<\/code> et <code>docker-compose.yml<\/code>, ainsi qu'un dossier familier <code>.ci-cd<\/code> avec un nombre de scripts l\u00e9g\u00e8rement inf\u00e9rieur \u00e0 celui du d\u00e9p\u00f4t backend (les scripts pour ex\u00e9cuter des tests ont \u00e9t\u00e9 supprim\u00e9s).<\/p>\n<h1>Cr\u00e9ons un projet dans Plesk<\/h1>\n<p>\nCommen\u00e7ons par la configuration de Plesk et la cr\u00e9ation d'un abonnement pour notre site.<\/p>\n<h2>Installation des extensions<\/h2>\n<p>\nDans Plesk, nous aurons besoin de quatre extensions : <\/p>\n<ul>\n<li><code>Docker<\/code> pour g\u00e9rer et afficher visuellement l'\u00e9tat des conteneurs dans l'interface d'administration de Plesk ;<\/li>\n<li><code>Git<\/code> pour configurer l'\u00e9tape de d\u00e9ploiement sur le serveur ;<\/li>\n<li><code>Let's Encrypt<\/code> pour g\u00e9n\u00e9rer (et renouveler automatiquement) des certificats TLS gratuits ;<\/li>\n<li><code>Pare-feu<\/code> pour configurer le filtrage du trafic entrant.<\/li>\n<\/ul>\n<p>\nNous pouvons les installer via l'interface d'administration de Plesk dans la section Extensions :<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNous ne traiterons pas la configuration d\u00e9taill\u00e9e des extensions, les param\u00e8tres par d\u00e9faut conviendront pour nos objectifs de d\u00e9monstration.<\/p>\n<h2>Cr\u00e9ation d'un abonnement et d'un site<\/h2>\n<p>\nEnsuite, nous devons cr\u00e9er un abonnement pour notre site helloworld.ru et ajouter le sous-domaine dev.helloworld.ru.<\/p>\n<ol>\n<li>Cr\u00e9ons un abonnement pour le domaine helloworld.ru et indiquons le nom d'utilisateur et le mot de passe pour l'utilisateur syst\u00e8me :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/3932dc7492aa523b6cbd540ad7a7fe6b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAu bas de la page, cocher la case <i>S\u00e9curisez le domaine avec Let's Encrypt<\/i>, si nous voulons configurer HTTPS pour le site :<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/f4d23a34764fdeb411fe2ca9cc95cb45.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Puis dans cet abonnement, cr\u00e9ons le sous-domaine dev.helloworld.ru (pour lequel un certificat TLS gratuit peut \u00e9galement \u00eatre \u00e9mis) :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/d57e6ba71454cb537917400de4275e92.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Installation des composants serveur<\/h1>\n<p>\nNous avons \u00e0 disposition un serveur avec <i>OS Debian Stretch 9.12<\/i> et le panneau de contr\u00f4le install\u00e9 <i>Plesk Obsidian 18.0.27<\/i>.<\/p>\n<p>Nous devons installer et configurer pour notre projet :<\/p>\n<ul>\n<li>PostgreSQL (dans notre cas, il y aura un serveur avec deux bases de donn\u00e9es pour les environnements dev et prod).<\/li>\n<li>RabbitMQ (m\u00eame chose, une instance avec diff\u00e9rents vhosts pour les environnements).<\/li>\n<li>Deux instances Redis (pour les environnements dev et prod).<\/li>\n<li>Docker Registry (pour le stockage local des images Docker g\u00e9n\u00e9r\u00e9es).<\/li>\n<li>Interface utilisateur pour Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nAvec Plesk, il y a d\u00e9j\u00e0 une base de donn\u00e9es PostgreSQL incluse, mais pas \u00e0 jour (au moment de la r\u00e9daction de cet article, Plesk Obsidian <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.plesk.com\/release-notes\/obsidian\/software-requirements\/\">supportait<\/a><\/noindex> les versions de Postgres 8.4 \u00e0 10.8). Nous souhaitons pour notre application la derni\u00e8re version (12.3 au moment de la r\u00e9daction de cet article), donc nous allons l'installer manuellement.<\/p>\n<p>Il existe de nombreuses instructions d\u00e9taill\u00e9es pour installer Postgres sur Debian en ligne (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">exemple<\/a><\/noindex>), c'est pourquoi je ne vais pas les d\u00e9crire en d\u00e9tail, je vais simplement donner les commandes :<\/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>\n\u00c9tant donn\u00e9 que PostgreSQL a des param\u00e8tres par d\u00e9faut plut\u00f4t m\u00e9diocres, il est essentiel de corriger la configuration. Pour cela, nous allons utiliser <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">calculateur<\/a><\/noindex>: il faut entrer les param\u00e8tres de votre serveur et remplacer les configurations dans le fichier <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>par celles propos\u00e9es. Il convient de pr\u00e9ciser que ces calculateurs ne sont pas une solution miracle, et la base doit \u00eatre ajust\u00e9e de mani\u00e8re plus pr\u00e9cise en fonction de votre mat\u00e9riel, de vos applications et de la complexit\u00e9 des requ\u00eates. Mais pour commencer, cela suffit.<\/p>\n<p>En plus des param\u00e8tres sugg\u00e9r\u00e9s par le calculateur, nous changeons dans <code>postgresql.conf<\/code>le port par d\u00e9faut 5432 \u00e0 un autre (dans notre exemple \u2014 <strong>53983<\/strong>).<\/p>\n<p>Apr\u00e8s avoir modifi\u00e9 le fichier de configuration, red\u00e9marrons le serveur postgresql avec la commande :<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nNous avons install\u00e9 et configur\u00e9 PostgreSQL. Maintenant, cr\u00e9ons une base de donn\u00e9es, des utilisateurs pour les environnements dev et prod, et attribuons aux utilisateurs des droits de gestion de la base de donn\u00e9es :<\/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>\nPassons \u00e0 l'installation de RabbitMQ \u2014 le courtier de messages pour Celery. Il s'installe assez facilement sur 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>\nApr\u00e8s l'installation, nous devons cr\u00e9er <i>vhosts<\/i>, des utilisateurs et leur attribuer les droits n\u00e9cessaires :<\/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>\nNous allons maintenant installer et configurer le dernier composant pour notre application \u2014 Redis. Il sera utilis\u00e9 comme backend pour stocker les r\u00e9sultats des t\u00e2ches Celery.<\/p>\n<p>Nous allons d\u00e9ployer deux conteneurs Docker avec Redis pour les environnements dev et prod \u00e0 l'aide de l'extension <code>Docker<\/code> pour Plesk.<\/p>\n<ol>\n<li>Acc\u00e9dez \u00e0 Plesk, allez dans la section Extensions, recherchez l'extension Docker et installez-la (nous avons besoin de la version gratuite) :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Allez dans l'extension install\u00e9e, recherchez l'image <code>redis bitnami<\/code> et installez la derni\u00e8re version :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Acc\u00e9dez au conteneur t\u00e9l\u00e9charg\u00e9 et modifiez la configuration : indiquez le port, la taille maximale de la RAM allou\u00e9e, le mot de passe dans les variables d'environnement, et montez le volume :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>R\u00e9p\u00e9tez les \u00e9tapes 2-3 pour le conteneur prod, en modifiant seulement les param\u00e8tres : port, mot de passe, taille de la RAM et chemin du dossier volume sur le serveur :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet 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>\nEn plus des services de base, il serait utile d'installer un d\u00e9p\u00f4t Docker priv\u00e9 sur le serveur. Heureusement, l'espace sur les serveurs est maintenant suffisamment bon march\u00e9 (certainement moins cher qu'un abonnement \u00e0 DockerHub), et le processus d'installation d'un d\u00e9p\u00f4t priv\u00e9 est tr\u00e8s simple. <\/p>\n<p>Nous souhaitons avoir install\u00e9 :<\/p>\n<ul>\n<li>un d\u00e9p\u00f4t Docker prot\u00e9g\u00e9 par mot de passe, accessible via un sous-domaine <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>une interface UI pour visualiser les images dans le d\u00e9p\u00f4t, accessible \u00e0 l'adresse <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nPour cela :<\/p>\n<ol>\n<li>Cr\u00e9ons dans Plesk deux sous-domaines dans notre abonnement : docker.helloworld.ru et docker-ui.helloworld.ru, et configurons des certificats Let\u2019s Encrypt pour eux.<\/li>\n<li>Dans le dossier du sous-domaine docker.helloworld.ru, ajoutons le fichier <code>docker-compose.yml<\/code> avec ce contenu :\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>Avec SSH, g\u00e9n\u00e9rez le fichier .htpasswd pour la Basic-authentification dans le d\u00e9p\u00f4t Docker :\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>Construisons et lan\u00e7ons les conteneurs :\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>Et il faut rediriger Nginx vers nos conteneurs. Cela peut se faire via Plesk. <\/li>\n<\/ol>\n<p>\nLes actions suivantes doivent \u00eatre effectu\u00e9es pour les sous-domaines docker.helloworld.ru et docker-ui.helloworld.ru :<\/p>\n<p>Dans la section <i>Dev Tools<\/i> de notre site, acc\u00e9dez \u00e0 <i>R\u00e8gles Proxy Docker<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEt ajoutez une r\u00e8gle pour le proxy du trafic entrant vers notre conteneur :<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>V\u00e9rifions que nous pouvons nous authentifier dans notre conteneur depuis la machine locale :\n<pre><code class=\"plaintext\">$ docker login docker.helloworld.ru -u hw_docker_admin -p hw_docker_password\nAVERTISSEMENT ! L'utilisation de --password via la CLI est peu s\u00e9curis\u00e9e. Utilisez --password-stdin.\nConnexion r\u00e9ussie<\/code><\/pre>\n<\/li>\n<li>V\u00e9rifions \u00e9galement le fonctionnement du sous-domaine docker-ui.helloworld.ru :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn cliquant sur Parcourir les d\u00e9p\u00f4ts, le navigateur affichera une fen\u00eatre d'authentification o\u00f9 vous devrez entrer votre nom d'utilisateur et votre mot de passe pour le d\u00e9p\u00f4t. Ensuite, nous serons redirig\u00e9s vers une page contenant la liste des d\u00e9p\u00f4ts (qui sera encore vide au d\u00e9but) :<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Ouvrir les ports dans le pare-feu Plesk<\/h2>\n<p>\nApr\u00e8s l'installation et la configuration des composants, nous devons ouvrir des ports pour que les composants soient accessibles \u00e0 partir des conteneurs Docker et du r\u00e9seau externe.<\/p>\n<p>Voyons comment proc\u00e9der, en prenant en exemple l'extension Firewall que nous avons install\u00e9e pr\u00e9c\u00e9demment pour Plesk.<\/p>\n<ol>\n<li>Passons \u00e0 <i>Outils &amp; Param\u00e8tres &gt; Param\u00e8tres &gt; Pare-feu<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Passons \u00e0 <i>Modifier les r\u00e8gles du pare-feu Plesk &gt; Ajouter une r\u00e8gle personnalis\u00e9e<\/i> et ouvrir les ports TCP suivants pour le sous-r\u00e9seau 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 dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Ajoutons \u00e9galement une r\u00e8gle pour ouvrir au monde ext\u00e9rieur les ports de PostgreSQL et du panneau de gestion de RabbitMQ :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Appliquez les r\u00e8gles \u00e0 l'aide du bouton Appliquer les modifications :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Configuration CI\/CD dans Github Actions<\/h1>\n<p>\nPassons \u00e0 la partie la plus int\u00e9ressante \u2014 la configuration du pipeline d'int\u00e9gration et de d\u00e9ploiement continus de notre projet sur le serveur.<\/p>\n<p>Ce pipeline sera compos\u00e9 de deux parties : <\/p>\n<ul>\n<li>construction de l'image et ex\u00e9cution des tests (pour le backend) \u2014 du c\u00f4t\u00e9 de Github ;<\/li>\n<li>ex\u00e9cution des migrations (pour le backend) et d\u00e9ploiement des conteneurs \u2014 sur le serveur.<\/li>\n<\/ul>\n<p><\/p>\n<h2>D\u00e9ploiement dans Plesk<\/h2>\n<p>\nCommen\u00e7ons par le second point (car le premier d\u00e9pend de lui).<\/p>\n<p>Nous allons configurer le processus de d\u00e9ploiement \u00e0 l'aide de l'extension Git pour Plesk.<\/p>\n<p>Prenons un exemple avec l'environnement Prod pour le d\u00e9p\u00f4t Backend.<\/p>\n<ol>\n<li>Acc\u00e9dez \u00e0 l'abonnement de notre site Helloworld et allez dans la section Git :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Collez dans le champ \u00ab D\u00e9p\u00f4t Git distant \u00bb le lien vers notre d\u00e9p\u00f4t Github et changez le dossier par d\u00e9faut <code>httpdocs<\/code> en un autre (par exemple, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Copiez la cl\u00e9 publique SSH de l'\u00e9tape pr\u00e9c\u00e9dente et <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">ajoutons<\/a><\/noindex> ajoutez-la dans les param\u00e8tres de Github.<\/li>\n<li>Cliquez sur OK \u00e0 l'\u00e9cran au point 2, puis vous serez redirig\u00e9 vers la page du d\u00e9p\u00f4t dans Plesk. Maintenant, nous devons configurer la mise \u00e0 jour du d\u00e9p\u00f4t lors des commits sur la branche master. Pour ce faire, allons dans <i>Param\u00e8tres du d\u00e9p\u00f4t<\/i> et sauvegardez la valeur <code>Webhook URL<\/code> (cela nous sera utile plus tard lors de la configuration de Github Actions) :\n<p><img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Dans le champ Actions \u00e0 l'\u00e9cran pr\u00e9c\u00e9dent, saisissez le script pour lancer le d\u00e9ploiement :\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>\no\u00f9 : <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 chemin vers le dossier prod du d\u00e9p\u00f4t backend sur le serveur ;<br \/>\n<code>{ENV}<\/code> \u2014 environnement (dev\/prod), dans notre cas <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 h\u00f4te de notre d\u00e9p\u00f4t docker<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 token du bot Telegram ;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 ID du chat\/canal pour l'envoi de notifications.<\/p>\n<p>Exemple 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>Nous ajoutons un utilisateur de notre abonnement au groupe Docker (pour qu'il puisse g\u00e9rer les conteneurs) :\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\nL'environnement de d\u00e9veloppement pour le d\u00e9p\u00f4t backend et frontend s'configure de la m\u00eame mani\u00e8re.<\/p>\n<h2>Pipeline de d\u00e9ploiement dans Github Actions<\/h2>\n<p>\nPassons \u00e0 la configuration de la premi\u00e8re partie de notre pipeline CI\/CD dans Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nLe pipeline est d\u00e9crit dans <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">le fichier deploy.yml<\/a><\/noindex>. <\/p>\n<p>Mais avant de l'examiner, remplissons dans Github les variables secr\u00e8tes n\u00e9cessaires. Pour cela, nous allons dans <i>Param\u00e8tres -&gt; Secrets<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 l'h\u00f4te de notre d\u00e9p\u00f4t Docker (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 identifiant pour le d\u00e9p\u00f4t Docker;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 mot de passe pour cela;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 l'h\u00f4te o\u00f9 l'interface Plesk est accessible (exemple : <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 ou <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 jeton pour d\u00e9ployer dans le d\u00e9p\u00f4t de production sur le serveur (nous l'avons obtenu dans le d\u00e9ploiement dans Plesk p. 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 jeton pour d\u00e9ployer dans le d\u00e9p\u00f4t de d\u00e9veloppement sur le serveur.<\/li>\n<\/ul>\n<p>\nLe processus de d\u00e9ploiement est simple et se compose de trois \u00e9tapes principales : <\/p>\n<ul>\n<li>construction et publication de l'image dans notre d\u00e9p\u00f4t ;<\/li>\n<li>ex\u00e9cution des tests dans le conteneur \u00e0 partir de l'image r\u00e9cemment construite ;<\/li>\n<li>d\u00e9ploiement dans l'environnement requis selon la branche (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\">Le fichier deploy.yml pour le d\u00e9p\u00f4t frontend<\/a><\/noindex> diff\u00e8re peu de celui du backend. Il n'y a pas d'\u00e9tape pour l'ex\u00e9cution des tests et les noms des jetons pour le d\u00e9ploiement changent. Les secrets pour le d\u00e9p\u00f4t frontend, d'ailleurs, doivent \u00eatre remplis s\u00e9par\u00e9ment.<\/p>\n<h1>Configuration du site<\/h1>\n<p><\/p>\n<h2>Proxy du trafic via Nginx<\/h2>\n<p>\nEh bien, nous sommes arriv\u00e9s \u00e0 la fin. Il ne reste plus qu'\u00e0 configurer le proxy du trafic entrant et sortant dans notre conteneur via Nginx. Ce processus a d\u00e9j\u00e0 \u00e9t\u00e9 abord\u00e9 au point 5 de la configuration du Docker Registry. La m\u00eame chose doit \u00eatre r\u00e9p\u00e9t\u00e9e pour les parties backend et frontend dans les environnements dev et prod.<\/p>\n<p>Je vais fournir des captures d'\u00e9cran des param\u00e8tres.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD dans Github Actions pour un projet 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 dans Github Actions pour un projet Flask+Angular.\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>Important \u00e0 pr\u00e9ciser<\/strong>. Dans le conteneur frontend, toutes les URL seront proxy\u00e9es, sauf celles qui commencent par <code>\/api\/<\/code> \u2014 elles seront proxy\u00e9es dans le conteneur backend (donc <strong>dans le conteneur backend, tous les gestionnaires doivent commencer par <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>R\u00e9sultats<\/h2>\n<p>\nMaintenant, notre site doit \u00eatre accessible aux adresses helloworld.ru et dev.helloworld.ru (environnements prod et dev respectivement).<\/p>\n<p>En r\u00e9sum\u00e9, nous avons appris comment pr\u00e9parer une simple application sur Flask et Angular et configurer un pipeline dans Github Actions pour son d\u00e9ploiement sur un serveur g\u00e9r\u00e9 par Plesk.<\/p>\n<p>Je vais redonner les liens vers les d\u00e9p\u00f4ts avec le code : <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">backend<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<br \/>\n<br \/>Source : <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\/fr\/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=\"fr_FR\" \/>\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\/fr\/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 avec Github Actions pour un projet Flask+Angular | ProHoster","description":"Dans cet article, je partagerai mon exp\u00e9rience.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}