Делаем микрообразы с микросервисами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Из цикла "Микросервисы или смерть"

Решаемая проблема: монолитное приложение на Node.js раньше, в развернутом состоянии, занимало 0.2 Гб всего. Теперь же, разбитое на 33 микросервиса, занимает 33 * 0.1 = 3.3 Гб. Можно ли избежать подобной издержки? -- можно! В статье мы избавимся от лишнего веса.

Решение, представленное в статье подходит больше для контура разработки, но и для продуктового контура его использовать никто не запрещал.

Откуда лишний размер?

Во-первых разберемся, откуда берётся лишний размер при переходе от монолита к микросервисной архитектуре.

Допустим есть микросервис MsA с зависимостями: express, axios, nats ; и микросервис MsB с зависимостями: express, axios и mongoose. Рассмотрим докер файл для MsA (упрощено для краткости):

FROM node:alpine
COPY . .
RUN npm install
CMD ["npm", "start"]

Получившиеся слои образа:

  1. FROM node:alpine --> 100 Mb

  2. COPY . . --> 0.5 Mb

  3. RUN npm install --> 50-100+ Mb

Dockerfile для MsB и его слои будут выглядить аналогично.

node:alpine у нас используется для всех микросервисов, и, соответственно, этот слой (слой №1) будет общий. Но два следующих слоя будут отличатся от микросервиса к микросервису. Именно они будут сказываться на общем размере. У наших микросервисов есть общие зависимости - express, axios При npm install они будут скопированы в каждый образ. Отсюда общее увеличение. Общие зависимости дублируются во всех микросервисах, увеличивая общий размер.

Решение

Приходит на ум решение, что должен быть некий общий образ, который будет базовым для всех микросервисов

Есть много способов прийти к такому результату, не буду ходить вокруг да около, рассматривая плюсы и минусы, а сразу приведу решение. В микросервисах MsA и MsB есть общие зависимости (express, axios) но есть и отличающиеся (mongoose и nats). В нашем решении мы просто возьмём и объединим их всех в один package.json. С некоторыми особенностями. Нам нужны только зависимости, работающие на этапе эксплуатации. Зависимости на этапе сборки нам не нужны. Т.е. jest, webpack, typescript и т.п. зависимости в devDependences каждого микросервиса могут отличаться. Но вот раздел dependences будет одинаков для всех микросервисов. Вне зависимости, используют ли они nats или mongoose, эта зависимость там будет. Вторая особенность это общая практика использования зависимостей со строго фиксированными версиями зависимостей. Чтобы билд при создании образа надёжно проходил, а не падал от минорных апдейтов.

Т.е. у нас будет проект BaseMS (типа базовый образ для всех микросервисов) с package.json примерно таким (упрощено для краткости):

{
    "name": "basems",
     "dependencies": {
        "axios": "0.21.1",
        "express": "4.17.1",
        "mongoose": "5.12.8",
        "node-nats-streaming": "0.3.2"
  }
}

и Dockerfile примерно таким:

FROM node:alpine
COPY . .
RUN npm install

Для MsA package.json будет таким (упрощено для краткости):

{
  "name": "markups",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "test": "jest --watchAll --no-cache"
  },
  "devDependencies": {
    "jest": "^26.1.0",
    "mongodb-memory-server": "^6.9.6",
    "supertest": "^6.1.2",
    "ts-jest": "^26.1.3",
    "typescript": "^4.1.3",
    "webpack": "^5.21.2",
    "webpack-cli": "^4.5.0",
    "webpack-node-externals": "^2.5.2"
  },
  "dependencies": {
    "axios": "0.21.1",
    "express": "4.17.1",
    "mongoose": "5.12.8",
    "node-nats-streaming": "0.3.2"
  }
}

Для MsB package.json будет аналогичен, но набор devDependencies может отличаться.

Webpack (опционально)

Допустим мы используем typescript и хотим еще всё пожать webpack. Webpack можно использовать для бэкэнда, надо лишь не включать в итоговый бандл node_modules. В этом нам поможет небольшой пакет для webpack -- webpack-node-externals. Надо просто добавить в webpack.config.js следующий код:

const nodeExternals = require('webpack-node-externals');
...
module.exports = {
    target: 'node',
    ...
    externals: [nodeExternals()],
    ...
};

Dockerfile для микросервисов

Для микросервисов теперь будем использовать мультистейдж билд в докере. На первом этапе собираем итоговый файл index.js. На втором создаем итоговый образ, просто копируя итоговый index.js в базовый образ

# Этап сборки 
FROM node:alpine
COPY package.json .
RUN npm install
COPY . .
RUN npm run build

# Рабочий уровень 
FROM localhost:5000/basems
COPY --from=0 app/dist/index.js .
CMD ["node", "index.js"]

Образ localhost:5000/basems - это как раз наш базовый образ, собранный выше. Он называется так, т.к. мы его пушим в локальный хаб registry

Для всех микросервисов файл докера будет одинаковый. Давайте рассмотрим получившиеся слои:

  1. FROM localhost:5000/basems --> 150 Mb

  2. COPY --from=0 app/dist/index.js . --> 0.5 Mb

Вот и всё, первый слой для всех общий и весит 150 Мб, а вот слой уникальный для каждого докера будет минимального размера.

Достоинства и недостатки подхода

Недостатки:

  1. Если необходимо изменить набор рабочих зависимостей для отдельного микросервиса, придется ребилдить/репушить все микросервисы. Но на деле набор зависимостей быстро устаканится в процессе разработки и это не будет проблемой.

  2. При изменении зависимостей в одном микросервисе (рабочих) необходимо менять package.json файлы всех микросервисов. На деле такие изменения легко автоматизировать, это может делаться по кнопке в доли секунду и, к тому же, даже будет плюсом, т.к. поможет не допустить ошибок при ручном изменении.

  3. Подобный трюк работает только на Node.js основанных микросервисах. Но ничего страшного, если у вас на ноде лишь часть микросервисов -- это сработает и для них. А остальные останутся как и раньше.

Достоинства:

  1. Итоговый размер слоев минимален. Можно делать хоть тысячу микросервисов с микрофункциональностью.

  2. Мы обновляем рабочие зависимости в одном месте. Устраняем vulnerabilities в одном месте. А затем по кнопке накатываем на все микросервисы.. Это, кстати, достоинство даже круче первого

Всем спасибо!

Источник: https://habr.com/ru/post/564136/


Интересные статьи

Интересные статьи

В данной пошаговой инструкции мы подробно опишем весь процесс получения доступа к WhatsApp Business API через официального партнера Facebook — сервис Gupshup и подключени...
В прошлый раз показал один из способов распределение ресурсов между конечными точками, а именно регистров EPnR, памяти под дескрипторы буферов и под сами буферы. Предлага...
Маркетплейс – это сервис от 1С-Битрикс, который позволяет разработчикам делиться своими решениями с широкой аудиторией, состоящей из клиентов и других разработчиков.
Привет, Хабр. В предыдущей части я рассматривал создание несложной распознавалки текста, основанной на нейронной сети. Сегодня мы применим аналогичный подход, и напишем автоматический переводч...
В 2019 году люди знакомятся с брендом, выбирают и, что самое главное, ПОКУПАЮТ через интернет. Сегодня практически у любого бизнеса есть свой сайт — от личных блогов, зарабатывающих на рекламе, до инт...