{"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\/pl\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD w GitHub Actions dla projektu na Flask+Angular","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nW tym artykule podziel\u0119 si\u0119 swoim do\u015bwiadczeniem w ustawianiu CI\/CD z wykorzystaniem panelu sterowania Plesk i Github Actions. Dzi\u015b nauczymy si\u0119 wdra\u017ca\u0107 prosty projekt o niezbyt skomplikowanej nazwie \u201eHelloworld\u201d. Zosta\u0142 napisany w frameworku Python Flask, z workerami na Celery i frontendem na Angular 8.<\/p>\n<p><b>Linki do repozytori\u00f3w:<\/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>W pierwszej cz\u0119\u015bci artyku\u0142u przyjrzymy si\u0119 naszemu projektowi i jego elementom. W drugiej \u2014 rozwi\u0105\u017cemy, jak skonfigurowa\u0107 Plesk oraz zainstalowa\u0107 niezb\u0119dne rozszerzenia i komponenty (Baza Danych, RabbitMQ, Redis, Docker itp.).<\/p>\n<p>W trzeciej cz\u0119\u015bci w ko\u0144cu zrozumiemy, jak skonfigurowa\u0107 pipeline do wdro\u017cenia naszego projektu na serwerze w \u015brodowisku dev i prod. A potem uruchomimy stron\u0119 na serwerze.<\/p>\n<p>A tak, zapomnia\u0142em si\u0119 przedstawi\u0107. Nazywam si\u0119 Oleg Borzow, jestem programist\u0105 fullstack w zespole CRM dla mened\u017cer\u00f3w kredyt\u00f3w hipotecznych w Domklik. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Przegl\u0105d projektu<\/h1>\n<p>\nNa pocz\u0105tek rozwa\u017cymy dwa repozytoria projektu \u2014 backendu i frontu \u2014 i przejdziemy przez kod.<\/p>\n<h2>Cz\u0119\u015b\u0107 backendowa: Flask+Celery<\/h2>\n<p>\nDo cz\u0119\u015bci backendowej wybra\u0142em do\u015b\u0107 popularne w\u015br\u00f3d programist\u00f3w Pythona po\u0142\u0105czenie: framework Flask (do API) oraz Celery (do kolejek zada\u0144). Jako ORM u\u017cywana jest SQLAlchemy. Do migracji stosuje si\u0119 Alembic. Do walidacji JSON w endpointach u\u017cywamy Marshmallow.<\/p>\n<p>W <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">repozytorium<\/a><\/noindex> jest plik Readme.md z dok\u0142adnym opisem struktury i instrukcjami uruchomienia projektu.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API cz\u0119\u015bci webowej<\/a><\/noindex> do\u015b\u0107 proste, sk\u0142ada si\u0119 z 6 endpoint\u00f3w:<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 do sprawdzania dost\u0119pno\u015bci;<\/li>\n<li>endpointy do rejestracji, autoryzacji, deautoryzacji i uzyskiwania zautoryzowanego u\u017cytkownika;<\/li>\n<li>endpoint do wysy\u0142ania e-maili, kt\u00f3ry umieszcza zadanie w kolejce Celery.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Cz\u0119\u015b\u0107 Celery<\/a><\/noindex> jeszcze prostsza, tam jest tylko jedno zadanie <code>send_mail_task<\/code>.<\/p>\n<p>W folderze <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> s\u0105 dwa podfoldery: <\/p>\n<ul>\n<li><code>docker<\/code> z dwoma plikami Docker (<code>base.dockerfile<\/code> do budowy rzadko zmieniaj\u0105cego si\u0119 podstawowego obrazu oraz <code>Dockerfile<\/code> do g\u0142\u00f3wnych bud\u00f3w);<\/li>\n<li><code>.env_files<\/code> \u2014 z plikami z zmiennymi \u015brodowiskowymi dla r\u00f3\u017cnych \u015brodowisk. <\/li>\n<\/ul>\n<p>\nW g\u0142\u00f3wnym katalogu projektu znajduj\u0105 si\u0119 cztery pliki docker-compose:<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> do uruchomienia lokalnej bazy danych do rozwoju;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> do lokalnego uruchomienia workera, Bazy Danych, Redis i RabbitMQ;<\/li>\n<li><code>docker-compose.test.yml<\/code> do uruchamiania test\u00f3w przy wdro\u017ceniu;<\/li>\n<li><code>docker-compose.yml<\/code> do wdro\u017ce\u0144.<\/li>\n<\/ul>\n<p>\nA ostatnia interesuj\u0105ca nas folder \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>. Znajduj\u0105 si\u0119 w niej skrypty shellowe do wdra\u017cania:<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 uruchomienie migracji i wdro\u017cenia. Uruchamia si\u0119 na serwerze po zbudowaniu i uruchomieniu test\u00f3w w Github Actions;<\/li>\n<li><code>rollback.sh<\/code> \u2014 przywr\u00f3cenie kontener\u00f3w do poprzedniej wersji budowy;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 wysy\u0142anie powiadomie\u0144 o wdro\u017ceniu do Telegramu.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend na Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">Repozytorium z frontendem<\/a><\/noindex> znacznie prostszy ni\u017c backend. Frontend sk\u0142ada si\u0119 z trzech stron:<\/p>\n<ul>\n<li>Strona g\u0142\u00f3wna z formularzem do wysy\u0142ania e-maili i przyciskiem wyj\u015bcia.<\/li>\n<li>Strona logowania.<\/li>\n<li>Strona rejestracji.<\/li>\n<\/ul>\n<p>\nStrona g\u0142\u00f3wna wygl\u0105da ascetycznie:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nW katalogu g\u0142\u00f3wnym znajduj\u0105 si\u0119 dwa pliki <code>Dockerfile<\/code> i <code>docker-compose.yml<\/code>, a tak\u017ce znana nam folder <code>.ci-cd<\/code> z nieco mniejsz\u0105 ilo\u015bci\u0105 skrypt\u00f3w ni\u017c w repozytorium backendowym (usuni\u0119to skrypty do uruchamiania test\u00f3w).<\/p>\n<h1>Zak\u0142adamy projekt w Plesk<\/h1>\n<p>\nZacznijmy od konfiguracji Plesk i utworzenia subskrypcji dla naszej strony.<\/p>\n<h2>Instalacja rozszerze\u0144<\/h2>\n<p>\nW Plesk b\u0119dziemy potrzebowa\u0107 czterech rozszerze\u0144: <\/p>\n<ul>\n<li><code>Docker<\/code> do zarz\u0105dzania i wizualizacji stanu kontener\u00f3w w panelu Plesk;<\/li>\n<li><code>Gitem<\/code> do konfiguracji kroku deploymentu na serwerze;<\/li>\n<li><code>Let's Encrypt<\/code> do generowania (i automatycznego przed\u0142u\u017cania) darmowych certyfikat\u00f3w TLS;<\/li>\n<li><code>Firewall<\/code> do konfiguracji filtracji ruchu przychodz\u0105cego.<\/li>\n<\/ul>\n<p>\nMo\u017cna je zainstalowa\u0107 przez panel administracyjny Plesk w sekcji Rozszerzenia:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSzczeg\u00f3\u0142owej konfiguracji rozszerze\u0144 nie b\u0119dziemy omawia\u0107, do naszych cel\u00f3w demo wystarcz\u0105 ustawienia domy\u015blne.<\/p>\n<h2>Tworzenie subskrypcji i strony<\/h2>\n<p>\nNast\u0119pnie musimy utworzy\u0107 subskrypcj\u0119 dla naszej strony helloworld.ru i doda\u0107 subdomen\u0119 dev.helloworld.ru.<\/p>\n<ol>\n<li>Tworzymy subskrypcj\u0119 dla domeny helloworld.ru i podajemy login-has\u0142o dla u\u017cytkownika systemowego:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3932dc7492aa523b6cbd540ad7a7fe6b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa dole strony zaznaczamy <i>Zabezpiecz domen\u0119 za pomoc\u0105 Let's Encrypt<\/i>, je\u015bli chcemy skonfigurowa\u0107 HTTPS dla strony:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f4d23a34764fdeb411fe2ca9cc95cb45.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Nast\u0119pnie w tej subskrypcji tworzymy subdomen\u0119 dev.helloworld.ru (dla kt\u00f3rej r\u00f3wnie\u017c mo\u017cna wyda\u0107 darmowy certyfikat TLS):\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d57e6ba71454cb537917400de4275e92.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Instalacja komponent\u00f3w serwerowych<\/h1>\n<p>\nMamy serwer z <i>OS Debian Stretch 9.12<\/i> i zainstalowanym panelem zarz\u0105dzania <i>Plesk Obsidian 18.0.27<\/i>.<\/p>\n<p>Musimy zainstalowa\u0107 i skonfigurowa\u0107 dla naszego projektu:<\/p>\n<ul>\n<li>PostgreSQL (w naszym przypadku b\u0119dzie jeden serwer z dwiema bazami danych dla \u015brodowiska dev i prod).<\/li>\n<li>RabbitMQ (to samo, jedna instancja z r\u00f3\u017cnymi vhostami dla \u015brodowisk).<\/li>\n<li>Dwie instancje Redis (dla \u015brodowisk dev i prod).<\/li>\n<li>Docker Registry (do lokalnego przechowywania zbudowanych obraz\u00f3w Docker).<\/li>\n<li>Interfejs UI dla Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nWraz z Plesk ju\u017c zawiera baz\u0119 danych PostgreSQL, ale nie najnowszej wersji (w momencie pisania artyku\u0142u Plesk Obsidian <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.plesk.com\/release-notes\/obsidian\/software-requirements\/\">wspiera\u0142<\/a><\/noindex> Postgresa w wersjach 8.4\u201310.8). Chcemy jednak dla naszej aplikacji najnowsz\u0105 wersj\u0119 (12.3 w momencie pisania artyku\u0142u), wi\u0119c zainstalujemy j\u0105 r\u0119cznie.<\/p>\n<p>Szczeg\u00f3\u0142owych instrukcji dotycz\u0105cych instalacji Postgresa na Debianie w sieci jest wiele (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">przyk\u0142ad<\/a><\/noindex>), wi\u0119c szczeg\u00f3\u0142owo ich nie opisz\u0119, po prostu podam komendy:<\/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>\nBior\u0105c pod uwag\u0119, \u017ce PostgreSQL ma do\u015b\u0107 przeci\u0119tne ustawienia domy\u015blne, konieczne jest dostosowanie konfiguracji. W tym pomo\u017ce nam <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">, kt\u00f3ry wizualnie pokazuje, ile osoba zaoszcz\u0119dzi lub straci, przeprowadzaj\u0105c si\u0119 z Lozanny, powiedzmy, do \u00c9cublens (przedmie\u015bciem, gdzie mie\u015bci si\u0119 EPFL).<\/a><\/noindex>: nale\u017cy wprowadzi\u0107 parametry swojego serwera i zast\u0105pi\u0107 ustawienia w pliku <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>sugerowanymi. Nale\u017cy doda\u0107, \u017ce tego typu kalkulatory nie s\u0105 z\u0142otym \u015brodkiem, a baz\u0119 powinno si\u0119 tuningowa\u0107 bardziej precyzyjnie, w oparciu o sprz\u0119t, aplikacj\u0119 i z\u0142o\u017cono\u015b\u0107 zapyta\u0144. Ale na pocz\u0105tek to wystarczy.<\/p>\n<p>Opr\u00f3cz sugerowanych przez kalkulator ustawie\u0144 zmieniamy r\u00f3wnie\u017c w <code>postgresql.conf<\/code>domy\u015blnie przypisany port 5432 na inny (w naszym przyk\u0142adzie \u2014 <strong>53983<\/strong>).<\/p>\n<p>Po zmianie pliku konfiguracyjnego restartujemy postgresql-server poleceniem:<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nZainstalowali\u015bmy i skonfigurowali\u015bmy PostgreSQL. Teraz stworzymy baz\u0119 danych, u\u017cytkownik\u00f3w dla \u015brodowisk dev i prod oraz nadamy im prawa do zarz\u0105dzania baz\u0105 danych:<\/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>\nPrzejd\u017amy do instalacji RabbitMQ \u2014 brokera wiadomo\u015bci dla Celery. Instaluje si\u0119 go na Debianie do\u015b\u0107 prosto:<\/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>\nPo instalacji musimy stworzy\u0107 <i>vhosts<\/i>, u\u017cytkownik\u00f3w i przyzna\u0107 odpowiednie prawa:<\/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>\nTeraz zainstalujemy i skonfigurujemy ostatni komponent dla naszej aplikacji \u2014 Redis. B\u0119dzie on u\u017cywany jako backend do przechowywania wynik\u00f3w zada\u0144 Celery.<\/p>\n<p>Uruchomimy dwa kontenery Docker z Redis dla \u015brodowisk dev i prod przy u\u017cyciu rozszerzenia <code>Docker<\/code> dla Plesk.<\/p>\n<ol>\n<li>Wchodzimy do Plesk, przechodzimy do sekcji Rozszerzenia, szukamy rozszerzenia Docker i instalujemy je (potrzebujemy wersji darmowej):\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Przechodzimy do zainstalowanego rozszerzenia, znajdujemy przez wyszukiwark\u0119 obraz <code>redis bitnami<\/code> i instalujemy ostatni\u0105 wersj\u0119:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Wchodzimy w \u015bci\u0105gni\u0119ty kontener i poprawiamy konfiguracj\u0119: wskazujemy port, maksymalny przydzielony rozmiar RAM, has\u0142o w zmiennych \u015brodowiskowych oraz montujemy wolumen:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Wykonujemy kroki 2-3 dla kontenera prod, w ustawieniach zmieniamy tylko parametry: port, has\u0142o, rozmiar RAM i \u015bcie\u017ck\u0119 do folderu wolumenu na serwerze:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na 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>\nOpr\u00f3cz podstawowych us\u0142ug warto zainstalowa\u0107 na serwerze w\u0142asne repozytorium obraz\u00f3w Docker. Na szcz\u0119\u015bcie miejsce na serwerach jest obecnie wystarczaj\u0105co tanie (na pewno ta\u0144sze ni\u017c subskrypcja na DockerHub), a proces instalacji prywatnego repozytorium jest bardzo prosty. <\/p>\n<p>Chcemy, aby by\u0142y zainstalowane:<\/p>\n<ul>\n<li>chronione has\u0142em repozytorium Docker, dost\u0119pne przez subdomen\u0119 <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>Interfejs UI do przegl\u0105dania obraz\u00f3w w repozytorium, dost\u0119pny pod adresem <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nW tym celu:<\/p>\n<ol>\n<li>Stworzymy w Plesk dwie subdomeny w naszej subskrypcji: docker.helloworld.ru i docker-ui.helloworld.ru oraz skonfigurujemy dla nich certyfikaty Let\u2019s Encrypt.<\/li>\n<li>Do folderu subdomeny docker.helloworld.ru dodamy plik <code>docker-compose.yml<\/code> z tak\u0105 zawarto\u015bci\u0105:\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>Pod SSH wygenerujemy plik .htpasswd dla podstawowej autoryzacji w repozytorium Docker:\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>Zbudujemy i uruchomimy kontenery:\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>I musimy przekierowa\u0107 Nginx na nasze kontenery. Mo\u017cna to zrobi\u0107 przez Plesk. <\/li>\n<\/ol>\n<p>\nNast\u0119puj\u0105ce dzia\u0142ania nale\u017cy wykona\u0107 dla subdomen docker.helloworld.ru i docker-ui.helloworld.ru:<\/p>\n<p>W sekcji <i>Dev Tools<\/i> naszej strony wchodzimy w <i>Regu\u0142y Proxy Docker<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nI dodajemy regu\u0142\u0119 do proxy'owania przychodz\u0105cego ruchu do naszego kontenera:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>Sprawdzamy, czy mo\u017cemy si\u0119 zalogowa\u0107 do naszego kontenera z lokalnej maszyny:\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>Sprawd\u017amy r\u00f3wnie\u017c dzia\u0142anie subdomeny docker-ui.helloworld.ru:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPo klikni\u0119ciu przycisku Przegl\u0105daj repozytoria, przegl\u0105darka wy\u015bwietli okno logowania, w kt\u00f3rym nale\u017cy wprowadzi\u0107 nazw\u0119 u\u017cytkownika i has\u0142o do repozytorium. Nast\u0119pnie zostaniemy przeniesieni na stron\u0119 z list\u0105 repozytori\u00f3w (na razie b\u0119dzie ona pusta):<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Otwieramy porty w Plesk Firewall<\/h2>\n<p>\nPo zainstalowaniu i skonfigurowaniu komponent\u00f3w musimy otworzy\u0107 porty, aby komponenty by\u0142y dost\u0119pne z kontener\u00f3w Docker i z sieci zewn\u0119trznej.<\/p>\n<p>Zobaczmy, jak to zrobi\u0107 na przyk\u0142adzie wcze\u015bniej zainstalowanego rozszerzenia Firewall dla Plesk.<\/p>\n<ol>\n<li>Przechodzimy do <i>Narz\u0119dzia &amp; Ustawienia &gt; Ustawienia &gt; Firewall<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Przechodzimy do <i>Modyfikuj zasady Plesk Firewall &gt; Dodaj regu\u0142\u0119 niestandardow\u0105<\/i> i otwieramy nast\u0119puj\u0105ce porty TCP dla podsieci 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 w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Dodamy r\u00f3wnie\u017c regu\u0142\u0119, kt\u00f3ra otworzy porty PostgreSQL oraz panelu zarz\u0105dzania RabbitMQ dla zewn\u0119trznego \u015bwiata:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Zastosuj zasady za pomoc\u0105 przycisku Zastosuj zmiany:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Konfiguracja CI\/CD w Github Actions<\/h1>\n<p>\nPrzejd\u017amy do najbardziej interesuj\u0105cej cz\u0119\u015bci \u2014 konfiguracji pipeline\u2019u ci\u0105g\u0142ej integracji i dostarczania naszego projektu na serwer.<\/p>\n<p>Ten pipeline b\u0119dzie sk\u0142ada\u0142 si\u0119 z dw\u00f3ch cz\u0119\u015bci: <\/p>\n<ul>\n<li>budowy obrazu i uruchamiania test\u00f3w (dla backendu) \u2014 po stronie Github;<\/li>\n<li>uruchomienia migracji (dla backendu) i wdro\u017cenia kontener\u00f3w \u2014 na serwerze.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Wdro\u017cenie w Plesk<\/h2>\n<p>\nNajpierw zajmiemy si\u0119 drugim punktem (poniewa\u017c od niego zale\u017cy pierwszy).<\/p>\n<p>Proces wdro\u017cenia skonfigurujemy za pomoc\u0105 rozszerzenia Git dla Plesk.<\/p>\n<p>Rozwa\u017cymy przyk\u0142ad z \u015brodowiskiem Prod dla repozytorium Backend.<\/p>\n<ol>\n<li>Wchodzimy w subskrypcj\u0119 naszej strony Helloworld i przechodzimy do sekcji Git:\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>W polu \u201eZdalne repozytorium Git\u201d wklejamy link do naszego repozytorium Github i zmieniamy domy\u015blny folder <code>httpdocs<\/code> na inny (na przyk\u0142ad, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Kopiujemy klucz publiczny SSH z wcze\u015bniejszego kroku i <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">dodajemy<\/a><\/noindex> go w ustawieniach Github.<\/li>\n<li>Klikamy OK na ekranie w punkcie 2, po czym zostaniemy przeniesieni na stron\u0119 repozytorium w Plesk. Teraz musimy skonfigurowa\u0107 aktualizacj\u0119 repozytorium przy commitach do ga\u0142\u0119zi master. W tym celu przechodzimy do <i>Ustawienia repozytorium<\/i> i zapisujemy warto\u015b\u0107 <code>Webhook URL<\/code> (b\u0119dzie nam potrzebne p\u00f3\u017aniej przy konfiguracji Github Actions):\n<p><img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>W polu Akcje na ekranie z poprzedniego kroku wprowadzamy skrypt do uruchomienia wdro\u017cenia:\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>\ngdzie: <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 \u015bcie\u017cka do folderu prod repozytorium backend na serwerze;<br \/>\n<code>{ENV}<\/code> \u2014 \u015brodowisko (dev\/prod), w naszym przypadku <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 host naszego repozytorium dockerowego<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 token bota Telegram;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 ID czatu\/kanalu do wysy\u0142ania powiadomie\u0144.<\/p>\n<p>Przyk\u0142ad skryptu:<\/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>Dodajemy u\u017cytkownika z naszej subskrypcji do grupy Docker (aby m\u00f3g\u0142 zarz\u0105dza\u0107 kontenerami):\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\n\u015arodowisko deweloperskie dla repozytorium backend i frontend konfiguruje si\u0119 analogicznie.<\/p>\n<h2>Pipeline wdro\u017cenia w Github Actions<\/h2>\n<p>\nPrzechodzimy do konfiguracji pierwszej cz\u0119\u015bci naszego CI\/CD pipeline'a w Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nPipeline jest opisany w <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">pliku deploy.yml<\/a><\/noindex>. <\/p>\n<p>Ale przed jego analiz\u0105 wype\u0142nijmy w Github potrzebne nam zmienne Secret. W tym celu przechodzimy do <i>Ustawienia -&gt; Sekrety<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 host naszego repozytorium Docker (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 login do repozytorium Docker;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 has\u0142o do niego;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 host, na kt\u00f3rym dost\u0119pna jest administracja Plesk (przyk\u0142ad: <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 lub <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 do wdro\u017cenia w repozytorium prod na serwerze (otrzymali\u015bmy go w Rozw\u00f3j w Plesk p. 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 token do wdro\u017cenia w repozytorium dev na serwerze.<\/li>\n<\/ul>\n<p>\nProces wdro\u017cenia jest prosty i sk\u0142ada si\u0119 z trzech g\u0142\u00f3wnych krok\u00f3w: <\/p>\n<ul>\n<li>budowanie i publikacja obrazu w naszym repozytorium;<\/li>\n<li>uruchamianie test\u00f3w w kontenerze na podstawie \u015bwie\u017co zbudowanego obrazu;<\/li>\n<li>wdro\u017cenie w odpowiednim \u015brodowisku w zale\u017cno\u015bci od ga\u0142\u0119zi (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\">Plik deploy.yml dla repozytorium frontend<\/a><\/noindex> nie r\u00f3\u017cni si\u0119 zbytnio od backendowego. Brakuje w nim kroku z uruchamianiem test\u00f3w i zmieniane s\u0105 nazwy token\u00f3w do wdro\u017cenia. Zmienne sekrety dla repozytorium frontend, nawiasem m\u00f3wi\u0105c, trzeba wype\u0142nia\u0107 oddzielnie.<\/p>\n<h1>Konfiguracja strony<\/h1>\n<p><\/p>\n<h2>Proxy ruchu przez Nginx<\/h2>\n<p>\nNo c\u00f3\u017c, zbli\u017camy si\u0119 do ko\u0144ca. Pozostaje nam tylko skonfigurowa\u0107 proxy dla przychodz\u0105cego i wychodz\u0105cego ruchu do naszego kontenera przez Nginx. Proces ten ju\u017c om\u00f3wili\u015bmy w punkcie 5 konfiguracji Docker Registry. To samo trzeba powt\u00f3rzy\u0107 dla cz\u0119\u015bci backend i frontend w \u015brodowiskach dev i prod.<\/p>\n<p>Podam zrzuty ekranu ustawie\u0144.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD w GitHub Actions dla projektu na 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 w GitHub Actions dla projektu na Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>Wa\u017cne uzupe\u0142nienie<\/strong>. Do kontenera frontend b\u0119d\u0105 proxy'owane wszystkie URL-e, z wyj\u0105tkiem zaczynaj\u0105cych si\u0119 na <code>\/api\/<\/code> \u2014 b\u0119d\u0105 one proxy'owane do kontenera backend (dlatego <strong>w kontenerze backend wszystkie przetwarzacze musz\u0105 zaczyna\u0107 si\u0119 od <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>Podsumowanie<\/h2>\n<p>\nTeraz nasza strona powinna by\u0107 dost\u0119pna pod adresami helloworld.ru i dev.helloworld.ru (\u015brodowisko prod i dev odpowiednio).<\/p>\n<p>Podsumowuj\u0105c, dowiedzieli\u015bmy si\u0119, jak przygotowa\u0107 prost\u0105 aplikacj\u0119 na Flask i Angular oraz skonfigurowa\u0107 w Github Actions pipeline do jej wdro\u017cenia na serwerze zarz\u0105dzanym przez Plesk.<\/p>\n<p>Powt\u00f3rz\u0119 linki do repozytori\u00f3w z kodem: <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 \/>\u0179r\u00f3d\u0142o: <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.1.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\/pl\/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.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\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\/pl\/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 w Github Actions dla projektu na Flask+Angular | ProHoster","description":"W tym artykule podziel\u0119 si\u0119 swoim do\u015bwiadczeniem.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","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\/pl\/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\/pl\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}