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 не имеет конкурентов. И эти вопросы стоят вдвойне если у вас… стартап.
Итак, легенда: некий стартап решил создать новый сервис… ну, например, для
между пользователями сервиса. Цель стартапа — выпустить MVP за три месяца на трех платформах (плюс четвертая — сервер, конечно).
10 лет назад я бы сказал, что этот кейс не имеет решения и постарался бы держаться от него подальше, 3 года назад решением мог стать стек ReactNative/React/NodeJs, в 2020 году для этого есть Dart. Добро пожаловать в атмосферу разработки альфа версии сервиса, я постараюсь наглядно пройти и объяснить весь процесс разработки. Код всех приложений будет выложен в паблик. Комментарии, включая набросы и холивары, приветствуются. Спросить автора «по существу» или просто посоветоваться можно в Telegram канале нашего отдела.
Инфраструктура бекэнд
Типовым способом размещения серверного приложения является, конечно, VPS (виртуальный приватный сервер). Фактически — это часть физического сервера в дата центре, ресурсы которого (ядра процессора и оперативная память) разделены с помощью технологии виртуализации (о наиболее распространённых технологиях аппаратной виртуализации можно почитать здесь XEN, KVM). Внимание: технологии программной виртуализации (OpenVZ, Virtuozzo) для нашей задачи могут не подойти из-за конфликтов с Docker и агрессивным оверселлингом (зачастую, при внимательном прочтении договора аренды такого
Итак, приобретём бюджетный
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. Его задачами будут:
- Раздача статических файлов (файлы веб приложения).
- Раздача служебных ресурсов, например, файлов подтверждения владения доменом для мобильных приложений, сведений о владельце для получения SSL сертификатов Let’s encrypt и пр.
- Reverse proxy для доступа к серверным приложениям.
- Шифрование соединений — https.
Два серверных приложения:
- Приложение регистрации и авторизации пользователей. Назовём его auth_app.
- Приложение с данными. Назовём его app.
- Для каждого из приложений п.2 нам понадобится отдельная база данных PostgreSQL.
- Приложение для автоматического получения и обновления сертификатов шифрования SSL (в следующей статье).
Очевидно, что такой «зоопарк» приложений необходимо изолировать друг от друга, а также заблокировать доступ извне. Для этого мы будем использовать технологию контейнеризации Docker и менеджер контейнеров Docker compose. В виде схемы это можно представить так:
Разработку будем вести в IDE Visual Studio Code от Microsoft, которая, благодаря множеству доступных плагинов, позволит работать со всеми необходимыми технологиями. Также необходимо установить следующие расширения:
После перезапуска VScode подключимся к нашему
Remote-SSH: connect to Host…
далее новое подключение:
+ Add New Ssh Host
затем:
ssh root@<ip-адрес сервера>
Откроем окно терминала VScode (Menu/Terminal/New terminal) и проверим системные ресурсы командой:
top
Готово, доступ к консоли и файловой системе
Утилита top будет использоваться довольно часто, поэтому установим её псевдографическую версию htop:
Ctrl-C #Завершаем выполнение утилиты top
apt-get update #Обновляем установленные пакеты
apt-get install htop #Устанавливаем htop
htop #Запускаем
Теперь необходимо установить 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:
В этом файле описывается сценарий запуска контейнеров сервиса, их зависимости, переменные, команды, сети, хранилища и пр.
Здесь необходимо остановиться и уточнить разницу между образом и контейнером 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 соединением к
scp scp -r ./* root@91.230.60.120:/opt/srv_0/
Теперь на сервере в папке проекта /opt/srv_0/ выполним команду:
docker-compose up -d
Напишем ещё один http тест, теперь уже для
Ну или откройте в браузере ссылка.
→ Исходный код github
Вместо заключения
Итак, сделан первый шаг. Мы успешно раскатили на боевой сервер приложение. Во второй статье мы продолжим настройку сервера: назначим доменное имя и установим сертификат шифрования SSL. В третьей статье напишем flutter web приложение с обратным отсчётом времени до запуска нашего сервиса, соберём его и разместим на нашем сервере. В четвертой статье напишем и соберём нативный сервер под Linux на языке Дарт, который станет основой для приложений авторизации и данных нашего сервиса.
Комментарии и предложения приветствуются. Пообщаться с автором можно в Telegram-канале.
Автор: Andrew