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?

ohadgk
źródło

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 <[email protected]>

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

ohadgk
źródło
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/

Abdennour TOUMI
źródło
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
J. Fritz Barnes
źródło
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

usrrname
źródło
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
Mike Zhang
źródło
więc wykonujesz instalację npm w katalogu / opt / app, a następnie kopiujesz wszystkie pliki z komputera lokalnego do / opt / app?
user557657