Локальна веб-розробка vs Vagrant vs Docker: що підійде вам?

21 хв. читання

Якими інструменти ви користуєтесь для створення веб-застосунків? Ми проводимо у середовищі розробки багато часу, тому до його організації слід ставитись серйозно. Оскільки вдало підібрані інструменти допомагатимуть вам у процесі розробки й допомагають вам домогтися бажаного результату. Натомість з невдалим вибором ваша робота буде неефективною.

Не витрачайте свій дорогоцінний час на інструменти, що не підходять вашому проекту!

У статті оглянемо декілька моделей веб-розробки та необхідні для цього інструменти.

Для веб-застосунку у статті було обрано Django, щоб на фрагментах коду продемонструвати поширені шаблони веб-фреймворків. Але якщо ви зробили інший вибір, нічого страшного.

Почнемо з простого

Вчора ви розпочали роботу над вашим проектом. У вас є грандіозні плани та очікування.

Ви не вперше маєте справу з Python, тому знаєте, що робити:

  • Розпочати з віртуального середовища
$ python3 -m venv venv && source venv/bin/activate
  • Встановити веб-фреймворк
(venv)$ pip install Django
  • Згенерувати шаблонний код для початку роботи
(venv)$ django-admin startproject new_hot_thing && cd new_hot_thing
(venv)$ ./manage.py startapp awesome
# Run more Django stuff
  • Запустити сервер
(venv)$ ./manage.py runserver

Таку послідовність ви знайдете у багатьох веб-фреймворках Python. Ви зробили місце для коду, згенерували код зі стартових шаблонів та запустили його на локальному веб-сервері. Вже можна робити серйозні кроки вперед.

Які переваги таких налаштувань?

  1. Все так легко, як і здається. Низький поріг входу робить веб-розробку доступною для багатьох.
  2. Кожен гідний уваги фреймворк пояснить наведену послідовність кроків. Посібники покликані допомогти опанувати технологію, щоб залучити розробників до екосистеми. Тобто ви знайдете достатньо підтримки, якщо підете далі.
  3. Як чудово бачити успішно створену сторінку на веб-сервері після п'яти хвилин роботи. Цей час того вартий!
  4. Ваші тести повинні бути швидкими, тому що запускатимуться одразу з вашої локальної операційної системи.

Як щодо недоліків?

  1. Запустити усе на іншій машині буде боляче. Оскільки ви встановили Django прямо з pip, вам слід запам'ятати цей крок при запуску проекту ще будь-де. Коли ви захочете поділитися вашим творінням зі світом, запуск застосунку з вашого ноутбуку може пройти невдало.
  2. Локальна операційна система не буде збігатися з операційною системою «живого» веб-сайту.Зазвичай, ви працюєте на Windows. Можливо, ви користувач macOS, але, за статистикою, Windows переважає. Більшість веб-сайтів працюють на AWS, GCP, Azure або на віртуальних приватних серверах (VPS), як Digital Ocean або Linode. Багато застосунків на цих сервісах використовують Linux. Проблема у тому, що різниця між Windows та Linux суттєва (згадаймо хоча б відмінність у файловій системі: / та \\).
  3. Локальний веб-сервер має свої обмеження. Його інструменти призначені для локальної розробки, тому не відрізняються широким функціоналом. Хочете протестувати підтримку HTTPS з таким сервером?Не пощастило.

Завершення початкового етапу

Минають тижні або місяці. Версія 1.0 проекту майже готова, а ваш сайт зростає.

У цей час робочий процес виходить з-під контролю. Ви все ще працюєте на локальному сервері, але вже почали запускати Celery для обробки фонових завдань. Ви знайшли застосування кешуванню, тому Redis також у дії. Крім всього, вашому проекту потрібен JavaScript, щоб оживити UI, а це означає, що тепер вам потрібен Webpack.

Управління такою системою — божевілля. У вас відкрито чотири термінали, де ви запускаєте різні процеси для перевірки сайту. Поширена проблема для проекту, що росте. Настав час Procfile та інструментів управління процесами.

Багато років тому у спільноті Ruby було створено Foreman. Foreman — інструмент, що управляє запуском декількох процесів однією командою. Кожному процесу присвоюється мітка (наприклад, web), і усе логування направляється у єдиний термінал.

У Profile ви визначаєте процеси для запуску приблизно таким чином:

web: ./manage.py runserver
worker: celery worker --app new_hot_thing:celeryapp --loglevel info
frontend: webpack --watch

Так дуже швидко запустити (та зупинити) систему. Heroku також використовує модель Profile, щоб визначити вашу систему як сервіс для їх платформи. Це дуже зручно, якщо ви хочете швидко запустити проект без клопоту з хмарною інфраструктурою.

Foreman — чудовий вибір, якщо ваш застосунок на Ruby on Rails або Sinatra, тому що ви матимете готове середовище виконання для Ruby.

Але ми працюємо з Django!

На щастя, у Python є схожий інструмент, під назвою Honcho. Ті самі переваги, але Ruby не потрібен.

Почнемо роботу з ним так:

(venv)$ honcho start
23:02:53 system     | web.1 started (pid=25464)
23:02:53 system     | worker.1 started (pid=25465)
23:02:53 system     | frontend.1 started (pid=25466)
23:02:53 frontend.1 | some logging
23:02:53 web.1      | some more logging
23:02:54 worker.1   | even more logging

Переваги такої моделі

  1. Можливість об'єднати все в одному інструменті. Якщо ви працюєте у невеликій команді з різними спеціалістами (наприклад, бекенд-розробники на Python, фронтенд розробники на JavaScript), кожен може запустити систему через honcho. Фронтенд-розробники можуть прокачати свої навички webpack, а бекенд зможе змінити runserver на більш функціональний сервер, такий як gunicorn або uwsgi.
  2. Ця система досі працює так швидко, як дозволяє ваша операційна система. Ніяких додаткових шарів, оскільки honcho запускає нативні процеси ОС.
  3. Вам більше не треба перестрибувати між терміналами або викликати tail для декількох файлів с логами.

Погані аспекти

  1. Складне налаштування. Наприклад, якщо ви запускаєте Celery локально, це означає, що вам потрібен RabbitMQ. Тож треба встановити усі додаткові інструменти, які потрібні для роботи системи.
  2. Якщо ви створювали засоби автоматизації для деплою на ваш сайт, ви можете легко створити розрив між тим, що потрібно для локального налаштування, а що для хмарного сервера.

Початковий етап №2

Якщо ваш проект стає завеликим, а рухомих частин багато (або ви підтримуєте декількох клієнтів, які можуть викликати конфлікт у налаштуванні), робота на локальній операційній системі втрачає свою привабливість.

Вас також може турбувати, що ваш «живий» сайт працює на іншій операційній системі.

Стаємо віртуальними.

Єдиний спосіб продовжити працювати з проектом локально — змінити операційну систему. Але це не дуже практично для більшості розробників.

Натомість, ви можете запустити ваш код на віртуальній машині (Virtual Machine, VM). Віртуальна машина дає повноцінній операційній системі запускатися всередині вашої локальної (хостової) ОС.

Для запуску віртуальної машини потрібен гіпервізор(наприклад, VirtualBox). Його завдання — здаватися фізичною машиною. Саме тому віртуальна машина працює так, ніби встановлена безпосередньо на «залізі».

Найбільш популярний інструмент для запуску середовища розробки на віртуальній машині — Vagrant. З ним взаємодія з гіпервізором буде плавною.

Щоб запустити вашу віртуальну машину, виконайте:

$ vagrant up

Vagrant завантажить операційну систему, якщо її ще немає. Далі він запустить ОС та сконфігурує її з акаунтом користувача. Тож можна швидко перейти до роботи з віртуальною машиною. Щоб виконувати команди на VM, ви з'єднуєтесь з нею як з віддаленим комп'ютером. Для цього Vagrant встановлює деякі сертифікати безпеки. Тепер можна виконати:

$ vagrant ssh

Після з'єднання з віртуальною машиною, вона буде поводитись як будь-яка інша операційна система, з якою ви мали справу раніше.

Налаштування інструментів foreman/honcho було пов'язано з Profile, тут маємо справу з Vagrantfile, де міститься інформація про віртуальну машину. З ним можна:

  1. Вказати Vagrant на якому образі операційної системи слід запускати віртуальну машину.
  2. Зіставити директорію хостової ОС з директорією на VM. Так ми отримаємо швидкий доступ до коду проекту одразу з віртуальної машини.
  3. Запускати скрипти налаштування віртуальної машини для запуску веб-сервера або налаштування будь-яких інструментів для запуску вашого сайту.

Такий підхід досить багатошаровий, тож навіщо використовувати його?

  1. Ваше середовище розробки запускатиметься на тій самій операційній системі, як і ваш сайт. Будь-які проблеми, які ви зустрінете і налагодите для сайту під час розробки, будуть розв'язані і для сайту на продакшині.
  2. Ви не будете забруднювати хостову операційну систему зайвими програмами, які не потрібні вам регулярно. Тепер запуск декількох проектів буде простішим і безпечнішим, тому що ви не турбуватиметесь про конфлікт.
  3. Чим менше відмінностей між локальною операційною системою та ОС, на якій запущено ваш «живий» сайт, тим легше проводити налагодження.
  4. Простіше ділитись з командою. Оскільки вам треба використовувати автоматизовані скрипти, щоб віртуальна машина запускалась з правильними залежностями, вашим колегам буде легше налаштувати все так само.

Не все так радісно

  1. Такий підхід повільніший. Запуск віртуальної машини потребує запуску віртуальної ОС та хостової ОС. Очікуйте втрати продуктивності на 10-20%.
  2. Більше шарів — більше проблем. Між віртуальною машиною та хостовою ОС часто виникають конфлікти. З такою проблемою ви не зустрінетесь локально.
  3. Залежність від конфігураційних інструментів. Автоматизовані скрипти для віртуальної машини можуть бути настільки складними, що ніхто не захоче їх чіпати.

Ви можете зайти дуже далеко з розробкою на віртуальній машині. Це надійне рішення для команд на довгий час.

Але що ви зможете зробити, якщо проект стане великим? Дійсно великим?

Якщо проект дійсно великий

Інший популярний підхід у розробці — об'єднання залежностей та веб-застосунку в єдине ціле, контейнер.

Контейнери стали популярними декілька років тому, з розвитком Docker. Від того часу, індустрію ПЗ заповнили дискусії щодо контейнерів.

Навіщо використовувати контейнери для власного застосунку?

При використанні контейнерів ви намагаєтесь усунути невідповідності між сайтом під час розробки та сайтом на продакшині. Ця проблема вже неодноразово згадувалась у статті.

Повернемось до прикладу локальної розробки. Якщо дуже примітивно, то ті елементи, що встановлювались для запуску сайту з runserver, не працюватимуть на продакшині. Якщо ви з тої більшості, що користується Windows на десктопі та Linux у хмарі, ви одразу отримаєте відмінності в ОС та базових речах, на зразок віртуальних середовищ.

Якщо ваш новий грандіозний проект — заміна Instagram? У такому разі ви будете працювати з зображеннями. Для бібліотек зображень властиві дивні залежності (привіт, PIL!) Ймовірно, що ваше локальне налаштування відрізнятиметься від продакшена.

Готові до поганих новин? Взаємні залежності та проблеми середовища ускладнюються зі зростанням вашого сайту та системи.

Контейнерні технології, на зразок Docker, намагаються розв'язати ці проблеми. Саме тому зрілі команди зі складними системами обирають контейнери.

Вантажні контейнери як аналогія

Сенс у тому, що такі контейнери подібні до вантажних.

Вантажні контейнери успішно перевозять товари, тому що мають стандартний розмір та інтерфейс, який полегшує роботу з ними. Якщо ви бачили один такий контейнер — ви бачили їх всі.

Зі стандартами розмірів, індустрія перевезень може надати інструменти для перевезення усіх видів товарів: як машин, так і плюшевих ведмежат. У будь-якому разі інструменти, що переміщують ці контейнери будуть однаковими. Такий підхід ефективний для перевезення вантажу по всьому світу.

Docker намагається зробити щось подібне для програмного забезпечення. Якщо можна помістити весь код і залежності в контейнер (наприклад, річ із загальним інтерфейсом), то переміщення такого контейнеру з одного середовища в інше полегшиться.

Контейнери на практиці

Це була теорія. Наскільки добре поводяться контейнери на практиці?

Усе залежить від кількості працівників, обсягу контролю та навіть мови програмування, на якій ви створюєте ваш веб-застосунок.

Після створення контейнера (технічно, образу контейнера, але поки що не вдаємося у подробиці), ви можете перемістити цей контейнер та приєднати до системи, що керує контейнерами.

Ось де важлива кількість персоналу. Існують сервіси, що керують виключно контейнерами, на зразок AWS Fargate. За допомогою Fargate Amazon запустить ваші контейнери на їх інфраструктурі, а вам не потрібно буде нічим керувати. З огляду на вартість часу розробника, це може бути чудовою вигодою для вас. Окрім вартості, до недоліків підходу можна віднести менший обсяг контролю.

Якщо вам треба більше контролю за середовищем контейнерів, зупиніться на Kubernetes. Kubernetes (часто скорочено k8s) — інструмент для «координації» контейнерів, організовує роботу з величезною розподіленою інфраструктурою. І це досить логічно, коли ви дізнаєтесь, що до проекту причетний Google.

З Kubernetes ви уявляєте свій контейнер/проект сервісом, якому слід бути достатньо гнучким для переміщення на інші машини в постійно мінливій інфраструктурі. Існує ціла купа інструментів та сервісів, які з'явилися для підтримки такої моделі розробки. Якщо звучить страхітливо (а це цілком виправдано), то вашому проекту така фіча не потрібна.

Великі постачальники хмарних сховищ, на зразок Google, пропонують Kubernetes для своєї інфраструктури. Але налаштувати великий проект на використання k8s дуже складно. Якщо команда DevOps буде працювати фул-тайм, то таке налаштування все одно займе місяці.

Розробка з контейнерами

Ми навіть не поговорили про процес розробки ПЗ у світі контейнерів!

Не потрібно багато часу, щоб втягнутися у цей вихор технічних проблем управління проектом у розробці/продакшині. У цьому вихрі можна забути, що хтось все ще повинен виробляти продукт.

Якщо проблема розробки/продакшину шкодить вашій команді, і ви вирішили перейти на контейнери та заплатити за це відповідну ціну, подивимось що це означатиме для розробників.

Першим кроком при роботі з контейнером є створення образу контейнера. Оскільки Docker — найбільш популярна контейнерна технологія, ми скористаємось ним для нашого прикладу з Django. Щоб створити контейнер, вашому проекту знадобиться Dockerfile.

Примітка: Procfile. Vagrantfile. Dockerfile. Хоч тут є певна постійність.

Dockerfile являє собою набір команд, які описують створення образу проекту. Приклад нижче не претендує на звання «кращої практики». Він скоріше допоможе вам зрозуміти що відбувається у Dockerfile.

FROM ubuntu:xenial

RUN apt-get update && apt-get install -y \\
    python-pip

ADD ./requirements.txt /srv/new_hot_thing/requirements.txt
RUN pip install -r /srv/new_hot_thing/requirements.txt

WORKDIR /srv/new_hot_thing
ADD . /srv/new_hot_thing

EXPOSE 8000

CMD ["uwsgi", "--ini", "uwsgi.ini"]

Спочатку бачимо базовий образ ubuntu:xenial, далі — встановлення деяких пакетів Python, описання команд, якими Docker буде запускати та виконувати контейнер (наприклад, uwsgi).

Образ створюється з директорії, де знаходиться Dockerfile:

$ docker build -t new-hot-thing:1234 

Образ не робить нічого сам по собі. Це артефакт. Ви можете вважати його планом процесу, який буде запущено в майбутньому. При запуску образа контейнеру, створюється його екземпляр. Цей живий процес відповідальний за виконання коду. Образ запускається командою run:

$ docker run new-hot-thing:1234

Послідовність дій для роботи з контейнерами:

  1. Написати деякий код;
  2. Створити образ;
  3. Запустити образ як екземпляр контейнера.

Запуск екземпляра — єдиний спосіб виконати юніт-тести. Оскільки в контейнерах зберігаються залежності, ваш робочий процес почне обертатися навколо Docker. Для взаємодії з кодом тепер потрібні wrapper-скрипти. Наприклад, PyPI створено з Warehouse. Проект використовує Makefile для запуску загальних команд Docker, на зразок make tests.

Розробка з декількома контейнерами (без k8s)

Спостережливі читачі могли помітити, що контейнер працює тільки на веб-сервері. Що сталося з Celery, Redis, та Webpack? Правильна відповідь — зробити більше образів контейнера.

Щоб повернути такий рівень продуктивності, як з honcho для локальної розробки, необхідно запустити декілька контейнерів у тандемі. Для тих, хто обрав Docker, ваш інструмент — Docker Compose. Docker Compose дає набір інструментів для зв'язування образів Docker, щоб створити систему локально.

Якщо ви обрали Docker Compose, вам потрібен YAML-файл. Він називається docker-compose.yml. Ми знову можемо використовувати Warehouse, оскільки в проекті є широкий docker-compose.yml. Він розбиває систему на «сервіси»: дуже схоже на те, що ми бачили у Procfile. Фактично, Warehouse використовує деякі назви з нашого прикладу, наприклад web та worker. З таким конфігураційним файлом ми можемо створити всі контейнери, необхідні для роботи системи.

Ось приклад сервісу (трохи скорочено для наочності):

 worker:
    build:
      context: .
      args:
        DEVEL: "yes"
    command: hupper -m celery -A warehouse worker -B -l info
    volumes:
      - ./warehouse:/opt/warehouse/src/warehouse:z
    env_file: dev/environment
    links:
      - db
      - redis

Щоб почати шоу:

$ docker-compose up

Розробка з декількома контейнерами (з k8s)

Коли згадую Kubernetes, майже кожному DevOps стає погано. Я пояснюю таку поведінку відсутністю знань про те, як правильно працювати з цією технологією.

Ми вже відчули складність, яку представляють контейнери. Kubernetes — ще один шар складності. Як розробнику вижити з цим усім?

Оскільки Kubernetes зовсім інша технологія з власним словником (наприклад, поди, простори імен, вузли), то і робота з нею значно відрізнятиметься.

На відміну від Docker Compose, у Kubernetes більш гнучка топологія. Контейнери працюють всередині кластера. Кластер — набір вузлів (тобто машин), що працюють спільно та управляються k8s у динамічно змінюваній конфігурації.

При роботі з Kubernetes ми спочатку отримуємо визначення контейнера і розміщуємо його у Pod.Контейнер визначає потреби рівня застосунку, такі як мовне середовище виконання і залежності, в той час як Pod визначає потреби рівня кластера, такі як необхідна пам'ять і використання процесора. Pod вказує Kubernetes як виділити застосунку деяку кількість вузлів в кластері.

Якщо ви хочете працювати з Kubernetes, вам потрібен кластер. Спочатку, ваші розробники користуватимуться minikube. minikube — кластер з одного вузла, що містить усі основні компоненти k8s всередині однієї віртуальної машини.

minikube — це Kubernetes у коробці.

Кластер, за замовчуванням, не матиме сервіси вашого застосунку. Ми можемо задеплоїти ці сервіси у minikube або будь-який інший кластер з kubectl або іншим інструментом, таким як Helm. Розглянемо деталі.

Предмет конфігураційних інструментів залишимо поза увагою, тому що ця тема не вміщується у цю (вже велику) статтю. У будь-якому з розглянутих нами підходів розробки конфігураційні інструменти автоматизують певні частини деплою. Ви можете розгорнути ваш застосунок локально або на віртуальній машині з такими інструментами, як Ansible, Puppet або Chef. Ви можете працювати з контейнерами з таким інструментом, як Helm. У будь-якому разі, вам потрібні допоміжні інструменти.

Неприємний недолік роботи з контейнерами — постійна необхідність створювати образи контейнера для запуску коду. Microsoft і Google створили інструменти для обходу такого обмеження.

Для полегшення вашої розробки з Kubernetes Microsoft випустив інструмент, під назвою Draft, а Google — Skaffold.

У кожного з цих інструментів різний підхід, але можна підсумувати їх основну функцію: запускати процес, який буде відстежувати зміни у коді, а також створювати і розгортати застосунок у кластері Kubernetes при виявленні змін.

Щоб запустити процес у Draft:

$ draft up

Для Skaffold:

$ skaffold dev

Обидва інструменти роблять приблизно одне й те саме:

  1. Відстежують зміни коду;
  2. Створюють новий образ контейнера;
  3. Відправляють образ у репозиторій контейнера, якщо ви працюєте на віддаленому кластері (minikube пропустить цей повільний крок);
  4. Розгортають образ контейнера на кластері розробки за допомогою конфігураційних інструментів.

Перевірте особливості кожного інструменту самостійно, щоб вирішити який краще підходить саме вам. На думку автора, Skaffold краще рішення через підтримку декількох конфігураційних інструментів та гнучкість у створенні контейнера.

Про розробку з контейнерами вже було сказано багато. Визначимо переваги та недоліки.

Що робить контейнери хорошим вибором?

  1. З контейнерами ви будете впевнені, що ваш проект у розробці працює майже ідентично до проекту на продакшині.
  2. Кількість рухомих частин в окремому контейнері менша, ніж у великій суцільній системі. Тому контейнери простіші.
  3. Контейнери організовують систему як архітектуру, орієнтовану на сервіси. Можна сперечатися з приводу корисності такого підходу, але так маємо чіткі межі між різними частинами системи, що є перевагою для дуже великих команд.

Контейнери мають певні недоліки

  1. Контейнери створюють багатошаровість і додають системі складності.
  2. Створення образів контейнера вимагає часу і призводить до розбіжностей у процесі розробки.
  3. Необхідно багато зусиль для створення середовища розробки. Зі збільшенням кількості контейнерів/сервісів, складніше організувати координацію та зв'язок.

Висновок

У статті ми оглянули три моделі розробки:

  1. Локальна розробка — запуск і робота з проектом безпосередньо в хостовій ОС;
  2. Віртуальна машина — запуск проекту і всіх його залежностей всередині VM (гостьової ОС) на вашому комп'ютері.
  3. Контейнери — створення образів контейнера, що інтегруються з інструментами управління кластерами, на зразок Kubernetes

Підсумкові плюси та мінуси

Якщо ви пробіглися до кінця статті у пошуках «відповіді», вам не пощастило. Вибір підходу залежить від вашого проекту та команди.

Коли слід розглядати конкретний спосіб розробки?

Локальна розробка:

  • За: Якщо швидкість на першому місці або ви моделюєте чи досліджуєте певне проблемне місце.
  • Проти: Різниця між середовищем розробки та середовищем «живого» сайту призводить до проблем.

Розробка з віртуальною машиною:

  • За: Віртуальна машина відтворює більшість характеристик живого сайту, тому отримаєте стабільність.
  • Проти: Запуск операційної системи на вашому комп'ютері повільніший і додає додатковий шар, що може викликати проблеми.

Контейнерний підхід:

  • За: Контейнери допоможуть великим командам масштабувати системи за допомогою шаблонів таких велетнів, як Google.
  • Проти: Складність управління контейнерами призводить до того, що розробники не розуміють як працює їх код всередині системи.

На думку автора

Працюйте локально доти, доки це стерпно.

Ранні або невеликі проекти повинні рухатися швидко, щоб можна було визначити, чи варті вони зусиль. Якщо ви починаєте бізнес або намагаєтеся довести життєздатність ідеї, останнє, що вам потрібно, — використання контейнерів.

Віртуальні машини також сповільнюють роботу, оскільки вам потрібно подумати про міст між віртуальною операційною системою та фактичним комп'ютером.

Якщо вам дійсно потрібна швидкість, використовуйте PaaS (Platform as a Service, платформа як послуга), на зразок Heroku. З огляду на вартість вашого часу і переваги швидкого виходу на ринок, цей варіант може бути набагато дешевшим, ніж ви думаєте.

Не дозволяйте складнощам знищити ваш проект передчасно!

Якщо ваш проект або ідея дійсно перспективний, ви будете відчувати коли перейти на більш складний рівень. Поки ви не переходите безпосередньо до сервіс-орієнтованої архітектури (і навіщо вам це робити?), я вважаю віртуальні машини кращим вибором у порівнянні з контейнерами.

Успішний проект має певну складність. Складність, що була керованою на локальній машині, може почати бентежити вашу команду. Віртуальна машина може покінчити з цією складністю, адже не буде відмінностей між середовищами, і вам стане легше працювати з іншими розробниками.

Нарешті, якщо ріст вашої системи дуже прискориться, можете перейти на контейнери.

Пам'ятайте: ваш новий грандіозний проект не Google. У вас, ймовірно, немає (і не буде) проблем рівня Google. І перевага в тому, що вам не потрібні і рішення рівня Google.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація