CI/CD në Github Actions për një projekt me Flask+Angular

CI/CD në Github Actions për një projekt me Flask+Angular
Në këtë artikull, do të ndaj përvojën time në konfigurimin e CI/CD duke përdorur panelin e kontrollit Plesk dhe Github Actions. Sot do të mësojmë si të depolojmë një projekt të vogël me emrin "Helloworld". Ai është shkruar në kuadër Python Flask, me punëtorë në Celery dhe frontend në Angular 8.

Lidhjet për repozitorët: backend, frontend.

Në pjesën e parë të artikullit do të shohim projektin tonë dhe pjesët e tij. Në të dytën, do të kuptojmë si të konfigurojmë Plesk dhe të instalojmë zgjerimet dhe komponentët e nevojshëm (Baza e të Dhënave, RabbitMQ, Redis, Docker, etj.).

Në pjesën e tretë, përfundimisht do të kuptojmë se si të konfiguroni pipeline për të depolar projektin tonë në serverin në mjedisin dev dhe prod. Dhe pastaj do ta nisim faqen në server.

Po, harrova të prezantohem. Më quajnë Oleg Borzov, unë jam zhvillues fullstack në ekipin CRM për menaxherët e kredive hipotekare në Domklik.

Përmbledhja e projektit

Fillimisht, le të shqyrtojmë dy repozitorët e projektit - backend dhe frontend - dhe të kalojmë përmes kodit.

Pjesa backend: Flask+Celery

Për pjesën backend, kam marrë një kombinim të mjaft popullarizuar midis zhvilluesve Python: kuadri Flask (për API) dhe Celery (për radhën e detyrave). Si ORM përdoret SQLAlchemy. Për migrimet është përdorur Alembic. Për validimin e JSON në duar - Marshmallow.

Në repotitë e tij ka një skedar Readme.md me përshkrimin e detajuar të strukturës dhe udhëzimeve për fillimin e projektit.

API i pjesës web mjaft i thjeshtë, përbëhet nga 6 duar:

  • /ping — pĂ«r kontrollimin e disponueshmĂ«risĂ«;
  • duar pĂ«r regjistrimin, autorizimin, deautorizimin dhe marrjen e pĂ«rdoruesit tĂ« autorizuar;
  • duar pĂ«r dĂ«rgimin e email-it, e cila vendos njĂ« detyrĂ« nĂ« radhĂ«n e Celery.

Pjesa Celery përsa i përket thjeshtësisë, aty ka vetëm një detyrë send_mail_task.

Në dosjen /conf ka dy nënfoldra:

  • docker me dy skedarĂ« Docker (base.dockerfile pĂ«r ndĂ«rtimin e imazhit bazĂ« qĂ« ndryshohet rrallĂ« dhe Dockerfile pĂ«r ndĂ«rtimet kryesore);
  • .env_files — me skedarĂ« tĂ« variablave tĂ« mjedisit pĂ«r mjedisa tĂ« ndryshme.

Në rrënjën e projektit ndodhen katër skedarë docker-compose:

  • docker-compose.local.db.yml pĂ«r ngritjen e Bazeve tĂ« DhĂ«nash lokale pĂ«r zhvillimin;
  • docker-compose.local.workers.yml pĂ«r ngritjen lokale tĂ« punĂ«torĂ«ve, BazĂ«s tĂ« DhĂ«nash, Redis dhe RabbitMQ;
  • docker-compose.test.yml pĂ«r ekzekutimin e testeve gjatĂ« vendosjes;
  • docker-compose.yml pĂ«r depolin.

Dhe folderi i fundit që na intereson është .ci-cd. Në të gjenden skriptet shell për vendosje:

  • deploy.sh — nisja e migrimit dhe depolimit. Nis nĂ« server pas ndĂ«rtimit dhe ekzekutimit tĂ« testeve nĂ« Github Actions;
  • rollback.sh — kthimi i konteinerĂ«ve nĂ« versionin e mĂ«parshĂ«m tĂ« ndĂ«rtimit;
  • curl_tg.sh — dĂ«rgimi i njoftimeve pĂ«r desplejtimin nĂ« Telegram.

Frontend në Angular

Repoja me frontendin është shumë më e thjeshtë se backendi. Fronti përbëhet nga tre faqe:

  • Faqja kryesore me njĂ« formĂ« pĂ«r dĂ«rgimin e emailit dhe njĂ« buton pĂ«r dalje.
  • Faqja e hyrjes.
  • Faqja e regjistrimit.

Faqja kryesore duket askejtike:

CI/CD në Github Actions për një projekt me Flask+Angular
Në rrënjë ndodhen dy skedarë Dockerfile dhe docker-compose.yml, si dhe një dosje e njohur për ne .ci-cd me një numër pak më të vogël skriptesh se në repo-n e backendit (skriptet për ekzekutimin e testeve janë hequr).

Krijojmë projektin në Plesk

Të fillojmë me konfigurimin e Plesk dhe krijimin e një abonimi për faqen tonë.

Instalimi i zgjerimeve

Në Plesk na nevojiten katër zgjerime:

  • Docker pĂ«r menaxhimin dhe shfaqjen vizuale tĂ« gjendjes sĂ« konteinerĂ«ve nĂ« administratĂ«n e Plesk;
  • Git pĂ«r tĂ« konfiguruar hapat e dep limit nĂ« server;
  • Let's Encrypt pĂ«r gjenerimin (dhe rinovimin automatik) e sertifikatave falas TLS;
  • Firewall pĂ«r tĂ« konfiguruar filtrimin e trafikut tĂ« ardhshĂ«m.

Mund t'i instalojmë ato përmes administratës Plesk në seksionin Zgjerime:

CI/CD në Github Actions për një projekt me Flask+Angular
Ne nuk do ta shqyrtojmë konfigurimin e detajuar të zgjerimeve, për qëllimet tona demo do të mjaftojnë cilësimet e parazgjedhura.

Krijimi i abonimit dhe faqes

Më pas na nevojitet të krijojmë një abonim për faqen tonë helloworld.ru dhe të shtojmë një subdomain dev.helloworld.ru.

  1. Krijojmë një abonim për domainin helloworld.ru dhe tregojmë emrin e përdoruesit dhe fjalëkalimin për përdoruesin sistemor:

    CI/CD në Github Actions për një projekt me Flask+Angular
    NĂ« fund tĂ« faqes vendosim njĂ« kontrol Secure the domain with Let’s Encrypt, nĂ«se duam tĂ« konfigurojmĂ« HTTPS pĂ«r faqen:

    CI/CD në Github Actions për një projekt me Flask+Angular

  2. Më pas në këtë abonim krijojmë subdomain dev.helloworld.ru (për të cilin gjithashtu mund të nxjerrim një sertifikat falas TLS):

    CI/CD në Github Actions për një projekt me Flask+Angular

Instalimi i komponentëve serverikë

Ne kemi në dispozicion një server me OS Debian Stretch 9.12 dhe panelin e menaxhimit të instaluar Plesk Obsidian 18.0.27.

Na nevojitet të instalojmë dhe konfiguroni për projektin tonë:

  • PostgreSQL (nĂ« rastin tonĂ« do tĂ« jetĂ« njĂ« server me dy DB pĂ«r mjedisin dev dhe prod).
  • RabbitMQ (tĂ« njĂ«jtĂ«n gjĂ«, njĂ« instancĂ« me vhosts tĂ« ndryshĂ«m pĂ«r mjediset).
  • Dy instanca Redis (pĂ«r mjediset dev dhe prod).
  • Docker Registry (pĂ«r ruajtjen lokale tĂ« imazheve Docker tĂ« ndĂ«rtuara).
  • UI-interfaca pĂ«r regjistrin Docker.

PostgreSQL

NĂ« paketĂ« me Plesk vjen gjithashtu DB PostgreSQL, por jo versioni mĂ« i fundit (nĂ« kohĂ«n e shkrimit tĂ« kĂ«tij artikulli Plesk Obsidian mbĂ«shteste Postgres versionet 8.4–10.8). Ne duam pĂ«r aplikacionin tonĂ« versionin mĂ« tĂ« fundit (12.3 nĂ« kohĂ«n e shkrimit tĂ« kĂ«tij artikulli), prandaj do ta instalojmĂ« atĂ« manualisht.

Ka shumë udhëzime të detajuara për instalimin e Postgres në Debian në internet (është), prandaj nuk do t'i përshkruaj në detaje, vetëm do të sjell komandat:

wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'

sudo apt-get update
sudo apt-get install postgresql postgresql-contrib

Duke marrë parasysh se postavijet e paracaktuar në PostgreSQL janë mjaft mesatare, është e domosdoshme të rregullojmë konfigurimin. Këtë do ta ndihmojmë me një kalkulator: duhet të vendosni parametrat e serverit tuaj dhe të zëvendësoni konfigurimet në skedarin /etc/postgresql/12/main/postgresql.confme ato të sugjeruara. Këtu duhet të theksohet se këto kalkulatorë nuk janë një zgjidhje magjike, dhe baza duhet të optimizohet më në detaje, duke u bazuar në harduerin tuaj, aplikacionin dhe kompleksitetin e kërkesave. Por për fillim, kjo është e mjaftueshme.

PĂ«rveç konfigurimeve tĂ« propozuara nga kalkulatori, gjithashtu ndryshojmĂ« nĂ« postgresql.confportin 5432 tĂ« vendosur si parazgjedhje nĂ« njĂ« tjetĂ«r (nĂ« shembullin tonĂ« — 53983).

Pas ndryshimit të skedarit të konfigurimit, rihapim postgresql-server me komandën:

service postgresql restart

Kemi instaluar dhe konfiguruar PostgreSQL. Tani do të krijojmë një DB, përdoruesit për mjediset dev dhe prod, dhe do t'u japim përdoruesve të drejtat për të menaxhuar DB-në:

$ su - postgres
postgres:~$ create database hw_dev_db_name;
CREATE DATABASE
postgres:~$ create user hw_dev_db_user with password 'hw_dev_db_password';
CREATE ROLE
postgres:~$ grant ALL privileges ON database hw_dev_db_name to hw_dev_db_user;
GRANT
postgres:~$ create database hw_prod_db_name;
CREATE DATABASE
postgres:~$ create user hw_prod_db_user with password 'hw_prod_db_password';
CREATE ROLE
postgres:~$ grant ALL privileges ON database hw_prod_db_name to hw_prod_db_user;
GRANT

RabbitMQ

Tani do tĂ« kalojmĂ« te instalimi i RabbitMQ — broker pĂ«r mesazhe pĂ«r Celery. Instalimi i tij nĂ« Debian Ă«shtĂ« mjaft i thjeshtĂ«:

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
sudo dpkg -i erlang-solutions_1.0_all.deb

sudo apt-get update
sudo apt-get install erlang erlang-nox

sudo add-apt-repository 'deb http://www.rabbitmq.com/debian/ testing main'
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

sudo apt-get update
sudo apt-get install rabbitmq-server

Pas instalimit, na nevojitet të krijojmë vhosts, përdoruesit dhe t'u japim të drejtat e nevojshme:

sudo rabbitmqctl add_user hw_dev_amqp_user hw_dev_amqp_password 
sudo rabbitmqctl set_user_tags hw_dev_amqp_user administrator
sudo rabbitmqctl add_vhost hw_dev_vhost
sudo rabbitmqctl set_permissions -p hw_dev_vhost hw_dev_amqp_user ".*" ".*" ".*"

sudo rabbitmqctl add_user hw_prod_amqp_user hw_prod_amqp_password 
sudo rabbitmqctl set_user_tags hw_prod_amqp_user administrator
sudo rabbitmqctl add_vhost hw_prod_vhost
sudo rabbitmqctl set_permissions -p hw_prod_vhost hw_prod_amqp_user ".*" ".*" ".*"

Redis

Tani do tĂ« instalojmĂ« dhe konfigurojmĂ« komponentin e fundit pĂ«r aplikacionin tonĂ« — Redis. Ai do tĂ« pĂ«rdoret si backend pĂ«r ruajtjen e rezultateve tĂ« detyrave Celery.

Ne do të ngremë dy kontejnerë Docker me Redis për ambientet dev dhe prod duke përdorur zgjerimin Docker për Plesk.

  1. Hyni në Plesk, kalojmë në seksionin Shtesat, kërkojmë shtesën Docker dhe e instalojmë atë (na nevojitet versioni falas):

    CI/CD në Github Actions për një projekt me Flask+Angular

  2. Kalim në shtesën e instaluar, gjejmë përmes kërkimit imazhin redis bitnami dhe vendosim versionin e fundit:

    CI/CD në Github Actions për një projekt me Flask+Angular

  3. Hyni në kontejnerin e shkarkuar dhe rregullojmë konfigurimin: specifikojmë portin, madhësinë maksimale të RAM-it, fjalëkalimin në variablat e mjedisit dhe montojmë volum:

    CI/CD në Github Actions për një projekt me Flask+Angular

  4. Kryejmë hapat 2-3 për kontejnerin prod, në cilësimet vetëm ndryshojmë parametrat: portin, fjalëkalimin, madhësinë e RAM-it dhe rrugën për dosjen e volumit në server:

    CI/CD në Github Actions për një projekt me Flask+Angular

Docker Registry

Përveç shërbimeve bazë, do të ishte mirë të vendosnim në server një depo të vetë vonuar të imazheve Docker. Fatmirësisht, vendi në servera tani është mjaft i lirë (me siguri më lirë se pajtimi me DockerHub), dhe procesi i instalimit të një depoje private është shumë i thjeshtë.

Duam që të kemi të instaluar:

Për këtë:

  1. Do tĂ« krijojmĂ« nĂ« Plesk dy subdomenĂ« nĂ« pajtimin tonĂ«: docker.helloworld.ru dhe docker-ui.helloworld.ru, dhe do tĂ« konfigurojmĂ« pĂ«r ta certifikatat Let’s Encrypt.
  2. Në dosjen e subdomenit docker.helloworld.ru do të shtojmë një skedë docker-compose.yml me këtë përmbajtje:
    version: "3"
    
    services:
      docker-registry:
        image: "registry:2"
        restart: always
        ports:
          - "53985:5000"
        environment:
          REGISTRY_AUTH: htpasswd
          REGISTRY_AUTH_HTPASSWD_REALM: basic-realm
          REGISTRY_AUTH_HTPASSWD_PATH: /auth/.htpasswd
          REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
        volumes:
          - ./docker-registry.htpasswd:/auth/.htpasswd
          - ./data:/data
    
      docker-registry-ui:
        image: konradkleine/docker-registry-frontend:v2
        restart: always
        ports:
          - "53986:80"
        environment:
          VIRTUAL_HOST: '*, https://*'
          ENV_DOCKER_REGISTRY_HOST: 'docker-registry'
          ENV_DOCKER_REGISTRY_PORT: 5000
        links:
          - 'docker-registry'
    

  3. Me SSH do të gjenerojmë skedën .htpasswd për autorizimin Basic në depo Docker:
    htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password
  4. Do të ndërtosh dhe ngritësh kontejnerët:
    docker-compose up -d
  5. Dhe duhet të drejtojmë Nginx drejt kontejnerëve tanë. Kjo mund të bëhet përmes Plesk.

Veprimet e mëposhtme duhet të kryhen për subdomenët docker.helloworld.ru dhe docker-ui.helloworld.ru:

Në seksionin Dev Tools në faqen tonë hyni në Rregullat e Proxy të Dockerit:

CI/CD në Github Actions për një projekt me Flask+Angular
Dhe shtoni një rregull për një proxy të trafikut të ardhshëm në kontejnerin tonë:

CI/CD në Github Actions për një projekt me Flask+Angular

  1. Kontrollojmë nëse mund të autorizojmë në kontejnerin tonë nga makina lokale:
    $ docker login docker.helloworld.ru -u hw_docker_admin -p hw_docker_password
    WARNING! Using --password via the CLI is insecure. Use --password-stdin.
    Login Succeeded
  2. Po ashtu do të kontrollojmë funksionimin e subdomenit docker-ui.helloworld.ru:

    CI/CD në Github Actions për një projekt me Flask+Angular
    Duke kliku në Browse repositories, shfletuesi do të tregojë një dritare për autorizimin, ku duhet të futni emrin e përdoruesit dhe fjalëkalimin për repositorin. Pas kësaj, do të na çojë në një faqe me listën e repositorëve (në fillim ajo do të jetë bosh):

    CI/CD në Github Actions për një projekt me Flask+Angular

Hapim portat në Plesk Firewall

Pas instalimit dhe konfigurimit të komponenteve, duhet të hapim portat që komponentet të jenë të qasshëm nga konteinerët Docker dhe nga rrjeti i jashtëm.

Le të shohim si ta bëjmë këtë, me shembullin e extensions Firewall për Plesk që kemi instaluar më parë.

  1. Kalojmë te Tools & Settings > Settings > Firewall:
    CI/CD në Github Actions për një projekt me Flask+Angular
  2. Kalojmë te Modifiko rregullat e Plesk Firewall > Shto Rregull të Personalizuar dhe hapim portat TCP në subnetin Docker (172.0.0.0/8):
    RabbitMQ: 1883, 4369, 5671-5672, 25672, 61613-61614
    Redis: 32785, 32786

    CI/CD në Github Actions për një projekt me Flask+Angular

  3. Gjithashtu do të shtojmë një rregull që do të hapë portat për PostgreSQL dhe panelin e menaxhimit të RabbitMQ për botën e jashtme:

    CI/CD në Github Actions për një projekt me Flask+Angular

  4. Përdorim rregullat me butonin Apply Changes:

    CI/CD në Github Actions për një projekt me Flask+Angular

Konfigurimi i CI/CD në Github Actions

Le tĂ« fillojmĂ« me pjesĂ«n mĂ« interesante — konfigurimin e pipeline pĂ«r integrimin dhe dorĂ«zimin e projektit tonĂ« nĂ« server.

Ky pipeline do të përbëhet nga dy pjesë:

  • nĂ« ndĂ«rtimin e imazhit dhe ekzekutimin e testeve (pĂ«r backend) — nĂ« anĂ«n e Github;
  • ekzekutimi i migrimeve (pĂ«r backend) dhe shpĂ«rndarja e konteinerĂ«ve — nĂ« server.

Shpërndarja në Plesk

Së pari do të merremi me pikën e dytë (sepse ndikon në të parën).

Procesi i shpërndarjes do ta vendosim me ndihmën e plugin-it Git për Plesk.

Le të shqyrtojmë një shembull me ambientin Prod për repositorin e Backend.

  1. Hymë në abonimin e faqes sonë Helloworld dhe kalojmë në nënseksionin Git:

    CI/CD në Github Actions për një projekt me Flask+Angular

  2. Futni në fushën "Remote Git repository" lidhjen për repositorin tonë në Github dhe ndryshoni folderin nga e para httpdocs në një tjetër (për shembull, /httpdocs/hw_back):

    CI/CD në Github Actions për një projekt me Flask+Angular

  3. Kopjojmë çelësin publik SSH nga faza e mëparshme dhe shtojmë e vendosim në konfigurimet e Github.
  4. Klikoni OK në ekranin në pikën 2, pas së cilës do të na çojë në faqen e repositorit në Plesk. Tani duhet të konfigurojmë përditësimin e repositorit gjatë komiteteve në degën master. Për këtë kalojmë në Repository Settings dhe ruajmë vlerën Webhook URL (do na nevojitet më vonë gjatë konfigurimit të Github Actions):

    CI/CD në Github Actions për një projekt me Flask+Angular

  5. Në fushën e Actions në ekranin nga pika paraprake, vendosim skriptin për të nisur shpërndarjen:
    cd {REPOSITORY_ABSOLUTE_PATH}
    .ci-cd/deploy.sh {ENV} {DOCKER_REGISTRY_HOST} {DOCKER_USER} {DOCKER_PASSWORD} {TG_BOT_TOKEN} {TG_CHAT_ID} 

    ku:

    {REPOSITORY_ABSOLUTE_PATH} — rruga e folderit prod tĂ« repositorit backend nĂ« server;
    {ENV} — ambienti (dev/prod), nĂ« rastin tonĂ« prod;
    {DOCKER_REGISTRY_HOST} — hosti i repositorit tonĂ« docker
    {TG_BOT_TOKEN} — tokeni i botit Telegram;
    {TG_CHAT_ID} — ID e bisedĂ«s/kanalit pĂ«r dĂ«rgimin e njoftimeve.

    Shembulli i skriptit:

    cd /var/www/vhosts/helloworld.ru/httpdocs/hw_back/
    .ci-cd/deploy.sh dev docker.helloworld.ru docker_user docker_password 12345678:AAbcdEfghCH1vGbCasdfSAs0K5PALDsaw -1001234567890
  6. Shtojmë përdoruesin nga abonimi ynë në grupin Docker (që të mund të menaxhojë kontejnerët):
    sudo usermod -aG docker helloworld_admin

Mjedisi Dev për repositorin backend dhe frontend konfigurohen në mënyrë të ngjashme.

Pipeline i deploy-it në Github Actions

Kalojmë në konfigurimin e pjesës së parë të CI/CD pipelines tonë në Github Actions.

Backend

Pipeline përshkruhet në skedarin deploy.yml.

Por para se ta shqyrtojmë, le të plotësojmë në Github variable Secret që na nevojiten. Për këtë kalojmë në Cilësimet -> Sekretet:

  • DOCKER_REGISTRY — host-i i repositorit tonĂ« Docker (docker.helloworld.ru);
  • DOCKER_LOGIN — logari pĂ«r repositorin Docker;
  • DOCKER_PASSWORD — fjalĂ«kalimi pĂ«r tĂ«;
  • DEPLOY_HOST — host-i ku admin panel-i Plesk Ă«shtĂ« e aksesueshme (p.sh.: helloworld.ru:8443 ose 123.4.56.78:8443);
  • DEPLOY_BACK_PROD_TOKEN — token-i pĂ«r deploy nĂ« repo-prod nĂ« server (e kemi marrĂ« atĂ« nĂ« Ngritjen nĂ« Plesk pika 4);
  • DEPLOY_BACK_DEV_TOKEN — token-i pĂ«r deploy nĂ« repo-dev nĂ« server.

Procesi i deploy-it është i thjeshtë dhe përbëhet nga tre hapa kryesorë:

  • ndĂ«rtimi dhe publikimi i imazhit nĂ« repositorin tonĂ«;
  • ekzekutimi i testeve nĂ« konteinerin e bazuar nĂ« imazhin e sapo ndĂ«rtuar;
  • implementimi nĂ« mjedisin e nevojshĂ«m nĂ« varĂ«si tĂ« degĂ«s (dev/master).

Frontend

Skedari deploy.yml për repositorin frontend nuk ndryshon shumë nga ai i backend-it. Në të mungon hapi me ekzekutimin e testeve dhe emrat e token-ave për deploy ndryshojnë. Sektret për repositorin frontend, për më shumë, duhet të plotësohen veçmas.

Konfigurimi i faqes

Proximi i trafikut përmes Nginx

E pra, kemi arritur në fund. Mbete vetëm të konfigurojmë proksimin e trafikut hyrës dhe dalës në kontejnerin tonë përmes Nginx. Ky proces e kemi shqyrtuar tashmë në pikën 5 të konfigurimit të Docker Registry. E njëjta gjë duhet të përsëritet për pjesët backend dhe frontend në mjediset dev dhe prod.

Do të jap disa ekranë të konfigurimeve.

Backend

CI/CD në Github Actions për një projekt me Flask+Angular

Frontend

CI/CD në Github Actions për një projekt me Flask+Angular
Sqarim i rĂ«ndĂ«sishĂ«m. NĂ« konteinerin frontend do tĂ« proksisohen tĂ« gjitha URL-tĂ«, pĂ«rveç atyre qĂ« fillojnĂ« me /api/ — ato do tĂ« proksisohen nĂ« konteinerin backend (prandaj nĂ« konteinerin backend tĂ« gjithĂ« procesorĂ«t duhet tĂ« fillojnĂ« me /api/).

Përfundime

Tani faqja jonë duhet të jetë e aksesueshme në adresat helloworld.ru dhe dev.helloworld.ru (mjedisi prod dhe dev përkatësisht).

Të klarojmë, mësuam se si të përgatisim një aplikacion të thjeshtë në Flask dhe Angular dhe të konfigurojmë në Github Actions pipeline-in për ta ekzekutuar atë në serverin nën administratën e Plesk.

Do të ripjegoj lidhjet në repositorët me kodin: backend, frontend.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster