{"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\/sv\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nI den h\u00e4r artikeln kommer jag att dela med mig av min erfarenhet av att konfigurera CI\/CD med hj\u00e4lp av kontrollpanelen Plesk och Github Actions. Idag ska vi l\u00e4ra oss att distribuera ett enkelt projekt med den okomplicerade titeln \"Helloworld\". Det \u00e4r skrivet i Python-ramverket Flask, med arbetare p\u00e5 Celery och frontend p\u00e5 Angular 8.<\/p>\n<p><b>L\u00e4nkar till repositoryn:<\/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>I den f\u00f6rsta delen av artikeln kommer vi att titta p\u00e5 v\u00e5rt projekt och dess delar. I den andra kommer vi att g\u00e5 igenom hur man konfigurerar Plesk och installerar n\u00f6dv\u00e4ndiga till\u00e4gg och komponenter (DB, RabbitMQ, Redis, Docker, etc.).<\/p>\n<p>I den tredje delen kommer vi \u00e4ntligen att ta reda p\u00e5 hur man st\u00e4ller in en pipeline f\u00f6r att distribuera v\u00e5rt projekt p\u00e5 servern i dev- och prod-milj\u00f6. Och sedan kommer vi att starta webbplatsen p\u00e5 servern.<\/p>\n<p>Och ja, jag gl\u00f6mde att presentera mig. Jag heter Oleg Borzov, jag \u00e4r fullstack-utvecklare i CRM-teamet f\u00f6r hypoteksl\u00e5neledare p\u00e5 Domklik. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Projekt\u00f6versikt<\/h1>\n<p>\nF\u00f6rst ska vi titta p\u00e5 tv\u00e5 repositoryn f\u00f6r projektet \u2014 backend och frontend \u2014 och g\u00e5 igenom koden.<\/p>\n<h2>Backend-del: Flask+Celery<\/h2>\n<p>\nF\u00f6r backend-delen har jag valt en ganska popul\u00e4r kombination bland Python-utvecklare: ramverket Flask (f\u00f6r API) och Celery (f\u00f6r uppgiftsk\u00f6). SQLAlchemy anv\u00e4nds som ORM. Alembic anv\u00e4nds f\u00f6r migreringar. F\u00f6r validering av JSON i endpoints \u2014 Marshmallow.<\/p>\n<p>I <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">f\u00f6rr\u00e5det<\/a><\/noindex> det finns en fil Readme.md med en detaljerad beskrivning av strukturen och instruktioner f\u00f6r att k\u00f6ra projektet.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API-webbdel<\/a><\/noindex> ganska okomplicerat, best\u00e5r av 6 endpoints:<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 f\u00f6r att kontrollera tillg\u00e4nglighet;<\/li>\n<li>endpoints f\u00f6r registrering, autentisering, avregistrering och att h\u00e4mta den autentiserade anv\u00e4ndaren;<\/li>\n<li>endpoint f\u00f6r att skicka e-post, som l\u00e4gger en uppgift i Celery-k\u00f6n.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Celery-del<\/a><\/noindex> \u00e4nnu enklare, det finns bara en uppgift <code>send_mail_task<\/code>.<\/p>\n<p>I mappen <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> det finns tv\u00e5 undermappar: <\/p>\n<ul>\n<li><code>docker<\/code> med tv\u00e5 Docker-filer (<code>base.dockerfile<\/code> f\u00f6r att skapa en s\u00e4llan \u00e4ndrad basbild och <code>Dockerfile<\/code> f\u00f6r huvudbyggnationerna);<\/li>\n<li><code>.env_files<\/code> \u2014 med filer f\u00f6r milj\u00f6variabler f\u00f6r olika milj\u00f6er. <\/li>\n<\/ul>\n<p>\nI roten av projektet finns fyra docker-compose-filer:<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> f\u00f6r att starta en lokal databas f\u00f6r utveckling;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> f\u00f6r lokal uppstart av arbetare, databas, Redis och RabbitMQ;<\/li>\n<li><code>docker-compose.test.yml<\/code> f\u00f6r att k\u00f6ra tester vid distribution;<\/li>\n<li><code>docker-compose.yml<\/code> f\u00f6r distribution.<\/li>\n<\/ul>\n<p>\nOch den sista intressanta mappen f\u00f6r oss \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>. Den inneh\u00e5ller shell-skript f\u00f6r distribution:<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 k\u00f6rning av migreringar och distribution. Startas p\u00e5 servern efter byggnation och k\u00f6rning av tester i Github Actions;<\/li>\n<li><code>rollback.sh<\/code> \u2014 \u00e5terst\u00e4llning av containrar till f\u00f6reg\u00e5ende version av bygget;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 skickar meddelanden om distribution till Telegram.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend p\u00e5 Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">Repository with the front end<\/a><\/noindex> is much simpler than the back end. The front consists of three pages:<\/p>\n<ul>\n<li>The main page with a form to send an email and a logout button.<\/li>\n<li>Login page.<\/li>\n<li>Registration page.<\/li>\n<\/ul>\n<p>\nThe main page looks minimalistic:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAt the root, there are two files <code>Dockerfile<\/code> och <code>docker-compose.yml<\/code>, as well as the 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>Setting up the project in Plesk<\/h1>\n<p>\nLet's start 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 status of containers in the Plesk admin panel;<\/li>\n<li><code>Git<\/code> for setting 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 configuring 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 i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWe won't discuss the detailed configuration of the extensions, the default settings will suffice for our demo purposes.<\/p>\n<h2>Creating a subscription and a 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>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 i Github Actions f\u00f6r ett projekt p\u00e5 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>S\u00e4kra dom\u00e4nen med Let's Encrypt<\/i>, if we want to set up HTTPS for the website:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 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 the subdomain dev.helloworld.ru (for which a free TLS certificate can also be issued):\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 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 built Docker images).<\/li>\n<li>UI interface for Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nThe package already includes a PostgreSQL DBMS in Plesk, but it's not the latest version (at the time of 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 for our application (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 online (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">exempel<\/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>\nEftersom PostgreSQL har ganska medelm\u00e5ttiga standardinst\u00e4llningar \u00e4r det viktigt att justera konfigurationen. Det kan vi f\u00e5 hj\u00e4lp med av <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">kalkylator<\/a><\/noindex>: vi beh\u00f6ver ange parametrarna f\u00f6r v\u00e5r server och ers\u00e4tta inst\u00e4llningarna i filen <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>med de f\u00f6reslagna. H\u00e4r ska det till\u00e4ggas att s\u00e5dana kalkylatorer inte \u00e4r en universall\u00f6sning, och databasen b\u00f6r justeras mer exakt baserat p\u00e5 din h\u00e5rdvara, applikationen och komplexiteten i fr\u00e5gorna. Men f\u00f6r att b\u00f6rja \u00e4r detta tillr\u00e4ckligt.<\/p>\n<p>F\u00f6rutom de av kalkylatorn f\u00f6reslagna inst\u00e4llningarna \u00e4ndrar vi \u00e4ven <code>postgresql.conf<\/code>den standardport som \u00e4r angiven 5432 till en annan (i v\u00e5rt exempel - <strong>53983<\/strong>).<\/p>\n<p>Efter att vi har \u00e4ndrat konfigurationsfilen, startar vi om postgresql-servern med kommandot:<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nVi har installerat och konfigurerat PostgreSQL. Nu ska vi skapa databaser, anv\u00e4ndare f\u00f6r dev- och prod-milj\u00f6er, och ge anv\u00e4ndarna r\u00e4ttigheter att hantera databasen:<\/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>\nL\u00e5t oss g\u00e5 vidare till installationen av RabbitMQ \u2014 meddelandebrokern f\u00f6r Celery. Det installeras ganska enkelt p\u00e5 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>\nEfter installationen beh\u00f6ver vi skapa <i>vhosts<\/i>, anv\u00e4ndare och ge dem n\u00f6dv\u00e4ndiga r\u00e4ttigheter:<\/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>\nNu ska vi installera och konfigurera den sista komponenten f\u00f6r v\u00e5r applikation \u2014 Redis. Den kommer att anv\u00e4ndas som backend f\u00f6r att lagra resultaten av uppgifter i Celery.<\/p>\n<p>Vi kommer att starta tv\u00e5 Docker-containrar med Redis f\u00f6r dev- och prod-milj\u00f6er med hj\u00e4lp av till\u00e4gget <code>Docker<\/code> f\u00f6r Plesk.<\/p>\n<ol>\n<li>Logga in p\u00e5 Plesk, g\u00e5 till avsnittet Till\u00e4gg, s\u00f6k efter till\u00e4gget Docker och installera det (vi beh\u00f6ver gratisversionen):\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>G\u00e5 till det installerade till\u00e4gget, s\u00f6k efter bilden <code>redis bitnami<\/code> och installera den senaste versionen:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>G\u00e5 in i den nedladdade containern och justera konfigurationen: ange port, maximalt tilldelat RAM-minne, l\u00f6senord i milj\u00f6variabler och montera volymen:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Utf\u00f6r steg 2-3 f\u00f6r prod-containern, \u00e4ndra bara inst\u00e4llningarna: port, l\u00f6senord, RAM-storlek och s\u00f6kv\u00e4g till volymmappen p\u00e5 servern:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 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>\nF\u00f6rutom grundl\u00e4ggande tj\u00e4nster skulle det vara bra att installera ett eget Docker-bildarkiv p\u00e5 servern. Tack vare att utrymme p\u00e5 servrar nu \u00e4r ganska billigt (definitivt billigare \u00e4n en prenumeration p\u00e5 DockerHub), och installationen av ett privat arkiv \u00e4r mycket enkel. <\/p>\n<p>Vi vill att f\u00f6ljande ska vara installerade:<\/p>\n<ul>\n<li>ett l\u00f6senordsskyddat Docker-arkiv, tillg\u00e4ngligt via en subdom\u00e4n <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>Ett anv\u00e4ndargr\u00e4nssnitt f\u00f6r att visa bilder i arkivet, tillg\u00e4ngligt p\u00e5 adressen <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nF\u00f6r detta:<\/p>\n<ol>\n<li>Vi skapar tv\u00e5 subdom\u00e4ner i Plesk i v\u00e5r prenumeration: docker.helloworld.ru och docker-ui.helloworld.ru, och konfigurerar Let's Encrypt-certifikat f\u00f6r dem.<\/li>\n<li>I mappen f\u00f6r subdom\u00e4nen docker.helloworld.ru l\u00e4gger vi till filen <code>docker-compose.yml<\/code> med f\u00f6ljande inneh\u00e5ll:\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>Genom SSH ska vi generera filen .htpasswd f\u00f6r grundl\u00e4ggande autentisering i Docker-arkivet:\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>Bygg och starta containrarna:\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>Och vi beh\u00f6ver dirigera Nginx till v\u00e5ra containrar. Det kan g\u00f6ras via Plesk. <\/li>\n<\/ol>\n<p>\nF\u00f6ljande \u00e5tg\u00e4rder m\u00e5ste vidtas f\u00f6r subdom\u00e4nerna docker.helloworld.ru och docker-ui.helloworld.ru:<\/p>\n<p>I avsnittet <i>Dev Tools<\/i> p\u00e5 v\u00e5r webbplats g\u00e5r vi till <i>Docker Proxy Rules<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nOch l\u00e4gg till en regel f\u00f6r att proxy-trafiken till v\u00e5r container:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>Kontrollera att vi kan logga in p\u00e5 v\u00e5r container fr\u00e5n den lokala maskinen:\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>Vi kontrollerar ocks\u00e5 att subdom\u00e4nen docker-ui.helloworld.ru fungerar:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nN\u00e4r du trycker p\u00e5 Browse repositories kommer webbl\u00e4saren att visa en inloggningsruta d\u00e4r du beh\u00f6ver ange anv\u00e4ndarnamn och l\u00f6senord f\u00f6r lagret. Efter det tas du till en sida med en lista \u00f6ver lagren (den kommer vara tom f\u00f6r dig just nu):<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>\u00d6ppna portar i Plesk Firewall<\/h2>\n<p>\nEfter installation och konfiguration av komponenterna beh\u00f6ver vi \u00f6ppna portar s\u00e5 att komponenterna blir tillg\u00e4ngliga fr\u00e5n Docker-containrar och fr\u00e5n det externa n\u00e4tverket.<\/p>\n<p>L\u00e5t oss se hur detta g\u00f6rs med hj\u00e4lp av det tidigare installerade Firewall-till\u00e4gget f\u00f6r Plesk.<\/p>\n<ol>\n<li>Vi g\u00e5r till <i>Verktyg &amp; Inst\u00e4llningar &gt; Inst\u00e4llningar &gt; Brandv\u00e4gg<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Vi g\u00e5r till <i>\u00c4ndra Plesk Firewall-regler &gt; L\u00e4gg till anpassad regel<\/i> och \u00f6ppna f\u00f6ljande TCP-portar f\u00f6r Docker-undern\u00e4tet (<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 i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>L\u00e5t oss ocks\u00e5 l\u00e4gga till en regel som \u00f6ppnar portar f\u00f6r PostgreSQL och RabbitMQ:s administrationspanel f\u00f6r omv\u00e4rlden:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Till\u00e4mpa reglerna med hj\u00e4lp av knappen Till\u00e4mpa \u00e4ndringar:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Konfiguration av CI\/CD i Github Actions<\/h1>\n<p>\nL\u00e5t oss g\u00e5 till den mest intressanta delen \u2014 konfigurationen av pipeline f\u00f6r kontinuerlig integration och leverans av v\u00e5rt projekt till servern.<\/p>\n<p>Denna pipeline kommer att best\u00e5 av tv\u00e5 delar: <\/p>\n<ul>\n<li>bygga bilden och k\u00f6ra tester (f\u00f6r backend) \u2014 p\u00e5 Github-sidan;<\/li>\n<li>k\u00f6ra migrationer (f\u00f6r backend) och distribuera containrar \u2014 p\u00e5 servern.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Distribution i Plesk<\/h2>\n<p>\nVi b\u00f6rjar med det andra punkten (eftersom det p\u00e5verkar det f\u00f6rsta).<\/p>\n<p>Vi kommer att konfigurera distributionsprocessen med hj\u00e4lp av Git-till\u00e4gget f\u00f6r Plesk.<\/p>\n<p>Vi tar ett exempel med produktionsmilj\u00f6n f\u00f6r Backend-lagret.<\/p>\n<ol>\n<li>G\u00e5 till prenumerationen f\u00f6r v\u00e5r webbplats Helloworld och navigera till avsnittet Git:\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Klistra in l\u00e4nken till v\u00e5rt Github-lager i f\u00e4ltet \"Remote Git repository\" och \u00e4ndra standardmappen <code>httpdocs<\/code> till en annan (till exempel, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Kopiera SSH-public key fr\u00e5n f\u00f6reg\u00e5ende steg och <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">l\u00e4gg till<\/a><\/noindex> s\u00e4tt in den i inst\u00e4llningarna f\u00f6r Github.<\/li>\n<li>Klicka p\u00e5 OK p\u00e5 sk\u00e4rmen under punkt 2, efter vilket du tas till sidan f\u00f6r lagret i Plesk. Nu m\u00e5ste vi st\u00e4lla in uppdateringen av lagret vid commit till master-grenen. F\u00f6r detta g\u00e5r vi till <i>Repository Settings<\/i> och sparar v\u00e4rdet <code>Webhook URL<\/code> (det kommer att beh\u00f6vas senare f\u00f6r att konfigurera Github Actions):\n<p><img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>I f\u00e4ltet Actions p\u00e5 sk\u00e4rmen fr\u00e5n f\u00f6reg\u00e5ende punkt, skriv in skriptet f\u00f6r att starta distributionen:\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>\nd\u00e4r: <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 v\u00e4gen till prod-mappen f\u00f6r backend-lagret p\u00e5 servern;<br \/>\n<code>{ENV}<\/code> \u2014 milj\u00f6 (dev\/prod), i v\u00e5rt fall <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 v\u00e4rden f\u00f6r v\u00e5rt docker-lager<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 token f\u00f6r Telegram-botten;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 ID f\u00f6r chatten\/kanalen f\u00f6r att skicka notifieringar.<\/p>\n<p>Exempel p\u00e5 skript:<\/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>L\u00e4gg till anv\u00e4ndaren fr\u00e5n v\u00e5r prenumeration i Docker-gruppen (s\u00e5 att han kan hantera containrar):\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\nUtvecklingsmilj\u00f6n f\u00f6r backend-repositoriet och frontend st\u00e4lls in p\u00e5 samma s\u00e4tt.<\/p>\n<h2>Deploy-pipeline i Github Actions<\/h2>\n<p>\nL\u00e5t oss g\u00e5 vidare med att konfigurera f\u00f6rsta delen av v\u00e5r CI\/CD-pipeline i Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nPipelinen beskrivs i <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">filen deploy.yml<\/a><\/noindex>. <\/p>\n<p>Men innan vi g\u00e5r igenom den beh\u00f6ver vi fylla i de n\u00f6dv\u00e4ndiga hemliga variablerna i Github. F\u00f6r detta g\u00e5r vi till <i>Inst\u00e4llningar -&gt; Hemliga<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 v\u00e4rd f\u00f6r v\u00e5rt Docker-repositorium (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 inloggning till Docker-repositoriet;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 l\u00f6senordet till det;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 v\u00e4rd d\u00e4r Plesk-administrationspanelen \u00e4r tillg\u00e4nglig (exempel: <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 eller <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 f\u00f6r att deploya till prod-repositoriet p\u00e5 servern (vi fick det i Deploy till Plesk punkt 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 token f\u00f6r att deploya till dev-repositoriet p\u00e5 servern.<\/li>\n<\/ul>\n<p>\nDeploy-processen \u00e4r enkel och best\u00e5r av tre huvudsteg: <\/p>\n<ul>\n<li>bygge och publicering av bilden i v\u00e5rt repositorium;<\/li>\n<li>k\u00f6rning av tester i containern baserat p\u00e5 den nybyggda bilden;<\/li>\n<li>deployering till r\u00e4tt milj\u00f6 beroende p\u00e5 grenen (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\">Filen deploy.yml f\u00f6r frontend-repositoriet<\/a><\/noindex> skiljer sig inte mycket fr\u00e5n backend-versionen. Den saknar steget med att k\u00f6ra tester och namnen p\u00e5 tokens f\u00f6r deployering \u00e4ndras. Hemligheterna f\u00f6r frontend-repositoriet m\u00e5ste f\u00f6rresten fyllas i separat.<\/p>\n<h1>Webbplatsinst\u00e4llning<\/h1>\n<p><\/p>\n<h2>Proxytrafik genom Nginx<\/h2>\n<p>\nN\u00e5v\u00e4l, vi har kommit till slutet. Det \u00e5terst\u00e5r bara att st\u00e4lla in proxytrafik f\u00f6r inkommande och utg\u00e5ende trafik till v\u00e5r container genom Nginx. Denna process har vi redan g\u00e5tt igenom i punkt 5 i Docker Registry-inst\u00e4llningarna. Samma sak m\u00e5ste upprepas f\u00f6r backend- och frontend-delarna i dev- och prod-milj\u00f6erna.<\/p>\n<p>Jag kommer att visa sk\u00e4rmdumpar av inst\u00e4llningarna.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD i Github Actions f\u00f6r ett projekt p\u00e5 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 i Github Actions f\u00f6r ett projekt p\u00e5 Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>Viktig anm\u00e4rkning<\/strong>. I frontend-containern kommer alla URL:er att proxas, f\u00f6rutom de som b\u00f6rjar med <code>\/api\/<\/code> \u2014 de kommer att proxas till backend-containern (d\u00e4rf\u00f6r m\u00e5ste <strong>i backend-containern alla handhavanden b\u00f6rja med <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>Sammanfattning<\/h2>\n<p>\nNu b\u00f6r v\u00e5r webbplats vara tillg\u00e4nglig p\u00e5 adresserna helloworld.ru och dev.helloworld.ru (prod- och dev-milj\u00f6er respektive).<\/p>\n<p>Sammanfattningsvis har vi l\u00e4rt oss hur man f\u00f6rbereder en enkel applikation p\u00e5 Flask och Angular och hur man st\u00e4ller in en pipeline i Github Actions f\u00f6r att lansera den p\u00e5 en server som styrs av Plesk.<\/p>\n<p>Jag vill ocks\u00e5 dela l\u00e4nkar till reporna med koden: <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 \/>K\u00e4lla: <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.4 - 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\/sv\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"sv_SE\" \/>\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\/sv\/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 i Github Actions f\u00f6r projekt p\u00e5 Flask+Angular | ProHoster","description":"I den h\u00e4r artikeln kommer jag att dela med mig av min erfarenhet.","canonical_url":"https:\/\/prohoster.info\/sv\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"sv_SE","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\/sv\/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\/sv\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}