На цій сторінці
Прототипування застосунків з Docker
October 17, 2021 — ☕🍪🍪🍪🍪 5 хвилин чтиваВступ
Дано: клієнт, який має певне бачення, і хоче запустити продукт. Ви обговорили скоуп проєкту, вивчили предметну область, залишилося перевірити цю бізнес-ідею в ділі. Ви вже потратили декілька тижнів на візуальний прототип, тож можна починати реалізовувати інтерфейс. Перша версія не повинна мати бекенду, оскільки:
- Всю логіку можна реалізувати на клієнтській частині
- Це прототип, тому вкладати ресурси в бекенд поки не хочеться
Разом з тим, хотілося б підстрахуватися від можливих змін курсу в майбутньому. Що як пілотна версія справді сподобається користувачам, і буде вирішено таки зробити з цього повноцінний продукт? В моєму випадку я використав підставний бекенд із MirageJS, таким чином весь фронтенд працював через API.
Отже, у нас є якийсь клієнтський інтерфейс із бізнес-логікою, який працює через невидимий API.
Перша повноцінне демо
На перших порах достатньо просто давати звіт шляхом демонстрації екрану, але так чи інакше хочеться дати можливість людям, для яких цей пілот робиться, погуляти по продукту самостійно. У нас є зібрана статична версія вебсторінки, але нам однаково треба якийсь вебсервер з мінімальною конфігурацією, зокрема:
- Щоб були правильні 404 редиректи на нашу зібрану сторінку. Інакше навігація на сторінці буде працювати до першого її перезавантаження користувачем;
- Для контролю доступу до вебсторінки. Оскільки це пілотний проєкту, це не зовсім те, що хочеться вивішувати у відкритий для конкурентів доступ.
Тобто сервер нам однаково потрібний, залишилося обрати, який саме. Для цього випадку я вирішив піти шляхом найменшого спротиву, і сервер зробити одразу на NodeJS (забігаючи наперед, клієнт пізніше вирішив таки розширити трохи скоуп, тому нам цей сервер таки став у пригоді).
Уявімо собі отаку структуру проєкту:
.
├── api
│ ├── bin
│ ├── Dockerfile
│ ├── package.json
│ └── ...
├── ...
└── docker-compose.yml
В ./api
міститься код нашого вебсервера. Маємо два файли конфігурації докера: ./api/Dockerfile
і ./docker-compose.yml
. Перший відповідає за контейнер з вебсервером, а другий — за конфігурацію системи в цілому (пам'ятаємо, пізніше нам може знадобитися іще й база даних).
Вміст ./api/Dockerfile
:
# Задамо образ, на базі якого будемо збирати наш вебсервер
FROM node:14
# Скажемо докеру перейти в директорію з проектом (в даному випадку директорія залежить від конфігурації docker-compose)
WORKDIR /usr/src/app
# Скопіюємо package.json в контейнер у вказану вище директорію
COPY package*.json ./
# Встановимо залежності
RUN npm install
# Скопіюємо вміст даної директорії всередину згаданої вище /usr/src/app
COPY . .
# Запустимо застосунок як тільки контейнер буде готовий
CMD ["npm", "start"]
Додамо таку конфігурацію в наш docker-compose:
version: "2"
services:
api:
# шлях до каталога з докерфайлом і файлами сервера
build: ./api
image: webapp-api
ports:
- "9000:9000"
Тут ми просто оголошуємо один-єдиний сервіс, з чотирма рядками його налаштувань.
Все. Для запуску проєкту достатньо виконати docker-compose up
, і відкрити у браузері адресу http://localhost:9000/
. Цю конфігурацію можна закинути в репозиторій, і потім однією командою розгортати на будь-якій підхожий віртуальній машині.
Одна маленька деталь. Зараз у файлі docker-compose вказано порт 9000, що не дуже зручно. Несолідно розгортати таку річ десь на публічному сервері, і давати клієнту посилання з портом.
Для такого випадку я додав іще один docker-compose файл, який стане в пригоді якраз у таких випадках:
├── ...
├── docker-compose.prod.yml
└── docker-compose.yml
Вміст docker-compose.prod.yml
:
version: "2"
services:
api:
restart: always
ports:
- "80:9000"
Docker-compose дозволяє використовувати одночасно декілька різних файлів конфігурації. Тож в цьому випадку я маю один базовий файл конфігурації (він же за сумісництвом і конфігурація для розробки), і один окремо для штатної експлуатації, який задає більш підхожу конфігурацію портів, і також вказує докеру завжди перезапускати контейнер.
Для запуску контейнера в експлуатаційному режимі треба виконати команду docker-compose -f ./docker-compose.yml -f ./docker-compose.prod.yml up -d
. Це буквально говорить докеру взяти налаштування з першого файлу, потім накласти поверх них налаштування з другого файлу, і запустити. Таким чином нам не потрібно дублювати однакові поля конфігурації в різних файлах.
Якщо ви не кожного дня пишете в терміналі команди для docker-compose, є сенс винести оту конструкцію вгорі в окремий скрипт, який можна покласти поряд в репозиторії. Наприклад, отак:
#!/usr/bin/env sh # Run docker-compose with applied production config docker-compose -f ./docker-compose.yml -f ./docker-compose.prod.yml up -d
Зміна планів — додаємо бекенд
Як я вже згадував вище, підставним бекендом на базі MirageJS все не закінчилося, зрештою нам таки знадобилося розширити скоуп роботи. В нашому випадку нам дуже знадобилася необхідність створювати нові сутності, так, щоб їх могли бачити (і модифікувати) інші користувачі. Без варіантів, це треба робити тільки на сервері. Отже, нам потрібна база даних.
Щоб додати MongoDB до нашої конфігурації, нам потрібно додати новий сервіс у файл ./docker-compose.yml
mongodb:
# Ім'я готового контейнера, який буде завантажено з dockerHub автоматично
image: mongo
container_name: mongodb
# Важлива деталь — за цією адресою база даних буде складати збережену інформацію,
# щоб ми її не втратили під час перезапуску контейнера
volumes:
- ./data-node:/data/db
ports:
- 27017:27017
# І у нас з'яляється додатковий параметр — ім'я мережі,
# за допомогою нього ми можемо вказати докеру, які контейнери можуть спілкуватись між собою
networks:
- webappnetwork
У нас додався ідентифікатор мережі, а, значить, її треба десь оголосити. Додамо її внизу в тому ж файлі:
networks:
# Ось наша мережа
webappnetwork:
# драйвер типу `bridge` трохи спрощує зневадження конфігурації докера.
# Але в цьому випадку порт бази даних буде доступний зовні,
# тож без налаштованої автентифікації на базі даних його краще вимкнути
driver: bridge
Залишається невеличка деталь: наші сервіси mongodb
і api
мають знаходитись в одній мережі, інакше вони один одного не побачать. Доповнимо конфігурацію api-сервісу відповідним чином:
api:
build: ./api
image: webapp-api
ports:
- "9000:9000"
# просимо цей сервіс зачекати, поки не запуститься контейнер з базою даних
depends_on:
- mongodb
# прив'язуємо його до тієї ж мережі, що й база даних
networks:
- webappnetwork
Повністю наш ./docker-compose.yml
виглядає так:
version: "2"
services:
api:
build: ./api
image: webapp-api
ports:
- "9000:9000"
depends_on:
- mongodb
networks:
- webappnetwork
mongodb:
image: mongo
container_name: mongodb
volumes:
- ./data-node:/data/db
ports:
- 27017:27017
networks:
- webappnetwork
networks:
webappnetwork:
driver: bridge
Власне все, конфігурація готова до роботи з базою даних. Все, що нам для цього знадобилося — це додати 18 рядків конфігурації в основний docker-compose файл!
Висновки
На жаль (чи на щастя), це іще не все. Так, ця конфігурація робоча, але не повна, хоча б тому, що в такому вигляді її не можна розгортати на публічному сервері (хоча це і буде працювати). За рамками цієї статті залишилося налаштування автентифікації, як на базі даних, так і на вебсервері. Розглянемо способи розв'язання цих завдань наступного разу.