Як кешувати інструкцію з встановлення RUN npm, коли docker створює файл Docker


86

Зараз я розробляю серверний сервер Node для моєї програми. При докеризації ( docker build .) найдовшою фазою є RUN npm install. У RUN npm installпробіги інструкції на кожному трохи зміниться коду сервера, продуктивність яких гальмує за рахунок збільшення часу збирання.

Я виявив, що запуск npm install там, де живе код програми, і додавання node_modules до контейнера за допомогою інструкції ADD вирішує цю проблему, але це далеко не найкраща практика. Це як би ламає всю ідею докеризації, і це змушує контейнер важити набагато більше.

Будь-які інші рішення?

Відповіді:


124

Гаразд, я знайшов цю чудову статтю про ефективність написання файлу докера.

Це приклад файлу поганого докера, який додає код програми перед запуском RUN npm installінструкції:

FROM ubuntu

RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

WORKDIR /opt/app

COPY . /opt/app
RUN npm install
EXPOSE 3001

CMD ["node", "server.js"]

Поділивши копію програми на 2 інструкції КОПІЮВАННЯ (одну для файлу package.json, а іншу для решти файлів) та запустивши інструкцію встановлення npm перед додаванням фактичного коду, будь-яка зміна коду не ініціює RUN npm install інструкція, лише зміни пакета package.json ініціюють його. Файл докера для кращої практики:

FROM ubuntu
MAINTAINER David Weinstein <david@bitjudo.com>

# install our dependencies and nodejs
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

# use changes to package.json to force Docker not to use the cache
# when we change our application's nodejs dependencies:
COPY package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

# From here we load our application's code in, therefore the previous docker
# "layer" thats been cached will be used if possible
WORKDIR /opt/app
COPY . /opt/app

EXPOSE 3000

CMD ["node", "server.js"]

Тут додано файл package.json, встановлено його залежності та скопійовано в контейнер WORKDIR, де живе програма:

ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

Щоб уникнути етапу встановлення npm на кожній збірці докера, просто скопіюйте ці рядки та змініть ^ / opt / app ^ на місце, де живе ваша програма всередині контейнера.


2
Це працює. Хоча деякі моменти. ADDзнеохочується на користь COPY, afaik. COPYще ефективніше. IMO, останні два абзаци не є необхідними, оскільки вони є дублікатами, а також з точки зору програми не має значення, де у файловій системі живе програма, доки WORKDIRвстановлено.
eljefedelrodeodeljefe

2
Краще - це поєднати всі команди apt-get на одному RUN, включаючи apt-get clean. Крім того, додайте ./node_modules у ваш .dockerignore, щоб уникнути копіювання вашого робочого каталогу у вбудований контейнер та пришвидшити етап копіювання контексту збірки збірки.
Симетричний

1
Той самий підхід, але просто додавання package.jsonдо остаточного положення спокою також добре працює (усуваючи будь-які cp / mv).
Дж. Фріц Барнс,

26
Я не розумію. Чому ви встановлюєте в тимчасовий каталог, а потім переміщуєте його в каталог додатків? Чому б просто не встановити в каталог додатків? Чого мені тут не вистачає?
joniba

1
Це, мабуть, мертво, але я думав, що згадаю це для майбутніх читачів. @joniba однією з причин цього було б встановити тимчасову папку як постійний том у компонуванні, не втручаючись у модуль node_modules локальної файлової системи. Тобто, можливо, я захочу запустити свій додаток локально, але також у контейнері, і при цьому зберегти можливість постійно перезавантажувати мої node_modules при зміні package.json
dancypants

40

Дивно! Ніхто не згадує багатоступеневу збірку .

# ---- Base Node ----
FROM alpine:3.5 AS base
# install node
RUN apk add --no-cache nodejs-current tini
# set working directory
WORKDIR /root/chat
# Set tini as entrypoint
ENTRYPOINT ["/sbin/tini", "--"]
# copy project file
COPY package.json .

#
# ---- Dependencies ----
FROM base AS dependencies
# install node packages
RUN npm set progress=false && npm config set depth 0
RUN npm install --only=production 
# copy production node_modules aside
RUN cp -R node_modules prod_node_modules
# install ALL node_modules, including 'devDependencies'
RUN npm install

#
# ---- Test ----
# run linters, setup and tests
FROM dependencies AS test
COPY . .
RUN  npm run lint && npm run setup && npm run test

#
# ---- Release ----
FROM base AS release
# copy production node_modules
COPY --from=dependencies /root/chat/prod_node_modules ./node_modules
# copy app sources
COPY . .
# expose port and define CMD
EXPOSE 5000
CMD npm run start

Чудова робота тут: https://codefresh.io/docker-tutorial/node_docker_multistage/


2
Що там із COPYзаявою після ENTRYPOINT?
лінд

Чудово, це також має хорошу перевагу під час тестування файлу Docker без перевстановлення залежностей кожного разу, коли ви редагуєте файл Docker
Xavier Brassoud

29

Я виявив, що найпростіший підхід полягає у використанні семантики копіювання Докера:

Інструкція COPY копіює нові файли або каталоги та додає їх до файлової системи контейнера на шляху.

Це означає, що якщо ви спочатку явно скопіюєте package.jsonфайл, а потім виконаєте npm installкрок, який можна буде кешувати, а потім ви зможете скопіювати решту вихідного каталогу. Якщо package.jsonфайл змінився, це буде новим, і він повторно запустить кешування встановлення npm, яке буде призначено для майбутніх збірок.

Фрагмент з кінця файлу Docker виглядатиме так:

# install node modules
WORKDIR  /usr/app
COPY     package.json /usr/app/package.json
RUN      npm install

# install application
COPY     . /usr/app

5
Замість cd /usr/appви можете / повинні використовувати WORKDIR /usr/app.
Володимир

1
@VladimirVukanac: +1: про використання WORKDIR; Я оновив відповідь вище, щоб врахувати це.
Дж. Фріц Барнс

npm встановити, запустити в каталозі / usr / app або. ?
user557657

1
@ user557657 WORKDIR встановлює каталог у майбутньому зображенні, з якого буде виконуватися команда. Отже, у цьому випадку він запускає npm install із /usr/appзображення, що створить /usr/app/node_modulesзалежність, встановлену з npm install.
Дж. Фріц Барнс

1
@ J.FritzBarnes велике спасибі. хіба COPY . /usr/appне скопіює package.jsonфайл знову /usr/appразом з рештою файлів?
user557657

3

Я думаю, ви, можливо, вже знаєте, але ви можете включити файл .dockerignore в ту ж папку, що містить

node_modules
npm-debug.log

щоб уникнути здуття зображення при натисканні на концентратор докера


1

вам не потрібно використовувати папку tmp, просто скопіюйте package.json у папку програми вашого контейнера, виконайте деякі роботи з встановлення та скопіюйте всі файли пізніше.

COPY app/package.json /opt/app/package.json
RUN cd /opt/app && npm install
COPY app /opt/app

отже, ви виконуєте встановлення npm у каталозі контейнера / opt / app, а потім копіюєте всі файли з локальної машини в / opt / app?
user557657
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.