Найкращі 4 тактики збереження Node.js Rockin ’у Docker

rockin

У нас є всі свої улюблені мови та фреймворки, і Node.js є для мене першим. Я запускав Node.js у Docker з перших днів для критично важливих програм. Я маю на меті навчати всіх, як отримати максимальну віддачу від цієї основи та його інструменти, такі як npm, Yarn та nodemon з Docker.

Існує маса інформації про використання Node.js з Docker, але стільки з цього застаріло на роки, і я тут, щоб допомогти вам оптимізувати налаштування для Node.js 10+ та Docker 18.09+. Якщо ви віддаєте перевагу перегляду мого виступу на DockerCon 2019, який охоплює ці теми та багато іншого, перегляньте його на YouTube.

Давайте пройдемо 4 кроки для того, щоб ваші контейнери Node.js співали! Я включу декілька коротких записів "Задовго; Не читав »для тих, хто цього потребує.

Дотримуйтесь поточного базового дистрибутива

TL; DR: Якщо ви переносите програми Node.js у контейнери, використовуйте базове зображення хост-ОС, яка ви маєте сьогодні на виробництві. Після цього моїм улюбленим базовим зображенням є офіційний node: slim editions, а не node: alpine, що все ще добре, але, як правило, більше роботи для реалізації та має обмеження.

Одне з перших питань, які хтось задає, розміщуючи програму Node.js у Docker, це "З якого базового зображення слід запустити файл Node.js Docker?"

тонкий та альпійський набагато менші за зображення за замовчуванням. Існує безліч факторів, які впливають на це, але не робіть "розмір зображення" головним пріоритетом, якщо ви не маєте справу з IoT або вбудованими пристроями, де кожен МБ має значення. За останні роки тонкий образ зменшився до 150 МБ і працює найкраще в найширшому наборі сценаріїв. Alpine - це дуже мінімальний розподіл контейнерів, із найменшим зображенням вузла лише 75 МБ. Однак рівень спроб поміняти менеджери пакетів (apt на apk), розібратися з крайовими випадками та обійти обмеження сканування безпеки змушує мене зупинятися на рекомендації node: alpine для більшості випадків використання.

Приймаючи контейнерні технології, як і будь-що інше, ви хочете зробити все можливе, щоб зменшити швидкість змін. Стільки нових інструментів та процесів поставляється разом із контейнерами. Вибір базового зображення, до якого ви найбільше звикли розробники та операційна система, має багато несподіваних переваг, тому намагайтеся дотримуватися цього, коли це має сенс, навіть якщо це означає створення власного образу для CentOS, Ubuntu тощо.

Робота з вузловими модулями

TL; DR: Вам не потрібно переміщувати node_modules у ваші контейнери, якщо ви дотримуєтеся кількох правил для належного місцевого розвитку. Другий варіант - перемістити mode_modules вгору по каталогу у вашому Dockerfile, правильно налаштувати контейнер, і він надасть найбільш гнучкий варіант, але може не працювати з кожною структурою npm.

Ми всі звикли до світу, де ми не пишемо весь код, який запускаємо в програмі, а це означає, що маємо справу з залежностями середовища програми. Одне загальне питання - як боротися із залежностями коду в контейнерах, коли вони є підкаталогом нашого додатка. Локальні монтування прив’язки для розробки можуть по-різному впливати на ваш додаток, якщо ці залежності були розроблені для запуску на хост-ОС, а не на контейнерній ОС.

Ядро цієї проблеми для Node.js полягає в тому, що node_modules можуть містити двійкові файли, скомпільовані для вашої хост-ОС, і якщо вона відрізняється від ОС контейнера, ви отримаєте помилки при спробі запустити програму, коли ви прив'язуєте її до господар для розвитку. Зверніть увагу: якщо ви розробник чистого Linux і розробляєте на Linux x64 для Linux x64, ця проблема з монтуванням, як правило, не турбує.

Для Node.js я пропоную вам два підходи, які мають свої переваги та обмеження:

Рішення A: Будьте простими

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

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

  1. Розробити тільки через контейнер. Чому? По суті, ви не хочете змішувати node_modules на вашому хості з node_modules у контейнері. У macOS і Windows Docker Desktop прив'язує ваш код через бар'єр ОС, і це може спричинити проблеми з двійковими файлами, які ви встановили з npm для хост-ОС, які неможливо запустити в контейнерній ОС.
  2. Запустіть усі свої команди npm через docker-compose. Це означає, що початковою установкою npm для вашого проекту тепер слід виконати docker-compose run npm install .

Рішення B: Перемістіть контейнерні модулі та сховайте хост-модулі

Перемістіть node_modules вгору по шляху до файлу в Dockerfile, щоб ви могли розробляти Node.js в контейнері та поза ним, і залежності не будуть суперечити, і ви переключаєтеся між розробкою, що працює на хості та розробкою на основі Docker.

Оскільки Node.js розроблений для роботи на декількох ОС та архітектурах, можливо, вам не доведеться завжди розробляти в контейнерах. Якщо ви хочете, щоб гнучкість іноді розробляла/запускала ваш додаток Node.js безпосередньо на хості, а потім інший раз обертала його в локальному контейнері, тоді рішення B - це ваш джем.
У цьому випадку вам потрібні node_modules на хості, який побудований для цієї ОС, і інші node_modules у контейнері для Linux.

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

  1. Перемістіть node_modules вгору по каталогу на зображенні контейнера. Node.js завжди шукає node_modules як підкаталог, але якщо його немає, він буде проходити по шляху до каталогу, поки не знайде його. Приклад цього у Dockerfile тут.
  2. Щоб запобігти відображенню підкаталогу вузлів_модулів хосту в контейнері, використовуйте обхідний шлях, який я називаю «порожнім монтом прив'язки», щоб запобігти коли-небудь використанню вузлів_модулів хосту в контейнері. У вашому творі YAML це буде виглядати так.
  3. Це працює з більшістю коду Node.js, але деякі великі фреймворки та проекти, здається, жорстко кодують, припускаючи, що node_modules є підкаталогом, що виключає для вас це рішення.
Для обох цих рішень завжди пам’ятайте про додавання node_modules до вашого файлу .dockerignore (той самий синтаксис, що і .gitignore), щоб ви ніколи не випадково створювали свої зображення за допомогою модулів із хосту. Ви завжди хочете, щоб ваші збірки запускали установку npm всередині побудова образу.

Використовуйте користувача Node, використовуйте найменший привілей

Всі офіційні образи Node.js мають користувача Linux, доданий у вихідний образ, який називається node. Цей користувач не використовується за замовчуванням, це означає, що ваша програма Node.js за замовчуванням буде виконуватися як root у контейнері. Це не найгірше, оскільки він все ще ізольований від цього контейнера, але вам слід увімкнути всі проекти, де вам не потрібен Node для запуску від імені користувача. Просто додайте новий рядок у ваш вузол Dockerfile: USER

Ось декілька правил його використання:

  1. Місце розташування в Dockerfile має значення. Додайте USER після команд apt/yum/apk та, як правило, перед командами встановлення npm.
  2. Це не впливає на всі команди, як-от COPY, яка має власний синтаксис для керування власником файлів, в які ви копіюєте.
  3. Ви завжди можете повернутися до USER root, якщо вам потрібно. У більш складних файлах Docker це буде необхідно, як мій багатоступеневий приклад, що включає запуски тестів та сканування безпеки на факультативних етапах.
  4. Дозволи можуть стати хитрими під час розробки, оскільки тепер ви будете робити речі в контейнері як користувач, що не має права користувача, за замовчуванням. Спосіб часто обійти це - зробити такі речі, як npm install, сказавши Docker, що ви хочете запустити ці одноразові команди як root: docker-compose run -u root npm install


Не використовуйте менеджерів процесів у виробництві

TL; DR: За винятком локальної розробки, не обгортайте команди запуску вузла нічим. Не використовуйте npm, nodemon тощо. Нехай ваш CMD Dockerfile має щось на зразок [„node“, „file-to-start.js“], і вам буде легше керувати та замінювати контейнери.

Nodemon та інші "спостерігачі за файлами" необхідні при розробці, але одним великим виграшем для використання Docker у ваших програмах Node.js є те, що Docker бере на себе роботу того, що ми звикли використовувати pm2, nodemon, назавжди, і systemd для на серверах.

Docker, Swarm та Kubernetes виконають роботу із запуском перевірок стану здоров’я та перезапуском або відтворенням вашого контейнера, якщо він вийде з ладу. Зараз робота оркестрантів також полягає в масштабуванні кількості копій наших додатків, які ми використовували для використання таких інструментів, як pm2 і назавжди для. Пам’ятайте, що Node.js у більшості випадків все однопотоковий, тому навіть на одному сервері ви, швидше за все, захочете закрутити кілька реплік контейнерів, щоб скористатися перевагами декількох процесорів.

Мій приклад репозиторію показує, як використовувати вузол безпосередньо у вашому файлі Docker, а потім для локальної розробки або будувати використовувати інший етап зображення з docker build --target, або перевизначити CMD у вашому складеному YAML.

Запустити вузол безпосередньо у файлах Docker

TL; DR Я також не рекомендую використовувати npm для запуску своїх програм у вашому Dockerfile. Дозволь пояснити.

Я рекомендую викликати двійковий файл вузла безпосередньо, головним чином через "Проблему PID 1", де ви знайдете певну плутанину та дезінформацію в Інтернеті про те, як боротися з цим у програмах Node.js. Щоб усунути плутанину в блогосфері, вам не завжди потрібен інструмент «init», щоб сидіти між Docker та Node.js, і, мабуть, варто витратити більше часу на роздуми про те, як ваш додаток витончено зупиняється.

Node.js приймає та пересилає з ОС такі сигнали, як SIGINT та SIGTERM, що важливо для належного вимкнення програми. Node.js залишає за вашим додатком рішення, як обробляти ці сигнали, а це означає, що якщо ви не пишете код або не використовуєте модуль для їх обробки, ваш додаток не вимкнеться витончено. Він ігноруватиме ці сигнали, а потім буде знищений Docker або Kubernetes після періоду очікування (Docker за замовчуванням становить 10 секунд, Kubernetes - 30 секунд). Ви будете дбати про це набагато більше, коли у вас буде робоча програма HTTP, яка вам потрібно переконатись, що не просто розриває зв’язки, коли ви хочете оновити свої програми.

Використання інших програм для запуску Node.js для вас, наприклад npm, часто порушує цю сигналізацію. npm не передаватиме ці сигнали у ваш додаток, тому краще залишити його поза файлами Dockerfiles ENTRYPOINT та CMD. Це також має ту перевагу, що в контейнері працює один двійковий файл. Ще один бонус - це те, що ви можете побачити в Dockerfile точно що робитиме ваша програма під час запуску контейнера, а не перевіряти пакет.json на справжню команду запуску.

Для тих, хто знає про параметри init, такі як docker run --init або використання tini у вашому Dockerfile, це хороші варіанти резервного копіювання, коли ви не можете змінити код програми, але набагато кращим рішенням є написання коду для обробки належної обробки сигналів для витончені відключення. Два приклади - це шаблонний код, який я маю тут, і розглядаю такі модулі, як зупинити.

Це все?

Ні. Це проблеми, з якими стикається майже кожна команда Node.js, і є безліч інших міркувань, які поєднуються з цим. Такі теми, як багатоступеневі збірки, HTTP-проксі, продуктивність встановлення npm, перевірки працездатності, сканування CVE, ведення журналу контейнерів, тестування під час побудови зображень та налаштування компонентів док-компонентів мікросервісу - загальні запитання для моїх клієнтів та студентів Node.js.

Якщо вам потрібна додаткова інформація щодо цих тем, ви можете переглянути моє відео сесії DockerCon 2019 на цю тему або перевірити мої 8-годинні відео Docker для відео Node.js на https://www.bretfisher.com/node

Дякуємо за читання. Ви можете зв’язатися зі мною у Twitter, отримати щотижневі інформаційні бюлетені про DevOps та Docker, підписатись на мої щотижневі відеоролики на YouTube та Live Show, а також переглянути інші мої ресурси та курси Docker.