{"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\/ro\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00cen acest articol, voi \u00eemp\u0103rt\u0103\u0219i experien\u021ba mea de configurare a CI\/CD folosind panoul de control Plesk \u0219i Github Actions. Ast\u0103zi vom \u00eenv\u0103\u021ba s\u0103 desf\u0103\u0219ur\u0103m un proiect simplu cu un nume nepreten\u021bios, \u201eHelloworld\u201d. Este scris pe cadrul Python Flask, cu lucr\u0103tori pe Celery \u0219i frontend pe Angular 8.<\/p>\n<p><b>Linkuri c\u0103tre repozitoriile:<\/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>\u00cen prima parte a articolului, ne vom uita la proiectul nostru \u0219i la componentele acestuia. \u00cen a doua parte, vom analiza cum s\u0103 configur\u0103m Plesk \u0219i s\u0103 instal\u0103m extensiile \u0219i componentele necesare (Baza de date, RabbitMQ, Redis, Docker etc.).<\/p>\n<p>\u00cen a treia parte, finalmente, vom \u00eenv\u0103\u021ba cum s\u0103 configur\u0103m un pipeline pentru desf\u0103\u0219urarea proiectului nostru pe server \u00een mediile dev \u0219i prod. Apoi, vom lansa site-ul pe server.<\/p>\n<p>\u0218i da, am uitat s\u0103 m\u0103 prezint. M\u0103 numesc Oleg Borzov, sunt dezvoltator fullstack \u00een echipa CRM pentru managerii de credite ipotecare la Domklik. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Prezentarea proiectului<\/h1>\n<p>\nLa \u00eenceput, s\u0103 examin\u0103m cele dou\u0103 repozitorii ale proiectului \u2014 backend-ul \u0219i frontend-ul \u2014 \u0219i s\u0103 trecem \u00een revist\u0103 codul.<\/p>\n<h2>Partea de backend: Flask+Celery<\/h2>\n<p>\nPentru partea de backend am ales o combina\u021bie destul de popular\u0103 \u00een r\u00e2ndul dezvoltatorilor Python: framework-ul Flask (pentru API) \u0219i Celery (pentru coada de sarcini). Ca ORM, se folose\u0219te SQLAchemy. Pentru migra\u021bii se utilizeaz\u0103 Alembic. Pentru validarea JSON \u00een endpoint-uri \u2014 Marshmallow.<\/p>\n<p>\u00cen <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">repository<\/a><\/noindex> exist\u0103 un fi\u0219ier Readme.md cu descriere detaliat\u0103 a structurii \u0219i instruc\u021biuni pentru lansarea proiectului.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API-ul p\u0103r\u021bii web<\/a><\/noindex> este destul de simplu, const\u00e2nd \u00een 6 endpoint-uri:<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 pentru verificarea disponibilit\u0103\u021bii;<\/li>\n<li>endpoint-uri pentru \u00eenregistrare, autentificare, deautentificare \u0219i ob\u021binerea utilizatorului autorizat;<\/li>\n<li>un endpoint pentru trimiterea email-ului, care adaug\u0103 o sarcin\u0103 \u00een coada Celery.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Partea Celery<\/a><\/noindex> este \u0219i mai simpl\u0103, acolo exist\u0103 doar o sarcin\u0103 <code>send_mail_task<\/code>.<\/p>\n<p>\u00cen folderul <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> exist\u0103 dou\u0103 subfoldere: <\/p>\n<ul>\n<li><code>docker<\/code> cu dou\u0103 fi\u0219iere Docker (<code>base.dockerfile<\/code> pentru construirea unei imagini de baz\u0103 care se schimb\u0103 rar \u0219i <code>Dockerfile<\/code> pentru construc\u021biile principale);<\/li>\n<li><code>.env_files<\/code> \u2014 cu fi\u0219iere care con\u021bin variabile de mediu pentru diferite medii. <\/li>\n<\/ul>\n<p>\n\u00cen r\u0103d\u0103cina proiectului se afl\u0103 patru fi\u0219iere docker-compose:<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> pentru a ridica o Baz\u0103 de date local\u0103 pentru dezvoltare;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> pentru ridicarea local\u0103 a unui lucr\u0103tor, a Bazei de date, Redis \u0219i RabbitMQ;<\/li>\n<li><code>docker-compose.test.yml<\/code> pentru rularea testelor la desf\u0103\u0219urare;<\/li>\n<li><code>docker-compose.yml<\/code> pentru desf\u0103\u0219urare.<\/li>\n<\/ul>\n<p>\n\u0218i ultimul folder interesant pentru noi \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>. \u00cen el se afl\u0103 scripturi shell pentru desf\u0103\u0219urare:<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 rularea migra\u021biilor \u0219i a desf\u0103\u0219ur\u0103rii. Se lanseaz\u0103 pe server dup\u0103 construc\u021bie \u0219i rularea testelor \u00een Github Actions;<\/li>\n<li><code>rollback.sh<\/code> \u2014 revenirea containerelor la versiunea anterioar\u0103 a construc\u021biei;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 trimiterea de notific\u0103ri despre desf\u0103\u0219urare \u00een Telegram.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend-ul pe Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">Repository with the front end<\/a><\/noindex> is significantly simpler than the back end. The front consists of three pages:<\/p>\n<ul>\n<li>Home page with an email submission form and a logout button.<\/li>\n<li>Login page.<\/li>\n<li>Registration page.<\/li>\n<\/ul>\n<p>\nThe home page looks minimalist:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThere are two files at the root <code>Dockerfile<\/code> \u0219i <code>docker-compose.yml<\/code>, as well as a familiar folder <code>.ci-cd<\/code> with slightly fewer scripts than in the back end repository (scripts for running tests have been removed).<\/p>\n<h1>Starting a project in Plesk<\/h1>\n<p>\nLet's begin with configuring Plesk and creating a subscription for our site.<\/p>\n<h2>Installing extensions<\/h2>\n<p>\nIn Plesk, we will need four extensions: <\/p>\n<ul>\n<li><code>Docker<\/code> for managing and visually displaying the state of containers in the Plesk admin panel;<\/li>\n<li><code>Git<\/code> for configuring the deployment step on the server;<\/li>\n<li><code>Let's Encrypt<\/code> for generating (and auto-renewing) free TLS certificates;<\/li>\n<li><code>Firewall<\/code> for setting up incoming traffic filtering.<\/li>\n<\/ul>\n<p>\nThey can be installed through the Plesk admin panel in the Extensions section:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWe will not consider the detailed configuration of the extensions, the default settings will suffice for our demo purposes.<\/p>\n<h2>Creating a subscription and site<\/h2>\n<p>\nNext, we need to create a subscription for our site helloworld.ru and add the subdomain dev.helloworld.ru.<\/p>\n<ol>\n<li>We create a subscription for the domain helloworld.ru and specify the username-password for the system user:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3932dc7492aa523b6cbd540ad7a7fe6b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAt the bottom of the page, we check the box <i>Asigura\u021bi domeniul cu Let's Encrypt<\/i>, if we want to configure HTTPS for the site:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f4d23a34764fdeb411fe2ca9cc95cb45.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Next, in this subscription, we create a subdomain dev.helloworld.ru (for which a free TLS certificate can also be issued):\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d57e6ba71454cb537917400de4275e92.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Installing server components<\/h1>\n<p>\nWe have a server with <i>OS Debian Stretch 9.12<\/i> and the control panel installed <i>Plesk Obsidian 18.0.27<\/i>.<\/p>\n<p>We need to install and configure for our project:<\/p>\n<ul>\n<li>PostgreSQL (in our case, there will be one server with two databases for dev and prod environments).<\/li>\n<li>RabbitMQ (the same, one instance with different vhosts for environments).<\/li>\n<li>Two instances of Redis (for dev and prod environments).<\/li>\n<li>Docker Registry (for local storage of collected Docker images).<\/li>\n<li>UI interface for Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nPlesk already includes a PostgreSQL DBMS, but not the latest version (at the time of this writing, Plesk Obsidian <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.plesk.com\/release-notes\/obsidian\/software-requirements\/\">supported<\/a><\/noindex> Postgres versions 8.4\u201310.8). We want the latest version (12.3 at the time of writing), so we will install it manually.<\/p>\n<p>There are plenty of detailed instructions for installing Postgres on Debian available online (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">exemplu<\/a><\/noindex>), so I won't describe them in detail, just provide the commands:<\/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>\nAv\u00e2nd \u00een vedere c\u0103 PostgreSQL are set\u0103ri de baz\u0103 destul de medii, este esen\u021bial s\u0103 corect\u0103m configura\u021bia. \u00cen acest sens, ne va ajuta <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">un calculator<\/a><\/noindex>: este necesar s\u0103 introducem parametrii serverului nostru \u0219i s\u0103 \u00eenlocuim set\u0103rile din fi\u0219ierul <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>cu cele sugerate. Aici trebuie men\u021bionat c\u0103 astfel de calculatoare nu sunt o solu\u021bie magic\u0103, iar baza ar trebui optimizat\u0103 mai precis, \u00een func\u021bie de hardware-ul, aplica\u021bia \u0219i complexitatea interog\u0103rilor. Dar pentru \u00eenceput, aceasta este suficient.<\/p>\n<p>Pe l\u00e2ng\u0103 set\u0103rile sugerate de calculator, modific\u0103m \u0219i <code>postgresql.conf<\/code>portul implicit 5432 cu altul (\u00een exemplul nostru \u2014 <strong>53983<\/strong>).<\/p>\n<p>Dup\u0103 modificarea fi\u0219ierului de configura\u021bie, repornim serverul postgresql cu comanda:<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nAm instalat \u0219i configurat PostgreSQL. Acum s\u0103 cre\u0103m baza de date, utilizatorii pentru medii dev- \u0219i prod- \u0219i s\u0103 le oferim utilizatorilor drepturi de gestionare a bazei de date:<\/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>\nS\u0103 trecem la instalarea RabbitMQ \u2014 broker de mesaje pentru Celery. Se instaleaz\u0103 destul de simplu pe 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>\nDup\u0103 instalare, trebuie s\u0103 cre\u0103m <i>vhosts<\/i>, utilizatori \u0219i s\u0103 oferim drepturile necesare:<\/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>\nAcum vom instala \u0219i configura ultimul component pentru aplica\u021bia noastr\u0103 \u2014 Redis. Acesta va fi folosit ca backend pentru stocarea rezultatelor sarcinilor Celery.<\/p>\n<p>Vom ridica dou\u0103 containere Docker cu Redis pentru medii dev- \u0219i prod- folosind extensia <code>Docker<\/code> pentru Plesk.<\/p>\n<ol>\n<li>Accesa\u021bi Plesk, merge\u021bi la sec\u021biunea Extensii, c\u0103uta\u021bi extensia Docker \u0219i instala\u021bi-o (ne trebuie versiunea gratuit\u0103):\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Merge\u021bi la extensia instalat\u0103, c\u0103uta\u021bi imaginea <code>redis bitnami<\/code> \u0219i instala\u021bi ultima versiune:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Accesa\u021bi containerul desc\u0103rcat \u0219i ajusta\u021bi configura\u021bia: specifica\u021bi portul, dimensiunea maxim\u0103 alocat\u0103 a RAM-ului, parola \u00een variabilele de mediu \u0219i monta\u021bi volumul:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Repeta\u021bi pa\u0219ii 2-3 pentru containerul prod, doar schimba\u021bi parametrii: port, parol\u0103, dimensiune RAM \u0219i calea c\u0103tre folderul volum pe server:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe 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>\nPe l\u00e2ng\u0103 serviciile de baz\u0103, ar fi bine s\u0103 instal\u0103m pe server un depozit propriu de imagini Docker. \u0218i, av\u00e2nd \u00een vedere c\u0103 spa\u021biul pe servere este destul de ieftin (cu siguran\u021b\u0103 mai ieftin dec\u00e2t abonamentul pe DockerHub), iar procesul de instalare a unui depozit privat este foarte simplu. <\/p>\n<p>Vrem s\u0103 avem instalat:<\/p>\n<ul>\n<li>un depozit Docker protejat prin parol\u0103, disponibil pe un subdomeniu <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>UI pentru vizualizarea imaginilor \u00een depozit, disponibil la adresa <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nPentru aceasta:<\/p>\n<ol>\n<li>Vom crea \u00een Plesk dou\u0103 subdomenii \u00een abonamentul nostru: docker.helloworld.ro \u0219i docker-ui.helloworld.ro, \u0219i le vom configura certificate Let\u2019s Encrypt.<\/li>\n<li>\u00cen folderul subdomeniului docker.helloworld.ro vom ad\u0103uga fi\u0219ierul <code>docker-compose.yml<\/code> cu urm\u0103torul con\u021binut:\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>Prin SSH, vom genera fi\u0219ierul .htpasswd pentru autentificarea Basic \u00een depozitul Docker:\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>Vom construi \u0219i ridica containerele:\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>\u0218i trebuie s\u0103 redirec\u021bion\u0103m Nginx c\u0103tre containerele noastre. Acest lucru se poate face prin Plesk. <\/li>\n<\/ol>\n<p>\nUrm\u0103toarele ac\u021biuni trebuie realizate pentru subdomeniile docker.helloworld.ro \u0219i docker-ui.helloworld.ro:<\/p>\n<p>\u00cen sec\u021biune <i>Dev Tools<\/i> \u00een site-ul nostru, accesa\u021bi <i>Docker Proxy Rules<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u0218i ad\u0103uga\u021bi o regul\u0103 pentru a face proxy traficului de intrare c\u0103tre containerul nostru:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>Verifica\u021bi dac\u0103 ne putem autentifica \u00een containerul nostru de pe ma\u0219ina local\u0103:\n<pre><code class=\"plaintext\">$ docker login docker.helloworld.ro -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>De asemenea, s\u0103 verific\u0103m func\u021bionarea subdomeniului docker-ui.helloworld.ro:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nC\u00e2nd face\u021bi clic pe Browse repositories, browserul va deschide o fereastr\u0103 pentru autentificare, unde va trebui s\u0103 introduce\u021bi numele de utilizator \u0219i parola pentru repository. Apoi, ve\u021bi fi redirec\u021bionat pe pagina cu lista de repository-uri (\u00een prezent aceasta va fi goal\u0103):<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Deschidem porturile \u00een Plesk Firewall<\/h2>\n<p>\nDup\u0103 instalarea \u0219i configurarea componentelor, trebuie s\u0103 deschidem porturile pentru ca acestea s\u0103 fie accesibile din containerele Docker \u0219i din re\u021beaua extern\u0103.<\/p>\n<p>S\u0103 vedem cum se face acest lucru, folosind ca exemplu extensia Firewall instalat\u0103 anterior pentru Plesk.<\/p>\n<ol>\n<li>Trecem la <i>Tools &amp; Settings &gt; Settings &gt; Firewall<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Trecem la <i>Modifica\u021bi regulile Plesk Firewall &gt; Ad\u0103uga\u021bi regul\u0103 personalizat\u0103<\/i> \u0219i deschidem urm\u0103toarele porturi TCP pentru subnetul 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 \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>De asemenea, vom ad\u0103uga o regul\u0103 care va deschide porturile PostgreSQL \u0219i panoul de management RabbitMQ pentru lumea extern\u0103:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Aplic\u0103m regulile folosind butonul Aplica\u021bi modifica\u0103rile:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Configurarea CI\/CD \u00een Github Actions<\/h1>\n<p>\nS\u0103 trecem la cea mai interesant\u0103 parte \u2014 configurarea pipeline-ului pentru integrarea \u0219i livrarea continu\u0103 a proiectului nostru pe server.<\/p>\n<p>Acest pipeline va consta din dou\u0103 p\u0103r\u021bi: <\/p>\n<ul>\n<li>construc\u021bia imaginii \u0219i rularea testelor (pentru backend) \u2014 pe partea Github;<\/li>\n<li>rularea migr\u0103rilor (pentru backend) \u0219i implementarea containerelor \u2014 pe server.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Implementarea \u00een Plesk<\/h2>\n<p>\nVom \u00eencepe cu a doua parte (deoarece prima depinde de aceasta).<\/p>\n<p>Procesul de implementare \u00eel vom configura folosind extensia Git pentru Plesk.<\/p>\n<p>S\u0103 lu\u0103m \u00een considerare un exemplu cu mediul Prod pentru repository-ul Backend.<\/p>\n<ol>\n<li>Acces\u0103m abonamentul site-ului nostru Helloworld \u0219i trecem la subsec\u021biunea Git:\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Introducem \u00een c\u00e2mpul \u201eRemote Git repository\u201d linkul nostru c\u0103tre repository-ul Github \u0219i schimb\u0103m folderul implicit <code>httpdocs<\/code> \u00eentr-un altul (de exemplu, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Copiem cheia public\u0103 SSH de la etapa anterioar\u0103 \u0219i <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">ad\u0103ug\u0103m<\/a><\/noindex> aceasta \u00een set\u0103rile Github.<\/li>\n<li>Facem clic pe OK pe ecranul din punctul 2, dup\u0103 care vom fi redirec\u021biona\u021bi pe pagina repository-ului din Plesk. Acum trebuie s\u0103 configur\u0103m actualizarea repository-ului la comiterea \u00een ramura master. Pentru aceasta, mergem la <i>Set\u0103rile repository-ului<\/i> \u0219i salv\u0103m valoarea <code>Webhook URL<\/code> (ne va fi necesar\u0103 mai t\u00e2rziu la configurarea Github Actions):\n<p><img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>\u00cen c\u00e2mpul Actions pe ecranul din punctul anterior, introducem scriptul pentru a ini\u021bia implementarea:\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>\nunde: <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 calea c\u0103tre folderul prod al repository-ului backend pe server;<br \/>\n<code>{ENV}<\/code> \u2014 mediu (dev\/prod), \u00een cazul nostru <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 hostul repository-ului nostru docker<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 token-ul botului Telegram;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 ID-ul chat-ului\/canalului pentru trimiterea notific\u0103rilor.<\/p>\n<p>Exemplu 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>Ad\u0103ug\u0103m utilizatorul din abonamentul nostru \u00een grupul Docker (pentru a putea gestiona containerele):\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\nMediul de dezvoltare pentru repo-ul backend \u0219i frontend este configurat similar.<\/p>\n<h2>Pipeline-ul de deploy \u00een Github Actions<\/h2>\n<p>\nTrecem la configurarea primei p\u0103r\u021bi a pipeline-ului nostru CI\/CD \u00een Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nPipeline-ul este descris \u00een <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">fi\u0219ierul deploy.yml<\/a><\/noindex>. <\/p>\n<p>Dar \u00eenainte de a-l analiza, s\u0103 complet\u0103m variabilele Secret de care avem nevoie \u00een Github. Pentru aceasta, mergem la <i>Settings -&gt; Secrets<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 gazda repo-ului nostru Docker (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 utilizatorul pentru repo-ul Docker;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 parola pentru acesta;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 gazda pe care este disponibil\u0103 interfa\u021ba de administrare Plesk (exemplu: <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 sau <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-ul pentru deploy \u00een repo-ul prod pe server (l-am ob\u021binut \u00een Etapa de desf\u0103\u0219urare \u00een Plesk, punctul 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 token-ul pentru deploy \u00een repo-ul dev pe server.<\/li>\n<\/ul>\n<p>\nProcesul de deploy este simplu \u0219i const\u0103 \u00een trei pa\u0219i principali: <\/p>\n<ul>\n<li>construirea \u0219i publicarea imaginii \u00een repo-ul nostru;<\/li>\n<li>rularea testelor \u00eentr-un container pe baza imaginii recent construite;<\/li>\n<li>desf\u0103\u0219urarea \u00een mediul necesar \u00een func\u021bie de ramur\u0103 (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\">Fi\u0219ierul deploy.yml pentru repo-ul frontend<\/a><\/noindex> nu se deosebe\u0219te prea mult de cel backend. Acesta nu con\u021bine pa\u0219ii pentru rularea testelor \u0219i se schimb\u0103 denumirile token-urilor pentru deploy. Secretele pentru repo-ul frontend, de altfel, trebuie completate separat.<\/p>\n<h1>Configurarea site-ului<\/h1>\n<p><\/p>\n<h2>Proxierea traficului prin Nginx<\/h2>\n<p>\nEi bine, am ajuns la final. R\u0103m\u00e2ne doar s\u0103 configur\u0103m proxierea traficului de intrare \u0219i ie\u0219ire \u00een containerul nostru prin Nginx. Acest proces l-am abordat deja \u00een punctul 5 privind configurarea Docker Registry. Acela\u0219i lucru trebuie repetat pentru partea de backend \u0219i frontend \u00een mediile dev \u0219i prod.<\/p>\n<p>Voi prezenta capturi de ecran ale set\u0103rilor.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD \u00een Github Actions pentru un proiect pe 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 \u00een Github Actions pentru un proiect pe Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>O clarificare important\u0103<\/strong>. \u00cen containerul frontend vor fi proxiate toate URL-urile, cu excep\u021bia celor care \u00eencep cu <code>\/api\/<\/code> \u2014 acestea vor fi proxiate \u00een containerul backend (de aceea <strong>\u00een containerul backend, toate handler-ele trebuie s\u0103 \u00eenceap\u0103 cu <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>Concluzii<\/h2>\n<p>\nAcum site-ul nostru ar trebui s\u0103 fie accesibil pe adresele helloworld.ru \u0219i dev.helloworld.ru (mediile prod \u0219i dev, respectiv).<\/p>\n<p>\u00cen concluzie, am \u00eenv\u0103\u021bat cum s\u0103 preg\u0103tim o aplica\u021bie simpl\u0103 pe Flask \u0219i Angular \u0219i s\u0103 configur\u0103m un pipeline \u00een Github Actions pentru desf\u0103\u0219urarea acesteia pe un server gestionat de Plesk.<\/p>\n<p>Voi repeta linkurile c\u0103tre repo-urile cu cod: <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 \/>Sursa: <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.1 - 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\/ro\/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.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"ro_RO\" \/>\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\/ro\/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 \u00een Github Actions pentru un proiect pe Flask+Angular | ProHoster","description":"\u00cen acest articol voi \u00eemp\u0103rt\u0103\u0219i experien\u021ba mea.","canonical_url":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"ro_RO","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\/ro\/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\/ro\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}