Меню Закрити

Розгортаємо fullstack-додаток з Docker

Розповідає João Henrique

У цій статті я ознайомлю вас із простим способом контейнеризації фронтенду на React, API бекенду на Node/Express і бази даних MongoDB, використовуючи контейнери Docker.

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

Навіщо вам Docker?

Docker — одна з найважливіших технологій на даний час. Вона дає змогу запускати програми всередині ізольованих контейнерів.

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

Ще одна особливість контейнерів — змінність. Якщо один контейнер “впаде”, його можна легко замінити, створивши такий же.

Інша річ, яка робить Docker прекрасним, — те, що програма всередині контейнерів однаково працюватиме в кожній ОС (Windows, Mac або Linux). Це особливо важливо, якщо ви здійснюєте розробку на своєму компі, а потім хочете розгорнути її у “хмарі”, наприклад на GCP або AWS.

З чого почати

  1. Переконайтеся, що на вашому комп’ютері встановлені Node та Docker.
  2. Я використовуватиму додаток React/Express, створення якого розглядав в іншій статті. Ви можете створити його самостійно ним або ж клонувати цей репозиторій GitHub, якщо вас не цікавить процес створення.
  3. Якщо ви обрали використання репозиторія, не забудьте запустити npm install у каталогах Client та API, щоб встановити всі потрібні залежності.
  4. І… це все. Ви готові розпочати контейнеризацію 🙂

Dockerfile

За документацією:

Dockerfile — це текстовий документ, що містить усі команди, необхідні для компонування образу контейнера. Docker може створювати образ автоматично, читаючи інструкції з Dockerfile.

Контейнери Docker

Контейнеризація вашого додатка за допомогою Docker така ж проста, як створення Dockerfile для кожного з ваших додатків: спочатку створюєте образ, а потім запуск кожного образу активує роботу контейнерів.

Контейнеризуйте клієнтську частину

Щоб побудувати образ Client, вам знадобиться Dockerfile. Створіть його у каталозі Client:

# Use a lighter version of Node as a parent image
FROM mhart/alpine-node:8.11.4
# Set the working directory to /client
WORKDIR /client
# copy package.json into the container at /client
COPY package*.json /client/
# install dependencies
RUN npm install
# Copy the current directory contents into the container at /client
COPY . /client/
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Run the app when the container launches
CMD ["npm", "start"]

Це дасть змогу Docker побудувати образ (використовуючи ці інструкції) для нашого клієнта.

Контейнеризуйте API

Для створення образа нашого API вам знадобиться інший Dockerfile. Створіть його в каталозі API:

# Use a lighter version of Node as a parent image
FROM mhart/alpine-node:8.11.4
# Set the working directory to /api
WORKDIR /api
# copy package.json into the container at /api
COPY package*.json /api/
# install dependencies
RUN npm install
# Copy the current directory contents into the container at /api
COPY . /api/
# Make port 80 available to the world outside this container
EXPOSE 80
# Run the app when the container launches
CMD ["npm", "start"]

Це дасть змогу Docker побудувати образ (використовуючи ці інструкції) для нашого бекенду.

Docker-Compose

Кожен окремий контейнер можна запускати за допомогою Dokerfiles. У даному випадку ми оперуємо трьома контейнерами (ще буде база даних), тож скористаємося командою docker-compose. Compose — це інструмент для створення та запуску мультиконтейнерних додатків Docker.

У головному каталозі App створіть файл з назвою docker-compose.yml і додайте у нього цей текст:

version: "2"
services:
    client:
        image: webapp-client
        restart: always
        ports:
            - "3000:3000"
        volumes:
            - ./client:/client
            - /client/node_modules
        links:
            - api
        networks:
            webappnetwork
    api:
        image: webapp-api
        restart: always
        ports:
            - "9000:9000"
        volumes:
            - ./api:/api
            - /api/node_modules
        depends_on:
            - mongodb
        networks:
            webappnetwork

По суті, я кажу Docker, що хочу створити контейнер під назвою Client, використовуючи образ webapp-client, який буде слухати порт 3000. Потім я кажу, що хочу створити контейнер під назвою api, використовуючи образ webapp-api, який займе порт 9000.

Додайте базу даних MongoDB

Додавання бази даних MongoDB  таке ж просте, як додавання цих рядків коду до вашого файлу docker-compose.yml:

mongodb:
       image: mongo
       restart: always
       container_name: mongodb
       volumes:
           - ./data-node:/data/db
       ports:
           - 27017:27017
       command: mongod --noauth --smallfiles
       networks:
           - webappnetwork

Це створить контейнер із використанням офіційного образу MongoDB.

Створіть спільні мережеві налаштування для своїх контейнерів

Щоб створити спільну мережу для ваших контейнерів, просто додайте такий код у файл docker-compose.yml:

networks:
    webappnetwork:
        driver: bridge

Зверніть увагу, ви вже визначили, що кожен контейнер вашої програми використовуватиме цю мережу.

Зрештою, ваш файл docker-compose.yml матиме такий вигляд:

У файлі docker-compose.yml відступ має значення. Пам’ятайте про це.

Запустіть контейнери

Тепер, коли у вас є файл docker-compose.yml, можна створити образи. Перейдіть до термінала та в головному каталозі вашого додатка і запустіть:

docker-compose build

2. Тепер, щоб змусити Docker розгорнути контейнери, просто запустіть:

docker-compose up

І… магія: тепер у вас є клієнтська частина, API і база даних, усі працюють у розділених контейнерах і запускаються лише з командою. Круто, правда ж?

Підключіть API до MongoDB

По-перше, встановіть Mongoose, щоб допомогти собі в підключенні до MongoDB:

npm install mongoose

Тепер створіть файл під назвою testDB.js у каталозі routes додатка API та вставте цей код:

const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
// Variable to be sent to Frontend with Database status
let databaseConnection = "Waiting for Database response...";
router.get("/", function(req, res, next) {
    res.send(databaseConnection);
});
// Connecting to MongoDB
mongoose.connect("mongodb://mongodb:27017/test");
// If there is a connection error send an error message
mongoose.connection.on("error", error => {
    console.log("Database connection error:", error);
    databaseConnection = "Error connecting to Database";
});
// If connected to MongoDB send a success message
mongoose.connection.once("open", () => {
    console.log("Connected to Database!");
    databaseConnection = "Connected to Database";
});
module.exports = router;

Розглянемо, що робить цей код. По-перше, я імпортую Express, ExpressRouter і Mongoose для використання на /testDB маршруті (в даному випадку – це кінець URL-адреси). Потім я створюю змінну, яка буде надіслана як відповідь на запит. Далі я підключаюся до бази даних за допомогою Mongoose.connect(). Потім перевіряю, чи з’єднання працює, і відповідно змінюю змінну (створену раніше). Нарешті, я використовую module.exports для експорту цього маршруту, щоб використовувати його для файлу app.js.

Тепер ви повинні “наказати” Express використовувати маршрут, який щойно створили. У каталозі API відкрийте файл app.js і вставте ці два рядки коду:

var testDBRouter = require("./routes/testDB");
app.use("/testDB", testDBRouter);

Це “підказуватиме” Express: щоразу, коли є запит до кінцевої точки /testDB, він має виконати інструкції із файлу testDB.js.

Тепер перевіримо, чи все працює належно. Перейдіть до свого термінала й натисніть ctrl+C, щоб зупинити контейнери. Потім запустіть docker-compose, щоб знову запустити їх. Після того як усе запущено й працює, коли ви перейдете у браузері за адресою http://localhost:9000/testDB, побачите повідомлення: “Connected to Database”.

Зрештою, файл app.js матиме такий вигляд:

Це означає, що API тепер підключений до бази даних. Але ваш фронтенд ще не знає про підключення, тож зараз над цим попрацюємо.

Відправка запиту до бази даних з React

Щоб перевірити, чи може додаток React досягти бази даних, зробимо простий запит до кінцевої точки, яку ви визначили в попередньому кроці.

  1. Перейдіть до папки Client та відкрийте файл App.js..
  2. Тепер вставте цей код в метод callAPI():
callDB() {
    fetch("http://localhost:9000/testDB")
        .then(res => res.text())
        .then(res => this.setState({ dbResponse: res }))
        .catch(err => err);
}

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

Додайте змінну до стану компонента, щоб зберегти відповідь:

dbResponse: ""

У межах життєвого циклу методу componentDidMount() вставте цей код, щоб виконати метод, який ви щойно створили, під час підключення компонента:

this.callDB();

Нарешті, додайте ще один блок <p> після того, який у вас уже є, щоб відобразити відповідь із бази даних:

<p className="App-intro">{this.state.dbResponse}</p>

Зрештою, ваш файл App.js має виглядати так:

А це працює?

У своєму браузері перейдіть до http://localhost:3000/ і якщо все працює належно, ви побачите ці три повідомлення:

  1. Welcome to React (Ласкаво просимо до React).
  2. API is working properly (API працює належно).
  3. Connected to Database (Підключено до бази даних).

Щось на зразок такого:

Мої вітання

Тепер у вас є fullstack додаток із React FrontEnd, Node/Express API та базою даних MongoDB. Усе це працює всередині окремих контейнерів Docker.

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

1+

Повідомити про помилку

Текст, який буде надіслано нашим редакторам: