Jak buforować instrukcję instalacji RUN npm, gdy docker buduje plik Dockerfile


86

Obecnie opracowuję zaplecze Node dla mojej aplikacji. Podczas dokowania ( docker build .) najdłuższą fazą jest RUN npm install. W RUN npm installinstrukcji działa na każdej małej zmiany kodu serwera, która utrudnia produktywność poprzez zwiększenie czasu kompilacji.

Okazało się, że uruchomienie instalacji npm, w której znajduje się kod aplikacji, i dodanie node_modules do kontenera za pomocą instrukcji ADD rozwiązuje ten problem, ale jest to dalekie od najlepszej praktyki. To w pewnym sensie łamie cały pomysł dokeryzacji i powoduje, że kontener waży znacznie więcej.

Jakieś inne rozwiązania?

Odpowiedzi:


124

Ok, więc znalazłem ten świetny artykuł o wydajności podczas pisania pliku dockera.

Oto przykład złego pliku dockera, który dodaje kod aplikacji przed uruchomieniem RUN npm installinstrukcji:

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"]

Dzieląc kopię aplikacji na 2 instrukcje COPY (jedną dla pliku package.json, a drugą dla pozostałych plików) i uruchamiając instrukcję instalacji npm przed dodaniem właściwego kodu, żadna zmiana kodu nie wywoła instalacji RUN npm instrukcji, tylko zmiany w pliku package.json będą ją wywoływać. Lepsze praktyki w pliku dockera:

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"]

W tym miejscu dodano plik package.json, zainstaluj jego zależności i skopiuj je do kontenera WORKDIR, w którym znajduje się aplikacja:

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

Aby uniknąć fazy instalacji npm w każdej kompilacji platformy Docker, po prostu skopiuj te wiersze i zmień ^ / opt / app ^ na lokalizację, w której znajduje się Twoja aplikacja w kontenerze.


2
To działa. Jednak kilka punktów. ADDjest odradzany na korzyść COPY, afaik. COPYjest jeszcze bardziej skuteczny. IMO, ostatnie dwa akapity nie są konieczne, ponieważ są duplikatami, a także z punktu widzenia aplikacji nie ma znaczenia, gdzie w systemie plików znajduje się aplikacja, o ile WORKDIRjest ustawiona.
— eljefedelrodeodeljefe

2
Jeszcze lepiej jest połączyć wszystkie polecenia apt-get w jedną RUN, łącznie z plikiem apt-get clean. Dodaj również ./node_modules do swojego .dockerignore, aby uniknąć kopiowania katalogu roboczego do zbudowanego kontenera i przyspieszyć krok tworzenia kopii kontekstowej kompilacji.
— Symmetric

1
To samo podejście, ale samo dodanie package.jsondo końcowej pozycji spoczynkowej również działa dobrze (eliminując wszelkie cp / mv).
— J. Fritz Barnes

27
Nie rozumiem. Dlaczego instalujesz w katalogu tymczasowym, a następnie przenosisz go do katalogu aplikacji? Dlaczego po prostu nie zainstalować w katalogu aplikacji? Czego tu brakuje?
— joniba

1
To prawdopodobnie nie żyje, ale pomyślałem, że wspomnę o tym dla przyszłych czytelników. @joniba jednym z powodów, aby to zrobić, byłoby zamontowanie folderu tymczasowego jako utrwalonego woluminu podczas tworzenia wiadomości bez ingerencji w node_modules lokalnego systemu plików hosta. Czyli ja chcieć uruchomić mój app lokalnie, ale również w pojemniku i nadal zachować zdolność do moich node_modules nie stale re-pobranych podczas zmiany package.json
— dancypants

41

Dziwne! Nikt nie wspomina o budowie wieloetapowej .

# ---- 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

Niesamowite tuto tutaj: https://codefresh.io/docker-tutorial/node_docker_multistage/


2
O co chodzi z otrzymaniem COPYoświadczenia po ENTRYPOINT?
— lindhe

Świetnie, to zapewnia również dużą przewagę podczas testowania pliku Dockerfile bez ponownej instalacji za każdym razem, gdy edytujesz plik Dockerfile
— Xavier Brassoud

30

Odkryłem, że najprostszym podejściem jest wykorzystanie semantyki kopiowania Dockera:

Instrukcja COPY kopiuje nowe pliki lub katalogi z i dodaje je do systemu plików kontenera w ścieżce.

Oznacza to, że jeśli najpierw jawnie skopiujesz package.jsonplik, a następnie uruchomisz npm installkrok, aby można go było buforować, a następnie możesz skopiować resztę katalogu źródłowego. Jeśli package.jsonplik się zmienił, to będzie nowy i ponownie uruchomi buforowanie instalacji npm, które będzie używane w przyszłych kompilacjach.

Fragment z końca pliku Dockerfile wyglądałby tak:

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

# install application
COPY     . /usr/app

6
Zamiast tego cd /usr/appmożesz / powinieneś użyć WORKDIR /usr/app.
— Vladimir Vukanac

1
@VladimirVukanac: +1: przy używaniu WORKDIR; Zaktualizowałem powyższą odpowiedź, aby to uwzględnić.
— J. Fritz Barnes

czy npm install działa w katalogu / usr / app lub. ?
— user557657

1
@ user557657 WORKDIR ustawia katalog w przyszłym obrazie, z którego zostanie uruchomiona komenda. Więc w tym przypadku uruchamia instalację npm z /usr/appobrazu, która utworzy /usr/app/node_modulesz zależnościami zainstalowanymi z instalacji npm.
— J. Fritz Barnes

1
@ J.FritzBarnes wielkie dzięki. czy nie COPY . /usr/appskopiuje package.jsonponownie pliku razem /usr/appz pozostałymi plikami?
— user557657

3

Wyobrażam sobie, że możesz już wiedzieć, ale możesz dołączyć plik .dockerignore do tego samego folderu zawierającego

node_modules
npm-debug.log

aby uniknąć rozdęcia obrazu podczas wypychania do centrum Docker


1

nie musisz używać folderu tmp, po prostu skopiuj plik package.json do folderu aplikacji swojego kontenera, przeprowadź instalację i skopiuj wszystkie pliki później.

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

więc wykonujesz instalację npm w katalogu / opt / app, a następnie kopiujesz wszystkie pliki z komputera lokalnego do / opt / app?
— user557657
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.