Оглавление
1. Введение
2. Backend
2.1. Инфраструктура.
2.2. Доменное имя. SSL.
2.3. Серверное приложение на Dart.

3. Web
3.1. Заглушка «Under construction»

4. Mobile


Введение


Меня, Flutter-разработчика, знакомые часто спрашивают: «Что же такое язык Dart?». Качают головой со словами: «А вот Петя серьёзные транспорты на Java пишет, а в Яндексе вообще плюсы в проде...». Ну что ж, пожалуй, действительно, Dart далёк от практик «фабрик для создания фабрик» из Java. Однако если стоит задача реализовать клиентские приложения сразу для нескольких платформ, не утонув в потоке задач по синхронизации разработчиков разных целевых ОС; создать целостный UI, узнаваемый, но специфичный для Android, iOS и веб и в целом уложиться в адекватные бюджет и сроки, — здесь Flutter не имеет конкурентов. И эти вопросы стоят вдвойне если у вас… стартап.

Итак, легенда: некий стартап решил создать новый сервис… ну, например, для
обмена списками покупок
Такая себе идея для стартапа, я знаю, но если я выпущу ещё один ToDo лист в этот мир, мне будет стыдно :)
между пользователями сервиса. Цель стартапа — выпустить MVP за три месяца на трех платформах (плюс четвертая — сервер, конечно).

10 лет назад я бы сказал, что этот кейс не имеет решения и постарался бы держаться от него подальше, 3 года назад решением мог стать стек ReactNative/React/NodeJs, в 2020 году для этого есть Dart. Добро пожаловать в атмосферу разработки альфа версии сервиса, я постараюсь наглядно пройти и объяснить весь процесс разработки. Код всех приложений будет выложен в паблик. Комментарии, включая набросы и холивары, приветствуются. Спросить автора «по существу» или просто посоветоваться можно в Telegram канале нашего отдела.



Инфраструктура бекэнд


Типовым способом размещения серверного приложения является, конечно, VPS (виртуальный приватный сервер). Фактически — это часть физического сервера в дата центре, ресурсы которого (ядра процессора и оперативная память) разделены с помощью технологии виртуализации (о наиболее распространённых технологиях аппаратной виртуализации можно почитать здесь XEN, KVM). Внимание: технологии программной виртуализации (OpenVZ, Virtuozzo) для нашей задачи могут не подойти из-за конфликтов с Docker и агрессивным оверселлингом (зачастую, при внимательном прочтении договора аренды такого VPS, можно обнаружить, что провайдеры гарантируют «не менее 5%» (!) утилизации ядра арендуемого процессора. Это означает, что провайдер планирует продать наше ядро процессора 20 (!) раз).

Итак, приобретём бюджетный VPS со следующими характеристиками: 1 ядро процессора, 1ГБ оперативной памяти, 10ГБ накопителя (в моём случае, это гибридный HDD). В качестве операционной системы выберем Ubuntu, желательно одной из LTS версий. После чего в электронную почту придёт сообщение об активации сервера с логином и паролем доступа по SSH (зашифрованный доступ к консоли операционной системы нашего VPS) в формате SSH:

IP-адрес: 91.230.60.120
Пользователь: root
Пароль: <Пароль>

Проверим подключение, введя в командной строке:

ssh root@91.230.60.120

и, по запросу:

password: <Пароль>

Результатом должен быть вывод сведений о виртуальном сервере и поле ввода внизу:

Server is hosted by хххххххххх

Hostname: 91.230.60.120
Kernel: 3.19.0-22-generic (Ubuntu хх.хх LTS)
Uptime: 09:07:06 up 3 days, 17:17, 1 user, load average: 0.00, 0.01, 0.05
CPU: Intel® Xeon® CPU 0 @ 2.00GHz (1 cores)
Memory: 989 MB total / 723 MB free

root@91.230.60.120:~$

Поздравляю, наш виртуальный сервер создан и доступен для работы.

Теперь определимся со структурой бекэнд. Нам понадобится HTTP сервер. Мы будем использовать NGINX. Его задачами будут:

  1. Раздача статических файлов (файлы веб приложения).
  2. Раздача служебных ресурсов, например, файлов подтверждения владения доменом для мобильных приложений, сведений о владельце для получения SSL сертификатов Let’s encrypt и пр.
  3. Reverse proxy для доступа к серверным приложениям.
  4. Шифрование соединений — https.

Два серверных приложения:

  1. Приложение регистрации и авторизации пользователей. Назовём его auth_app.
  2. Приложение с данными. Назовём его app.
  3. Для каждого из приложений п.2 нам понадобится отдельная база данных PostgreSQL.
  4. Приложение для автоматического получения и обновления сертификатов шифрования SSL (в следующей статье).

Очевидно, что такой «зоопарк» приложений необходимо изолировать друг от друга, а также заблокировать доступ извне. Для этого мы будем использовать технологию контейнеризации Docker и менеджер контейнеров Docker compose. В виде схемы это можно представить так:

image

Разработку будем вести в IDE Visual Studio Code от Microsoft, которая, благодаря множеству доступных плагинов, позволит работать со всеми необходимыми технологиями. Также необходимо установить следующие расширения:


После перезапуска VScode подключимся к нашему VPS. Нажимаем F1 и начнем вводить команду:

Remote-SSH: connect to  Host…

далее новое подключение:

+ Add New Ssh Host

затем:

ssh root@<ip-адрес сервера>

Откроем окно терминала VScode (Menu/Terminal/New terminal) и проверим системные ресурсы командой:

top

Готово, доступ к консоли и файловой системе VPS получен:





Утилита top будет использоваться довольно часто, поэтому установим её псевдографическую версию htop:

Ctrl-C #Завершаем выполнение утилиты top

apt-get update #Обновляем установленные пакеты

apt-get install htop #Устанавливаем htop 

htop #Запускаем 

image

Теперь необходимо установить Docker и Docker compose:

Ctrl-C #Завершаем выполнение утилиты htop

Поскольку docker отсутствует в официальном репозитории Ubuntu, установим дополнительный репозиторий

apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common #Устанавливаем необходимые утилиты 

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - #Устанавливаем ключ репозитория docker 

add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" #Добавляем репозиторий 

apt-get install docker-ce docker-ce-cli containerd.io #Устанавливаем

curl -L "https://github.com/docker/compose/releases/download/1.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose #Скачиваем менеджер Docker compose 

chmod +x /usr/local/bin/docker-compose #Устанавливаем разрешение для загруженного файла «исполняемый файл»

ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose #Добавляем символьную ссылку в директорию исполняемых файлов 

 docker  --version #Проверяем

docker-compose --version

Отлично, сервер готов к тестовому развёртыванию сервиса.

Теперь установим Docker desktop на нашем локальном ПК для разработки. Установщик для Windows 10, версия для MacOS здесь. Будет установлен не только Docker, но и Docker toolbox, в который входят Docker compose и графические утилиты для работы с контейнерами.

Откроем новое окно VScode, Menu/File/Open folder… Создадим новую папку нашего проекта, например, Srv и откроем её. В этой папке создадим файл docker-compose.yaml:

image

В этом файле описывается сценарий запуска контейнеров сервиса, их зависимости, переменные, команды, сети, хранилища и пр.

Здесь необходимо остановиться и уточнить разницу между образом и контейнером Docker. Контейнер — это приложение + его зависимости (например, пакеты и библиотеки) + операционная система, которую можно с помощью Docker запустить как обычное приложение. А образ — это подготовленный к запуску и упакованный в архив контейнер. Таким образом, весь наш бекэнд будет представлять собой набор контейнеров с описанием сценария их запуска.

Первым контейнером, который мы запланировали станет HTTP сервер NGINX. И давайте подготовим необходимый образ… или нет? Дело в том, что для очень многих веб приложений и сред исполнения их разработчиками или комьюнити уже собраны необходимые образы и размещены в публичном реестре DockerHub. Разумеется такой широко используемый образ уже собран и ожидает нас по этой ссылке.

Обратите внимание на список — это различные версии, они отличаются и версиями самого NGINX и дополнительными инструментами (например, установленным PERL). Для разработки можно использовать тэг «latest» (последняя стабильная версия на момент запроса образа), но для развёртывания на сервере, конечно, стоит использовать конкретную версию. В данный момент это образ nginx:1.19.0.

Здесь и далее необходимые пояснения к содержимому docker-compose.yaml я буду указывать в комментариях в самом листинге файла:



Сохраним изменения в файле, откроем консоль VScode и выполним команду запуска сценария

docker-compose up

Этой командой был не только запущен сценарий, но и вывод консоли контейнера был направлен в консоль хоста. Теперь, если в адресной строке браузера обратиться к локальному хосту на порте 8081 будет получена стандартный ответ NGINX:



Наш первый контейнер уже работает. Обычно запуск сценария выполняется командой:

docker-compose up -d

это позволяет запустить контейнеры в режиме сервиса (без вывода в консоль). Остановка контейнеров сценария выполняется командой:

docker-compose down

Для тестирования http запросов в VScode есть удобное расширение REST client.

Установим его и напишем первый отладочный тест нашего сервиса. Для этого создадим файл client.http в папке test/http_dev/:





Таким образом можно выполнять тестовые запросы, просматривая подробную информацию об ответах сервера.

Теперь давайте заглянем внутрь контейнера. Остановим выполнение сценария в консоли:

Ctrl-C

и запустим с флагом:

docker-compose up -d

Теперь запросим список выполняемых в данный момент контейнеров (процессов):

docker-compose ps



В списке выполняемых только один контейнер. Давайте откроем его:

docker exec -it srv_web_1 bash

Эта команда выполняет (exec) приложение bash (командная оболочка Linux) в контейнере srv_web_1 и не дает закрываться консоли (флаги -it):



Команда ls покажет стандартную структуру папок Linux:



Нас интересует файл /etc/nginx/conf.d/default.conf — настройки NGINX, для просмотра можно использовать утилиту cat

cat /etc/nginx/conf.d/default.conf



В настройках NGINX один блок (так называемый location), в котором включено прослушивание порта 80 и раздача статических файлов из папки контейнера /usr/share/nginx/html. Можно попробовать внести изменения в файл настройки NGINX и перезапустить его, применив изменения, но при перезапуске сценария контейнер будет восстановлен из образа и никакие наши изменения не сохранятся. Это неправильный путь.

Выйдем из консоли контейнера:

Ctrl-D

Мы напишем свой файл настройки и разместим свои статические файлы, а при запуске будем их монтировать в контейнер NGINX. Создадим файл default.conf в папке /conf.d нашего проекта:





Создадим заглушку статического файла /public/index.html:



Теперь в сценарии запуска docker-compose.yaml смонтируем наши папки в файловую систему контейнера:



Обратите внимание, что содержимое папки проекта ./conf.d заменит содержимое контейнера в /etc/nginx/conf.d/, а в корневую папку папку контейнера будет смонтирована папка ./public.

Перезапустим сценарий:

docker-compose restart

Тестовый запрос:



Давайте посмотрим на файл default.conf. Обратите внимание, что мы отключили логирование доступа к статическим файлам access_log off. Это хорошее решение для прода, но очень неудобное при тестировании и разработке. Давайте создадим тестовые файл конфигурации NGINX /conf.dev.d/default.conf и сценарий docker-compose.dev.yaml.







Остановим сценарий:

docker-compose down

и запустим уже с флагами имен файлов:

docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml up

При таком запуске сценария сначала будет прочитаны настройки из файла docker-compose.yaml, а затем будут добавлены или заменены совпадающие поля из docker-compose.dev.yaml (ports, volumes). Проверим логирование повторив запрос:





Итак, нам осталось выполнить копирование и запуск на сервере. Создадим на сервере папку /opt/srv_0/ (мы ведь ещё не закрыли окно VScode c SSH соединением к VPS) и скопируем в неё всё содержимое нашего проекта командой:

scp scp -r ./* root@91.230.60.120:/opt/srv_0/ 



Теперь на сервере в папке проекта /opt/srv_0/ выполним команду:

docker-compose up -d

Напишем ещё один http тест, теперь уже для VPS:



Ну или откройте в браузере ссылка.

> Исходный код github

Вместо заключения


Итак, сделан первый шаг. Мы успешно раскатили на боевой сервер приложение. Во второй статье мы продолжим настройку сервера: назначим доменное имя и установим сертификат шифрования SSL. В третьей статье напишем flutter web приложение с обратным отсчётом времени до запуска нашего сервиса, соберём его и разместим на нашем сервере. В четвертой статье напишем и соберём нативный сервер под Linux на языке Дарт, который станет основой для приложений авторизации и данных нашего сервиса.

Комментарии и предложения приветствуются. Пообщаться с автором можно в Telegram-канале.