วิธีแคชคำแนะนำในการติดตั้ง RUN npm เมื่อนักเทียบท่าสร้าง Dockerfile


86

ฉันกำลังพัฒนาแบ็กเอนด์โหนดสำหรับแอปพลิเคชันของฉัน เมื่อเทียบท่า ( docker build .) ระยะที่ยาวที่สุดคือRUN npm install. RUN npm installการเรียนการสอนการทำงานเกี่ยวกับการเปลี่ยนแปลงรหัสเซิร์ฟเวอร์ขนาดเล็กทุกที่ขัดขวางการผลิตผ่านการเพิ่มขึ้นเวลาในการสร้าง

ฉันพบว่าการรัน npm ติดตั้งโดยที่โค้ดของแอปพลิเคชันอยู่และการเพิ่ม node_modules ลงในคอนเทนเนอร์ด้วยคำสั่ง ADD ช่วยแก้ปัญหานี้ได้ แต่ก็ยังห่างไกลจากแนวทางปฏิบัติที่ดีที่สุด มันเป็นการทำลายความคิดทั้งหมดของการเทียบท่าและทำให้คอนเทนเนอร์มีน้ำหนักมากขึ้น

วิธีแก้ปัญหาอื่น ๆ ?

คำตอบ:


125

โอเคฉันพบบทความดีๆเกี่ยวกับประสิทธิภาพเมื่อเขียนไฟล์นักเทียบท่า

นี่คือตัวอย่างของไฟล์นักเทียบท่าที่ไม่ดีที่เพิ่มรหัสแอปพลิเคชันก่อนรัน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 คำสั่ง COPY (คำสั่งหนึ่งสำหรับไฟล์ package.json และอีกคำสั่งสำหรับไฟล์ที่เหลือ) และเรียกใช้คำแนะนำในการติดตั้ง npm ก่อนที่จะเพิ่มรหัสจริงการเปลี่ยนแปลงโค้ดใด ๆ จะไม่ทำให้เกิดการติดตั้ง RUN npm คำแนะนำเฉพาะการเปลี่ยนแปลงของ 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ท้อแท้ในความโปรดปรานสำหรับCOPYafaik COPYมีประสิทธิภาพมากยิ่งขึ้น IMO ไม่จำเป็นต้องใช้สองย่อหน้าสุดท้ายเนื่องจากซ้ำกันและจากมุมมองของแอปไม่สำคัญว่าแอปจะอยู่ที่ใดในระบบไฟล์ตราบเท่าที่WORKDIRตั้งค่าไว้
eljefedelrodeodeljefe

2
ยังดีกว่าคือการรวมคำสั่ง apt-get ทั้งหมดไว้ใน RUN เดียวรวมถึงapt-get cleanไฟล์. นอกจากนี้ให้เพิ่ม. /node_modules ลงใน. dockerignore ของคุณเพื่อหลีกเลี่ยงการคัดลอกไดเร็กทอรีการทำงานของคุณไปยังคอนเทนเนอร์ที่สร้างขึ้นของคุณและเพื่อเพิ่มความเร็วในขั้นตอนการคัดลอกบริบทของบิลด์
สมมาตร

1
แนวทางเดียวกัน แต่เพียงแค่เพิ่มpackage.jsonไปยังตำแหน่งพักสุดท้ายก็ใช้ได้ดีเช่นกัน (กำจัด cp / mv ใด ๆ )
J. Fritz Barnes

27
ฉันไม่เข้าใจ เหตุใดคุณจึงติดตั้งในไดเรกทอรีชั่วคราวแล้วย้ายไปยังไดเรกทอรีแอป ทำไมไม่ติดตั้งในไดเรกทอรีแอป ฉันขาดอะไรไปที่นี่?
joniba

1
นี่อาจจะตายไปแล้ว แต่คิดว่าฉันพูดถึงมันสำหรับผู้อ่านในอนาคต @joniba เหตุผลหนึ่งในการทำเช่นนี้คือการเมานต์โฟลเดอร์ temp เป็นโวลุ่มที่คงอยู่ในการเขียนโดยไม่รบกวนการทำงานของ node_modules ของระบบไฟล์โฮสต์ภายใน คือฉันอาจต้องการเรียกใช้แอปของฉันในเครื่อง แต่ยังอยู่ในคอนเทนเนอร์และยังคงรักษาความสามารถในการไม่ให้ node_modules ของฉันดาวน์โหลดซ้ำอย่างต่อเนื่องเมื่อ package.json มีการเปลี่ยนแปลง
dancypants

41

แปลก! ไม่มีใครกล่าวถึงหลายขั้นตอนการสร้าง

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

tuto น่ากลัวที่นี่: https://codefresh.io/docker-tutorial/node_docker_multistage/


2
เกิดอะไรขึ้นกับการมีCOPYคำสั่งหลังจากนั้นENTRYPOINT?
lindhe

เยี่ยมมากที่ให้ข้อได้เปรียบที่ดีเช่นกันเมื่อคุณทดสอบ Dockerfile โดยไม่ต้องติดตั้งการอ้างอิงใหม่ทุกครั้งที่คุณแก้ไข Dockerfile
Xavier Brassoud

31

ฉันพบว่าวิธีที่ง่ายที่สุดคือการใช้ประโยชน์จากความหมายการคัดลอกของ Docker:

คำสั่ง COPY คัดลอกไฟล์หรือไดเร็กทอรีใหม่จากและเพิ่มลงในระบบไฟล์ของคอนเทนเนอร์ที่พา ธ

ซึ่งหมายความว่าหากคุณคัดลอกpackage.jsonไฟล์อย่างชัดเจนก่อนแล้วจึงรันnpm installขั้นตอนที่สามารถแคชได้จากนั้นคุณสามารถคัดลอกส่วนที่เหลือของไดเร็กทอรีต้นทางได้ หากpackage.jsonไฟล์มีการเปลี่ยนแปลงไฟล์นั้นจะเป็นไฟล์ใหม่และจะรันการแคชการติดตั้ง npm อีกครั้งสำหรับการสร้างในอนาคต

ตัวอย่างข้อมูลจากส่วนท้ายของ Dockerfile จะมีลักษณะดังนี้:

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

# install application
COPY     . /usr/app

7
แทนคุณcd /usr/appสามารถ / ควรใช้WORKDIR /usr/app.
Vladimir Vukanac

1
@VladimirVukanac: +1: ในการใช้ WORKDIR; ฉันได้อัปเดตคำตอบข้างต้นเพื่อนำไปพิจารณา
J. Fritz Barnes

1
@ user557657 WORKDIR ตั้งค่าไดเร็กทอรีภายในอิมเมจในอนาคตซึ่งจะเรียกใช้คำสั่ง ดังนั้นในกรณีนี้จะเรียกใช้การติดตั้ง npm จาก/usr/appภายในอิมเมจซึ่งจะสร้างการ/usr/app/node_modulesอ้างอิงที่ติดตั้งจากการติดตั้ง npm
J. Fritz Barnes

1
@ J.FritzBarnes ขอบคุณมาก COPY . /usr/appจะไม่คัดลอกpackage.jsonไฟล์อีกครั้ง/usr/appกับไฟล์ที่เหลือ?
user557657

1
นักเทียบท่าจะไม่รันnpm installคำสั่งซ้ำหากมีpackage.jsonการเปลี่ยนแปลงมันจะแคชผลลัพธ์ของคำสั่ง RUN และถือว่าคำสั่ง RUN เดียวกันให้ผลลัพธ์เดียวกัน ในการทำให้แคชไม่ถูกต้องคุณควรรันdocker buildด้วยแฟล็ก --no-cache หรือเปลี่ยนคำสั่ง RUN
มิคาอิล Zhuravlev

3

ฉันคิดว่าคุณอาจรู้อยู่แล้ว แต่คุณสามารถรวมไฟล์. dockerignore ไว้ในโฟลเดอร์เดียวกันที่มีไฟล์

node_modules
npm-debug.log

เพื่อหลีกเลี่ยงไม่ให้ภาพของคุณบวมเมื่อคุณดันไปที่ Docker Hub


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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.