Длительное время сборки Docker с npm global install angular-cli

#node.js #docker #angular-cli #alpine

#node.js #docker #angular-cli #alpine-linux

Вопрос:

Я столкнулся с очень странным сценарием, и мне было интересно, смог ли кто-нибудь пролить некоторый свет на ситуацию. Короче говоря, docker build npm install -g angular-cli@1.0.0-beta.16 сборка вместе с остальными шагами приложения занимает целую вечность. docker build только с этим в собственном образе, а затем docker build с остальной частью приложения FROM , которое является первым изображением, вместе или по отдельности, они не занимают так много времени. Когда я говорю долго, я имею в виду около 2 часов. На сборку у отдельных пользователей уходит от 5 до 7 минут.

Итак, прежде чем я изложу весь код, несколько замечаний. Я пробовал это на OS X 10.10.5, OS X 10.11.X, OS X 10.12.X, Arch Linux 4.5.1-1-ARCH, Ubuntu 14.04 LTS (в vagrant box и в AWS), а также некоторые друзья помогли мне с этим на своих машинах. Все те же результаты. Я запускаю Docker 1.12.1. и я создаю FROM alpine:3.4 . Версия node is v6.2.0 и npm 3.8.9 (из пакетов alpine). Я также пробовал это с изображением, которое я создал для nodejs v5.11.1 и npm 3 из исходного кода.

 FROM alpine:3.4
MAINTAINER First Lastname <user@example.com>

ARG ENV

#Set environment vars
ENV HOME=/home 
    APP_DIR=/root/app 
    DIST_DIR=/var/www 
    ENV=${ENV} 
    AWS_REGION=us-east-1 
    NPM_CONFIG_LOGLEVEL=info 
    LANG=en_US.UTF-8 
    LC_ALL=C.UTF-8 
    LANGUAGE=en_US.UTF-8

#Install runtime packages
RUN apk --no-cache add 
          ca-certificates 
          nodejs 
          nginx

#Install build time packages
RUN apk --no-cache add 
        --virtual build-dependencies 
          busybox 
          build-base 
          bzip2 
          git 
          python-dev 
          libffi-dev

RUN mkdir -p ${APP_DIR}
WORKDIR ${APP_DIR}
COPY . ${APP_DIR}

#Bug https://github.com/npm/npm/issues/9863
RUN cd $(npm root -g)/npm amp;amp; 
    npm install fs-extra amp;amp; 
    sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js amp;amp; 
    rm -fr ${APP_DIR}/node_modules

#install node packages
RUN npm install -g angular-cli@1.0.0-beta.16 amp;amp; 
    npm install -g typescript@2.0.3 amp;amp; 
    npm install amp;amp; npm install -g --save process-nextick-args amp;amp; npm cache clean | tee /tmp/npm-install.log

#build project
RUN ng build  --environment=${ENV}
RUN mv dist ${DIST_DIR}

#clean up
RUN rm -fr ${APP_DIR}
RUN apk del build-dependencies

#nginx config
COPY ./nginx/nginx.conf /etc/nginx/
RUN mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled/ amp;amp; 
    chmod -R 755 /etc/nginx/sites-*
RUN ln -sf /dev/stdout /var/log/nginx/access.log amp;amp; 
    ln -sf /dev/stderr /var/log/nginx/error.log
COPY ./nginx/account-curation.conf /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/account-curation.conf /etc/nginx/sites-enabled/

#app port
EXPOSE 80 81

#start nginx
ENTRYPOINT [ "nginx" ]
  

И package.json выглядит так

 {
  "name": "app-name",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "ng serve",
    "lint": "tslint "src/**/*.ts"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular2-material/button": "2.0.0-alpha.8-2",
    "@angular2-material/card": "2.0.0-alpha.8-2",
    "@angular2-material/checkbox": "2.0.0-alpha.8-2",
    "@angular2-material/core": "2.0.0-alpha.8-2",
    "@angular2-material/grid-list": "2.0.0-alpha.8-2",
    "@angular2-material/icon": "2.0.0-alpha.8-2",
    "@angular2-material/input": "2.0.0-alpha.8-2",
    "@angular2-material/input": "2.0.0-alpha.8-2",
    "@angular2-material/list": "2.0.0-alpha.8-2",
    "@angular2-material/progress-circle": "2.0.0-alpha.8-2",
    "@angular2-material/tabs": "2.0.0-alpha.8-2",
    "@angular2-material/toolbar": "2.0.0-alpha.8-2",
    "angular2-modal": "2.0.0-beta.13",
    "core-js": "2.4.1",
    "es6-shim": "0.35.1",
    "hammerjs": "2.0.8",
    "jquery": "3.1.0",
    "jstree": "3.3.1",
    "localStorage": "1.0.3",
    "rxjs": "5.0.0-beta.12",
    "ts-helpers": "1.1.1",
    "zone.js": "0.6.25"
  },
  "devDependencies": {
    "@types/hammerjs": "^2.0.33",
    "@types/jasmine": "^2.2.30",
    "@types/jquery": "^2.0.32",
    "@types/jstree": "^3.3.32",
    "angular-cli": "1.0.0-beta.16",
    "codelyzer": "0.0.26",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "1.2.0",
    "karma-chrome-launcher": "2.0.0",
    "karma-cli": "1.0.1",
    "karma-jasmine": "1.0.2",
    "karma-remap-istanbul": "0.2.1",
    "protractor": "4.0.9",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "2.0.3"
  }
}
  

Я указываю на это, потому что в нем уже есть все пакеты, которые я пытаюсь установить глобально (та же версия), но когда это устанавливается, это происходит намного быстрее. Таким образом, сборка занимает около 2 часов. Безумие, верно? Итак, после нескольких дней дурачиться с этим, я не могу понять, почему. Я решил, что в свете времени я создам базовый образ с глобальными установками, а затем создам образ проекта FROM . Таким образом, задание сборки CI не будет занимать несколько часов при каждом нажатии на эту ветку репозиториев. Но когда я это сделал, это волшебным образом стало быстрее. Около 7 минут для базового образа и 5 для образа приложения. Это выглядело примерно так.

Base Image:

 FROM alpine:3.4
MAINTAINER First Lastname <user@example.com>

#Set environment vars
ENV NPM_CONFIG_LOGLEVEL=info 
    LANG=en_US.UTF-8 
    LC_ALL=C.UTF-8 
    LANGUAGE=en_US.UTF-8

#Install runtime packages
RUN apk --no-cache add 
          ca-certificates 
          nodejs

#Install build time packages
RUN apk --no-cache add 
        --virtual build-dependencies 
          busybox 
          build-base 
          bzip2 
          git 
          python-dev 
          libffi-dev

#Bug https://github.com/npm/npm/issues/9863
RUN cd $(npm root -g)/npm amp;amp; 
    npm install fs-extra amp;amp; 
    sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js

#install node packages
RUN npm install -g angular-cli@1.0.0-beta.16 amp;amp; 
    npm install -g typescript@2.0.3 amp;amp; 
    npm install -g --save process-nextick-args amp;amp; npm cache clean | tee /tmp/npm-global-install.log

RUN apk del build-dependencies

ENTRYPOINT [ "/bin/ash" ]
  

Изображение приложения

 FROM company/application_base:latest
MAINTAINER First Lastname <user@example.com>

ARG ENV

#Set environment vars
ENV APP_DIR=/root/app 
    DIST_DIR=/var/www 
    ENV=${ENV} 
    AWS_REGION=us-east-1 
    NPM_CONFIG_LOGLEVEL=info 
    LANG=en_US.UTF-8 
    LC_ALL=C.UTF-8 
    LANGUAGE=en_US.UTF-8

#Install runtime packages
RUN apk --no-cache add 
          ca-certificates 
          nginx

#Install build time packages
RUN apk --no-cache add 
        --virtual build-dependencies 
          busybox 
          build-base 
          bzip2 
          git 
          python-dev 
          libffi-dev

RUN mkdir -p ${APP_DIR}
COPY . ${APP_DIR}
WORKDIR ${APP_DIR}

#install node packages
RUN npm install amp;amp; npm cache clean | tee /tmp/npm-install.log

#build project
RUN ng build amp;amp; mv dist ${DIST_DIR}

#clean up
RUN rm -fr ${APP_DIR} amp;amp; apk del build-dependencies

#nginx config
COPY ./nginx/nginx.conf /etc/nginx/
RUN mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled/ amp;amp; 
    chmod -R 755 /etc/nginx/sites-* amp;amp; 
    ln -sf /dev/stdout /var/log/nginx/access.log amp;amp; 
    ln -sf /dev/stderr /var/log/nginx/error.log
COPY ./nginx/account-curation.conf /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/account-curation.conf /etc/nginx/sites-enabled/

#app port
EXPOSE 80 81

#start nginx
ENTRYPOINT [ "nginx" ]
  

Я не знаю nodejs этого хорошо, поэтому я пытался избежать установки angular-cli дважды, был npm install один из package.json , затем mv он из /path/to/app/node_modules/.bin/ng /usr/bin , также просто добавляя /path/to/app/node_modules/.bin к моему пути, оба привели ng not found к.

Не уверен, что это связано, но я назову это кем угодно, я продолжаю получать эту раздражающую проблему с разрешениями npm gyp только для глобально установленных пакетов. gyp WARN EACCES user "nobody" does not have permission to access the dev dir Они просто предупреждения, но я подумал, что погуглю и посмотрю, о чем это. Просто чтобы исключить возможности, я попробовал предложенные подходы к исправлению этого из таких сообщений, как https://docs.npmjs.com/getting-started/fixing-npm-permissions и https://github.com/nodejs/node-gyp/issues/454 Ничто из того, что я мог найти, не работало.

Что-то еще, что я пробовал, это возиться с объемом оперативной памяти и процессора, которые может использовать docker. например docker build --build-arg ENV=dev --cpuset-cpus "0-5" --no-cache -t company/app_name:0.1.0 . .

Я действительно не хочу поддерживать два Dockerfile , особенно когда я чувствую, что мне не хватает чего-то действительно глупого. Потому что я знаю, что они работают намного лучше, когда они разделены. Что, черт возьми, может привести к тому, что это увеличит время сборки на столько, когда все это вместе.

Ответ №1:

Сборка образа «все» занимает много времени из-за проблем с execSync node-zopfli их собственными модулями и очень медленной сборкой. Оба являются необязательными зависимостями, поэтому установка выдерживает, когда они не работают, но для сбоя требуется много времени.

Из-за разделенной сборки образа эти два пакета не могут быть собраны быстро. Я действительно не уверен, как это произошло, хотя, по-видимому, существует ряд изменений, которые могут привести к тому, что они не смогут быстро построить.

execAsync

Модуль execSync больше не должен существовать, он предназначен для узла 0.10. Если я удалю npm исправление ошибки из Dockerfile , сборка execAsync завершится немедленно, а не займет много времени для сбоя.

node-zopfli

Чтобы устранить проблемы с разрешениями, используйте npm install --unsafe-perm , чтобы разрешить запуск сборки root от имени пользователя в контейнере.

devDependencies

Используйте либо devDependencies глобальные установки, либо глобальные установки для вашей ng сборки, а не обе. An npm install --production удалит devDependencies из установки приложения, что означает angular-cli , что оно больше не дублируется. Если вам нужно больше devDependencies для завершения сборки приложения, тогда вы можете пойти другим путем и не выполнять глобальные установки, а полагаться только на devDepenencies ( ./node_modules/.bin/ng )

Другие примечания

Используйте FROM mhart/alpine-node:6 для получения последней версии node / npm и не устанавливайте nodejs через apk .

Установите ARG ENV и ENV ENV как можно позже (т. Е. Непосредственно перед ng командой, которая его использует), чтобы изменение среды не вызвало полную перестройку образа.

При выполнении шагов очистки в более поздней команде, например, нет размера изображения, который можно было бы увеличить (или потерять на самом деле) Dockerfile RUN apk del build-dependencies . К этому времени файлы уже привязаны к предыдущему слою изображения.

При выполнении повторных сборок образа docker используйте что-то вроде verdacio / npm-register для npm и / или apt-cacher-ng для общих пакетов ОС. Они устранят большую часть сетевых издержек для повторных сборок docker.

Dockerfile

Итак, в итоге получается что-то вроде этого:

 FROM mhart/alpine-node:6.7
MAINTAINER First Lastname <user@example.com>

# Set environment vars
ENV HOME=/root 
    APP_DIR=/root/app 
    DIST_DIR=/var/www 
    AWS_REGION=us-east-1 
    NPM_CONFIG_LOGLEVEL=info 
    LANG=en_US.UTF-8 
    LC_ALL=C.UTF-8 
    LANGUAGE=en_US.UTF-8

# Install packages
RUN apk --no-cache add 
        --virtual build-dependencies 
        ca-certificates 
        nginx 
        build-base 
        bzip2 
        git 
        python-dev 
        libffi-dev

RUN mkdir -p ${APP_DIR}
WORKDIR ${APP_DIR}
COPY . ${APP_DIR}

# Install node packages
RUN set -uex ;
    npm install -g angular-cli@1.0.0-beta.16 typescript@2.0.3 process-nextick-args ;
    npm install --production --unsafe-perm ;
    npm cache clean

ARG ENV
ENV ENV=${ENV}

# Build project
RUN set -uex ;
    ng build  --environment=${ENV} ;
    mv dist ${DIST_DIR} ;
    rm -fr ${APP_DIR}

# nginx config
COPY ./nginx/nginx.conf /etc/nginx/
RUN set -uex ;
    mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled/ ;
    chmod -R 755 /etc/nginx/sites-* ;
    ln -sf /dev/stdout /var/log/nginx/access.log ;
    ln -sf /dev/stderr /var/log/nginx/error.log
COPY ./nginx/account-curation.conf /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/account-curation.conf /etc/nginx/sites-enabled/

# App port
EXPOSE 80 81

# Start nginx
ENTRYPOINT [ "nginx" ]
  

Также нет проблем с наличием двух файлов Dockerfile. Поскольку каждый должен делать свое дело, а не дублировать действия других. Обычно это нужно делать только тогда, когда у вас есть несколько образов для сборки из одного базового образа.

Ответ №2:

Стоит отметить, что это может быть связано с добавлением этого также перед этапами установки npm:

 RUN npm config set maxsockets 10 
  

В более старых версиях npm это бесконечность, в более новых — 50.
Я получил огромный прирост скорости, когда сам явно установил для него значение 10 (смутно помню кого-то, кто сказал, что 25 для него хорошо).